Identity and profile
u-42 identifies the User independently. The User can be loaded without first loading an Address, Company, or Team.
Fjell gives you a common model for a graph of objects that are related to one another. Start with a real domain: a User, the Company they work for, their Addresses, and the Teams where they contribute.
Our example User is not the whole application. They are one node in a useful graph. A User has a first name, last name, and perhaps a birth date. Their work points to a Company. Their addresses belong to them. Their team memberships carry roles such as manager or member.
u-42 identifies the User independently. The User can be loaded without first loading an Address, Company, or Team.
A User can reference a Company as another primary Item. Both objects have their own identity and lifecycle.
A User can have many Addresses. Their location in the graph is expressed through the User parent, not by flattening everything into User.
A Team can have many memberships. Each membership can point to a User and carry a role: manager, member, or another application-defined role.
The arrows describe domain relationships. The labels describe how those relationships can be represented in Fjell.
worksAt→ Companyemployees↔ UsersUser → addresses[]→ child locationsmemberships[]→ manager · memberUser → Company is a PItem-to-PItem reference. The User and Company stand on their own, and the relationship can be represented by a key or domain property such as companyId.
User → Addresses is a PItem-to-many-CItem relationship. An Address is still a full Item, but its location is reached through its User parent.
Team → memberships is a useful child collection. A membership can reference a User and add relationship data that does not belong on either endpoint:
Team: team-9 Membership: alex-morgan role: "manager" joinedAt: "2025-04-12"
A reciprocal view is possible too: “Which Teams is Alex on?” and “Who reports to Alex on this Team?” are graph queries over related Items. The model does not require one giant User record containing every object.
The distinction is about how an Item is reached through the hierarchy. It is not a distinction between important and unimportant objects.
A top-level Item addressed independently, such as /users/:id, /companies/:id, or /teams/:id.
An Item reached through a parent location, such as /users/:userId/addresses/:id or /teams/:teamId/memberships/:id.
const alex = await users.operations.get({ kt: "user", pk: "u-42" });
const home = await alex.addresses.operations.get({ kt: "address", pk: "home" });
const team = await teams.operations.get({ kt: "team", pk: "team-9" });Once the graph has a shared shape, each package owns one part of its journey: types define the language, validation protects boundaries, libraries apply operations and business rules, storage adapters persist Items, routers transport them, clients consume them, and cache/providers keep them useful in applications.
Follow one Item end to end →Browse the layers →