@kyneta/yjs-schema 1.0.0 → 1.2.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
@@ -1,14 +1,27 @@
1
+ // src/index.ts
2
+ import {
3
+ applyChanges,
4
+ change,
5
+ Schema,
6
+ subscribe,
7
+ subscribeNode
8
+ } from "@kyneta/schema";
9
+
1
10
  // src/create.ts
2
- import { interpret, registerSubstrate } from "@kyneta/schema";
3
- import { changefeed, readable, writable } from "@kyneta/schema";
11
+ import {
12
+ interpret,
13
+ observation,
14
+ readable,
15
+ registerSubstrate,
16
+ writable
17
+ } from "@kyneta/schema";
4
18
 
5
19
  // src/substrate.ts
6
- import { buildWritableContext, executeBatch } from "@kyneta/schema";
20
+ import { BACKING_DOC, buildWritableContext, executeBatch } from "@kyneta/schema";
7
21
  import * as Y5 from "yjs";
8
22
 
9
23
  // src/change-mapping.ts
10
- import { advanceSchema as advanceSchema2, expandMapOpsToLeaves } from "@kyneta/schema";
11
- import { RawPath } from "@kyneta/schema";
24
+ import { advanceSchema as advanceSchema2, expandMapOpsToLeaves, KIND as KIND2, RawPath } from "@kyneta/schema";
12
25
  import * as Y2 from "yjs";
13
26
 
14
27
  // src/yjs-resolve.ts
@@ -23,19 +36,13 @@ function stepIntoYjs(current, segment) {
23
36
  return current.get(resolved);
24
37
  }
25
38
  if (current instanceof Y.Text) {
26
- throw new Error(
27
- `yjs-resolve: cannot step into Y.Text`
28
- );
39
+ throw new Error(`yjs-resolve: cannot step into Y.Text`);
29
40
  }
30
41
  return void 0;
31
42
  }
