@loro-extended/change 0.8.1 → 0.9.1

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 (41) hide show
  1. package/README.md +78 -0
  2. package/dist/index.d.ts +199 -43
  3. package/dist/index.js +642 -429
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/change.test.ts +277 -1
  7. package/src/conversion.test.ts +72 -72
  8. package/src/conversion.ts +5 -5
  9. package/src/discriminated-union-assignability.test.ts +45 -0
  10. package/src/discriminated-union-tojson.test.ts +128 -0
  11. package/src/index.ts +7 -0
  12. package/src/overlay-recursion.test.ts +325 -0
  13. package/src/overlay.ts +45 -8
  14. package/src/placeholder-proxy.test.ts +52 -0
  15. package/src/placeholder-proxy.ts +37 -0
  16. package/src/presence-interface.ts +52 -0
  17. package/src/shape.ts +44 -50
  18. package/src/typed-doc.ts +4 -4
  19. package/src/typed-presence.ts +96 -0
  20. package/src/{draft-nodes → typed-refs}/base.ts +14 -4
  21. package/src/{draft-nodes → typed-refs}/counter.test.ts +1 -1
  22. package/src/{draft-nodes → typed-refs}/counter.ts +9 -3
  23. package/src/{draft-nodes → typed-refs}/doc.ts +32 -25
  24. package/src/typed-refs/json-compatibility.test.ts +255 -0
  25. package/src/{draft-nodes → typed-refs}/list-base.ts +115 -42
  26. package/src/{draft-nodes → typed-refs}/list.test.ts +1 -1
  27. package/src/{draft-nodes → typed-refs}/list.ts +4 -4
  28. package/src/{draft-nodes → typed-refs}/map.ts +50 -66
  29. package/src/{draft-nodes → typed-refs}/movable-list.test.ts +1 -1
  30. package/src/{draft-nodes → typed-refs}/movable-list.ts +6 -6
  31. package/src/{draft-nodes → typed-refs}/proxy-handlers.ts +25 -26
  32. package/src/{draft-nodes → typed-refs}/record.test.ts +78 -9
  33. package/src/typed-refs/record.ts +193 -0
  34. package/src/{draft-nodes → typed-refs}/text.ts +13 -3
  35. package/src/{draft-nodes → typed-refs}/tree.ts +6 -3
  36. package/src/typed-refs/utils.ts +177 -0
  37. package/src/types.test.ts +97 -2
  38. package/src/types.ts +62 -5
  39. package/src/draft-nodes/counter.md +0 -31
  40. package/src/draft-nodes/record.ts +0 -177
  41. package/src/draft-nodes/utils.ts +0 -96
@@ -9,7 +9,7 @@ import {
9
9
  type LoroText,
10
10
  } from "loro-crdt"
11
11
  import { describe, expect, it } from "vitest"
12
- import { convertInputToNode } from "./conversion.js"
12
+ import { convertInputToRef } from "./conversion.js"
13
13
  import { Shape } from "./shape.js"
