@kyneta/yjs-schema 1.1.0 → 1.3.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/dist/index.js CHANGED
@@ -2,40 +2,150 @@
2
2
  import {
3
3
  applyChanges,
4
4
  change,
5
+ createDoc,
6
+ createRef,
7
+ exportEntirety,
8
+ exportSince,
9
+ merge,
10
+ NATIVE,
5
11
  Schema,
6
12
  subscribe,
7
- subscribeNode
13
+ subscribeNode,
14
+ unwrap,
15
+ version
8
16
  } from "@kyneta/schema";
9
17
 
10
- // src/create.ts
18
+ // src/bind-yjs.ts
11
19
  import {
12
- changefeed,
13
- interpret,
14
- readable,
15
- registerSubstrate,
16
- writable
20
+ BACKING_DOC as BACKING_DOC2,
21
+ createSubstrateNamespace,
22
+ STRUCTURAL_YJS_CLIENT_ID as STRUCTURAL_YJS_CLIENT_ID2
17
23
  } from "@kyneta/schema";
24
+ import * as Y6 from "yjs";
25
+
26
+ // src/populate.ts
27
+ import { KIND, STRUCTURAL_YJS_CLIENT_ID, Zero } from "@kyneta/schema";
28
+ import * as Y from "yjs";
29
+ function ensureContainers(doc, schema, conditional = false) {
30
+ const rootMap = doc.getMap("root");
31
+ if (schema[KIND] !== "product") {
32
+ return;
33
+ }
34
+ const savedClientID = doc.clientID;
35
+ doc.clientID = STRUCTURAL_YJS_CLIENT_ID;
36
+ try {
37
+ doc.transact(() => {
38
+ for (const [key, fieldSchema] of Object.entries(schema.fields).sort(
39
+ ([a], [b]) => a.localeCompare(b)
40
+ )) {
41
+ if (conditional && rootMap.has(key)) continue;
42
+ ensureRootField(rootMap, key, fieldSchema);
43
+ }
44
+ });
45
+ } finally {
46
+ doc.clientID = savedClientID;
47
+ }
48
+ }
49
+ function ensureRootField(rootMap, key, fieldSchema) {
50
+ switch (fieldSchema[KIND]) {
51
+ case "text":
52
+ rootMap.set(key, new Y.Text());
53
+ return;
54
+ case "product":
55
+ rootMap.set(key, ensureMapContainers(fieldSchema));
56
+ return;
57
+ case "sequence":
58
+ rootMap.set(key, new Y.Array());
59
+ return;
60
+ case "map":
61
+ rootMap.set(key, new Y.Map());
62
+ return;
63
+ case "scalar":
64
+ case "sum": {
65
+ const zero = Zero.structural(fieldSchema);
66
+ if (zero !== void 0) {
67
+ rootMap.set(key, zero);
68
+ }
69
+ return;
70
+ }
71
+ case "counter":
72
+ case "set":
73
+ case "tree":
74
+ case "movable":
75
+ throw new Error(
76
+ `Yjs substrate does not support [KIND]="${fieldSchema[KIND]}". Supported kinds: text, product, sequence, map, scalar, sum. Encountered unsupported kind at root field "${key}".`
77
+ );
78
+ }
79
+ }
80
+ function ensureMapContainers(schema) {
81
+ const map = new Y.Map();
82
+ if (schema[KIND] !== "product") return map;
83
+ for (const [key, fieldSchema] of Object.entries(
84
+ schema.fields
85
+ ).sort(([a], [b]) => a.localeCompare(b))) {
86
+ switch (fieldSchema[KIND]) {
87
+ case "text":
88
+ map.set(key, new Y.Text());
89
+ break;
90
+ case "product":
91
+ map.set(key, ensureMapContainers(fieldSchema));
92
+ break;
93
+ case "sequence":
94
+ map.set(key, new Y.Array());
95
+ break;
96
+ case "map":
97
+ map.set(key, new Y.Map());
98
+ break;
99
+ case "scalar":
100
+ case "sum": {
101
+ const zero = Zero.structural(fieldSchema);
102
+ if (zero !== void 0) {
103
+ map.set(key, zero);
104
+ }
105
+ break;
106
+ }
107
+ case "counter":
108
+ case "set":
109
+ case "tree":
110
+ case "movable":
111
+ throw new Error(
112
+ `Yjs substrate does not support [KIND]="${fieldSchema[KIND]}". Supported kinds: text, product, sequence, map, scalar, sum. Encountered unsupported kind at nested field "${key}".`
113
+ );
114
+ }
115
+ }
116
+ return map;
117
+ }
18
118
 
19
119
  // src/substrate.ts
