Skip to content

@repo/datadata/yjs

Yjs integration: awareness (cursors and selections) bridged into datadata presence.

Functions

composeYjsAwarenessPresence

export declare function composeYjsAwarenessPresence<S extends SchemaRegistry>(schemas: S): S;

Normalize a schema registry by composing the Yjs awareness field into every presence schema (the presence:<type> docTypes), leaving all other schemas untouched. Applied wherever the app's schemas are turned into stored schema documents (the seed paths) and wherever they are introspected (describe_schemas), so the reserved cursor field is present uniformly — enforced by the server, carried in the stored schema, and visible to schema introspection — without the app having to declare it.

createYjsAwareness

export declare function createYjsAwareness(params: YjsAwarenessParams): YjsAwarenessBridge;

Create a y-protocols Awareness for one Y.Doc, backed by datadata presence. Requires an active subscription to docId (presence is scoped to one); set the bridge up after subscribing and destroy it before unsubscribing.

withYjsAwarenessField

export declare function withYjsAwarenessField(schema: DocumentSchema): DocumentSchema;

Compose the engine-owned Yjs awareness field into one presence schema. The field is an optional record keyed by yjsId (or a staged copyRef) whose values are opaque y-protocols state (json): datadata carries the cursor blobs but says nothing about their shape, which is the bridge's / y-protocols' to own. A no-op on a non-object root (presence schemas are objects). Idempotent — re-composing overwrites the same field with the same shape.

Interfaces

YjsAwarenessBridge

export interface YjsAwarenessBridge

A y-protocols Awareness bridged onto datadata presence, returned by createYjsAwareness. Call destroy when the editor unbinds.

awareness: Awareness;

A real y-protocols Awareness — hand it to y-prosemirror et al. as-is.

destroy(): void;

Unhook everything, clear this bridge's field from the published presence.

YjsAwarenessParams

export interface YjsAwarenessParams

Parameters for createYjsAwareness.

awareness?: Awareness;

Bind the protocol to an EXISTING Awareness instead of creating one — the session's staged bridge owns one shared Awareness per staged doc and upgrades it with transport when the session is persisted.

port: YjsAwarenessPresencePort;

The session's presence-view port — read/write this session's cell + peers.

publishDebounceMs?: number;

Debounce for publishing local awareness changes (cursor moves fire per keystroke). Positive: trailing debounce in ms; 0: publish synchronously. Default: 50.

ydoc: Y.Doc;

The Y.Doc the editor binds to; supplies the local numeric clientID.

yjsId: string;

The Y.Doc's id within the document — namespaces this bridge's field.

YjsAwarenessPresencePort

export interface YjsAwarenessPresencePort

The presence-VIEW port the bridge consumes — the session's lens over one target's presence aggregate, scoped to the session's own cell. The session supplies it (LiveSession / StagingSession): getSelfState / setSelfState read-modify-write THIS session's own cell state, getPeers reads the OTHER cells (stale-flagged), and onChange fires when the view (cells or liveness overlay) moves. Keeping the bridge to this narrow port means it neither knows about presence docIds nor the client's subscription state — the session owns both.

getPeers(): RemotePresenceEntry[];

The OTHER cells in the view — each peer's presenceId, state and stale flag.

getSelfState(): Record<string, JsonValue> | null;

This session's own published cell state (the whole state object), or null.

onChange(listener: () => void): () => void;

Subscribe to view changes (cells or overlay moved). Returns an unsubscribe.

setSelfState(state: Record<string, JsonValue> | null): void;

Publish (or, with null, clear) this session's own cell state. A no-op when unpublishable.

Variables

AWARENESS_PRESENCE_FIELD

AWARENESS_PRESENCE_FIELD = "sys:awareness"

The single reserved presence field carrying Yjs awareness (cursors/selections). A live editor publishes under it keyed by the target's yjsId; a staging session's staged editors publish under the SAME field on the session's own cell, keyed by the copy's copyRef (sys:session:copy:<docId>:<yjsId>). The two never collide — a reader only ever looks up the one key it bound the bridge with (a live yjsId, never the sys:session:copy:-prefixed copyRef) — so live and staged cursors stay isolated without a separate field.