Skip to content

References & integrity

datadata’s model is referential: schemas mark fields as references — to a record in the same document, to another document, or to a collaborative sub-document. Declaring them in the schema is what lets the engine enforce integrity (and, where it can, repair it) instead of leaving dangling ids to the application.

A schema document marks fields as references. There are three kinds, each declared as such in the schema:

  • Entity references point at a record inside the same document — for example, an edge in a diagram pointing at two nodes.
  • Document references point at another document of a given type, within the same folder — the reference can’t reach across a folder boundary.
  • Yjs references hold a handle to one of the document’s own collaborative sub-documents — a Yjs Y.Doc with its own CRDT sync lane and lifecycle.

The first two are constraints over an ordinary id; a yjs reference is its own value kind — a handle to a sub-document, not a pointer into the JSON.

An entity reference also declares what should happen when its target goes away. Because the referrer and the target live in the same document, these rules are enforced within that document, at each write:

  • restrict (the default) — the write is rejected while a referrer still points at the target, so you can’t delete a node while an edge needs it.
  • cascade — deleting the target also drops the referrers that pointed at it.
  • unreferenced — a target that loses its last referrer is reclaimed (reference-counted: the target lives only as long as something points at it).

An entity reference can also bound how many referrers a target must carry (for example, “at least one”). These rules make integrity a property of the schema rather than something each write path re-implements — a raw update gets the same cleanup as any other.

How a reference is enforced depends on its kind — and the guarantees differ because the target lives in a different place each time:

  • Entity references are both checked and repaired at every write. Because referrer and target live in the same document, this is atomic: the declared rules run first (cascade drops dangling referrers, unreferenced reclaims targets nothing points at, iterated to a fixpoint), then restrict and cardinality violations reject the write. The repaired state is the same whether the client applied it optimistically or the server applied it authoritatively, so sync converges.
  • Yjs references are reference-counted. When a write leaves one of the document’s sub-documents with no referring field, that Y.Doc is deleted in the same write — no separate cleanup step. A reference to a sub-document that doesn’t exist yet is fine: it’s created lazily on first use.
  • Document references point at another document within the same folder — the existence check resolves against the folder’s own registry, so a document reference can’t reach outside it. They are checked at write time only: the target must exist when the write lands, but there is no cross-document repair — a target deleted afterward leaves a dangling pointer, which surfaces the referrer as an invalid document on read rather than erroring. There’s no transaction spanning the two documents. Every validated write also records the document’s outbound references in a folder-level index, so the invalid-document enumeration derives dangling-reference invalidity from that index against the live document set — deleting a target lists its referrers instantly, and restoring it heals the listing just as instantly, with nothing persisted into the referrers that could go stale.

This matters doubly for AI agents: an agent can validate the referential consistency of its staged changes before committing, and the rules it must respect are readable from the schema documents themselves.