Skip to content

Schemas as documents

The schema for document type task is itself a document: sys:schema:task, of type sys:schema. It is created and updated through the same API as user data, synced to clients like user data, and validated like user data — against a meta-schema.

A schema document declares the shape of a document type’s data:

  • Field types — objects, arrays, records, strings, numbers, booleans, enums, and unknown for escape hatches.
  • References — fields that point at other documents (or at records within the same document), with integrity rules for what happens when the target disappears. See References & integrity.
  • Migrations — an append-only log of schema evolutions (rename, remove, remap). A document written under an old schema version is brought forward on its next read and the migrated data is persisted back — the full story is on Schema evolution.

Documents whose type has no schema are freeform: they sync and patch like any other document, just without validation.

One API. Evolving the data model is a document write, not a deploy. The same subscription machinery that gives you live user data gives you live schema changes.

Agents can model. An AI agent that decides it needs a new document type writes a schema document and starts using it — no out-of-band tooling.

One timeline, not two. When schemas live only in source code, a stored document’s meaning is split across two histories — the repository’s and the data’s — and correlating them means mapping deploy times onto document versions. Remove a field from a code-only schema and that removal is recorded nowhere except a commit. As documents, schemas have their history in the same event log as the data they govern: introspecting a document as it was at any point in time means looking in exactly one place. The same property is what makes the data portable — a folder’s history carries its own interpretation with it.

Schemas version like data. Sequence numbers, event history, and guarded writes apply to schema changes exactly as they do to data changes.

Schemas being documents doesn’t mean giving up TypeScript. There are three ways to source them, and they compose:

Authored in TypeScript. A schema can be defined in code, and the document data types are inferred from the schema literal — compile-time validation and typed reads/writes with no code-generation step. The defined value is a plain schema document’s data; the types are phantom.

Upserted at startup. The host application writes its TypeScript-defined schemas into the sys:schema:* documents when a folder boots — a no-op when nothing changed. Rolling out a schema change in code becomes a document update in each folder, still subject to the server’s append-only migration rules. This is what makes TypeScript authoring compatible with the one-timeline argument above: the upsert turns every code change back into document history, so even when git is where schemas are written, the schema documents remain where their history lives — the repository never becomes a second timeline you’d have to consult.

Document-driven only. Types created at runtime — by a user, or by an agent — exist purely as schema documents, with no TypeScript counterpart.

Whatever the source, the server’s write path always validates against the schema documents — the TypeScript definitions are never a second, shadow truth. Clients then choose per app: bundle the static TypeScript schemas (typed, instant optimistic validation, no waiting for schemas to sync) or, with dynamic schemas enabled, subscribe to sys:schema:* and validate against whatever the folder currently declares — required when document types are born at runtime.