@kyneta/yjs-schema 1.8.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 +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- 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 +50 -50
- package/src/__tests__/eager-write-coherence.test.ts +21 -21
- 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 +53 -53
- package/src/bind-yjs.ts +1 -1
- package/src/index.ts +1 -1
- package/src/substrate.ts +36 -4
|
@@ -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
|
})
|
|
@@ -225,7 +225,7 @@ describe("root document replacement", () => {
|
|
|
225
225
|
it("throws an actionable error when attempting to replace the root struct", () => {
|
|
226
226
|
const doc = createDoc(boundSimple)
|
|
227
227
|
expect(() => {
|
|
228
|
-
|
|
228
|
+
batch(doc, (d: any) => {
|
|
229
229
|
d.set({ title: "New", count: 1, items: [] })
|
|
230
230
|
})
|
|
231
231
|
}).toThrowError(/Cannot replace the root document struct/)
|
|
@@ -235,12 +235,12 @@ describe("root document replacement", () => {
|
|
|
235
235
|
describe("createDoc with payload", () => {
|
|
236
236
|
it("reconstructs state from a snapshot", () => {
|
|
237
237
|
const doc1 = createDoc(boundSimple)
|
|
238
|
-
|
|
238
|
+
batch(doc1, (d: any) => {
|
|
239
239
|
d.title.insert(0, "Snapshot")
|
|
240
240
|
d.count.set(42)
|
|
241
241
|
})
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
batch(doc1, (d: any) => d.items.push("a"))
|
|
243
|
+
batch(doc1, (d: any) => d.items.push("b"))
|
|
244
244
|
|
|
245
245
|
const payload = exportEntirety(doc1)
|
|
246
246
|
const doc2 = createDoc(boundSimple, payload)
|
|
@@ -252,11 +252,11 @@ describe("createDoc with payload", () => {
|
|
|
252
252
|
|
|
253
253
|
it("reconstructs state after mutations", () => {
|
|
254
254
|
const doc1 = createDoc(boundSimple)
|
|
255
|
-
|
|
255
|
+
batch(doc1, (d: any) => {
|
|
256
256
|
d.title.insert(0, "Start")
|
|
257
257
|
})
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
batch(doc1, (d: any) => {
|
|
260
260
|
d.title.insert(5, " End")
|
|
261
261
|
d.count.set(99)
|
|
262
262
|
d.items.push("x")
|
|
@@ -272,13 +272,13 @@ describe("createDoc with payload", () => {
|
|
|
272
272
|
|
|
273
273
|
it("reconstructs nested struct state from snapshot", () => {
|
|
274
274
|
const doc1 = createDoc(boundNested)
|
|
275
|
-
|
|
275
|
+
batch(doc1, (d: any) => {
|
|
276
276
|
d.title.insert(0, "Nested")
|
|
277
277
|
d.meta.author.set("Alice")
|
|
278
278
|
d.labels.set("bug", "red")
|
|
279
279
|
})
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
batch(doc1, (d: any) => d.meta.tags.push("v1"))
|
|
281
|
+
batch(doc1, (d: any) => d.meta.tags.push("v2"))
|
|
282
282
|
|
|
283
283
|
const payload = exportEntirety(doc1)
|
|
284
284
|
const doc2 = createDoc(boundNested, payload)
|
|
@@ -292,8 +292,8 @@ describe("createDoc with payload", () => {
|
|
|
292
292
|
|
|
293
293
|
it("reconstructs struct list state from snapshot", () => {
|
|
294
294
|
const doc1 = createDoc(boundStructList)
|
|
295
|
-
|
|
296
|
-
|
|
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 }))
|
|
297
297
|
|
|
298
298
|
const payload = exportEntirety(doc1)
|
|
299
299
|
const doc2 = createDoc(boundStructList, payload)
|
|
@@ -305,13 +305,13 @@ describe("createDoc with payload", () => {
|
|
|
305
305
|
|
|
306
306
|
it("is writable after reconstruction", () => {
|
|
307
307
|
const doc1 = createDoc(boundSimple)
|
|
308
|
-
|
|
308
|
+
batch(doc1, (d: any) => {
|
|
309
309
|
d.title.insert(0, "Original")
|
|
310
310
|
})
|
|
311
311
|
const payload = exportEntirety(doc1)
|
|
312
312
|
const doc2 = createDoc(boundSimple, payload)
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
batch(doc2, (d: any) => {
|
|
315
315
|
d.title.insert(8, " Copy")
|
|
316
316
|
d.count.set(7)
|
|
317
317
|
})
|
|
@@ -322,7 +322,7 @@ describe("createDoc with payload", () => {
|
|
|
322
322
|
|
|
323
323
|
it("is observable after reconstruction", () => {
|
|
324
324
|
const doc1 = createDoc(boundSimple)
|
|
325
|
-
|
|
325
|
+
batch(doc1, (d: any) => {
|
|
326
326
|
d.title.insert(0, "Original")
|
|
327
327
|
})
|
|
328
328
|
const payload = exportEntirety(doc1)
|
|
@@ -333,7 +333,7 @@ describe("createDoc with payload", () => {
|
|
|
333
333
|
received.push(changeset)
|
|
334
334
|
})
|
|
335
335
|
|
|
336
|
-
|
|
336
|
+
batch(doc2, (d: any) => {
|
|
337
337
|
d.count.set(42)
|
|
338
338
|
})
|
|
339
339
|
|
|
@@ -357,7 +357,7 @@ describe("sync primitives", () => {
|
|
|
357
357
|
const doc = createDoc(boundSimple)
|
|
358
358
|
const v1 = version(doc)
|
|
359
359
|
|
|
360
|
-
|
|
360
|
+
batch(doc, (d: any) => {
|
|
361
361
|
d.count.set(1)
|
|
362
362
|
})
|
|
363
363
|
const v2 = version(doc)
|
|
@@ -367,7 +367,7 @@ describe("sync primitives", () => {
|
|
|
367
367
|
|
|
368
368
|
it("serialize/parse round-trips", () => {
|
|
369
369
|
const doc = createDoc(boundSimple)
|
|
370
|
-
|
|
370
|
+
batch(doc, (d: any) => {
|
|
371
371
|
d.title.insert(0, "Test")
|
|
372
372
|
})
|
|
373
373
|
const v = version(doc)
|
|
@@ -380,7 +380,7 @@ describe("sync primitives", () => {
|
|
|
380
380
|
describe("exportEntirety", () => {
|
|
381
381
|
it("returns a binary payload", () => {
|
|
382
382
|
const doc = createDoc(boundSimple)
|
|
383
|
-
|
|
383
|
+
batch(doc, (d: any) => {
|
|
384
384
|
d.title.insert(0, "Snap")
|
|
385
385
|
})
|
|
386
386
|
const payload = exportEntirety(doc)
|
|
@@ -393,7 +393,7 @@ describe("sync primitives", () => {
|
|
|
393
393
|
describe("exportSince + merge", () => {
|
|
394
394
|
it("syncs incremental changes between two docs", () => {
|
|
395
395
|
const doc1 = createDoc(boundSimple)
|
|
396
|
-
|
|
396
|
+
batch(doc1, (d: any) => {
|
|
397
397
|
d.title.insert(0, "Start")
|
|
398
398
|
})
|
|
399
399
|
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
@@ -401,7 +401,7 @@ describe("sync primitives", () => {
|
|
|
401
401
|
const v2Before = version(doc2)
|
|
402
402
|
|
|
403
403
|
// Mutate doc1
|
|
404
|
-
|
|
404
|
+
batch(doc1, (d: any) => {
|
|
405
405
|
d.title.insert(5, " Edited")
|
|
406
406
|
d.count.set(42)
|
|
407
407
|
d.items.push("new-item")
|
|
@@ -425,21 +425,21 @@ describe("sync primitives", () => {
|
|
|
425
425
|
|
|
426
426
|
// First round
|
|
427
427
|
let vBefore = version(doc2)
|
|
428
|
-
|
|
428
|
+
batch(doc1, (d: any) => {
|
|
429
429
|
d.title.insert(0, "A")
|
|
430
430
|
})
|
|
431
431
|
merge(doc2, exportSince(doc1, vBefore)!)
|
|
432
432
|
|
|
433
433
|
// Second round
|
|
434
434
|
vBefore = version(doc2)
|
|
435
|
-
|
|
435
|
+
batch(doc1, (d: any) => {
|
|
436
436
|
d.title.insert(1, "B")
|
|
437
437
|
})
|
|
438
438
|
merge(doc2, exportSince(doc1, vBefore)!)
|
|
439
439
|
|
|
440
440
|
// Third round
|
|
441
441
|
vBefore = version(doc2)
|
|
442
|
-
|
|
442
|
+
batch(doc1, (d: any) => {
|
|
443
443
|
d.count.set(3)
|
|
444
444
|
})
|
|
445
445
|
merge(doc2, exportSince(doc1, vBefore)!)
|
|
@@ -450,14 +450,14 @@ describe("sync primitives", () => {
|
|
|
450
450
|
|
|
451
451
|
it("changefeed fires on merge", () => {
|
|
452
452
|
const doc1 = createDoc(boundSimple)
|
|
453
|
-
|
|
453
|
+
batch(doc1, (d: any) => {
|
|
454
454
|
d.title.insert(0, "Source")
|
|
455
455
|
})
|
|
456
456
|
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
457
457
|
|
|
458
458
|
const v2Before = version(doc2)
|
|
459
459
|
|
|
460
|
-
|
|
460
|
+
batch(doc1, (d: any) => {
|
|
461
461
|
d.count.set(77)
|
|
462
462
|
})
|
|
463
463
|
|
|
@@ -479,7 +479,7 @@ describe("sync primitives", () => {
|
|
|
479
479
|
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
480
480
|
|
|
481
481
|
const v2Before = version(doc2)
|
|
482
|
-
|
|
482
|
+
batch(doc1, (d: any) => {
|
|
483
483
|
d.count.set(1)
|
|
484
484
|
})
|
|
485
485
|
|
|
@@ -497,10 +497,10 @@ describe("sync primitives", () => {
|
|
|
497
497
|
describe("versions equal after sync", () => {
|
|
498
498
|
it("versions equal after full snapshot sync", () => {
|
|
499
499
|
const doc1 = createDoc(boundSimple)
|
|
500
|
-
|
|
500
|
+
batch(doc1, (d: any) => {
|
|
501
501
|
d.title.insert(0, "Same")
|
|
502
502
|
})
|
|
503
|
-
|
|
503
|
+
batch(doc1, (d: any) => {
|
|
504
504
|
d.count.set(42)
|
|
505
505
|
})
|
|
506
506
|
|
|
@@ -517,10 +517,10 @@ describe("sync primitives", () => {
|
|
|
517
517
|
const v2Before = version(doc2)
|
|
518
518
|
|
|
519
519
|
// Independent mutations
|
|
520
|
-
|
|
520
|
+
batch(doc1, (d: any) => {
|
|
521
521
|
d.title.insert(0, "A")
|
|
522
522
|
})
|
|
523
|
-
|
|
523
|
+
batch(doc2, (d: any) => {
|
|
524
524
|
d.count.set(7)
|
|
525
525
|
})
|
|
526
526
|
|
|
@@ -554,11 +554,11 @@ describe("full workflow", () => {
|
|
|
554
554
|
// 3. Mutate doc1
|
|
555
555
|
const vBefore = version(doc2)
|
|
556
556
|
|
|
557
|
-
|
|
557
|
+
batch(doc1, (d: any) => {
|
|
558
558
|
d.tasks.push({ name: "Buy milk", done: false })
|
|
559
559
|
})
|
|
560
560
|
|
|
561
|
-
|
|
561
|
+
batch(doc1, (d: any) => {
|
|
562
562
|
d.tasks.push({ name: "Walk dog", done: false })
|
|
563
563
|
})
|
|
564
564
|
|
|
@@ -580,7 +580,7 @@ describe("full workflow", () => {
|
|
|
580
580
|
// 8. Mutate doc2 and sync back
|
|
581
581
|
const v1Before = version(doc1)
|
|
582
582
|
|
|
583
|
-
|
|
583
|
+
batch(doc2, (d: any) => {
|
|
584
584
|
d.tasks.push({ name: "Read book", done: false })
|
|
585
585
|
})
|
|
586
586
|
|
|
@@ -595,10 +595,10 @@ describe("full workflow", () => {
|
|
|
595
595
|
it("create → mutate → snapshot → reconstruct → continue", () => {
|
|
596
596
|
// 1. Create and mutate
|
|
597
597
|
const doc1 = createDoc(boundSimple)
|
|
598
|
-
|
|
598
|
+
batch(doc1, (d: any) => {
|
|
599
599
|
d.title.insert(0, "Start")
|
|
600
600
|
})
|
|
601
|
-
|
|
601
|
+
batch(doc1, (d: any) => {
|
|
602
602
|
d.title.insert(5, " Middle")
|
|
603
603
|
d.count.set(10)
|
|
604
604
|
d.items.push("first")
|
|
@@ -614,7 +614,7 @@ describe("full workflow", () => {
|
|
|
614
614
|
expect(doc2.items()).toEqual(["first"])
|
|
615
615
|
|
|
616
616
|
// 4. Continue mutating the reconstructed doc
|
|
617
|
-
|
|
617
|
+
batch(doc2, (d: any) => {
|
|
618
618
|
d.title.insert(12, " End")
|
|
619
619
|
d.count.set(20)
|
|
620
620
|
d.items.push("second")
|
|
@@ -640,11 +640,11 @@ describe("full workflow", () => {
|
|
|
640
640
|
const v2Before = version(doc2)
|
|
641
641
|
|
|
642
642
|
// 2. Both peers edit concurrently
|
|
643
|
-
|
|
643
|
+
batch(doc1, (d: any) => {
|
|
644
644
|
d.title.insert(0, "Peer1")
|
|
645
645
|
d.items.push("from-1")
|
|
646
646
|
})
|
|
647
|
-
|
|
647
|
+
batch(doc2, (d: any) => {
|
|
648
648
|
d.count.set(42)
|
|
649
649
|
d.items.push("from-2")
|
|
650
650
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eager-write-coherence — pins the post-Phase-3 contract for the Yjs
|
|
2
2
|
// substrate's write path:
|
|
3
3
|
//
|
|
4
|
-
// 1. Re-entry: subscriber callbacks may freely `
|
|
4
|
+
// 1. Re-entry: subscriber callbacks may freely `batch()` the doc.
|
|
5
5
|
// Substrate writes land synchronously; both reads (σ via the
|
|
6
6
|
// Reader) AND subsequent writes (λ via applyChangeToYjs) succeed
|
|
7
7
|
// against the new state.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// native `Y.transact` already collapses re-entrant nesting for free.
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
|
-
|
|
20
|
+
batch,
|
|
21
21
|
interpret,
|
|
22
22
|
observation,
|
|
23
23
|
readable,
|
|
@@ -76,16 +76,16 @@ describe("Yjs re-entry: subscriber writes after subscriber push", () => {
|
|
|
76
76
|
|
|
77
77
|
subscribe(doc.events, () => {
|
|
78
78
|
if ((doc.events as any).length !== 1) return
|
|
79
|
-
|
|
79
|
+
batch(doc, (d: any) => {
|
|
80
80
|
d.events.push({ kind: "assistant", body: "" })
|
|
81
81
|
})
|
|
82
|
-
|
|
82
|
+
batch(doc, (d: any) => {
|
|
83
83
|
d.events.at(1).body.set("hello")
|
|
84
84
|
})
|
|
85
85
|
})
|
|
86
86
|
|
|
87
87
|
expect(() => {
|
|
88
|
-
|
|
88
|
+
batch(doc, (d: any) => {
|
|
89
89
|
d.events.push({ kind: "user", body: "hi" })
|
|
90
90
|
})
|
|
91
91
|
}).not.toThrow()
|
|
@@ -103,13 +103,13 @@ describe("Yjs re-entry: subscriber writes after subscriber push", () => {
|
|
|
103
103
|
let observed: string | undefined
|
|
104
104
|
subscribe(doc.items, () => {
|
|
105
105
|
if ((doc.items as any).length !== 1) return
|
|
106
|
-
|
|
106
|
+
batch(doc, (d: any) => {
|
|
107
107
|
d.items.push({ name: "synthesised" })
|
|
108
108
|
})
|
|
109
109
|
observed = (doc.items as any).at(1).name()
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
batch(doc, (d: any) => {
|
|
113
113
|
d.items.push({ name: "user" })
|
|
114
114
|
})
|
|
115
115
|
|
|
@@ -136,15 +136,15 @@ describe("Yjs projection law", () => {
|
|
|
136
136
|
})
|
|
137
137
|
const { doc } = buildUnbound(schema)
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
batch(doc, (d: any) => {
|
|
140
140
|
d.title.insert(0, "Hello")
|
|
141
141
|
d.items.push({ name: "a", done: false })
|
|
142
142
|
})
|
|
143
|
-
|
|
143
|
+
batch(doc, (d: any) => {
|
|
144
144
|
d.items.at(0).done.set(true)
|
|
145
145
|
d.items.push({ name: "b", done: false })
|
|
146
146
|
})
|
|
147
|
-
|
|
147
|
+
batch(doc, (d: any) => {
|
|
148
148
|
d.meta.set({ tags: "kyneta", version: 2 })
|
|
149
149
|
d.peers.set("alice", true)
|
|
150
150
|
d.peers.set("bob", false)
|
|
@@ -179,12 +179,12 @@ describe("Yjs json-boundary storage", () => {
|
|
|
179
179
|
})
|
|
180
180
|
const { doc } = build(schema)
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
batch(doc, (d: any) => {
|
|
183
183
|
d.config.set({ tags: "ci", retries: 3 })
|
|
184
184
|
})
|
|
185
185
|
expect(doc.config()).toEqual({ tags: "ci", retries: 3 })
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
batch(doc, (d: any) => {
|
|
188
188
|
d.config.tags.set("prod")
|
|
189
189
|
})
|
|
190
190
|
expect(doc.config()).toEqual({ tags: "prod", retries: 3 })
|
|
@@ -214,10 +214,10 @@ describe("Yjs json-boundary storage", () => {
|
|
|
214
214
|
})
|
|
215
215
|
const { doc } = build(schema)
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
batch(doc, (d: any) => {
|
|
218
218
|
d.todos.push({ title: "first", done: false })
|
|
219
219
|
})
|
|
220
|
-
|
|
220
|
+
batch(doc, (d: any) => {
|
|
221
221
|
d.todos.push({ title: "second", done: false })
|
|
222
222
|
})
|
|
223
223
|
expect(doc.todos()).toEqual([
|
|
@@ -225,7 +225,7 @@ describe("Yjs json-boundary storage", () => {
|
|
|
225
225
|
{ title: "second", done: false },
|
|
226
226
|
])
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
batch(doc, (d: any) => {
|
|
229
229
|
d.todos.at(0).done.set(true)
|
|
230
230
|
})
|
|
231
231
|
expect(doc.todos()).toEqual([
|
|
@@ -240,7 +240,7 @@ describe("Yjs json-boundary storage", () => {
|
|
|
240
240
|
})
|
|
241
241
|
const { doc } = build(schema)
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
batch(doc, (d: any) => {
|
|
244
244
|
d.profiles.set("alice", { email: "alice@example.com" })
|
|
245
245
|
d.profiles.set("bob", { email: "bob@example.com" })
|
|
246
246
|
})
|
|
@@ -249,7 +249,7 @@ describe("Yjs json-boundary storage", () => {
|
|
|
249
249
|
bob: { email: "bob@example.com" },
|
|
250
250
|
})
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
batch(doc, (d: any) => {
|
|
253
253
|
d.profiles.at("alice").email.set("alice@new.example.com")
|
|
254
254
|
})
|
|
255
255
|
expect(doc.profiles()).toEqual({
|
|
@@ -265,13 +265,13 @@ describe("Yjs json-boundary storage", () => {
|
|
|
265
265
|
// ---------------------------------------------------------------------------
|
|
266
266
|
|
|
267
267
|
describe("Yjs three-primitive substrate (jj:ryquprut)", () => {
|
|
268
|
-
it("multi-push in one
|
|
268
|
+
it("multi-push in one batch() block appends in order against the CRDT", () => {
|
|
269
269
|
const schema = Schema.struct({
|
|
270
270
|
todos: Schema.list(Schema.string()),
|
|
271
271
|
})
|
|
272
272
|
const { doc } = build(schema)
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
batch(doc, (d: any) => {
|
|
275
275
|
d.todos.push("a")
|
|
276
276
|
d.todos.push("b")
|
|
277
277
|
d.todos.push("c")
|
|
@@ -288,7 +288,7 @@ describe("Yjs three-primitive substrate (jj:ryquprut)", () => {
|
|
|
288
288
|
const { doc } = build(schema)
|
|
289
289
|
|
|
290
290
|
expect(() => {
|
|
291
|
-
|
|
291
|
+
batch(doc, (d: any) => {
|
|
292
292
|
d.a.set("set-a")
|
|
293
293
|
d.b.set("set-b")
|
|
294
294
|
throw new Error("abort")
|
|
@@ -309,7 +309,7 @@ describe("Yjs three-primitive substrate (jj:ryquprut)", () => {
|
|
|
309
309
|
})
|
|
310
310
|
|
|
311
311
|
expect(() => {
|
|
312
|
-
|
|
312
|
+
batch(doc, (d: any) => {
|
|
313
313
|
d.a.set("hello")
|
|
314
314
|
throw new Error("abort")
|
|
315
315
|
})
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { ProductSchema, SchemaBinding } from "@kyneta/schema"
|
|
10
10
|
import {
|
|
11
11
|
BACKING_DOC,
|
|
12
|
-
|
|
12
|
+
batch,
|
|
13
13
|
createDoc,
|
|
14
14
|
deriveSchemaBinding,
|
|
15
15
|
KIND,
|
|
@@ -80,7 +80,7 @@ const FullSchema = Schema.struct({
|
|
|
80
80
|
describe("materializeYjsShadow", () => {
|
|
81
81
|
it("materializes text fields", () => {
|
|
82
82
|
const doc = createDoc(yjs.bind(TextSchema))
|
|
83
|
-
|
|
83
|
+
batch(doc, (d: any) => {
|
|
84
84
|
d.title.insert(0, "hello")
|
|
85
85
|
})
|
|
86
86
|
|
|
@@ -93,7 +93,7 @@ describe("materializeYjsShadow", () => {
|
|
|
93
93
|
|
|
94
94
|
it("materializes scalar fields", () => {
|
|
95
95
|
const doc = createDoc(yjs.bind(ScalarSchema))
|
|
96
|
-
|
|
96
|
+
batch(doc, (d: any) => {
|
|
97
97
|
d.name.set("Alice")
|
|
98
98
|
d.age.set(30)
|
|
99
99
|
d.active.set(true)
|
|
@@ -108,11 +108,11 @@ describe("materializeYjsShadow", () => {
|
|
|
108
108
|
|
|
109
109
|
it("materializes sequence fields", () => {
|
|
110
110
|
const doc = createDoc(yjs.bind(SequenceSchema))
|
|
111
|
-
// Separate
|
|
111
|
+
// Separate batch() calls for list pushes to preserve order
|
|
112
112
|
// (Yjs reverses order within a single transaction)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
batch(doc, (d: any) => d.items.push("alpha"))
|
|
114
|
+
batch(doc, (d: any) => d.items.push("beta"))
|
|
115
|
+
batch(doc, (d: any) => d.items.push("gamma"))
|
|
116
116
|
|
|
117
117
|
const yDoc = getYDoc(doc)
|
|
118
118
|
const binding = trivialBinding(SequenceSchema)
|
|
@@ -123,7 +123,7 @@ describe("materializeYjsShadow", () => {
|
|
|
123
123
|
|
|
124
124
|
it("materializes nested struct fields", () => {
|
|
125
125
|
const doc = createDoc(yjs.bind(NestedSchema))
|
|
126
|
-
|
|
126
|
+
batch(doc, (d: any) => {
|
|
127
127
|
d.meta.author.set("Bob")
|
|
128
128
|
d.meta.version.set(42)
|
|
129
129
|
})
|
|
@@ -154,7 +154,7 @@ describe("materializeYjsShadow", () => {
|
|
|
154
154
|
|
|
155
155
|
it("uses raw field names, not identity hashes", () => {
|
|
156
156
|
const doc = createDoc(yjs.bind(TextSchema))
|
|
157
|
-
|
|
157
|
+
batch(doc, (d: any) => {
|
|
158
158
|
d.title.insert(0, "test")
|
|
159
159
|
})
|
|
160
160
|
|
|
@@ -175,14 +175,14 @@ describe("materializeYjsShadow", () => {
|
|
|
175
175
|
it("materializes a complex document with multiple field types", () => {
|
|
176
176
|
const doc = createDoc(yjs.bind(FullSchema))
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
batch(doc, (d: any) => {
|
|
179
179
|
d.title.insert(0, "My Doc")
|
|
180
180
|
d.theme.set("dark")
|
|
181
181
|
d.settings.darkMode.set(true)
|
|
182
182
|
d.settings.fontSize.set(16)
|
|
183
183
|
})
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
batch(doc, (d: any) => d.tags.push("important"))
|
|
185
|
+
batch(doc, (d: any) => d.tags.push("urgent"))
|
|
186
186
|
|
|
187
187
|
const yDoc = getYDoc(doc)
|
|
188
188
|
const binding = trivialBinding(FullSchema)
|
|
@@ -198,7 +198,7 @@ describe("materializeYjsShadow", () => {
|
|
|
198
198
|
|
|
199
199
|
it("materializes without binding (undefined binding)", () => {
|
|
200
200
|
const doc = createDoc(yjs.bind(TextSchema))
|
|
201
|
-
|
|
201
|
+
batch(doc, (d: any) => {
|
|
202
202
|
d.title.insert(0, "no binding")
|
|
203
203
|
})
|
|
204
204
|
|