@kyneta/yjs-schema 1.8.0 → 2.0.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 +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/bind-yjs.test.ts +7 -7
- package/src/__tests__/create.test.ts +50 -50
- package/src/__tests__/eager-write-coherence.test.ts +21 -21
- package/src/__tests__/materialize.test.ts +13 -13
- package/src/__tests__/position.test.ts +18 -18
- package/src/__tests__/record-text-spike.test.ts +34 -34
- package/src/__tests__/substrate.test.ts +53 -53
- package/src/bind-yjs.ts +1 -1
- package/src/index.ts +1 -1
- package/src/substrate.ts +36 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["encodeStateVector","yjsEncodeStateVector","yjsSnapshot"],"sources":["../src/populate.ts","../src/yjs-resolve.ts","../src/change-mapping.ts","../src/yjs-extract.ts","../src/materialize.ts","../src/position.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.\n//\n// Only container types (text, product, sequence, map) require CRDT\n// writes here. Scalar and sum fields are handled by the materializer's\n// zero fallback — no Yjs writes are needed for non-container types.\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 { isJsonBoundary, KIND, STRUCTURAL_YJS_CLIENT_ID } 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 * Container fields (text, product, sequence, map) are created if absent;\n * existing containers are preserved (calling `rootMap.set()` on a field\n * that already exists would be a destructive CRDT write). Scalar and sum\n * fields are no-ops — the materializer handles zeros.\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 binding - Optional SchemaBinding for identity-keyed containers.\n */\nexport function ensureContainers(\n doc: Y.Doc,\n schema: SchemaNode,\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 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\"` → no-op (materializer zero fallback)\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 // Skip fields that already exist — calling rootMap.set() on an existing\n // shared type would replace it (a destructive CRDT write), and scalars\n // are no-ops regardless. This is safe on fresh docs (nothing to skip)\n // and necessary on hydrated docs (preserves existing data).\n if (rootMap.has(key)) return\n\n // JSON-boundary fields (struct.json/list.json/record.json) store the\n // subtree as a plain JSON value in the root Y.Map entry. We leave the\n // entry absent at structural-init time; the first write materialises\n // it with `rootMap.set(key, plainValue)`.\n if (isJsonBoundary(fieldSchema)) return\n\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 // Value concerns are handled by the materializer's zero fallback.\n // No CRDT writes needed for non-container types.\n return\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 skipped (materializer zero fallback).\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 // JSON-boundary nested field: leave the entry absent, the first\n // write will set the plain JSON value at this key.\n if (isJsonBoundary(fieldSchema)) {\n continue\n }\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 // Value concerns are handled by the materializer's zero fallback.\n // No CRDT writes needed for non-container types.\n break\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// `stepIntoYjs` is the per-step substrate dispatch; `resolveYjsType`\n// applies the core `foldPath` primitive (from `@kyneta/schema`) around\n// it. The semantic invariants of the fold — identity-keying at\n// product-field boundaries, sum-boundary short-circuit — live in\n// `fold-path.ts`, not here.\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 {\n foldPath,\n type Path,\n type PathFoldResult,\n type PathStepper,\n type SchemaBinding,\n type Schema as SchemaNode,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// stepIntoYjs — per-step substrate dispatch (PathStepper for Yjs)\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 * `_nextSchema` is part of the `PathStepper` contract for Loro's root\n * dispatch but is unused here — Yjs's `instanceof` dispatch doesn't\n * need to look ahead at the next schema kind.\n */\nexport const stepIntoYjs: PathStepper = (\n current,\n _nextSchema,\n segment,\n identity,\n) => {\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 foldPath\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a Yjs shared type (or plain value) at the given path.\n *\n * Thin wrapper around `foldPath(stepIntoYjs, ...)`. Returns the\n * `PathFoldResult` shape from core — `{ resolved, schema }`.\n *\n * When a `binding` is provided, every product-field boundary uses the\n * identity hash from `binding.forward` instead of the field name.\n *\n * For an empty path, returns the root map and root schema.\n */\nexport function resolveYjsType(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n binding?: SchemaBinding,\n): PathFoldResult {\n return foldPath(rootMap, rootSchema, path, stepIntoYjs, binding)\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 ProductSchema,\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 expandMapOpsToLeaves,\n isJsonBoundary,\n isPlainObject,\n KIND,\n pathSchema,\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 case \"set-op\":\n // Sets (`Schema.set`) are rejected by `yjs.bind` at compile time\n // (`\"add-wins-per-key\"` is not in `YjsLaws`). Unreachable from any\n // bound Yjs substrate today; kept against the new `SetChange`\n // vocabulary so future law-set expansion has a clear extension point.\n throw new Error(\n `Yjs substrate does not support \"${change.type}\" changes. ` +\n `Schema.set requires \"add-wins-per-key\" which is not in YjsLaws. ` +\n `Attempted SetChange 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 = pathSchema(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 = pathSchema(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 — only product-field\n // segments contribute (entry segments are runtime keys).\n const parentAbsPath = path.segments\n .filter(s => s.role === \"field\")\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 \"Cannot replace the root document struct in a CRDT backend. The root identity is fixed. Please mutate its properties individually (e.g., `doc.myField.set(value)` instead of `doc.set({ myField: value })`).\",\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 (\n parent instanceof Y.Map &&\n (lastSeg.role === \"field\" || lastSeg.role === \"entry\")\n ) {\n // Resolve schema for the target field for structured value detection\n const targetSchema = pathSchema(rootSchema, path)\n const yjsValue = maybeCreateSharedType(change.value, targetSchema)\n // Identity-keying applies only at product-field boundaries; entry\n // segments use the runtime key as-is.\n let mapKey = resolved as string\n if (binding && lastSeg.role === \"field\") {\n const absPath = path.segments\n .filter(s => s.role === \"field\")\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 = pathSchema(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 // JSON-boundary schemas (struct.json/list.json/record.json) store\n // their entire subtree as a plain JSON value in the parent Y.Map\n // entry. Skip Y.Map/Y.Array materialisation and pass the value\n // through unchanged — including nested objects/arrays underneath.\n if (isJsonBoundary(schema)) 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 (!isPlainObject(value)) {\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 (!isPlainObject(value)) {\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, schema, 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 to a kyneta `RawPath`, walking the schema\n * alongside so each segment is classified as field / entry / index by\n * the current schema kind.\n *\n * Why schema-aware and not \"did the inverse lookup hit?\": the binding's\n * inverse map only covers declared product-field positions reachable\n * without crossing a runtime-keyed container. A declared struct field\n * nested under a `record(...)` value type is reachable via Yjs but\n * absent from `binding.inverse` — without the schema walk it would be\n * misclassified as an entry and then rejected by `advanceSchema`.\n */\nfunction yjsPathToKynetaPath(\n yjsPath: (string | number)[],\n rootSchema: SchemaNode,\n binding?: SchemaBinding,\n): RawPath {\n let path = RawPath.empty\n let schema: SchemaNode | undefined = rootSchema\n for (const segment of yjsPath) {\n if (typeof segment === \"string\") {\n // Inverse-lookup recovers the original declared field name when\n // the segment IS an identity hash; otherwise we keep the string.\n let leaf = segment\n const absPath = binding?.inverse.get(segment as any)\n if (absPath) {\n const lastDot = absPath.lastIndexOf(\".\")\n leaf = lastDot >= 0 ? absPath.slice(lastDot + 1) : absPath\n }\n const kind = schema?.[KIND]\n if (kind === \"product\") {\n path = path.field(leaf)\n schema = (schema as ProductSchema | undefined)?.fields[leaf]\n } else if (kind === \"map\" || kind === \"set\" || kind === \"tree\") {\n path = path.entry(leaf)\n schema = (schema as any)?.item\n } else {\n // Unknown / sum / unrecognized — fall back to entry. Subsequent\n // segments are likely walking plain JSON inside a sum variant.\n path = path.entry(leaf)\n schema = undefined\n }\n } else if (typeof segment === \"number\") {\n path = path.item(segment)\n const kind = schema?.[KIND]\n if (kind === \"sequence\" || kind === \"movable\") {\n schema = (schema as any).item\n } else {\n schema = undefined\n }\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 = pathSchema(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 * 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-extract — shared value-extraction helpers for Yjs shared types.\n//\n// These functions are used by both the reader (yjsReader) and the\n// materialize interpreter to convert Yjs shared types into plain values.\n\nimport type { RichTextDelta, RichTextSpan } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\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 */\nexport function 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 * 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 */\nexport function 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","// materialize — Yjs→PlainState materialization via generic resolver.\n//\n// Implements `createYjsResolver`, a closure-based `MaterializeResolver`\n// that navigates the Yjs shared type tree via `resolveYjsType`. The\n// generic `createMaterializeInterpreter` drives the catamorphism; the\n// resolver handles only the CRDT-specific value extraction.\n//\n// Unsupported types (counter, tree, movable) return `undefined` from\n// the resolver, triggering the generic interpreter's zero fallback.\n//\n// Zero fallback for missing values is handled canonically by the\n// generic interpreter — not inlined here.\n\nimport type {\n MaterializeResolver,\n Path,\n PlainState,\n RichTextDelta,\n SchemaBinding,\n Schema as SchemaNode,\n} from \"@kyneta/schema\"\nimport {\n createMaterializeInterpreter,\n interpret,\n isNonNullObject,\n materializeContextFromResolver,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { extractValue, yTextToRichTextDelta } from \"./yjs-extract.js\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Yjs resolver\n// ---------------------------------------------------------------------------\n\nfunction createYjsResolver(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n binding?: SchemaBinding,\n): MaterializeResolver {\n return {\n resolveValue(path: Path): unknown {\n const result = resolveYjsType(rootMap, rootSchema, path, binding)\n return extractValue(result.resolved)\n },\n\n resolveText(path: Path): string | undefined {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Text) {\n return resolved.toJSON()\n }\n const value = extractValue(resolved)\n return typeof value === \"string\" ? value : undefined\n },\n\n // Yjs does not support counters — schemas with counter types are\n // rejected at bind time. Return undefined to trigger zero fallback.\n resolveCounter(_path: Path): number | undefined {\n return undefined\n },\n\n resolveRichText(path: Path): RichTextDelta | undefined {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Text) {\n return yTextToRichTextDelta(resolved)\n }\n return undefined\n },\n\n resolveLength(path: Path): number {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Array) {\n return resolved.length\n }\n return Array.isArray(resolved) ? resolved.length : 0\n },\n\n resolveKeys(path: Path): string[] {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Map) {\n return Array.from(resolved.keys())\n }\n return isNonNullObject(resolved) ? Object.keys(resolved) : []\n },\n\n // Yjs has no tree primitive — schemas with `Schema.tree` are rejected\n // at bind time. Defensive [] for any caller that reaches here.\n resolveForest(_path: Path): readonly never[] {\n return []\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport function materializeYjsShadow(\n doc: Y.Doc,\n schema: SchemaNode,\n binding?: SchemaBinding,\n): PlainState {\n const rootMap = doc.getMap(\"root\")\n const resolver = createYjsResolver(rootMap, schema, binding)\n const interp = createMaterializeInterpreter(resolver)\n const ctx = materializeContextFromResolver(resolver)\n const result = interpret(schema, interp, ctx)\n return result as PlainState\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","// 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-eager local writes: `prepare` advances both the shadow σ\n// AND the native Y.Doc tree λ inside the ambient `Y.transact` opened\n// by `runBatch`. The projection law `σ ≡ Π(λ)` holds at every prepare\n// boundary — re-entrant subscribers reading either σ (via the Reader)\n// or λ (via `unwrap`) see a coherent state.\n// - `runBatch(body)` opens one `Y.transact(doc, body, options?.origin)` per\n// outermost logical action. Yjs's native transact nesting collapses\n// inner re-entrant transacts into the outer one for free — no depth\n// counter needed (unlike Loro). External `observeDeep` consumers see\n// exactly one batched event per outermost `change(doc, fn)`.\n// - JSON-boundary writes (struct.json/list.json/record.json subtrees)\n// are buffered in a per-target-key coalescer and flushed in\n// `afterBatch`. Non-boundary writes are applied directly to λ via\n// `applyChangeToYjs`.\n// - `afterBatch` flushes the json-boundary coalescer on local writes\n// and re-materialises σ from λ on replay.\n// - Persistent observeDeep event bridge for external changes.\n// - Per-transaction meta mark (`KYNETA_MARK`) inscribed from inside the\n// transact body to ignore our own writes; survives Yjs's nested-transact\n// collapse so external wrapping is handled correctly.\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// `prepare` and `afterBatch` accept `BatchOptions` and branch on\n// `options?.replay`. The event bridge constructs the replay batch via\n// `executeBatch(ctx, ops, { origin, replay: true })`; substrate-side\n// work (transact, write) is skipped when `replay` is true because the\n// native Y.Doc already absorbed the change. This makes `prepare` and\n// `afterBatch` total functions of their declared inputs — no hidden\n// ambient state for the substrate-write decision. Context: jj:qpultxsw.\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 BatchOptions,\n ChangeBase,\n Path,\n PlainState,\n PositionCapable,\n ProductSchema,\n Reader,\n RecordInverseFn,\n Replica,\n ReplicaFactory,\n SchemaBinding,\n Schema as SchemaNode,\n Side,\n Substrate,\n SubstrateFactory,\n SubstratePayload,\n Version,\n WritableContext,\n} from \"@kyneta/schema\"\nimport {\n applyChange,\n BACKING_DOC,\n buildWritableContext,\n deepClonePreState,\n deriveSchemaBinding,\n executeBatch,\n findJsonBoundary,\n invert,\n KIND,\n plainReader,\n RECORD_INVERSE,\n syncShadow,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { applyChangeToYjs, eventsToOps } from \"./change-mapping.js\"\nimport { materializeYjsShadow } from \"./materialize.js\"\nimport { ensureContainers } from \"./populate.js\"\nimport { toYjsAssoc, YjsPosition } from \"./position.js\"\nimport { YjsVersion } from \"./version.js\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Own-commit discriminator\n// ---------------------------------------------------------------------------\n\n// Own-commit discriminator. We mark `transaction.meta` from inside\n// the transact body — the mark travels with the Transaction object\n// that Yjs hands to observeDeep, regardless of `transaction.origin`.\n// This frees the user-facing `origin` slot for `options.origin`\n// round-trip and correctly handles the case where external code\n// wraps `change(doc, fn)` in its own `Y.transact` (Yjs's nested-\n// transact collapse delivers the SAME Transaction object to both\n// outer and inner callbacks; verified by probe — see TECHNICAL.md\n// \"Why transaction.meta mark\").\nconst KYNETA_MARK = Symbol(\"kyneta:own-commit\")\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 // JSON-boundary coalescing buffer. Keyed by the target Y.Map and the\n // boundary key — repeated writes inside the same struct.json /\n // record.json subtree overwrite the entry with the latest σ value\n // before `afterBatch` flushes it back into λ as a single\n // `target.set(key, value)`. Non-boundary writes bypass the buffer\n // entirely — they go straight to `applyChangeToYjs` in prepare.\n const jsonBoundaryBuffer = new Map<\n string,\n {\n target: Y.Map<unknown> | Y.Array<unknown>\n key: string | number\n value: unknown\n }\n >()\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 shadow — a plain JS object materialized from the Y.Doc.\n // Kept in sync by applyChange() in prepare().\n const shadow: PlainState = materializeYjsShadow(doc, schema, binding)\n const reader: Reader = plainReader(shadow)\n\n // --- Coalescer helpers ---\n\n /**\n * Compute the identity-aware boundary key (or numeric index) for a\n * json-boundary write at `prefixLength`. Mirrors the Loro substrate's\n * `boundaryKey`; field segments inside a bound product get the\n * identity hash, others pass through raw.\n */\n function boundaryKey(path: Path, prefixLength: number): string | number {\n const seg = path.segments[prefixLength]!\n if (seg.role === \"field\" && binding) {\n const absPath = path.segments\n .slice(0, prefixLength + 1)\n .filter(s => s.role === \"field\")\n .map(s => s.resolve() as string)\n .join(\".\")\n const identity = binding.forward.get(absPath) as string | undefined\n if (identity) return identity\n }\n return seg.resolve() as string | number\n }\n\n /**\n * Buffer a json-boundary write. The boundary value is the entire σ\n * subtree at the boundary path — already updated by the preceding\n * `applyChange(shadow, ...)`. Subsequent writes inside the same\n * subtree overwrite this entry (last-write-wins by σ snapshot).\n *\n * Returns silently when the parent container can't be resolved\n * (root-level json fields land in `rootMap` directly — Yjs's\n * root is the rootMap, so the parentResolved is `rootMap`).\n */\n function stageJsonBoundaryWrite(path: Path, prefixLength: number): void {\n const parentPath = path.slice(0, prefixLength)\n const { resolved: parent } = resolveYjsType(\n rootMap,\n schema,\n parentPath,\n binding,\n )\n const boundaryPath = path.slice(0, prefixLength + 1)\n const value = boundaryPath.read(shadow)\n const key = boundaryKey(path, prefixLength)\n\n // The target can be either a Y.Map (struct field, record entry,\n // or rootMap) or a Y.Array (list/movable item). Both expose a\n // shape we can stash and flush in `afterBatch`.\n let target: Y.Map<unknown> | Y.Array<unknown>\n if (parent instanceof Y.Map) {\n target = parent\n } else if (parent instanceof Y.Array) {\n target = parent\n } else {\n throw new Error(\n `yjs substrate: json-boundary write to unsupported parent type at path ${path.format()}`,\n )\n }\n\n // Use the Yjs shared-type's stable identity for the buffer key\n // when available; fall back to a unique sentinel for the\n // ultra-rare case where `_item` is undefined (freshly-created\n // shared types before they're attached). Combine with key/index\n // for a unique slot — repeat writes to the same slot overwrite.\n const targetId = `${(target as any)._item?.id?.client ?? \"root\"}:${(target as any)._item?.id?.clock ?? \"root\"}`\n const slot = `${targetId}/${String(key)}`\n jsonBoundaryBuffer.set(slot, { target, key, value })\n }\n\n /**\n * Drain the json-boundary buffer into λ. Called from `afterBatch`\n * inside the ambient `Y.transact` opened by `runBatch`. Each entry\n * is applied as `target.set(key, value)` for Y.Map parents or as a\n * delete+insert for Y.Array parents (Yjs Arrays don't have a\n * `set(index, value)` primitive — replace = delete one + insert one).\n */\n function flushJsonBoundaryBuffer(): void {\n if (jsonBoundaryBuffer.size === 0) return\n for (const { target, key, value } of jsonBoundaryBuffer.values()) {\n if (target instanceof Y.Map) {\n target.set(String(key), value)\n } else {\n const index = key as number\n target.delete(index, 1)\n target.insert(index, [value])\n }\n }\n jsonBoundaryBuffer.clear()\n }\n\n // --- Substrate object ---\n\n const substrate = {\n [BACKING_DOC]: doc,\n\n reader: reader,\n\n prepare(path: Path, change: ChangeBase, options?: BatchOptions): void {\n // Replay writes: λ has already absorbed these ops via\n // Y.applyUpdate at the event-bridge call site; skip σ/λ\n // advance — afterBatch(replay) rebuilds σ from λ in one\n // Π pass.\n if (options?.replay) return\n\n // Inverse recording under the normal handler. Capture σ at the\n // target path before applyChange mutates the shadow; the\n // recording closure pushes onto the active runBatch frame's\n // stack. Skipped under the undo-replay handler (compensating).\n const record = (\n options as\n | (BatchOptions & { [RECORD_INVERSE]?: RecordInverseFn })\n | undefined\n )?.[RECORD_INVERSE]\n if (record && !options?.compensating) {\n const pre = deepClonePreState(path.read(shadow))\n const inverse = invert(pre, change)\n record(path, inverse)\n }\n\n // Local write — σ advances eagerly. CRDT-side writes happen\n // inside the ambient Y.transact opened by runBatch (the\n // substrate's `runBatch` wraps `executeBatch`'s prepare-loop +\n // flush).\n applyChange(shadow, path, change)\n\n // JSON-boundary write: stage a full-value write at the\n // boundary segment of the parent container. Coalesces with\n // repeated writes inside the same subtree (last σ snapshot\n // wins) and lands in λ on `afterBatch` flush.\n const boundary = findJsonBoundary(schema, path, binding)\n if (boundary !== null) {\n stageJsonBoundaryWrite(path, boundary.prefixLength)\n return\n }\n\n // Non-boundary write: imperatively apply to λ inside the\n // ambient Y.transact. The KYNETA_ORIGIN tag lets the\n // observeDeep bridge below recognise and skip the events we\n // generate here, so the changefeed isn't fired twice.\n applyChangeToYjs(rootMap, schema, path, change, binding)\n },\n\n afterBatch(options?: BatchOptions): void {\n if (options?.replay) {\n // CRDT merge is a lattice join — re-materialise σ from λ in\n // one Π pass instead of replaying ops incrementally.\n syncShadow(shadow, materializeYjsShadow(doc, schema, binding))\n return\n }\n // Local write: drain the json-boundary coalescer. Runs inside\n // the ambient Y.transact from `runBatch`; the transact closes\n // when `runBatch`'s body returns, emitting one batched\n // observeDeep event for the whole logical action.\n flushJsonBoundaryBuffer()\n },\n\n runBatch(work: () => void, options?: BatchOptions): void {\n // Yjs's native transact nesting collapses inner re-entrant\n // transacts into the outermost — exactly the \"one batched\n // event per outermost logical action\" semantic we want. No\n // depth counter needed.\n //\n // We mark the transaction via `tr.meta.set` inside the transact body.\n // The mark lives on per-transaction meta, orthogonal to origin.\n // The app-level `options?.origin` flows directly to `transaction.origin`\n // and round-trips to the changefeed layer.\n doc.transact(tr => {\n tr.meta.set(KYNETA_MARK, true)\n work()\n }, options?.origin)\n },\n\n context(): WritableContext {\n if (!cachedCtx) {\n cachedCtx = buildWritableContext(substrate, {\n 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)\n .resolved\n },\n 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 }\n return cachedCtx\n },\n\n version(): YjsVersion {\n // Derive the deleteSet from the live struct store on every read.\n // Eager-prepare delivers notifications *inside* the ambient\n // `Y.transact` opened by `runBatch`, so `afterTransaction` fires\n // AFTER the changefeed's notify pipeline. Computing from the\n // store picks up in-progress deletes too, so the exchange's\n // auto-subscribe sees a version that already reflects the\n // just-applied mutation.\n return YjsVersion.fromDeleteSet(\n doc,\n Y.createDeleteSetFromStructStore(doc.store),\n )\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: Version): SubstratePayload | null {\n try {\n // ReplicaLike variance: signature uses Version, runtime type is always YjsVersion.\n const bytes = Y.encodeStateAsUpdate(doc, (since as YjsVersion).sv)\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, options?: BatchOptions): 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 = options?.origin\n try {\n Y.applyUpdate(doc, payload.data, options?.origin ?? \"remote\")\n } finally {\n pendingMergeOrigin = undefined\n }\n // That's it — the observeDeep handler bridges events to the\n // changefeed via executeBatch with `replay: true`.\n },\n }\n\n // --- Event bridge (registered once at construction) ---\n\n rootMap.observeDeep((events, transaction) => {\n // Own-commit discriminator: kyneta's runBatch marks the transaction\n // via `tr.meta.set` inside the transact body. The mark survives Yjs's\n // nested-transact collapse, so external code wrapping `change()` in\n // its own Y.transact is correctly classified as own.\n if (transaction.meta.get(KYNETA_MARK)) {\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 // 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 // `replay: true` tells substrate.prepare/afterBatch to skip native-side\n // work (Yjs has already absorbed these ops via Y.applyUpdate) and\n // surfaces on the Changeset for downstream filters (exchange echo).\n executeBatch(ctx, ops, { origin, replay: true })\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: Version): 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: Version): SubstratePayload | null {\n try {\n // The ReplicaLike contract uses the base `Version` type for variance\n // safety. At runtime the synchronizer always passes a YjsVersion from\n // this replica's own factory — the cast is sound.\n const bytes = Y.encodeStateAsUpdate(\n currentDoc,\n (since as YjsVersion).sv,\n )\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, _options?: BatchOptions): 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 ensureContainers(doc, schema, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n const doc = new Y.Doc()\n const binding = trivialBinding(schema)\n ensureContainers(doc, schema, 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 ensureContainers(doc, schema, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n // Fresh doc — set identity immediately.\n const doc = new Y.Doc()\n doc.clientID = numericClientId\n ensureContainers(doc, schema, 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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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["encodeStateVector","yjsEncodeStateVector","yjsSnapshot"],"sources":["../src/populate.ts","../src/yjs-resolve.ts","../src/change-mapping.ts","../src/yjs-extract.ts","../src/materialize.ts","../src/position.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.\n//\n// Only container types (text, product, sequence, map) require CRDT\n// writes here. Scalar and sum fields are handled by the materializer's\n// zero fallback — no Yjs writes are needed for non-container types.\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 { isJsonBoundary, KIND, STRUCTURAL_YJS_CLIENT_ID } 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 * Container fields (text, product, sequence, map) are created if absent;\n * existing containers are preserved (calling `rootMap.set()` on a field\n * that already exists would be a destructive CRDT write). Scalar and sum\n * fields are no-ops — the materializer handles zeros.\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 binding - Optional SchemaBinding for identity-keyed containers.\n */\nexport function ensureContainers(\n doc: Y.Doc,\n schema: SchemaNode,\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 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\"` → no-op (materializer zero fallback)\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 // Skip fields that already exist — calling rootMap.set() on an existing\n // shared type would replace it (a destructive CRDT write), and scalars\n // are no-ops regardless. This is safe on fresh docs (nothing to skip)\n // and necessary on hydrated docs (preserves existing data).\n if (rootMap.has(key)) return\n\n // JSON-boundary fields (struct.json/list.json/record.json) store the\n // subtree as a plain JSON value in the root Y.Map entry. We leave the\n // entry absent at structural-init time; the first write materialises\n // it with `rootMap.set(key, plainValue)`.\n if (isJsonBoundary(fieldSchema)) return\n\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 // Value concerns are handled by the materializer's zero fallback.\n // No CRDT writes needed for non-container types.\n return\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 skipped (materializer zero fallback).\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 // JSON-boundary nested field: leave the entry absent, the first\n // write will set the plain JSON value at this key.\n if (isJsonBoundary(fieldSchema)) {\n continue\n }\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 // Value concerns are handled by the materializer's zero fallback.\n // No CRDT writes needed for non-container types.\n break\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// `stepIntoYjs` is the per-step substrate dispatch; `resolveYjsType`\n// applies the core `foldPath` primitive (from `@kyneta/schema`) around\n// it. The semantic invariants of the fold — identity-keying at\n// product-field boundaries, sum-boundary short-circuit — live in\n// `fold-path.ts`, not here.\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 {\n foldPath,\n type Path,\n type PathFoldResult,\n type PathStepper,\n type SchemaBinding,\n type Schema as SchemaNode,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\n\n// ---------------------------------------------------------------------------\n// stepIntoYjs — per-step substrate dispatch (PathStepper for Yjs)\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 * `_nextSchema` is part of the `PathStepper` contract for Loro's root\n * dispatch but is unused here — Yjs's `instanceof` dispatch doesn't\n * need to look ahead at the next schema kind.\n */\nexport const stepIntoYjs: PathStepper = (\n current,\n _nextSchema,\n segment,\n identity,\n) => {\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 foldPath\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a Yjs shared type (or plain value) at the given path.\n *\n * Thin wrapper around `foldPath(stepIntoYjs, ...)`. Returns the\n * `PathFoldResult` shape from core — `{ resolved, schema }`.\n *\n * When a `binding` is provided, every product-field boundary uses the\n * identity hash from `binding.forward` instead of the field name.\n *\n * For an empty path, returns the root map and root schema.\n */\nexport function resolveYjsType(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n path: Path,\n binding?: SchemaBinding,\n): PathFoldResult {\n return foldPath(rootMap, rootSchema, path, stepIntoYjs, binding)\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 ProductSchema,\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 expandMapOpsToLeaves,\n isJsonBoundary,\n isPlainObject,\n KIND,\n pathSchema,\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 case \"set-op\":\n // Sets (`Schema.set`) are rejected by `yjs.bind` at compile time\n // (`\"add-wins-per-key\"` is not in `YjsLaws`). Unreachable from any\n // bound Yjs substrate today; kept against the new `SetChange`\n // vocabulary so future law-set expansion has a clear extension point.\n throw new Error(\n `Yjs substrate does not support \"${change.type}\" changes. ` +\n `Schema.set requires \"add-wins-per-key\" which is not in YjsLaws. ` +\n `Attempted SetChange 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 = pathSchema(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 = pathSchema(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 — only product-field\n // segments contribute (entry segments are runtime keys).\n const parentAbsPath = path.segments\n .filter(s => s.role === \"field\")\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 \"Cannot replace the root document struct in a CRDT backend. The root identity is fixed. Please mutate its properties individually (e.g., `doc.myField.set(value)` instead of `doc.set({ myField: value })`).\",\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 (\n parent instanceof Y.Map &&\n (lastSeg.role === \"field\" || lastSeg.role === \"entry\")\n ) {\n // Resolve schema for the target field for structured value detection\n const targetSchema = pathSchema(rootSchema, path)\n const yjsValue = maybeCreateSharedType(change.value, targetSchema)\n // Identity-keying applies only at product-field boundaries; entry\n // segments use the runtime key as-is.\n let mapKey = resolved as string\n if (binding && lastSeg.role === \"field\") {\n const absPath = path.segments\n .filter(s => s.role === \"field\")\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 = pathSchema(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 // JSON-boundary schemas (struct.json/list.json/record.json) store\n // their entire subtree as a plain JSON value in the parent Y.Map\n // entry. Skip Y.Map/Y.Array materialisation and pass the value\n // through unchanged — including nested objects/arrays underneath.\n if (isJsonBoundary(schema)) 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 (!isPlainObject(value)) {\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 (!isPlainObject(value)) {\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, schema, 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 to a kyneta `RawPath`, walking the schema\n * alongside so each segment is classified as field / entry / index by\n * the current schema kind.\n *\n * Why schema-aware and not \"did the inverse lookup hit?\": the binding's\n * inverse map only covers declared product-field positions reachable\n * without crossing a runtime-keyed container. A declared struct field\n * nested under a `record(...)` value type is reachable via Yjs but\n * absent from `binding.inverse` — without the schema walk it would be\n * misclassified as an entry and then rejected by `advanceSchema`.\n */\nfunction yjsPathToKynetaPath(\n yjsPath: (string | number)[],\n rootSchema: SchemaNode,\n binding?: SchemaBinding,\n): RawPath {\n let path = RawPath.empty\n let schema: SchemaNode | undefined = rootSchema\n for (const segment of yjsPath) {\n if (typeof segment === \"string\") {\n // Inverse-lookup recovers the original declared field name when\n // the segment IS an identity hash; otherwise we keep the string.\n let leaf = segment\n const absPath = binding?.inverse.get(segment as any)\n if (absPath) {\n const lastDot = absPath.lastIndexOf(\".\")\n leaf = lastDot >= 0 ? absPath.slice(lastDot + 1) : absPath\n }\n const kind = schema?.[KIND]\n if (kind === \"product\") {\n path = path.field(leaf)\n schema = (schema as ProductSchema | undefined)?.fields[leaf]\n } else if (kind === \"map\" || kind === \"set\" || kind === \"tree\") {\n path = path.entry(leaf)\n schema = (schema as any)?.item\n } else {\n // Unknown / sum / unrecognized — fall back to entry. Subsequent\n // segments are likely walking plain JSON inside a sum variant.\n path = path.entry(leaf)\n schema = undefined\n }\n } else if (typeof segment === \"number\") {\n path = path.item(segment)\n const kind = schema?.[KIND]\n if (kind === \"sequence\" || kind === \"movable\") {\n schema = (schema as any).item\n } else {\n schema = undefined\n }\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 = pathSchema(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 * 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-extract — shared value-extraction helpers for Yjs shared types.\n//\n// These functions are used by both the reader (yjsReader) and the\n// materialize interpreter to convert Yjs shared types into plain values.\n\nimport type { RichTextDelta, RichTextSpan } from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\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 */\nexport function 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 * 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 */\nexport function 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","// materialize — Yjs→PlainState materialization via generic resolver.\n//\n// Implements `createYjsResolver`, a closure-based `MaterializeResolver`\n// that navigates the Yjs shared type tree via `resolveYjsType`. The\n// generic `createMaterializeInterpreter` drives the catamorphism; the\n// resolver handles only the CRDT-specific value extraction.\n//\n// Unsupported types (counter, tree, movable) return `undefined` from\n// the resolver, triggering the generic interpreter's zero fallback.\n//\n// Zero fallback for missing values is handled canonically by the\n// generic interpreter — not inlined here.\n\nimport type {\n MaterializeResolver,\n Path,\n PlainState,\n RichTextDelta,\n SchemaBinding,\n Schema as SchemaNode,\n} from \"@kyneta/schema\"\nimport {\n createMaterializeInterpreter,\n interpret,\n isNonNullObject,\n materializeContextFromResolver,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { extractValue, yTextToRichTextDelta } from \"./yjs-extract.js\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Yjs resolver\n// ---------------------------------------------------------------------------\n\nfunction createYjsResolver(\n rootMap: Y.Map<any>,\n rootSchema: SchemaNode,\n binding?: SchemaBinding,\n): MaterializeResolver {\n return {\n resolveValue(path: Path): unknown {\n const result = resolveYjsType(rootMap, rootSchema, path, binding)\n return extractValue(result.resolved)\n },\n\n resolveText(path: Path): string | undefined {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Text) {\n return resolved.toJSON()\n }\n const value = extractValue(resolved)\n return typeof value === \"string\" ? value : undefined\n },\n\n // Yjs does not support counters — schemas with counter types are\n // rejected at bind time. Return undefined to trigger zero fallback.\n resolveCounter(_path: Path): number | undefined {\n return undefined\n },\n\n resolveRichText(path: Path): RichTextDelta | undefined {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Text) {\n return yTextToRichTextDelta(resolved)\n }\n return undefined\n },\n\n resolveLength(path: Path): number {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Array) {\n return resolved.length\n }\n return Array.isArray(resolved) ? resolved.length : 0\n },\n\n resolveKeys(path: Path): string[] {\n const { resolved } = resolveYjsType(rootMap, rootSchema, path, binding)\n if (resolved instanceof Y.Map) {\n return Array.from(resolved.keys())\n }\n return isNonNullObject(resolved) ? Object.keys(resolved) : []\n },\n\n // Yjs has no tree primitive — schemas with `Schema.tree` are rejected\n // at bind time. Defensive [] for any caller that reaches here.\n resolveForest(_path: Path): readonly never[] {\n return []\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport function materializeYjsShadow(\n doc: Y.Doc,\n schema: SchemaNode,\n binding?: SchemaBinding,\n): PlainState {\n const rootMap = doc.getMap(\"root\")\n const resolver = createYjsResolver(rootMap, schema, binding)\n const interp = createMaterializeInterpreter(resolver)\n const ctx = materializeContextFromResolver(resolver)\n const result = interpret(schema, interp, ctx)\n return result as PlainState\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","// 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-eager local writes: `prepare` advances both the shadow σ\n// AND the native Y.Doc tree λ inside the ambient `Y.transact` opened\n// by `runBatch`. The projection law `σ ≡ Π(λ)` holds at every prepare\n// boundary — re-entrant subscribers reading either σ (via the Reader)\n// or λ (via `unwrap`) see a coherent state.\n// - `runBatch(body)` opens one `Y.transact(doc, body, options?.origin)` per\n// outermost logical action. Yjs's native transact nesting collapses\n// inner re-entrant transacts into the outer one for free — no depth\n// counter needed (unlike Loro). External `observeDeep` consumers see\n// exactly one batched event per outermost `batch(doc, fn)`.\n// - JSON-boundary writes (struct.json/list.json/record.json subtrees)\n// are buffered in a per-target-key coalescer and flushed in\n// `afterBatch`. Non-boundary writes are applied directly to λ via\n// `applyChangeToYjs`.\n// - `afterBatch` flushes the json-boundary coalescer on local writes\n// and re-materialises σ from λ on replay.\n// - Persistent observeDeep event bridge for external changes.\n// - Per-transaction meta mark (`KYNETA_MARK`) inscribed from inside the\n// transact body to ignore our own writes; survives Yjs's nested-transact\n// collapse so external wrapping is handled correctly.\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// `prepare` and `afterBatch` accept `BatchOptions` and branch on\n// `options?.replay`. The event bridge constructs the replay batch via\n// `executeBatch(ctx, ops, { origin, replay: true })`; substrate-side\n// work (transact, write) is skipped when `replay` is true because the\n// native Y.Doc already absorbed the change. This makes `prepare` and\n// `afterBatch` total functions of their declared inputs — no hidden\n// ambient state for the substrate-write decision. Context: jj:qpultxsw.\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 BatchOptions,\n ChangeBase,\n Path,\n PlainState,\n PositionCapable,\n ProductSchema,\n Reader,\n RecordInverseFn,\n Replica,\n ReplicaFactory,\n SchemaBinding,\n Schema as SchemaNode,\n Side,\n Substrate,\n SubstrateFactory,\n SubstratePayload,\n Version,\n WritableContext,\n} from \"@kyneta/schema\"\nimport {\n applyChange,\n BACKING_DOC,\n buildWritableContext,\n DEVTOOLS_HISTORY,\n type DevtoolsHistory,\n type DevtoolsHistorySummary,\n deepClonePreState,\n deriveSchemaBinding,\n executeBatch,\n findJsonBoundary,\n invert,\n KIND,\n plainReader,\n RECORD_INVERSE,\n syncShadow,\n} from \"@kyneta/schema\"\nimport * as Y from \"yjs\"\nimport { applyChangeToYjs, eventsToOps } from \"./change-mapping.js\"\nimport { materializeYjsShadow } from \"./materialize.js\"\nimport { ensureContainers } from \"./populate.js\"\nimport { toYjsAssoc, YjsPosition } from \"./position.js\"\nimport { YjsVersion } from \"./version.js\"\nimport { resolveYjsType } from \"./yjs-resolve.js\"\n\n// ---------------------------------------------------------------------------\n// Own-commit discriminator\n// ---------------------------------------------------------------------------\n\n// Own-commit discriminator. We mark `transaction.meta` from inside\n// the transact body — the mark travels with the Transaction object\n// that Yjs hands to observeDeep, regardless of `transaction.origin`.\n// This frees the user-facing `origin` slot for `options.origin`\n// round-trip and correctly handles the case where external code\n// wraps `batch(doc, fn)` in its own `Y.transact` (Yjs's nested-\n// transact collapse delivers the SAME Transaction object to both\n// outer and inner callbacks; verified by probe — see TECHNICAL.md\n// \"Why transaction.meta mark\").\nconst KYNETA_MARK = Symbol(\"kyneta:own-commit\")\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 // JSON-boundary coalescing buffer. Keyed by the target Y.Map and the\n // boundary key — repeated writes inside the same struct.json /\n // record.json subtree overwrite the entry with the latest σ value\n // before `afterBatch` flushes it back into λ as a single\n // `target.set(key, value)`. Non-boundary writes bypass the buffer\n // entirely — they go straight to `applyChangeToYjs` in prepare.\n const jsonBoundaryBuffer = new Map<\n string,\n {\n target: Y.Map<unknown> | Y.Array<unknown>\n key: string | number\n value: unknown\n }\n >()\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 shadow — a plain JS object materialized from the Y.Doc.\n // Kept in sync by applyChange() in prepare().\n const shadow: PlainState = materializeYjsShadow(doc, schema, binding)\n const reader: Reader = plainReader(shadow)\n\n // --- Coalescer helpers ---\n\n /**\n * Compute the identity-aware boundary key (or numeric index) for a\n * json-boundary write at `prefixLength`. Mirrors the Loro substrate's\n * `boundaryKey`; field segments inside a bound product get the\n * identity hash, others pass through raw.\n */\n function boundaryKey(path: Path, prefixLength: number): string | number {\n const seg = path.segments[prefixLength]!\n if (seg.role === \"field\" && binding) {\n const absPath = path.segments\n .slice(0, prefixLength + 1)\n .filter(s => s.role === \"field\")\n .map(s => s.resolve() as string)\n .join(\".\")\n const identity = binding.forward.get(absPath) as string | undefined\n if (identity) return identity\n }\n return seg.resolve() as string | number\n }\n\n /**\n * Buffer a json-boundary write. The boundary value is the entire σ\n * subtree at the boundary path — already updated by the preceding\n * `applyChange(shadow, ...)`. Subsequent writes inside the same\n * subtree overwrite this entry (last-write-wins by σ snapshot).\n *\n * Returns silently when the parent container can't be resolved\n * (root-level json fields land in `rootMap` directly — Yjs's\n * root is the rootMap, so the parentResolved is `rootMap`).\n */\n function stageJsonBoundaryWrite(path: Path, prefixLength: number): void {\n const parentPath = path.slice(0, prefixLength)\n const { resolved: parent } = resolveYjsType(\n rootMap,\n schema,\n parentPath,\n binding,\n )\n const boundaryPath = path.slice(0, prefixLength + 1)\n const value = boundaryPath.read(shadow)\n const key = boundaryKey(path, prefixLength)\n\n // The target can be either a Y.Map (struct field, record entry,\n // or rootMap) or a Y.Array (list/movable item). Both expose a\n // shape we can stash and flush in `afterBatch`.\n let target: Y.Map<unknown> | Y.Array<unknown>\n if (parent instanceof Y.Map) {\n target = parent\n } else if (parent instanceof Y.Array) {\n target = parent\n } else {\n throw new Error(\n `yjs substrate: json-boundary write to unsupported parent type at path ${path.format()}`,\n )\n }\n\n // Use the Yjs shared-type's stable identity for the buffer key\n // when available; fall back to a unique sentinel for the\n // ultra-rare case where `_item` is undefined (freshly-created\n // shared types before they're attached). Combine with key/index\n // for a unique slot — repeat writes to the same slot overwrite.\n const targetId = `${(target as any)._item?.id?.client ?? \"root\"}:${(target as any)._item?.id?.clock ?? \"root\"}`\n const slot = `${targetId}/${String(key)}`\n jsonBoundaryBuffer.set(slot, { target, key, value })\n }\n\n /**\n * Drain the json-boundary buffer into λ. Called from `afterBatch`\n * inside the ambient `Y.transact` opened by `runBatch`. Each entry\n * is applied as `target.set(key, value)` for Y.Map parents or as a\n * delete+insert for Y.Array parents (Yjs Arrays don't have a\n * `set(index, value)` primitive — replace = delete one + insert one).\n */\n function flushJsonBoundaryBuffer(): void {\n if (jsonBoundaryBuffer.size === 0) return\n for (const { target, key, value } of jsonBoundaryBuffer.values()) {\n if (target instanceof Y.Map) {\n target.set(String(key), value)\n } else {\n const index = key as number\n target.delete(index, 1)\n target.insert(index, [value])\n }\n }\n jsonBoundaryBuffer.clear()\n }\n\n // --- Substrate object ---\n\n const substrate = {\n [BACKING_DOC]: doc,\n [DEVTOOLS_HISTORY]: yjsDevtoolsHistory(() => doc),\n\n reader: reader,\n\n prepare(path: Path, change: ChangeBase, options?: BatchOptions): void {\n // Replay writes: λ has already absorbed these ops via\n // Y.applyUpdate at the event-bridge call site; skip σ/λ\n // advance — afterBatch(replay) rebuilds σ from λ in one\n // Π pass.\n if (options?.replay) return\n\n // Inverse recording under the normal handler. Capture σ at the\n // target path before applyChange mutates the shadow; the\n // recording closure pushes onto the active runBatch frame's\n // stack. Skipped under the undo-replay handler (compensating).\n const record = (\n options as\n | (BatchOptions & { [RECORD_INVERSE]?: RecordInverseFn })\n | undefined\n )?.[RECORD_INVERSE]\n if (record && !options?.compensating) {\n const pre = deepClonePreState(path.read(shadow))\n const inverse = invert(pre, change)\n record(path, inverse)\n }\n\n // Local write — σ advances eagerly. CRDT-side writes happen\n // inside the ambient Y.transact opened by runBatch (the\n // substrate's `runBatch` wraps `executeBatch`'s prepare-loop +\n // flush).\n applyChange(shadow, path, change)\n\n // JSON-boundary write: stage a full-value write at the\n // boundary segment of the parent container. Coalesces with\n // repeated writes inside the same subtree (last σ snapshot\n // wins) and lands in λ on `afterBatch` flush.\n const boundary = findJsonBoundary(schema, path, binding)\n if (boundary !== null) {\n stageJsonBoundaryWrite(path, boundary.prefixLength)\n return\n }\n\n // Non-boundary write: imperatively apply to λ inside the\n // ambient Y.transact. The KYNETA_ORIGIN tag lets the\n // observeDeep bridge below recognise and skip the events we\n // generate here, so the changefeed isn't fired twice.\n applyChangeToYjs(rootMap, schema, path, change, binding)\n },\n\n afterBatch(options?: BatchOptions): void {\n if (options?.replay) {\n // CRDT merge is a lattice join — re-materialise σ from λ in\n // one Π pass instead of replaying ops incrementally.\n syncShadow(shadow, materializeYjsShadow(doc, schema, binding))\n return\n }\n // Local write: drain the json-boundary coalescer. Runs inside\n // the ambient Y.transact from `runBatch`; the transact closes\n // when `runBatch`'s body returns, emitting one batched\n // observeDeep event for the whole logical action.\n flushJsonBoundaryBuffer()\n },\n\n runBatch(work: () => void, options?: BatchOptions): void {\n // Yjs's native transact nesting collapses inner re-entrant\n // transacts into the outermost — exactly the \"one batched\n // event per outermost logical action\" semantic we want. No\n // depth counter needed.\n //\n // We mark the transaction via `tr.meta.set` inside the transact body.\n // The mark lives on per-transaction meta, orthogonal to origin.\n // The app-level `options?.origin` flows directly to `transaction.origin`\n // and round-trips to the changefeed layer.\n doc.transact(tr => {\n tr.meta.set(KYNETA_MARK, true)\n work()\n }, options?.origin)\n },\n\n context(): WritableContext {\n if (!cachedCtx) {\n cachedCtx = buildWritableContext(substrate, {\n 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)\n .resolved\n },\n 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 }\n return cachedCtx\n },\n\n version(): YjsVersion {\n // Derive the deleteSet from the live struct store on every read.\n // Eager-prepare delivers notifications *inside* the ambient\n // `Y.transact` opened by `runBatch`, so `afterTransaction` fires\n // AFTER the changefeed's notify pipeline. Computing from the\n // store picks up in-progress deletes too, so the exchange's\n // auto-subscribe sees a version that already reflects the\n // just-applied mutation.\n return YjsVersion.fromDeleteSet(\n doc,\n Y.createDeleteSetFromStructStore(doc.store),\n )\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: Version): SubstratePayload | null {\n try {\n // ReplicaLike variance: signature uses Version, runtime type is always YjsVersion.\n const bytes = Y.encodeStateAsUpdate(doc, (since as YjsVersion).sv)\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, options?: BatchOptions): 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 = options?.origin\n try {\n Y.applyUpdate(doc, payload.data, options?.origin ?? \"remote\")\n } finally {\n pendingMergeOrigin = undefined\n }\n // That's it — the observeDeep handler bridges events to the\n // changefeed via executeBatch with `replay: true`.\n },\n }\n\n // --- Event bridge (registered once at construction) ---\n\n rootMap.observeDeep((events, transaction) => {\n // Own-commit discriminator: kyneta's runBatch marks the transaction\n // via `tr.meta.set` inside the transact body. The mark survives Yjs's\n // nested-transact collapse, so external code wrapping `batch()` in\n // its own Y.transact is correctly classified as own.\n if (transaction.meta.get(KYNETA_MARK)) {\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 // 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 // `replay: true` tells substrate.prepare/afterBatch to skip native-side\n // work (Yjs has already absorbed these ops via Y.applyUpdate) and\n // surfaces on the Changeset for downstream filters (exchange echo).\n executeBatch(ctx, ops, { origin, replay: true })\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 `batch()` 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 */\n// ---------------------------------------------------------------------------\n// DevTools history capability (pull) — version/op summary.\n// ---------------------------------------------------------------------------\n\n/**\n * Build the `DevtoolsHistory` capability over a Y.Doc accessor.\n *\n * `summary()` only: reliable Yjs time-travel (`valueAt`) requires the doc to\n * be constructed with `gc: false`, which this substrate does not impose (it\n * wraps a user-provided Y.Doc). So `valueAt` is intentionally omitted.\n * Context: jj:qpmkoryn.\n */\nfunction yjsDevtoolsHistory(getDoc: () => Y.Doc): DevtoolsHistory {\n return {\n summary(): DevtoolsHistorySummary {\n const sv = Y.encodeStateVector(getDoc())\n const actors: Record<string, number> = {}\n let opCount = 0\n for (const [client, clock] of Y.decodeStateVector(sv)) {\n actors[String(client)] = clock\n opCount += clock\n }\n return { version: new YjsVersion(sv).serialize(), opCount, actors }\n },\n }\n}\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 [DEVTOOLS_HISTORY]: yjsDevtoolsHistory(() => currentDoc),\n\n version(): YjsVersion {\n return YjsVersion.fromDoc(currentDoc)\n },\n\n baseVersion(): YjsVersion {\n return currentBase\n },\n\n advance(to: Version): 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: Version): SubstratePayload | null {\n try {\n // The ReplicaLike contract uses the base `Version` type for variance\n // safety. At runtime the synchronizer always passes a YjsVersion from\n // this replica's own factory — the cast is sound.\n const bytes = Y.encodeStateAsUpdate(\n currentDoc,\n (since as YjsVersion).sv,\n )\n return { kind: \"since\", encoding: \"binary\", data: bytes }\n } catch {\n return null\n }\n },\n\n merge(payload: SubstratePayload, _options?: BatchOptions): 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 ensureContainers(doc, schema, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n const doc = new Y.Doc()\n const binding = trivialBinding(schema)\n ensureContainers(doc, schema, 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 ensureContainers(doc, schema, binding)\n return createYjsSubstrate(doc, schema, binding)\n },\n\n create(schema: SchemaNode): Substrate<YjsVersion> {\n // Fresh doc — set identity immediately.\n const doc = new Y.Doc()\n doc.clientID = numericClientId\n ensureContainers(doc, schema, 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 syncMode: 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": "
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"yjs": ">=13.6.0",
|
|
32
|
-
"@kyneta/changefeed": "^
|
|
33
|
-
"@kyneta/schema": "^
|
|
32
|
+
"@kyneta/changefeed": "^2.0.0",
|
|
33
|
+
"@kyneta/schema": "^2.0.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"tsdown": "^0.22.0",
|
|
37
37
|
"typescript": "^5.9.2",
|
|
38
38
|
"vitest": "^4.0.17",
|
|
39
39
|
"yjs": "^13.6.30",
|
|
40
|
-
"@kyneta/
|
|
41
|
-
"@kyneta/
|
|
40
|
+
"@kyneta/changefeed": "^2.0.0",
|
|
41
|
+
"@kyneta/schema": "^2.0.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsdown",
|