@kyneta/yjs-schema 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -25
- package/dist/index.d.ts +185 -86
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1159 -860
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/bind-constraints.test.ts +39 -30
- package/src/__tests__/bind-yjs.test.ts +53 -16
- package/src/__tests__/position.test.ts +376 -0
- package/src/__tests__/structural-merge.test.ts +111 -54
- package/src/__tests__/substrate.test.ts +18 -0
- package/src/__tests__/version.test.ts +87 -0
- package/src/bind-yjs.ts +44 -37
- package/src/change-mapping.ts +219 -25
- package/src/index.ts +3 -1
- package/src/populate.ts +59 -12
- package/src/position.ts +45 -0
- package/src/reader.ts +62 -6
- package/src/substrate.ts +99 -11
- package/src/version.ts +135 -33
- package/src/yjs-resolve.ts +59 -12
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/bind-yjs.ts","../src/populate.ts","../src/substrate.ts","../src/change-mapping.ts","../src/yjs-resolve.ts","../src/reader.ts","../src/version.ts"],"sourcesContent":["// @kyneta/yjs-schema — Yjs CRDT substrate for @kyneta/schema.\n//\n// Provides a Substrate<YjsVersion> implementation that wraps a Y.Doc\n// with schema-aware typed reads, writes, versioning, and export/import.\n//\n// The single entry point is `createDoc(yjs.bind(schema))`. For the\n// batteries-included API, import from this package. For the composable\n// toolkit, import from `@kyneta/schema` directly.\n\n// ---------------------------------------------------------------------------\n// Generic API (re-exported from @kyneta/schema for convenience)\n// ---------------------------------------------------------------------------\n\n// Types (re-exported for convenience)\nexport type { Changeset } from \"@kyneta/changefeed\"\nexport type { DocRef, Op, Ref, SubstratePayload } from \"@kyneta/schema\"\n// Construction\n// Mutation & observation (re-exported from @kyneta/schema for convenience)\n// Schema definition (re-exported for convenience)\n// Native escape hatch\n// Sync primitives (generic — work for any substrate)\nexport {\n applyChanges,\n change,\n createDoc,\n createRef,\n exportEntirety,\n exportSince,\n merge,\n NATIVE,\n Schema,\n subscribe,\n subscribeNode,\n unwrap,\n version,\n} from \"@kyneta/schema\"\n\n// ---------------------------------------------------------------------------\n// Yjs-specific exports\n// ---------------------------------------------------------------------------\n\nexport type { YjsCaps } from \"./bind-yjs.js\"\n// Namespace\nexport { yjs } from \"./bind-yjs.js\"\n// Change mapping\nexport { applyChangeToYjs, eventsToOps } from \"./change-mapping.js\"\n// NativeMap — the Yjs functor\nexport type { YjsNativeMap } from \"./native-map.js\"\n// Container creation\nexport { ensureContainers } from \"./populate.js\"\n// Reader\nexport { yjsReader } from \"./reader.js\"\n// Substrate\nexport {\n createYjsSubstrate,\n yjsReplicaFactory,\n yjsSubstrateFactory,\n} from \"./substrate.js\"\n// Version\nexport { YjsVersion } from \"./version.js\"\n// Container resolution\nexport { resolveYjsType, stepIntoYjs } from \"./yjs-resolve.js\"\n","// bind-yjs — Yjs CRDT substrate namespace and factory.\n//\n// Provides the `yjs` substrate namespace (`yjs.bind()`, `yjs.replica()`)\n// and the internal factory builder that injects a deterministic numeric\n// Yjs clientID derived from the exchange's peerId.\n//\n// Yjs clientID is a uint32 number. We use FNV-1a hash truncated to\n// 32 bits, mirroring the Loro binding's hashPeerId pattern but\n// targeting Yjs's number type (not Loro's bigint/53-bit PeerID).\n//\n// Usage:\n// import { yjs } from \"@kyneta/yjs-schema\"\n//\n// const TodoDoc = yjs.bind(Schema.struct({\n// title: Schema.text(),\n// items: Schema.list(Schema.struct({ name: Schema.string() })),\n// }))\n//\n// const doc = exchange.get(\"my-doc\", TodoDoc)\n\nimport type {\n CrdtStrategy,\n Replica,\n Schema as SchemaNode,\n Substrate,\n SubstrateFactory,\n SubstrateNamespace,\n SubstratePayload,\n} from \"@kyneta/schema\"\nimport {\n BACKING_DOC,\n createSubstrateNamespace,\n STRUCTURAL_YJS_CLIENT_ID,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport type { YjsNativeMap } from \"./native-map.js\"\nimport { ensureContainers } from \"./populate.js\"\nimport {\n createYjsReplica,\n createYjsSubstrate,\n yjsReplicaFactory,\n} from \"./substrate.js\"\nimport { YjsVersion } from \"./version.js\"\n\n// ---------------------------------------------------------------------------\n// Peer ID hashing — deterministic string → numeric Yjs clientID\n// ---------------------------------------------------------------------------\n\n/**\n * Hash a string peerId to a deterministic numeric Yjs clientID.\n *\n * Yjs clientIDs are unsigned 32-bit integers. We use FNV-1a hash to\n * produce a deterministic uint32 from the string peerId.\n *\n * The hash is deterministic: the same string always produces the same\n * numeric clientID, across restarts and across machines.\n */\nfunction hashPeerId(peerId: string): number {\n // FNV-1a 32-bit hash\n let hash = 0x811c9dc5\n for (let i = 0; i < peerId.length; i++) {\n hash ^= peerId.charCodeAt(i)\n // Multiply by FNV prime 0x01000193.\n // Use Math.imul for correct 32-bit integer multiplication.\n hash = Math.imul(hash, 0x01000193)\n }\n // Ensure unsigned 32-bit integer\n const result = hash >>> 0\n // Reserve 0 for structural ops — real peers never collide\n return result === STRUCTURAL_YJS_CLIENT_ID ? 1 : result\n}\n\n// ---------------------------------------------------------------------------\n// createYjsFactory — factory builder with peer identity injection\n// ---------------------------------------------------------------------------\n\n/**\n * Create a SubstrateFactory<YjsVersion> that sets doc.clientID\n * on every new Y.Doc with a deterministic uint32 clientID derived\n * from the exchange's string peerId.\n */\nfunction createYjsFactory(peerId: string): SubstrateFactory<YjsVersion> {\n const numericClientId = hashPeerId(peerId)\n\n return {\n replica: yjsReplicaFactory,\n\n createReplica(): Replica<YjsVersion> {\n // Default random clientID — safe for hydration (no local writes).\n // Identity is set at upgrade() time, after hydration.\n return createYjsReplica(new Y.Doc())\n },\n\n upgrade(\n replica: Replica<YjsVersion>,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n const doc = (replica as any)[BACKING_DOC] as Y.Doc\n // Set stable identity AFTER hydration — avoids Yjs clientID\n // conflict detection that would reassign to a random value.\n doc.clientID = numericClientId\n // Conditional ensureContainers: skip fields that already exist\n // from hydrated state (each set() is a CRDT write).\n ensureContainers(doc, schema, true)\n return createYjsSubstrate(doc, schema)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n // Fresh doc — set identity immediately, unconditional containers.\n const doc = new Y.Doc()\n doc.clientID = numericClientId\n ensureContainers(doc, schema)\n return createYjsSubstrate(doc, schema)\n },\n\n fromEntirety(\n payload: SubstratePayload,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n // Two-phase path: createReplica → merge → upgrade\n // Identity is set at upgrade() time, after hydration —\n // avoids Yjs clientID conflict detection.\n const replica = this.createReplica()\n replica.merge(payload)\n return this.upgrade(replica, schema)\n },\n\n parseVersion(serialized: string): YjsVersion {\n return YjsVersion.parse(serialized)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// yjs — the Yjs CRDT substrate namespace\n// ---------------------------------------------------------------------------\n\n/**\n * The Yjs CRDT substrate namespace.\n *\n * - `yjs.bind(schema)` — collaborative sync (default)\n * - `yjs.bind(schema, \"ephemeral\")` — ephemeral/presence broadcast\n * - `yjs.replica()` — collaborative replication (default)\n * - `yjs.replica(\"ephemeral\")` — ephemeral replication\n *\n * Strategy is constrained to `CrdtStrategy` (`\"collaborative\" | \"ephemeral\"`).\n * Passing `\"authoritative\"` is a compile error.\n *\n * To access the underlying Y.Doc, use `unwrap(ref)` from `@kyneta/schema`.\n */\n/** The closed set of capability tags that the Yjs substrate supports. */\nexport type YjsCaps = \"text\" | \"json\"\n\nexport const yjs: SubstrateNamespace<CrdtStrategy, YjsCaps, YjsNativeMap> =\n createSubstrateNamespace<CrdtStrategy, YjsCaps, YjsNativeMap>({\n strategies: {\n collaborative: {\n factory: ctx => createYjsFactory(ctx.peerId),\n replicaFactory: yjsReplicaFactory,\n },\n ephemeral: {\n factory: ctx => createYjsFactory(ctx.peerId),\n replicaFactory: yjsReplicaFactory,\n },\n },\n defaultStrategy: \"collaborative\",\n })\n","// populate — Yjs container creation from schema structure.\n//\n// Ensures that the correct Yjs shared types (Y.Text, Y.Array, Y.Map)\n// exist in a Y.Doc's root map to match the schema structure, and that\n// scalar/sum fields are initialized with Zero.structural defaults.\n//\n// This is NOT seed data — it's structural completeness, matching what\n// PlainSubstrate does when it initializes its store with Zero.structural.\n// The Yjs store reader expects to find values at every schema path;\n// without this, unset scalars would return undefined instead of their\n// type-correct zero (\"\", 0, false).\n//\n// Root container strategy: All schema fields are children of a single\n// root `Y.Map` obtained via `doc.getMap(\"root\")`. This root map holds\n// shared types (Y.Text, Y.Array, Y.Map) and plain value slots uniformly.\n\nimport type { Schema as SchemaNode } from \"@kyneta/schema\"\nimport { KIND, STRUCTURAL_YJS_CLIENT_ID, Zero } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// ensureContainers — top-level entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure that a Y.Doc's root map contains the correct Yjs shared types\n * matching the schema structure.\n *\n * Obtains the root map via `doc.getMap(\"root\")`, reads the root product\n * schema's fields, and creates empty containers for each field within a\n * single `doc.transact()` call for atomicity.\n *\n * When `conditional` is true, fields that already exist in the root map\n * are skipped. This is the correct mode after hydration — containers\n * present from stored state must not be overwritten (each `rootMap.set()`\n * is a CRDT write that advances the version vector and may conflict\n * with stored operations).\n *\n * When `conditional` is false (default), all fields are created\n * unconditionally. This is the correct mode for fresh documents.\n *\n * **Structural identity:** This function temporarily sets `doc.clientID`\n * to `STRUCTURAL_YJS_CLIENT_ID` (0) for the duration of container creation,\n * then restores the caller's clientID. This produces byte-identical\n * structural ops across all peers, enabling Yjs deduplication on merge.\n *\n * @param doc - The Y.Doc to prepare\n * @param schema - The root document schema (a ProductSchema)\n * @param conditional - If true, skip fields that already exist in the root map.\n * Context: jj:smmulzkm (two-phase substrate construction)\n */\nexport function ensureContainers(\n doc: Y.Doc,\n schema: SchemaNode,\n conditional = false,\n): void {\n const rootMap = doc.getMap(\"root\")\n\n if (schema[KIND] !== \"product\") {\n return\n }\n\n // Switch to structural identity for deterministic container creation.\n // All peers produce byte-identical structural ops at clientID 0.\n const savedClientID = doc.clientID\n doc.clientID = STRUCTURAL_YJS_CLIENT_ID\n\n try {\n doc.transact(() => {\n for (const [key, fieldSchema] of Object.entries(schema.fields).sort(\n ([a], [b]) => a.localeCompare(b),\n )) {\n if (conditional && rootMap.has(key)) continue\n ensureRootField(rootMap, key, fieldSchema as SchemaNode)\n }\n })\n } finally {\n // Restore the caller's identity for application writes.\n doc.clientID = savedClientID\n }\n}\n\n// ---------------------------------------------------------------------------\n// ensureRootField — create a single root-level container\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure a root-level Yjs shared type exists for a schema field.\n *\n * Dispatches on `[KIND]`:\n * - `\"text\"` → empty Y.Text\n * - `\"product\"` → empty Y.Map (recursive for nested products)\n * - `\"sequence\"` → empty Y.Array\n * - `\"map\"` → empty Y.Map\n * - `\"scalar\"` / `\"sum\"` → Zero.structural default\n * - `\"counter\"` / `\"set\"` / `\"tree\"` / `\"movable\"` → throw (not supported by Yjs)\n */\nfunction ensureRootField(\n rootMap: Y.Map<unknown>,\n key: string,\n fieldSchema: SchemaNode,\n): void {\n switch (fieldSchema[KIND]) {\n case \"text\":\n rootMap.set(key, new Y.Text())\n return\n\n case \"product\":\n rootMap.set(key, ensureMapContainers(fieldSchema))\n return\n\n case \"sequence\":\n rootMap.set(key, new Y.Array())\n return\n\n case \"map\":\n rootMap.set(key, new Y.Map())\n return\n\n case \"scalar\":\n case \"sum\": {\n // Plain values don't need shared type containers, but they DO\n // need structural zero defaults so the store reader returns\n // type-correct values (e.g. \"\" not undefined for strings).\n const zero = Zero.structural(fieldSchema)\n if (zero !== undefined) {\n rootMap.set(key, zero)\n }\n return\n }\n\n case \"counter\":\n case \"set\":\n case \"tree\":\n case \"movable\":\n throw new Error(\n `Yjs substrate does not support [KIND]=\"${fieldSchema[KIND]}\". ` +\n `Supported kinds: text, product, sequence, map, scalar, sum. ` +\n `Encountered unsupported kind at root field \"${key}\".`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// ensureMapContainers — recursively create nested Y.Map structure\n// ---------------------------------------------------------------------------\n\n/**\n * Create an empty Y.Map with nested shared type children matching\n * the product schema's field structure.\n *\n * Only creates containers for fields that require Yjs shared types\n * (text → Y.Text, product → Y.Map, sequence → Y.Array, map → Y.Map).\n * Scalar and sum fields are set to their structural zero defaults.\n */\nfunction ensureMapContainers(schema: SchemaNode): Y.Map<unknown> {\n const map = new Y.Map()\n\n if (schema[KIND] !== \"product\") return map\n\n for (const [key, fieldSchema] of Object.entries(\n schema.fields as Record<string, SchemaNode>,\n ).sort(([a], [b]) => a.localeCompare(b))) {\n switch (fieldSchema[KIND]) {\n case \"text\":\n map.set(key, new Y.Text())\n break\n\n case \"product\":\n map.set(key, ensureMapContainers(fieldSchema))\n break\n\n case \"sequence\":\n map.set(key, new Y.Array())\n break\n\n case \"map\":\n map.set(key, new Y.Map())\n break\n\n case \"scalar\":\n case \"sum\": {\n const zero = Zero.structural(fieldSchema)\n if (zero !== undefined) {\n map.set(key, zero)\n }\n break\n }\n\n case \"counter\":\n case \"set\":\n case \"tree\":\n case \"movable\":\n throw new Error(\n `Yjs substrate does not support [KIND]=\"${fieldSchema[KIND]}\". ` +\n `Supported kinds: text, product, sequence, map, scalar, sum. ` +\n `Encountered unsupported kind at nested field \"${key}\".`,\n )\n }\n }\n\n return map\n}\n","// substrate — YjsSubstrate implementation.\n//\n// Implements Substrate<YjsVersion> with:\n// - Imperative local writes (prepare accumulates, onFlush applies in transact)\n// - Persistent observeDeep event bridge for external changes\n// - Single re-entrancy guard + transaction.origin check\n//\n// The event bridge contract: wrapping a Y.Doc in a kyneta substrate\n// means subscribing to the kyneta doc observes ALL mutations to the\n// underlying Y.Doc, regardless of source (local kyneta writes,\n// merge, external Y.applyUpdate, external raw Yjs API mutations).\n\nimport type {\n ChangeBase,\n Path,\n Reader,\n Replica,\n ReplicaFactory,\n Schema as SchemaNode,\n Substrate,\n SubstrateFactory,\n SubstratePayload,\n WritableContext,\n} from \"@kyneta/schema\"\nimport {\n BACKING_DOC,\n buildWritableContext,\n executeBatch,\n KIND,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { applyChangeToYjs, eventsToOps } from \"./change-mapping.js\"\nimport { ensureContainers } from \"./populate.js\"\nimport { yjsReader } from \"./reader.js\"\nimport { YjsVersion } from \"./version.js\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Origin tag — used to suppress echo from our own transactions\n// ---------------------------------------------------------------------------\n\nconst KYNETA_ORIGIN = \"kyneta-prepare\"\n\n// ---------------------------------------------------------------------------\n// createYjsSubstrate — wrap a user-provided Y.Doc\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a `Substrate<YjsVersion>` wrapping a user-provided Y.Doc.\n *\n * This is the \"bring your own doc\" entry point. The user creates and\n * manages the Y.Doc (possibly via a Yjs provider); this function wraps\n * it with a schema-aware overlay providing typed reads, writes,\n * versioning, and export/merge through the standard Substrate interface.\n *\n * **Event bridge contract:** A persistent `observeDeep` handler is\n * registered on the root Y.Map at construction time. All non-kyneta\n * mutations to the Y.Doc (merges, external local writes) are bridged\n * to the kyneta changefeed. Subscribing to the kyneta doc observes all\n * mutations regardless of source.\n *\n * @param doc - The Y.Doc to wrap. The substrate does NOT own the doc;\n * the caller is responsible for its lifecycle.\n * @param schema - The root schema for the document.\n */\nexport function createYjsSubstrate(\n doc: Y.Doc,\n schema: SchemaNode,\n): Substrate<YjsVersion> {\n // --- Closure-scoped state ---\n\n // Accumulated changes from prepare(), drained by onFlush().\n const pendingChanges: Array<{ path: Path; change: ChangeBase }> = []\n\n // Re-entrancy guard: set true around our doc.transact() in onFlush\n // AND around executeBatch in the event bridge. When true, prepare()\n // skips Yjs-side work (changes are already applied by Yjs or about\n // to be), and onFlush() skips transact/commit.\n let inOurTransaction = false\n\n // Stashed origin from merge for the event bridge to pick up.\n let pendingMergeOrigin: string | undefined\n\n // Lazy-built WritableContext (same pattern as PlainSubstrate / LoroSubstrate).\n let cachedCtx: WritableContext | undefined\n\n // The root Y.Map — all schema fields are children of this single map.\n const rootMap = doc.getMap(\"root\")\n\n // The Reader — live view over the Yjs shared type tree.\n const reader: Reader = yjsReader(doc, schema)\n\n // --- Substrate object ---\n\n const substrate = {\n [BACKING_DOC]: doc,\n\n reader: reader,\n\n prepare(path: Path, change: ChangeBase): void {\n if (!inOurTransaction) {\n // Local write: accumulate for flush.\n // No Yjs side effects — mutations happen at flush time.\n pendingChanges.push({ path, change })\n }\n // During event handler replay: no-op on Yjs side.\n // wrappedPrepare (changefeed layer) still buffers the op.\n },\n\n onFlush(_origin?: string): void {\n if (!inOurTransaction && pendingChanges.length > 0) {\n // Local write: apply accumulated changes within a single\n // Yjs transaction tagged with our origin for echo suppression.\n inOurTransaction = true\n try {\n doc.transact(() => {\n for (const { path, change } of pendingChanges) {\n applyChangeToYjs(rootMap, schema, path, change)\n }\n }, KYNETA_ORIGIN)\n pendingChanges.length = 0\n } finally {\n inOurTransaction = false\n }\n }\n // During event handler replay: no-op on Yjs side.\n // wrappedFlush (changefeed layer) still delivers notifications.\n },\n\n context(): WritableContext {\n if (!cachedCtx) {\n cachedCtx = buildWritableContext(substrate)\n // Attach nativeResolver — used by interpretImpl to set [NATIVE]\n // on every ref. The resolver maps schema positions to Yjs shared types.\n ;(cachedCtx as any).nativeResolver = (\n nodeSchema: SchemaNode,\n path: { segments: readonly unknown[] },\n ) => {\n if (path.segments.length === 0) return doc\n if (nodeSchema[KIND] === \"scalar\" || nodeSchema[KIND] === \"sum\")\n return undefined\n return resolveYjsType(rootMap, schema, path as any)\n }\n }\n return cachedCtx\n },\n\n version(): YjsVersion {\n return new YjsVersion(Y.encodeStateVector(doc))\n },\n\n baseVersion(): YjsVersion {\n // Yjs substrate: base is always the initial state (no advance supported).\n return new YjsVersion(new Uint8Array([0]))\n },\n\n advance(_to: YjsVersion): void {\n throw new Error(\n \"advance() on a live Yjs substrate is not yet supported. \" +\n \"Use advance() on a YjsReplica instead.\",\n )\n },\n\n exportEntirety(): SubstratePayload {\n return {\n kind: \"entirety\",\n encoding: \"binary\",\n data: Y.encodeStateAsUpdate(doc),\n }\n },\n\n exportSince(since: YjsVersion): SubstratePayload | null {\n try {\n const bytes = Y.encodeStateAsUpdate(doc, since.sv)\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, origin?: string): void {\n if (\n payload.encoding !== \"binary\" ||\n !(payload.data instanceof Uint8Array)\n ) {\n throw new Error(\n \"YjsSubstrate.merge expects binary-encoded payloads. \" +\n \"If you recently switched CRDT backends, stale clients may be sending incompatible data.\",\n )\n }\n // Stash origin for the event bridge to pick up\n pendingMergeOrigin = origin\n try {\n Y.applyUpdate(doc, payload.data, origin ?? \"remote\")\n } finally {\n pendingMergeOrigin = undefined\n }\n // That's it — the observeDeep handler bridges events to the\n // changefeed via executeBatch.\n },\n }\n\n // --- Event bridge (registered once at construction) ---\n\n rootMap.observeDeep((events, transaction) => {\n // Ignore our own transactions (changefeed already captured via wrappedPrepare)\n if (transaction.origin === KYNETA_ORIGIN) {\n return\n }\n\n // Convert Yjs events → kyneta Ops\n const ops = eventsToOps(events, schema)\n if (ops.length === 0) {\n return\n }\n\n // Determine origin: prefer stashed kyneta origin (from merge),\n // fall back to the transaction's origin if it's a string.\n const origin =\n pendingMergeOrigin ??\n (typeof transaction.origin === \"string\" ? transaction.origin : undefined)\n\n // Lazily ensure the context is built\n const ctx = substrate.context()\n\n // Feed through executeBatch for changefeed delivery.\n // The inOurTransaction guard prevents prepare/onFlush from doing\n // Yjs-side work — the changes are already applied by Yjs.\n inOurTransaction = true\n try {\n executeBatch(ctx, ops, origin)\n } finally {\n inOurTransaction = false\n }\n })\n\n return substrate as Substrate<YjsVersion>\n}\n\n// ---------------------------------------------------------------------------\n// yjsSubstrateFactory — SubstrateFactory<YjsVersion>\n// ---------------------------------------------------------------------------\n\n/**\n * Factory for constructing Yjs-backed substrates.\n *\n * - `create(schema)` — creates a fresh Y.Doc with empty containers\n * matching the schema structure. No seed data — initial content\n * should be applied via `change()` after construction.\n * - `fromEntirety(payload, schema)` — creates a Y.Doc from an entirety\n * payload, returns a substrate.\n * - `parseVersion(serialized)` — deserializes a YjsVersion.\n */\n// ---------------------------------------------------------------------------\n// yjsReplicaFactory — ReplicaFactory<YjsVersion>\n// ---------------------------------------------------------------------------\n\n/**\n * Schema-free replica factory for Yjs substrates.\n *\n * Constructs headless `Replica<YjsVersion>` instances backed by bare\n * `Y.Doc`s — no schema walking, no container initialization, no\n * Reader, no event bridge, no changefeed. Just the CRDT runtime\n * with version tracking and export/merge.\n *\n * Used by conduit participants (stores, routing servers)\n * that need to accumulate state, compute per-peer deltas, and compact\n * storage without ever interpreting document fields.\n */\nexport function createYjsReplica(doc: Y.Doc): Replica<YjsVersion> {\n let currentDoc = doc\n let currentBase: YjsVersion = new YjsVersion(Y.encodeStateVector(new Y.Doc()))\n\n return {\n get [BACKING_DOC]() {\n return currentDoc\n },\n\n version(): YjsVersion {\n return new YjsVersion(Y.encodeStateVector(currentDoc))\n },\n\n baseVersion(): YjsVersion {\n return currentBase\n },\n\n advance(to: YjsVersion): void {\n const baseCmp = currentBase.compare(to)\n if (baseCmp === \"ahead\") {\n throw new Error(\"advance(): target is behind base version\")\n }\n const currentCmp = to.compare(this.version())\n if (currentCmp === \"ahead\") {\n throw new Error(\"advance(): target is ahead of current version\")\n }\n\n // Yjs can only do full projection (to = version).\n // For any to < version, it's a no-op — undershoot contract.\n if (currentCmp !== \"equal\") return\n\n // Full projection: create a new doc with current state, no history.\n const update = Y.encodeStateAsUpdate(currentDoc)\n const newDoc = new Y.Doc()\n Y.applyUpdate(newDoc, update)\n currentDoc = newDoc\n currentBase = new YjsVersion(Y.encodeStateVector(currentDoc))\n },\n\n exportEntirety(): SubstratePayload {\n return {\n kind: \"entirety\",\n encoding: \"binary\",\n data: Y.encodeStateAsUpdate(currentDoc),\n }\n },\n\n exportSince(since: YjsVersion): SubstratePayload | null {\n try {\n const bytes = Y.encodeStateAsUpdate(currentDoc, since.sv)\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, _origin?: string): void {\n if (\n payload.encoding !== \"binary\" ||\n !(payload.data instanceof Uint8Array)\n ) {\n throw new Error(\n \"YjsReplica.merge expects binary-encoded payloads. \" +\n \"If you recently switched CRDT backends, stale clients may be sending incompatible data.\",\n )\n }\n Y.applyUpdate(currentDoc, payload.data)\n },\n } as Replica<YjsVersion>\n}\n\nexport const yjsReplicaFactory: ReplicaFactory<YjsVersion> = {\n replicaType: [\"yjs\", 1, 0] as const,\n\n createEmpty(): Replica<YjsVersion> {\n return createYjsReplica(new Y.Doc())\n },\n\n fromEntirety(payload: SubstratePayload): Replica<YjsVersion> {\n if (\n payload.encoding !== \"binary\" ||\n !(payload.data instanceof Uint8Array)\n ) {\n throw new Error(\n \"YjsReplicaFactory.fromEntirety only supports binary-encoded payloads\",\n )\n }\n const doc = new Y.Doc()\n Y.applyUpdate(doc, payload.data)\n return createYjsReplica(doc)\n },\n\n parseVersion(serialized: string): YjsVersion {\n return YjsVersion.parse(serialized)\n },\n}\n\n// ---------------------------------------------------------------------------\n// yjsSubstrateFactory — SubstrateFactory<YjsVersion>\n// ---------------------------------------------------------------------------\n\nexport const yjsSubstrateFactory: SubstrateFactory<YjsVersion> = {\n replica: yjsReplicaFactory,\n\n createReplica(): Replica<YjsVersion> {\n // Default random clientID — safe for hydration (no local writes).\n return createYjsReplica(new Y.Doc())\n },\n\n upgrade(\n replica: Replica<YjsVersion>,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n const doc = (replica as any)[BACKING_DOC] as Y.Doc\n // No identity injection for the standalone factory (no peerId).\n // Conditional ensureContainers: skip fields that already exist\n // from hydrated state.\n ensureContainers(doc, schema, true)\n return createYjsSubstrate(doc, schema)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n // Fresh doc — unconditional ensureContainers (nothing to conflict with).\n const doc = new Y.Doc()\n ensureContainers(doc, schema)\n return createYjsSubstrate(doc, schema)\n },\n\n fromEntirety(\n payload: SubstratePayload,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n // Two-phase path: createReplica → merge → upgrade\n const replica = this.createReplica()\n replica.merge(payload)\n return this.upgrade(replica, schema)\n },\n\n parseVersion(serialized: string): YjsVersion {\n return YjsVersion.parse(serialized)\n },\n}\n","// change-mapping — bidirectional change mapping between kyneta and Yjs.\n//\n// Two directions:\n//\n// 1. kyneta → Yjs (`applyChangeToYjs`): Resolves the target Yjs shared\n// type at a path, then applies the change imperatively via Yjs API.\n// No intermediate diff format — direct imperative mutations.\n//\n// 2. Yjs → kyneta (`eventsToOps`): Converts `observeDeep` events into\n// kyneta `Op[]` for changefeed delivery. Each Y.YEvent maps to one Op\n// with a path derived from `event.path` (relative to the observed root\n// Y.Map) and a Change derived from the event's delta/keys.\n//\n// Structured inserts use populate-then-attach order: new shared types\n// are fully populated before being inserted into their parent container.\n// This produces a single observeDeep event with the complete struct,\n// rather than a cascade of child MapChange events.\n\nimport type {\n ChangeBase,\n IncrementChange,\n MapChange,\n Op,\n Path,\n ReplaceChange,\n Schema as SchemaNode,\n SequenceChange,\n SequenceInstruction,\n TextChange,\n TextInstruction,\n} from \"@kyneta/schema\"\nimport {\n advanceSchema,\n expandMapOpsToLeaves,\n KIND,\n RawPath,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Direction 1: kyneta → Yjs (`applyChangeToYjs`)\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a kyneta Change to the Yjs shared type tree imperatively.\n *\n * Resolves the target shared type at `path`, then applies the change\n * via the appropriate Yjs API. Must be called within a `doc.transact()`\n * for atomicity and correct event batching.\n *\n * @param rootMap - The root `Y.Map` obtained via `doc.getMap(\"root\")`\n * @param rootSchema - The root document schema\n * @param path - The path to the target\n * @param change - The kyneta Change to apply\n */\nexport function applyChangeToYjs(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: ChangeBase,\n): void {\n switch (change.type) {\n case \"text\":\n applyTextChange(rootMap, rootSchema, path, change as TextChange)\n return\n\n case \"sequence\":\n applySequenceChange(rootMap, rootSchema, path, change as SequenceChange)\n return\n\n case \"map\":\n applyMapChange(rootMap, rootSchema, path, change as MapChange)\n return\n\n case \"replace\":\n applyReplaceChange(rootMap, rootSchema, path, change as ReplaceChange)\n return\n\n case \"increment\":\n throw new Error(\n `Yjs substrate does not support \"${change.type}\" changes. ` +\n `Counter requires a CRDT backend that supports counters (e.g. Loro). ` +\n `Attempted IncrementChange with amount=${(change as IncrementChange).amount} at path [${pathToString(path)}].`,\n )\n\n case \"tree\":\n throw new Error(\n `Yjs substrate does not support \"${change.type}\" changes. ` +\n `Tree requires a CRDT backend that supports trees (e.g. Loro). ` +\n `Attempted TreeChange at path [${pathToString(path)}].`,\n )\n\n default:\n throw new Error(\n `applyChangeToYjs: unsupported change type \"${change.type}\"`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Text change\n// ---------------------------------------------------------------------------\n\nfunction applyTextChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: TextChange,\n): void {\n const resolved = resolveYjsType(rootMap, rootSchema, path)\n if (!(resolved instanceof Y.Text)) {\n throw new Error(\n `applyChangeToYjs: TextChange target at path [${pathToString(path)}] is not a Y.Text`,\n )\n }\n\n // Yjs Y.Text.applyDelta uses the Quill Delta format, which is\n // structurally identical to kyneta TextInstruction[].\n resolved.applyDelta(change.instructions as any)\n}\n\n// ---------------------------------------------------------------------------\n// Sequence change\n// ---------------------------------------------------------------------------\n\nfunction applySequenceChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: SequenceChange,\n): void {\n const resolved = resolveYjsType(rootMap, rootSchema, path)\n if (!(resolved instanceof Y.Array)) {\n throw new Error(\n `applyChangeToYjs: SequenceChange target at path [${pathToString(path)}] is not a Y.Array`,\n )\n }\n\n // Resolve the item schema for structured insert detection\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n const itemSchema = getItemSchema(targetSchema)\n\n let cursor = 0\n for (const instruction of change.instructions) {\n if (\"retain\" in instruction) {\n cursor += instruction.retain\n } else if (\"delete\" in instruction) {\n resolved.delete(cursor, instruction.delete)\n // cursor stays — deleted items shift remaining items down\n } else if (\"insert\" in instruction) {\n const items = instruction.insert as readonly unknown[]\n const yjsItems = items.map(item =>\n maybeCreateSharedType(item, itemSchema),\n )\n resolved.insert(cursor, yjsItems)\n cursor += items.length\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Map change\n// ---------------------------------------------------------------------------\n\nfunction applyMapChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: MapChange,\n): void {\n const resolved = resolveYjsType(rootMap, rootSchema, path)\n if (!(resolved instanceof Y.Map)) {\n throw new Error(\n `applyChangeToYjs: MapChange target at path [${pathToString(path)}] is not a Y.Map`,\n )\n }\n\n // Resolve the schema at this path for structured value detection\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n\n // Apply deletes first\n if (change.delete) {\n for (const key of change.delete) {\n resolved.delete(key)\n }\n }\n\n // Apply sets\n if (change.set) {\n for (const [key, value] of Object.entries(change.set)) {\n const fieldSchema = getFieldSchema(targetSchema, key)\n const yjsValue = maybeCreateSharedType(value, fieldSchema)\n resolved.set(key, yjsValue)\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Replace change\n// ---------------------------------------------------------------------------\n\nfunction applyReplaceChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: ReplaceChange,\n): void {\n if (path.length === 0) {\n throw new Error(\n \"applyChangeToYjs: ReplaceChange at root path is not supported\",\n )\n }\n\n // Target the parent container, using the last segment to identify\n // which child to replace.\n const lastSeg = path.segments[path.segments.length - 1]!\n const parentPath = path.slice(0, -1)\n const parent = resolveYjsType(rootMap, rootSchema, parentPath)\n\n const resolved = lastSeg.resolve()\n if (parent instanceof Y.Map && lastSeg.role === \"key\") {\n // Resolve schema for the target field for structured value detection\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n const yjsValue = maybeCreateSharedType(change.value, targetSchema)\n parent.set(resolved as string, yjsValue)\n } else if (parent instanceof Y.Array && lastSeg.role === \"index\") {\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n const yjsValue = maybeCreateSharedType(change.value, targetSchema)\n parent.delete(resolved as number, 1)\n parent.insert(resolved as number, [yjsValue])\n } else {\n throw new Error(\n `applyChangeToYjs: ReplaceChange parent at path [${pathToString(parentPath)}] ` +\n `is not a Y.Map or Y.Array (got ${typeof parent})`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Structured value creation (populate-then-attach pattern)\n// ---------------------------------------------------------------------------\n\n/**\n * If the schema says the value should be a shared type (product → Y.Map,\n * sequence → Y.Array, text → Y.Text), create and populate it.\n * Otherwise return the plain value as-is.\n *\n * Uses populate-then-attach: the new shared type is fully populated\n * before being returned for insertion into its parent.\n */\nfunction maybeCreateSharedType(\n value: unknown,\n schema: SchemaNode | undefined,\n): unknown {\n if (schema === undefined) return value\n\n switch (schema[KIND]) {\n // First-class text → Y.Text\n case \"text\": {\n const text = new Y.Text()\n if (typeof value === \"string\" && value.length > 0) {\n text.insert(0, value)\n }\n return text\n }\n\n case \"product\": {\n if (\n value === null ||\n value === undefined ||\n typeof value !== \"object\" ||\n Array.isArray(value)\n ) {\n return value\n }\n return createStructuredMap(value as Record<string, unknown>, schema)\n }\n\n case \"sequence\": {\n if (!Array.isArray(value)) return value\n const arr = new Y.Array()\n const itemSchema = schema.item\n const items = (value as unknown[]).map(item =>\n maybeCreateSharedType(item, itemSchema),\n )\n arr.insert(0, items)\n return arr\n }\n\n case \"map\": {\n if (\n value === null ||\n value === undefined ||\n typeof value !== \"object\" ||\n Array.isArray(value)\n ) {\n return value\n }\n const map = new Y.Map()\n const valueSchema = schema.item\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n map.set(k, maybeCreateSharedType(v, valueSchema))\n }\n return map\n }\n\n // Unsupported first-class CRDT types — should not reach here\n // (rejected at bind time by caps check)\n case \"counter\":\n case \"set\":\n case \"tree\":\n case \"movable\":\n throw new Error(\n `Yjs substrate does not support [KIND]=\"${schema[KIND]}\". ` +\n `This should have been caught at bind() time.`,\n )\n\n default:\n // Scalar, sum — return as plain value\n return value\n }\n}\n\n/**\n * Create a Y.Map from a plain object, recursively creating nested\n * shared types as guided by the product schema.\n *\n * Follows populate-then-attach: fully populates the map before the\n * caller inserts it into a parent container.\n */\nfunction createStructuredMap(\n obj: Record<string, unknown>,\n productSchema: SchemaNode,\n): Y.Map<any> {\n const map = new Y.Map()\n\n if (productSchema[KIND] !== \"product\") {\n // Fallback: set all values as plain\n for (const [key, val] of Object.entries(obj)) {\n map.set(key, val)\n }\n return map\n }\n\n // Process fields present in the value object\n for (const [key, val] of Object.entries(obj)) {\n if (val === undefined) continue\n const fieldSchema = productSchema.fields[key]\n const yjsVal = fieldSchema ? maybeCreateSharedType(val, fieldSchema) : val\n map.set(key, yjsVal)\n }\n\n // Create shared types for first-class CRDT fields declared in the schema\n // but missing from the value object. This ensures Yjs containers\n // exist for later mutation (e.g. .insert() on a text field inside\n // a struct inside a record/list).\n for (const [key, fieldSchema] of Object.entries(\n productSchema.fields as Record<string, SchemaNode>,\n )) {\n if (key in obj) continue // already processed above\n if (fieldSchema[KIND] === \"text\") {\n map.set(key, new Y.Text())\n }\n }\n\n return map\n}\n\n// ---------------------------------------------------------------------------\n// Direction 2: Yjs → kyneta (`eventsToOps`)\n// ---------------------------------------------------------------------------\n\n/**\n * Convert `observeDeep` events into kyneta `Op[]` for changefeed delivery.\n *\n * Each `Y.YEvent` in the array maps to one Op with:\n * - `path`: derived from `event.path` (relative to the observed root Y.Map)\n * - `change`: derived from the event's delta/keys based on target type\n *\n * `event.path` in `observeDeep` is relative to the observed shared type.\n * Since we observe `rootMap` (the single root Y.Map), paths map directly\n * to kyneta `PathSegment[]`.\n *\n * @param events - The events from the `observeDeep` callback\n */\nexport function eventsToOps(events: Y.YEvent<any>[], schema: SchemaNode): Op[] {\n const ops: Op[] = []\n\n for (const event of events) {\n const kynetaPath = yjsPathToKynetaPath(event.path)\n const change = eventToChange(event)\n if (change) {\n ops.push({ path: kynetaPath, change })\n }\n }\n\n return expandMapOpsToLeaves(ops, schema)\n}\n\n// ---------------------------------------------------------------------------\n// Yjs path → kyneta Path conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a Yjs event path (array of string | number) to a kyneta Path.\n *\n * `event.path` from `observeDeep` is relative to the observed type.\n * Strings become key segments, numbers become index segments.\n */\nfunction yjsPathToKynetaPath(yjsPath: (string | number)[]): RawPath {\n let path = RawPath.empty\n for (const segment of yjsPath) {\n if (typeof segment === \"string\") {\n path = path.field(segment)\n } else if (typeof segment === \"number\") {\n path = path.item(segment)\n }\n }\n return path\n}\n\n// ---------------------------------------------------------------------------\n// Per-type event → Change converters\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a single Yjs event into a kyneta Change.\n * Returns null for event types we can't map.\n */\nfunction eventToChange(event: Y.YEvent<any>): ChangeBase | null {\n if (event.target instanceof Y.Text) {\n return textEventToChange(event)\n }\n if (event.target instanceof Y.Array) {\n return arrayEventToChange(event)\n }\n if (event.target instanceof Y.Map) {\n return mapEventToChange(event)\n }\n return null\n}\n\n/**\n * Y.Text event → TextChange.\n *\n * `event.delta` uses the Quill Delta format, structurally identical to\n * kyneta `TextInstruction[]`. We strip the `attributes` field (rich text\n * formatting not surfaced by kyneta).\n */\nfunction textEventToChange(event: Y.YEvent<any>): TextChange {\n const instructions: TextInstruction[] = []\n\n for (const delta of event.delta) {\n if (delta.retain !== undefined) {\n instructions.push({ retain: delta.retain as number })\n } else if (delta.insert !== undefined) {\n instructions.push({ insert: delta.insert as string })\n } else if (delta.delete !== undefined) {\n instructions.push({ delete: delta.delete as number })\n }\n }\n\n return { type: \"text\", instructions }\n}\n\n/**\n * Y.Array event → SequenceChange.\n *\n * `event.changes.delta` provides the same cursor-based ops as kyneta\n * SequenceInstruction[]. Container values (Y.Map, Y.Array) in insert\n * arrays are converted to plain objects via `.toJSON()`.\n */\nfunction arrayEventToChange(event: Y.YEvent<any>): SequenceChange {\n const instructions: SequenceInstruction[] = []\n\n for (const delta of event.changes.delta) {\n if (delta.retain !== undefined) {\n instructions.push({ retain: delta.retain as number })\n } else if (delta.delete !== undefined) {\n instructions.push({ delete: delta.delete as number })\n } else if (delta.insert !== undefined) {\n const items = (delta.insert as unknown[]).map((item: unknown) =>\n extractEventValue(item),\n )\n instructions.push({ insert: items })\n }\n }\n\n return { type: \"sequence\", instructions }\n}\n\n/**\n * Y.Map event → MapChange.\n *\n * `event.changes.keys` is a `Map<string, { action: 'add'|'update'|'delete', ... }>`.\n * - `action: 'add'|'update'` → `set[key] = map.get(key)`\n * - `action: 'delete'` → `delete.push(key)`\n */\nfunction mapEventToChange(event: Y.YEvent<any>): MapChange | null {\n const set: Record<string, unknown> = {}\n const deleteKeys: string[] = []\n let hasSet = false\n let hasDelete = false\n\n const target = event.target as Y.Map<any>\n\n event.changes.keys.forEach((change: { action: string }, key: string) => {\n if (change.action === \"add\" || change.action === \"update\") {\n const value = target.get(key)\n set[key] = extractEventValue(value)\n hasSet = true\n } else if (change.action === \"delete\") {\n deleteKeys.push(key)\n hasDelete = true\n }\n })\n\n if (!hasSet && !hasDelete) return null\n\n return {\n type: \"map\",\n ...(hasSet ? { set } : {}),\n ...(hasDelete ? { delete: deleteKeys } : {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Value extraction from Yjs events\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a Yjs value from an event into a plain value.\n * Container values (Y.Map, Y.Array, Y.Text) → `.toJSON()`.\n * Plain values → returned as-is.\n */\nfunction extractEventValue(value: unknown): unknown {\n if (value instanceof Y.Map) return value.toJSON()\n if (value instanceof Y.Array) return value.toJSON()\n if (value instanceof Y.Text) return value.toJSON()\n return value\n}\n\n// ---------------------------------------------------------------------------\n// Schema helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the schema at a given path by walking through advanceSchema.\n */\nfunction resolveSchemaAtPath(rootSchema: SchemaNode, path: Path): SchemaNode {\n let schema = rootSchema\n for (const seg of path.segments) {\n schema = advanceSchema(schema, seg)\n }\n return schema\n}\n\n/**\n * Get the item schema from a sequence schema, if available.\n */\nfunction getItemSchema(schema: SchemaNode): SchemaNode | undefined {\n if (schema[KIND] === \"sequence\") return schema.item\n if (schema[KIND] === \"movable\") return schema.item\n return undefined\n}\n\n/**\n * Get the field schema from a product or map schema for a given key.\n */\nfunction getFieldSchema(\n schema: SchemaNode,\n key: string,\n): SchemaNode | undefined {\n if (schema[KIND] === \"product\") {\n return schema.fields[key]\n }\n if (schema[KIND] === \"map\") {\n return schema.item\n }\n if (schema[KIND] === \"set\") {\n return schema.item\n }\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// Path formatting\n// ---------------------------------------------------------------------------\n\nfunction pathToString(path: Path): string {\n return path.segments.map(seg => String(seg.resolve())).join(\".\")\n}\n","// yjs-resolve — Yjs-specific path resolution.\n//\n// Implements stepIntoYjs and resolveYjsType for schema-guided\n// navigation of the Yjs shared type tree.\n//\n// resolveYjsType is a left-fold over path segments, accumulating\n// (currentType, currentSchema) at each step. This mirrors how\n// resolveContainer works for Loro — but uses `instanceof` for\n// runtime type discrimination instead of Loro's `.kind()` method.\n//\n// Root container strategy: All schema fields are children of a single\n// root `Y.Map` obtained via `doc.getMap(\"root\")`. This root map holds\n// shared types (Y.Text, Y.Array, Y.Map) and plain values uniformly.\n// Using a single root Y.Map enables one `observeDeep` call that\n// captures all mutations with correct relative paths.\n\nimport type { Path, Schema as SchemaNode, Segment } from \"@kyneta/schema\"\nimport { advanceSchema } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// stepIntoYjs — single step of the fold\n// ---------------------------------------------------------------------------\n\n/**\n * Navigate one step deeper into the Yjs shared type tree.\n *\n * Uses `instanceof` for runtime type discrimination:\n * - `Y.Map` → `.get(key)`\n * - `Y.Array` → `.get(index)`\n * - `Y.Text` → terminal (cannot step further)\n * - Plain value → terminal (return `undefined`)\n *\n * @param current - The current position (a Yjs shared type or plain value)\n * @param segment - The path segment to follow\n */\nexport function stepIntoYjs(current: unknown, segment: Segment): unknown {\n const resolved = segment.resolve()\n\n if (current instanceof Y.Map) {\n return current.get(resolved as string)\n }\n\n if (current instanceof Y.Array) {\n return current.get(resolved as number)\n }\n\n if (current instanceof Y.Text) {\n throw new Error(`yjs-resolve: cannot step into Y.Text`)\n }\n\n // Plain value — terminal, cannot step further\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// resolveYjsType — full path resolution via left-fold\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a Yjs shared type (or plain value) at the given path.\n *\n * Left-folds over path segments using `advanceSchema` for pure schema\n * descent and `stepIntoYjs` for Yjs-specific navigation.\n *\n * Returns the Yjs shared type or plain value at the terminal position.\n * For an empty path, returns the root map itself.\n *\n * @param rootMap - The root `Y.Map` obtained via `doc.getMap(\"root\")`\n * @param rootSchema - The root document schema\n * @param path - The path to resolve\n */\nexport function resolveYjsType(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n): unknown {\n let current: unknown = rootMap\n let schema = rootSchema\n\n for (let i = 0; i < path.length; i++) {\n const seg = path.segments[i]!\n const nextSchema = advanceSchema(schema, seg)\n\n // For the first segment, we step into the root map directly.\n // For subsequent segments, we use stepIntoYjs on the current value.\n current = stepIntoYjs(current, seg)\n schema = nextSchema\n }\n\n return current\n}\n","// store-reader — YjsReader implementation.\n//\n// Implements Reader via schema-guided live navigation of the\n// Yjs shared type tree. Each read operation resolves the shared type\n// at the given path using resolveYjsType, then extracts the\n// appropriate value based on `instanceof` discrimination.\n//\n// Y.Text → .toJSON() (string), Y.Map → .toJSON() (plain object),\n// Y.Array → .toJSON() (plain array), plain values → as-is.\n\nimport type { Path, Reader, Schema as SchemaNode } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Value extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract a plain value from a Yjs shared type or return a plain value as-is.\n *\n * - Y.Text → `.toJSON()` (string)\n * - Y.Map → `.toJSON()` (plain object snapshot — for product/map reads)\n * - Y.Array → `.toJSON()` (plain array snapshot)\n * - Plain values (string, number, boolean, null) → returned as-is\n */\nfunction extractValue(resolved: unknown): unknown {\n if (resolved instanceof Y.Text) {\n return resolved.toJSON()\n }\n if (resolved instanceof Y.Map) {\n return resolved.toJSON()\n }\n if (resolved instanceof Y.Array) {\n return resolved.toJSON()\n }\n // Plain scalar value (string, number, boolean, null, etc.)\n return resolved\n}\n\n// ---------------------------------------------------------------------------\n// yjsReader\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a Reader that navigates the Yjs shared type tree live,\n * using the schema as a type witness to determine navigation at each\n * path segment.\n *\n * The reader is a live view — mutations to the underlying Y.Doc\n * (via `doc.transact()`, or `Y.applyUpdate()`) are immediately\n * visible through the reader.\n *\n * Internally obtains the root map via `doc.getMap(\"root\")`.\n *\n * @param doc - The Y.Doc to read from.\n * @param schema - The root schema for the document.\n */\nexport function yjsReader(doc: Y.Doc, schema: SchemaNode): Reader {\n const rootMap = doc.getMap(\"root\")\n\n return {\n read(path: Path): unknown {\n if (path.length === 0) {\n // Root read — return the full root map as JSON\n return rootMap.toJSON()\n }\n const resolved = resolveYjsType(rootMap, schema, path)\n return extractValue(resolved)\n },\n\n arrayLength(path: Path): number {\n const resolved = resolveYjsType(rootMap, schema, path)\n if (resolved instanceof Y.Array) {\n return resolved.length\n }\n // Graceful fallback for plain array values\n if (Array.isArray(resolved)) {\n return resolved.length\n }\n return 0\n },\n\n keys(path: Path): string[] {\n const resolved = resolveYjsType(rootMap, schema, path)\n if (resolved instanceof Y.Map) {\n return Array.from(resolved.keys())\n }\n // Graceful fallback for plain object values\n if (\n resolved !== null &&\n resolved !== undefined &&\n typeof resolved === \"object\" &&\n !Array.isArray(resolved)\n ) {\n return Object.keys(resolved as Record<string, unknown>)\n }\n return []\n },\n\n hasKey(path: Path, key: string): boolean {\n const resolved = resolveYjsType(rootMap, schema, path)\n if (resolved instanceof Y.Map) {\n return resolved.has(key)\n }\n // Graceful fallback for plain object values\n if (\n resolved !== null &&\n resolved !== undefined &&\n typeof resolved === \"object\" &&\n !Array.isArray(resolved)\n ) {\n return key in (resolved as Record<string, unknown>)\n }\n return false\n },\n }\n}\n","// YjsVersion — Version implementation wrapping Yjs state vectors.\n//\n// Yjs state vectors (`Y.encodeStateVector(doc)`) are the complete peer\n// state used for sync diffing — matching the semantics of kyneta's\n// Version interface.\n//\n// Serialization uses base64-encoded bytes for text-safe embedding in\n// HTML meta tags, script tags, etc.\n//\n// Yjs does not export a state vector comparison function, so we\n// implement standard version-vector partial-order comparison over\n// decoded `Map<number, number>` (clientID → clock) maps ourselves.\n\nimport type { Version } from \"@kyneta/schema\"\nimport {\n base64ToUint8Array,\n uint8ArrayToBase64,\n versionVectorCompare,\n versionVectorMeet,\n} from \"@kyneta/schema\"\nimport { decodeStateVector } from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// State vector encoding — manual varint (unsigned LEB128)\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a state vector map to Yjs's binary state vector format.\n *\n * Yjs does not export `encodeStateVector(map)` — only `Y.encodeStateVector(doc)`\n * which requires a full doc. This implements the same binary format directly:\n * `[entryCount: varint, (clientId: varint, clock: varint)*]`\n *\n * Each value is encoded as an unsigned LEB128 varint.\n */\nfunction encodeStateVector(map: Map<number, number>): Uint8Array {\n const bytes: number[] = []\n\n function writeVarUint(value: number): void {\n while (value > 0x7f) {\n bytes.push((value & 0x7f) | 0x80)\n value >>>= 7\n }\n bytes.push(value & 0x7f)\n }\n\n writeVarUint(map.size)\n for (const [clientId, clock] of map) {\n writeVarUint(clientId)\n writeVarUint(clock)\n }\n\n return new Uint8Array(bytes)\n}\n\n// ---------------------------------------------------------------------------\n// YjsVersion\n// ---------------------------------------------------------------------------\n\n/**\n * A Version wrapping a Yjs state vector.\n *\n * State vectors track the complete peer state — which operations from\n * each client have been observed. This is the right abstraction for sync\n * diffing: `exportSince(version)` uses the state vector to compute the\n * minimal update payload via `Y.encodeStateAsUpdate(doc, sv)`.\n *\n * `serialize()` encodes to base64 for text-safe embedding.\n * `compare()` decodes both state vectors and performs standard\n * version-vector partial-order comparison over the client-clock maps.\n */\nexport class YjsVersion implements Version {\n readonly sv: Uint8Array\n\n constructor(sv: Uint8Array) {\n this.sv = sv\n }\n\n /**\n * Serialize the state vector to a base64 string.\n *\n * The encoding is: raw state vector bytes → base64.\n * This is text-safe for embedding in HTML meta tags, URL parameters, etc.\n */\n serialize(): string {\n return uint8ArrayToBase64(this.sv)\n }\n\n /**\n * Compare with another version using version-vector partial order.\n *\n * Delegates to the shared `versionVectorCompare` utility after decoding\n * both state vectors via `Y.decodeStateVector()`.\n *\n * @throws If `other` is not a `YjsVersion`.\n */\n compare(other: Version): \"behind\" | \"equal\" | \"ahead\" | \"concurrent\" {\n if (!(other instanceof YjsVersion)) {\n throw new Error(\"YjsVersion can only be compared with another YjsVersion\")\n }\n return versionVectorCompare(\n decodeStateVector(this.sv),\n decodeStateVector(other.sv),\n )\n }\n\n /**\n * Greatest lower bound (lattice meet) of two Yjs versions.\n *\n * Decodes both state vectors, computes the component-wise minimum\n * via the shared `versionVectorMeet` utility, and encodes the result\n * back to a Yjs state vector.\n *\n * @throws If `other` is not a `YjsVersion`.\n */\n meet(other: Version): YjsVersion {\n if (!(other instanceof YjsVersion)) {\n throw new Error(\"YjsVersion can only be meet'd with another YjsVersion\")\n }\n const thisMap = decodeStateVector(this.sv)\n const otherMap = decodeStateVector(other.sv)\n const result = versionVectorMeet(thisMap, otherMap)\n return new YjsVersion(encodeStateVector(result))\n }\n\n /**\n * Parse a serialized YjsVersion string back into a YjsVersion.\n *\n * The inverse of `serialize()`: base64 → `Uint8Array`.\n */\n static parse(serialized: string): YjsVersion {\n if (serialized === \"\") {\n throw new Error(\"Invalid YjsVersion value: (empty string)\")\n }\n const bytes = base64ToUint8Array(serialized)\n return new YjsVersion(bytes)\n }\n}\n"],"mappings":"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","names":["BACKING_DOC","STRUCTURAL_YJS_CLIENT_ID","Y","KIND","Y","advanceSchema","KIND","Y","Y","change","KIND","advanceSchema","Y","change","KIND","STRUCTURAL_YJS_CLIENT_ID","BACKING_DOC"]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["encodeStateVector","yjsEncodeStateVector","yjsSnapshot"],"sources":["../src/populate.ts","../src/yjs-resolve.ts","../src/change-mapping.ts","../src/position.ts","../src/reader.ts","../src/version.ts","../src/substrate.ts","../src/bind-yjs.ts"],"sourcesContent":["// populate — Yjs container creation from schema structure.\n//\n// Ensures that the correct Yjs shared types (Y.Text, Y.Array, Y.Map)\n// exist in a Y.Doc's root map to match the schema structure, and that\n// scalar/sum fields are initialized with Zero.structural defaults.\n//\n// This is NOT seed data — it's structural completeness, matching what\n// PlainSubstrate does when it initializes its store with Zero.structural.\n// The Yjs store reader expects to find values at every schema path;\n// without this, unset scalars would return undefined instead of their\n// type-correct zero (\"\", 0, false).\n//\n// Root container strategy: All schema fields are children of a single\n// root `Y.Map` obtained via `doc.getMap(\"root\")`. This root map holds\n// shared types (Y.Text, Y.Array, Y.Map) and plain value slots uniformly.\n//\n// Identity-keying: when a SchemaBinding is provided, every product-field\n// boundary uses the identity hash (from binding.forward) instead of the\n// field name as the Y.Map key. The binding is threaded through all three\n// functions: ensureContainers, ensureRootField, ensureMapContainers.\n\nimport type { SchemaBinding, Schema as SchemaNode } from \"@kyneta/schema\"\nimport { KIND, STRUCTURAL_YJS_CLIENT_ID, Zero } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// ensureContainers — top-level entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure that a Y.Doc's root map contains the correct Yjs shared types\n * matching the schema structure.\n *\n * Obtains the root map via `doc.getMap(\"root\")`, reads the root product\n * schema's fields, and creates empty containers for each field within a\n * single `doc.transact()` call for atomicity.\n *\n * When `conditional` is true, fields that already exist in the root map\n * are skipped. This is the correct mode after hydration — containers\n * present from stored state must not be overwritten (each `rootMap.set()`\n * is a CRDT write that advances the version vector and may conflict\n * with stored operations).\n *\n * When `conditional` is false (default), all fields are created\n * unconditionally. This is the correct mode for fresh documents.\n *\n * **Structural identity:** This function temporarily sets `doc.clientID`\n * to `STRUCTURAL_YJS_CLIENT_ID` (0) for the duration of container creation,\n * then restores the caller's clientID. This produces byte-identical\n * structural ops across all peers, enabling Yjs deduplication on merge.\n *\n * **Identity-keying:** When a `binding` is provided, each root field's\n * key in the root Y.Map is the identity hash from `binding.forward`\n * instead of the field name. Nested product fields are similarly keyed\n * via `ensureMapContainers`.\n *\n * @param doc - The Y.Doc to prepare\n * @param schema - The root document schema (a ProductSchema)\n * @param conditional - If true, skip fields that already exist in the root map.\n * Context: jj:smmulzkm (two-phase substrate construction)\n * @param binding - Optional SchemaBinding for identity-keyed containers.\n */\nexport function ensureContainers(\n doc: Y.Doc,\n schema: SchemaNode,\n conditional = false,\n binding?: SchemaBinding,\n): void {\n const rootMap = doc.getMap(\"root\")\n\n if (schema[KIND] !== \"product\") {\n return\n }\n\n // Switch to structural identity for deterministic container creation.\n // All peers produce byte-identical structural ops at clientID 0.\n const savedClientID = doc.clientID\n doc.clientID = STRUCTURAL_YJS_CLIENT_ID\n\n try {\n doc.transact(() => {\n for (const [key, fieldSchema] of Object.entries(schema.fields).sort(\n ([a], [b]) => a.localeCompare(b),\n )) {\n const identity = binding?.forward.get(key) as string | undefined\n const mapKey = identity ?? key\n if (conditional && rootMap.has(mapKey)) continue\n ensureRootField(\n rootMap,\n mapKey,\n fieldSchema as SchemaNode,\n binding,\n key,\n )\n }\n })\n } finally {\n // Restore the caller's identity for application writes.\n doc.clientID = savedClientID\n }\n}\n\n// ---------------------------------------------------------------------------\n// ensureRootField — create a single root-level container\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure a root-level Yjs shared type exists for a schema field.\n *\n * Dispatches on `[KIND]`:\n * - `\"text\"` → empty Y.Text\n * - `\"product\"` → empty Y.Map (recursive for nested products)\n * - `\"sequence\"` → empty Y.Array\n * - `\"map\"` → empty Y.Map\n * - `\"scalar\"` / `\"sum\"` → Zero.structural default\n * - `\"counter\"` / `\"set\"` / `\"tree\"` / `\"movable\"` → throw (not supported by Yjs)\n *\n * @param rootMap - The root Y.Map to set the field on.\n * @param key - The key to use in the root map (identity hash or field name).\n * @param fieldSchema - The schema for this field.\n * @param binding - Optional SchemaBinding for nested identity-keying.\n * @param prefix - The absolute schema path prefix for this field (used for nested lookups).\n */\nfunction ensureRootField(\n rootMap: Y.Map<unknown>,\n key: string,\n fieldSchema: SchemaNode,\n binding?: SchemaBinding,\n prefix?: string,\n): void {\n switch (fieldSchema[KIND]) {\n case \"text\":\n case \"richtext\":\n rootMap.set(key, new Y.Text())\n return\n\n case \"product\":\n rootMap.set(key, ensureMapContainers(fieldSchema, binding, prefix))\n return\n\n case \"sequence\":\n rootMap.set(key, new Y.Array())\n return\n\n case \"map\":\n rootMap.set(key, new Y.Map())\n return\n\n case \"scalar\":\n case \"sum\": {\n // Plain values don't need shared type containers, but they DO\n // need structural zero defaults so the store reader returns\n // type-correct values (e.g. \"\" not undefined for strings).\n const zero = Zero.structural(fieldSchema)\n if (zero !== undefined) {\n rootMap.set(key, zero)\n }\n return\n }\n\n case \"counter\":\n case \"set\":\n case \"tree\":\n case \"movable\":\n throw new Error(\n `Yjs substrate does not support [KIND]=\"${fieldSchema[KIND]}\". ` +\n `Supported kinds: text, richtext, product, sequence, map, scalar, sum. ` +\n `Encountered unsupported kind at root field \"${key}\".`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// ensureMapContainers — recursively create nested Y.Map structure\n// ---------------------------------------------------------------------------\n\n/**\n * Create an empty Y.Map with nested shared type children matching\n * the product schema's field structure.\n *\n * Only creates containers for fields that require Yjs shared types\n * (text → Y.Text, product → Y.Map, sequence → Y.Array, map → Y.Map).\n * Scalar and sum fields are set to their structural zero defaults.\n *\n * **Identity-keying:** When a `binding` is provided, computes the\n * absolute schema path for each nested field (`prefix.fieldName`) and\n * looks up the identity hash from `binding.forward`. The identity hash\n * is used as the Y.Map entry key instead of the field name.\n *\n * @param schema - The product schema for this nested map.\n * @param binding - Optional SchemaBinding for identity-keyed containers.\n * @param prefix - The absolute schema path prefix (e.g. \"meta\" for fields under meta).\n */\nfunction ensureMapContainers(\n schema: SchemaNode,\n binding?: SchemaBinding,\n prefix?: string,\n): Y.Map<unknown> {\n const map = new Y.Map()\n\n if (schema[KIND] !== \"product\") return map\n\n for (const [key, fieldSchema] of Object.entries(\n schema.fields as Record<string, SchemaNode>,\n ).sort(([a], [b]) => a.localeCompare(b))) {\n const absPath = prefix ? `${prefix}.${key}` : key\n const identity = binding?.forward.get(absPath) as string | undefined\n const mapKey = identity ?? key\n\n switch (fieldSchema[KIND]) {\n case \"text\":\n case \"richtext\":\n map.set(mapKey, new Y.Text())\n break\n\n case \"product\":\n map.set(mapKey, ensureMapContainers(fieldSchema, binding, absPath))\n break\n\n case \"sequence\":\n map.set(mapKey, new Y.Array())\n break\n\n case \"map\":\n map.set(mapKey, new Y.Map())\n break\n\n case \"scalar\":\n case \"sum\": {\n const zero = Zero.structural(fieldSchema)\n if (zero !== undefined) {\n map.set(mapKey, zero)\n }\n break\n }\n\n case \"counter\":\n case \"set\":\n case \"tree\":\n case \"movable\":\n throw new Error(\n `Yjs substrate does not support [KIND]=\"${fieldSchema[KIND]}\". ` +\n `Supported kinds: text, richtext, product, sequence, map, scalar, sum. ` +\n `Encountered unsupported kind at nested field \"${key}\".`,\n )\n }\n }\n\n return map\n}\n","// yjs-resolve — Yjs-specific path resolution.\n//\n// Implements stepIntoYjs and resolveYjsType for schema-guided\n// navigation of the Yjs shared type tree.\n//\n// resolveYjsType is a left-fold over path segments, accumulating\n// (currentType, currentSchema) at each step. This mirrors how\n// resolveContainer works for Loro — but uses `instanceof` for\n// runtime type discrimination instead of Loro's `.kind()` method.\n//\n// Root container strategy: All schema fields are children of a single\n// root `Y.Map` obtained via `doc.getMap(\"root\")`. This root map holds\n// shared types (Y.Text, Y.Array, Y.Map) and plain values uniformly.\n// Using a single root Y.Map enables one `observeDeep` call that\n// captures all mutations with correct relative paths.\n//\n// Identity-keying: when a SchemaBinding is provided, every product-field\n// boundary uses the identity hash (from binding.forward) instead of the\n// field name as the Y.Map key. The binding is threaded through\n// resolveYjsType and stepIntoYjs.\n\nimport type {\n Path,\n SchemaBinding,\n Schema as SchemaNode,\n Segment,\n} from \"@kyneta/schema\"\nimport { advanceSchema } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// stepIntoYjs — single step of the fold\n// ---------------------------------------------------------------------------\n\n/**\n * Navigate one step deeper into the Yjs shared type tree.\n *\n * Uses `instanceof` for runtime type discrimination:\n * - `Y.Map` → `.get(key)` — uses the identity hash when provided\n * - `Y.Array` → `.get(index)`\n * - `Y.Text` → terminal (cannot step further)\n * - Plain value → terminal (return `undefined`)\n *\n * @param current - The current position (a Yjs shared type or plain value)\n * @param segment - The path segment to follow\n * @param identity - Optional identity hash to use instead of the segment's resolved value\n */\nexport function stepIntoYjs(\n current: unknown,\n segment: Segment,\n identity?: string,\n): unknown {\n const resolved = segment.resolve()\n\n if (current instanceof Y.Map) {\n return current.get(identity ?? (resolved as string))\n }\n\n if (current instanceof Y.Array) {\n return current.get(resolved as number)\n }\n\n if (current instanceof Y.Text) {\n throw new Error(`yjs-resolve: cannot step into Y.Text`)\n }\n\n // Plain value — terminal, cannot step further\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// resolveYjsType — full path resolution via left-fold\n// ---------------------------------------------------------------------------\n\n/**\n * Result of resolving a Yjs shared type at a path.\n *\n * Includes both the resolved Yjs value and the schema at that position,\n * enabling callers to distinguish between schema kinds that map to the\n * same Yjs type (e.g. \"text\" vs \"richtext\" both use Y.Text).\n */\nexport interface ResolvedYjs {\n readonly resolved: unknown\n readonly schema: SchemaNode\n}\n\n/**\n * Resolve a Yjs shared type (or plain value) at the given path.\n *\n * Left-folds over path segments using `advanceSchema` for pure schema\n * descent and `stepIntoYjs` for Yjs-specific navigation.\n *\n * When a `binding` is provided, each step computes the absolute schema\n * path and looks up the identity hash from `binding.forward`. This\n * identity hash is used instead of the field name at every product-field\n * boundary (root and nested).\n *\n * Returns both the Yjs shared type (or plain value) and the schema at\n * the terminal position. For an empty path, returns the root map and\n * root schema.\n *\n * @param rootMap - The root `Y.Map` obtained via `doc.getMap(\"root\")`\n * @param rootSchema - The root document schema\n * @param path - The path to resolve\n * @param binding - Optional SchemaBinding for identity-keyed navigation.\n */\nexport function resolveYjsType(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n binding?: SchemaBinding,\n): ResolvedYjs {\n let current: unknown = rootMap\n let schema = rootSchema\n // Track the accumulated absolute schema path for identity lookup.\n // Only string (key) segments contribute — index segments are structural\n // and don't participate in identity-keying.\n let absPath = \"\"\n\n for (let i = 0; i < path.length; i++) {\n const seg = path.segments[i]\n if (!seg) throw new Error(`Missing segment at index ${i}`)\n const nextSchema = advanceSchema(schema, seg)\n\n // Compute identity for this step if binding is provided and the\n // segment is a key (field name at a product boundary).\n let identity: string | undefined\n if (binding && seg.role === \"key\") {\n const segStr = seg.resolve() as string\n absPath = absPath ? `${absPath}.${segStr}` : segStr\n identity = binding.forward.get(absPath) as string | undefined\n }\n\n current = stepIntoYjs(current, seg, identity)\n schema = nextSchema\n }\n\n return { resolved: current, schema }\n}\n","// change-mapping — bidirectional change mapping between kyneta and Yjs.\n//\n// Two directions:\n//\n// 1. kyneta → Yjs (`applyChangeToYjs`): Resolves the target Yjs shared\n// type at a path, then applies the change imperatively via Yjs API.\n// No intermediate diff format — direct imperative mutations.\n//\n// 2. Yjs → kyneta (`eventsToOps`): Converts `observeDeep` events into\n// kyneta `Op[]` for changefeed delivery. Each Y.YEvent maps to one Op\n// with a path derived from `event.path` (relative to the observed root\n// Y.Map) and a Change derived from the event's delta/keys.\n//\n// Structured inserts use populate-then-attach order: new shared types\n// are fully populated before being inserted into their parent container.\n// This produces a single observeDeep event with the complete struct,\n// rather than a cascade of child MapChange events.\n\nimport type {\n ChangeBase,\n IncrementChange,\n MapChange,\n Op,\n Path,\n ReplaceChange,\n RichTextChange,\n RichTextInstruction,\n SchemaBinding,\n Schema as SchemaNode,\n SequenceChange,\n SequenceInstruction,\n TextChange,\n TextInstruction,\n} from \"@kyneta/schema\"\nimport {\n advanceSchema,\n expandMapOpsToLeaves,\n KIND,\n RawPath,\n richTextChange,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Direction 1: kyneta → Yjs (`applyChangeToYjs`)\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a kyneta Change to the Yjs shared type tree imperatively.\n *\n * Resolves the target shared type at `path`, then applies the change\n * via the appropriate Yjs API. Must be called within a `doc.transact()`\n * for atomicity and correct event batching.\n *\n * @param rootMap - The root `Y.Map` obtained via `doc.getMap(\"root\")`\n * @param rootSchema - The root document schema\n * @param path - The path to the target\n * @param change - The kyneta Change to apply\n */\nexport function applyChangeToYjs(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: ChangeBase,\n binding?: SchemaBinding,\n): void {\n switch (change.type) {\n case \"text\":\n applyTextChange(rootMap, rootSchema, path, change as TextChange, binding)\n return\n\n case \"richtext\":\n applyRichTextChange(\n rootMap,\n rootSchema,\n path,\n change as RichTextChange,\n binding,\n )\n return\n\n case \"sequence\":\n applySequenceChange(\n rootMap,\n rootSchema,\n path,\n change as SequenceChange,\n binding,\n )\n return\n\n case \"map\":\n applyMapChange(rootMap, rootSchema, path, change as MapChange, binding)\n return\n\n case \"replace\":\n applyReplaceChange(\n rootMap,\n rootSchema,\n path,\n change as ReplaceChange,\n binding,\n )\n return\n\n case \"increment\":\n throw new Error(\n `Yjs substrate does not support \"${change.type}\" changes. ` +\n `Counter requires a CRDT backend that supports counters (e.g. Loro). ` +\n `Attempted IncrementChange with amount=${(change as IncrementChange).amount} at path [${pathToString(path)}].`,\n )\n\n case \"tree\":\n throw new Error(\n `Yjs substrate does not support \"${change.type}\" changes. ` +\n `Tree requires a CRDT backend that supports trees (e.g. Loro). ` +\n `Attempted TreeChange at path [${pathToString(path)}].`,\n )\n\n default:\n throw new Error(\n `applyChangeToYjs: unsupported change type \"${change.type}\"`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Text change\n// ---------------------------------------------------------------------------\n\nfunction applyTextChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: TextChange,\n binding?: SchemaBinding,\n): void {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (!(resolved instanceof Y.Text)) {\n throw new Error(\n `applyChangeToYjs: TextChange target at path [${pathToString(path)}] is not a Y.Text`,\n )\n }\n\n // Yjs Y.Text.applyDelta uses the Quill Delta format, which is\n // structurally identical to kyneta TextInstruction[].\n resolved.applyDelta(change.instructions as any)\n}\n\n// ---------------------------------------------------------------------------\n// Rich text change\n// ---------------------------------------------------------------------------\n\nfunction applyRichTextChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: RichTextChange,\n binding?: SchemaBinding,\n): void {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (!(resolved instanceof Y.Text)) {\n throw new Error(\n `applyChangeToYjs: RichTextChange target at path [${pathToString(path)}] is not a Y.Text`,\n )\n }\n // Map RichTextInstruction → Yjs delta format\n const delta = change.instructions.map((inst: RichTextInstruction) => {\n if (\"retain\" in inst) return { retain: inst.retain }\n if (\"format\" in inst) return { retain: inst.format, attributes: inst.marks }\n if (\"insert\" in inst) {\n const d: any = { insert: inst.insert }\n if (inst.marks && Object.keys(inst.marks).length > 0) {\n d.attributes = inst.marks\n }\n return d\n }\n if (\"delete\" in inst) return { delete: inst.delete }\n throw new Error(\"applyRichTextChange: unknown instruction type\")\n })\n resolved.applyDelta(delta as any)\n}\n\n// ---------------------------------------------------------------------------\n// Sequence change\n// ---------------------------------------------------------------------------\n\nfunction applySequenceChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: SequenceChange,\n binding?: SchemaBinding,\n): void {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (!(resolved instanceof Y.Array)) {\n throw new Error(\n `applyChangeToYjs: SequenceChange target at path [${pathToString(path)}] is not a Y.Array`,\n )\n }\n\n // Resolve the item schema for structured insert detection\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n const itemSchema = getItemSchema(targetSchema)\n\n let cursor = 0\n for (const instruction of change.instructions) {\n if (\"retain\" in instruction) {\n cursor += instruction.retain\n } else if (\"delete\" in instruction) {\n resolved.delete(cursor, instruction.delete)\n // cursor stays — deleted items shift remaining items down\n } else if (\"insert\" in instruction) {\n const items = instruction.insert as readonly unknown[]\n const yjsItems = items.map(item =>\n maybeCreateSharedType(item, itemSchema),\n )\n resolved.insert(cursor, yjsItems)\n cursor += items.length\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Map change\n// ---------------------------------------------------------------------------\n\nfunction applyMapChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: MapChange,\n binding?: SchemaBinding,\n): void {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (!(resolved instanceof Y.Map)) {\n throw new Error(\n `applyChangeToYjs: MapChange target at path [${pathToString(path)}] is not a Y.Map`,\n )\n }\n\n // Resolve the schema at this path for structured value detection\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n\n // Apply deletes first\n if (change.delete) {\n for (const key of change.delete) {\n resolved.delete(key)\n }\n }\n\n // Apply sets\n if (change.set) {\n for (const [key, value] of Object.entries(change.set)) {\n const fieldSchema = getFieldSchema(targetSchema, key)\n const yjsValue = maybeCreateSharedType(value, fieldSchema)\n // For product schemas (structs), use the identity hash as the map key.\n // For map schemas (records), use the key as-is (no identity-keying).\n let mapKey = key\n if (binding && targetSchema[KIND] === \"product\") {\n // Compute absolute schema path for this field.\n const parentAbsPath = path.segments\n .filter(s => s.role === \"key\")\n .map(s => s.resolve() as string)\n .join(\".\")\n const absPath = parentAbsPath ? `${parentAbsPath}.${key}` : key\n const identity = binding.forward.get(absPath) as string | undefined\n if (identity) mapKey = identity\n }\n resolved.set(mapKey, yjsValue)\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Replace change\n// ---------------------------------------------------------------------------\n\nfunction applyReplaceChange(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n change: ReplaceChange,\n binding?: SchemaBinding,\n): void {\n if (path.length === 0) {\n throw new Error(\n \"applyChangeToYjs: ReplaceChange at root path is not supported\",\n )\n }\n\n // Target the parent container, using the last segment to identify\n // which child to replace.\n const lastSeg = path.segments.at(-1)\n if (!lastSeg) throw new Error(\"replaceChangeToDiff: empty path\")\n const parentPath = path.slice(0, -1)\n const { resolved: parent } = resolveYjsType(\n rootMap,\n rootSchema,\n parentPath,\n binding,\n )\n\n const resolved = lastSeg.resolve()\n if (parent instanceof Y.Map && lastSeg.role === \"key\") {\n // Resolve schema for the target field for structured value detection\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n const yjsValue = maybeCreateSharedType(change.value, targetSchema)\n // Use identity hash for product-field boundaries.\n let mapKey = resolved as string\n if (binding) {\n const absPath = path.segments\n .filter(s => s.role === \"key\")\n .map(s => s.resolve() as string)\n .join(\".\")\n const identity = binding.forward.get(absPath) as string | undefined\n if (identity) mapKey = identity\n }\n parent.set(mapKey, yjsValue)\n } else if (parent instanceof Y.Array && lastSeg.role === \"index\") {\n const targetSchema = resolveSchemaAtPath(rootSchema, path)\n const yjsValue = maybeCreateSharedType(change.value, targetSchema)\n parent.delete(resolved as number, 1)\n parent.insert(resolved as number, [yjsValue])\n } else {\n throw new Error(\n `applyChangeToYjs: ReplaceChange parent at path [${pathToString(parentPath)}] ` +\n `is not a Y.Map or Y.Array (got ${typeof parent})`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Structured value creation (populate-then-attach pattern)\n// ---------------------------------------------------------------------------\n\n/**\n * If the schema says the value should be a shared type (product → Y.Map,\n * sequence → Y.Array, text → Y.Text, richtext → Y.Text), create and\n * populate it. Otherwise return the plain value as-is.\n *\n * Uses populate-then-attach: the new shared type is fully populated\n * before being returned for insertion into its parent.\n */\nfunction maybeCreateSharedType(\n value: unknown,\n schema: SchemaNode | undefined,\n): unknown {\n if (schema === undefined) return value\n\n switch (schema[KIND]) {\n // First-class text → Y.Text\n case \"text\": {\n const text = new Y.Text()\n if (typeof value === \"string\" && value.length > 0) {\n text.insert(0, value)\n }\n return text\n }\n\n // Rich text → Y.Text (Yjs uses Y.Text for both plain and rich text)\n case \"richtext\": {\n const text = new Y.Text()\n if (typeof value === \"string\" && value.length > 0) {\n text.insert(0, value)\n } else if (Array.isArray(value)) {\n // RichTextDelta: array of { text, marks? } spans → Yjs delta\n const delta = (\n value as Array<{ text: string; marks?: Record<string, unknown> }>\n ).map(span => {\n const d: any = { insert: span.text }\n if (span.marks && Object.keys(span.marks).length > 0) {\n d.attributes = span.marks\n }\n return d\n })\n if (delta.length > 0) {\n text.applyDelta(delta)\n }\n }\n return text\n }\n\n case \"product\": {\n if (\n value === null ||\n value === undefined ||\n typeof value !== \"object\" ||\n Array.isArray(value)\n ) {\n return value\n }\n return createStructuredMap(value as Record<string, unknown>, schema)\n }\n\n case \"sequence\": {\n if (!Array.isArray(value)) return value\n const arr = new Y.Array()\n const itemSchema = schema.item\n const items = (value as unknown[]).map(item =>\n maybeCreateSharedType(item, itemSchema),\n )\n arr.insert(0, items)\n return arr\n }\n\n case \"map\": {\n if (\n value === null ||\n value === undefined ||\n typeof value !== \"object\" ||\n Array.isArray(value)\n ) {\n return value\n }\n const map = new Y.Map()\n const valueSchema = schema.item\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n map.set(k, maybeCreateSharedType(v, valueSchema))\n }\n return map\n }\n\n // Unsupported first-class CRDT types — should not reach here\n // (rejected at bind time by caps check)\n case \"counter\":\n case \"set\":\n case \"tree\":\n case \"movable\":\n throw new Error(\n `Yjs substrate does not support [KIND]=\"${schema[KIND]}\". ` +\n `This should have been caught at bind() time.`,\n )\n\n default:\n // Scalar, sum — return as plain value\n return value\n }\n}\n\n/**\n * Create a Y.Map from a plain object, recursively creating nested\n * shared types as guided by the product schema.\n *\n * Follows populate-then-attach: fully populates the map before the\n * caller inserts it into a parent container.\n */\nfunction createStructuredMap(\n obj: Record<string, unknown>,\n productSchema: SchemaNode,\n): Y.Map<any> {\n const map = new Y.Map()\n\n if (productSchema[KIND] !== \"product\") {\n // Fallback: set all values as plain\n for (const [key, val] of Object.entries(obj)) {\n map.set(key, val)\n }\n return map\n }\n\n // Process fields present in the value object\n for (const [key, val] of Object.entries(obj)) {\n if (val === undefined) continue\n const fieldSchema = productSchema.fields[key]\n const yjsVal = fieldSchema ? maybeCreateSharedType(val, fieldSchema) : val\n map.set(key, yjsVal)\n }\n\n // Create shared types for first-class CRDT fields declared in the schema\n // but missing from the value object. This ensures Yjs containers\n // exist for later mutation (e.g. .insert() on a text field inside\n // a struct inside a record/list).\n for (const [key, fieldSchema] of Object.entries(\n productSchema.fields as Record<string, SchemaNode>,\n )) {\n if (key in obj) continue // already processed above\n if (fieldSchema[KIND] === \"text\" || fieldSchema[KIND] === \"richtext\") {\n map.set(key, new Y.Text())\n }\n }\n\n return map\n}\n\n// ---------------------------------------------------------------------------\n// Direction 2: Yjs → kyneta (`eventsToOps`)\n// ---------------------------------------------------------------------------\n\n/**\n * Convert `observeDeep` events into kyneta `Op[]` for changefeed delivery.\n *\n * Each `Y.YEvent` in the array maps to one Op with:\n * - `path`: derived from `event.path` (relative to the observed root Y.Map)\n * - `change`: derived from the event's delta/keys based on target type\n *\n * `event.path` in `observeDeep` is relative to the observed shared type.\n * Since we observe `rootMap` (the single root Y.Map), paths map directly\n * to kyneta `PathSegment[]`.\n *\n * @param events - The events from the `observeDeep` callback\n */\nexport function eventsToOps(\n events: Y.YEvent<any>[],\n schema: SchemaNode,\n binding?: SchemaBinding,\n): Op[] {\n const ops: Op[] = []\n\n for (const event of events) {\n const kynetaPath = yjsPathToKynetaPath(event.path, binding)\n const change = eventToChange(event, schema, kynetaPath, binding)\n if (change) {\n ops.push({ path: kynetaPath, change })\n }\n }\n\n return expandMapOpsToLeaves(ops, schema)\n}\n\n// ---------------------------------------------------------------------------\n// Yjs path → kyneta Path conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a Yjs event path (array of string | number) to a kyneta Path.\n *\n * `event.path` from `observeDeep` is relative to the observed type.\n * Strings become key segments, numbers become index segments.\n */\nfunction yjsPathToKynetaPath(\n yjsPath: (string | number)[],\n binding?: SchemaBinding,\n): RawPath {\n let path = RawPath.empty\n for (const segment of yjsPath) {\n if (typeof segment === \"string\") {\n // Reverse-map identity hash → absolute schema path → leaf field name.\n // Yjs events emit identity-keyed strings at product-field positions;\n // we need to recover the original field name for kyneta schema paths.\n const absPath = binding?.inverse.get(segment as any)\n if (absPath) {\n const lastDot = absPath.lastIndexOf(\".\")\n const leaf = lastDot >= 0 ? absPath.slice(lastDot + 1) : absPath\n path = path.field(leaf)\n } else {\n path = path.field(segment)\n }\n } else if (typeof segment === \"number\") {\n path = path.item(segment)\n }\n }\n return path\n}\n\n// ---------------------------------------------------------------------------\n// Per-type event → Change converters\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a single Yjs event into a kyneta Change.\n *\n * For Y.Text events, dispatches to either `textEventToChange` or\n * `richTextEventToChange` based on the schema at the event's path.\n * Both text and richtext produce `Y.YTextEvent`, so schema awareness\n * is required for correct dispatch.\n *\n * Returns null for event types we can't map.\n */\nfunction eventToChange(\n event: Y.YEvent<any>,\n rootSchema: SchemaNode,\n kynetaPath: RawPath,\n binding?: SchemaBinding,\n): ChangeBase | null {\n if (event.target instanceof Y.Text) {\n // Both text and richtext use Y.Text — resolve the schema to dispatch.\n const schemaAtPath = resolveSchemaAtPath(rootSchema, kynetaPath)\n if (schemaAtPath[KIND] === \"richtext\") {\n return richTextEventToChange(event)\n }\n return textEventToChange(event)\n }\n if (event.target instanceof Y.Array) {\n return arrayEventToChange(event)\n }\n if (event.target instanceof Y.Map) {\n return mapEventToChange(event, binding)\n }\n return null\n}\n\n/**\n * Y.Text event → TextChange.\n *\n * `event.delta` uses the Quill Delta format, structurally identical to\n * kyneta `TextInstruction[]`. We strip the `attributes` field (rich text\n * formatting not surfaced by kyneta plain text).\n */\nfunction textEventToChange(event: Y.YEvent<any>): TextChange {\n const instructions: TextInstruction[] = []\n\n for (const delta of event.delta) {\n if (delta.retain !== undefined) {\n instructions.push({ retain: delta.retain as number })\n } else if (delta.insert !== undefined) {\n instructions.push({ insert: delta.insert as string })\n } else if (delta.delete !== undefined) {\n instructions.push({ delete: delta.delete as number })\n }\n }\n\n return { type: \"text\", instructions }\n}\n\n/**\n * Y.Text event → RichTextChange.\n *\n * `event.delta` uses the Quill Delta format. We map each delta op to a\n * `RichTextInstruction`, preserving `attributes` as `marks` for format\n * and insert instructions.\n */\nfunction richTextEventToChange(event: Y.YEvent<any>): RichTextChange {\n const instructions: RichTextInstruction[] = []\n\n for (const delta of event.delta) {\n if (delta.retain !== undefined) {\n const attrs = (delta as any).attributes\n if (attrs && Object.keys(attrs).length > 0) {\n instructions.push({ format: delta.retain as number, marks: attrs })\n } else {\n instructions.push({ retain: delta.retain as number })\n }\n } else if (delta.insert !== undefined) {\n const attrs = (delta as any).attributes\n if (attrs && Object.keys(attrs).length > 0) {\n instructions.push({ insert: delta.insert as string, marks: attrs })\n } else {\n instructions.push({ insert: delta.insert as string })\n }\n } else if (delta.delete !== undefined) {\n instructions.push({ delete: delta.delete as number })\n }\n }\n\n return richTextChange(instructions)\n}\n\n/**\n * Y.Array event → SequenceChange.\n *\n * `event.changes.delta` provides the same cursor-based ops as kyneta\n * SequenceInstruction[]. Container values (Y.Map, Y.Array) in insert\n * arrays are converted to plain objects via `.toJSON()`.\n */\nfunction arrayEventToChange(event: Y.YEvent<any>): SequenceChange {\n const instructions: SequenceInstruction[] = []\n\n for (const delta of event.changes.delta) {\n if (delta.retain !== undefined) {\n instructions.push({ retain: delta.retain as number })\n } else if (delta.delete !== undefined) {\n instructions.push({ delete: delta.delete as number })\n } else if (delta.insert !== undefined) {\n const items = (delta.insert as unknown[]).map((item: unknown) =>\n extractEventValue(item),\n )\n instructions.push({ insert: items })\n }\n }\n\n return { type: \"sequence\", instructions }\n}\n\n/**\n * Y.Map event → MapChange.\n *\n * `event.changes.keys` is a `Map<string, { action: 'add'|'update'|'delete', ... }>`.\n * - `action: 'add'|'update'` → `set[key] = map.get(key)`\n * - `action: 'delete'` → `delete.push(key)`\n */\nfunction mapEventToChange(\n event: Y.YEvent<any>,\n binding?: SchemaBinding,\n): MapChange | null {\n const set: Record<string, unknown> = {}\n const deleteKeys: string[] = []\n let hasSet = false\n let hasDelete = false\n\n const target = event.target as Y.Map<any>\n\n event.changes.keys.forEach((change: { action: string }, key: string) => {\n // Reverse-map identity hash → absolute schema path → leaf field name.\n const absPath = binding?.inverse.get(key as any)\n const fieldName = absPath\n ? absPath.lastIndexOf(\".\") >= 0\n ? absPath.slice(absPath.lastIndexOf(\".\") + 1)\n : absPath\n : key\n\n if (change.action === \"add\" || change.action === \"update\") {\n const value = target.get(key)\n set[fieldName] = extractEventValue(value)\n hasSet = true\n } else if (change.action === \"delete\") {\n deleteKeys.push(fieldName)\n hasDelete = true\n }\n })\n\n if (!hasSet && !hasDelete) return null\n\n return {\n type: \"map\",\n ...(hasSet ? { set } : {}),\n ...(hasDelete ? { delete: deleteKeys } : {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Value extraction from Yjs events\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a Yjs value from an event into a plain value.\n * Container values (Y.Map, Y.Array, Y.Text) → `.toJSON()`.\n * Plain values → returned as-is.\n */\nfunction extractEventValue(value: unknown): unknown {\n if (value instanceof Y.Map) return value.toJSON()\n if (value instanceof Y.Array) return value.toJSON()\n if (value instanceof Y.Text) return value.toJSON()\n return value\n}\n\n// ---------------------------------------------------------------------------\n// Schema helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the schema at a given path by walking through advanceSchema.\n */\nfunction resolveSchemaAtPath(rootSchema: SchemaNode, path: Path): SchemaNode {\n let schema = rootSchema\n for (const seg of path.segments) {\n schema = advanceSchema(schema, seg)\n }\n return schema\n}\n\n/**\n * Get the item schema from a sequence schema, if available.\n */\nfunction getItemSchema(schema: SchemaNode): SchemaNode | undefined {\n if (schema[KIND] === \"sequence\") return schema.item\n if (schema[KIND] === \"movable\") return schema.item\n return undefined\n}\n\n/**\n * Get the field schema from a product or map schema for a given key.\n */\nfunction getFieldSchema(\n schema: SchemaNode,\n key: string,\n): SchemaNode | undefined {\n if (schema[KIND] === \"product\") {\n return schema.fields[key]\n }\n if (schema[KIND] === \"map\") {\n return schema.item\n }\n if (schema[KIND] === \"set\") {\n return schema.item\n }\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// Path formatting\n// ---------------------------------------------------------------------------\n\nfunction pathToString(path: Path): string {\n return path.segments.map(seg => String(seg.resolve())).join(\".\")\n}\n","// position — YjsPosition implementation.\n//\n// Wraps Yjs's RelativePosition to implement @kyneta/schema's Position interface.\n// Relative positions bind to specific item IDs in the Yjs document, making\n// resolve() a stateless query — transform() is a no-op.\n\nimport type { Instruction, Position, Side } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\n\n/** Map kyneta Side to Yjs assoc. Left → -1 (left-sticky), Right → 0 (right-sticky). */\nexport function toYjsAssoc(side: Side): number {\n return side === \"left\" ? -1 : 0\n}\n\n/** Map Yjs assoc to kyneta Side. Negative → left, non-negative → right. */\nexport function fromYjsAssoc(assoc: number): Side {\n return assoc < 0 ? \"left\" : \"right\"\n}\n\nexport class YjsPosition implements Position {\n readonly side: Side\n\n constructor(\n private readonly rpos: Y.RelativePosition,\n private readonly doc: Y.Doc,\n ) {\n this.side = fromYjsAssoc(rpos.assoc)\n }\n\n resolve(): number | null {\n const abs = Y.createAbsolutePositionFromRelativePosition(\n this.rpos,\n this.doc,\n )\n return abs ? abs.index : null\n }\n\n encode(): Uint8Array {\n return Y.encodeRelativePosition(this.rpos)\n }\n\n transform(_instructions: readonly Instruction[]): void {\n // No-op — Yjs relative positions resolve statelessly against the document.\n }\n}\n","// store-reader — YjsReader implementation.\n//\n// Implements Reader via schema-guided live navigation of the\n// Yjs shared type tree. Each read operation resolves the shared type\n// at the given path using resolveYjsType, then extracts the\n// appropriate value based on `instanceof` discrimination.\n//\n// Y.Text → .toJSON() (string), Y.Map → .toJSON() (plain object),\n// Y.Array → .toJSON() (plain array), plain values → as-is.\n//\n// Richtext: when the schema at the resolved path is \"richtext\" and\n// the resolved value is a Y.Text, we call .toDelta() and convert to\n// RichTextDelta (array of { text, marks? } spans) instead of .toJSON().\n//\n// Identity-keying: when a SchemaBinding is provided, resolveYjsType\n// navigates Y.Map children using identity hashes instead of field names.\n\nimport type {\n Path,\n Reader,\n RichTextDelta,\n RichTextSpan,\n SchemaBinding,\n Schema as SchemaNode,\n} from \"@kyneta/schema\"\nimport { KIND } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Value extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract a plain value from a Yjs shared type or return a plain value as-is.\n *\n * - Y.Text → `.toJSON()` (string)\n * - Y.Map → `.toJSON()` (plain object snapshot — for product/map reads)\n * - Y.Array → `.toJSON()` (plain array snapshot)\n * - Plain values (string, number, boolean, null) → returned as-is\n */\nfunction extractValue(resolved: unknown): unknown {\n if (resolved instanceof Y.Text) {\n return resolved.toJSON()\n }\n if (resolved instanceof Y.Map) {\n return resolved.toJSON()\n }\n if (resolved instanceof Y.Array) {\n return resolved.toJSON()\n }\n // Plain scalar value (string, number, boolean, null, etc.)\n return resolved\n}\n\n// ---------------------------------------------------------------------------\n// Rich text delta conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a Y.Text's delta (Quill format) to a kyneta RichTextDelta.\n *\n * Yjs `.toDelta()` returns `{ insert: string, attributes?: Record<string, any> }[]`.\n * Kyneta RichTextDelta is `{ text: string, marks?: MarkMap }[]`.\n */\nfunction yTextToRichTextDelta(ytext: Y.Text): RichTextDelta {\n const delta = ytext.toDelta() as Array<{\n insert: string\n attributes?: Record<string, unknown>\n }>\n const spans: RichTextSpan[] = []\n for (const d of delta) {\n if (typeof d.insert !== \"string\") continue\n const span: RichTextSpan =\n d.attributes && Object.keys(d.attributes).length > 0\n ? { text: d.insert, marks: d.attributes }\n : { text: d.insert }\n spans.push(span)\n }\n return spans\n}\n\n// ---------------------------------------------------------------------------\n// yjsReader\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a Reader that navigates the Yjs shared type tree live,\n * using the schema as a type witness to determine navigation at each\n * path segment.\n *\n * The reader is a live view — mutations to the underlying Y.Doc\n * (via `doc.transact()`, or `Y.applyUpdate()`) are immediately\n * visible through the reader.\n *\n * Internally obtains the root map via `doc.getMap(\"root\")`.\n *\n * @param doc - The Y.Doc to read from.\n * @param schema - The root schema for the document.\n * @param binding - Optional SchemaBinding for identity-keyed navigation.\n */\nexport function yjsReader(\n doc: Y.Doc,\n schema: SchemaNode,\n binding?: SchemaBinding,\n): Reader {\n const rootMap = doc.getMap(\"root\")\n\n return {\n read(path: Path): unknown {\n if (path.length === 0) {\n // Root read — return the full root map as JSON\n return rootMap.toJSON()\n }\n const { resolved, schema: nodeSchema } = resolveYjsType(\n rootMap,\n schema,\n path,\n binding,\n )\n // Richtext: Y.Text at a richtext schema position → RichTextDelta\n if (nodeSchema[KIND] === \"richtext\" && resolved instanceof Y.Text) {\n return yTextToRichTextDelta(resolved)\n }\n return extractValue(resolved)\n },\n\n arrayLength(path: Path): number {\n const { resolved } = resolveYjsType(rootMap, schema, path, binding)\n if (resolved instanceof Y.Array) {\n return resolved.length\n }\n // Graceful fallback for plain array values\n if (Array.isArray(resolved)) {\n return resolved.length\n }\n return 0\n },\n\n keys(path: Path): string[] {\n const { resolved } = resolveYjsType(rootMap, schema, path, binding)\n if (resolved instanceof Y.Map) {\n return Array.from(resolved.keys())\n }\n // Graceful fallback for plain object values\n if (\n resolved !== null &&\n resolved !== undefined &&\n typeof resolved === \"object\" &&\n !Array.isArray(resolved)\n ) {\n return Object.keys(resolved as Record<string, unknown>)\n }\n return []\n },\n\n hasKey(path: Path, key: string): boolean {\n const { resolved } = resolveYjsType(rootMap, schema, path, binding)\n if (resolved instanceof Y.Map) {\n return resolved.has(key)\n }\n // Graceful fallback for plain object values\n if (\n resolved !== null &&\n resolved !== undefined &&\n typeof resolved === \"object\" &&\n !Array.isArray(resolved)\n ) {\n return key in (resolved as Record<string, unknown>)\n }\n return false\n },\n }\n}\n","// YjsVersion — Version wrapping a Yjs snapshot (state vector + delete set).\n//\n// The Yjs state vector only tracks inserted items — it does NOT advance\n// when items are deleted (tombstoned). A version based on the state vector\n// alone cannot detect delete-only changes, causing the sync protocol to\n// skip pushing deletes to peers.\n//\n// YjsVersion wraps the full Yjs Snapshot (state vector + delete set) so\n// that compare() faithfully distinguishes \"same state\" from \"divergent\n// deletes.\" The state vector component is kept separately for exportSince(),\n// which uses it to compute the minimal update payload.\n//\n// Serialization format: base64(sv) + \".\" + base64(snapshotBytes).\n// Legacy format (no \".\"): base64(sv) only — decoded as SV-only version\n// for backward compatibility. When a legacy version is compared against\n// a new-format version with matching SVs, the differing snapshot bytes\n// yield \"concurrent\", triggering a (redundant but safe) sync push.\n\nimport type { Version } from \"@kyneta/schema\"\nimport {\n base64ToUint8Array,\n uint8ArrayToBase64,\n versionVectorCompare,\n versionVectorMeet,\n} from \"@kyneta/schema\"\nimport {\n createSnapshot,\n type Doc,\n decodeStateVector,\n encodeSnapshot,\n encodeStateVector as yjsEncodeStateVector,\n snapshot as yjsSnapshot,\n} from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// State vector encoding — manual varint (unsigned LEB128)\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a state vector map to Yjs's binary state vector format.\n *\n * Yjs does not export `encodeStateVector(map)` — only `Y.encodeStateVector(doc)`\n * which requires a full doc. This implements the same binary format directly:\n * `[entryCount: varint, (clientId: varint, clock: varint)*]`\n *\n * Each value is encoded as an unsigned LEB128 varint.\n */\nfunction encodeStateVector(map: Map<number, number>): Uint8Array {\n const bytes: number[] = []\n\n function writeVarUint(value: number): void {\n while (value > 0x7f) {\n bytes.push((value & 0x7f) | 0x80)\n value >>>= 7\n }\n bytes.push(value & 0x7f)\n }\n\n writeVarUint(map.size)\n for (const [clientId, clock] of map) {\n writeVarUint(clientId)\n writeVarUint(clock)\n }\n\n return new Uint8Array(bytes)\n}\n\n// ---------------------------------------------------------------------------\n// Byte-level equality\n// ---------------------------------------------------------------------------\n\nfunction arraysEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n// ---------------------------------------------------------------------------\n// YjsVersion\n// ---------------------------------------------------------------------------\n\n/**\n * A Version wrapping a Yjs snapshot (state vector + delete set).\n *\n * The state vector tracks which insertions from each client have been\n * observed. The delete set tracks which of those items have been\n * tombstoned. Together they fully describe a Yjs document's state.\n *\n * - `sv` is used by `exportSince()` to compute the minimal update payload\n * via `Y.encodeStateAsUpdate(doc, sv)`.\n * - `snapshotBytes` is the encoded Yjs `Snapshot` (SV + delete set),\n * used for equality comparison: two documents are \"equal\" only when\n * both their inserts and deletes match.\n *\n * `compare()` first performs standard version-vector partial-order\n * comparison on the state vectors. If the SVs are equal, it falls\n * through to a byte-level comparison of the snapshot bytes — if they\n * differ (same inserts, different deletes), the result is \"concurrent\",\n * ensuring the sync protocol pushes the divergent deletes.\n */\nexport class YjsVersion implements Version {\n /** Encoded state vector — used by exportSince(). */\n readonly sv: Uint8Array\n\n /**\n * Encoded Yjs snapshot (state vector + delete set) — used for equality\n * comparison. Two documents are \"equal\" only if both their inserts\n * (state vector) and deletes (delete set) match.\n */\n readonly snapshotBytes: Uint8Array\n\n constructor(sv: Uint8Array, snapshotBytes?: Uint8Array) {\n this.sv = sv\n // If no snapshot provided, use sv as snapshot (backward compat / SV-only).\n this.snapshotBytes = snapshotBytes ?? sv\n }\n\n /**\n * Construct a version from a live `Y.Doc` by snapshotting its full state.\n *\n * Walks the struct store to derive the delete set — O(n) in the number\n * of items. Use {@link fromDeleteSet} for the incremental path.\n */\n static fromDoc(doc: Doc): YjsVersion {\n const sv = yjsEncodeStateVector(doc)\n const snap = encodeSnapshot(yjsSnapshot(doc))\n return new YjsVersion(sv, snap)\n }\n\n /**\n * Construct a version from a `Y.Doc`'s state vector and an externally\n * maintained delete set — the incremental path that avoids a struct\n * store walk.\n *\n * @param doc The live Y.Doc (for the state vector).\n * @param ds An accumulated delete set, kept in sync by merging\n * `transaction.deleteSet` on each transaction.\n */\n static fromDeleteSet(\n doc: Doc,\n ds: ReturnType<typeof import(\"yjs\").createDeleteSet>,\n ): YjsVersion {\n const sv = yjsEncodeStateVector(doc)\n const svMap = decodeStateVector(sv)\n const snap = encodeSnapshot(createSnapshot(ds, svMap))\n return new YjsVersion(sv, snap)\n }\n\n /**\n * Serialize to a text-safe string.\n *\n * Format: `base64(sv) + \".\" + base64(snapshotBytes)`.\n * The \".\" separator is unambiguous since base64 never contains \".\".\n */\n serialize(): string {\n const svB64 = uint8ArrayToBase64(this.sv)\n const snapB64 = uint8ArrayToBase64(this.snapshotBytes)\n return svB64 + \".\" + snapB64\n }\n\n /**\n * Compare with another version using version-vector partial order,\n * extended with delete-set equality checking.\n *\n * 1. Decode both state vectors and compare via `versionVectorCompare`.\n * 2. If the SV comparison yields anything other than \"equal\", return it.\n * 3. If the SVs are equal, compare snapshot bytes for byte equality.\n * If they differ (same inserts, different deletes), return \"concurrent\"\n * — both sides may have tombstones the other lacks.\n * 4. \"equal\" is returned only when BOTH the state vector AND the\n * delete set match.\n *\n * @throws If `other` is not a `YjsVersion`.\n */\n compare(other: Version): \"behind\" | \"equal\" | \"ahead\" | \"concurrent\" {\n if (!(other instanceof YjsVersion)) {\n throw new Error(\"YjsVersion can only be compared with another YjsVersion\")\n }\n const svResult = versionVectorCompare(\n decodeStateVector(this.sv),\n decodeStateVector(other.sv),\n )\n if (svResult !== \"equal\") return svResult\n // State vectors are equal — check if delete sets match via snapshot bytes.\n return arraysEqual(this.snapshotBytes, other.snapshotBytes)\n ? \"equal\"\n : \"concurrent\"\n }\n\n /**\n * Greatest lower bound (lattice meet) of two Yjs versions.\n *\n * Decodes both state vectors, computes the component-wise minimum\n * via `versionVectorMeet`, and encodes the result back to a Yjs\n * state vector.\n *\n * The meet snapshot uses the meet SV with no delete-set information\n * (conservative lower bound). meet() feeds into advance(), which Yjs\n * does not support incrementally, so this is safe.\n *\n * @throws If `other` is not a `YjsVersion`.\n */\n meet(other: Version): YjsVersion {\n if (!(other instanceof YjsVersion)) {\n throw new Error(\"YjsVersion can only be meet'd with another YjsVersion\")\n }\n const thisMap = decodeStateVector(this.sv)\n const otherMap = decodeStateVector(other.sv)\n const result = versionVectorMeet(thisMap, otherMap)\n const meetSv = encodeStateVector(result)\n // Conservative: meet snapshot uses only the meet SV (no delete set).\n return new YjsVersion(meetSv)\n }\n\n /**\n * Parse a serialized YjsVersion string back into a YjsVersion.\n *\n * New format: `base64(sv) + \".\" + base64(snapshotBytes)`.\n * Legacy format (no \".\"): `base64(sv)` only — constructed with\n * `snapshotBytes` equal to the SV bytes. When compared against a\n * new-format version with matching SVs, the differing snapshot bytes\n * yield \"concurrent\", triggering a safe redundant sync push.\n */\n static parse(serialized: string): YjsVersion {\n if (serialized === \"\") {\n throw new Error(\"Invalid YjsVersion value: (empty string)\")\n }\n const dotIndex = serialized.indexOf(\".\")\n if (dotIndex === -1) {\n // Legacy format: SV-only (no delete set).\n const bytes = base64ToUint8Array(serialized)\n return new YjsVersion(bytes)\n }\n const sv = base64ToUint8Array(serialized.slice(0, dotIndex))\n const snapshotBytes = base64ToUint8Array(serialized.slice(dotIndex + 1))\n return new YjsVersion(sv, snapshotBytes)\n }\n}\n","// substrate — YjsSubstrate implementation.\n//\n// Implements Substrate<YjsVersion> with:\n// - Imperative local writes (prepare accumulates, onFlush applies in transact)\n// - Persistent observeDeep event bridge for external changes\n// - Single re-entrancy guard + transaction.origin check\n//\n// The event bridge contract: wrapping a Y.Doc in a kyneta substrate\n// means subscribing to the kyneta doc observes ALL mutations to the\n// underlying Y.Doc, regardless of source (local kyneta writes,\n// merge, external Y.applyUpdate, external raw Yjs API mutations).\n//\n// Identity-keying: when a SchemaBinding is provided, all Y.Map key\n// lookups and writes use the identity hash instead of the field name.\n// The binding is threaded to the reader, event bridge, and write path.\n\nimport type {\n ChangeBase,\n Path,\n PositionCapable,\n ProductSchema,\n Reader,\n Replica,\n ReplicaFactory,\n SchemaBinding,\n Schema as SchemaNode,\n Side,\n Substrate,\n SubstrateFactory,\n SubstratePayload,\n WritableContext,\n} from \"@kyneta/schema\"\nimport {\n BACKING_DOC,\n buildWritableContext,\n deriveSchemaBinding,\n executeBatch,\n KIND,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { applyChangeToYjs, eventsToOps } from \"./change-mapping.js\"\nimport { ensureContainers } from \"./populate.js\"\nimport { toYjsAssoc, YjsPosition } from \"./position.js\"\nimport { yjsReader } from \"./reader.js\"\nimport { YjsVersion } from \"./version.js\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Origin tag — used to suppress echo from our own transactions\n// ---------------------------------------------------------------------------\n\nconst KYNETA_ORIGIN = \"kyneta-prepare\"\n\n// ---------------------------------------------------------------------------\n// createYjsSubstrate — wrap a user-provided Y.Doc\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a `Substrate<YjsVersion>` wrapping a user-provided Y.Doc.\n *\n * This is the \"bring your own doc\" entry point. The user creates and\n * manages the Y.Doc (possibly via a Yjs provider); this function wraps\n * it with a schema-aware overlay providing typed reads, writes,\n * versioning, and export/merge through the standard Substrate interface.\n *\n * **Event bridge contract:** A persistent `observeDeep` handler is\n * registered on the root Y.Map at construction time. All non-kyneta\n * mutations to the Y.Doc (merges, external local writes) are bridged\n * to the kyneta changefeed. Subscribing to the kyneta doc observes all\n * mutations regardless of source.\n *\n * @param doc - The Y.Doc to wrap. The substrate does NOT own the doc;\n * the caller is responsible for its lifecycle.\n * @param schema - The root schema for the document.\n * @param binding - Optional SchemaBinding for identity-keyed containers.\n */\nexport function createYjsSubstrate(\n doc: Y.Doc,\n schema: SchemaNode,\n binding?: SchemaBinding,\n): Substrate<YjsVersion> {\n // --- Closure-scoped state ---\n\n // Accumulated changes from prepare(), drained by onFlush().\n const pendingChanges: Array<{ path: Path; change: ChangeBase }> = []\n\n // Re-entrancy guard: set true around our doc.transact() in onFlush\n // AND around executeBatch in the event bridge. When true, prepare()\n // skips Yjs-side work (changes are already applied by Yjs or about\n // to be), and onFlush() skips transact/commit.\n let inOurTransaction = false\n\n // Stashed origin from merge for the event bridge to pick up.\n let pendingMergeOrigin: string | undefined\n\n // Lazy-built WritableContext (same pattern as PlainSubstrate / LoroSubstrate).\n let cachedCtx: WritableContext | undefined\n\n // Incremental delete set tracking.\n // Initialized once from the struct store (O(n)), then maintained\n // incrementally by merging each transaction's deleteSet (O(delta)).\n // Note: DeleteSet class isn't exported from yjs's public entry point,\n // so we infer the type from createDeleteSet's return type.\n let accumulatedDs: ReturnType<typeof Y.createDeleteSet> =\n Y.createDeleteSetFromStructStore(doc.store)\n\n // The root Y.Map — all schema fields are children of this single map.\n const rootMap = doc.getMap(\"root\")\n\n // The Reader — live view over the Yjs shared type tree.\n const reader: Reader = yjsReader(doc, schema, binding)\n\n // --- Substrate object ---\n\n const substrate = {\n [BACKING_DOC]: doc,\n\n reader: reader,\n\n prepare(path: Path, change: ChangeBase): void {\n if (!inOurTransaction) {\n // Local write: accumulate for flush.\n // No Yjs side effects — mutations happen at flush time.\n pendingChanges.push({ path, change })\n }\n // During event handler replay: no-op on Yjs side.\n // wrappedPrepare (changefeed layer) still buffers the op.\n },\n\n onFlush(_origin?: string): void {\n if (!inOurTransaction && pendingChanges.length > 0) {\n // Local write: apply accumulated changes within a single\n // Yjs transaction tagged with our origin for echo suppression.\n inOurTransaction = true\n try {\n doc.transact(() => {\n for (const { path, change } of pendingChanges) {\n applyChangeToYjs(rootMap, schema, path, change, binding)\n }\n }, KYNETA_ORIGIN)\n pendingChanges.length = 0\n } finally {\n inOurTransaction = false\n }\n }\n // During event handler replay: no-op on Yjs side.\n // wrappedFlush (changefeed layer) still delivers notifications.\n },\n\n context(): WritableContext {\n if (!cachedCtx) {\n cachedCtx = buildWritableContext(substrate)\n // Attach nativeResolver — used by interpretImpl to set [NATIVE]\n // on every ref. The resolver maps schema positions to Yjs shared types.\n ;(cachedCtx as any).nativeResolver = (\n nodeSchema: SchemaNode,\n path: { segments: readonly unknown[] },\n ) => {\n if (path.segments.length === 0) return doc\n if (nodeSchema[KIND] === \"scalar\" || nodeSchema[KIND] === \"sum\")\n return undefined\n return resolveYjsType(rootMap, schema, path as any, binding).resolved\n }\n ;(cachedCtx as any).positionResolver = (\n _nodeSchema: unknown,\n path: { segments: readonly unknown[] },\n ) => {\n return {\n createPosition(index: number, side: Side) {\n // Resolve path to the Y.Text shared type\n const { resolved: ytype } = resolveYjsType(\n rootMap,\n schema,\n path as any,\n binding,\n )\n if (!(ytype instanceof Y.Text)) {\n throw new Error(\n `positionResolver: path does not resolve to a Y.Text`,\n )\n }\n const assoc = toYjsAssoc(side)\n const rpos = Y.createRelativePositionFromTypeIndex(\n ytype,\n index,\n assoc,\n )\n return new YjsPosition(rpos, doc)\n },\n decodePosition(bytes: Uint8Array) {\n const rpos = Y.decodeRelativePosition(bytes)\n return new YjsPosition(rpos, doc)\n },\n } satisfies PositionCapable\n }\n }\n return cachedCtx\n },\n\n version(): YjsVersion {\n return YjsVersion.fromDeleteSet(doc, accumulatedDs)\n },\n\n baseVersion(): YjsVersion {\n // Yjs substrate: base is always the initial state (no advance supported).\n return new YjsVersion(new Uint8Array([0]))\n },\n\n advance(_to: YjsVersion): void {\n throw new Error(\n \"advance() on a live Yjs substrate is not yet supported. \" +\n \"Use advance() on a YjsReplica instead.\",\n )\n },\n\n exportEntirety(): SubstratePayload {\n return {\n kind: \"entirety\",\n encoding: \"binary\",\n data: Y.encodeStateAsUpdate(doc),\n }\n },\n\n exportSince(since: YjsVersion): SubstratePayload | null {\n try {\n const bytes = Y.encodeStateAsUpdate(doc, since.sv)\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, origin?: string): void {\n if (\n payload.encoding !== \"binary\" ||\n !(payload.data instanceof Uint8Array)\n ) {\n throw new Error(\n \"YjsSubstrate.merge expects binary-encoded payloads. \" +\n \"If you recently switched CRDT backends, stale clients may be sending incompatible data.\",\n )\n }\n // Stash origin for the event bridge to pick up\n pendingMergeOrigin = origin\n try {\n Y.applyUpdate(doc, payload.data, origin ?? \"remote\")\n } finally {\n pendingMergeOrigin = undefined\n }\n // That's it — the observeDeep handler bridges events to the\n // changefeed via executeBatch.\n },\n }\n\n // --- Event bridge (registered once at construction) ---\n\n rootMap.observeDeep((events, transaction) => {\n // Ignore our own transactions (changefeed already captured via wrappedPrepare)\n if (transaction.origin === KYNETA_ORIGIN) {\n return\n }\n\n // Convert Yjs events → kyneta Ops\n const ops = eventsToOps(events, schema, binding)\n if (ops.length === 0) {\n return\n }\n\n // Update accumulated delete set BEFORE executeBatch, so version()\n // reflects deletes when notifyLocalChange fires from the changefeed.\n if (transaction.deleteSet.clients.size > 0) {\n accumulatedDs = Y.mergeDeleteSets([accumulatedDs, transaction.deleteSet])\n }\n\n // Determine origin: prefer stashed kyneta origin (from merge),\n // fall back to the transaction's origin if it's a string.\n const origin =\n pendingMergeOrigin ??\n (typeof transaction.origin === \"string\" ? transaction.origin : undefined)\n\n // Lazily ensure the context is built\n const ctx = substrate.context()\n\n // Feed through executeBatch for changefeed delivery.\n // The inOurTransaction guard prevents prepare/onFlush from doing\n // Yjs-side work — the changes are already applied by Yjs.\n inOurTransaction = true\n try {\n executeBatch(ctx, ops, origin)\n } finally {\n inOurTransaction = false\n }\n })\n\n // For local mutations (KYNETA_ORIGIN): the observeDeep handler returns\n // early, so we merge the delete set via afterTransaction instead.\n // afterTransaction fires inside doc.transact() — before onFlush returns\n // — so accumulatedDs is up to date when the changefeed's\n // deliverNotifications → notifyLocalChange → version() fires.\n doc.on(\"afterTransaction\", (transaction: Y.Transaction) => {\n if (\n transaction.origin === KYNETA_ORIGIN &&\n transaction.deleteSet.clients.size > 0\n ) {\n accumulatedDs = Y.mergeDeleteSets([accumulatedDs, transaction.deleteSet])\n }\n })\n\n return substrate as Substrate<YjsVersion>\n}\n\n// ---------------------------------------------------------------------------\n// yjsSubstrateFactory — SubstrateFactory<YjsVersion>\n// ---------------------------------------------------------------------------\n\n/**\n * Factory for constructing Yjs-backed substrates.\n *\n * - `create(schema)` — creates a fresh Y.Doc with empty containers\n * matching the schema structure. No seed data — initial content\n * should be applied via `change()` after construction.\n * - `fromEntirety(payload, schema)` — creates a Y.Doc from an entirety\n * payload, returns a substrate.\n * - `parseVersion(serialized)` — deserializes a YjsVersion.\n *\n * Uses trivialBinding for identity-keying: every path maps to\n * `deriveIdentity(path, 1)` (generation 1, no renames).\n */\n\n/**\n * Compute a trivial SchemaBinding for a schema with no migration history.\n * Every product field maps to `deriveIdentity(path, 1)`.\n */\nfunction trivialBinding(schema: SchemaNode): SchemaBinding {\n if (schema[KIND] === \"product\") {\n return deriveSchemaBinding(schema as ProductSchema, {})\n }\n return { forward: new Map(), inverse: new Map() }\n}\n// ---------------------------------------------------------------------------\n// yjsReplicaFactory — ReplicaFactory<YjsVersion>\n// ---------------------------------------------------------------------------\n\n/**\n * Schema-free replica factory for Yjs substrates.\n *\n * Constructs headless `Replica<YjsVersion>` instances backed by bare\n * `Y.Doc`s — no schema walking, no container initialization, no\n * Reader, no event bridge, no changefeed. Just the CRDT runtime\n * with version tracking and export/merge.\n *\n * Used by conduit participants (stores, routing servers)\n * that need to accumulate state, compute per-peer deltas, and compact\n * storage without ever interpreting document fields.\n */\nexport function createYjsReplica(doc: Y.Doc): Replica<YjsVersion> {\n let currentDoc = doc\n let currentBase: YjsVersion = new YjsVersion(Y.encodeStateVector(new Y.Doc()))\n\n return {\n get [BACKING_DOC]() {\n return currentDoc\n },\n\n version(): YjsVersion {\n return YjsVersion.fromDoc(currentDoc)\n },\n\n baseVersion(): YjsVersion {\n return currentBase\n },\n\n advance(to: YjsVersion): void {\n const baseCmp = currentBase.compare(to)\n if (baseCmp === \"ahead\") {\n throw new Error(\"advance(): target is behind base version\")\n }\n const currentCmp = to.compare(this.version())\n if (currentCmp === \"ahead\") {\n throw new Error(\"advance(): target is ahead of current version\")\n }\n\n // Yjs can only do full projection (to = version).\n // For any to < version, it's a no-op — undershoot contract.\n if (currentCmp !== \"equal\") return\n\n // Full projection: create a new doc with current state, no history.\n const update = Y.encodeStateAsUpdate(currentDoc)\n const newDoc = new Y.Doc()\n Y.applyUpdate(newDoc, update)\n currentDoc = newDoc\n currentBase = YjsVersion.fromDoc(currentDoc)\n },\n\n exportEntirety(): SubstratePayload {\n return {\n kind: \"entirety\",\n encoding: \"binary\",\n data: Y.encodeStateAsUpdate(currentDoc),\n }\n },\n\n exportSince(since: YjsVersion): SubstratePayload | null {\n try {\n const bytes = Y.encodeStateAsUpdate(currentDoc, since.sv)\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, _origin?: string): void {\n if (\n payload.encoding !== \"binary\" ||\n !(payload.data instanceof Uint8Array)\n ) {\n throw new Error(\n \"YjsReplica.merge expects binary-encoded payloads. \" +\n \"If you recently switched CRDT backends, stale clients may be sending incompatible data.\",\n )\n }\n Y.applyUpdate(currentDoc, payload.data)\n },\n } as Replica<YjsVersion>\n}\n\nexport const yjsReplicaFactory: ReplicaFactory<YjsVersion> = {\n replicaType: [\"yjs\", 1, 0] as const,\n\n createEmpty(): Replica<YjsVersion> {\n return createYjsReplica(new Y.Doc())\n },\n\n fromEntirety(payload: SubstratePayload): Replica<YjsVersion> {\n if (\n payload.encoding !== \"binary\" ||\n !(payload.data instanceof Uint8Array)\n ) {\n throw new Error(\n \"YjsReplicaFactory.fromEntirety only supports binary-encoded payloads\",\n )\n }\n const doc = new Y.Doc()\n Y.applyUpdate(doc, payload.data)\n return createYjsReplica(doc)\n },\n\n parseVersion(serialized: string): YjsVersion {\n return YjsVersion.parse(serialized)\n },\n}\n\n// ---------------------------------------------------------------------------\n// yjsSubstrateFactory — SubstrateFactory<YjsVersion>\n// ---------------------------------------------------------------------------\n\nexport const yjsSubstrateFactory: SubstrateFactory<YjsVersion> = {\n replica: yjsReplicaFactory,\n\n createReplica(): Replica<YjsVersion> {\n // Default random clientID — safe for hydration (no local writes).\n return createYjsReplica(new Y.Doc())\n },\n\n upgrade(\n replica: Replica<YjsVersion>,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n const doc = (replica as any)[BACKING_DOC] as Y.Doc\n const binding = trivialBinding(schema)\n // No identity injection for the standalone factory (no peerId).\n // Conditional ensureContainers: skip fields that already exist\n // from hydrated state.\n ensureContainers(doc, schema, true, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n // Fresh doc — unconditional ensureContainers (nothing to conflict with).\n const doc = new Y.Doc()\n const binding = trivialBinding(schema)\n ensureContainers(doc, schema, false, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n fromEntirety(\n payload: SubstratePayload,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n // Two-phase path: createReplica → merge → upgrade\n const replica = this.createReplica()\n replica.merge(payload)\n return this.upgrade(replica, schema)\n },\n\n parseVersion(serialized: string): YjsVersion {\n return YjsVersion.parse(serialized)\n },\n}\n","// bind-yjs — Yjs CRDT binding target and factory internals.\n//\n// The `yjs` binding target provides `yjs.bind()` and `yjs.replica()` for\n// binding schemas to the Yjs substrate with collaborative sync protocol.\n// The factory builder accepts { peerId } and returns a SubstrateFactory\n// that calls doc.clientID = hashPeerId(peerId) on every new Y.Doc,\n// ensuring deterministic peer identity across all documents in an exchange.\n//\n// Yjs clientID is a uint32 number. We use FNV-1a hash truncated to\n// 32 bits, mirroring the Loro binding's hashPeerId pattern but\n// targeting Yjs's number type (not Loro's bigint/53-bit PeerID).\n//\n// Usage:\n// import { yjs } from \"@kyneta/yjs-schema\"\n//\n// const TodoDoc = yjs.bind(Schema.struct({\n// title: Schema.text(),\n// items: Schema.list(Schema.struct({ name: Schema.string() })),\n// }))\n//\n// const doc = exchange.get(\"my-doc\", TodoDoc)\n\nimport type {\n BindingTarget,\n Replica,\n SchemaBinding,\n Schema as SchemaNode,\n Substrate,\n SubstrateFactory,\n SubstratePayload,\n} from \"@kyneta/schema\"\nimport {\n BACKING_DOC,\n createBindingTarget,\n STRUCTURAL_YJS_CLIENT_ID,\n SYNC_COLLABORATIVE,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport type { YjsNativeMap } from \"./native-map.js\"\nimport { ensureContainers } from \"./populate.js\"\nimport {\n createYjsReplica,\n createYjsSubstrate,\n yjsReplicaFactory,\n} from \"./substrate.js\"\nimport { YjsVersion } from \"./version.js\"\n\n// ---------------------------------------------------------------------------\n// Peer ID hashing — deterministic string → numeric Yjs clientID\n// ---------------------------------------------------------------------------\n\n/**\n * Hash a string peerId to a deterministic numeric Yjs clientID.\n *\n * Yjs clientIDs are unsigned 32-bit integers. We use FNV-1a hash to\n * produce a deterministic uint32 from the string peerId.\n *\n * The hash is deterministic: the same string always produces the same\n * numeric clientID, across restarts and across machines.\n */\nfunction hashPeerId(peerId: string): number {\n // FNV-1a 32-bit hash\n let hash = 0x811c9dc5\n for (let i = 0; i < peerId.length; i++) {\n hash ^= peerId.charCodeAt(i)\n // Multiply by FNV prime 0x01000193.\n // Use Math.imul for correct 32-bit integer multiplication.\n hash = Math.imul(hash, 0x01000193)\n }\n // Ensure unsigned 32-bit integer\n const result = hash >>> 0\n // Reserve 0 for structural ops — real peers never collide\n return result === STRUCTURAL_YJS_CLIENT_ID ? 1 : result\n}\n\n// ---------------------------------------------------------------------------\n// createYjsFactory — factory builder with peer identity injection\n// ---------------------------------------------------------------------------\n\n/**\n * Create a SubstrateFactory<YjsVersion> that sets doc.clientID\n * on every new Y.Doc with a deterministic uint32 clientID derived\n * from the exchange's string peerId.\n */\nfunction createYjsFactory(\n peerId: string,\n binding: SchemaBinding,\n): SubstrateFactory<YjsVersion> {\n const numericClientId = hashPeerId(peerId)\n\n return {\n replica: yjsReplicaFactory,\n\n createReplica(): Replica<YjsVersion> {\n // Default random clientID — safe for hydration (no local writes).\n // Identity is set at upgrade() time, after hydration.\n return createYjsReplica(new Y.Doc())\n },\n\n upgrade(\n replica: Replica<YjsVersion>,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n const doc = (replica as any)[BACKING_DOC] as Y.Doc\n // Set stable identity AFTER hydration — avoids Yjs clientID\n // conflict detection that would reassign to a random value.\n doc.clientID = numericClientId\n // Conditional ensureContainers: skip fields that already exist\n // from hydrated state (each set() is a CRDT write).\n ensureContainers(doc, schema, true, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n // Fresh doc — set identity immediately, unconditional containers.\n const doc = new Y.Doc()\n doc.clientID = numericClientId\n ensureContainers(doc, schema, false, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n fromEntirety(\n payload: SubstratePayload,\n schema: SchemaNode,\n ): Substrate<YjsVersion> {\n // Two-phase path: createReplica → merge → upgrade\n // Identity is set at upgrade() time, after hydration —\n // avoids Yjs clientID conflict detection.\n const replica = this.createReplica()\n replica.merge(payload)\n return this.upgrade(replica, schema)\n },\n\n parseVersion(serialized: string): YjsVersion {\n return YjsVersion.parse(serialized)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// yjs — the Yjs CRDT binding target\n// ---------------------------------------------------------------------------\n\n/**\n * Yjs composition-law tags — the set of concurrent composition laws\n * that the Yjs substrate faithfully implements.\n */\nexport type YjsLaws =\n | \"lww\"\n | \"positional-ot\"\n | \"lww-per-key\"\n | \"lww-tag-replaced\"\n\n/**\n * The Yjs CRDT binding target.\n *\n * - `yjs.bind(schema)` — bind a schema to Yjs with collaborative sync\n * - `yjs.replica()` — create a collaborative replica\n *\n * Laws are constrained to `YjsLaws` — schemas requiring composition laws\n * outside this set (e.g. `\"additive\"` from `Schema.counter()`,\n * `\"positional-ot-move\"` from `Schema.movableList()`) are rejected at\n * compile time.\n *\n * To access the underlying Y.Doc, use `unwrap(ref)` from `@kyneta/schema`.\n */\nexport const yjs: BindingTarget<YjsLaws, YjsNativeMap> = createBindingTarget<\n YjsLaws,\n YjsNativeMap\n>({\n factory: ctx => createYjsFactory(ctx.peerId, ctx.binding),\n replicaFactory: yjsReplicaFactory,\n syncProtocol: SYNC_COLLABORATIVE,\n})\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kyneta/yjs-schema",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Yjs CRDT substrate for @kyneta/schema — collaborative data types with typed refs",
|
|
5
5
|
"author": "Duane Johnson",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,20 +30,20 @@
|
|
|
30
30
|
"./src/*": "./src/*"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@kyneta/changefeed": "^1.
|
|
34
|
-
"@kyneta/schema": "^1.
|
|
33
|
+
"@kyneta/changefeed": "^1.4.0",
|
|
34
|
+
"@kyneta/schema": "^1.4.0",
|
|
35
35
|
"yjs": ">=13.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"
|
|
38
|
+
"tsdown": "^0.21.9",
|
|
39
39
|
"typescript": "^5.9.2",
|
|
40
40
|
"vitest": "^4.0.17",
|
|
41
41
|
"yjs": "^13.6.30",
|
|
42
|
-
"@kyneta/changefeed": "^1.
|
|
43
|
-
"@kyneta/schema": "^1.
|
|
42
|
+
"@kyneta/changefeed": "^1.4.0",
|
|
43
|
+
"@kyneta/schema": "^1.4.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
-
"build": "
|
|
46
|
+
"build": "tsdown",
|
|
47
47
|
"test": "verify logic",
|
|
48
48
|
"verify": "verify"
|
|
49
49
|
}
|