Relations
Relations are how you connect nodes together in xNet. Instead of embedding nested objects, you store references between nodes using two specialized property types: relation() and person().
relation()
Section titled “relation()”The relation() property stores a reference to another node by its ID. You can think of it like a foreign key in a relational database, except it works across peers in a local-first system. When you need to link a task to a project, or a comment to a post, relation() is the right choice.
import { defineSchema, relation, text } from '@xnetjs/data'
const TaskSchema = defineSchema({ name: 'Task', namespace: 'xnet://my-app/', properties: { title: text(), project: relation({ target: 'xnet://my-app/Project@1.0.0' }), // single reference blockedBy: relation({ multiple: true }) // multiple references }})Relations support both single and multiple cardinality. Use relation() for a single link and relation({ multiple: true }) for an array of links.
person()
Section titled “person()”The person() property links to a user by their DID (Decentralized Identifier) rather than a node ID. This is useful for ownership, assignment, and authorship fields where you need to reference an identity rather than a piece of data.
const TaskSchema = defineSchema({ name: 'Task', namespace: 'xnet://my-app/', properties: { title: text(), assignee: person(), // single DID reference watchers: person({ multiple: true }) // multiple DID references }})Related creates in one mutate call
Section titled “Related creates in one mutate call”When creating multiple related nodes at once, use explicit IDs so later operations can reference earlier creates in the same mutate call:
const { mutate } = useMutate()
await mutate([ { type: 'create', schema: ProjectSchema, id: 'project-acme', data: { name: 'Acme' } }, { type: 'create', schema: TaskSchema, data: { title: 'Setup', project: 'project-acme' } }])Because the project node is created first (with a known ID), the task can safely reference it in the next operation.