defineSchema
Quick example
Section titled “Quick example”import { defineSchema, text, select, checkbox, relation } from '@xnetjs/data'
export const TaskSchema = defineSchema({ name: 'Task', namespace: 'xnet://my-app/', properties: { title: text({ required: true, maxLength: 500 }), status: select({ options: [ { id: 'todo', name: 'To Do', color: '#6366f1' }, { id: 'doing', name: 'Doing', color: '#f59e0b' }, { id: 'done', name: 'Done', color: '#10b981' } ] as const }), completed: checkbox({ default: false }), project: relation({ target: 'xnet://my-app/Project' }) }})Signature
Section titled “Signature”function defineSchema<P extends Record<string, PropertyBuilder>>( options: DefineSchemaOptions<P>): DefinedSchema<P>Options
Section titled “Options”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Schema name (e.g., 'Task'). Used in the schema IRI. |
namespace | `xnet://${string}/` | Yes | Namespace for grouping. Must end with /. |
properties | Record<string, PropertyBuilder> | Yes | Property definitions using builder functions. |
extends | DefinedSchema | No | Parent schema to inherit properties from. |
document | 'yjs' | 'automerge' | No | CRDT document type. Required for useNode rich text. |
authorization | AuthorizationBlock | No | Roles, actions, and encryption policy. See Authorization. |
Schema IRI
Section titled “Schema IRI”The schema’s unique identifier is ${namespace}${name}:
defineSchema({ name: 'Task', namespace: 'xnet://my-app/' })// Schema IRI: 'xnet://my-app/Task'Each property also gets an IRI: ${schemaId}#${propertyName}:
xnet://my-app/Task#titlexnet://my-app/Task#statusReturn value: DefinedSchema
Section titled “Return value: DefinedSchema”| Field/Method | Type | Description |
|---|---|---|
schema | Schema | The JSON-LD compatible schema object. |
validate(node) | (unknown) => ValidationResult | Validate a node against this schema. |
create(props, options) | (props, options) => Node | Create a new node (used internally). |
is(node) | (node) => boolean | Type guard — check if a node matches this schema. |
_schemaId | string | The computed schema IRI. |
_properties | P | Property builders (for type inference). |
ValidationResult
Section titled “ValidationResult”interface ValidationResult { valid: boolean errors: ValidationError[] // { path, message, value? }}Validation checks:
- Required system fields (
id,schemaId,createdAt,createdBy) schemaIdmatches the schemacreatedBystarts withdid:key:- Each property’s constraints (required, min/max, pattern, etc.)
Adding authorization
Section titled “Adding authorization”Add an authorization block to define who can read, write, delete, and share nodes of this schema:
import { defineSchema, text, person, relation } from '@xnetjs/data'import { allow, role } from '@xnetjs/data/auth'
export const TaskSchema = defineSchema({ name: 'Task', namespace: 'xnet://my-app/', properties: { title: text({ required: true }), assignee: person(), project: relation({ target: 'xnet://my-app/Project' as const }) }, authorization: { roles: { owner: role.creator(), assignee: role.property('assignee'), admin: role.relation('project', 'admin') }, actions: { read: allow('owner', 'assignee', 'admin'), write: allow('owner', 'admin'), delete: allow('owner', 'admin'), share: allow('owner', 'admin') }, publicProps: ['title'] // these fields are readable without decryption }})Nodes are encrypted with a per-node content key and only users in the recipient list can decrypt them. The hub stores encrypted envelopes and filters queries by recipient list without ever decrypting content.
See the Authorization Guide for the full model including presets, useCan, useGrants, delegation, and key recovery.
Adding rich text support
Section titled “Adding rich text support”Add document: 'yjs' to enable collaborative editing via useNode:
export const PageSchema = defineSchema({ name: 'Page', namespace: 'xnet://my-app/', properties: { title: text({ required: true }), icon: text() }, document: 'yjs'})When document: 'yjs' is set, useNode creates a Y.Doc for the node and manages P2P sync for its content.
Schema inheritance
Section titled “Schema inheritance”Use extends to inherit properties from a parent schema:
const BaseSchema = defineSchema({ name: 'Base', namespace: 'xnet://my-app/', properties: { title: text({ required: true }), tags: multiSelect({ options: ['draft', 'published'] as const }) }})
const ArticleSchema = defineSchema({ name: 'Article', namespace: 'xnet://my-app/', extends: BaseSchema, properties: { body: text(), publishedAt: date() }})// ArticleSchema has: title, tags, body, publishedAtCoercion
Section titled “Coercion”When you create or update a node, property values are coerced through the property builder:
text()— callsString(value), trims whitespacenumber()— callsNumber(value), appliesMath.round()ifintegerselect()— validates against option IDs, tries case-insensitive name matching as fallbackdate()— acceptsnumber,Date, or ISO string; normalizes to timestampemail()— trims and lowercasesurl()— auto-prependshttps://if no protocol
This means your data is always normalized regardless of what the user types.
Dev-time warnings
Section titled “Dev-time warnings”In development, defineSchema warns if:
- A
text()property has a name that looks like a reference (e.g.,targetId,parentId), suggesting you userelation()instead
Related
Section titled “Related”- Property Types — all 15 property builders
- Relations — linking nodes together
- Type Inference — how TypeScript types flow
- Authorization — roles, grants, encryption, and key recovery