@repo/datadata/document
The document model every other subpath builds on: the document handle, write results, and the errors reads and writes reject with.
Classes
CommandRefusedError
export declare class CommandRefusedError extends WriteRejectedErrorThe rejection of an awaited command the mutator (or the executor, for a name the registry does not know at the docType, args that fail its shape, or a mutator that threw) refused: a WriteRejectedError of category commandRefused carrying the refusal's code, so a caller can branch on it.
constructor(
code: string, message: string);Constructs a new instance of the CommandRefusedError class
readonly code: string;The refusal's app-defined or library-reserved code.
DocumentUnavailableError
export declare class DocumentUnavailableError extends ErrorThe rejection every "can I USE this document?" wait settles with: the client's waitForAvailable, and a session's prepareDocument(...) handle — both its available and (for a failed subscribe) its settled. status is the terminal status that ended the wait; category/message forward the server's own doc:error when one is attached, and are null for notFound/deleted, which are lifecycle ANSWERS about the document rather than failures to reach it.
The forwarded category is what makes a denial diagnosable at the await point: an undeclared presence channel (unknownType) and a read-scope denial (unauthorized) both leave a document unreadable, and only the category tells the caller whether to fix configuration or degrade.
ReadRejectedError
export declare class ReadRejectedError extends ErrorThe read-side sibling of WriteRejectedError: the rejection every getDocumentEvents request settles with, carrying the same structured .category union. notFound/deleted answer the read definitively; a server doc:error forwards its wire category (message in the Document error (<category>): <message> format); timeout/unconfirmed mean the answer never arrived — a read is idempotent, so retrying is always safe. A caller's own AbortSignal rejects with its abort reason (DOMException "AbortError") per platform convention, as on the write side.
constructor(
category: DocumentErrorCategory, message: string);Constructs a new instance of the ReadRejectedError class
readonly category: DocumentErrorCategory;Why the read was rejected. DocumentErrorCategory is exported from @repo/datadata/events.
SchemaUnavailableError
export declare class SchemaUnavailableError extends ErrorThe rejection of a schema wait (a session's waitForSchema, a prepared document's available, an awaited write held for its type's schema) on a dynamic-schema client that could not READ the type's schema: sys:index or the sys:schema:<type> document the index lists answered doc:error. A write of the type is refused with it too, rather than validated against a static registry shape the server may have moved past. cause is the unreadable document's DocumentUnavailableError, which forwards the server's category and message.
An ABSENT schema is not this error: a type the index lists no schema document for (or whose document is notFound or deleted) resolves to the static registry.
WriteRejectedError
export declare class WriteRejectedError extends ErrorThe rejection every awaited write settles with, carrying a structured .category so callers branch on one discriminant (e.g. category === "unauthorized", category === "timeout") instead of parsing messages or juggling error types. Minted by the server doc:error path (message keeps the long-standing Document error (<category>): <message> format), by the client's own authorization prediction, and by the client-minted lifecycle paths — queueFull, notFound, deleted, timeout, unconfirmed — whose messages are plain prose. unconfirmed/timeout mean the outcome is UNKNOWN (the write may have committed): refetch server truth instead of blindly re-submitting. The one exception to the shape: a caller's own AbortSignal rejects with its abort reason (DOMException "AbortError") per platform convention.
The fire-and-forget counterpart is the ambient WriteError (@repo/datadata/client), which carries the SAME category union — a write failure surfaces on exactly one of the two, never both.
constructor(
category: DocumentErrorCategory, message: string);Constructs a new instance of the WriteRejectedError class
readonly category: DocumentErrorCategory;Why the write was rejected. DocumentErrorCategory is exported from @repo/datadata/events.
Functions
toAbortError
export declare function toAbortError(signal: AbortSignal): Error;The rejection a caller's own AbortSignal produces — its reason when that is an Error, else a platform-conventional AbortError. Lives here rather than beside the awaited-write ledger because both the client (which rejects in-flight waits) and the sessions (which refuse an already-aborted write before taking any lease) raise it, and a session may not import from the client layer.
writeApplied
export declare function writeApplied(result: WriteResult): boolean;Whether a settled write's result means the server APPLIED it — the kind's success flag, false for every benign non-applied outcome (preconditionFailed, alreadyExists, …). Exhaustive over the whole union: the satisfies never guard fails compilation when a new result shape is added without an arm here.
Interfaces
BlobUploadIntent
export interface BlobUploadIntentThe document write a blob upload is intended for — named by the uploader at staging so the upload is authorized as THAT write (create or update on docId, a document of docType), the same verdict the eventual handle- committing write will get: the docType's rule, or the sys:access per-document entry for docId where one exists (a per-document entry wins for every kind, create included — a privately minted id is such a case). A hint for authorization and for the type-wide constraint check, not a binding: the handle may still be committed anywhere the write boundary authorizes, and no document existence is checked at staging (so a probe learns nothing).
docId: string;The document the handle is meant to be committed into.
docType: string;That document's docType; its rule authorizes the upload and its blobRef fields constrain it.
kind: "create" | "update";Which write the upload is for: creating docId, or updating it.
path?: readonly (string | number)[];Where in the document the handle is meant to go — object fields / record keys as strings, array indices as non-negative integers. Optional narrowing for the staging-time constraint check: with it, only the blobRef leaves at that path (every union variant's, since staging sees no data) are candidates instead of every leaf of the type, and a path naming no blobRef leaf is refused outright. The write boundary's exact check is unaffected.
DatadataDocument
export interface DatadataDocument<T = unknown>A document as the client and server hand it out: its identity, its JSON data and the encoded state of its Yjs sub-documents.
conformedSchemaSequence?: number;How far this copy's data has been carried along its type's schema: the sequence of the sys:schema:<type> document whose migrations have all been replayed over it. A schema commit reaches a subscriber as the schema frame and then each document's migration frame, so between the two a copy is BEHIND the schema the client holds — this is what says so, and from where to replay. It says "carried to", not "conforms at": it advances for a document the schema rejects too. Absent on sys: documents and synthesized ones (the static registry carries no migration log), and on an optimistic create the server has not confirmed yet.
data: T;The document's JSON value.
docId: string;The document's id, unique within the folder.
generation?: string;Which incarnation of a STORED document this is: an opaque random token minted when the document is created or imported, kept through every update, delete/restore and rename. A purge releases the docId, so a later create under it is a new generation whose sequence restarts at 1 — only the generation tells a copy of the old document from the new one. Absent on synthesized documents (folder projections, sys:principal, presence) and on an optimistic create the server has not confirmed yet.
sequence: number;JSON-lane version: bumps on data changes only, never on Yjs updates.
type: string;The document's docType: the schema registry key its data validates against.
yjsDocs: Record<string, Uint8Array>;The document's Yjs sub-documents, keyed by yjsId, each as an encoded Yjs state update (apply it with Y.applyUpdate).
DocumentEventHistory
export interface DocumentEventHistoryA document's full history, one log per lane (see getAllDocumentEvents).
events: StoredDocumentEvent[];The JSON lane's events, in sequence order.
yjsEvents: StoredYjsEvent[];The Yjs lane's events, in yjsSequence order.
DocumentEventsPage
export interface DocumentEventsPage extends DocumentEventHistoryOne page of a document's history (see getDocumentEvents): each lane's events in its own order, the JSON lane filled first. The next page's cursor is the last event of each lane — its sequence / yjsSequence — or the previous cursor for a lane this page left empty. hasMore says whether rows remain after this page in either lane; generation which incarnation of the document the page belongs to.
generation: string;The generation the page was read from (see DatadataDocument.generation). A purge and re-create under the same docId restarts both lanes' sequences, so a walk whose pages name different generations has spliced two documents' logs.
hasMore: boolean;Whether either lane has rows after this page: read the next page from this one's cursor.
DocumentSubscriptionLease
export interface DocumentSubscriptionLeaseOne holder's claim on a document subscription. The client keeps a document subscribed while any lease on it is held, and releasing the last one unsubscribes it.
docId: string;The subscribed document.
subscriptionId: string;Identifies this lease among the others on the same document; unique per client.
DocumentUpdateOps
export interface DocumentUpdateOpsWhat an update callback changes a document with besides assigning to doc.data: operations whose INTENT reaches the server, where a plain assignment reaches it as the difference it made.
move(from: string, to: string): void;Move the value at from to to, both JSON Pointers (RFC 6901) into doc.data ("/todo/t1"). The draft changes at once, so the callback reads and edits the value at to afterwards.
The write then carries a move rather than a copy of the value, so another author's concurrent edit inside it is kept. It moves a member of one object to a member of another (replacing what is there); an array's members do not move. Move first and edit after: a value the callback changed before moving it is written as the plain difference. Where the write is not a patch of the document — a staged edit, a presence view — the value moves in the draft and that is all.
Throws
when either pointer does not name a member of an object in the draft, from holds no value, or to is inside from.
YjsDocsAccessor
export interface YjsDocsAccessorAccessor for Y.Doc instances during createDocument/updateDocument() callbacks
Provides two ways to access Y.Docs: - get(yjsId): Direct access by ID - getOrCreate(yjsId): Access by ID, creating a new Y.Doc if it doesn't exist
get(yjsId: string): Y.Doc | null;Get a Y.Doc by its ID
Parameters
yjsIdThe ID of the Y.Doc, unique within the document
Returns
The Y.Doc instance, or null if the ID doesn't exist
getOrCreate(yjsId: string): Y.Doc;Get or create a Y.Doc by its ID
Parameters
yjsIdThe ID of the Y.Doc, unique within the document
Returns
The existing or newly created Y.Doc instance If the Y.Doc didn't exist, a new empty Y.Doc is created and returned
Types
AccessCapabilities
export type AccessCapabilities = Record<BuiltinAccessKind, boolean | null>;The predicted capabilities for a docType (see the client's can): one verdict per access kind — the five write kinds plus read — null where no prediction is possible. read: false predicts the server would answer doc:notfound; a definitively hidden document is indistinguishable from a nonexistent one by design.
CommandResult
export type CommandResult = UpdateResult;The outcome of an awaited command: committed, or — for a command whose contract is "exact", sent with a "sequence" guard — the benign miss when the document moved since the caller read it. A mutator's refusal rejects with a CommandRefusedError instead; every other failure rejects too.
CreateResult
export type CreateResult = {
created: true;
} | {
created: false;
category: "alreadyExists";
message: string;
};The outcome of an awaited create. Only the benign upsert signal (alreadyExists) is reported as a non-created result; every other failure rejects the promise.
DeleteResult
export type DeleteResult = {
deleted: true;
} | {
deleted: false;
category: "preconditionFailed";
message: string;
};The outcome of an awaited delete. Only the benign guard miss (preconditionFailed, for a guard: "sequence" delete) is reported as a non-deleted result — the document advanced past the base the caller looked at, so refetch and decide again. Every other failure rejects the promise.
DocumentChangeCallback
export type DocumentChangeCallback = (docId: string, document: DatadataDocument | null) => void;A client-side document change listener: called with the changed document's id and its current view, or null when it is not readable (not loaded, not found or deleted).
PurgeResult
export type PurgeResult = {
purged: true;
};The outcome of an awaited purge. All-or-nothing with no benign non-purged outcome: any failure (a bad target anywhere in the batch, a denial) rejects and destroys nothing.
ReadonlyDatadataDocument
export type ReadonlyDatadataDocument<T = unknown> = Readonly<Omit<DatadataDocument<T>, "yjsDocs">> & {
readonly yjsDocs: Readonly<Record<string, Uint8Array>>;
};The read-only view a server change callback receives — see DocumentChangeCallback (server/broadcast.ts). data is T = unknown, so a consumer that casts it to its own mutable type sheds the protection — this is the contract's signature, not a jail — and Uint8Array contents remain mutable at the byte level (TS has no readonly typed-array element type).
Derived from DatadataDocument (rather than hand-duplicated) so a future field added to the source interface flows through here automatically — yjsDocs is the one field re-shaped, since Readonly alone doesn't touch a Record's index signature.
RegistryFor
export type RegistryFor<S extends SchemaRegistry> = ValidatorsFromSchemas<S> & SystemValidatorRegistry;The complete registry the platform runs a schema map against: the docTypes' validators plus the built-in sys:* validators that createClient/createServer merge in. This is the bridge between a caller's schemas (their source of truth) and the validator-registry type the engine generics are parameterised by — so callers name only their schemas. Prefer DatadataClientFor / DatadataServerFor for naming client/server types; reach for RegistryFor directly only to fill a lower-level generic argument (e.g. createServer, the storage adapter, a mock connection).
RemotePresenceEntry
export type RemotePresenceEntry = PresenceEntry & {
stale: boolean;
};A remote participant's presence as the client exposes it: the wire entry plus stale — true while the entry awaits re-confirmation after a presence resync or a reconnect (the peer either republishes within the grace window, refreshing the entry, or ages out). UIs typically dim stale participants.
RenameResult
export type RenameResult = {
renamed: true;
};The outcome of an awaited rename. Rename is unconditional last-writer-wins, so there is no benign non-renamed outcome — failures (unauthorized, not found) always reject.
RestoreResult
export type RestoreResult = {
restored: true;
};The outcome of an awaited restore. Restore has no benign non-restored outcome — restoring a live document converges to the same acked state — so failures (never existed, storage) always reject.
SchemaResolution
export type SchemaResolution = {
source: "dynamic";
sequence: number;
confirmed: boolean;
} | {
source: "staged";
} | {
source: "static";
} | {
source: "none";
};Where a docType's schema resolves from, as a session's waitForSchema answers it once the resolution can no longer flip from "not synced yet".
- dynamic: a sys:schema:<type> document the client holds. sequence is the schema sequence a write authored now names on the wire. confirmed is false while the copy is only hydrated from the offline cache, the server not having answered the subscription yet: the schema is usable, and may trail the server's. - staged: a schema document staged in the staging session asked, not committed yet. Only a staging session answers it, and only for its own staged work. - static: no schema document exists for the type, and the static registry holds a validator for it. - none: no schema document exists and the static registry has no entry either. Nothing validates the type, so a create or update of it is refused.
SettledWriteOutcome
export type SettledWriteOutcome = "applied" | "rejected";How a write settled: applied when the server applied it, rejected when it settled any other way — rolled back by a refusal, a benign non-applied result, or an awaited promise that gave up (abort, timeout) on a write that may still be pending. See the client's settledWriteOutcome.
UpdateResult
export type UpdateResult = {
committed: true;
} | {
committed: false;
category: "preconditionFailed";
message: string;
};The outcome of an awaited update. Only the benign guard miss (preconditionFailed) is reported as a non-committed result; every other failure rejects the promise, so the category here is exactly that one literal, not the full DocumentErrorCategory.
WriteResult
export type WriteResult = UpdateResult | CreateResult | DeleteResult | RestoreResult | RenameResult | PurgeResult;The outcome of any awaited write; writeApplied reads whether it was applied.