@kyneta/yjs-schema 1.7.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 CHANGED
@@ -42,7 +42,7 @@ doc.title() // "My Todos"
42
42
  doc.items.length // 1
43
43
 
44
44
  // Write
45
- change(doc, (d) => {
45
+ batch(doc, (d) => {
46
46
  d.title.insert(9, " (v2)")
47
47
  d.items.push({ name: "Walk dog", done: false })
48
48
  })
@@ -95,7 +95,7 @@ const docB = createDoc(MyDoc, snapshot)
95
95
 
96
96
  // After mutations on A, sync incrementally
97
97
  const vBefore = version(docB)
98
- change(docA, (d) => d.title.insert(5, " v2"))
98
+ batch(docA, (d) => d.title.insert(5, " v2"))
99
99
 
100
100
  const delta = exportSince(docA, vBefore)
101
101
  importDelta(docB, delta!)
@@ -120,7 +120,7 @@ const TodoDoc = yjs.bind(Schema.struct({
120
120
  const doc = exchange.get("my-todos", TodoDoc)
121
121
  ```
122
122
 
123
- `yjs.bind()` produces a `BoundSchema` with the collaborative `SyncProtocol`, which the exchange uses for bidirectional CRDT sync.
123
+ `yjs.bind()` produces a `BoundSchema` with the collaborative `SyncMode`, which the exchange uses for bidirectional CRDT sync.
124
124
 
125
125
  ## Escape Hatch
126
126
 
@@ -139,6 +139,8 @@ yjsDoc.getMap("root").toJSON() // raw state
139
139
  yjsDoc.clientID // client ID
140
140
  ```
141
141
 
142
+ *Note for raw Y.Doc consumers:* Subscribers attached directly to the underlying `Y.Doc` will newly see `options.origin` faithfully on `transaction.origin` (where previously it was silently dropped).
143
+
142
144
  ## Yjs Ecosystem Compatibility
143
145
 
144
146
  Because `yjs(doc)` returns a standard `Y.Doc`, the entire Yjs provider ecosystem works out of the box:
@@ -161,7 +163,7 @@ Because `yjs(doc)` returns a standard `Y.Doc`, the entire Yjs provider ecosystem
161
163
  | `exportEntirety(doc)` | Full state as `SubstratePayload` |
162
164
  | `exportSince(doc, since)` | Delta since version |
163
165
  | `merge(doc, payload, origin?)` | Apply delta from peer |
164
- | `change(doc, fn)` | Transactional mutation |
166
+ | `batch(doc, fn)` | Transactional mutation |
165
167
  | `subscribe(doc, callback)` | Observe changes |
166
168
  | `yjs.bind(schema)` | Bind schema for exchange use |
167
169
  | `yjs(ref)` | Escape hatch → `Y.Doc` |
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BindingTarget, ChangeBase, DocRef, Instruction, NATIVE, NativeMap, Op, Op as Op$1, Path, PathFoldResult, PathStepper, Position, Ref, ReplicaFactory, Schema, Schema as Schema$1, SchemaBinding, Side, Substrate, SubstrateFactory, SubstratePayload, Version, applyChanges, change, createDoc, createRef, exportEntirety, exportSince, merge, subscribe, subscribeNode, unwrap, version } from "@kyneta/schema";
1
+ import { BindingTarget, ChangeBase, DocRef, Instruction, NATIVE, NativeMap, Op, Op as Op$1, Path, PathFoldResult, PathStepper, Position, Ref, ReplicaFactory, Schema, Schema as Schema$1, SchemaBinding, Side, Substrate, SubstrateFactory, SubstratePayload, Version, applyChanges, batch, createDoc, createRef, exportEntirety, exportSince, merge, subscribe, subscribeNode, unwrap, version } from "@kyneta/schema";
2
2
  import * as Y from "yjs";
3
3
  import { Doc } from "yjs";
4
4
  import { Changeset } from "@kyneta/changefeed";
@@ -276,5 +276,5 @@ declare const stepIntoYjs: PathStepper;
276
276
  */
277
277
  declare function resolveYjsType(rootMap: Y.Map<any>, rootSchema: Schema$1, path: Path, binding?: SchemaBinding): PathFoldResult;
278
278
  //#endregion
279
- export { type Changeset, type DocRef, NATIVE, type Op, type Ref, Schema, type SubstratePayload, type YjsLaws, type YjsNativeMap, YjsPosition, YjsVersion, applyChangeToYjs, applyChanges, change, createDoc, createRef, createYjsSubstrate, ensureContainers, eventsToOps, exportEntirety, exportSince, fromYjsAssoc, merge, resolveYjsType, stepIntoYjs, subscribe, subscribeNode, toYjsAssoc, unwrap, version, yjs, yjsReplicaFactory, yjsSubstrateFactory };
279
+ export { type Changeset, type DocRef, NATIVE, type Op, type Ref, Schema, type SubstratePayload, type YjsLaws, type YjsNativeMap, YjsPosition, YjsVersion, applyChangeToYjs, applyChanges, batch, createDoc, createRef, createYjsSubstrate, ensureContainers, eventsToOps, exportEntirety, exportSince, fromYjsAssoc, merge, resolveYjsType, stepIntoYjs, subscribe, subscribeNode, toYjsAssoc, unwrap, version, yjs, yjsReplicaFactory, yjsSubstrateFactory };
280
280
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/native-map.ts","../src/bind-yjs.ts","../src/change-mapping.ts","../src/populate.ts","../src/position.ts","../src/version.ts","../src/substrate.ts","../src/yjs-resolve.ts"],"mappings":";;;;;;;;;;AAwBA;;;;;;;;;;;;UAAiB,YAAA,SAAqB,SAAA;EAAA,SAC3B,IAAA,EAAM,CAAA,CAAE,GAAA;EAAA,SACR,IAAA,EAAM,CAAA,CAAE,IAAA;EAAA,SACR,OAAA;EAAA,SACA,IAAA,EAAM,CAAA,CAAE,KAAA;EAAA,SACR,WAAA;EAAA,SACA,MAAA,EAAQ,CAAA,CAAE,GAAA;EAAA,SACV,GAAA,EAAK,CAAA,CAAE,GAAA;EAAA,SACP,IAAA;EAAA,SACA,GAAA;EAAA,SACA,MAAA;EAAA,SACA,GAAA;AAAA;;;;;;;KC8GC,OAAA;;;;;;;;;;;;;;cAmBC,GAAA,EAAK,aAAA,CAAc,OAAA,EAAS,YAAA;;;;;;;AD5IzC;;;;;;;;iBEqCgB,gBAAA,CACd,OAAA,EAAS,CAAA,CAAE,GAAA,OACX,UAAA,EAAY,QAAA,EACZ,IAAA,EAAM,IAAA,EACN,MAAA,EAAQ,UAAA,EACR,OAAA,GAAU,aAAA;;;;;;;;;;;;;;iBAscI,WAAA,CACd,MAAA,EAAQ,CAAA,CAAE,MAAA,SACV,MAAA,EAAQ,QAAA,EACR,OAAA,GAAU,aAAA,GACT,IAAA;;;;;;;AFpfH;;;;;;;;;;;;;;;;;;;;;;;iBG6BgB,gBAAA,CACd,GAAA,EAAK,CAAA,CAAE,GAAA,EACP,MAAA,EAAQ,QAAA,EACR,OAAA,GAAU,aAAA;;;;iBC9CI,UAAA,CAAW,IAAU,EAAJ,IAAI;;iBAKrB,YAAA,CAAa,KAAA,WAAgB,IAAI;AAAA,cAIpC,WAAA,YAAuB,QAAA;EAAA,iBAIf,IAAA;EAAA,iBACA,GAAA;EAAA,SAJV,IAAA,EAAM,IAAA;cAGI,IAAA,EAAM,CAAA,CAAE,gBAAA,EACR,GAAA,EAAK,CAAA,CAAE,GAAA;EAK1B,OAAA,CAAA;EAQA,MAAA,CAAA,GAAU,UAAA;EAIV,SAAA,CAAU,aAAA,WAAwB,WAAA;AAAA;;;;;;;AJjBpC;;;;;;;;;;;;;;;cK8Ea,UAAA,YAAsB,OAAA;EL5ElB;EAAA,SK8EN,EAAA,EAAI,UAAA;EL7EJ;;;;;EAAA,SKoFA,aAAA,EAAe,UAAA;cAEZ,EAAA,EAAI,UAAA,EAAY,aAAA,GAAgB,UAAA;ELnFzB;;;;;;EAAA,OK+FZ,OAAA,CAAQ,GAAA,EAAK,GAAA,GAAM,UAAA;EL1FjB;;AAAG;;;;AC8Gd;;;ED9GW,OKyGF,aAAA,CACL,GAAA,EAAK,GAAA,EACL,EAAA,EAAI,UAAA,QADI,CAAA,CAC4B,eAAA,IACnC,UAAA;EJEc;AAmBnB;;;;;EIRE,SAAA,CAAA;EJQ6B;;;;;;AAAsB;;;;ACvGrD;;;;EGmHE,OAAA,CAAQ,KAAA,EAAO,OAAA;EHhHT;;;;;;;;;;;;;EG4IN,IAAA,CAAK,KAAA,EAAO,OAAA,GAAU,UAAA;EH1IZ;;;AAAa;AAsczB;;;;;EAtcY,OG+JH,KAAA,CAAM,UAAA,WAAqB,UAAA;AAAA;;;;;;ALzMpC;;;;;;;;;;;;;;;;iBMiEgB,kBAAA,CACd,GAAA,EAAK,CAAA,CAAE,GAAA,EACP,MAAA,EAAQ,QAAA,EACR,OAAA,GAAU,aAAA,GACT,SAAA,CAAU,UAAA;AAAA,cAkWA,iBAAA,EAAmB,cAAc,CAAC,UAAA;AAAA,cA8BlC,mBAAA,EAAqB,gBAAgB,CAAC,UAAA;;;;;;;ANrcnD;;;;;;;;;cOiBa,WAAA,EAAa,WAsBzB;;;;;;;;;;;;iBAiBe,cAAA,CACd,OAAA,EAAS,CAAA,CAAE,GAAA,OACX,UAAA,EAAY,QAAA,EACZ,IAAA,EAAM,IAAA,EACN,OAAA,GAAU,aAAA,GACT,cAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/native-map.ts","../src/bind-yjs.ts","../src/change-mapping.ts","../src/populate.ts","../src/position.ts","../src/version.ts","../src/substrate.ts","../src/yjs-resolve.ts"],"mappings":";;;;;;;;;;AAwBA;;;;;;;;;;;;UAAiB,YAAA,SAAqB,SAAA;EAAA,SAC3B,IAAA,EAAM,CAAA,CAAE,GAAA;EAAA,SACR,IAAA,EAAM,CAAA,CAAE,IAAA;EAAA,SACR,OAAA;EAAA,SACA,IAAA,EAAM,CAAA,CAAE,KAAA;EAAA,SACR,WAAA;EAAA,SACA,MAAA,EAAQ,CAAA,CAAE,GAAA;EAAA,SACV,GAAA,EAAK,CAAA,CAAE,GAAA;EAAA,SACP,IAAA;EAAA,SACA,GAAA;EAAA,SACA,MAAA;EAAA,SACA,GAAA;AAAA;;;;;;;KC8GC,OAAA;;;;;;;;;;;;;;cAmBC,GAAA,EAAK,aAAA,CAAc,OAAA,EAAS,YAAA;;;;;;;AD5IzC;;;;;;;;iBEuCgB,gBAAA,CACd,OAAA,EAAS,CAAA,CAAE,GAAA,OACX,UAAA,EAAY,QAAA,EACZ,IAAA,EAAM,IAAA,EACN,MAAA,EAAQ,UAAA,EACR,OAAA,GAAU,aAAA;;;;;;;;;;;;;;iBAkcI,WAAA,CACd,MAAA,EAAQ,CAAA,CAAE,MAAA,SACV,MAAA,EAAQ,QAAA,EACR,OAAA,GAAU,aAAA,GACT,IAAA;;;;;;;AFlfH;;;;;;;;;;;;;;;;;;;;;;;iBG6BgB,gBAAA,CACd,GAAA,EAAK,CAAA,CAAE,GAAA,EACP,MAAA,EAAQ,QAAA,EACR,OAAA,GAAU,aAAA;;;;iBC9CI,UAAA,CAAW,IAAU,EAAJ,IAAI;;iBAKrB,YAAA,CAAa,KAAA,WAAgB,IAAI;AAAA,cAIpC,WAAA,YAAuB,QAAA;EAAA,iBAIf,IAAA;EAAA,iBACA,GAAA;EAAA,SAJV,IAAA,EAAM,IAAA;cAGI,IAAA,EAAM,CAAA,CAAE,gBAAA,EACR,GAAA,EAAK,CAAA,CAAE,GAAA;EAK1B,OAAA,CAAA;EAQA,MAAA,CAAA,GAAU,UAAA;EAIV,SAAA,CAAU,aAAA,WAAwB,WAAA;AAAA;;;;;;;AJjBpC;;;;;;;;;;;;;;;cK8Ea,UAAA,YAAsB,OAAA;EL5ElB;EAAA,SK8EN,EAAA,EAAI,UAAA;EL7EJ;;;;;EAAA,SKoFA,aAAA,EAAe,UAAA;cAEZ,EAAA,EAAI,UAAA,EAAY,aAAA,GAAgB,UAAA;ELnFzB;;;;;;EAAA,OK+FZ,OAAA,CAAQ,GAAA,EAAK,GAAA,GAAM,UAAA;EL1FjB;;AAAG;;;;AC8Gd;;;ED9GW,OKyGF,aAAA,CACL,GAAA,EAAK,GAAA,EACL,EAAA,EAAI,UAAA,QADI,CAAA,CAC4B,eAAA,IACnC,UAAA;EJEc;AAmBnB;;;;;EIRE,SAAA,CAAA;EJQ6B;;;;;;AAAsB;;;;ACrGrD;;;;EGiHE,OAAA,CAAQ,KAAA,EAAO,OAAA;EH9GT;;;;;;;;;;;;;EG0IN,IAAA,CAAK,KAAA,EAAO,OAAA,GAAU,UAAA;EHxIZ;;;AAAa;AAkczB;;;;;EAlcY,OG6JH,KAAA,CAAM,UAAA,WAAqB,UAAA;AAAA;;;;;;ALzMpC;;;;;;;;;;;;;;;;iBMoGgB,kBAAA,CACd,GAAA,EAAK,CAAA,CAAE,GAAA,EACP,MAAA,EAAQ,QAAA,EACR,OAAA,GAAU,aAAA,GACT,SAAA,CAAU,UAAA;AAAA,cAwfA,iBAAA,EAAmB,cAAc,CAAC,UAAA;AAAA,cA8BlC,mBAAA,EAAqB,gBAAgB,CAAC,UAAA;;;;;;;AN9nBnD;;;;;;;;;cOiBa,WAAA,EAAa,WAsBzB;;;;;;;;;;;;iBAiBe,cAAA,CACd,OAAA,EAAS,CAAA,CAAE,GAAA,OACX,UAAA,EAAY,QAAA,EACZ,IAAA,EAAM,IAAA,EACN,OAAA,GAAU,aAAA,GACT,cAAA"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BACKING_DOC, KIND, NATIVE, RawPath, STRUCTURAL_YJS_CLIENT_ID, SYNC_COLLABORATIVE, Schema, applyChange, applyChanges, base64ToUint8Array, buildWritableContext, change, createBindingTarget, createDoc, createMaterializeInterpreter, createRef, deriveSchemaBinding, executeBatch, expandMapOpsToLeaves, exportEntirety, exportSince, foldPath, interpret, isNonNullObject, materializeContextFromResolver, merge, pathSchema, plainReader, richTextChange, subscribe, subscribeNode, uint8ArrayToBase64, unwrap, version, versionVectorCompare, versionVectorMeet } from "@kyneta/schema";
1
+ import { BACKING_DOC, DEVTOOLS_HISTORY, KIND, NATIVE, RECORD_INVERSE, RawPath, STRUCTURAL_YJS_CLIENT_ID, SYNC_COLLABORATIVE, Schema, applyChange, applyChanges, base64ToUint8Array, batch, buildWritableContext, createBindingTarget, createDoc, createMaterializeInterpreter, createRef, deepClonePreState, deriveSchemaBinding, executeBatch, expandMapOpsToLeaves, exportEntirety, exportSince, findJsonBoundary, foldPath, interpret, invert, isJsonBoundary, isNonNullObject, isPlainObject, materializeContextFromResolver, merge, pathSchema, plainReader, richTextChange, subscribe, subscribeNode, syncShadow, uint8ArrayToBase64, unwrap, version, versionVectorCompare, versionVectorMeet } from "@kyneta/schema";
2
2
  import * as Y from "yjs";
3
3
  import { createSnapshot, decodeStateVector, encodeSnapshot, encodeStateVector, snapshot } from "yjs";
4
4
  //#region src/populate.ts
@@ -61,6 +61,7 @@ function ensureContainers(doc, schema, binding) {
61
61
  */
62
62
  function ensureRootField(rootMap, key, fieldSchema, binding, prefix) {
63
63
  if (rootMap.has(key)) return;
64
+ if (isJsonBoundary(fieldSchema)) return;
64
65
  switch (fieldSchema[KIND]) {
65
66
  case "text":
66
67
  case "richtext":
@@ -106,6 +107,7 @@ function ensureMapContainers(schema, binding, prefix) {
106
107
  for (const [key, fieldSchema] of Object.entries(schema.fields).sort(([a], [b]) => a.localeCompare(b))) {
107
108
  const absPath = prefix ? `${prefix}.${key}` : key;
108
109
  const mapKey = binding?.forward.get(absPath) ?? key;
110
+ if (isJsonBoundary(fieldSchema)) continue;
109
111
  switch (fieldSchema[KIND]) {
110
112
  case "text":
111
113
  case "richtext":
@@ -258,7 +260,7 @@ function applyMapChange(rootMap, rootSchema, path, change, binding) {
258
260
  }
259
261
  }
260
262
  function applyReplaceChange(rootMap, rootSchema, path, change, binding) {
261
- if (path.length === 0) throw new Error("applyChangeToYjs: ReplaceChange at root path is not supported");
263
+ if (path.length === 0) throw new Error("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 })`).");
262
264
  const lastSeg = path.segments.at(-1);
263
265
  if (!lastSeg) throw new Error("replaceChangeToDiff: empty path");
264
266
  const parentPath = path.slice(0, -1);
@@ -291,6 +293,7 @@ function applyReplaceChange(rootMap, rootSchema, path, change, binding) {
291
293
  */
292
294
  function maybeCreateSharedType(value, schema) {
293
295
  if (schema === void 0) return value;
296
+ if (isJsonBoundary(schema)) return value;
294
297
  switch (schema[KIND]) {
295
298
  case "text": {
296
299
  const text = new Y.Text();
@@ -311,7 +314,7 @@ function maybeCreateSharedType(value, schema) {
311
314
  return text;
312
315
  }
313
316
  case "product":
314
- if (value === null || value === void 0 || typeof value !== "object" || Array.isArray(value)) return value;
317
+ if (!isPlainObject(value)) return value;
315
318
  return createStructuredMap(value, schema);
316
319
  case "sequence": {
317
320
  if (!Array.isArray(value)) return value;
@@ -322,7 +325,7 @@ function maybeCreateSharedType(value, schema) {
322
325
  return arr;
323
326
  }
324
327
  case "map": {
325
- if (value === null || value === void 0 || typeof value !== "object" || Array.isArray(value)) return value;
328
+ if (!isPlainObject(value)) return value;
326
329
  const map = new Y.Map();
327
330
  const valueSchema = schema.item;
328
331
  for (const [k, v] of Object.entries(value)) map.set(k, maybeCreateSharedType(v, valueSchema));
@@ -820,7 +823,7 @@ var YjsVersion = class YjsVersion {
820
823
  };
821
824
  //#endregion
822
825
  //#region src/substrate.ts
823
- const KYNETA_ORIGIN = "kyneta-prepare";
826
+ const KYNETA_MARK = Symbol("kyneta:own-commit");
824
827
  /**
825
828
  * Creates a `Substrate<YjsVersion>` wrapping a user-provided Y.Doc.
826
829
  *
@@ -841,46 +844,106 @@ const KYNETA_ORIGIN = "kyneta-prepare";
841
844
  * @param binding - Optional SchemaBinding for identity-keyed containers.
842
845
  */
843
846
  function createYjsSubstrate(doc, schema, binding) {
844
- const pendingChanges = [];
847
+ const jsonBoundaryBuffer = /* @__PURE__ */ new Map();
845
848
  let pendingMergeOrigin;
846
849
  let cachedCtx;
847
- let accumulatedDs = Y.createDeleteSetFromStructStore(doc.store);
848
850
  const rootMap = doc.getMap("root");
849
851
  const shadow = materializeYjsShadow(doc, schema, binding);
850
852
  const reader = plainReader(shadow);
853
+ /**
854
+ * Compute the identity-aware boundary key (or numeric index) for a
855
+ * json-boundary write at `prefixLength`. Mirrors the Loro substrate's
856
+ * `boundaryKey`; field segments inside a bound product get the
857
+ * identity hash, others pass through raw.
858
+ */
859
+ function boundaryKey(path, prefixLength) {
860
+ const seg = path.segments[prefixLength];
861
+ if (seg.role === "field" && binding) {
862
+ const absPath = path.segments.slice(0, prefixLength + 1).filter((s) => s.role === "field").map((s) => s.resolve()).join(".");
863
+ const identity = binding.forward.get(absPath);
864
+ if (identity) return identity;
865
+ }
866
+ return seg.resolve();
867
+ }
868
+ /**
869
+ * Buffer a json-boundary write. The boundary value is the entire σ
870
+ * subtree at the boundary path — already updated by the preceding
871
+ * `applyChange(shadow, ...)`. Subsequent writes inside the same
872
+ * subtree overwrite this entry (last-write-wins by σ snapshot).
873
+ *
874
+ * Returns silently when the parent container can't be resolved
875
+ * (root-level json fields land in `rootMap` directly — Yjs's
876
+ * root is the rootMap, so the parentResolved is `rootMap`).
877
+ */
878
+ function stageJsonBoundaryWrite(path, prefixLength) {
879
+ const { resolved: parent } = resolveYjsType(rootMap, schema, path.slice(0, prefixLength), binding);
880
+ const value = path.slice(0, prefixLength + 1).read(shadow);
881
+ const key = boundaryKey(path, prefixLength);
882
+ let target;
883
+ if (parent instanceof Y.Map) target = parent;
884
+ else if (parent instanceof Y.Array) target = parent;
885
+ else throw new Error(`yjs substrate: json-boundary write to unsupported parent type at path ${path.format()}`);
886
+ const slot = `${`${target._item?.id?.client ?? "root"}:${target._item?.id?.clock ?? "root"}`}/${String(key)}`;
887
+ jsonBoundaryBuffer.set(slot, {
888
+ target,
889
+ key,
890
+ value
891
+ });
892
+ }
893
+ /**
894
+ * Drain the json-boundary buffer into λ. Called from `afterBatch`
895
+ * inside the ambient `Y.transact` opened by `runBatch`. Each entry
896
+ * is applied as `target.set(key, value)` for Y.Map parents or as a
897
+ * delete+insert for Y.Array parents (Yjs Arrays don't have a
898
+ * `set(index, value)` primitive — replace = delete one + insert one).
899
+ */
900
+ function flushJsonBoundaryBuffer() {
901
+ if (jsonBoundaryBuffer.size === 0) return;
902
+ for (const { target, key, value } of jsonBoundaryBuffer.values()) if (target instanceof Y.Map) target.set(String(key), value);
903
+ else {
904
+ const index = key;
905
+ target.delete(index, 1);
906
+ target.insert(index, [value]);
907
+ }
908
+ jsonBoundaryBuffer.clear();
909
+ }
851
910
  const substrate = {
852
911
  [BACKING_DOC]: doc,
912
+ [DEVTOOLS_HISTORY]: yjsDevtoolsHistory(() => doc),
853
913
  reader,
854
914
  prepare(path, change, options) {
855
- if (!options?.replay) applyChange(shadow, path, change);
856
915
  if (options?.replay) return;
857
- pendingChanges.push({
858
- path,
859
- change
860
- });
916
+ const record = options?.[RECORD_INVERSE];
917
+ if (record && !options?.compensating) record(path, invert(deepClonePreState(path.read(shadow)), change));
918
+ applyChange(shadow, path, change);
919
+ const boundary = findJsonBoundary(schema, path, binding);
920
+ if (boundary !== null) {
921
+ stageJsonBoundaryWrite(path, boundary.prefixLength);
922
+ return;
923
+ }
924
+ applyChangeToYjs(rootMap, schema, path, change, binding);
861
925
  },
862
- onFlush(options) {
926
+ afterBatch(options) {
863
927
  if (options?.replay) {
864
- const fresh = materializeYjsShadow(doc, schema, binding);
865
- for (const key of Object.keys(fresh)) shadow[key] = fresh[key];
866
- for (const key of Object.keys(shadow)) if (!(key in fresh)) delete shadow[key];
928
+ syncShadow(shadow, materializeYjsShadow(doc, schema, binding));
867
929
  return;
868
930
  }
869
- if (pendingChanges.length === 0) return;
870
- doc.transact(() => {
871
- for (const { path, change } of pendingChanges) applyChangeToYjs(rootMap, schema, path, change, binding);
872
- }, KYNETA_ORIGIN);
873
- pendingChanges.length = 0;
931
+ flushJsonBoundaryBuffer();
932
+ },
933
+ runBatch(work, options) {
934
+ doc.transact((tr) => {
935
+ tr.meta.set(KYNETA_MARK, true);
936
+ work();
937
+ }, options?.origin);
874
938
  },
875
939
  context() {
876
- if (!cachedCtx) {
877
- cachedCtx = buildWritableContext(substrate);
878
- cachedCtx.nativeResolver = (nodeSchema, path) => {
940
+ if (!cachedCtx) cachedCtx = buildWritableContext(substrate, {
941
+ nativeResolver: (nodeSchema, path) => {
879
942
  if (path.segments.length === 0) return doc;
880
943
  if (nodeSchema[KIND] === "scalar" || nodeSchema[KIND] === "sum") return void 0;
881
944
  return resolveYjsType(rootMap, schema, path, binding).resolved;
882
- };
883
- cachedCtx.positionResolver = (_nodeSchema, path) => {
945
+ },
946
+ positionResolver: (_nodeSchema, path) => {
884
947
  return {
885
948
  createPosition(index, side) {
886
949
  const { resolved: ytype } = resolveYjsType(rootMap, schema, path, binding);
@@ -892,12 +955,12 @@ function createYjsSubstrate(doc, schema, binding) {
892
955
  return new YjsPosition(Y.decodeRelativePosition(bytes), doc);
893
956
  }
894
957
  };
895
- };
896
- }
958
+ }
959
+ });
897
960
  return cachedCtx;
898
961
  },
899
962
  version() {
900
- return YjsVersion.fromDeleteSet(doc, accumulatedDs);
963
+ return YjsVersion.fromDeleteSet(doc, Y.createDeleteSetFromStructStore(doc.store));
901
964
  },
902
965
  baseVersion() {
903
966
  return new YjsVersion(new Uint8Array([0]));
@@ -934,19 +997,15 @@ function createYjsSubstrate(doc, schema, binding) {
934
997
  }
935
998
  };
936
999
  rootMap.observeDeep((events, transaction) => {
937
- if (transaction.origin === KYNETA_ORIGIN) return;
1000
+ if (transaction.meta.get(KYNETA_MARK)) return;
938
1001
  const ops = eventsToOps(events, schema, binding);
939
1002
  if (ops.length === 0) return;
940
- if (transaction.deleteSet.clients.size > 0) accumulatedDs = Y.mergeDeleteSets([accumulatedDs, transaction.deleteSet]);
941
1003
  const origin = pendingMergeOrigin ?? (typeof transaction.origin === "string" ? transaction.origin : void 0);
942
1004
  executeBatch(substrate.context(), ops, {
943
1005
  origin,
944
1006
  replay: true
945
1007
  });
946
1008
  });
947
- doc.on("afterTransaction", (transaction) => {
948
- if (transaction.origin === KYNETA_ORIGIN && transaction.deleteSet.clients.size > 0) accumulatedDs = Y.mergeDeleteSets([accumulatedDs, transaction.deleteSet]);
949
- });
950
1009
  return substrate;
951
1010
  }
952
1011
  /**
@@ -954,7 +1013,7 @@ function createYjsSubstrate(doc, schema, binding) {
954
1013
  *
955
1014
  * - `create(schema)` — creates a fresh Y.Doc with empty containers
956
1015
  * matching the schema structure. No seed data — initial content
957
- * should be applied via `change()` after construction.
1016
+ * should be applied via `batch()` after construction.
958
1017
  * - `fromEntirety(payload, schema)` — creates a Y.Doc from an entirety
959
1018
  * payload, returns a substrate.
960
1019
  * - `parseVersion(serialized)` — deserializes a YjsVersion.
@@ -985,6 +1044,30 @@ function trivialBinding(schema) {
985
1044
  * that need to accumulate state, compute per-peer deltas, and compact
986
1045
  * storage without ever interpreting document fields.
987
1046
  */
1047
+ /**
1048
+ * Build the `DevtoolsHistory` capability over a Y.Doc accessor.
1049
+ *
1050
+ * `summary()` only: reliable Yjs time-travel (`valueAt`) requires the doc to
1051
+ * be constructed with `gc: false`, which this substrate does not impose (it
1052
+ * wraps a user-provided Y.Doc). So `valueAt` is intentionally omitted.
1053
+ * Context: jj:qpmkoryn.
1054
+ */
1055
+ function yjsDevtoolsHistory(getDoc) {
1056
+ return { summary() {
1057
+ const sv = Y.encodeStateVector(getDoc());
1058
+ const actors = {};
1059
+ let opCount = 0;
1060
+ for (const [client, clock] of Y.decodeStateVector(sv)) {
1061
+ actors[String(client)] = clock;
1062
+ opCount += clock;
1063
+ }
1064
+ return {
1065
+ version: new YjsVersion(sv).serialize(),
1066
+ opCount,
1067
+ actors
1068
+ };
1069
+ } };
1070
+ }
988
1071
  function createYjsReplica(doc) {
989
1072
  let currentDoc = doc;
990
1073
  let currentBase = new YjsVersion(Y.encodeStateVector(new Y.Doc()));
@@ -992,6 +1075,7 @@ function createYjsReplica(doc) {
992
1075
  get [BACKING_DOC]() {
993
1076
  return currentDoc;
994
1077
  },
1078
+ [DEVTOOLS_HISTORY]: yjsDevtoolsHistory(() => currentDoc),
995
1079
  version() {
996
1080
  return YjsVersion.fromDoc(currentDoc);
997
1081
  },
@@ -1148,9 +1232,9 @@ function createYjsFactory(peerId, binding) {
1148
1232
  const yjs = createBindingTarget({
1149
1233
  factory: (ctx) => createYjsFactory(ctx.peerId, ctx.binding),
1150
1234
  replicaFactory: yjsReplicaFactory,
1151
- syncProtocol: SYNC_COLLABORATIVE
1235
+ syncMode: SYNC_COLLABORATIVE
1152
1236
  });
1153
1237
  //#endregion
1154
- export { NATIVE, Schema, YjsPosition, YjsVersion, applyChangeToYjs, applyChanges, change, createDoc, createRef, createYjsSubstrate, ensureContainers, eventsToOps, exportEntirety, exportSince, fromYjsAssoc, merge, resolveYjsType, stepIntoYjs, subscribe, subscribeNode, toYjsAssoc, unwrap, version, yjs, yjsReplicaFactory, yjsSubstrateFactory };
1238
+ export { NATIVE, Schema, YjsPosition, YjsVersion, applyChangeToYjs, applyChanges, batch, createDoc, createRef, createYjsSubstrate, ensureContainers, eventsToOps, exportEntirety, exportSince, fromYjsAssoc, merge, resolveYjsType, stepIntoYjs, subscribe, subscribeNode, toYjsAssoc, unwrap, version, yjs, yjsReplicaFactory, yjsSubstrateFactory };
1155
1239
 
1156
1240
  //# sourceMappingURL=index.js.map