Domain / @fjell/lib

The full domain layer for related Items.

Core defines the operation contract. Registry finds the right instances. Lib turns those pieces into application-facing libraries with validation, hooks, finders, actions, facets, and primary/contained behavior for the User graph.

What Lib adds to Core.

Lib is where generic Item operations become domain behavior. It wraps the lower-level operations and gives your application one coherent place to say what a User, Address, Company, Team, or Membership is allowed to do.

Operations

CRUD and queries with policy

Expose create, get, update, remove, find, and all operations through a typed library instance.

Validators

Guard domain transitions

Reject an invalid User update, an Address without required fields, or a Membership with an unsupported role.

Hooks

Shape the lifecycle

Normalize names before create, enrich an Address, emit an audit event, or enforce a policy around updates.

Finders, facets, actions

Go beyond CRUD

Ask for Alex's manager memberships, find Users by Company, or expose a deliberate domain action instead of leaking storage queries.

One request through Lib.

HTTP
GET /users/u-42
Registry
find [user]
Lib
hooks + policy
Adapter
persist/read
User
Alex Morgan
The same route can reach a User PItem or an Address CItem; the coordinate and parent location travel with the operation.

Primary and contained libraries.

const users = createLibrary(
  registry,
  userCoordinate,       // ["user"]
  userOperations,
  {
    validators: {
      onCreate: async user =>
        !!user.firstName && !!user.lastName,
    },
    hooks: {
      preCreate: async user => ({
        ...user,
        firstName: user.firstName.trim(),
      }),
    },
  },
);

const addresses = createLibrary(
  registry,
  addressCoordinate,     // ["address", "user"]
  addressOperations,
  { /* Address-specific rules */ },
);

A User library owns User behavior. An Address library owns Address behavior. Neither needs to become a giant service containing every related object. Relationships are composed through coordinates, parent locations, and explicit domain operations.

Relationship-specific behavior belongs here.

A Team membership is not merely a link. It has a role, joined date, and possibly permissions. Lib is the right place to express actions such as “promote Alex to manager,” “add Alex to the Platform team,” or “list all managers for this Team.”

await memberships.operations.action(
  { kt: "membership", pk: "alex-platform" },
  "promote",
  { role: "manager" },
);

What Lib does not do.

Not storage

Adapters decide how Items are persisted.

Not transport

HTTP and Express routers decide how requests become operations.

Not UI state

Client API, cache, and providers decide how consumers load and retain results.

Not a schema generator

Your application defines User fields and business policy; Lib supplies the consistent operation boundary.

Continue the walkthrough.

Start with the object graph, see how Core defines the runtime, and understand how the Registry composes instances. From here, follow the request into Express Router and persistence adapters.

Previous: registry →Next: Express Router →See the full architecture →