@loro-extended/change 0.9.1 → 1.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.
Files changed (43) hide show
  1. package/README.md +179 -69
  2. package/dist/index.d.ts +361 -169
  3. package/dist/index.js +516 -235
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/change.test.ts +180 -175
  7. package/src/conversion.test.ts +19 -19
  8. package/src/conversion.ts +7 -7
  9. package/src/derive-placeholder.test.ts +14 -14
  10. package/src/derive-placeholder.ts +3 -3
  11. package/src/discriminated-union-assignability.test.ts +7 -7
  12. package/src/discriminated-union-tojson.test.ts +13 -24
  13. package/src/discriminated-union.test.ts +9 -8
  14. package/src/equality.test.ts +10 -2
  15. package/src/functional-helpers.test.ts +149 -0
  16. package/src/functional-helpers.ts +61 -0
  17. package/src/grand-unified-api.test.ts +423 -0
  18. package/src/index.ts +8 -6
  19. package/src/json-patch.test.ts +64 -56
  20. package/src/overlay-recursion.test.ts +23 -22
  21. package/src/overlay.ts +9 -9
  22. package/src/readonly.test.ts +27 -26
  23. package/src/shape.ts +103 -21
  24. package/src/typed-doc.ts +227 -58
  25. package/src/typed-refs/base.ts +23 -1
  26. package/src/typed-refs/counter.test.ts +44 -13
  27. package/src/typed-refs/counter.ts +40 -3
  28. package/src/typed-refs/doc.ts +12 -6
  29. package/src/typed-refs/json-compatibility.test.ts +37 -32
  30. package/src/typed-refs/list-base.ts +26 -22
  31. package/src/typed-refs/list.test.ts +4 -3
  32. package/src/typed-refs/movable-list.test.ts +3 -2
  33. package/src/typed-refs/movable-list.ts +4 -1
  34. package/src/typed-refs/proxy-handlers.ts +14 -1
  35. package/src/typed-refs/record.test.ts +107 -42
  36. package/src/typed-refs/record.ts +37 -19
  37. package/src/typed-refs/{map.ts → struct.ts} +31 -16
  38. package/src/typed-refs/text.ts +42 -1
  39. package/src/typed-refs/utils.ts +28 -6
  40. package/src/types.test.ts +34 -39
  41. package/src/types.ts +5 -40
  42. package/src/utils/type-guards.ts +11 -6
  43. package/src/validation.ts +10 -10
@@ -1,7 +1,8 @@
1
1
  import { LoroDoc, LoroMap } from "loro-crdt"
2
2
  import { describe, expect, it } from "vitest"
3
+ import { change } from "./functional-helpers.js"
3
4
  import { Shape } from "./shape.js"
4
- import { createTypedDoc, TypedDoc } from "./typed-doc.js"
5
+ import { createTypedDoc } from "./typed-doc.js"
5
6
 