32
43
  function resolveYjsType(rootMap, rootSchema, path) {
33
44
  let current = rootMap;
34
45
  let schema = rootSchema;
35
- let rootProduct = rootSchema;
36
- while (rootProduct._kind === "annotated" && rootProduct.schema !== void 0) {
37
- rootProduct = rootProduct.schema;
38
- }
39
46
  for (let i = 0; i < path.length; i++) {
40
47
  const seg = path.segments[i];
41
48
  const nextSchema = advanceSchema(schema, seg);
@@ -62,11 +69,11 @@ function applyChangeToYjs(rootMap, rootSchema, path, change2) {
62
69
  return;
63
70
  case "increment":
64
71
  throw new Error(
65
- `Yjs substrate does not support counter annotations. Use Schema.number() with ReplaceChange instead. Attempted IncrementChange with amount=${change2.amount} at path [${pathToString(path)}].`
72
+ `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)}].`
66
73
  );
67
74
  case "tree":
68
75
  throw new Error(
69
- `Yjs substrate does not support tree annotations. Yjs has no native tree type. Attempted TreeChange at path [${pathToString(path)}].`
76
+ `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)}].`
70
77
  );
71
78
  default:
72
79
  throw new Error(
@@ -156,34 +163,25 @@ function applyReplaceChange(rootMap, rootSchema, path, change2) {
156
163
  }
157
164
  function maybeCreateSharedType(value, schema) {
158
165
  if (schema === void 0) return value;
159
- const structural = unwrapAnnotations(schema);
160
- const tag = schema._kind === "annotated" ? schema.tag : void 0;
161
- if (tag === "text") {
162
- const text2 = new Y2.Text();
163
- if (typeof value === "string" && value.length > 0) {
164
- text2.insert(0, value);
166
+ switch (schema[KIND2]) {
167
+ // First-class text Y.Text
168
+ case "text": {
169
+ const text = new Y2.Text();
170
+ if (typeof value === "string" && value.length > 0) {
171
+ text.insert(0, value);
172
+ }
173
+ return text;
165
174
  }
166
- return text2;
167
- }
168
- if (tag === "counter" || tag === "movable" || tag === "tree") {
169
- throw new Error(
170
- `Yjs substrate does not support "${tag}" annotations.`
171
- );
172
- }
173
- switch (structural._kind) {
174
175
  case "product": {
175
176
  if (value === null || value === void 0 || typeof value !== "object" || Array.isArray(value)) {
176
177
  return value;
177
178
  }
178
- return createStructuredMap(
179
- value,
180
- structural
181
- );
179
+ return createStructuredMap(value, schema);
182
180
  }
183
181
  case "sequence": {
184
182
  if (!Array.isArray(value)) return value;
185
183
  const arr = new Y2.Array();
186
- const itemSchema = structural.item;
184
+ const itemSchema = schema.item;
187
185
  const items = value.map(
188
186
  (item) => maybeCreateSharedType(item, itemSchema)
189
187
  );
@@ -195,22 +193,28 @@ function maybeCreateSharedType(value, schema) {
195
193
  return value;
196
194
  }
197
195
  const map = new Y2.Map();
198
- const valueSchema = structural.item;
199
- for (const [k, v] of Object.entries(
200
- value
201
- )) {
196
+ const valueSchema = schema.item;
197
+ for (const [k, v] of Object.entries(value)) {
202
198
  map.set(k, maybeCreateSharedType(v, valueSchema));
203
199
  }
204
200
  return map;
205
201
  }
202
+ // Unsupported first-class CRDT types — should not reach here
203
+ // (rejected at bind time by caps check)
204
+ case "counter":
205
+ case "set":
206
+ case "tree":
207
+ case "movable":
208
+ throw new Error(
209
+ `Yjs substrate does not support [KIND]="${schema[KIND2]}". This should have been caught at bind() time.`
210
+ );
206
211
  default:
207
212
  return value;
208
213
  }
209
214
  }
210
215
  function createStructuredMap(obj, productSchema) {
211
216
  const map = new Y2.Map();
212
- const structural = unwrapAnnotations(productSchema);
213
- if (structural._kind !== "product") {
217
+ if (productSchema[KIND2] !== "product") {
214
218
  for (const [key, val] of Object.entries(obj)) {
215
219
  map.set(key, val);
216
220
  }
@@ -218,22 +222,21 @@ function createStructuredMap(obj, productSchema) {
218
222
  }
219
223
  for (const [key, val] of Object.entries(obj)) {
220
224
  if (val === void 0) continue;
221
- const fieldSchema = structural.fields[key];
225
+ const fieldSchema = productSchema.fields[key];
222
226
  const yjsVal = fieldSchema ? maybeCreateSharedType(val, fieldSchema) : val;
223
227
  map.set(key, yjsVal);
224
228
  }
225
229
  for (const [key, fieldSchema] of Object.entries(
226
- structural.fields
230
+ productSchema.fields
227
231
  )) {
228
232
  if (key in obj) continue;
229
- const tag = fieldSchema._kind === "annotated" ? fieldSchema.tag : void 0;
230
- if (tag === "text") {
233
+ if (fieldSchema[KIND2] === "text") {
231
234
  map.set(key, new Y2.Text());
232
235
  }
233
236
  }
234
237
  return map;
235
238
  }
236
- function eventsToOps(events) {
239
+ function eventsToOps(events, schema) {
237
240
  const ops = [];
238
241
  for (const event of events) {
239
242
  const kynetaPath = yjsPathToKynetaPath(event.path);
@@ -242,7 +245,7 @@ function eventsToOps(events) {
242
245
  ops.push({ path: kynetaPath, change: change2 });
243
246
  }
244
247
  }
245
- return expandMapOpsToLeaves(ops);
248
+ return expandMapOpsToLeaves(ops, schema);
246
249
  }
247
250
  function yjsPathToKynetaPath(yjsPath) {
248
251
  let path = RawPath.empty;
@@ -302,18 +305,16 @@ function mapEventToChange(event) {
302
305
  let hasSet = false;
303
306
  let hasDelete = false;
304
307
  const target = event.target;
305
- event.changes.keys.forEach(
306
- (change2, key) => {
307
- if (change2.action === "add" || change2.action === "update") {
308
- const value = target.get(key);
309
- set[key] = extractEventValue(value);
310
- hasSet = true;
311
- } else if (change2.action === "delete") {
312
- deleteKeys.push(key);
313
- hasDelete = true;
314
- }
308
+ event.changes.keys.forEach((change2, key) => {
309
+ if (change2.action === "add" || change2.action === "update") {
310
+ const value = target.get(key);
311
+ set[key] = extractEventValue(value);
312
+ hasSet = true;
313
+ } else if (change2.action === "delete") {
314
+ deleteKeys.push(key);
315
+ hasDelete = true;
315
316
  }
316
- );
317
+ });
317
318
  if (!hasSet && !hasDelete) return null;
318
319
  return {
319
320
  type: "map",
@@ -327,13 +328,6 @@ function extractEventValue(value) {
327
328
  if (value instanceof Y2.Text) return value.toJSON();
328
329
  return value;
329
330
  }
330
- function unwrapAnnotations(schema) {
331
- let s = schema;
332
- while (s._kind === "annotated" && s.schema !== void 0) {
333
- s = s.schema;
334
- }
335
- return s;
336
- }
337
331
  function resolveSchemaAtPath(rootSchema, path) {
338
332
  let schema = rootSchema;
339
333
  for (const seg of path.segments) {
@@ -342,16 +336,19 @@ function resolveSchemaAtPath(rootSchema, path) {
342
336
  return schema;
343
337
  }
344
338
  function getItemSchema(schema) {
345
- const structural = unwrapAnnotations(schema);
346
- return structural._kind === "sequence" ? structural.item : void 0;
339
+ if (schema[KIND2] === "sequence") return schema.item;
340
+ if (schema[KIND2] === "movable") return schema.item;
341
+ return void 0;
347
342
  }
348
343
  function getFieldSchema(schema, key) {
349
- const structural = unwrapAnnotations(schema);
350
- if (structural._kind === "product") {
351
- return structural.fields[key];
344
+ if (schema[KIND2] === "product") {
345
+ return schema.fields[key];
346
+ }
347
+ if (schema[KIND2] === "map") {
348
+ return schema.item;
352
349
  }
353
- if (structural._kind === "map") {
354
- return structural.item;
350
+ if (schema[KIND2] === "set") {
351
+ return schema.item;
355
352
  }
356
353
  return void 0;
357
354
  }
@@ -360,46 +357,35 @@ function pathToString(path) {
360
357
  }
361
358
 
362
359
  // src/populate.ts
363
- import { Zero } from "@kyneta/schema";
360
+ import { KIND as KIND3, STRUCTURAL_YJS_CLIENT_ID, Zero } from "@kyneta/schema";
364
361
  import * as Y3 from "yjs";
365
- function ensureContainers(doc, schema) {
362
+ function ensureContainers(doc, schema, conditional = false) {
366
363
  const rootMap = doc.getMap("root");
367
- let rootProduct = schema;
368
- while (rootProduct._kind === "annotated" && rootProduct.schema !== void 0) {
369
- rootProduct = rootProduct.schema;
370
- }
371
- if (rootProduct._kind !== "product") {
364
+ if (schema[KIND3] !== "product") {
372
365
  return;
373
366
  }
374
- doc.transact(() => {
375
- for (const [key, fieldSchema] of Object.entries(rootProduct.fields)) {
376
- ensureRootField(rootMap, key, fieldSchema);
377
- }
378
- });
367
+ const savedClientID = doc.clientID;
368
+ doc.clientID = STRUCTURAL_YJS_CLIENT_ID;
369
+ try {
370
+ doc.transact(() => {
371
+ for (const [key, fieldSchema] of Object.entries(schema.fields).sort(
372
+ ([a], [b]) => a.localeCompare(b)
373
+ )) {
374
+ if (conditional && rootMap.has(key)) continue;
375
+ ensureRootField(rootMap, key, fieldSchema);
376
+ }
377
+ });
378
+ } finally {
379
+ doc.clientID = savedClientID;
380
+ }
379
381
  }
380
382
  function ensureRootField(rootMap, key, fieldSchema) {
381
- const tag = fieldSchema._kind === "annotated" ? fieldSchema.tag : void 0;
382
- switch (tag) {
383
+ switch (fieldSchema[KIND3]) {
383
384
  case "text":
384
385
  rootMap.set(key, new Y3.Text());
385
386
  return;
386
- case "counter":
387
- throw new Error(
388
- `Yjs substrate does not support counter annotations. Use Schema.number() with ReplaceChange instead. Encountered counter annotation at root field "${key}".`
389
- );
390
- case "movable":
391
- throw new Error(
392
- `Yjs substrate does not support movable list annotations. Yjs has no native movable list type. Encountered movable annotation at root field "${key}".`
393
- );
394
- case "tree":
395
- throw new Error(
396
- `Yjs substrate does not support tree annotations. Yjs has no native tree type. Encountered tree annotation at root field "${key}".`
397
- );
398
- }
399
- const structural = unwrapAnnotations2(fieldSchema);
400
- switch (structural._kind) {
401
387
  case "product":
402
- rootMap.set(key, ensureMapContainers(structural));
388
+ rootMap.set(key, ensureMapContainers(fieldSchema));
403
389
  return;
404
390
  case "sequence":
405
391
  rootMap.set(key, new Y3.Array());
@@ -415,22 +401,25 @@ function ensureRootField(rootMap, key, fieldSchema) {
415
401
  }
416
402
  return;
417
403
  }
404
+ case "counter":
405
+ case "set":
406
+ case "tree":
407
+ case "movable":
408
+ throw new Error(
409
+ `Yjs substrate does not support [KIND]="${fieldSchema[KIND3]}". Supported kinds: text, product, sequence, map, scalar, sum. Encountered unsupported kind at root field "${key}".`
410
+ );
418
411
  }
419
412
  }
420
413
  function ensureMapContainers(schema) {
421
414
  const map = new Y3.Map();
422
- const structural = unwrapAnnotations2(schema);
423
- if (structural._kind !== "product") return map;
415
+ if (schema[KIND3] !== "product") return map;
424
416
  for (const [key, fieldSchema] of Object.entries(
425
- structural.fields
426
- )) {
427
- const tag = fieldSchema._kind === "annotated" ? fieldSchema.tag : void 0;
428
- if (tag === "text") {
429
- map.set(key, new Y3.Text());
430
- continue;
431
- }
432
- const fs = unwrapAnnotations2(fieldSchema);
433
- switch (fs._kind) {
417
+ schema.fields
418
+ ).sort(([a], [b]) => a.localeCompare(b))) {
419
+ switch (fieldSchema[KIND3]) {
420
+ case "text":
421
+ map.set(key, new Y3.Text());
422
+ break;
434
423
  case "product":
435
424
  map.set(key, ensureMapContainers(fieldSchema));
436
425
  break;
@@ -448,19 +437,19 @@ function ensureMapContainers(schema) {
448
437
  }
449
438
  break;
450
439
  }
440
+ case "counter":
441
+ case "set":
442
+ case "tree":
443
+ case "movable":
444
+ throw new Error(
445
+ `Yjs substrate does not support [KIND]="${fieldSchema[KIND3]}". Supported kinds: text, product, sequence, map, scalar, sum. Encountered unsupported kind at nested field "${key}".`
446
+ );
451
447
  }
452
448
  }
453
449
  return map;
454
450
  }
455
- function unwrapAnnotations2(schema) {
456
- let s = schema;
457
- while (s._kind === "annotated" && s.schema !== void 0) {
458
- s = s.schema;
459
- }
460
- return s;
461
- }
462
451
 
463
- // src/store-reader.ts
452
+ // src/reader.ts
464
453
  import * as Y4 from "yjs";
465
454
  function extractValue(resolved) {
466
455
  if (resolved instanceof Y4.Text) {
@@ -474,7 +463,7 @@ function extractValue(resolved) {
474
463
  }
475
464
  return resolved;
476
465
  }
477
- function yjsStoreReader(doc, schema) {
466
+ function yjsReader(doc, schema) {
478
467
  const rootMap = doc.getMap("root");
479
468
  return {
480
469
  read(path) {
@@ -518,6 +507,7 @@ function yjsStoreReader(doc, schema) {
518
507
  }
519
508
 
520
509
  // src/version.ts
510
+ import { versionVectorMeet } from "@kyneta/schema";
521
511
  import { decodeStateVector } from "yjs";
522
512
  function uint8ArrayToBase64(bytes) {
523
513
  let binary = "";
@@ -534,6 +524,22 @@ function base64ToUint8Array(base64) {
534
524
  }
535
525
  return bytes;
536
526
  }
527
+ function encodeStateVector(map) {
528
+ const bytes = [];
529
+ function writeVarUint(value) {
530
+ while (value > 127) {
531
+ bytes.push(value & 127 | 128);
532
+ value >>>= 7;
533
+ }
534
+ bytes.push(value & 127);
535
+ }
536
+ writeVarUint(map.size);
537
+ for (const [clientId, clock] of map) {
538
+ writeVarUint(clientId);
539
+ writeVarUint(clock);
540
+ }
541
+ return new Uint8Array(bytes);
542
+ }
537
543
  var YjsVersion = class _YjsVersion {
538
544
  sv;
539
545
  constructor(sv) {
@@ -565,9 +571,7 @@ var YjsVersion = class _YjsVersion {
565
571
  */
566
572
  compare(other) {
567
573
  if (!(other instanceof _YjsVersion)) {
568
- throw new Error(
569
- "YjsVersion can only be compared with another YjsVersion"
570
- );
574
+ throw new Error("YjsVersion can only be compared with another YjsVersion");
571
575
  }
572
576
  const thisMap = decodeStateVector(this.sv);
573
577
  const otherMap = decodeStateVector(other.sv);
@@ -593,6 +597,26 @@ var YjsVersion = class _YjsVersion {
593
597
  if (hasGreater && !hasLess) return "ahead";
594
598
  return "equal";
595
599
  }
600
+ /**
601
+ * Greatest lower bound (lattice meet) of two Yjs versions.
602
+ *
603
+ * Decodes both state vectors, computes the component-wise minimum
604
+ * via the shared `versionVectorMeet` utility, and encodes the result
605
+ * back to a Yjs state vector.
606
+ *
607
+ * @throws If `other` is not a `YjsVersion`.
608
+ */
609
+ meet(other) {
610
+ if (!(other instanceof _YjsVersion)) {
611
+ throw new Error(
612
+ "YjsVersion can only be meet'd with another YjsVersion"
613
+ );
614
+ }
615
+ const thisMap = decodeStateVector(this.sv);
616
+ const otherMap = decodeStateVector(other.sv);
617
+ const result = versionVectorMeet(thisMap, otherMap);
618
+ return new _YjsVersion(encodeStateVector(result));
619
+ }
596
620
  /**
597
621
  * Parse a serialized YjsVersion string back into a YjsVersion.
598
622
  *
@@ -607,47 +631,24 @@ var YjsVersion = class _YjsVersion {
607
631
  }
608
632
  };
609
633
 
610
- // src/yjs-escape.ts
611
- import { unwrap } from "@kyneta/schema";
612
- var substrateToYjsDoc = /* @__PURE__ */ new WeakMap();
613
- function registerYjsSubstrate(substrate, doc) {
614
- substrateToYjsDoc.set(substrate, doc);
615
- }
616
- function yjs(ref) {
617
- let substrate;
618
- try {
619
- substrate = unwrap(ref);
620
- } catch {
621
- throw new Error(
622
- "yjs() requires a ref backed by a Yjs substrate. Use a doc created by exchange.get() with a bindYjs() schema, or by createYjsDoc()."
623
- );
624
- }
625
- const doc = substrateToYjsDoc.get(substrate);
626
- if (!doc) {
627
- throw new Error(
628
- "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()."
629
- );
630
- }
631
- return doc;
632
- }
633
-
634
634
  // src/substrate.ts
635
635
  var KYNETA_ORIGIN = "kyneta-prepare";
636
636
  function createYjsSubstrate(doc, schema) {
637
637
  const pendingChanges = [];
638
638
  let inOurTransaction = false;
639
- let pendingImportOrigin;
639
+ let pendingMergeOrigin;
640
640
  let cachedCtx;
641
641
  const rootMap = doc.getMap("root");
642
- const reader = yjsStoreReader(doc, schema);
642
+ const reader = yjsReader(doc, schema);
643
643
  const substrate = {
644
- store: reader,
644
+ [BACKING_DOC]: doc,
645
+ reader,
645
646
  prepare(path, change2) {
646
647
  if (!inOurTransaction) {
647
648
  pendingChanges.push({ path, change: change2 });
648
649
  }
649
650
  },
650
- onFlush(origin) {
651
+ onFlush(_origin) {
651
652
  if (!inOurTransaction && pendingChanges.length > 0) {
652
653
  inOurTransaction = true;
653
654
  try {
@@ -671,8 +672,17 @@ function createYjsSubstrate(doc, schema) {
671
672
  version() {
672
673
  return new YjsVersion(Y5.encodeStateVector(doc));
673
674
  },
674
- exportSnapshot() {
675
+ baseVersion() {
676
+ return new YjsVersion(new Uint8Array([0]));
677
+ },
678
+ advance(_to) {
679
+ throw new Error(
680
+ "advance() on a live Yjs substrate is not yet supported. Use advance() on a YjsReplica instead."
681
+ );
682
+ },
683
+ exportEntirety() {
675
684
  return {
685
+ kind: "entirety",
676
686
  encoding: "binary",
677
687
  data: Y5.encodeStateAsUpdate(doc)
678
688
  };
@@ -680,22 +690,22 @@ function createYjsSubstrate(doc, schema) {
680
690
  exportSince(since) {
681
691
  try {
682
692
  const bytes = Y5.encodeStateAsUpdate(doc, since.sv);
683
- return { encoding: "binary", data: bytes };
693
+ return { kind: "since", encoding: "binary", data: bytes };
684
694
  } catch {
685
695
  return null;
686
696
  }
687
697
  },
688
- importDelta(payload, origin) {
698
+ merge(payload, origin) {
689
699
  if (payload.encoding !== "binary" || !(payload.data instanceof Uint8Array)) {
690
700
  throw new Error(
691
- "YjsSubstrate.importDelta only supports binary-encoded payloads"
701
+ "YjsSubstrate.merge expects binary-encoded payloads. If you recently switched CRDT backends, stale clients may be sending incompatible data."
692
702
  );
693
703
  }
694
- pendingImportOrigin = origin;
704
+ pendingMergeOrigin = origin;
695
705
  try {
696
706
  Y5.applyUpdate(doc, payload.data, origin ?? "remote");
697
707
  } finally {
698
- pendingImportOrigin = void 0;
708
+ pendingMergeOrigin = void 0;
699
709
  }
700
710
  }
701
711
  };
@@ -703,11 +713,11 @@ function createYjsSubstrate(doc, schema) {
703
713
  if (transaction.origin === KYNETA_ORIGIN) {
704
714
  return;
705
715
  }
706
- const ops = eventsToOps(events);
716
+ const ops = eventsToOps(events, schema);
707
717
  if (ops.length === 0) {
708
718
  return;
709
719
  }
710
- const origin = pendingImportOrigin ?? (typeof transaction.origin === "string" ? transaction.origin : void 0);
720
+ const origin = pendingMergeOrigin ?? (typeof transaction.origin === "string" ? transaction.origin : void 0);
711
721
  const ctx = substrate.context();
712
722
  inOurTransaction = true;
713
723
  try {
@@ -716,25 +726,103 @@ function createYjsSubstrate(doc, schema) {
716
726
  inOurTransaction = false;
717
727
  }
718
728
  });
719
- registerYjsSubstrate(substrate, doc);
720
729
  return substrate;
721
730
  }
722
- var yjsSubstrateFactory = {
723
- create(schema) {
724
- const doc = new Y5.Doc();
725
- ensureContainers(doc, schema);
726
- return createYjsSubstrate(doc, schema);
731
+ function createYjsReplica(doc) {
732
+ let currentDoc = doc;
733
+ let currentBase = new YjsVersion(
734
+ Y5.encodeStateVector(new Y5.Doc())
735
+ );
736
+ return {
737
+ get [BACKING_DOC]() {
738
+ return currentDoc;
739
+ },
740
+ version() {
741
+ return new YjsVersion(Y5.encodeStateVector(currentDoc));
742
+ },
743
+ baseVersion() {
744
+ return currentBase;
745
+ },
746
+ advance(to) {
747
+ const baseCmp = currentBase.compare(to);
748
+ if (baseCmp === "ahead") {
749
+ throw new Error("advance(): target is behind base version");
750
+ }
751
+ const currentCmp = to.compare(this.version());
752
+ if (currentCmp === "ahead") {
753
+ throw new Error("advance(): target is ahead of current version");
754
+ }
755
+ if (currentCmp !== "equal") return;
756
+ const update = Y5.encodeStateAsUpdate(currentDoc);
757
+ const newDoc = new Y5.Doc();
758
+ Y5.applyUpdate(newDoc, update);
759
+ currentDoc = newDoc;
760
+ currentBase = new YjsVersion(Y5.encodeStateVector(currentDoc));
761
+ },
762
+ exportEntirety() {
763
+ return {
764
+ kind: "entirety",
765
+ encoding: "binary",
766
+ data: Y5.encodeStateAsUpdate(currentDoc)
767
+ };
768
+ },
769
+ exportSince(since) {
770
+ try {
771
+ const bytes = Y5.encodeStateAsUpdate(currentDoc, since.sv);
772
+ return { kind: "since", encoding: "binary", data: bytes };
773
+ } catch {
774
+ return null;
775
+ }
776
+ },
777
+ merge(payload, _origin) {
778
+ if (payload.encoding !== "binary" || !(payload.data instanceof Uint8Array)) {
779
+ throw new Error(
780
+ "YjsReplica.merge expects binary-encoded payloads. If you recently switched CRDT backends, stale clients may be sending incompatible data."
781
+ );
782
+ }
783
+ Y5.applyUpdate(currentDoc, payload.data);
784
+ }
785
+ };
786
+ }
787
+ var yjsReplicaFactory = {
788
+ replicaType: ["yjs", 1, 0],
789
+ createEmpty() {
790
+ return createYjsReplica(new Y5.Doc());
727
791
  },
728
- fromSnapshot(payload, schema) {
792
+ fromEntirety(payload) {
729
793
  if (payload.encoding !== "binary" || !(payload.data instanceof Uint8Array)) {
730
794
  throw new Error(
731
- "YjsSubstrateFactory.fromSnapshot only supports binary-encoded payloads"
795
+ "YjsReplicaFactory.fromEntirety only supports binary-encoded payloads"
732
796
  );
733
797
  }
734
798
  const doc = new Y5.Doc();
735
799
  Y5.applyUpdate(doc, payload.data);
800
+ return createYjsReplica(doc);
801
+ },
802
+ parseVersion(serialized) {
803
+ return YjsVersion.parse(serialized);
804
+ }
805
+ };
806
+ var yjsSubstrateFactory = {
807
+ replica: yjsReplicaFactory,
808
+ createReplica() {
809
+ return createYjsReplica(new Y5.Doc());
810
+ },
811
+ upgrade(replica, schema) {
812
+ const doc = replica[BACKING_DOC];
813
+ ensureContainers(doc, schema, true);
814
+ return createYjsSubstrate(doc, schema);
815
+ },
816
+ create(schema) {
817
+ const doc = new Y5.Doc();
818
+ ensureContainers(doc, schema);
736
819
  return createYjsSubstrate(doc, schema);
737
820
  },
821
+ fromEntirety(payload, schema) {
822
+ const replica = this.createReplica();
823
+ replica.merge(payload);
824
+ return this.upgrade(replica, schema);
825
+ },
738
826
  parseVersion(serialized) {
739
827
  return YjsVersion.parse(serialized);
740
828
  }
@@ -746,13 +834,13 @@ function getSubstrate(doc) {
746
834
  const s = substrates.get(doc);
747
835
  if (!s) {
748
836
  throw new Error(
749
- "version/exportSnapshot/importDelta called on an object without a YjsSubstrate. Use a doc created by createYjsDoc() or createYjsDocFromSnapshot()."
837
+ "version/exportEntirety/merge called on an object without a YjsSubstrate. Use a doc created by createYjsDoc() or createYjsDocFromEntirety()."
750
838
  );
751
839
  }
752
840
  return s;
753
841
  }
754
842
  function registerDoc(schema, substrate) {
755
- const doc = interpret(schema, substrate.context()).with(readable).with(writable).with(changefeed).done();
843
+ const doc = interpret(schema, substrate.context()).with(readable).with(writable).with(observation).done();
756
844
  substrates.set(doc, substrate);
757
845
  registerSubstrate(doc, substrate);
758
846
  return doc;
@@ -767,30 +855,29 @@ var createYjsDoc = (schema, doc) => {
767
855
  }
768
856
  return registerDoc(schema, yjsSubstrateFactory.create(schema));
769
857
  };
770
- var createYjsDocFromSnapshot = (schema, payload) => registerDoc(schema, yjsSubstrateFactory.fromSnapshot(payload, schema));
858
+ var createYjsDocFromEntirety = (schema, payload) => registerDoc(schema, yjsSubstrateFactory.fromEntirety(payload, schema));
771
859
 
772
860
  // src/sync.ts
773
861
  function version(doc) {
774
862
  return getSubstrate(doc).version();
775
863
  }
776
- function exportSnapshot(doc) {
777
- return getSubstrate(doc).exportSnapshot();
864
+ function exportEntirety(doc) {
865
+ return getSubstrate(doc).exportEntirety();
778
866
  }
779
867
  function exportSince(doc, since) {
780
868
  return getSubstrate(doc).exportSince(since);
781
869
  }
782
- function importDelta(doc, payload, origin) {
783
- getSubstrate(doc).importDelta(payload, origin);
870
+ function merge(doc, payload, origin) {
871
+ getSubstrate(doc).merge(payload, origin);
784
872
  }
785
873
 
786
- // src/index.ts
787
- import { applyChanges, change } from "@kyneta/schema";
788
- import { subscribe, subscribeNode } from "@kyneta/schema";
789
- import { Schema } from "@kyneta/schema";
790
- import { Schema as Schema2 } from "@kyneta/schema";
791
-
792
874
  // src/bind-yjs.ts
793
- import { bind } from "@kyneta/schema";
875
+ import {
876
+ BACKING_DOC as BACKING_DOC2,
877
+ createSubstrateNamespace,
878
+ STRUCTURAL_YJS_CLIENT_ID as STRUCTURAL_YJS_CLIENT_ID2,
879
+ unwrap
880
+ } from "@kyneta/schema";
794
881
  import * as Y6 from "yjs";
795
882
  function hashPeerId(peerId) {
796
883
  let hash = 2166136261;
@@ -798,68 +885,92 @@ function hashPeerId(peerId) {
798
885
  hash ^= peerId.charCodeAt(i);
799
886
  hash = Math.imul(hash, 16777619);
800
887
  }
801
- return hash >>> 0;
888
+ const result = hash >>> 0;
889
+ return result === STRUCTURAL_YJS_CLIENT_ID2 ? 1 : result;
802
890
  }
803
891
  function createYjsFactory(peerId) {
804
892
  const numericClientId = hashPeerId(peerId);
805
893
  return {
806
- create(schema) {
807
- const doc = new Y6.Doc();
894
+ replica: yjsReplicaFactory,
895
+ createReplica() {
896
+ return createYjsReplica(new Y6.Doc());
897
+ },
898
+ upgrade(replica, schema) {
899
+ const doc = replica[BACKING_DOC2];
808
900
  doc.clientID = numericClientId;
809
- ensureContainers(doc, schema);
901
+ ensureContainers(doc, schema, true);
810
902
  return createYjsSubstrate(doc, schema);
811
903
  },
812
- fromSnapshot(payload, schema) {
813
- if (payload.encoding !== "binary" || !(payload.data instanceof Uint8Array)) {
814
- throw new Error(
815
- "YjsSubstrateFactory.fromSnapshot only supports binary-encoded payloads"
816
- );
817
- }
904
+ create(schema) {
818
905
  const doc = new Y6.Doc();
819
906
  doc.clientID = numericClientId;
820
- Y6.applyUpdate(doc, payload.data);
907
+ ensureContainers(doc, schema);
821
908
  return createYjsSubstrate(doc, schema);
822
909
  },
910
+ fromEntirety(payload, schema) {
911
+ const replica = this.createReplica();
912
+ replica.merge(payload);
913
+ return this.upgrade(replica, schema);
914
+ },
823
915
  parseVersion(serialized) {
824
916
  return YjsVersion.parse(serialized);
825
917
  }
826
918
  };
827
919
  }
828
- function bindYjs(schema) {
829
- return bind({
830
- schema,
831
- factory: (ctx) => createYjsFactory(ctx.peerId),
832
- strategy: "causal"
833
- });
834
- }
835
-
836
- // src/index.ts
837
- function text() {
838
- return Schema2.annotated("text");
839
- }
920
+ var yjs = {
921
+ ...createSubstrateNamespace({
922
+ strategies: {
923
+ collaborative: {
924
+ factory: (ctx) => createYjsFactory(ctx.peerId),
925
+ replicaFactory: yjsReplicaFactory
926
+ },
927
+ ephemeral: {
928
+ factory: (ctx) => createYjsFactory(ctx.peerId),
929
+ replicaFactory: yjsReplicaFactory
930
+ }
931
+ },
932
+ defaultStrategy: "collaborative"
933
+ }),
934
+ unwrap(ref) {
935
+ let substrate;
936
+ try {
937
+ substrate = unwrap(ref);
938
+ } catch {
939
+ throw new Error(
940
+ "yjs.unwrap() requires a ref backed by a Yjs substrate. Use a doc created by exchange.get() with a yjs.bind() schema, or by createYjsDoc()."
941
+ );
942
+ }
943
+ const doc = substrate[BACKING_DOC2];
944
+ if (!doc || typeof doc !== "object" || typeof doc.getMap !== "function" || typeof doc.clientID !== "number") {
945
+ throw new Error(
946
+ "yjs.unwrap() 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 yjs.bind() schema or createYjsDoc()."
947
+ );
948
+ }
949
+ return doc;
950
+ }
951
+ };
840
952
  export {
841
953
  Schema,
842
954
  YjsVersion,
843
955
  applyChangeToYjs,
844
956
  applyChanges,
845
- bindYjs,
846
957
  change,
847
958
  createYjsDoc,
848
- createYjsDocFromSnapshot,
959
+ createYjsDocFromEntirety,
849
960
  createYjsSubstrate,
850
961
  ensureContainers,
851
962
  eventsToOps,
963
+ exportEntirety,
852
964
  exportSince,
853
- exportSnapshot,
854
- importDelta,
965
+ merge,
855
966
  resolveYjsType,
856
967
  stepIntoYjs,
857
968
  subscribe,
858
969
  subscribeNode,
859
- text,
860
970
  version,
861
971
  yjs,
862
- yjsStoreReader,
972
+ yjsReader,
973
+ yjsReplicaFactory,
863
974
  yjsSubstrateFactory
864
975
  };
865
976
  //# sourceMappingURL=index.js.map