14
14
  import {
15
15
  isContainer,
@@ -24,7 +24,7 @@ describe("Conversion Functions", () => {
24
24
  describe("convertInputToNode - Text Conversion", () => {
25
25
  it("should convert string to LoroText", () => {
26
26
  const shape = Shape.text()
27
- const result = convertInputToNode("Hello World", shape)
27
+ const result = convertInputToRef("Hello World", shape)
28
28
 
29
29
  expect(isContainer(result)).toBe(true)
30
30
  expect(isLoroText(result as any)).toBe(true)
@@ -35,7 +35,7 @@ describe("Conversion Functions", () => {
35
35
 
36
36
  it("should handle empty string", () => {
37
37
  const shape = Shape.text()
38
- const result = convertInputToNode("", shape)
38
+ const result = convertInputToRef("", shape)
39
39
 
40
40
  expect(isLoroText(result as any)).toBe(true)
41
41
  const text = result as LoroText
@@ -53,7 +53,7 @@ describe("Conversion Functions", () => {
53
53
  ]
54
54
 
55
55
  for (const testString of testStrings) {
56
- const result = convertInputToNode(testString, shape)
56
+ const result = convertInputToRef(testString, shape)
57
57
  expect(isLoroText(result as any)).toBe(true)
58
58
  const text = result as LoroText
59
59
  expect(text.toString()).toBe(testString)
@@ -63,19 +63,19 @@ describe("Conversion Functions", () => {
63
63
  it("should throw error for non-string input", () => {
64
64
  const shape = Shape.text()
65
65
 
66
- expect(() => convertInputToNode(123 as any, shape)).toThrow(
66
+ expect(() => convertInputToRef(123 as any, shape)).toThrow(
67
67
  "string expected",
68
68
  )
69
- expect(() => convertInputToNode(null as any, shape)).toThrow(
69
+ expect(() => convertInputToRef(null as any, shape)).toThrow(
70
70
  "string expected",
71
71
  )
72
- expect(() => convertInputToNode([] as any, shape)).toThrow(
72
+ expect(() => convertInputToRef([] as any, shape)).toThrow(
73
73
  "string expected",
74
74
  )
75
- expect(() => convertInputToNode({} as any, shape)).toThrow(
75
+ expect(() => convertInputToRef({} as any, shape)).toThrow(
76
76
  "string expected",
77
77
  )
78
- expect(() => convertInputToNode(true as any, shape)).toThrow(
78
+ expect(() => convertInputToRef(true as any, shape)).toThrow(
79
79
  "string expected",
80
80
  )
81
81
  })
@@ -84,7 +84,7 @@ describe("Conversion Functions", () => {
84
84
  describe("convertInputToNode - Counter Conversion", () => {
85
85
  it("should convert number to LoroCounter", () => {
86
86
  const shape = Shape.counter()
87
- const result = convertInputToNode(42, shape)
87
+ const result = convertInputToRef(42, shape)
88
88
 
89
89
  expect(isContainer(result)).toBe(true)
90
90
  expect(isLoroCounter(result as any)).toBe(true)
@@ -95,7 +95,7 @@ describe("Conversion Functions", () => {
95
95
 
96
96
  it("should handle zero value", () => {
97
97
  const shape = Shape.counter()
98
- const result = convertInputToNode(0, shape)
98
+ const result = convertInputToRef(0, shape)
99
99
 
100
100
  expect(isLoroCounter(result as any)).toBe(true)
101
101
  const counter = result as LoroCounter
@@ -104,7 +104,7 @@ describe("Conversion Functions", () => {
104
104
 
105
105
  it("should handle negative numbers", () => {
106
106
  const shape = Shape.counter()
107
- const result = convertInputToNode(-15, shape)
107
+ const result = convertInputToRef(-15, shape)
108
108
 
109
109
  expect(isLoroCounter(result as any)).toBe(true)
110
110
  const counter = result as LoroCounter
@@ -113,7 +113,7 @@ describe("Conversion Functions", () => {
113
113
 
114
114
  it("should handle floating point numbers", () => {
115
115
  const shape = Shape.counter()
116
- const result = convertInputToNode(3.14, shape)
116
+ const result = convertInputToRef(3.14, shape)
117
117
 
118
118
  expect(isLoroCounter(result as any)).toBe(true)
119
119
  const counter = result as LoroCounter
@@ -123,19 +123,19 @@ describe("Conversion Functions", () => {
123
123
  it("should throw error for non-number input", () => {
124
124
  const shape = Shape.counter()
125
125
 
126
- expect(() => convertInputToNode("123" as any, shape)).toThrow(
126
+ expect(() => convertInputToRef("123" as any, shape)).toThrow(
127
127
  "number expected",
128
128
  )
129
- expect(() => convertInputToNode(null as any, shape)).toThrow(
129
+ expect(() => convertInputToRef(null as any, shape)).toThrow(
130
130
  "number expected",
131
131
  )
132
- expect(() => convertInputToNode([] as any, shape)).toThrow(
132
+ expect(() => convertInputToRef([] as any, shape)).toThrow(
133
133
  "number expected",
134
134
  )
135
- expect(() => convertInputToNode({} as any, shape)).toThrow(
135
+ expect(() => convertInputToRef({} as any, shape)).toThrow(
136
136
  "number expected",
137
137
  )
138
- expect(() => convertInputToNode(true as any, shape)).toThrow(
138
+ expect(() => convertInputToRef(true as any, shape)).toThrow(
139
139
  "number expected",
140
140
  )
141
141
  })
@@ -144,7 +144,7 @@ describe("Conversion Functions", () => {
144
144
  describe("convertInputToNode - List Conversion", () => {
145
145
  it("should convert array to LoroList with value items", () => {
146
146
  const shape = Shape.list(Shape.plain.string())
147
- const result = convertInputToNode(["hello", "world"], shape)
147
+ const result = convertInputToRef(["hello", "world"], shape)
148
148
 
149
149
  expect(isContainer(result)).toBe(true)
150
150
  expect(isLoroList(result as any)).toBe(true)
@@ -157,7 +157,7 @@ describe("Conversion Functions", () => {
157
157
 
158
158
  it("should convert array to LoroList with container items", () => {
159
159
  const shape = Shape.list(Shape.text())
160
- const result = convertInputToNode(["first", "second"], shape)
160
+ const result = convertInputToRef(["first", "second"], shape)
161
161
 
162
162
  expect(isLoroList(result as any)).toBe(true)
163
163
  const list = result as LoroList
@@ -172,7 +172,7 @@ describe("Conversion Functions", () => {
172
172
 
173
173
  it("should handle empty array", () => {
174
174
  const shape = Shape.list(Shape.plain.string())
175
- const result = convertInputToNode([], shape)
175
+ const result = convertInputToRef([], shape)
176
176
 
177
177
  expect(isLoroList(result as any)).toBe(true)
178
178
  const list = result as LoroList
@@ -181,7 +181,7 @@ describe("Conversion Functions", () => {
181
181
 
182
182
  it("should handle mixed value types in list", () => {
183
183
  const shape = Shape.list(Shape.plain.number())
184
- const result = convertInputToNode([1, 2.5, -3, 0], shape)
184
+ const result = convertInputToRef([1, 2.5, -3, 0], shape)
185
185
 
186
186
  expect(isLoroList(result as any)).toBe(true)
187
187
  const list = result as LoroList
@@ -195,7 +195,7 @@ describe("Conversion Functions", () => {
195
195
  it("should return plain array for value shape", () => {
196
196
  const shape = Shape.plain.array(Shape.plain.string())
197
197
  const input = ["hello", "world"]
198
- const result = convertInputToNode(input, shape)
198
+ const result = convertInputToRef(input, shape)
199
199
 
200
200
  expect(isContainer(result)).toBe(false)
201
201
  expect(Array.isArray(result)).toBe(true)
@@ -204,7 +204,7 @@ describe("Conversion Functions", () => {
204
204
 
205
205
  it("should handle nested container conversion", () => {
206
206
  const shape = Shape.list(Shape.counter())
207
- const result = convertInputToNode([5, 10, 15], shape)
207
+ const result = convertInputToRef([5, 10, 15], shape)
208
208
 
209
209
  expect(isLoroList(result as any)).toBe(true)
210
210
  const list = result as LoroList
@@ -217,16 +217,16 @@ describe("Conversion Functions", () => {
217
217
  it("should throw error for non-array input", () => {
218
218
  const shape = Shape.list(Shape.plain.string())
219
219
 
220
- expect(() => convertInputToNode("not array" as any, shape)).toThrow(
220
+ expect(() => convertInputToRef("not array" as any, shape)).toThrow(
221
221
  "array expected",
222
222
  )
223
- expect(() => convertInputToNode(123 as any, shape)).toThrow(
223
+ expect(() => convertInputToRef(123 as any, shape)).toThrow(
224
224
  "array expected",
225
225
  )
226
- expect(() => convertInputToNode({} as any, shape)).toThrow(
226
+ expect(() => convertInputToRef({} as any, shape)).toThrow(
227
227
  "array expected",
228
228
  )
229
- expect(() => convertInputToNode(null as any, shape)).toThrow(
229
+ expect(() => convertInputToRef(null as any, shape)).toThrow(
230
230
  "array expected",
231
231
  )
232
232
  })
@@ -235,7 +235,7 @@ describe("Conversion Functions", () => {
235
235
  describe("convertInputToNode - MovableList Conversion", () => {
236
236
  it("should convert array to LoroMovableList with value items", () => {
237
237
  const shape = Shape.movableList(Shape.plain.string())
238
- const result = convertInputToNode(["first", "second", "third"], shape)
238
+ const result = convertInputToRef(["first", "second", "third"], shape)
239
239
 
240
240
  expect(isContainer(result)).toBe(true)
241
241
  expect(isLoroMovableList(result as any)).toBe(true)
@@ -249,7 +249,7 @@ describe("Conversion Functions", () => {
249
249
 
250
250
  it("should convert array to LoroMovableList with container items", () => {
251
251
  const shape = Shape.movableList(Shape.counter())
252
- const result = convertInputToNode([1, 5, 10], shape)
252
+ const result = convertInputToRef([1, 5, 10], shape)
253
253
 
254
254
  expect(isLoroMovableList(result as any)).toBe(true)
255
255
  const list = result as LoroMovableList
@@ -261,7 +261,7 @@ describe("Conversion Functions", () => {
261
261
 
262
262
  it("should handle empty movable list", () => {
263
263
  const shape = Shape.movableList(Shape.plain.boolean())
264
- const result = convertInputToNode([], shape)
264
+ const result = convertInputToRef([], shape)
265
265
 
266
266
  expect(isLoroMovableList(result as any)).toBe(true)
267
267
  const list = result as LoroMovableList
@@ -271,7 +271,7 @@ describe("Conversion Functions", () => {
271
271
  it("should return plain array for value shape", () => {
272
272
  const shape = Shape.plain.array(Shape.plain.number())
273
273
  const input = [1, 2, 3]
274
- const result = convertInputToNode(input, shape)
274
+ const result = convertInputToRef(input, shape)
275
275
 
276
276
  expect(isContainer(result)).toBe(false)
277
277
  expect(Array.isArray(result)).toBe(true)
@@ -281,16 +281,16 @@ describe("Conversion Functions", () => {
281
281
  it("should throw error for non-array input", () => {
282
282
  const shape = Shape.movableList(Shape.plain.string())
283
283
 
284
- expect(() => convertInputToNode("not array" as any, shape)).toThrow(
284
+ expect(() => convertInputToRef("not array" as any, shape)).toThrow(
285
285
  "array expected",
286
286
  )
287
- expect(() => convertInputToNode(123 as any, shape)).toThrow(
287
+ expect(() => convertInputToRef(123 as any, shape)).toThrow(
288
288
  "array expected",
289
289
  )
290
- expect(() => convertInputToNode({} as any, shape)).toThrow(
290
+ expect(() => convertInputToRef({} as any, shape)).toThrow(
291
291
  "array expected",
292
292
  )
293
- expect(() => convertInputToNode(null as any, shape)).toThrow(
293
+ expect(() => convertInputToRef(null as any, shape)).toThrow(
294
294
  "array expected",
295
295
  )
296
296
  })
@@ -310,7 +310,7 @@ describe("Conversion Functions", () => {
310
310
  active: true,
311
311
  }
312
312
 
313
- const result = convertInputToNode(input, shape)
313
+ const result = convertInputToRef(input, shape)
314
314
 
315
315
  expect(isContainer(result)).toBe(true)
316
316
  expect(isLoroMap(result as any)).toBe(true)
@@ -332,7 +332,7 @@ describe("Conversion Functions", () => {
332
332
  count: 42,
333
333
  }
334
334
 
335
- const result = convertInputToNode(input, shape)
335
+ const result = convertInputToRef(input, shape)
336
336
 
337
337
  expect(isLoroMap(result as any)).toBe(true)
338
338
  const map = result as LoroMap
@@ -344,7 +344,7 @@ describe("Conversion Functions", () => {
344
344
 
345
345
  it("should handle empty object", () => {
346
346
  const shape = Shape.map({})
347
- const result = convertInputToNode({}, shape)
347
+ const result = convertInputToRef({}, shape)
348
348
 
349
349
  expect(isLoroMap(result as any)).toBe(true)
350
350
  const map = result as LoroMap
@@ -361,7 +361,7 @@ describe("Conversion Functions", () => {
361
361
  extraProp: "should be ignored", // This should be set as-is
362
362
  }
363
363
 
364
- const result = convertInputToNode(input, shape)
364
+ const result = convertInputToRef(input, shape)
365
365
 
366
366
  expect(isLoroMap(result as any)).toBe(true)
367
367
  const map = result as LoroMap
@@ -389,7 +389,7 @@ describe("Conversion Functions", () => {
389
389
  },
390
390
  }
391
391
 
392
- const result = convertInputToNode(input, shape)
392
+ const result = convertInputToRef(input, shape)
393
393
 
394
394
  expect(isLoroMap(result as any)).toBe(true)
395
395
  const map = result as LoroMap
@@ -404,7 +404,7 @@ describe("Conversion Functions", () => {
404
404
  })
405
405
 
406
406
  const input = { name: "John", age: 30 }
407
- const result = convertInputToNode(input, shape)
407
+ const result = convertInputToRef(input, shape)
408
408
 
409
409
  expect(isContainer(result)).toBe(false)
410
410
  expect(typeof result).toBe("object")
@@ -416,16 +416,16 @@ describe("Conversion Functions", () => {
416
416
  name: Shape.plain.string(),
417
417
  })
418
418
 
419
- expect(() => convertInputToNode("not object" as any, shape)).toThrow(
419
+ expect(() => convertInputToRef("not object" as any, shape)).toThrow(
420
420
  "object expected",
421
421
  )
422
- expect(() => convertInputToNode(123 as any, shape)).toThrow(
422
+ expect(() => convertInputToRef(123 as any, shape)).toThrow(
423
423
  "object expected",
424
424
  )
425
- expect(() => convertInputToNode([] as any, shape)).toThrow(
425
+ expect(() => convertInputToRef([] as any, shape)).toThrow(
426
426
  "object expected",
427
427
  )
428
- expect(() => convertInputToNode(null as any, shape)).toThrow(
428
+ expect(() => convertInputToRef(null as any, shape)).toThrow(
429
429
  "object expected",
430
430
  )
431
431
  })
@@ -438,10 +438,10 @@ describe("Conversion Functions", () => {
438
438
  const booleanShape = Shape.plain.boolean()
439
439
  const nullShape = Shape.plain.null()
440
440
 
441
- expect(convertInputToNode("hello", stringShape)).toBe("hello")
442
- expect(convertInputToNode(42, numberShape)).toBe(42)
443
- expect(convertInputToNode(true, booleanShape)).toBe(true)
444
- expect(convertInputToNode(null, nullShape)).toBe(null)
441
+ expect(convertInputToRef("hello", stringShape)).toBe("hello")
442
+ expect(convertInputToRef(42, numberShape)).toBe(42)
443
+ expect(convertInputToRef(true, booleanShape)).toBe(true)
444
+ expect(convertInputToRef(null, nullShape)).toBe(null)
445
445
  })
446
446
 
447
447
  it("should handle complex value shapes", () => {
@@ -454,15 +454,15 @@ describe("Conversion Functions", () => {
454
454
  const arrayInput = ["a", "b", "c"]
455
455
  const objectInput = { name: "test", count: 5 }
456
456
 
457
- expect(convertInputToNode(arrayInput, arrayShape)).toEqual(arrayInput)
458
- expect(convertInputToNode(objectInput, objectShape)).toEqual(objectInput)
457
+ expect(convertInputToRef(arrayInput, arrayShape)).toEqual(arrayInput)
458
+ expect(convertInputToRef(objectInput, objectShape)).toEqual(objectInput)
459
459
  })
460
460
 
461
461
  it("should handle Uint8Array values", () => {
462
462
  const shape = Shape.plain.uint8Array()
463
463
  const input = new Uint8Array([1, 2, 3, 4])
464
464
 
465
- const result = convertInputToNode(input, shape)
465
+ const result = convertInputToRef(input, shape)
466
466
  expect(result).toBe(input)
467
467
  expect(result instanceof Uint8Array).toBe(true)
468
468
  })
@@ -472,7 +472,7 @@ describe("Conversion Functions", () => {
472
472
  it("should throw error for tree type (unimplemented)", () => {
473
473
  const shape = Shape.tree(Shape.map({}))
474
474
 
475
- expect(() => convertInputToNode({}, shape)).toThrow(
475
+ expect(() => convertInputToRef({}, shape)).toThrow(
476
476
  "tree type unimplemented",
477
477
  )
478
478
  })
@@ -480,7 +480,7 @@ describe("Conversion Functions", () => {
480
480
  it("should throw error for invalid value shape", () => {
481
481
  const invalidShape = { _type: "value", valueType: "invalid" } as any
482
482
 
483
- expect(() => convertInputToNode("test", invalidShape)).toThrow(
483
+ expect(() => convertInputToRef("test", invalidShape)).toThrow(
484
484
  "value expected",
485
485
  )
486
486
  })
@@ -515,7 +515,7 @@ describe("Conversion Functions", () => {
515
515
  },
516
516
  ]
517
517
 
518
- const result = convertInputToNode(input, shape)
518
+ const result = convertInputToRef(input, shape)
519
519
 
520
520
  expect(isLoroList(result as any)).toBe(true)
521
521
  const list = result as LoroList
@@ -545,7 +545,7 @@ describe("Conversion Functions", () => {
545
545
  },
546
546
  }
547
547
 
548
- const result = convertInputToNode(input, shape)
548
+ const result = convertInputToRef(input, shape)
549
549
 
550
550
  expect(isLoroMap(result as any)).toBe(true)
551
551
  const map = result as LoroMap
@@ -564,7 +564,7 @@ describe("Conversion Functions", () => {
564
564
  [7, 8, 9],
565
565
  ]
566
566
 
567
- const result = convertInputToNode(input, shape)
567
+ const result = convertInputToRef(input, shape)
568
568
 
569
569
  expect(isLoroList(result as any)).toBe(true)
570
570
  const outerList = result as LoroList
@@ -585,7 +585,7 @@ describe("Conversion Functions", () => {
585
585
  { id: "2", title: "Task 2", completed: true },
586
586
  ]
587
587
 
588
- const result = convertInputToNode(input, shape)
588
+ const result = convertInputToRef(input, shape)
589
589
 
590
590
  expect(isLoroMovableList(result as any)).toBe(true)
591
591
  const list = result as LoroMovableList
@@ -598,8 +598,8 @@ describe("Conversion Functions", () => {
598
598
  const nullShape = Shape.plain.null()
599
599
  const undefinedShape = Shape.plain.undefined()
600
600
 
601
- expect(convertInputToNode(null, nullShape)).toBe(null)
602
- expect(convertInputToNode(undefined, undefinedShape)).toBe(undefined)
601
+ expect(convertInputToRef(null, nullShape)).toBe(null)
602
+ expect(convertInputToRef(undefined, undefinedShape)).toBe(undefined)
603
603
  })
604
604
 
605
605
  it("should handle empty containers", () => {
@@ -607,9 +607,9 @@ describe("Conversion Functions", () => {
607
607
  const emptyMapShape = Shape.map({})
608
608
  const emptyMovableListShape = Shape.movableList(Shape.plain.number())
609
609
 
610
- const emptyList = convertInputToNode([], emptyListShape)
611
- const emptyMap = convertInputToNode({}, emptyMapShape)
612
- const emptyMovableList = convertInputToNode([], emptyMovableListShape)
610
+ const emptyList = convertInputToRef([], emptyListShape)
611
+ const emptyMap = convertInputToRef({}, emptyMapShape)
612
+ const emptyMovableList = convertInputToRef([], emptyMovableListShape)
613
613
 
614
614
  expect(isLoroList(emptyList as any)).toBe(true)
615
615
  expect((emptyList as LoroList).length).toBe(0)
@@ -624,7 +624,7 @@ describe("Conversion Functions", () => {
624
624
  it("should handle very large numbers", () => {
625
625
  const shape = Shape.counter()
626
626
  const largeNumber = Number.MAX_SAFE_INTEGER
627
- const result = convertInputToNode(largeNumber, shape)
627
+ const result = convertInputToRef(largeNumber, shape)
628
628
 
629
629
  expect(isLoroCounter(result as any)).toBe(true)
630
630
  expect((result as LoroCounter).value).toBe(largeNumber)
@@ -633,7 +633,7 @@ describe("Conversion Functions", () => {
633
633
  it("should handle very long strings", () => {
634
634
  const shape = Shape.text()
635
635
  const longString = "a".repeat(10000)
636
- const result = convertInputToNode(longString, shape)
636
+ const result = convertInputToRef(longString, shape)
637
637
 
638
638
  expect(isLoroText(result as any)).toBe(true)
639
639
  expect((result as LoroText).toString()).toBe(longString)
@@ -643,7 +643,7 @@ describe("Conversion Functions", () => {
643
643
  it("should handle arrays with many items", () => {
644
644
  const shape = Shape.list(Shape.plain.number())
645
645
  const largeArray = Array.from({ length: 1000 }, (_, i) => i)
646
- const result = convertInputToNode(largeArray, shape)
646
+ const result = convertInputToRef(largeArray, shape)
647
647
 
648
648
  expect(isLoroList(result as any)).toBe(true)
649
649
  const list = result as LoroList
@@ -663,7 +663,7 @@ describe("Conversion Functions", () => {
663
663
  }
664
664
 
665
665
  const shape = Shape.map(shapes)
666
- const result = convertInputToNode(input, shape)
666
+ const result = convertInputToRef(input, shape)
667
667
 
668
668
  expect(isLoroMap(result as any)).toBe(true)
669
669
  const map = result as LoroMap
@@ -676,7 +676,7 @@ describe("Conversion Functions", () => {
676
676
  describe("convertInputToNode - Type Safety", () => {
677
677
  it("should maintain referential integrity for containers", () => {
678
678
  const shape = Shape.list(Shape.text())
679
- const result = convertInputToNode(["test"], shape)
679
+ const result = convertInputToRef(["test"], shape)
680
680
 
681
681
  expect(isLoroList(result as any)).toBe(true)
682
682
  const list = result as LoroList
@@ -688,8 +688,8 @@ describe("Conversion Functions", () => {
688
688
 
689
689
  it("should create independent container instances", () => {
690
690
  const shape = Shape.counter()
691
- const result1 = convertInputToNode(5, shape)
692
- const result2 = convertInputToNode(5, shape)
691
+ const result1 = convertInputToRef(5, shape)
692
+ const result2 = convertInputToRef(5, shape)
693
693
 
694
694
  expect(isLoroCounter(result1 as any)).toBe(true)
695
695
  expect(isLoroCounter(result2 as any)).toBe(true)
@@ -718,7 +718,7 @@ describe("Conversion Functions", () => {
718
718
  children: ["child1", "child2"],
719
719
  }
720
720
 
721
- const result = convertInputToNode(input, shape)
721
+ const result = convertInputToRef(input, shape)
722
722
 
723
723
  expect(isLoroMap(result as any)).toBe(true)
724
724
  const map = result as LoroMap
package/src/conversion.ts CHANGED
@@ -59,7 +59,7 @@ function convertListInput(
59
59
  const list = new LoroList()
60
60
 
61
61
  for (const item of value) {
62
- const convertedItem = convertInputToNode(item, shape.shape)
62
+ const convertedItem = convertInputToRef(item, shape.shape)
63
63
  if (isContainer(convertedItem)) {
64
64
  list.pushContainer(convertedItem)
65
65
  } else {
@@ -85,7 +85,7 @@ function convertMovableListInput(
85
85
  const list = new LoroMovableList()
86
86
 
87
87
  for (const item of value) {
88
- const convertedItem = convertInputToNode(item, shape.shape)
88
+ const convertedItem = convertInputToRef(item, shape.shape)
89
89
  if (isContainer(convertedItem)) {
90
90
  list.pushContainer(convertedItem)
91
91
  } else {
@@ -111,7 +111,7 @@ function convertMapInput(
111
111
  for (const [k, v] of Object.entries(value)) {
112
112
  const nestedSchema = shape.shapes[k]
113
113
  if (nestedSchema) {
114
- const convertedValue = convertInputToNode(v, nestedSchema)
114
+ const convertedValue = convertInputToRef(v, nestedSchema)
115
115
  if (isContainer(convertedValue)) {
116
116
  map.setContainer(k, convertedValue)
117
117
  } else {
@@ -138,7 +138,7 @@ function convertRecordInput(
138
138
 
139
139
  const map = new LoroMap()
140
140
  for (const [k, v] of Object.entries(value)) {
141
- const convertedValue = convertInputToNode(v, shape.shape)
141
+ const convertedValue = convertInputToRef(v, shape.shape)
142
142
  if (isContainer(convertedValue)) {
143
143
  map.setContainer(k, convertedValue)
144
144
  } else {
@@ -153,7 +153,7 @@ function convertRecordInput(
153
153
  * Main conversion function that transforms input values to appropriate CRDT containers
154
154
  * based on schema definitions
155
155
  */
156
- export function convertInputToNode<Shape extends ContainerOrValueShape>(
156
+ export function convertInputToRef<Shape extends ContainerOrValueShape>(
157
157
  value: Value,
158
158
  shape: Shape,
159
159
  ): Container | Value {
@@ -0,0 +1,45 @@
1
+ import { describe, expectTypeOf, it } from "vitest"
2
+ import { Shape } from "./shape.js"
3
+
4
+ describe("Discriminated Union Placeholder Issue", () => {
5
+ it("should allow discriminated union with placeholder in a map", () => {
6
+ const PauseReasonSchema = Shape.plain.string()
7
+ const ActiveModeSchema = Shape.plain.string()
8
+
9
+ const SessionPhaseSchema = Shape.plain
10
+ .discriminatedUnion("phase", {
11
+ "not-started": Shape.plain.object({
12
+ phase: Shape.plain.string("not-started"),
13
+ }),
14
+
15
+ lobby: Shape.plain.object({
16
+ phase: Shape.plain.string("lobby"),
17
+ }),
18
+ "lobby-paused": Shape.plain.object({
19
+ phase: Shape.plain.string("lobby-paused"),
20
+ reason: PauseReasonSchema,
21
+ }),
22
+
23
+ active: Shape.plain.object({
24
+ phase: Shape.plain.string("active"),
25
+ mode: ActiveModeSchema,
26
+ }),
27
+ "active-paused": Shape.plain.object({
28
+ phase: Shape.plain.string("active-paused"),
29
+ mode: ActiveModeSchema,
30
+ reason: PauseReasonSchema,
31
+ }),
32
+
33
+ ended: Shape.plain.object({
34
+ phase: Shape.plain.string("ended"),
35
+ }),
36
+ })
37
+ .placeholder({ phase: "not-started" })
38
+
39
+ const PhaseTransitionSchema = Shape.map({
40
+ phase: SessionPhaseSchema,
41
+ })
42
+
43
+ expectTypeOf(PhaseTransitionSchema).not.toBeNever()
44
+ })
45
+ })