Docs

Links

Links describe how objects relate: a project is delivered for a customer, led by one employee, and staffed by many. Declare links on the source object type with link(...), then write and traverse them through the typed API.

Use links for relationships your app needs to navigate, query, or display. For values that live on a single object, use properties instead.

Add a links array to defineObjectType. Each entry connects this object type (the source) to a target object type.

TS
import { defineObjectType, link, prop, stringEnum } from "@sixb/core/ontology"
import { Customer } from "./customer"
import { Employee } from "./employee"

export const Project = defineObjectType({
  id: "Project",
  name: "Project",
  properties: [
    prop("id", "string", { required: true, primary: true }),
    prop("name", "string", { required: true }),
    prop("status", stringEnum(["draft", "active", "paused", "completed", "cancelled"])),
  ],
  links: [
    link("customer", Customer, { cardinality: "one" }),
    link("lead", Employee, { cardinality: "one" }),
    link("members", Employee, { cardinality: "many" }),
  ],
})
TS
link(id, target, options?)
link.ref(id, targetTypeId, options?)
link.self(id, options?)
link.any(id, options?)

For links to object types you can import directly, use link(...):

TS
link("customer", Customer)
link("relatedTo", [Project, Task])

For id references, self-links, or intentionally open links, use the explicit helpers:

TS
link.ref("customer", "Customer")
link.self("parent", { cardinality: "one" })
link.any("relatedTo", { cardinality: "many" })

Use link.ref(...) when you want to reference another object type by id instead of importing it. Sixb's generated ontology type manifest lets typed client queries resolve those ids.

Use link.self(...) for recursive relationships such as folders, org charts, or threaded comments. The target id is filled in from the object type that declares the link.

Use link.any(...) only for wildcard relationships that can point to any object type. Prefer a specific target for relationships your app understands.

ParameterRequiredExpected
idYesA stable relationship key, unique within the source object type
targetDepends on helperThe object type or object type id this link can point to
optionsNoMetadata and relationship behavior

options accepts:

OptionTypeMeaning
namestringDisplay name. Defaults to the link id.
descriptionstringHuman-readable context for the relationship.
cardinality"one" | "many"Whether each source links to one or many targets.
propertiesProperty[]Metadata stored on each relationship instance.

Target forms

link(...) accepts an object type or an array of object types. link.ref(...) accepts an object type id string or an array of ids. link.self(...) points back to the declaring object type. link.any(...) creates a wildcard.

FormExamplePoints to
Object typelink("customer", Customer)One specific type
Object type idlink.ref("customer", "Customer")One specific type, by id
Array of typeslink("relatedTo", [Project, Task])Any of the listed types
Array of idslink.ref("relatedTo", ["Project", "Task"])Any of the listed types
Self-linklink.self("parent")The declaring object type
Wildcardlink.any("anything")Any object type
Wildcard with optionslink.any("anything", { cardinality: "many" })Any object type

Passing an object type extracts its .id at build time. Prefer the object-type form when the target can be imported — it keeps the target type-checked and powers typed traversal.

Cardinality

cardinality controls how many targets each source object can link to under this link id.

ValueMeaning
"one"Each source links to at most one target — a project has one lead.
"many"The source can link to multiple targets — a project has many members.
TS
link("lead", Employee, { cardinality: "one" })
link("members", Employee, { cardinality: "many" })

A link can carry metadata about the relationship itself — not about either object. Declare these with prop(...), exactly like object properties.

TS
import { defineObjectType, link, prop } from "@sixb/core/ontology"
import { Employee } from "./employee"

export const Project = defineObjectType({
  id: "Project",
  name: "Project",
  properties: [prop("id", "string", { required: true, primary: true })],
  links: [
    link("members", Employee, {
      cardinality: "many",
      properties: [
        prop("role", "string"),
        prop("allocatedAt", "timestamp"),
      ],
    }),
  ],
})

Good link properties are facts about the connection: a member's role on the project, when they were allocatedAt, or a confidence score. If a link does not declare properties, writing a link with properties is rejected with an OntologyValidationError.

Once a type is registered, write links through the typed API on a byId(...) handle. The link token comes from the object type as Type.l.<linkId>.

TS
const projects = sixb.objects(Project)

// Link to a target by object reference
await projects.byId("proj-001").link(Project.l.members, {
  objectTypeId: "Employee",
  primaryId: "emp-014",
})

// With link properties
await projects.byId("proj-001").link(
  Project.l.members,
  { objectTypeId: "Employee", primaryId: "emp-014" },
  { properties: { role: "backend", allocatedAt: new Date() } }
)

// Remove a link
await projects.byId("proj-001").unlink(Project.l.members, {
  objectTypeId: "Employee",
  primaryId: "emp-014",
})

The target is an ObjectRef ({ objectTypeId, primaryId }). TypeScript checks it against the link's declared target, so linking members to a Customer is a compile error.

For a cardinality: "one" link, link(...) is not a setter. Calling link(...) with the same target updates that relationship's properties, but calling it with a different target while one is already present is rejected. Reassign by removing the current target, then linking the new one:

TS
const [currentLead] = await sixb.objects(Project).byId("proj-001").listLinks(Project.l.lead)

if (currentLead) {
  await projects.byId("proj-001").unlink(Project.l.lead, {
    objectTypeId: currentLead.targetTypeId,
    primaryId: currentLead.targetId,
  })
}

await projects.byId("proj-001").link(Project.l.lead, {
  objectTypeId: "Employee",
  primaryId: "emp-027",
})

unlink(...) removes one exact relationship row. Even for cardinality: "one" links, pass the current target; when you only know the linkId, read it first with listLinks(Project.l.lead).

Writing a link emits a link.upserted event; removing one emits link.removed.

listLinks(...) returns the relationship rows for an object, optionally filtered to one link token.

TS
// All links from this project
const all = await sixb.objects(Project).byId("proj-001").listLinks()

// Only members links
const members = await sixb
  .objects(Project)
  .byId("proj-001")
  .listLinks(Project.l.members)

traverse(...) follows a link inside an object query and changes the result type to the type on the other end of the link.

TS
// Outgoing: from a project to its members
const members = await sixb
  .objects(Project)
  .query()
  .where((project) => project.p.id.eq("proj-001"))
  .traverse(Project.l.members)
  .list()

Pass { direction: "incoming" } to walk a link backwards — from the link's target type to its source type:

TS
// Incoming: from a customer to the active projects that link to it
const projects = await sixb
  .objects(Customer)
  .query()
  .where((customer) => customer.p.id.eq("cust-001"))
  .traverse(Project.l.customer, { direction: "incoming" })
  .where((project) => project.p.status.eq("active"))
  .list()
DirectionToken comes fromResult type
"outgoing" (default)The source type's linkThe link's target type
"incoming"The other type's linkThe link's source type

After traverse(...), the builder operates on the new result type, so where(...), orderBy(...), and list(...) apply to the traversed objects.

When an object type uses extends, it inherits the parent's links along with its properties. A Contract extends Document gains Document's project and author links without redeclaring them. See object types for inheritance details.

  • Object types — declare the types links connect
  • Properties — values that live on a single object
  • Querying objects — filters, ordering, and traverse
  • CRUD — upsert, link, and unlink from code
  • Eventslink.upserted and link.removed

Search docs

Search the documentation