20
- import { BACKING_DOC, buildWritableContext, executeBatch } from "@kyneta/schema";
120
+ import {
121
+ BACKING_DOC,
122
+ buildWritableContext,
123
+ executeBatch,
124
+ KIND as KIND3
125
+ } from "@kyneta/schema";
21
126
  import * as Y5 from "yjs";
22
127
 
23
128
  // src/change-mapping.ts
24
- import { advanceSchema as advanceSchema2, expandMapOpsToLeaves, RawPath } from "@kyneta/schema";
25
- import * as Y2 from "yjs";
129
+ import {
130
+ advanceSchema as advanceSchema2,
131
+ expandMapOpsToLeaves,
132
+ KIND as KIND2,
133
+ RawPath
134
+ } from "@kyneta/schema";
135
+ import * as Y3 from "yjs";
26
136
 
27
137
  // src/yjs-resolve.ts
28
138
  import { advanceSchema } from "@kyneta/schema";
29
- import * as Y from "yjs";
139
+ import * as Y2 from "yjs";
30
140
  function stepIntoYjs(current, segment) {
31
141
  const resolved = segment.resolve();
32
- if (current instanceof Y.Map) {
142
+ if (current instanceof Y2.Map) {
33
143
  return current.get(resolved);
34
144
  }
35
- if (current instanceof Y.Array) {
145
+ if (current instanceof Y2.Array) {
36
146
  return current.get(resolved);
37
147
  }
38
- if (current instanceof Y.Text) {
148
+ if (current instanceof Y2.Text) {
39
149
  throw new Error(`yjs-resolve: cannot step into Y.Text`);
40
150
  }
41
151
  return void 0;
@@ -43,10 +153,6 @@ function stepIntoYjs(current, segment) {
43
153
  function resolveYjsType(rootMap, rootSchema, path) {
44
154
  let current = rootMap;
45
155
  let schema = rootSchema;
46
- let rootProduct = rootSchema;
47
- while (rootProduct._kind === "annotated" && rootProduct.schema !== void 0) {
48
- rootProduct = rootProduct.schema;
49
- }
50
156
  for (let i = 0; i < path.length; i++) {
51
157
  const seg = path.segments[i];
52
158
  const nextSchema = advanceSchema(schema, seg);
@@ -73,11 +179,11 @@ function applyChangeToYjs(rootMap, rootSchema, path, change2) {
73
179
  return;
74
180
  case "increment":
75
181
  throw new Error(
76
- `Yjs substrate does not support counter annotations. Use Schema.number() with ReplaceChange instead. Attempted IncrementChange with amount=${change2.amount} at path [${pathToString(path)}].`
182
+ `Yjs substrate does not support "${change2.type}" changes. Counter requires a CRDT backend that supports counters (e.g. Loro). Attempted IncrementChange with amount=${change2.amount} at path [${pathToString(path)}].`
77
183
  );
78
184
  case "tree":
79
185
  throw new Error(
80
- `Yjs substrate does not support tree annotations. Yjs has no native tree type. Attempted TreeChange at path [${pathToString(path)}].`
186
+ `Yjs substrate does not support "${change2.type}" changes. Tree requires a CRDT backend that supports trees (e.g. Loro). Attempted TreeChange at path [${pathToString(path)}].`
81
187
  );
82
188
  default:
83
189
  throw new Error(
@@ -87,7 +193,7 @@ function applyChangeToYjs(rootMap, rootSchema, path, change2) {
87
193
  }
88
194
  function applyTextChange(rootMap, rootSchema, path, change2) {
89
195
  const resolved = resolveYjsType(rootMap, rootSchema, path);
90
- if (!(resolved instanceof Y2.Text)) {
196
+ if (!(resolved instanceof Y3.Text)) {
91
197
  throw new Error(
92
198
  `applyChangeToYjs: TextChange target at path [${pathToString(path)}] is not a Y.Text`
93
199
  );
@@ -96,7 +202,7 @@ function applyTextChange(rootMap, rootSchema, path, change2) {
96
202
  }
97
203
  function applySequenceChange(rootMap, rootSchema, path, change2) {
98
204
  const resolved = resolveYjsType(rootMap, rootSchema, path);
99
- if (!(resolved instanceof Y2.Array)) {
205
+ if (!(resolved instanceof Y3.Array)) {
100
206
  throw new Error(
101
207
  `applyChangeToYjs: SequenceChange target at path [${pathToString(path)}] is not a Y.Array`
102
208
  );
@@ -121,7 +227,7 @@ function applySequenceChange(rootMap, rootSchema, path, change2) {
121
227
  }
122
228
  function applyMapChange(rootMap, rootSchema, path, change2) {
123
229
  const resolved = resolveYjsType(rootMap, rootSchema, path);
124
- if (!(resolved instanceof Y2.Map)) {
230
+ if (!(resolved instanceof Y3.Map)) {
125
231
  throw new Error(
126
232
  `applyChangeToYjs: MapChange target at path [${pathToString(path)}] is not a Y.Map`
127
233
  );
@@ -150,11 +256,11 @@ function applyReplaceChange(rootMap, rootSchema, path, change2) {
150
256
  const parentPath = path.slice(0, -1);
151
257
  const parent = resolveYjsType(rootMap, rootSchema, parentPath);
152
258
  const resolved = lastSeg.resolve();
153
- if (parent instanceof Y2.Map && lastSeg.role === "key") {
259
+ if (parent instanceof Y3.Map && lastSeg.role === "key") {
154
260
  const targetSchema = resolveSchemaAtPath(rootSchema, path);
155
261
  const yjsValue = maybeCreateSharedType(change2.value, targetSchema);
156
262
  parent.set(resolved, yjsValue);
157
- } else if (parent instanceof Y2.Array && lastSeg.role === "index") {
263
+ } else if (parent instanceof Y3.Array && lastSeg.role === "index") {
158
264
  const targetSchema = resolveSchemaAtPath(rootSchema, path);
159
265
  const yjsValue = maybeCreateSharedType(change2.value, targetSchema);
160
266
  parent.delete(resolved, 1);
@@ -167,29 +273,25 @@ function applyReplaceChange(rootMap, rootSchema, path, change2) {
167
273
  }
168
274
  function maybeCreateSharedType(value, schema) {
169
275
  if (schema === void 0) return value;
170
- const structural = unwrapAnnotations(schema);
171
- const tag = schema._kind === "annotated" ? schema.tag : void 0;
172
- if (tag === "text") {
173
- const text2 = new Y2.Text();
174
- if (typeof value === "string" && value.length > 0) {
175
- text2.insert(0, value);
276
+ switch (schema[KIND2]) {
277
+ // First-class text Y.Text
278
+ case "text": {
279
+ const text = new Y3.Text();
280
+ if (typeof value === "string" && value.length > 0) {
281
+ text.insert(0, value);
282
+ }
283
+ return text;
176
284
  }
177
- return text2;
178
- }
179
- if (tag === "counter" || tag === "movable" || tag === "tree") {
180
- throw new Error(`Yjs substrate does not support "${tag}" annotations.`);
181
- }
182
- switch (structural._kind) {
183
285
  case "product": {
184
286
  if (value === null || value === void 0 || typeof value !== "object" || Array.isArray(value)) {
185
287
  return value;
186
288
  }
187
- return createStructuredMap(value, structural);
289
+ return createStructuredMap(value, schema);
188
290
  }
189
291
  case "sequence": {
190
292
  if (!Array.isArray(value)) return value;
191
- const arr = new Y2.Array();
192
- const itemSchema = structural.item;
293
+ const arr = new Y3.Array();
294
+ const itemSchema = schema.item;
193
295
  const items = value.map(
194
296
  (item) => maybeCreateSharedType(item, itemSchema)
195
297
  );
@@ -200,21 +302,29 @@ function maybeCreateSharedType(value, schema) {
200
302
  if (value === null || value === void 0 || typeof value !== "object" || Array.isArray(value)) {
201
303
  return value;
202
304
  }
203
- const map = new Y2.Map();
204
- const valueSchema = structural.item;
305
+ const map = new Y3.Map();
306
+ const valueSchema = schema.item;
205
307
  for (const [k, v] of Object.entries(value)) {
206
308
  map.set(k, maybeCreateSharedType(v, valueSchema));
207
309
  }
208
310
  return map;
209
311
  }
312
+ // Unsupported first-class CRDT types — should not reach here
313
+ // (rejected at bind time by caps check)
314
+ case "counter":
315
+ case "set":
316
+ case "tree":
317
+ case "movable":
318
+ throw new Error(
319
+ `Yjs substrate does not support [KIND]="${schema[KIND2]}". This should have been caught at bind() time.`
320
+ );
210
321
  default:
211
322
  return value;
212
323
  }
213
324
  }
214
325
  function createStructuredMap(obj, productSchema) {
215
- const map = new Y2.Map();
216
- const structural = unwrapAnnotations(productSchema);
217
- if (structural._kind !== "product") {
326
+ const map = new Y3.Map();
327
+ if (productSchema[KIND2] !== "product") {
218
328
  for (const [key, val] of Object.entries(obj)) {
219
329
  map.set(key, val);
220
330
  }
@@ -222,22 +332,21 @@ function createStructuredMap(obj, productSchema) {
222
332
  }
223
333
  for (const [key, val] of Object.entries(obj)) {
224
334
  if (val === void 0) continue;
225
- const fieldSchema = structural.fields[key];
335
+ const fieldSchema = productSchema.fields[key];
226
336
  const yjsVal = fieldSchema ? maybeCreateSharedType(val, fieldSchema) : val;
227
337
  map.set(key, yjsVal);
228
338
  }
229
339
  for (const [key, fieldSchema] of Object.entries(
230
- structural.fields
340
+ productSchema.fields
231
341
  )) {
232
342
  if (key in obj) continue;
233
- const tag = fieldSchema._kind === "annotated" ? fieldSchema.tag : void 0;
234
- if (tag === "text") {
235
- map.set(key, new Y2.Text());
343
+ if (fieldSchema[KIND2] === "text") {
344
+ map.set(key, new Y3.Text());
236
345
  }
237
346
  }
238
347
  return map;
239
348
  }
240
- function eventsToOps(events) {
349
+ function eventsToOps(events, schema) {
241
350
  const ops = [];
242
351
  for (const event of events) {
243
352
  const kynetaPath = yjsPathToKynetaPath(event.path);
@@ -246,7 +355,7 @@ function eventsToOps(events) {
246
355
  ops.push({ path: kynetaPath, change: change2 });
247
356
  }
248
357
  }
249
- return expandMapOpsToLeaves(ops);
358
+ return expandMapOpsToLeaves(ops, schema);
250
359
  }
251
360
  function yjsPathToKynetaPath(yjsPath) {
252
361
  let path = RawPath.empty;
@@ -260,13 +369,13 @@ function yjsPathToKynetaPath(yjsPath) {
260
369
  return path;
261
370
  }
262
371
  function eventToChange(event) {
263
- if (event.target instanceof Y2.Text) {
372
+ if (event.target instanceof Y3.Text) {
264
373
  return textEventToChange(event);
265
374
  }
266
- if (event.target instanceof Y2.Array) {
375
+ if (event.target instanceof Y3.Array) {
267
376
  return arrayEventToChange(event);
268
377
  }
269
- if (event.target instanceof Y2.Map) {
378
+ if (event.target instanceof Y3.Map) {
270
379
  return mapEventToChange(event);
271
380
  }
272
381
  return null;
@@ -324,18 +433,11 @@ function mapEventToChange(event) {
324
433
  };
325
434
  }
326
435
  function extractEventValue(value) {
327
- if (value instanceof Y2.Map) return value.toJSON();
328
- if (value instanceof Y2.Array) return value.toJSON();
329
- if (value instanceof Y2.Text) return value.toJSON();
436
+ if (value instanceof Y3.Map) return value.toJSON();
437
+ if (value instanceof Y3.Array) return value.toJSON();
438
+ if (value instanceof Y3.Text) return value.toJSON();
330
439
  return value;
331
440
  }
332
- function unwrapAnnotations(schema) {
333
- let s = schema;
334
- while (s._kind === "annotated" && s.schema !== void 0) {
335
- s = s.schema;
336
- }
337
- return s;
338
- }
339
441
  function resolveSchemaAtPath(rootSchema, path) {
340
442
  let schema = rootSchema;
341
443
  for (const seg of path.segments) {
@@ -344,16 +446,19 @@ function resolveSchemaAtPath(rootSchema, path) {
344
446
  return schema;
345
447
  }
346
448
  function getItemSchema(schema) {
347
- const structural = unwrapAnnotations(schema);
348
- return structural._kind === "sequence" ? structural.item : void 0;
449
+ if (schema[KIND2] === "sequence") return schema.item;
450
+ if (schema[KIND2] === "movable") return schema.item;
451
+ return void 0;
349
452
  }
350
453
  function getFieldSchema(schema, key) {
351
- const structural = unwrapAnnotations(schema);
352
- if (structural._kind === "product") {
353
- return structural.fields[key];
454
+ if (schema[KIND2] === "product") {
455
+ return schema.fields[key];
456
+ }
457
+ if (schema[KIND2] === "map") {
458
+ return schema.item;
354
459
  }
355
- if (structural._kind === "map") {
356
- return structural.item;
460
+ if (schema[KIND2] === "set") {
461
+ return schema.item;
357
462
  }
358
463
  return void 0;
359
464
  }
@@ -361,116 +466,6 @@ function pathToString(path) {
361
466
  return path.segments.map((seg) => String(seg.resolve())).join(".");
362
467
  }
363
468
 
364
- // src/populate.ts
365
- import { STRUCTURAL_YJS_CLIENT_ID, Zero } from "@kyneta/schema";
366
- import * as Y3 from "yjs";
367
- function ensureContainers(doc, schema, conditional = false) {
368
- const rootMap = doc.getMap("root");
369
- let rootProduct = schema;
370
- while (rootProduct._kind === "annotated" && rootProduct.schema !== void 0) {
371
- rootProduct = rootProduct.schema;
372
- }
373
- if (rootProduct._kind !== "product") {
374
- return;
375
- }
376
- const savedClientID = doc.clientID;
377
- doc.clientID = STRUCTURAL_YJS_CLIENT_ID;
378
- try {
379
- doc.transact(() => {
380
- for (const [key, fieldSchema] of Object.entries(rootProduct.fields).sort(
381
- ([a], [b]) => a.localeCompare(b)
382
- )) {
383
- if (conditional && rootMap.has(key)) continue;
384
- ensureRootField(rootMap, key, fieldSchema);
385
- }
386
- });
387
- } finally {
388
- doc.clientID = savedClientID;
389
- }
390
- }
391
- function ensureRootField(rootMap, key, fieldSchema) {
392
- const tag = fieldSchema._kind === "annotated" ? fieldSchema.tag : void 0;
393
- switch (tag) {
394
- case "text":
395
- rootMap.set(key, new Y3.Text());
396
- return;
397
- case "counter":
398
- throw new Error(
399
- `Yjs substrate does not support counter annotations. Use Schema.number() with ReplaceChange instead. Encountered counter annotation at root field "${key}".`
400
- );
401
- case "movable":
402
- throw new Error(
403
- `Yjs substrate does not support movable list annotations. Yjs has no native movable list type. Encountered movable annotation at root field "${key}".`
404
- );
405
- case "tree":
406
- throw new Error(
407
- `Yjs substrate does not support tree annotations. Yjs has no native tree type. Encountered tree annotation at root field "${key}".`
408
- );
409
- }
410
- const structural = unwrapAnnotations2(fieldSchema);
411
- switch (structural._kind) {
412
- case "product":
413
- rootMap.set(key, ensureMapContainers(structural));
414
- return;
415
- case "sequence":
416
- rootMap.set(key, new Y3.Array());
417
- return;
418
- case "map":
419
- rootMap.set(key, new Y3.Map());
420
- return;
421
- case "scalar":
422
- case "sum": {
423
- const zero = Zero.structural(fieldSchema);
424
- if (zero !== void 0) {
425
- rootMap.set(key, zero);
426
- }
427
- return;
428
- }
429
- }
430
- }
431
- function ensureMapContainers(schema) {
432
- const map = new Y3.Map();
433
- const structural = unwrapAnnotations2(schema);
434
- if (structural._kind !== "product") return map;
435
- for (const [key, fieldSchema] of Object.entries(
436
- structural.fields
437
- ).sort(([a], [b]) => a.localeCompare(b))) {
438
- const tag = fieldSchema._kind === "annotated" ? fieldSchema.tag : void 0;
439
- if (tag === "text") {
440
- map.set(key, new Y3.Text());
441
- continue;
442
- }
443
- const fs = unwrapAnnotations2(fieldSchema);
444
- switch (fs._kind) {
445
- case "product":
446
- map.set(key, ensureMapContainers(fieldSchema));
447
- break;
448
- case "sequence":
449
- map.set(key, new Y3.Array());
450
- break;
451
- case "map":
452
- map.set(key, new Y3.Map());
453
- break;
454
- case "scalar":
455
- case "sum": {
456
- const zero = Zero.structural(fieldSchema);
457
- if (zero !== void 0) {
458
- map.set(key, zero);
459
- }
460
- break;
461
- }
462
- }
463
- }
464
- return map;
465
- }
466
- function unwrapAnnotations2(schema) {
467
- let s = schema;
468
- while (s._kind === "annotated" && s.schema !== void 0) {
469
- s = s.schema;
470
- }
471
- return s;
472
- }
473
-
474
469
  // src/reader.ts
475
470
  import * as Y4 from "yjs";
476
471
  function extractValue(resolved) {
@@ -529,21 +524,28 @@ function yjsReader(doc, schema) {
529
524
  }
530
525
 
531
526
  // src/version.ts
527
+ import {
528
+ base64ToUint8Array,
529
+ uint8ArrayToBase64,
530
+ versionVectorCompare,
531
+ versionVectorMeet
532
+ } from "@kyneta/schema";
532
533
  import { decodeStateVector } from "yjs";
533
- function uint8ArrayToBase64(bytes) {
534
- let binary = "";
535
- for (let i = 0; i < bytes.length; i++) {
536
- binary += String.fromCharCode(bytes[i]);
534
+ function encodeStateVector(map) {
535
+ const bytes = [];
536
+ function writeVarUint(value) {
537
+ while (value > 127) {
538
+ bytes.push(value & 127 | 128);
539
+ value >>>= 7;
540
+ }
541
+ bytes.push(value & 127);
537
542
  }
538
- return btoa(binary);
539
- }
540
- function base64ToUint8Array(base64) {
541
- const binary = atob(base64);
542
- const bytes = new Uint8Array(binary.length);
543
- for (let i = 0; i < binary.length; i++) {
544
- bytes[i] = binary.charCodeAt(i);
543
+ writeVarUint(map.size);
544
+ for (const [clientId, clock] of map) {
545
+ writeVarUint(clientId);
546
+ writeVarUint(clock);
545
547
  }
546
- return bytes;
548
+ return new Uint8Array(bytes);
547
549
  }
548
550
  var YjsVersion = class _YjsVersion {
549
551
  sv;
@@ -562,45 +564,37 @@ var YjsVersion = class _YjsVersion {
562
564
  /**
563
565
  * Compare with another version using version-vector partial order.
564
566
  *
565
- * Decodes both state vectors via `Y.decodeStateVector()` to get
566
- * `Map<number, number>` (clientID clock), then compares:
567
+ * Delegates to the shared `versionVectorCompare` utility after decoding
568
+ * both state vectors via `Y.decodeStateVector()`.
567
569
  *
568
- * - Collect the union of all client IDs from both maps.
569
- * - For each client, compare clocks (missing client = clock 0).
570
- * - If all clocks in `this` ≤ `other` and at least one strictly less → `"behind"`
571
- * - If all clocks in `this` ≥ `other` and at least one strictly greater → `"ahead"`
572
- * - If all clocks equal → `"equal"`
573
- * - Otherwise → `"concurrent"`
574
- *
575
- * Throws if `other` is not a `YjsVersion`.
570
+ * @throws If `other` is not a `YjsVersion`.
576
571
  */
577
572
  compare(other) {
578
573
  if (!(other instanceof _YjsVersion)) {
579
574
  throw new Error("YjsVersion can only be compared with another YjsVersion");
580
575
  }
576
+ return versionVectorCompare(
577
+ decodeStateVector(this.sv),
578
+ decodeStateVector(other.sv)
579
+ );
580
+ }
581
+ /**
582
+ * Greatest lower bound (lattice meet) of two Yjs versions.
583
+ *
584
+ * Decodes both state vectors, computes the component-wise minimum
585
+ * via the shared `versionVectorMeet` utility, and encodes the result
586
+ * back to a Yjs state vector.
587
+ *
588
+ * @throws If `other` is not a `YjsVersion`.
589
+ */
590
+ meet(other) {
591
+ if (!(other instanceof _YjsVersion)) {
592
+ throw new Error("YjsVersion can only be meet'd with another YjsVersion");
593
+ }
581
594
  const thisMap = decodeStateVector(this.sv);
582
595
  const otherMap = decodeStateVector(other.sv);
583
- const allClients = /* @__PURE__ */ new Set();
584
- for (const id of thisMap.keys()) allClients.add(id);
585
- for (const id of otherMap.keys()) allClients.add(id);
586
- let hasLess = false;
587
- let hasGreater = false;
588
- for (const clientId of allClients) {
589
- const thisClock = thisMap.get(clientId) ?? 0;
590
- const otherClock = otherMap.get(clientId) ?? 0;
591
- if (thisClock < otherClock) {
592
- hasLess = true;
593
- }
594
- if (thisClock > otherClock) {
595
- hasGreater = true;
596
- }
597
- if (hasLess && hasGreater) {
598
- return "concurrent";
599
- }
600
- }
601
- if (hasLess && !hasGreater) return "behind";
602
- if (hasGreater && !hasLess) return "ahead";
603
- return "equal";
596
+ const result = versionVectorMeet(thisMap, otherMap);
597
+ return new _YjsVersion(encodeStateVector(result));
604
598
  }
605
599
  /**
606
600
  * Parse a serialized YjsVersion string back into a YjsVersion.
@@ -633,7 +627,7 @@ function createYjsSubstrate(doc, schema) {
633
627
  pendingChanges.push({ path, change: change2 });
634
628
  }
635
629
  },
636
- onFlush(origin) {
630
+ onFlush(_origin) {
637
631
  if (!inOurTransaction && pendingChanges.length > 0) {
638
632
  inOurTransaction = true;
639
633
  try {
@@ -651,12 +645,26 @@ function createYjsSubstrate(doc, schema) {
651
645
  context() {
652
646
  if (!cachedCtx) {
653
647
  cachedCtx = buildWritableContext(substrate);
648
+ cachedCtx.nativeResolver = (nodeSchema, path) => {
649
+ if (path.segments.length === 0) return doc;
650
+ if (nodeSchema[KIND3] === "scalar" || nodeSchema[KIND3] === "sum")
651
+ return void 0;
652
+ return resolveYjsType(rootMap, schema, path);
653
+ };
654
654
  }
655
655
  return cachedCtx;
656
656
  },
657
657
  version() {
658
658
  return new YjsVersion(Y5.encodeStateVector(doc));
659
659
  },
660
+ baseVersion() {
661
+ return new YjsVersion(new Uint8Array([0]));
662
+ },
663
+ advance(_to) {
664
+ throw new Error(
665
+ "advance() on a live Yjs substrate is not yet supported. Use advance() on a YjsReplica instead."
666
+ );
667
+ },
660
668
  exportEntirety() {
661
669
  return {
662
670
  kind: "entirety",
@@ -690,7 +698,7 @@ function createYjsSubstrate(doc, schema) {
690
698
  if (transaction.origin === KYNETA_ORIGIN) {
691
699
  return;
692
700
  }
693
- const ops = eventsToOps(events);
701
+ const ops = eventsToOps(events, schema);
694
702
  if (ops.length === 0) {
695
703
  return;
696
704
  }
@@ -706,21 +714,44 @@ function createYjsSubstrate(doc, schema) {
706
714
  return substrate;
707
715
  }
708
716
  function createYjsReplica(doc) {
717
+ let currentDoc = doc;
718
+ let currentBase = new YjsVersion(Y5.encodeStateVector(new Y5.Doc()));
709
719
  return {
710
- [BACKING_DOC]: doc,
720
+ get [BACKING_DOC]() {
721
+ return currentDoc;
722
+ },
711
723
  version() {
712
- return new YjsVersion(Y5.encodeStateVector(doc));
724
+ return new YjsVersion(Y5.encodeStateVector(currentDoc));
725
+ },
726
+ baseVersion() {
727
+ return currentBase;
728
+ },
729
+ advance(to) {
730
+ const baseCmp = currentBase.compare(to);
731
+ if (baseCmp === "ahead") {
732
+ throw new Error("advance(): target is behind base version");
733
+ }
734
+ const currentCmp = to.compare(this.version());
735
+ if (currentCmp === "ahead") {
736
+ throw new Error("advance(): target is ahead of current version");
737
+ }
738
+ if (currentCmp !== "equal") return;
739
+ const update = Y5.encodeStateAsUpdate(currentDoc);
740
+ const newDoc = new Y5.Doc();
741
+ Y5.applyUpdate(newDoc, update);
742
+ currentDoc = newDoc;
743
+ currentBase = new YjsVersion(Y5.encodeStateVector(currentDoc));
713
744
  },
714
745
  exportEntirety() {
715
746
  return {
716
747
  kind: "entirety",
717
748
  encoding: "binary",
718
- data: Y5.encodeStateAsUpdate(doc)
749
+ data: Y5.encodeStateAsUpdate(currentDoc)
719
750
  };
720
751
  },
721
752
  exportSince(since) {
722
753
  try {
723
- const bytes = Y5.encodeStateAsUpdate(doc, since.sv);
754
+ const bytes = Y5.encodeStateAsUpdate(currentDoc, since.sv);
724
755
  return { kind: "since", encoding: "binary", data: bytes };
725
756
  } catch {
726
757
  return null;
@@ -732,7 +763,7 @@ function createYjsReplica(doc) {
732
763
  "YjsReplica.merge expects binary-encoded payloads. If you recently switched CRDT backends, stale clients may be sending incompatible data."
733
764
  );
734
765
  }
735
- Y5.applyUpdate(doc, payload.data);
766
+ Y5.applyUpdate(currentDoc, payload.data);
736
767
  }
737
768
  };
738
769
  }
@@ -780,55 +811,7 @@ var yjsSubstrateFactory = {
780
811
  }
781
812
  };
782
813
 
783
- // src/create.ts
784
- var substrates = /* @__PURE__ */ new WeakMap();
785
- function getSubstrate(doc) {
786
- const s = substrates.get(doc);
787
- if (!s) {
788
- throw new Error(
789
- "version/exportEntirety/merge called on an object without a YjsSubstrate. Use a doc created by createYjsDoc() or createYjsDocFromEntirety()."
790
- );
791
- }
792
- return s;
793
- }
794
- function registerDoc(schema, substrate) {
795
- const doc = interpret(schema, substrate.context()).with(readable).with(writable).with(changefeed).done();
796
- substrates.set(doc, substrate);
797
- registerSubstrate(doc, substrate);
798
- return doc;
799
- }
800
- function isYDoc(value) {
801
- return value !== null && value !== void 0 && typeof value === "object" && "getMap" in value && "getText" in value && "getArray" in value && "transact" in value && typeof value.transact === "function" && // Y.Doc has clientID; distinguish from other objects
802
- "clientID" in value && typeof value.clientID === "number";
803
- }
804
- var createYjsDoc = (schema, doc) => {
805
- if (doc !== void 0 && isYDoc(doc)) {
806
- return registerDoc(schema, createYjsSubstrate(doc, schema));
807
- }
808
- return registerDoc(schema, yjsSubstrateFactory.create(schema));
809
- };
810
- var createYjsDocFromEntirety = (schema, payload) => registerDoc(schema, yjsSubstrateFactory.fromEntirety(payload, schema));
811
-
812
- // src/sync.ts
813
- function version(doc) {
814
- return getSubstrate(doc).version();
815
- }
816
- function exportEntirety(doc) {
817
- return getSubstrate(doc).exportEntirety();
818
- }
819
- function exportSince(doc, since) {
820
- return getSubstrate(doc).exportSince(since);
821
- }
822
- function merge(doc, payload, origin) {
823
- getSubstrate(doc).merge(payload, origin);
824
- }
825
-
826
- // src/index.ts
827
- import { Schema as Schema2 } from "@kyneta/schema";
828
-
829
814
  // src/bind-yjs.ts
830
- import { BACKING_DOC as BACKING_DOC2, bind, STRUCTURAL_YJS_CLIENT_ID as STRUCTURAL_YJS_CLIENT_ID2 } from "@kyneta/schema";
831
- import * as Y6 from "yjs";
832
815
  function hashPeerId(peerId) {
833
816
  let hash = 2166136261;
834
817
  for (let i = 0; i < peerId.length; i++) {
@@ -867,47 +850,28 @@ function createYjsFactory(peerId) {
867
850
  }
868
851
  };
869
852
  }
870
- function bindYjs(schema) {
871
- return bind({
872
- schema,
873
- factory: (ctx) => createYjsFactory(ctx.peerId),
874
- strategy: "causal"
875
- });
876
- }
877
-
878
- // src/yjs-escape.ts
879
- import { BACKING_DOC as BACKING_DOC3, unwrap } from "@kyneta/schema";
880
- function yjs(ref) {
881
- let substrate;
882
- try {
883
- substrate = unwrap(ref);
884
- } catch {
885
- throw new Error(
886
- "yjs() requires a ref backed by a Yjs substrate. Use a doc created by exchange.get() with a bindYjs() schema, or by createYjsDoc()."
887
- );
888
- }
889
- const doc = substrate[BACKING_DOC3];
890
- if (!doc || typeof doc !== "object" || typeof doc.getMap !== "function" || typeof doc.clientID !== "number") {
891
- throw new Error(
892
- "yjs() requires a ref backed by a Yjs substrate. The ref has a substrate but it is not a Yjs substrate. Use a doc created with a bindYjs() schema or createYjsDoc()."
893
- );
894
- }
895
- return doc;
896
- }
897
-
898
- // src/index.ts
899
- function text() {
900
- return Schema2.annotated("text");
901
- }
853
+ var yjs = createSubstrateNamespace({
854
+ strategies: {
855
+ collaborative: {
856
+ factory: (ctx) => createYjsFactory(ctx.peerId),
857
+ replicaFactory: yjsReplicaFactory
858
+ },
859
+ ephemeral: {
860
+ factory: (ctx) => createYjsFactory(ctx.peerId),
861
+ replicaFactory: yjsReplicaFactory
862
+ }
863
+ },
864
+ defaultStrategy: "collaborative"
865
+ });
902
866
  export {
867
+ NATIVE,
903
868
  Schema,
904
869
  YjsVersion,
905
870
  applyChangeToYjs,
906
871
  applyChanges,
907
- bindYjs,
908
872
  change,
909
- createYjsDoc,
910
- createYjsDocFromEntirety,
873
+ createDoc,
874
+ createRef,
911
875
  createYjsSubstrate,
912
876
  ensureContainers,
913
877
  eventsToOps,
@@ -918,10 +882,11 @@ export {
918
882
  stepIntoYjs,
919
883
  subscribe,
920
884
  subscribeNode,
921
- text,
885
+ unwrap,
922
886
  version,
923
887
  yjs,
924
888
  yjsReader,
889
+ yjsReplicaFactory,
925
890
  yjsSubstrateFactory
926
891
  };
927
892
  //# sourceMappingURL=index.js.map