@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 +6 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -37
- 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 +60 -49
- package/src/__tests__/eager-write-coherence.test.ts +321 -0
- 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 +106 -51
- package/src/bind-yjs.ts +1 -1
- package/src/change-mapping.ts +11 -13
- package/src/index.ts +1 -1
- package/src/populate.ts +13 -1
- package/src/substrate.ts +298 -113
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ doc.title() // "My Todos"
|
|
|
42
42
|
doc.items.length // 1
|
|
43
43
|
|
|
44
44
|
// Write
|
|
45
|
-
|
|
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
|
-
|
|
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 `
|
|
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
|
-
| `
|
|
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,
|
|
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,
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;;;;;;;;
|
|
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,
|
|
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("
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
|
|
926
|
+
afterBatch(options) {
|
|
863
927
|
if (options?.replay) {
|
|
864
|
-
|
|
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
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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 `
|
|
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
|
-
|
|
1235
|
+
syncMode: SYNC_COLLABORATIVE
|
|
1152
1236
|
});
|
|
1153
1237
|
//#endregion
|
|
1154
|
-
export { NATIVE, Schema, YjsPosition, YjsVersion, applyChangeToYjs, applyChanges,
|
|
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
|