@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
batch,
|
|
3
3
|
createRef,
|
|
4
4
|
deriveIdentity,
|
|
5
5
|
exportEntirety,
|
|
@@ -59,7 +59,7 @@ describe("yjs.bind", () => {
|
|
|
59
59
|
const bound = yjs.bind(TodoSchema)
|
|
60
60
|
expect(bound._brand).toBe("BoundSchema")
|
|
61
61
|
expect(bound.schema).toBe(TodoSchema)
|
|
62
|
-
expect(bound.
|
|
62
|
+
expect(bound.syncMode).toEqual(SYNC_COLLABORATIVE)
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
it("has a factory builder function", () => {
|
|
@@ -89,7 +89,7 @@ describe("yjs.bind", () => {
|
|
|
89
89
|
|
|
90
90
|
// Populate via the substrate's writable context
|
|
91
91
|
const doc = createYjsDocFromFactory(factory, SimpleSchema)
|
|
92
|
-
|
|
92
|
+
batch(doc, (d: any) => {
|
|
93
93
|
d.title.insert(0, "Test")
|
|
94
94
|
d.count.set(7)
|
|
95
95
|
})
|
|
@@ -107,7 +107,7 @@ describe("yjs.bind", () => {
|
|
|
107
107
|
|
|
108
108
|
// Create and populate
|
|
109
109
|
const doc1 = createYjsDocFromFactory(factory, SimpleSchema)
|
|
110
|
-
|
|
110
|
+
batch(doc1, (d: any) => {
|
|
111
111
|
d.title.insert(0, "Snap")
|
|
112
112
|
d.count.set(42)
|
|
113
113
|
})
|
|
@@ -230,7 +230,7 @@ describe("yjs.bind", () => {
|
|
|
230
230
|
describe("unwrap() escape hatch", () => {
|
|
231
231
|
it("returns the underlying Y.Doc from a createDoc ref", () => {
|
|
232
232
|
const doc = createDoc(yjs.bind(SimpleSchema))
|
|
233
|
-
|
|
233
|
+
batch(doc, (d: any) => {
|
|
234
234
|
d.title.insert(0, "Escape")
|
|
235
235
|
d.count.set(0)
|
|
236
236
|
})
|
|
@@ -242,7 +242,7 @@ describe("yjs.bind", () => {
|
|
|
242
242
|
|
|
243
243
|
it("returns a Y.Doc with the correct root map state", () => {
|
|
244
244
|
const doc = createDoc(yjs.bind(SimpleSchema))
|
|
245
|
-
|
|
245
|
+
batch(doc, (d: any) => {
|
|
246
246
|
d.title.insert(0, "Hello")
|
|
247
247
|
d.count.set(42)
|
|
248
248
|
})
|
|
@@ -281,7 +281,7 @@ describe("yjs.bind", () => {
|
|
|
281
281
|
|
|
282
282
|
it("text mutations through escape hatch are visible", () => {
|
|
283
283
|
const doc = createDoc(yjs.bind(SimpleSchema))
|
|
284
|
-
|
|
284
|
+
batch(doc, (d: any) => {
|
|
285
285
|
d.title.insert(0, "Hello")
|
|
286
286
|
})
|
|
287
287
|
const yjsDoc = unwrap(doc) as Y.Doc
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
batch,
|
|
3
3
|
createDoc,
|
|
4
4
|
createRef,
|
|
5
5
|
Schema,
|
|
@@ -93,15 +93,15 @@ describe("createDoc", () => {
|
|
|
93
93
|
describe("with seeds", () => {
|
|
94
94
|
it("creates a doc with scalar seed values", () => {
|
|
95
95
|
const doc = createDoc(boundSimple)
|
|
96
|
-
|
|
96
|
+
batch(doc, (d: any) => {
|
|
97
97
|
d.title.insert(0, "Hello")
|
|
98
98
|
d.count.set(42)
|
|
99
99
|
})
|
|
100
|
-
// Separate
|
|
100
|
+
// Separate batch() calls for list pushes to preserve order
|
|
101
101
|
// (Yjs reverses order within a single transaction)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
batch(doc, (d: any) => d.items.push("a"))
|
|
103
|
+
batch(doc, (d: any) => d.items.push("b"))
|
|
104
|
+
batch(doc, (d: any) => d.items.push("c"))
|
|
105
105
|
expect(doc.title()).toBe("Hello")
|
|
106
106
|
expect(doc.count()).toBe(42)
|
|
107
107
|
expect(doc.items()).toEqual(["a", "b", "c"])
|
|
@@ -109,7 +109,7 @@ describe("createDoc", () => {
|
|
|
109
109
|
|
|
110
110
|
it("creates a doc with partial seed (defaults fill gaps)", () => {
|
|
111
111
|
const doc = createDoc(boundSimple)
|
|
112
|
-
|
|
112
|
+
batch(doc, (d: any) => {
|
|
113
113
|
d.title.insert(0, "Partial")
|
|
114
114
|
})
|
|
115
115
|
expect(doc.title()).toBe("Partial")
|
|
@@ -119,7 +119,7 @@ describe("createDoc", () => {
|
|
|
119
119
|
|
|
120
120
|
it("creates a doc with nested struct seed", () => {
|
|
121
121
|
const doc = createDoc(boundNested)
|
|
122
|
-
|
|
122
|
+
batch(doc, (d: any) => {
|
|
123
123
|
d.title.insert(0, "Doc")
|
|
124
124
|
d.meta.author.set("Alice")
|
|
125
125
|
d.meta.tags.push("draft")
|
|
@@ -134,9 +134,9 @@ describe("createDoc", () => {
|
|
|
134
134
|
|
|
135
135
|
it("creates a doc with struct list seed items", () => {
|
|
136
136
|
const doc = createDoc(boundStructList)
|
|
137
|
-
// Separate
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
// Separate batch() calls for list pushes to preserve order
|
|
138
|
+
batch(doc, (d: any) => d.tasks.push({ name: "Task 1", done: false }))
|
|
139
|
+
batch(doc, (d: any) => d.tasks.push({ name: "Task 2", done: true }))
|
|
140
140
|
expect(doc.tasks.length).toBe(2)
|
|
141
141
|
expect(doc.tasks.at(0)?.name()).toBe("Task 1")
|
|
142
142
|
expect(doc.tasks.at(0)?.done()).toBe(false)
|
|
@@ -177,7 +177,7 @@ describe("createDoc", () => {
|
|
|
177
177
|
SimpleSchema,
|
|
178
178
|
createYjsSubstrate(yjsDoc, SimpleSchema),
|
|
179
179
|
)
|
|
180
|
-
|
|
180
|
+
batch(doc, (d: any) => {
|
|
181
181
|
d.title.insert(0, "Hello")
|
|
182
182
|
d.count.set(42)
|
|
183
183
|
})
|
|
@@ -221,15 +221,26 @@ describe("createDoc", () => {
|
|
|
221
221
|
// createDoc with payload (fromEntirety)
|
|
222
222
|
// ===========================================================================
|
|
223
223
|
|
|
224
|
+
describe("root document replacement", () => {
|
|
225
|
+
it("throws an actionable error when attempting to replace the root struct", () => {
|
|
226
|
+
const doc = createDoc(boundSimple)
|
|
227
|
+
expect(() => {
|
|
228
|
+
batch(doc, (d: any) => {
|
|
229
|
+
d.set({ title: "New", count: 1, items: [] })
|
|
230
|
+
})
|
|
231
|
+
}).toThrowError(/Cannot replace the root document struct/)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
224
235
|
describe("createDoc with payload", () => {
|
|
225
236
|
it("reconstructs state from a snapshot", () => {
|
|
226
237
|
const doc1 = createDoc(boundSimple)
|
|
227
|
-
|
|
238
|
+
batch(doc1, (d: any) => {
|
|
228
239
|
d.title.insert(0, "Snapshot")
|
|
229
240
|
d.count.set(42)
|
|
230
241
|
})
|
|
231
|
-
|
|
232
|
-
|
|
242
|
+
batch(doc1, (d: any) => d.items.push("a"))
|
|
243
|
+
batch(doc1, (d: any) => d.items.push("b"))
|
|
233
244
|
|
|
234
245
|
const payload = exportEntirety(doc1)
|
|
235
246
|
const doc2 = createDoc(boundSimple, payload)
|
|
@@ -241,11 +252,11 @@ describe("createDoc with payload", () => {
|
|
|
241
252
|
|
|
242
253
|
it("reconstructs state after mutations", () => {
|
|
243
254
|
const doc1 = createDoc(boundSimple)
|
|
244
|
-
|
|
255
|
+
batch(doc1, (d: any) => {
|
|
245
256
|
d.title.insert(0, "Start")
|
|
246
257
|
})
|
|
247
258
|
|
|
248
|
-
|
|
259
|
+
batch(doc1, (d: any) => {
|
|
249
260
|
d.title.insert(5, " End")
|
|
250
261
|
d.count.set(99)
|
|
251
262
|
d.items.push("x")
|
|
@@ -261,13 +272,13 @@ describe("createDoc with payload", () => {
|
|
|
261
272
|
|
|
262
273
|
it("reconstructs nested struct state from snapshot", () => {
|
|
263
274
|
const doc1 = createDoc(boundNested)
|
|
264
|
-
|
|
275
|
+
batch(doc1, (d: any) => {
|
|
265
276
|
d.title.insert(0, "Nested")
|
|
266
277
|
d.meta.author.set("Alice")
|
|
267
278
|
d.labels.set("bug", "red")
|
|
268
279
|
})
|
|
269
|
-
|
|
270
|
-
|
|
280
|
+
batch(doc1, (d: any) => d.meta.tags.push("v1"))
|
|
281
|
+
batch(doc1, (d: any) => d.meta.tags.push("v2"))
|
|
271
282
|
|
|
272
283
|
const payload = exportEntirety(doc1)
|
|
273
284
|
const doc2 = createDoc(boundNested, payload)
|
|
@@ -281,8 +292,8 @@ describe("createDoc with payload", () => {
|
|
|
281
292
|
|
|
282
293
|
it("reconstructs struct list state from snapshot", () => {
|
|
283
294
|
const doc1 = createDoc(boundStructList)
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
batch(doc1, (d: any) => d.tasks.push({ name: "Task A", done: false }))
|
|
296
|
+
batch(doc1, (d: any) => d.tasks.push({ name: "Task B", done: true }))
|
|
286
297
|
|
|
287
298
|
const payload = exportEntirety(doc1)
|
|
288
299
|
const doc2 = createDoc(boundStructList, payload)
|
|
@@ -294,13 +305,13 @@ describe("createDoc with payload", () => {
|
|
|
294
305
|
|
|
295
306
|
it("is writable after reconstruction", () => {
|
|
296
307
|
const doc1 = createDoc(boundSimple)
|
|
297
|
-
|
|
308
|
+
batch(doc1, (d: any) => {
|
|
298
309
|
d.title.insert(0, "Original")
|
|
299
310
|
})
|
|
300
311
|
const payload = exportEntirety(doc1)
|
|
301
312
|
const doc2 = createDoc(boundSimple, payload)
|
|
302
313
|
|
|
303
|
-
|
|
314
|
+
batch(doc2, (d: any) => {
|
|
304
315
|
d.title.insert(8, " Copy")
|
|
305
316
|
d.count.set(7)
|
|
306
317
|
})
|
|
@@ -311,7 +322,7 @@ describe("createDoc with payload", () => {
|
|
|
311
322
|
|
|
312
323
|
it("is observable after reconstruction", () => {
|
|
313
324
|
const doc1 = createDoc(boundSimple)
|
|
314
|
-
|
|
325
|
+
batch(doc1, (d: any) => {
|
|
315
326
|
d.title.insert(0, "Original")
|
|
316
327
|
})
|
|
317
328
|
const payload = exportEntirety(doc1)
|
|
@@ -322,7 +333,7 @@ describe("createDoc with payload", () => {
|
|
|
322
333
|
received.push(changeset)
|
|
323
334
|
})
|
|
324
335
|
|
|
325
|
-
|
|
336
|
+
batch(doc2, (d: any) => {
|
|
326
337
|
d.count.set(42)
|
|
327
338
|
})
|
|
328
339
|
|
|
@@ -346,7 +357,7 @@ describe("sync primitives", () => {
|
|
|
346
357
|
const doc = createDoc(boundSimple)
|
|
347
358
|
const v1 = version(doc)
|
|
348
359
|
|
|
349
|
-
|
|
360
|
+
batch(doc, (d: any) => {
|
|
350
361
|
d.count.set(1)
|
|
351
362
|
})
|
|
352
363
|
const v2 = version(doc)
|
|
@@ -356,7 +367,7 @@ describe("sync primitives", () => {
|
|
|
356
367
|
|
|
357
368
|
it("serialize/parse round-trips", () => {
|
|
358
369
|
const doc = createDoc(boundSimple)
|
|
359
|
-
|
|
370
|
+
batch(doc, (d: any) => {
|
|
360
371
|
d.title.insert(0, "Test")
|
|
361
372
|
})
|
|
362
373
|
const v = version(doc)
|
|
@@ -369,7 +380,7 @@ describe("sync primitives", () => {
|
|
|
369
380
|
describe("exportEntirety", () => {
|
|
370
381
|
it("returns a binary payload", () => {
|
|
371
382
|
const doc = createDoc(boundSimple)
|
|
372
|
-
|
|
383
|
+
batch(doc, (d: any) => {
|
|
373
384
|
d.title.insert(0, "Snap")
|
|
374
385
|
})
|
|
375
386
|
const payload = exportEntirety(doc)
|
|
@@ -382,7 +393,7 @@ describe("sync primitives", () => {
|
|
|
382
393
|
describe("exportSince + merge", () => {
|
|
383
394
|
it("syncs incremental changes between two docs", () => {
|
|
384
395
|
const doc1 = createDoc(boundSimple)
|
|
385
|
-
|
|
396
|
+
batch(doc1, (d: any) => {
|
|
386
397
|
d.title.insert(0, "Start")
|
|
387
398
|
})
|
|
388
399
|
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
@@ -390,7 +401,7 @@ describe("sync primitives", () => {
|
|
|
390
401
|
const v2Before = version(doc2)
|
|
391
402
|
|
|
392
403
|
// Mutate doc1
|
|
393
|
-
|
|
404
|
+
batch(doc1, (d: any) => {
|
|
394
405
|
d.title.insert(5, " Edited")
|
|
395
406
|
d.count.set(42)
|
|
396
407
|
d.items.push("new-item")
|
|
@@ -414,21 +425,21 @@ describe("sync primitives", () => {
|
|
|
414
425
|
|
|
415
426
|
// First round
|
|
416
427
|
let vBefore = version(doc2)
|
|
417
|
-
|
|
428
|
+
batch(doc1, (d: any) => {
|
|
418
429
|
d.title.insert(0, "A")
|
|
419
430
|
})
|
|
420
431
|
merge(doc2, exportSince(doc1, vBefore)!)
|
|
421
432
|
|
|
422
433
|
// Second round
|
|
423
434
|
vBefore = version(doc2)
|
|
424
|
-
|
|
435
|
+
batch(doc1, (d: any) => {
|
|
425
436
|
d.title.insert(1, "B")
|
|
426
437
|
})
|
|
427
438
|
merge(doc2, exportSince(doc1, vBefore)!)
|
|
428
439
|
|
|
429
440
|
// Third round
|
|
430
441
|
vBefore = version(doc2)
|
|
431
|
-
|
|
442
|
+
batch(doc1, (d: any) => {
|
|
432
443
|
d.count.set(3)
|
|
433
444
|
})
|
|
434
445
|
merge(doc2, exportSince(doc1, vBefore)!)
|
|
@@ -439,14 +450,14 @@ describe("sync primitives", () => {
|
|
|
439
450
|
|
|
440
451
|
it("changefeed fires on merge", () => {
|
|
441
452
|
const doc1 = createDoc(boundSimple)
|
|
442
|
-
|
|
453
|
+
batch(doc1, (d: any) => {
|
|
443
454
|
d.title.insert(0, "Source")
|
|
444
455
|
})
|
|
445
456
|
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
446
457
|
|
|
447
458
|
const v2Before = version(doc2)
|
|
448
459
|
|
|
449
|
-
|
|
460
|
+
batch(doc1, (d: any) => {
|
|
450
461
|
d.count.set(77)
|
|
451
462
|
})
|
|
452
463
|
|
|
@@ -468,7 +479,7 @@ describe("sync primitives", () => {
|
|
|
468
479
|
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
469
480
|
|
|
470
481
|
const v2Before = version(doc2)
|
|
471
|
-
|
|
482
|
+
batch(doc1, (d: any) => {
|
|
472
483
|
d.count.set(1)
|
|
473
484
|
})
|
|
474
485
|
|
|
@@ -486,10 +497,10 @@ describe("sync primitives", () => {
|
|
|
486
497
|
describe("versions equal after sync", () => {
|
|
487
498
|
it("versions equal after full snapshot sync", () => {
|
|
488
499
|
const doc1 = createDoc(boundSimple)
|
|
489
|
-
|
|
500
|
+
batch(doc1, (d: any) => {
|
|
490
501
|
d.title.insert(0, "Same")
|
|
491
502
|
})
|
|
492
|
-
|
|
503
|
+
batch(doc1, (d: any) => {
|
|
493
504
|
d.count.set(42)
|
|
494
505
|
})
|
|
495
506
|
|
|
@@ -506,10 +517,10 @@ describe("sync primitives", () => {
|
|
|
506
517
|
const v2Before = version(doc2)
|
|
507
518
|
|
|
508
519
|
// Independent mutations
|
|
509
|
-
|
|
520
|
+
batch(doc1, (d: any) => {
|
|
510
521
|
d.title.insert(0, "A")
|
|
511
522
|
})
|
|
512
|
-
|
|
523
|
+
batch(doc2, (d: any) => {
|
|
513
524
|
d.count.set(7)
|
|
514
525
|
})
|
|
515
526
|
|
|
@@ -543,11 +554,11 @@ describe("full workflow", () => {
|
|
|
543
554
|
// 3. Mutate doc1
|
|
544
555
|
const vBefore = version(doc2)
|
|
545
556
|
|
|
546
|
-
|
|
557
|
+
batch(doc1, (d: any) => {
|
|
547
558
|
d.tasks.push({ name: "Buy milk", done: false })
|
|
548
559
|
})
|
|
549
560
|
|
|
550
|
-
|
|
561
|
+
batch(doc1, (d: any) => {
|
|
551
562
|
d.tasks.push({ name: "Walk dog", done: false })
|
|
552
563
|
})
|
|
553
564
|
|
|
@@ -569,7 +580,7 @@ describe("full workflow", () => {
|
|
|
569
580
|
// 8. Mutate doc2 and sync back
|
|
570
581
|
const v1Before = version(doc1)
|
|
571
582
|
|
|
572
|
-
|
|
583
|
+
batch(doc2, (d: any) => {
|
|
573
584
|
d.tasks.push({ name: "Read book", done: false })
|
|
574
585
|
})
|
|
575
586
|
|
|
@@ -584,10 +595,10 @@ describe("full workflow", () => {
|
|
|
584
595
|
it("create → mutate → snapshot → reconstruct → continue", () => {
|
|
585
596
|
// 1. Create and mutate
|
|
586
597
|
const doc1 = createDoc(boundSimple)
|
|
587
|
-
|
|
598
|
+
batch(doc1, (d: any) => {
|
|
588
599
|
d.title.insert(0, "Start")
|
|
589
600
|
})
|
|
590
|
-
|
|
601
|
+
batch(doc1, (d: any) => {
|
|
591
602
|
d.title.insert(5, " Middle")
|
|
592
603
|
d.count.set(10)
|
|
593
604
|
d.items.push("first")
|
|
@@ -603,7 +614,7 @@ describe("full workflow", () => {
|
|
|
603
614
|
expect(doc2.items()).toEqual(["first"])
|
|
604
615
|
|
|
605
616
|
// 4. Continue mutating the reconstructed doc
|
|
606
|
-
|
|
617
|
+
batch(doc2, (d: any) => {
|
|
607
618
|
d.title.insert(12, " End")
|
|
608
619
|
d.count.set(20)
|
|
609
620
|
d.items.push("second")
|
|
@@ -629,11 +640,11 @@ describe("full workflow", () => {
|
|
|
629
640
|
const v2Before = version(doc2)
|
|
630
641
|
|
|
631
642
|
// 2. Both peers edit concurrently
|
|
632
|
-
|
|
643
|
+
batch(doc1, (d: any) => {
|
|
633
644
|
d.title.insert(0, "Peer1")
|
|
634
645
|
d.items.push("from-1")
|
|
635
646
|
})
|
|
636
|
-
|
|
647
|
+
batch(doc2, (d: any) => {
|
|
637
648
|
d.count.set(42)
|
|
638
649
|
d.items.push("from-2")
|
|
639
650
|
})
|