Skip to content

Core Concepts

Every piece of data in xNet is a node. Every node conforms to a schema. A schema is defined once in TypeScript and describes the shape of your data:

import { defineSchema, text, number, select } from '@xnet/data'
export const ProjectSchema = defineSchema({
name: 'Project',
namespace: 'xnet://my-app/',
properties: {
title: text({ required: true }),
priority: number({ min: 1, max: 5 }),
status: select({
options: [
{ id: 'active', name: 'Active' },
{ id: 'archived', name: 'Archived' }
] as const
})
}
})

Schemas give you:

  • Type inferenceproject.status is 'active' | 'archived', not string
  • Validation — invalid data is rejected at creation time
  • Identity — each schema has a unique IRI like xnet://my-app/Project

See defineSchema and Property Types for full reference.

A node is an instance of a schema. When you create a task, you create a node:

const { create } = useMutate()
const task = await create(TaskSchema, { title: 'Ship it', status: 'todo' })

Every node has system fields managed automatically by the store — you never need to define or set these yourself:

FieldTypeDescription
idstringUnique identifier (nanoid, 21 chars)
schemaIdstringSchema IRI (e.g., xnet://my-app/Task)
createdAtnumberSet automatically at creation time
createdBystringAuthor’s DID (e.g., did:key:z6Mk...)
updatedAtnumberUpdated automatically on every change
updatedBystringLast modifier’s DID
deletedbooleanSoft-delete flag

Your schema properties are merged alongside these fields into a flat node — you access task.title directly, not task.properties.title. You can filter and sort by any system field (e.g., orderBy: { createdAt: 'desc' }) without defining them in your schema.

const { data: tasks } = useQuery(TaskSchema, {
where: { status: 'todo' },
orderBy: { createdAt: 'desc' },
limit: 20
})

Returns reactive data that auto-updates when the underlying store changes — whether from your own mutations, a peer’s sync, or a background process.

const { create, update, remove, restore, mutate } = useMutate()
await create(TaskSchema, { title: 'New' })
await update(TaskSchema, id, { status: 'done' })
await remove(id)
await restore(id)
await mutate([
{ type: 'create', schema: TaskSchema, data: { title: 'A' } },
{ type: 'create', schema: TaskSchema, data: { title: 'B' } }
])

All mutations are type-safe, validated against the schema, and automatically synced.

const { data, doc, update, syncStatus, remoteUsers } = useNode(PageSchema, pageId, {
createIfMissing: { title: 'Untitled' },
did: currentUserDID
})

useNode is the powerhouse hook for collaborative editing. It gives you:

  • Structured data via data (the node’s properties)
  • A Yjs document via doc (for rich text, bind to TipTap)
  • Sync status and remote users for presence UI
  • Automatic persistence with debounced saves

xNet uses different strategies for different data types:

Data TypeStrategyHow It Works
Structured data (properties)Lamport LWW (Last Writer Wins)Each property change carries a Lamport timestamp. Higher timestamp wins.
Rich text (documents)Yjs CRDTCharacter-level merge. Two users typing in the same paragraph are merged without data loss.

This dual strategy means structured fields like title or status use simple, predictable conflict resolution, while collaborative text editing uses the industry-standard Yjs CRDT for seamless merging.

When you create or update a node, xNet:

  1. Signs the change with your Ed25519 private key
  2. Hashes the change with BLAKE3
  3. Stores the signed change locally
  4. Syncs the signed change to peers

When a peer receives a change, they verify the signature before applying it. This means:

  • You can prove who made every change
  • Tampered data is rejected
  • No server is needed to enforce trust

Your identity in xNet is a DID:key — a decentralized identifier derived from an Ed25519 public key:

did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK

No accounts. No passwords. No auth server. Your key pair is your identity. You can delegate permissions to other keys using UCAN tokens (capability-based authorization).