6
7
  describe("CRDT Operations", () => {
7
8
  describe("Text Operations", () => {
@@ -12,11 +13,11 @@ describe("CRDT Operations", () => {
12
13
 
13
14
  const typedDoc = createTypedDoc(schema)
14
15
 
15
- const result = typedDoc.change(draft => {
16
+ const result = change(typedDoc, draft => {
16
17
  draft.title.insert(0, "Hello")
17
18
  draft.title.insert(5, " World")
18
19
  draft.title.delete(0, 5) // Delete "Hello"
19
- })
20
+ }).toJSON()
20
21
 
21
22
  expect(result.title).toBe(" World")
22
23
  })
@@ -28,10 +29,10 @@ describe("CRDT Operations", () => {
28
29
 
29
30
  const typedDoc = createTypedDoc(schema)
30
31
 
31
- const result = typedDoc.change(draft => {
32
+ const result = change(typedDoc, draft => {
32
33
  draft.content.insert(0, "Initial content")
33
34
  draft.content.update("Replaced content")
34
- })
35
+ }).toJSON()
35
36
 
36
37
  expect(result.content).toBe("Replaced content")
37
38
  })
@@ -43,11 +44,11 @@ describe("CRDT Operations", () => {
43
44
 
44
45
  const typedDoc = createTypedDoc(schema)
45
46
 
46
- const result = typedDoc.change(draft => {
47
+ const result = change(typedDoc, draft => {
47
48
  draft.richText.insert(0, "Bold text")
48
49
  draft.richText.mark({ start: 0, end: 4 }, "bold", true)
49
50
  draft.richText.unmark({ start: 0, end: 2 }, "bold")
50
- })
51
+ }).toJSON()
51
52
 
52
53
  expect(result.richText).toBe("Bold text")
53
54
  })
@@ -59,7 +60,7 @@ describe("CRDT Operations", () => {
59
60
 
60
61
  const typedDoc = createTypedDoc(schema)
61
62
 
62
- typedDoc.change(draft => {
63
+ change(typedDoc, draft => {
63
64
  draft.deltaText.insert(0, "Hello World")
64
65
  const delta = draft.deltaText.toDelta()
65
66
  expect(delta).toBeDefined()
@@ -68,7 +69,8 @@ describe("CRDT Operations", () => {
68
69
  draft.deltaText.applyDelta([{ insert: "New " }])
69
70
  })
70
71
 
71
- const result = typedDoc.value
72
+ // Use toJSON() to get plain values for comparison
73
+ const result = typedDoc.toJSON()
72
74
  expect(result.deltaText).toContain("New")
73
75
  })
74
76
 
@@ -79,7 +81,7 @@ describe("CRDT Operations", () => {
79
81
 
80
82
  const typedDoc = createTypedDoc(schema)
81
83
 
82
- typedDoc.change(draft => {
84
+ change(typedDoc, draft => {
83
85
  draft.measuredText.insert(0, "Hello")
84
86
  expect(draft.measuredText.length).toBe(5)
85
87
 
@@ -97,11 +99,11 @@ describe("CRDT Operations", () => {
97
99
 
98
100
  const typedDoc = createTypedDoc(schema)
99
101
 
100
- const result = typedDoc.change(draft => {
102
+ const result = change(typedDoc, draft => {
101
103
  draft.count.increment(5)
102
104
  draft.count.decrement(2)
103
105
  draft.count.increment(10)
104
- })
106
+ }).toJSON()
105
107
 
106
108
  expect(result.count).toBe(13) // 5 - 2 + 10 = 13
107
109
  })
@@ -113,7 +115,7 @@ describe("CRDT Operations", () => {
113
115
 
114
116
  const typedDoc = createTypedDoc(schema)
115
117
 
116
- typedDoc.change(draft => {
118
+ change(typedDoc, draft => {
117
119
  draft.counter.increment(7)
118
120
  expect(draft.counter.value).toBe(7)
119
121
 
@@ -129,10 +131,10 @@ describe("CRDT Operations", () => {
129
131
 
130
132
  const typedDoc = createTypedDoc(schema)
131
133
 
132
- const result = typedDoc.change(draft => {
134
+ const result = change(typedDoc, draft => {
133
135
  draft.negativeCounter.increment(-5) // Negative increment
134
136
  draft.negativeCounter.decrement(-3) // Negative decrement (adds 3)
135
- })
137
+ }).toJSON()
136
138
 
137
139
  expect(result.negativeCounter).toBe(-2) // -5 + 3 = -2
138
140
  })
@@ -146,12 +148,12 @@ describe("CRDT Operations", () => {
146
148
 
147
149
  const typedDoc = createTypedDoc(schema)
148
150
 
149
- const result = typedDoc.change(draft => {
151
+ const result = change(typedDoc, draft => {
150
152
  draft.items.push("first")
151
153
  draft.items.insert(0, "zero")
152
154
  draft.items.push("second")
153
155
  draft.items.delete(1, 1) // Delete "first"
154
- })
156
+ }).toJSON()
155
157
 
156
158
  expect(result.items).toEqual(["zero", "second"])
157
159
  })
@@ -163,11 +165,11 @@ describe("CRDT Operations", () => {
163
165
 
164
166
  const typedDoc = createTypedDoc(schema)
165
167
 
166
- const result = typedDoc.change(draft => {
168
+ const result = change(typedDoc, draft => {
167
169
  draft.numbers.push(1)
168
170
  draft.numbers.push(2)
169
171
  draft.numbers.insert(1, 1.5)
170
- })
172
+ }).toJSON()
171
173
 
172
174
  expect(result.numbers).toEqual([1, 1.5, 2])
173
175
  })
@@ -179,11 +181,11 @@ describe("CRDT Operations", () => {
179
181
 
180
182
  const typedDoc = createTypedDoc(schema)
181
183
 
182
- const result = typedDoc.change(draft => {
184
+ const result = change(typedDoc, draft => {
183
185
  draft.flags.push(true)
184
186
  draft.flags.push(false)
185
187
  draft.flags.insert(1, true)
186
- })
188
+ }).toJSON()
187
189
 
188
190
  expect(result.flags).toEqual([true, true, false])
189
191
  })
@@ -195,7 +197,7 @@ describe("CRDT Operations", () => {
195
197
 
196
198
  const typedDoc = createTypedDoc(schema)
197
199
 
198
- typedDoc.change(draft => {
200
+ change(typedDoc, draft => {
199
201
  draft.testList.push("a")
200
202
  draft.testList.push("b")
201
203
 
@@ -213,13 +215,13 @@ describe("CRDT Operations", () => {
213
215
 
214
216
  const typedDoc = createTypedDoc(schema)
215
217
 
216
- typedDoc.change(draft => {
218
+ change(typedDoc, draft => {
217
219
  // Note: pushContainer and insertContainer expect actual container instances
218
220
  // For testing purposes, we'll just verify the list exists
219
221
  expect(draft.containerList.length).toBe(0)
220
222
  })
221
223
 
222
- const result = typedDoc.value
224
+ const result = typedDoc.toJSON()
223
225
  expect(result.containerList).toHaveLength(0) // No containers were actually added
224
226
  })
225
227
 
@@ -231,18 +233,18 @@ describe("CRDT Operations", () => {
231
233
  const typedDoc = createTypedDoc(schema)
232
234
 
233
235
  // Add initial items
234
- typedDoc.change(draft => {
236
+ change(typedDoc, draft => {
235
237
  draft.items.push("first")
236
238
  draft.items.push("second")
237
239
  draft.items.push("third")
238
240
  })
239
241
 
240
242
  // Test move operation: move first item to the end
241
- const result = typedDoc.change(draft => {
243
+ const result = change(typedDoc, draft => {
242
244
  const valueToMove = draft.items.get(0)
243
245
  draft.items.delete(0, 1)
244
246
  draft.items.insert(2, valueToMove)
245
- })
247
+ }).toJSON()
246
248
 
247
249
  expect(result.items).toEqual(["second", "third", "first"])
248
250
  })
@@ -252,7 +254,7 @@ describe("CRDT Operations", () => {
252
254
  it("should handle push, insert, delete, and move operations", () => {
253
255
  const schema = Shape.doc({
254
256
  tasks: Shape.movableList(
255
- Shape.plain.object({
257
+ Shape.plain.struct({
256
258
  id: Shape.plain.string(),
257
259
  title: Shape.plain.string(),
258
260
  }),
@@ -261,13 +263,13 @@ describe("CRDT Operations", () => {
261
263
 
262
264
  const typedDoc = createTypedDoc(schema)
263
265
 
264
- const result = typedDoc.change(draft => {
266
+ const result = change(typedDoc, draft => {
265
267
  draft.tasks.push({ id: "1", title: "Task 1" })
266
268
  draft.tasks.push({ id: "2", title: "Task 2" })
267
269
  draft.tasks.push({ id: "3", title: "Task 3" })
268
270
  draft.tasks.move(0, 2) // Move first task to position 2
269
271
  draft.tasks.delete(1, 1) // Delete middle task
270
- })
272
+ }).toJSON()
271
273
 
272
274
  expect(result.tasks).toHaveLength(2)
273
275
  expect(result.tasks[0]).toEqual({ id: "2", title: "Task 2" })
@@ -281,10 +283,10 @@ describe("CRDT Operations", () => {
281
283
 
282
284
  const typedDoc = createTypedDoc(schema)
283
285
 
284
- const result = typedDoc.change(draft => {
286
+ const result = change(typedDoc, draft => {
285
287
  draft.editableList.push("original")
286
288
  draft.editableList.set(0, "modified")
287
- })
289
+ }).toJSON()
288
290
 
289
291
  expect(result.editableList).toEqual(["modified"])
290
292
  })
@@ -296,7 +298,7 @@ describe("CRDT Operations", () => {
296
298
 
297
299
  const typedDoc = createTypedDoc(schema)
298
300
 
299
- typedDoc.change(draft => {
301
+ change(typedDoc, draft => {
300
302
  draft.movableItems.push(10)
301
303
  draft.movableItems.push(20)
302
304
 
@@ -310,7 +312,7 @@ describe("CRDT Operations", () => {
310
312
  describe("Map Operations", () => {
311
313
  it("should handle set, get, and delete operations", () => {
312
314
  const schema = Shape.doc({
313
- metadata: Shape.map({
315
+ metadata: Shape.struct({
314
316
  title: Shape.plain.string(),
315
317
  count: Shape.plain.number().placeholder(1),
316
318
  enabled: Shape.plain.boolean(),
@@ -319,12 +321,12 @@ describe("CRDT Operations", () => {
319
321
 
320
322
  const typedDoc = createTypedDoc(schema)
321
323
 
322
- const result = typedDoc.change(draft => {
324
+ const result = change(typedDoc, draft => {
323
325
  draft.metadata.set("title", "Test Title")
324
326
  draft.metadata.set("count", 42)
325
327
  draft.metadata.set("enabled", true)
326
328
  draft.metadata.delete("count")
327
- })
329
+ }).toJSON()
328
330
 
329
331
  expect(result.metadata.title).toBe("Test Title")
330
332
  expect(result.metadata.count).toBe(1) // Should fall back to empty state
@@ -333,7 +335,7 @@ describe("CRDT Operations", () => {
333
335
 
334
336
  it("should handle array values in maps", () => {
335
337
  const schema = Shape.doc({
336
- config: Shape.map({
338
+ config: Shape.struct({
337
339
  tags: Shape.plain.array(Shape.plain.string()),
338
340
  numbers: Shape.plain.array(Shape.plain.number()),
339
341
  }),
@@ -341,10 +343,10 @@ describe("CRDT Operations", () => {
341
343
 
342
344
  const typedDoc = createTypedDoc(schema)
343
345
 
344
- const result = typedDoc.change(draft => {
346
+ const result = change(typedDoc, draft => {
345
347
  draft.config.set("tags", ["tag1", "tag2", "tag3"])
346
348
  draft.config.set("numbers", [1, 2, 3])
347
- })
349
+ }).toJSON()
348
350
 
349
351
  expect(result.config.tags).toEqual(["tag1", "tag2", "tag3"])
350
352
  expect(result.config.numbers).toEqual([1, 2, 3])
@@ -352,7 +354,7 @@ describe("CRDT Operations", () => {
352
354
 
353
355
  it("should provide map utility methods", () => {
354
356
  const schema = Shape.doc({
355
- testMap: Shape.map({
357
+ testMap: Shape.struct({
356
358
  key1: Shape.plain.string(),
357
359
  key2: Shape.plain.number(),
358
360
  }),
@@ -360,7 +362,7 @@ describe("CRDT Operations", () => {
360
362
 
361
363
  const typedDoc = createTypedDoc(schema)
362
364
 
363
- typedDoc.change(draft => {
365
+ change(typedDoc, draft => {
364
366
  draft.testMap.set("key1", "value1")
365
367
  draft.testMap.set("key2", 123)
366
368
 
@@ -377,20 +379,20 @@ describe("CRDT Operations", () => {
377
379
 
378
380
  it("should handle container insertion in maps", () => {
379
381
  const schema = Shape.doc({
380
- containerMap: Shape.map({
382
+ containerMap: Shape.struct({
381
383
  textField: Shape.text(),
382
384
  }),
383
385
  })
384
386
 
385
387
  const typedDoc = createTypedDoc(schema)
386
388
 
387
- typedDoc.change(draft => {
389
+ change(typedDoc, draft => {
388
390
  // Note: setContainer expects actual container instances
389
391
  // For testing purposes, we'll just verify the map exists
390
392
  expect(draft.containerMap).toBeDefined()
391
393
  })
392
394
 
393
- const rawValue = typedDoc.rawValue
395
+ const rawValue = typedDoc.$.rawValue
394
396
  // Since no container was actually set, containerMap might be undefined
395
397
  expect(rawValue.containerMap).toBeUndefined()
396
398
  })
@@ -399,12 +401,12 @@ describe("CRDT Operations", () => {
399
401
  describe("Tree Operations", () => {
400
402
  it("should handle basic tree operations", () => {
401
403
  const schema = Shape.doc({
402
- tree: Shape.tree(Shape.map({ name: Shape.text() })),
404
+ tree: Shape.tree(Shape.struct({ name: Shape.text() })),
403
405
  })
404
406
 
405
407
  const typedDoc = createTypedDoc(schema)
406
408
 
407
- typedDoc.change(draft => {
409
+ change(typedDoc, draft => {
408
410
  const root = draft.tree.createNode()
409
411
  expect(root).toBeDefined()
410
412
 
@@ -416,12 +418,12 @@ describe("CRDT Operations", () => {
416
418
 
417
419
  it("should handle tree node movement and deletion", () => {
418
420
  const schema = Shape.doc({
419
- hierarchy: Shape.tree(Shape.map({ name: Shape.text() })),
421
+ hierarchy: Shape.tree(Shape.struct({ name: Shape.text() })),
420
422
  })
421
423
 
422
424
  const typedDoc = createTypedDoc(schema)
423
425
 
424
- typedDoc.change(draft => {
426
+ change(typedDoc, draft => {
425
427
  const parent1 = draft.hierarchy.createNode()
426
428
  const parent2 = draft.hierarchy.createNode()
427
429
 
@@ -434,12 +436,12 @@ describe("CRDT Operations", () => {
434
436
 
435
437
  it("should handle tree node lookup by ID", () => {
436
438
  const schema = Shape.doc({
437
- searchableTree: Shape.tree(Shape.map({ name: Shape.text() })),
439
+ searchableTree: Shape.tree(Shape.struct({ name: Shape.text() })),
438
440
  })
439
441
 
440
442
  const typedDoc = createTypedDoc(schema)
441
443
 
442
- typedDoc.change(draft => {
444
+ change(typedDoc, draft => {
443
445
  const node = draft.searchableTree.createNode()
444
446
 
445
447
  // Note: getNodeByID might not be available in all versions
@@ -454,11 +456,11 @@ describe("Nested Operations", () => {
454
456
  describe("Nested Maps", () => {
455
457
  it("should handle deeply nested map structures", () => {
456
458
  const schema = Shape.doc({
457
- article: Shape.map({
459
+ article: Shape.struct({
458
460
  title: Shape.text(),
459
- metadata: Shape.map({
461
+ metadata: Shape.struct({
460
462
  views: Shape.counter(),
461
- author: Shape.map({
463
+ author: Shape.struct({
462
464
  name: Shape.plain.string(),
463
465
  email: Shape.plain.string(),
464
466
  }),
@@ -468,12 +470,12 @@ describe("Nested Operations", () => {
468
470
 
469
471
  const typedDoc = createTypedDoc(schema)
470
472
 
471
- const result = typedDoc.change(draft => {
473
+ const result = change(typedDoc, draft => {
472
474
  draft.article.title.insert(0, "Nested Article")
473
475
  draft.article.metadata.views.increment(10)
474
476
  draft.article.metadata.author.set("name", "John Doe")
475
477
  draft.article.metadata.author.set("email", "john@example.com")
476
- })
478
+ }).toJSON()
477
479
 
478
480
  expect(result.article.title).toBe("Nested Article")
479
481
  expect(result.article.metadata.views).toBe(10)
@@ -483,7 +485,7 @@ describe("Nested Operations", () => {
483
485
 
484
486
  it("should handle maps with mixed Zod and Loro schemas", () => {
485
487
  const schema = Shape.doc({
486
- mixed: Shape.map({
488
+ mixed: Shape.struct({
487
489
  plainString: Shape.plain.string(),
488
490
  plainArray: Shape.plain.array(Shape.plain.number()),
489
491
  loroText: Shape.text(),
@@ -493,12 +495,12 @@ describe("Nested Operations", () => {
493
495
 
494
496
  const typedDoc = createTypedDoc(schema)
495
497
 
496
- const result = typedDoc.change(draft => {
498
+ const result = change(typedDoc, draft => {
497
499
  draft.mixed.set("plainString", "Hello")
498
500
  draft.mixed.set("plainArray", [1, 2, 3])
499
501
  draft.mixed.loroText.insert(0, "Loro Text")
500
502
  draft.mixed.loroCounter.increment(5)
501
- })
503
+ }).toJSON()
502
504
 
503
505
  expect(result.mixed.plainString).toBe("Hello")
504
506
  expect(result.mixed.plainArray).toEqual([1, 2, 3])
@@ -511,10 +513,10 @@ describe("Nested Operations", () => {
511
513
  it("should handle lists of maps with nested structures", () => {
512
514
  const schema = Shape.doc({
513
515
  articles: Shape.list(
514
- Shape.map({
516
+ Shape.struct({
515
517
  title: Shape.text(),
516
518
  tags: Shape.list(Shape.plain.string()),
517
- metadata: Shape.map({
519
+ metadata: Shape.struct({
518
520
  views: Shape.counter(),
519
521
  published: Shape.plain.boolean(),
520
522
  }),
@@ -524,7 +526,7 @@ describe("Nested Operations", () => {
524
526
 
525
527
  const typedDoc = createTypedDoc(schema)
526
528
 
527
- const result = typedDoc.change(draft => {
529
+ const result = change(typedDoc, draft => {
528
530
  draft.articles.push({
529
531
  title: "First Article",
530
532
  tags: ["tech", "programming"],
@@ -542,7 +544,7 @@ describe("Nested Operations", () => {
542
544
  published: false,
543
545
  },
544
546
  })
545
- })
547
+ }).toJSON()
546
548
 
547
549
  expect(result.articles).toHaveLength(2)
548
550
  expect(result.articles[0].title).toBe("First Article")
@@ -554,9 +556,9 @@ describe("Nested Operations", () => {
554
556
 
555
557
  it("should handle nested plain value maps", () => {
556
558
  const schema = Shape.doc({
557
- articles: Shape.map({
558
- metadata: Shape.plain.object({
559
- views: Shape.plain.object({
559
+ articles: Shape.struct({
560
+ metadata: Shape.plain.struct({
561
+ views: Shape.plain.struct({
560
562
  page: Shape.plain.number(),
561
563
  }),
562
564
  }),
@@ -565,25 +567,25 @@ describe("Nested Operations", () => {
565
567
 
566
568
  const typedDoc = createTypedDoc(schema)
567
569
 
568
- const result1 = typedDoc.change(draft => {
570
+ const result1 = change(typedDoc, draft => {
569
571
  // natural object access & assignment for Value nodes
570
572
  draft.articles.metadata.views.page = 1
571
- })
573
+ }).toJSON()
572
574
 
573
575
  expect(result1).toEqual({
574
576
  articles: { metadata: { views: { page: 1 } } },
575
577
  })
576
578
 
577
- const result2 = typedDoc.change(draft => {
579
+ const result2 = change(typedDoc, draft => {
578
580
  // natural object access & assignment for Value nodes
579
581
  draft.articles.metadata = { views: { page: 2 } }
580
- })
582
+ }).toJSON()
581
583
 
582
584
  expect(result2).toEqual({
583
585
  articles: { metadata: { views: { page: 2 } } },
584
586
  })
585
587
 
586
- expect(typedDoc.rawValue).toEqual({
588
+ expect(typedDoc.$.rawValue).toEqual({
587
589
  articles: { metadata: { views: { page: 2 } } },
588
590
  })
589
591
  })
@@ -595,10 +597,10 @@ describe("Nested Operations", () => {
595
597
 
596
598
  const typedDoc = createTypedDoc(schema)
597
599
 
598
- const result = typedDoc.change(draft => {
600
+ const result = change(typedDoc, draft => {
599
601
  draft.matrix.push([1, 2, 3])
600
602
  draft.matrix.push([4, 5, 6])
601
- })
603
+ }).toJSON()
602
604
 
603
605
  const correctResult = {
604
606
  matrix: [
@@ -608,14 +610,14 @@ describe("Nested Operations", () => {
608
610
  }
609
611
 
610
612
  expect(result).toEqual(correctResult)
611
- expect(typedDoc.rawValue).toEqual(correctResult)
613
+ expect(typedDoc.$.rawValue).toEqual(correctResult)
612
614
  })
613
615
  })
614
616
 
615
617
  describe("Maps with List Values", () => {
616
618
  it("should handle maps containing lists", () => {
617
619
  const schema = Shape.doc({
618
- categories: Shape.map({
620
+ categories: Shape.struct({
619
621
  tech: Shape.list(Shape.plain.string()),
620
622
  design: Shape.list(Shape.plain.string()),
621
623
  }),
@@ -623,11 +625,11 @@ describe("Nested Operations", () => {
623
625
 
624
626
  const typedDoc = createTypedDoc(schema)
625
627
 
626
- const result = typedDoc.change(draft => {
628
+ const result = change(typedDoc, draft => {
627
629
  draft.categories.tech.push("JavaScript")
628
630
  draft.categories.tech.push("TypeScript")
629
631
  draft.categories.design.push("UI/UX")
630
- })
632
+ }).toJSON()
631
633
 
632
634
  expect(result.categories.tech).toEqual(["JavaScript", "TypeScript"])
633
635
  expect(result.categories.design).toEqual(["UI/UX"])
@@ -662,10 +664,10 @@ describe("TypedLoroDoc", () => {
662
664
 
663
665
  const typedDoc = createTypedDoc(schema)
664
666
 
665
- const result = typedDoc.change(draft => {
667
+ const result = change(typedDoc, draft => {
666
668
  draft.title.insert(0, "Hello World")
667
669
  draft.count.increment(5)
668
- })
670
+ }).toJSON()
669
671
 
670
672
  expect(result.title).toBe("Hello World")
671
673
  expect(result.count).toBe(5)
@@ -674,9 +676,9 @@ describe("TypedLoroDoc", () => {
674
676
 
675
677
  it("should handle nested empty state structures", () => {
676
678
  const schema = Shape.doc({
677
- article: Shape.map({
679
+ article: Shape.struct({
678
680
  title: Shape.text().placeholder("Default Title"),
679
- metadata: Shape.map({
681
+ metadata: Shape.struct({
680
682
  views: Shape.counter(),
681
683
  tags: Shape.plain.array(Shape.plain.string()),
682
684
  author: Shape.plain.string().placeholder("Anonymous"),
@@ -699,11 +701,11 @@ describe("TypedLoroDoc", () => {
699
701
 
700
702
  expect(typedDoc.toJSON()).toEqual(expectedPlaceholder)
701
703
 
702
- const result = typedDoc.change(draft => {
704
+ const result = change(typedDoc, draft => {
703
705
  draft.article.title.insert(0, "New Title")
704
706
  draft.article.metadata.views.increment(10)
705
707
  draft.article.metadata.set("author", "John Doe")
706
- })
708
+ }).toJSON()
707
709
 
708
710
  expect(result.article.title).toBe("New Title")
709
711
  expect(result.article.metadata.views).toBe(10)
@@ -713,7 +715,7 @@ describe("TypedLoroDoc", () => {
713
715
 
714
716
  it("should handle empty state with optional fields", () => {
715
717
  const schema = Shape.doc({
716
- profile: Shape.map({
718
+ profile: Shape.struct({
717
719
  name: Shape.plain.string().placeholder("Anonymous"),
718
720
  email: Shape.plain
719
721
  .union([Shape.plain.null(), Shape.plain.string()])
@@ -726,10 +728,10 @@ describe("TypedLoroDoc", () => {
726
728
 
727
729
  const typedDoc = createTypedDoc(schema)
728
730
 
729
- const result = typedDoc.change(draft => {
731
+ const result = change(typedDoc, draft => {
730
732
  draft.profile.set("name", "John Doe")
731
733
  draft.profile.set("email", "john@example.com")
732
- })
734
+ }).toJSON()
733
735
 
734
736
  expect(result.profile.name).toBe("John Doe")
735
737
  expect(result.profile.email).toBe("john@example.com")
@@ -741,24 +743,25 @@ describe("TypedLoroDoc", () => {
741
743
  it("should distinguish between raw CRDT and overlaid values", () => {
742
744
  const schema = Shape.doc({
743
745
  title: Shape.text(),
744
- metadata: Shape.map({
746
+ metadata: Shape.struct({
745
747
  optional: Shape.plain.string().placeholder("default-optional"),
746
748
  }),
747
749
  })
748
750
 
749
751
  const typedDoc = createTypedDoc(schema)
750
752
 
751
- typedDoc.change(draft => {
753
+ change(typedDoc, draft => {
752
754
  draft.title.insert(0, "Hello")
753
755
  })
754
756
 
755
757
  // Raw value should only contain what was actually set in CRDT
756
- const rawValue = typedDoc.rawValue
758
+ const rawValue = typedDoc.$.rawValue
757
759
  expect(rawValue.title).toBe("Hello")
758
760
  expect(rawValue.metadata).toBeUndefined()
759
761
 
760
762
  // Overlaid value should include empty state defaults
761
- const overlaidValue = typedDoc.value
763
+ // Use toJSON() to get plain values for comparison
764
+ const overlaidValue = typedDoc.toJSON()
762
765
  expect(overlaidValue.title).toBe("Hello")
763
766
  expect(overlaidValue.metadata.optional).toBe("default-optional")
764
767
  })
@@ -791,7 +794,7 @@ describe("TypedLoroDoc", () => {
791
794
 
792
795
  it("should handle null values in placeholder correctly", () => {
793
796
  const schema = Shape.doc({
794
- interjection: Shape.map({
797
+ interjection: Shape.struct({
795
798
  currentPrediction: Shape.plain
796
799
  .union([Shape.plain.null(), Shape.plain.string()])
797
800
  .placeholder(null),
@@ -802,7 +805,7 @@ describe("TypedLoroDoc", () => {
802
805
 
803
806
  // This should not throw "placeholder required"
804
807
  expect(() => {
805
- typedDoc.change(draft => {
808
+ change(typedDoc, draft => {
806
809
  // Accessing the property triggers getOrCreateNode
807
810
  const current = draft.interjection.currentPrediction
808
811
  expect(current).toBeNull()
@@ -827,22 +830,22 @@ describe("TypedLoroDoc", () => {
827
830
  const typedDoc = createTypedDoc(schema)
828
831
 
829
832
  // First change
830
- let result = typedDoc.change(draft => {
833
+ let result = change(typedDoc, draft => {
831
834
  draft.title.insert(0, "Hello")
832
835
  draft.count.increment(5)
833
836
  draft.items.push("first")
834
- })
837
+ }).toJSON()
835
838
 
836
839
  expect(result.title).toBe("Hello")
837
840
  expect(result.count).toBe(5)
838
841
  expect(result.items).toEqual(["first"])
839
842
 
840
843
  // Second change - should build on previous state
841
- result = typedDoc.change(draft => {
844
+ result = change(typedDoc, draft => {
842
845
  draft.title.insert(5, " World")
843
846
  draft.count.increment(3)
844
847
  draft.items.push("second")
845
- })
848
+ }).toJSON()
846
849
 
847
850
  expect(result.title).toBe("Hello World")
848
851
  expect(result.count).toBe(8) // 5 + 3
@@ -854,7 +857,7 @@ describe("TypedLoroDoc", () => {
854
857
  it("should convert plain objects to map containers in lists", () => {
855
858
  const schema = Shape.doc({
856
859
  articles: Shape.list(
857
- Shape.map({
860
+ Shape.struct({
858
861
  title: Shape.text(),
859
862
  tags: Shape.list(Shape.plain.string()),
860
863
  }),
@@ -863,12 +866,12 @@ describe("TypedLoroDoc", () => {
863
866
 
864
867
  const typedDoc = createTypedDoc(schema)
865
868
 
866
- const result = typedDoc.change(draft => {
869
+ const result = change(typedDoc, draft => {
867
870
  draft.articles.push({
868
871
  title: "Hello World",
869
872
  tags: ["hello", "world"],
870
873
  })
871
- })
874
+ }).toJSON()
872
875
 
873
876
  expect(result.articles).toHaveLength(1)
874
877
  expect(result.articles[0].title).toBe("Hello World")
@@ -878,7 +881,7 @@ describe("TypedLoroDoc", () => {
878
881
  it("should handle nested conversion in movable lists", () => {
879
882
  const schema = Shape.doc({
880
883
  tasks: Shape.movableList(
881
- Shape.map({
884
+ Shape.struct({
882
885
  title: Shape.text(),
883
886
  completed: Shape.plain.boolean(),
884
887
  subtasks: Shape.list(Shape.plain.string()),
@@ -888,13 +891,13 @@ describe("TypedLoroDoc", () => {
888
891
 
889
892
  const typedDoc = createTypedDoc(schema)
890
893
 
891
- const result = typedDoc.change(draft => {
894
+ const result = change(typedDoc, draft => {
892
895
  draft.tasks.push({
893
896
  title: "Main Task",
894
897
  completed: false,
895
898
  subtasks: ["subtask1", "subtask2"],
896
899
  })
897
- })
900
+ }).toJSON()
898
901
 
899
902
  expect(result.tasks).toHaveLength(1)
900
903
  expect(result.tasks[0].title).toBe("Main Task")
@@ -905,9 +908,9 @@ describe("TypedLoroDoc", () => {
905
908
  it("should handle deeply nested conversion", () => {
906
909
  const schema = Shape.doc({
907
910
  posts: Shape.list(
908
- Shape.map({
911
+ Shape.struct({
909
912
  title: Shape.text(),
910
- metadata: Shape.map({
913
+ metadata: Shape.struct({
911
914
  views: Shape.counter(),
912
915
  tags: Shape.plain.array(Shape.plain.string()),
913
916
  }),
@@ -917,7 +920,7 @@ describe("TypedLoroDoc", () => {
917
920
 
918
921
  const typedDoc = createTypedDoc(schema)
919
922
 
920
- const result = typedDoc.change(draft => {
923
+ const result = change(typedDoc, draft => {
921
924
  draft.posts.push({
922
925
  title: "Complex Post",
923
926
  metadata: {
@@ -925,7 +928,7 @@ describe("TypedLoroDoc", () => {
925
928
  tags: ["complex", "nested"],
926
929
  },
927
930
  })
928
- })
931
+ }).toJSON()
929
932
 
930
933
  expect(result.posts).toHaveLength(1)
931
934
  expect(result.posts[0].title).toBe("Complex Post")
@@ -940,7 +943,7 @@ describe("Edge Cases and Error Handling", () => {
940
943
  it("should maintain type safety with complex schemas", () => {
941
944
  const schema = Shape.doc({
942
945
  title: Shape.text(),
943
- metadata: Shape.map({
946
+ metadata: Shape.struct({
944
947
  author: Shape.plain.string().placeholder("Anonymous"),
945
948
  publishedAt: Shape.plain.string().placeholder("2025-01-01"),
946
949
  }),
@@ -949,23 +952,24 @@ describe("Edge Cases and Error Handling", () => {
949
952
  const typedDoc = createTypedDoc(schema)
950
953
 
951
954
  // Multiple changes
952
- typedDoc.change(draft => {
955
+ change(typedDoc, draft => {
953
956
  draft.title.insert(0, "First Title")
954
957
  draft.metadata.set("author", "John Doe")
955
958
  })
956
959
 
957
- let result = typedDoc.value
960
+ // Use toJSON() to get plain values for comparison
961
+ let result = typedDoc.toJSON()
958
962
  expect(result.title).toBe("First Title")
959
963
  expect(result.metadata.author).toBe("John Doe")
960
964
  expect(result.metadata.publishedAt).toBe("2025-01-01")
961
965
 
962
966
  // More changes
963
- typedDoc.change(draft => {
967
+ change(typedDoc, draft => {
964
968
  draft.title.update("Updated Title")
965
969
  draft.metadata.set("publishedAt", "2025-12-01")
966
970
  })
967
971
 
968
- result = typedDoc.value
972
+ result = typedDoc.toJSON()
969
973
  expect(result.title).toBe("Updated Title")
970
974
  expect(result.metadata.author).toBe("John Doe") // Preserved from previous change
971
975
  expect(result.metadata.publishedAt).toBe("2025-12-01")
@@ -974,7 +978,7 @@ describe("Edge Cases and Error Handling", () => {
974
978
  it("should handle empty containers gracefully", () => {
975
979
  const schema = Shape.doc({
976
980
  todos: Shape.list(
977
- Shape.map({
981
+ Shape.struct({
978
982
  text: Shape.text(),
979
983
  completed: Shape.plain.boolean(),
980
984
  }),
@@ -984,12 +988,12 @@ describe("Edge Cases and Error Handling", () => {
984
988
  const typedDoc = createTypedDoc(schema)
985
989
 
986
990
  // Add a todo item with minimal data
987
- const result = typedDoc.change(draft => {
991
+ const result = change(typedDoc, draft => {
988
992
  draft.todos.push({
989
993
  text: "Test Todo",
990
994
  completed: false,
991
995
  })
992
- })
996
+ }).toJSON()
993
997
 
994
998
  expect(result.todos).toHaveLength(1)
995
999
  expect(result.todos[0].text).toBe("Test Todo")
@@ -1006,13 +1010,13 @@ describe("Edge Cases and Error Handling", () => {
1006
1010
 
1007
1011
  const typedDoc = createTypedDoc(schema)
1008
1012
 
1009
- const result = typedDoc.change(draft => {
1013
+ const result = change(typedDoc, draft => {
1010
1014
  // Add many items
1011
1015
  for (let i = 0; i < 100; i++) {
1012
1016
  draft.items.push(`item-${i}`)
1013
1017
  draft.counter.increment(1)
1014
1018
  }
1015
- })
1019
+ }).toJSON()
1016
1020
 
1017
1021
  expect(result.items).toHaveLength(100)
1018
1022
  expect(result.counter).toBe(100)
@@ -1031,11 +1035,11 @@ describe("Edge Cases and Error Handling", () => {
1031
1035
 
1032
1036
  const typedDoc = createTypedDoc(schema)
1033
1037
 
1034
- const result = typedDoc.change(draft => {
1038
+ const result = change(typedDoc, draft => {
1035
1039
  draft.text.insert(0, "")
1036
1040
  draft.count.increment(0)
1037
1041
  draft.items.push("")
1038
- })
1042
+ }).toJSON()
1039
1043
 
1040
1044
  expect(result.text).toBe("")
1041
1045
  expect(result.count).toBe(0)
@@ -1050,11 +1054,11 @@ describe("Edge Cases and Error Handling", () => {
1050
1054
 
1051
1055
  const typedDoc = createTypedDoc(schema)
1052
1056
 
1053
- const result = typedDoc.change(draft => {
1057
+ const result = change(typedDoc, draft => {
1054
1058
  draft.unicode.insert(0, "Hello 世界 🌍")
1055
1059
  draft.emoji.push("🚀")
1056
1060
  draft.emoji.push("⭐")
1057
- })
1061
+ }).toJSON()
1058
1062
 
1059
1063
  expect(result.unicode).toBe("Hello 世界 🌍")
1060
1064
  expect(result.emoji).toEqual(["🚀", "⭐"])
@@ -1070,7 +1074,7 @@ describe("Edge Cases and Error Handling", () => {
1070
1074
 
1071
1075
  const typedDoc = createTypedDoc(schema)
1072
1076
 
1073
- typedDoc.change(draft => {
1077
+ change(typedDoc, draft => {
1074
1078
  draft.items.push("apple")
1075
1079
  draft.items.push("banana")
1076
1080
  draft.items.push("cherry")
@@ -1091,7 +1095,7 @@ describe("Edge Cases and Error Handling", () => {
1091
1095
 
1092
1096
  const typedDoc = createTypedDoc(schema)
1093
1097
 
1094
- typedDoc.change(draft => {
1098
+ change(typedDoc, draft => {
1095
1099
  draft.numbers.push(10)
1096
1100
  draft.numbers.push(20)
1097
1101
  draft.numbers.push(30)
@@ -1112,7 +1116,7 @@ describe("Edge Cases and Error Handling", () => {
1112
1116
 
1113
1117
  const typedDoc = createTypedDoc(schema)
1114
1118
 
1115
- typedDoc.change(draft => {
1119
+ change(typedDoc, draft => {
1116
1120
  draft.words.push("hello")
1117
1121
  draft.words.push("world")
1118
1122
 
@@ -1139,7 +1143,7 @@ describe("Edge Cases and Error Handling", () => {
1139
1143
 
1140
1144
  const typedDoc = createTypedDoc(schema)
1141
1145
 
1142
- typedDoc.change(draft => {
1146
+ change(typedDoc, draft => {
1143
1147
  draft.numbers.push(1)
1144
1148
  draft.numbers.push(2)
1145
1149
  draft.numbers.push(3)
@@ -1162,7 +1166,7 @@ describe("Edge Cases and Error Handling", () => {
1162
1166
 
1163
1167
  const typedDoc = createTypedDoc(schema)
1164
1168
 
1165
- typedDoc.change(draft => {
1169
+ change(typedDoc, draft => {
1166
1170
  draft.items.push("a")
1167
1171
  draft.items.push("b")
1168
1172
  draft.items.push("c")
@@ -1188,7 +1192,7 @@ describe("Edge Cases and Error Handling", () => {
1188
1192
 
1189
1193
  const typedDoc = createTypedDoc(schema)
1190
1194
 
1191
- typedDoc.change(draft => {
1195
+ change(typedDoc, draft => {
1192
1196
  draft.numbers.push(1)
1193
1197
  draft.numbers.push(3)
1194
1198
  draft.numbers.push(5)
@@ -1214,7 +1218,7 @@ describe("Edge Cases and Error Handling", () => {
1214
1218
 
1215
1219
  const typedDoc = createTypedDoc(schema)
1216
1220
 
1217
- typedDoc.change(draft => {
1221
+ change(typedDoc, draft => {
1218
1222
  draft.numbers.push(2)
1219
1223
  draft.numbers.push(4)
1220
1224
  draft.numbers.push(6)
@@ -1236,7 +1240,7 @@ describe("Edge Cases and Error Handling", () => {
1236
1240
  it("should work with lists of plain objects", () => {
1237
1241
  const schema = Shape.doc({
1238
1242
  todos: Shape.list(
1239
- Shape.plain.object({
1243
+ Shape.plain.struct({
1240
1244
  id: Shape.plain.string(),
1241
1245
  text: Shape.plain.string(),
1242
1246
  completed: Shape.plain.boolean(),
@@ -1246,7 +1250,7 @@ describe("Edge Cases and Error Handling", () => {
1246
1250
 
1247
1251
  const typedDoc = createTypedDoc(schema)
1248
1252
 
1249
- typedDoc.change(draft => {
1253
+ change(typedDoc, draft => {
1250
1254
  draft.todos.push({ id: "1", text: "Buy milk", completed: false })
1251
1255
  draft.todos.push({ id: "2", text: "Walk dog", completed: true })
1252
1256
  draft.todos.push({ id: "3", text: "Write code", completed: false })
@@ -1286,7 +1290,7 @@ describe("Edge Cases and Error Handling", () => {
1286
1290
  it("should work with lists of maps (nested containers)", () => {
1287
1291
  const schema = Shape.doc({
1288
1292
  articles: Shape.list(
1289
- Shape.map({
1293
+ Shape.struct({
1290
1294
  title: Shape.text(),
1291
1295
  published: Shape.plain.boolean(),
1292
1296
  }),
@@ -1295,7 +1299,7 @@ describe("Edge Cases and Error Handling", () => {
1295
1299
 
1296
1300
  const typedDoc = createTypedDoc(schema)
1297
1301
 
1298
- typedDoc.change(draft => {
1302
+ change(typedDoc, draft => {
1299
1303
  draft.articles.push({
1300
1304
  title: "First Article",
1301
1305
  published: true,
@@ -1328,7 +1332,7 @@ describe("Edge Cases and Error Handling", () => {
1328
1332
  it("should support all array methods on movable lists", () => {
1329
1333
  const schema = Shape.doc({
1330
1334
  tasks: Shape.movableList(
1331
- Shape.plain.object({
1335
+ Shape.plain.struct({
1332
1336
  id: Shape.plain.string(),
1333
1337
  priority: Shape.plain.number(),
1334
1338
  }),
@@ -1337,7 +1341,7 @@ describe("Edge Cases and Error Handling", () => {
1337
1341
 
1338
1342
  const typedDoc = createTypedDoc(schema)
1339
1343
 
1340
- typedDoc.change(draft => {
1344
+ change(typedDoc, draft => {
1341
1345
  draft.tasks.push({ id: "1", priority: 1 })
1342
1346
  draft.tasks.push({ id: "2", priority: 3 })
1343
1347
  draft.tasks.push({ id: "3", priority: 2 })
@@ -1381,7 +1385,7 @@ describe("Edge Cases and Error Handling", () => {
1381
1385
 
1382
1386
  const typedDoc = createTypedDoc(schema)
1383
1387
 
1384
- typedDoc.change(draft => {
1388
+ change(typedDoc, draft => {
1385
1389
  // Test all methods on empty list
1386
1390
  expect(draft.items.find(_item => true)).toBeUndefined()
1387
1391
  expect(draft.items.findIndex(_item => true)).toBe(-1)
@@ -1405,7 +1409,7 @@ describe("Edge Cases and Error Handling", () => {
1405
1409
 
1406
1410
  const typedDoc = createTypedDoc(schema)
1407
1411
 
1408
- typedDoc.change(draft => {
1412
+ change(typedDoc, draft => {
1409
1413
  draft.items.push(42)
1410
1414
 
1411
1415
  // Test all methods on single item list
@@ -1434,7 +1438,7 @@ describe("Edge Cases and Error Handling", () => {
1434
1438
 
1435
1439
  const typedDoc = createTypedDoc(schema)
1436
1440
 
1437
- typedDoc.change(draft => {
1441
+ change(typedDoc, draft => {
1438
1442
  draft.items.push("a")
1439
1443
  draft.items.push("b")
1440
1444
  draft.items.push("c")
@@ -1480,7 +1484,7 @@ describe("Edge Cases and Error Handling", () => {
1480
1484
  it("should allow mutation of items found via array methods", () => {
1481
1485
  const schema = Shape.doc({
1482
1486
  todos: Shape.list(
1483
- Shape.plain.object({
1487
+ Shape.plain.struct({
1484
1488
  id: Shape.plain.string(),
1485
1489
  text: Shape.plain.string(),
1486
1490
  completed: Shape.plain.boolean(),
@@ -1491,14 +1495,14 @@ describe("Edge Cases and Error Handling", () => {
1491
1495
  const typedDoc = createTypedDoc(schema)
1492
1496
 
1493
1497
  // Add initial todos
1494
- typedDoc.change(draft => {
1498
+ change(typedDoc, draft => {
1495
1499
  draft.todos.push({ id: "1", text: "Buy milk", completed: false })
1496
1500
  draft.todos.push({ id: "2", text: "Walk dog", completed: false })
1497
1501
  draft.todos.push({ id: "3", text: "Write code", completed: true })
1498
1502
  })
1499
1503
 
1500
1504
  // Test the key developer expectation: find + mutate
1501
- const result = typedDoc.change(draft => {
1505
+ const result = change(typedDoc, draft => {
1502
1506
  // Find a todo and toggle its completion status
1503
1507
  const todo = draft.todos.find(t => t.id === "2")
1504
1508
  if (todo) {
@@ -1510,7 +1514,7 @@ describe("Edge Cases and Error Handling", () => {
1510
1514
  if (codeTodo) {
1511
1515
  codeTodo.text = "Write better code"
1512
1516
  }
1513
- })
1517
+ }).toJSON()
1514
1518
 
1515
1519
  // Verify the mutations persisted to the document state
1516
1520
  expect(result.todos[0]).toEqual({
@@ -1529,8 +1533,8 @@ describe("Edge Cases and Error Handling", () => {
1529
1533
  completed: true,
1530
1534
  }) // Text should be changed
1531
1535
 
1532
- // Also verify via typedDoc.value
1533
- const finalState = typedDoc.value
1536
+ // Also verify via typedDoc.toJSON()
1537
+ const finalState = typedDoc.toJSON()
1534
1538
  expect(finalState.todos[1].completed).toBe(true)
1535
1539
  expect(finalState.todos[2].text).toBe("Write better code")
1536
1540
  })
@@ -1538,10 +1542,10 @@ describe("Edge Cases and Error Handling", () => {
1538
1542
  it("should allow mutation of nested container items found via array methods", () => {
1539
1543
  const schema = Shape.doc({
1540
1544
  articles: Shape.list(
1541
- Shape.map({
1545
+ Shape.struct({
1542
1546
  title: Shape.text(),
1543
1547
  viewCount: Shape.counter(),
1544
- metadata: Shape.plain.object({
1548
+ metadata: Shape.plain.struct({
1545
1549
  author: Shape.plain.string(),
1546
1550
  published: Shape.plain.boolean(),
1547
1551
  }),
@@ -1552,7 +1556,7 @@ describe("Edge Cases and Error Handling", () => {
1552
1556
  const typedDoc = createTypedDoc(schema)
1553
1557
 
1554
1558
  // Add initial articles
1555
- typedDoc.change(draft => {
1559
+ change(typedDoc, draft => {
1556
1560
  draft.articles.push({
1557
1561
  title: "First Article",
1558
1562
  viewCount: 0,
@@ -1566,7 +1570,7 @@ describe("Edge Cases and Error Handling", () => {
1566
1570
  })
1567
1571
 
1568
1572
  // Test mutation of nested containers found via array methods
1569
- const result = typedDoc.change(draft => {
1573
+ const result = change(typedDoc, draft => {
1570
1574
  // Find article by author and modify its nested properties
1571
1575
  const aliceArticle = draft.articles.find(
1572
1576
  article => article.metadata.author === "Alice",
@@ -1590,7 +1594,7 @@ describe("Edge Cases and Error Handling", () => {
1590
1594
  publishedArticle.title.update("Updated Second Article")
1591
1595
  publishedArticle.viewCount.increment(3)
1592
1596
  }
1593
- })
1597
+ }).toJSON()
1594
1598
 
1595
1599
  // Verify all mutations persisted correctly
1596
1600
  expect(result.articles[0].title).toBe("📝 First Article")
@@ -1599,8 +1603,8 @@ describe("Edge Cases and Error Handling", () => {
1599
1603
  expect(result.articles[1].title).toBe("Updated Second Article")
1600
1604
  expect(result.articles[1].viewCount).toBe(8) // 5 + 3
1601
1605
 
1602
- // Verify via typedDoc.value as well
1603
- const finalState = typedDoc.value
1606
+ // Verify via typedDoc.toJSON() as well (use toJSON for plain values)
1607
+ const finalState = typedDoc.toJSON()
1604
1608
  expect(finalState.articles[0].title).toBe("📝 First Article")
1605
1609
  expect(finalState.articles[0].viewCount).toBe(10)
1606
1610
  expect(finalState.articles[1].viewCount).toBe(8)
@@ -1609,7 +1613,7 @@ describe("Edge Cases and Error Handling", () => {
1609
1613
  it("should support common developer patterns with array methods", () => {
1610
1614
  const schema = Shape.doc({
1611
1615
  users: Shape.list(
1612
- Shape.plain.object({
1616
+ Shape.plain.struct({
1613
1617
  id: Shape.plain.string(),
1614
1618
  name: Shape.plain.string(),
1615
1619
  active: Shape.plain.boolean(),
@@ -1621,7 +1625,7 @@ describe("Edge Cases and Error Handling", () => {
1621
1625
  const typedDoc = createTypedDoc(schema)
1622
1626
 
1623
1627
  // Add initial users
1624
- typedDoc.change(draft => {
1628
+ change(typedDoc, draft => {
1625
1629
  draft.users.push({
1626
1630
  id: "1",
1627
1631
  name: "Alice",
@@ -1637,7 +1641,7 @@ describe("Edge Cases and Error Handling", () => {
1637
1641
  })
1638
1642
  })
1639
1643
 
1640
- const result = typedDoc.change(draft => {
1644
+ const result = change(typedDoc, draft => {
1641
1645
  // Pattern 1: Find and toggle boolean
1642
1646
  const inactiveUser = draft.users.find(user => !user.active)
1643
1647
  if (inactiveUser) {
@@ -1662,7 +1666,7 @@ describe("Edge Cases and Error Handling", () => {
1662
1666
  if (firstUser) {
1663
1667
  firstUser.name = `👑 ${firstUser.name}`
1664
1668
  }
1665
- })
1669
+ }).toJSON()
1666
1670
 
1667
1671
  // Verify all patterns worked
1668
1672
  expect(result.users[0].name).toBe("👑 Alice")
@@ -1673,7 +1677,7 @@ describe("Edge Cases and Error Handling", () => {
1673
1677
  expect(result.users[2].score).toBe(180) // 120 + 50 VIP + 10 bonus
1674
1678
 
1675
1679
  // Verify persistence
1676
- const finalState = typedDoc.value
1680
+ const finalState = typedDoc.toJSON()
1677
1681
  expect(finalState.users.every(user => user.active)).toBe(true)
1678
1682
  expect(finalState.users[2].name).toContain("VIP")
1679
1683
  })
@@ -1681,7 +1685,7 @@ describe("Edge Cases and Error Handling", () => {
1681
1685
  it("should handle edge cases in find-and-mutate patterns", () => {
1682
1686
  const schema = Shape.doc({
1683
1687
  items: Shape.list(
1684
- Shape.plain.object({
1688
+ Shape.plain.struct({
1685
1689
  id: Shape.plain.string(),
1686
1690
  value: Shape.plain.number(),
1687
1691
  }),
@@ -1690,7 +1694,7 @@ describe("Edge Cases and Error Handling", () => {
1690
1694
 
1691
1695
  const typedDoc = createTypedDoc(schema)
1692
1696
 
1693
- const result = typedDoc.change(draft => {
1697
+ const result = change(typedDoc, draft => {
1694
1698
  // Add some items
1695
1699
  draft.items.push({ id: "1", value: 10 })
1696
1700
  draft.items.push({ id: "2", value: 20 })
@@ -1717,7 +1721,7 @@ describe("Edge Cases and Error Handling", () => {
1717
1721
  item.value += 5
1718
1722
  }
1719
1723
  }
1720
- })
1724
+ }).toJSON()
1721
1725
 
1722
1726
  // Verify mutations worked correctly
1723
1727
  expect(result.items).toHaveLength(2)
@@ -1737,7 +1741,7 @@ describe("Edge Cases and Error Handling", () => {
1737
1741
 
1738
1742
  const typedDoc = createTypedDoc(schema)
1739
1743
 
1740
- typedDoc.change(draft => {
1744
+ change(typedDoc, draft => {
1741
1745
  draft.items.push("a")
1742
1746
  draft.items.push("b")
1743
1747
  draft.items.push("c")
@@ -1757,7 +1761,7 @@ describe("Edge Cases and Error Handling", () => {
1757
1761
 
1758
1762
  const typedDoc = createTypedDoc(schema)
1759
1763
 
1760
- typedDoc.change(draft => {
1764
+ change(typedDoc, draft => {
1761
1765
  draft.items.push("a")
1762
1766
  draft.items.push("b")
1763
1767
  draft.items.push("c")
@@ -1785,7 +1789,7 @@ describe("Edge Cases and Error Handling", () => {
1785
1789
 
1786
1790
  const typedDoc = createTypedDoc(schema)
1787
1791
 
1788
- typedDoc.change(draft => {
1792
+ change(typedDoc, draft => {
1789
1793
  draft.items.push("a")
1790
1794
  draft.items.push("b")
1791
1795
  draft.items.push("c")
@@ -1804,7 +1808,7 @@ describe("Edge Cases and Error Handling", () => {
1804
1808
 
1805
1809
  const typedDoc = createTypedDoc(schema)
1806
1810
 
1807
- typedDoc.change(draft => {
1811
+ change(typedDoc, draft => {
1808
1812
  draft.items.push("a")
1809
1813
  draft.items.push("b")
1810
1814
  draft.items.push("c")
@@ -1822,7 +1826,7 @@ describe("Edge Cases and Error Handling", () => {
1822
1826
 
1823
1827
  const typedDoc = createTypedDoc(schema)
1824
1828
 
1825
- typedDoc.change(draft => {
1829
+ change(typedDoc, draft => {
1826
1830
  draft.items.push("a")
1827
1831
  draft.items.push("b")
1828
1832
  draft.items.push("c")
@@ -1848,7 +1852,7 @@ describe("Edge Cases and Error Handling", () => {
1848
1852
 
1849
1853
  const typedDoc = createTypedDoc(schema)
1850
1854
 
1851
- typedDoc.change(draft => {
1855
+ change(typedDoc, draft => {
1852
1856
  // slice() on empty list returns []
1853
1857
  expect(draft.items.slice()).toEqual([])
1854
1858
  expect(draft.items.slice(0, 10)).toEqual([])
@@ -1859,7 +1863,7 @@ describe("Edge Cases and Error Handling", () => {
1859
1863
  it("should allow mutations to persist", () => {
1860
1864
  const schema = Shape.doc({
1861
1865
  items: Shape.list(
1862
- Shape.plain.object({
1866
+ Shape.plain.struct({
1863
1867
  id: Shape.plain.string(),
1864
1868
  value: Shape.plain.number(),
1865
1869
  }),
@@ -1868,7 +1872,7 @@ describe("Edge Cases and Error Handling", () => {
1868
1872
 
1869
1873
  const typedDoc = createTypedDoc(schema)
1870
1874
 
1871
- typedDoc.change(draft => {
1875
+ change(typedDoc, draft => {
1872
1876
  draft.items.push({ id: "1", value: 10 })
1873
1877
  draft.items.push({ id: "2", value: 20 })
1874
1878
  draft.items.push({ id: "3", value: 30 })
@@ -1876,12 +1880,12 @@ describe("Edge Cases and Error Handling", () => {
1876
1880
  })
1877
1881
 
1878
1882
  // Modify items from slice and verify changes persist
1879
- const result = typedDoc.change(draft => {
1883
+ const result = change(typedDoc, draft => {
1880
1884
  const middleItems = draft.items.slice(1, 3)
1881
1885
  // Mutate the sliced items
1882
1886
  middleItems[0].value = 200
1883
1887
  middleItems[1].value = 300
1884
- })
1888
+ }).toJSON()
1885
1889
 
1886
1890
  // Verify mutations persisted to the original list
1887
1891
  expect(result.items[0].value).toBe(10) // unchanged
@@ -1897,7 +1901,7 @@ describe("Edge Cases and Error Handling", () => {
1897
1901
 
1898
1902
  const typedDoc = createTypedDoc(schema)
1899
1903
 
1900
- typedDoc.change(draft => {
1904
+ change(typedDoc, draft => {
1901
1905
  draft.tasks.push("task1")
1902
1906
  draft.tasks.push("task2")
1903
1907
  draft.tasks.push("task3")
@@ -1915,7 +1919,7 @@ describe("Edge Cases and Error Handling", () => {
1915
1919
  it("should work with nested container items", () => {
1916
1920
  const schema = Shape.doc({
1917
1921
  articles: Shape.list(
1918
- Shape.map({
1922
+ Shape.struct({
1919
1923
  title: Shape.text(),
1920
1924
  views: Shape.counter(),
1921
1925
  }),
@@ -1924,19 +1928,19 @@ describe("Edge Cases and Error Handling", () => {
1924
1928
 
1925
1929
  const typedDoc = createTypedDoc(schema)
1926
1930
 
1927
- typedDoc.change(draft => {
1931
+ change(typedDoc, draft => {
1928
1932
  draft.articles.push({ title: "Article 1", views: 10 })
1929
1933
  draft.articles.push({ title: "Article 2", views: 20 })
1930
1934
  draft.articles.push({ title: "Article 3", views: 30 })
1931
1935
  })
1932
1936
 
1933
- const result = typedDoc.change(draft => {
1937
+ const result = change(typedDoc, draft => {
1934
1938
  const sliced = draft.articles.slice(0, 2)
1935
1939
  // Mutate nested containers in sliced items
1936
1940
  sliced[0].title.update("Updated Article 1")
1937
1941
  sliced[0].views.increment(5)
1938
1942
  sliced[1].views.increment(10)
1939
- })
1943
+ }).toJSON()
1940
1944
 
1941
1945
  // Verify mutations persisted
1942
1946
  expect(result.articles[0].title).toBe("Updated Article 1")
@@ -1963,12 +1967,12 @@ describe("Edge Cases and Error Handling", () => {
1963
1967
  */
1964
1968
  it("should call toJSON() without error when Record has entries with partial CRDT data", () => {
1965
1969
  // Schema with a Record containing Maps (similar to user's tomState schema)
1966
- const StudentStateSchema = Shape.map({
1970
+ const StudentStateSchema = Shape.struct({
1967
1971
  peerId: Shape.plain.string(),
1968
1972
  authorName: Shape.plain.string(),
1969
1973
  authorColor: Shape.plain.string(),
1970
1974
  history: Shape.list(
1971
- Shape.map({
1975
+ Shape.struct({
1972
1976
  timestamp: Shape.plain.number(),
1973
1977
  value: Shape.plain.string(),
1974
1978
  }),
@@ -1992,11 +1996,12 @@ describe("Edge Cases and Error Handling", () => {
1992
1996
  // Note: authorColor is NOT set - this should fall back to placeholder default
1993
1997
 
1994
1998
  // Wrap with TypedDoc
1995
- const typedDoc = new TypedDoc(DocSchema, loroDoc)
1999
+ const typedDoc = createTypedDoc(DocSchema, loroDoc)
1996
2000
 
1997
2001
  // This should not throw "placeholder required"
1998
2002
  expect(() => {
1999
- const json = typedDoc.value.toJSON()
2003
+ // Use typedDoc.toJSON() to get plain values
2004
+ const json = typedDoc.toJSON()
2000
2005
  // Verify the result has placeholder defaults for missing fields
2001
2006
  expect(json.students["peer-123"].peerId).toBe("peer-123")
2002
2007
  expect(json.students["peer-123"].authorName).toBe("Alice")