@voidhash/mimic 1.0.0-beta.15 → 1.0.0-beta.17

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 (112) hide show
  1. package/dist/Document.cjs +0 -3
  2. package/dist/Document.d.mts.map +1 -1
  3. package/dist/Document.mjs +0 -3
  4. package/dist/Document.mjs.map +1 -1
  5. package/dist/EffectSchema.cjs +3 -3
  6. package/dist/EffectSchema.d.cts +5 -5
  7. package/dist/EffectSchema.d.cts.map +1 -1
  8. package/dist/EffectSchema.d.mts +5 -5
  9. package/dist/EffectSchema.d.mts.map +1 -1
  10. package/dist/EffectSchema.mjs +3 -3
  11. package/dist/EffectSchema.mjs.map +1 -1
  12. package/dist/FractionalIndex.mjs.map +1 -1
  13. package/dist/Operation.d.cts +4 -4
  14. package/dist/Operation.d.cts.map +1 -1
  15. package/dist/Operation.d.mts +4 -4
  16. package/dist/Operation.d.mts.map +1 -1
  17. package/dist/Operation.mjs.map +1 -1
  18. package/dist/OperationDefinition.d.cts +2 -2
  19. package/dist/OperationDefinition.d.cts.map +1 -1
  20. package/dist/OperationDefinition.d.mts +2 -2
  21. package/dist/OperationDefinition.d.mts.map +1 -1
  22. package/dist/OperationDefinition.mjs.map +1 -1
  23. package/dist/Presence.mjs.map +1 -1
  24. package/dist/Primitive.d.cts +2 -2
  25. package/dist/Primitive.d.mts +2 -2
  26. package/dist/SchemaJSON.cjs +305 -0
  27. package/dist/SchemaJSON.d.cts +11 -0
  28. package/dist/SchemaJSON.d.cts.map +1 -0
  29. package/dist/SchemaJSON.d.mts +11 -0
  30. package/dist/SchemaJSON.d.mts.map +1 -0
  31. package/dist/SchemaJSON.mjs +301 -0
  32. package/dist/SchemaJSON.mjs.map +1 -0
  33. package/dist/index.cjs +7 -0
  34. package/dist/index.d.cts +2 -1
  35. package/dist/index.d.mts +2 -1
  36. package/dist/index.mjs +2 -1
  37. package/dist/primitives/Array.cjs +12 -2
  38. package/dist/primitives/Array.d.cts.map +1 -1
  39. package/dist/primitives/Array.d.mts.map +1 -1
  40. package/dist/primitives/Array.mjs +12 -2
  41. package/dist/primitives/Array.mjs.map +1 -1
  42. package/dist/primitives/Boolean.mjs.map +1 -1
  43. package/dist/primitives/Either.mjs.map +1 -1
  44. package/dist/primitives/Literal.mjs.map +1 -1
  45. package/dist/primitives/Number.cjs +27 -5
  46. package/dist/primitives/Number.d.cts.map +1 -1
  47. package/dist/primitives/Number.d.mts.map +1 -1
  48. package/dist/primitives/Number.mjs +27 -5
  49. package/dist/primitives/Number.mjs.map +1 -1
  50. package/dist/primitives/String.cjs +44 -13
  51. package/dist/primitives/String.d.cts.map +1 -1
  52. package/dist/primitives/String.d.mts.map +1 -1
  53. package/dist/primitives/String.mjs +44 -13
  54. package/dist/primitives/String.mjs.map +1 -1
  55. package/dist/primitives/Struct.cjs +48 -9
  56. package/dist/primitives/Struct.d.cts +22 -3
  57. package/dist/primitives/Struct.d.cts.map +1 -1
  58. package/dist/primitives/Struct.d.mts +22 -3
  59. package/dist/primitives/Struct.d.mts.map +1 -1
  60. package/dist/primitives/Struct.mjs +48 -9
  61. package/dist/primitives/Struct.mjs.map +1 -1
  62. package/dist/primitives/Union.mjs.map +1 -1
  63. package/dist/primitives/shared.cjs +2 -5
  64. package/dist/primitives/shared.d.cts +2 -4
  65. package/dist/primitives/shared.d.cts.map +1 -1
  66. package/dist/primitives/shared.d.mts +2 -4
  67. package/dist/primitives/shared.d.mts.map +1 -1
  68. package/dist/primitives/shared.mjs +2 -5
  69. package/dist/primitives/shared.mjs.map +1 -1
  70. package/package.json +15 -8
  71. package/src/Document.ts +13 -4
  72. package/src/EffectSchema.ts +3 -3
  73. package/src/FractionalIndex.ts +18 -18
  74. package/src/Operation.ts +5 -5
  75. package/src/OperationDefinition.ts +2 -2
  76. package/src/Presence.ts +3 -3
  77. package/src/SchemaJSON.ts +396 -0
  78. package/src/index.ts +1 -0
  79. package/src/primitives/Array.ts +18 -8
  80. package/src/primitives/Boolean.ts +2 -2
  81. package/src/primitives/Either.ts +2 -2
  82. package/src/primitives/Literal.ts +2 -2
  83. package/src/primitives/Number.ts +44 -22
  84. package/src/primitives/String.ts +61 -34
  85. package/src/primitives/Struct.ts +100 -12
  86. package/src/primitives/Union.ts +1 -1
  87. package/src/primitives/shared.ts +12 -2
  88. package/.turbo/turbo-build.log +0 -270
  89. package/tests/Document.test.ts +0 -557
  90. package/tests/EffectSchema.test.ts +0 -546
  91. package/tests/FractionalIndex.test.ts +0 -377
  92. package/tests/OperationPath.test.ts +0 -151
  93. package/tests/Presence.test.ts +0 -321
  94. package/tests/Primitive.test.ts +0 -381
  95. package/tests/client/ClientDocument.test.ts +0 -1981
  96. package/tests/client/WebSocketTransport.test.ts +0 -1217
  97. package/tests/primitives/Array.test.ts +0 -526
  98. package/tests/primitives/Boolean.test.ts +0 -126
  99. package/tests/primitives/Either.test.ts +0 -707
  100. package/tests/primitives/Lazy.test.ts +0 -143
  101. package/tests/primitives/Literal.test.ts +0 -122
  102. package/tests/primitives/Number.test.ts +0 -133
  103. package/tests/primitives/String.test.ts +0 -128
  104. package/tests/primitives/Struct.test.ts +0 -1044
  105. package/tests/primitives/Tree.test.ts +0 -1139
  106. package/tests/primitives/TreeNode.test.ts +0 -50
  107. package/tests/primitives/Union.test.ts +0 -554
  108. package/tests/server/ServerDocument.test.ts +0 -903
  109. package/tsconfig.build.json +0 -24
  110. package/tsconfig.json +0 -8
  111. package/tsdown.config.ts +0 -18
  112. package/vitest.mts +0 -11
@@ -1,1044 +0,0 @@
1
- import { describe, expect, it } from "@effect/vitest";
2
- import * as Primitive from "../../src/Primitive";
3
- import * as ProxyEnvironment from "../../src/ProxyEnvironment";
4
- import * as OperationPath from "../../src/OperationPath";
5
- import * as Operation from "../../src/Operation";
6
-
7
- const hasOwn = (value: unknown, key: string): boolean =>
8
- Object.prototype.hasOwnProperty.call(value, key);
9
-
10
- describe("StructPrimitive", () => {
11
- describe("proxy", () => {
12
- it("nested field access returns field primitive proxy", () => {
13
- const operations: Operation.Operation<any, any, any>[] = [];
14
- const env = ProxyEnvironment.make((op) => {
15
- operations.push(op);
16
- });
17
-
18
- const structPrimitive = Primitive.Struct({
19
- name: Primitive.String().required(),
20
- title: Primitive.String(),
21
- });
22
-
23
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
24
-
25
- // Access nested field and call set
26
- proxy.name.set("John Doe");
27
-
28
- expect(operations).toHaveLength(1);
29
- expect(operations[0]!.kind).toBe("string.set");
30
- expect(operations[0]!.payload).toBe("John Doe");
31
- expect(operations[0]!.path.toTokens()).toEqual(["name"]);
32
- });
33
-
34
- it("nested field paths are constructed correctly", () => {
35
- const operations: Operation.Operation<any, any, any>[] = [];
36
- const env = ProxyEnvironment.make((op) => {
37
- operations.push(op);
38
- });
39
-
40
- const structPrimitive = Primitive.Struct({
41
- name: Primitive.String(),
42
- email: Primitive.String(),
43
- });
44
-
45
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make("users/0"));
46
-
47
- proxy.email.set("test@example.com");
48
-
49
- expect(operations[0]!.path.toTokens()).toEqual(["users", "0", "email"]);
50
- });
51
-
52
- it("set() on struct generates struct.set operation", () => {
53
- const operations: Operation.Operation<any, any, any>[] = [];
54
- const env = ProxyEnvironment.make((op) => {
55
- operations.push(op);
56
- });
57
-
58
- const structPrimitive = Primitive.Struct({
59
- name: Primitive.String(),
60
- age: Primitive.String(),
61
- });
62
-
63
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
64
-
65
- proxy.set({ name: "Alice", age: "30" });
66
-
67
- expect(operations).toHaveLength(1);
68
- expect(operations[0]!.kind).toBe("struct.set");
69
- expect(operations[0]!.payload).toEqual({ name: "Alice", age: "30" });
70
- });
71
-
72
- it("set() only requires fields that are required and without defaults", () => {
73
- const operations: Operation.Operation<any, any, any>[] = [];
74
- const env = ProxyEnvironment.make((op) => {
75
- operations.push(op);
76
- });
77
-
78
- const structPrimitive = Primitive.Struct({
79
- name: Primitive.String().required().default("John Doe"),
80
- age: Primitive.Number().required(),
81
- email: Primitive.String(),
82
- });
83
-
84
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
85
- proxy.set({ age: 30 });
86
-
87
- expect(operations).toHaveLength(1);
88
- expect(operations[0]!.kind).toBe("struct.set");
89
- expect(operations[0]!.payload).toEqual({ name: "John Doe", age: 30 });
90
- expect(hasOwn(operations[0]!.payload, "email")).toBe(false);
91
- });
92
-
93
- it("set() only requires fields that are required and without defaults", () => {
94
- const operations: Operation.Operation<any, any, any>[] = [];
95
- const env = ProxyEnvironment.make((op) => {
96
- operations.push(op);
97
- });
98
-
99
- const structPrimitive = Primitive.Struct({
100
- name: Primitive.String().required().default("John Doe"),
101
- age: Primitive.Number().required(),
102
- email: Primitive.String(),
103
- }).default({ age: 30 });
104
-
105
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
106
- proxy.set({ age: 30 });
107
-
108
- expect(operations).toHaveLength(1);
109
- expect(operations[0]!.kind).toBe("struct.set");
110
- expect(operations[0]!.payload).toEqual({ name: "John Doe", age: 30 });
111
- expect(hasOwn(operations[0]!.payload, "email")).toBe(false);
112
- });
113
-
114
- it("set() prunes optional keys explicitly set to undefined", () => {
115
- const operations: Operation.Operation<any, any, any>[] = [];
116
- const env = ProxyEnvironment.make((op) => {
117
- operations.push(op);
118
- });
119
-
120
- const structPrimitive = Primitive.Struct({
121
- name: Primitive.String().required(),
122
- email: Primitive.String(),
123
- });
124
-
125
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
126
- proxy.set({ name: "Alice", email: undefined });
127
-
128
- expect(operations).toHaveLength(1);
129
- expect(operations[0]!.kind).toBe("struct.set");
130
- expect(operations[0]!.payload).toEqual({ name: "Alice" });
131
- expect(hasOwn(operations[0]!.payload, "email")).toBe(false);
132
- });
133
-
134
- it("set() prunes optional keys explicitly set to null", () => {
135
- const operations: Operation.Operation<any, any, any>[] = [];
136
- const env = ProxyEnvironment.make((op) => {
137
- operations.push(op);
138
- });
139
-
140
- const structPrimitive = Primitive.Struct({
141
- name: Primitive.String().required(),
142
- email: Primitive.String(),
143
- });
144
-
145
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
146
- (proxy as any).set({ name: "Alice", email: null });
147
-
148
- expect(operations).toHaveLength(1);
149
- expect(operations[0]!.kind).toBe("struct.set");
150
- expect(operations[0]!.payload).toEqual({ name: "Alice" });
151
- expect(hasOwn(operations[0]!.payload, "email")).toBe(false);
152
- });
153
-
154
- it("multiple field sets generate separate operations", () => {
155
- const operations: Operation.Operation<any, any, any>[] = [];
156
- const env = ProxyEnvironment.make((op) => {
157
- operations.push(op);
158
- });
159
-
160
- const structPrimitive = Primitive.Struct({
161
- firstName: Primitive.String(),
162
- lastName: Primitive.String(),
163
- });
164
-
165
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
166
-
167
- proxy.firstName.set("John");
168
- proxy.lastName.set("Doe");
169
-
170
- expect(operations).toHaveLength(2);
171
- expect(operations[0]!.payload).toBe("John");
172
- expect(operations[1]!.payload).toBe("Doe");
173
- });
174
- });
175
-
176
- describe("applyOperation", () => {
177
- it("struct.set replaces entire struct state", () => {
178
- const structPrimitive = Primitive.Struct({
179
- name: Primitive.String(),
180
- email: Primitive.String(),
181
- });
182
-
183
- const operation: Operation.Operation<any, any, any> = {
184
- kind: "struct.set",
185
- path: OperationPath.make(""),
186
- payload: { name: "Bob", email: "bob@test.com" },
187
- };
188
-
189
- const result = structPrimitive._internal.applyOperation(undefined, operation);
190
-
191
- expect(result).toEqual({ name: "Bob", email: "bob@test.com" });
192
- });
193
-
194
- it("delegates field operations to nested primitives", () => {
195
- const structPrimitive = Primitive.Struct({
196
- name: Primitive.String(),
197
- title: Primitive.String(),
198
- });
199
-
200
- const operation: Operation.Operation<any, any, any> = {
201
- kind: "string.set",
202
- path: OperationPath.make("name"),
203
- payload: "Updated Name",
204
- };
205
-
206
- const currentState = { name: "Original", title: "Mr" };
207
- const result = structPrimitive._internal.applyOperation(currentState, operation);
208
-
209
- expect(result).toEqual({ name: "Updated Name", title: "Mr" });
210
- });
211
-
212
- it("creates state from undefined when applying field operation", () => {
213
- const structPrimitive = Primitive.Struct({
214
- name: Primitive.String(),
215
- });
216
-
217
- const operation: Operation.Operation<any, any, any> = {
218
- kind: "string.set",
219
- path: OperationPath.make("name"),
220
- payload: "New Name",
221
- };
222
-
223
- const result = structPrimitive._internal.applyOperation(undefined, operation);
224
-
225
- expect(result).toEqual({ name: "New Name" });
226
- });
227
-
228
- it("throws ValidationError for unknown field", () => {
229
- const structPrimitive = Primitive.Struct({
230
- name: Primitive.String(),
231
- });
232
-
233
- const operation: Operation.Operation<any, any, any> = {
234
- kind: "string.set",
235
- path: OperationPath.make("unknownField"),
236
- payload: "value",
237
- };
238
-
239
- expect(() => structPrimitive._internal.applyOperation(undefined, operation)).toThrow(
240
- Primitive.ValidationError
241
- );
242
- });
243
-
244
- it("throws ValidationError for non-object payload on struct.set", () => {
245
- const structPrimitive = Primitive.Struct({
246
- name: Primitive.String(),
247
- });
248
-
249
- const operation: Operation.Operation<any, any, any> = {
250
- kind: "struct.set",
251
- path: OperationPath.make(""),
252
- payload: "not an object",
253
- };
254
-
255
- expect(() => structPrimitive._internal.applyOperation(undefined, operation)).toThrow(
256
- Primitive.ValidationError
257
- );
258
- });
259
- });
260
-
261
- describe("getInitialState", () => {
262
- it("returns undefined when no field has defaults", () => {
263
- const structPrimitive = Primitive.Struct({
264
- name: Primitive.String(),
265
- email: Primitive.String(),
266
- });
267
-
268
- expect(structPrimitive._internal.getInitialState()).toBeUndefined();
269
- });
270
-
271
- it("returns partial state from field defaults", () => {
272
- const structPrimitive = Primitive.Struct({
273
- name: Primitive.String().default("Anonymous"),
274
- email: Primitive.String(),
275
- });
276
-
277
- const initialState = structPrimitive._internal.getInitialState();
278
-
279
- expect(initialState).toEqual({ name: "Anonymous" });
280
- });
281
-
282
- it("returns complete state when all fields have defaults", () => {
283
- const structPrimitive = Primitive.Struct({
284
- name: Primitive.String().default("Guest"),
285
- role: Primitive.String().default("user"),
286
- });
287
-
288
- const initialState = structPrimitive._internal.getInitialState();
289
-
290
- expect(initialState).toEqual({ name: "Guest", role: "user" });
291
- });
292
-
293
- it("uses struct default value over field defaults", () => {
294
- const structPrimitive = Primitive.Struct({
295
- name: Primitive.String().default("Field Default"),
296
- }).default({ name: "Struct Default" });
297
-
298
- const initialState = structPrimitive._internal.getInitialState();
299
-
300
- expect(initialState).toEqual({ name: "Struct Default" });
301
- });
302
- });
303
-
304
- describe("nested structs", () => {
305
- it("supports nested struct primitives", () => {
306
- const operations: Operation.Operation<any, any, any>[] = [];
307
- const env = ProxyEnvironment.make((op) => {
308
- operations.push(op);
309
- });
310
-
311
- const addressPrimitive = Primitive.Struct({
312
- street: Primitive.String(),
313
- city: Primitive.String(),
314
- });
315
-
316
- const personPrimitive = Primitive.Struct({
317
- name: Primitive.String(),
318
- address: addressPrimitive,
319
- });
320
-
321
- const proxy = personPrimitive._internal.createProxy(env, OperationPath.make(""));
322
-
323
- proxy.address.city.set("New York");
324
-
325
- expect(operations).toHaveLength(1);
326
- expect(operations[0]!.kind).toBe("string.set");
327
- expect(operations[0]!.payload).toBe("New York");
328
- expect(operations[0]!.path.toTokens()).toEqual(["address", "city"]);
329
- });
330
-
331
- it("applies operations to nested structs", () => {
332
- const addressPrimitive = Primitive.Struct({
333
- street: Primitive.String(),
334
- city: Primitive.String(),
335
- });
336
-
337
- const personPrimitive = Primitive.Struct({
338
- name: Primitive.String(),
339
- address: addressPrimitive,
340
- });
341
-
342
- const operation: Operation.Operation<any, any, any> = {
343
- kind: "string.set",
344
- path: OperationPath.make("address/city"),
345
- payload: "Los Angeles",
346
- };
347
-
348
- const currentState = {
349
- name: "John",
350
- address: { street: "123 Main St", city: "San Francisco" },
351
- };
352
-
353
- const result = personPrimitive._internal.applyOperation(currentState, operation);
354
-
355
- expect(result).toEqual({
356
- name: "John",
357
- address: { street: "123 Main St", city: "Los Angeles" },
358
- });
359
- });
360
- });
361
-
362
- describe("type inference", () => {
363
- it("infers correct state type from struct definition", () => {
364
- const structPrimitive = Primitive.Struct({
365
- name: Primitive.String(),
366
- email: Primitive.String(),
367
- });
368
-
369
- // This is a compile-time check - the type should be inferred correctly
370
- type ExpectedState = Primitive.InferState<typeof structPrimitive>;
371
- const state: ExpectedState = { name: "test", email: "test@test.com" };
372
-
373
- expect(state).toEqual({ name: "test", email: "test@test.com" });
374
- });
375
-
376
- it("infers correct proxy type with field access", () => {
377
- const structPrimitive = Primitive.Struct({
378
- name: Primitive.String(),
379
- });
380
-
381
- type ExpectedProxy = Primitive.InferProxy<typeof structPrimitive>;
382
-
383
- // This would fail at compile time if types are wrong
384
- const env = ProxyEnvironment.make(() => {});
385
- const proxy: ExpectedProxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
386
-
387
- // Verify the proxy has the expected shape
388
- expect(typeof proxy.name.set).toBe("function");
389
- expect(typeof proxy.set).toBe("function");
390
- });
391
- });
392
-
393
- describe("update", () => {
394
- it("update() generates individual field operations", () => {
395
- const operations: Operation.Operation<any, any, any>[] = [];
396
- const env = ProxyEnvironment.make((op) => {
397
- operations.push(op);
398
- });
399
-
400
- const structPrimitive = Primitive.Struct({
401
- name: Primitive.String(),
402
- email: Primitive.String(),
403
- });
404
-
405
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
406
-
407
- proxy.update({ name: "John" });
408
-
409
- expect(operations).toHaveLength(1);
410
- expect(operations[0]!.kind).toBe("string.set");
411
- expect(operations[0]!.payload).toBe("John");
412
- expect(operations[0]!.path.toTokens()).toEqual(["name"]);
413
- });
414
-
415
- it("update() with multiple fields generates multiple operations", () => {
416
- const operations: Operation.Operation<any, any, any>[] = [];
417
- const env = ProxyEnvironment.make((op) => {
418
- operations.push(op);
419
- });
420
-
421
- const structPrimitive = Primitive.Struct({
422
- firstName: Primitive.String(),
423
- lastName: Primitive.String(),
424
- email: Primitive.String(),
425
- });
426
-
427
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
428
-
429
- proxy.update({ firstName: "John", lastName: "Doe" });
430
-
431
- expect(operations).toHaveLength(2);
432
- expect(operations.map((op) => op.payload)).toContain("John");
433
- expect(operations.map((op) => op.payload)).toContain("Doe");
434
- });
435
-
436
- it("update() emits struct.unset for undefined optional values", () => {
437
- const operations: Operation.Operation<any, any, any>[] = [];
438
- const env = ProxyEnvironment.make((op) => {
439
- operations.push(op);
440
- });
441
-
442
- const structPrimitive = Primitive.Struct({
443
- name: Primitive.String(),
444
- email: Primitive.String(),
445
- });
446
-
447
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
448
-
449
- proxy.update({ name: "John", email: undefined });
450
-
451
- expect(operations).toHaveLength(2);
452
- const nameOp = operations.find((op) => op.path.toTokens().join("/") === "name");
453
- const unsetOp = operations.find((op) => op.path.toTokens().join("/") === "email");
454
- expect(nameOp!.kind).toBe("string.set");
455
- expect(nameOp!.payload).toBe("John");
456
- expect(unsetOp!.kind).toBe("struct.unset");
457
- });
458
-
459
- it("update() removes existing optional key for undefined value", () => {
460
- const operations: Operation.Operation<any, any, any>[] = [];
461
- const structPrimitive = Primitive.Struct({
462
- name: Primitive.String(),
463
- email: Primitive.String(),
464
- });
465
- let state: Primitive.InferState<typeof structPrimitive> | undefined = {
466
- name: "John",
467
- email: "john@example.com",
468
- };
469
-
470
- const env = ProxyEnvironment.make({
471
- onOperation: (op) => {
472
- operations.push(op);
473
- state = structPrimitive._internal.applyOperation(state, op);
474
- },
475
- getState: () => state,
476
- });
477
-
478
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
479
- proxy.update({ email: undefined });
480
-
481
- expect(operations).toHaveLength(1);
482
- expect(operations[0]!.kind).toBe("struct.unset");
483
- expect(state).toEqual({ name: "John" });
484
- expect(hasOwn(state, "email")).toBe(false);
485
- });
486
-
487
- it("update() removes existing optional key for null value", () => {
488
- const operations: Operation.Operation<any, any, any>[] = [];
489
- const structPrimitive = Primitive.Struct({
490
- name: Primitive.String(),
491
- email: Primitive.String(),
492
- });
493
- let state: Primitive.InferState<typeof structPrimitive> | undefined = {
494
- name: "John",
495
- email: "john@example.com",
496
- };
497
-
498
- const env = ProxyEnvironment.make({
499
- onOperation: (op) => {
500
- operations.push(op);
501
- state = structPrimitive._internal.applyOperation(state, op);
502
- },
503
- getState: () => state,
504
- });
505
-
506
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
507
- (proxy as any).update({ email: null });
508
-
509
- expect(operations).toHaveLength(1);
510
- expect(operations[0]!.kind).toBe("struct.unset");
511
- expect(state).toEqual({ name: "John" });
512
- expect(hasOwn(state, "email")).toBe(false);
513
- });
514
-
515
- it("update() throws for undefined on required fields without defaults", () => {
516
- const operations: Operation.Operation<any, any, any>[] = [];
517
- const env = ProxyEnvironment.make((op) => {
518
- operations.push(op);
519
- });
520
-
521
- const structPrimitive = Primitive.Struct({
522
- name: Primitive.String().required(),
523
- });
524
-
525
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
526
-
527
- expect(() => proxy.update({ name: undefined as never })).toThrow(Primitive.ValidationError);
528
- expect(operations).toHaveLength(0);
529
- });
530
-
531
- it("update() throws for null on required fields without defaults", () => {
532
- const operations: Operation.Operation<any, any, any>[] = [];
533
- const env = ProxyEnvironment.make((op) => {
534
- operations.push(op);
535
- });
536
-
537
- const structPrimitive = Primitive.Struct({
538
- name: Primitive.String().required(),
539
- });
540
-
541
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
542
-
543
- expect(() => (proxy as any).update({ name: null })).toThrow(Primitive.ValidationError);
544
- expect(operations).toHaveLength(0);
545
- });
546
-
547
- it("update() recursively updates nested structs", () => {
548
- const operations: Operation.Operation<any, any, any>[] = [];
549
- const env = ProxyEnvironment.make((op) => {
550
- operations.push(op);
551
- });
552
-
553
- const addressPrimitive = Primitive.Struct({
554
- street: Primitive.String(),
555
- city: Primitive.String(),
556
- zip: Primitive.String(),
557
- });
558
-
559
- const personPrimitive = Primitive.Struct({
560
- name: Primitive.String(),
561
- address: addressPrimitive,
562
- });
563
-
564
- const proxy = personPrimitive._internal.createProxy(env, OperationPath.make(""));
565
-
566
- // Partial update of nested struct - only city should be updated
567
- proxy.update({ address: { city: "New York" } });
568
-
569
- expect(operations).toHaveLength(1);
570
- expect(operations[0]!.kind).toBe("string.set");
571
- expect(operations[0]!.payload).toBe("New York");
572
- expect(operations[0]!.path.toTokens()).toEqual(["address", "city"]);
573
- });
574
-
575
- it("update() handles deeply nested structs", () => {
576
- const operations: Operation.Operation<any, any, any>[] = [];
577
- const env = ProxyEnvironment.make((op) => {
578
- operations.push(op);
579
- });
580
-
581
- const coordsPrimitive = Primitive.Struct({
582
- lat: Primitive.String(),
583
- lng: Primitive.String(),
584
- });
585
-
586
- const locationPrimitive = Primitive.Struct({
587
- name: Primitive.String(),
588
- coords: coordsPrimitive,
589
- });
590
-
591
- const personPrimitive = Primitive.Struct({
592
- name: Primitive.String(),
593
- location: locationPrimitive,
594
- });
595
-
596
- const proxy = personPrimitive._internal.createProxy(env, OperationPath.make(""));
597
-
598
- proxy.update({ location: { coords: { lat: "40.7128" } } });
599
-
600
- expect(operations).toHaveLength(1);
601
- expect(operations[0]!.kind).toBe("string.set");
602
- expect(operations[0]!.payload).toBe("40.7128");
603
- expect(operations[0]!.path.toTokens()).toEqual(["location", "coords", "lat"]);
604
- });
605
-
606
- it("update() can update both nested and top-level fields", () => {
607
- const operations: Operation.Operation<any, any, any>[] = [];
608
- const env = ProxyEnvironment.make((op) => {
609
- operations.push(op);
610
- });
611
-
612
- const addressPrimitive = Primitive.Struct({
613
- city: Primitive.String(),
614
- zip: Primitive.String(),
615
- });
616
-
617
- const personPrimitive = Primitive.Struct({
618
- name: Primitive.String(),
619
- address: addressPrimitive,
620
- });
621
-
622
- const proxy = personPrimitive._internal.createProxy(env, OperationPath.make(""));
623
-
624
- proxy.update({ name: "Jane", address: { city: "Boston" } });
625
-
626
- expect(operations).toHaveLength(2);
627
-
628
- const nameOp = operations.find((op) => op.path.toTokens().join("/") === "name");
629
- const cityOp = operations.find((op) => op.path.toTokens().join("/") === "address/city");
630
-
631
- expect(nameOp).toBeDefined();
632
- expect(nameOp!.payload).toBe("Jane");
633
-
634
- expect(cityOp).toBeDefined();
635
- expect(cityOp!.payload).toBe("Boston");
636
- });
637
-
638
- it("update() with empty object generates no operations", () => {
639
- const operations: Operation.Operation<any, any, any>[] = [];
640
- const env = ProxyEnvironment.make((op) => {
641
- operations.push(op);
642
- });
643
-
644
- const structPrimitive = Primitive.Struct({
645
- name: Primitive.String(),
646
- email: Primitive.String(),
647
- });
648
-
649
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
650
-
651
- proxy.update({});
652
-
653
- expect(operations).toHaveLength(0);
654
- });
655
-
656
- it("update() ignores unknown fields", () => {
657
- const operations: Operation.Operation<any, any, any>[] = [];
658
- const env = ProxyEnvironment.make((op) => {
659
- operations.push(op);
660
- });
661
-
662
- const structPrimitive = Primitive.Struct({
663
- name: Primitive.String(),
664
- });
665
-
666
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
667
-
668
- // Cast to any to bypass type checking for testing unknown fields
669
- (proxy as any).update({ name: "John", unknownField: "value" });
670
-
671
- expect(operations).toHaveLength(1);
672
- expect(operations[0]!.payload).toBe("John");
673
- });
674
-
675
- it("update() with nested path prefix", () => {
676
- const operations: Operation.Operation<any, any, any>[] = [];
677
- const env = ProxyEnvironment.make((op) => {
678
- operations.push(op);
679
- });
680
-
681
- const structPrimitive = Primitive.Struct({
682
- name: Primitive.String(),
683
- email: Primitive.String(),
684
- });
685
-
686
- const proxy = structPrimitive._internal.createProxy(env, OperationPath.make("users/0"));
687
-
688
- proxy.update({ name: "Updated" });
689
-
690
- expect(operations).toHaveLength(1);
691
- expect(operations[0]!.path.toTokens()).toEqual(["users", "0", "name"]);
692
- });
693
- });
694
- describe("extend", () => {
695
- it("extends struct with additional fields", () => {
696
- const basePrimitive = Primitive.Struct({
697
- name: Primitive.String().required(),
698
- });
699
-
700
- const extendedPrimitive = basePrimitive.extend({
701
- email: Primitive.String().required(),
702
- age: Primitive.Number(),
703
- });
704
-
705
- // Verify fields exist
706
- expect(extendedPrimitive.fields).toHaveProperty("name");
707
- expect(extendedPrimitive.fields).toHaveProperty("email");
708
- expect(extendedPrimitive.fields).toHaveProperty("age");
709
- });
710
-
711
- it("extended struct generates correct operations", () => {
712
- const operations: Operation.Operation<any, any, any>[] = [];
713
- const env = ProxyEnvironment.make((op) => {
714
- operations.push(op);
715
- });
716
-
717
- const basePrimitive = Primitive.Struct({
718
- name: Primitive.String(),
719
- });
720
-
721
- const extendedPrimitive = basePrimitive.extend({
722
- email: Primitive.String(),
723
- });
724
-
725
- const proxy = extendedPrimitive._internal.createProxy(env, OperationPath.make(""));
726
-
727
- proxy.name.set("John");
728
- proxy.email.set("john@example.com");
729
-
730
- expect(operations).toHaveLength(2);
731
- expect(operations[0]!.kind).toBe("string.set");
732
- expect(operations[0]!.payload).toBe("John");
733
- expect(operations[0]!.path.toTokens()).toEqual(["name"]);
734
- expect(operations[1]!.kind).toBe("string.set");
735
- expect(operations[1]!.payload).toBe("john@example.com");
736
- expect(operations[1]!.path.toTokens()).toEqual(["email"]);
737
- });
738
-
739
- it("extended struct set() works with all fields", () => {
740
- const operations: Operation.Operation<any, any, any>[] = [];
741
- const env = ProxyEnvironment.make((op) => {
742
- operations.push(op);
743
- });
744
-
745
- const basePrimitive = Primitive.Struct({
746
- name: Primitive.String(),
747
- });
748
-
749
- const extendedPrimitive = basePrimitive.extend({
750
- email: Primitive.String(),
751
- });
752
-
753
- const proxy = extendedPrimitive._internal.createProxy(env, OperationPath.make(""));
754
-
755
- proxy.set({ name: "John", email: "john@example.com" });
756
-
757
- expect(operations).toHaveLength(1);
758
- expect(operations[0]!.kind).toBe("struct.set");
759
- expect(operations[0]!.payload).toEqual({ name: "John", email: "john@example.com" });
760
- });
761
-
762
- it("extended struct preserves required status", () => {
763
- const basePrimitive = Primitive.Struct({
764
- name: Primitive.String(),
765
- }).required();
766
-
767
- const extendedPrimitive = basePrimitive.extend({
768
- email: Primitive.String(),
769
- });
770
-
771
- // The extended struct should still be required - verify via type system
772
- // Compile-time check: if the type doesn't match, this would be a type error
773
- type ExtendedTRequired = typeof extendedPrimitive._TRequired;
774
- const _typeCheck: ExtendedTRequired = true as const;
775
- expect(_typeCheck).toBe(true);
776
- });
777
-
778
- it("extended struct applyOperation works for both base and new fields", () => {
779
- const basePrimitive = Primitive.Struct({
780
- name: Primitive.String(),
781
- });
782
-
783
- const extendedPrimitive = basePrimitive.extend({
784
- email: Primitive.String(),
785
- });
786
-
787
- const nameOperation: Operation.Operation<any, any, any> = {
788
- kind: "string.set",
789
- path: OperationPath.make("name"),
790
- payload: "John",
791
- };
792
-
793
- const emailOperation: Operation.Operation<any, any, any> = {
794
- kind: "string.set",
795
- path: OperationPath.make("email"),
796
- payload: "john@example.com",
797
- };
798
-
799
- let state = extendedPrimitive._internal.applyOperation(undefined, nameOperation);
800
- state = extendedPrimitive._internal.applyOperation(state, emailOperation);
801
-
802
- expect(state).toEqual({ name: "John", email: "john@example.com" });
803
- });
804
-
805
- it("can chain multiple extend calls", () => {
806
- const basePrimitive = Primitive.Struct({
807
- id: Primitive.String(),
808
- });
809
-
810
- const extendedOnce = basePrimitive.extend({
811
- name: Primitive.String(),
812
- });
813
-
814
- const extendedTwice = extendedOnce.extend({
815
- email: Primitive.String(),
816
- });
817
-
818
- expect(extendedTwice.fields).toHaveProperty("id");
819
- expect(extendedTwice.fields).toHaveProperty("name");
820
- expect(extendedTwice.fields).toHaveProperty("email");
821
- });
822
-
823
- it("extend with nested struct works correctly", () => {
824
- const operations: Operation.Operation<any, any, any>[] = [];
825
- const env = ProxyEnvironment.make((op) => {
826
- operations.push(op);
827
- });
828
-
829
- const basePrimitive = Primitive.Struct({
830
- name: Primitive.String(),
831
- });
832
-
833
- const addressPrimitive = Primitive.Struct({
834
- city: Primitive.String(),
835
- zip: Primitive.String(),
836
- });
837
-
838
- const extendedPrimitive = basePrimitive.extend({
839
- address: addressPrimitive,
840
- });
841
-
842
- const proxy = extendedPrimitive._internal.createProxy(env, OperationPath.make(""));
843
-
844
- proxy.address.city.set("New York");
845
-
846
- expect(operations).toHaveLength(1);
847
- expect(operations[0]!.kind).toBe("string.set");
848
- expect(operations[0]!.payload).toBe("New York");
849
- expect(operations[0]!.path.toTokens()).toEqual(["address", "city"]);
850
- });
851
- });
852
-
853
- describe("partial", () => {
854
- it("makes all fields optional", () => {
855
- const structPrimitive = Primitive.Struct({
856
- name: Primitive.String().required(),
857
- email: Primitive.String().required(),
858
- age: Primitive.Number().required(),
859
- });
860
-
861
- const partialPrimitive = structPrimitive.partial();
862
-
863
- // All fields should now be optional (not required)
864
- expect(partialPrimitive.fields).toHaveProperty("name");
865
- expect(partialPrimitive.fields).toHaveProperty("email");
866
- expect(partialPrimitive.fields).toHaveProperty("age");
867
- });
868
-
869
- it("partial struct allows empty set()", () => {
870
- const operations: Operation.Operation<any, any, any>[] = [];
871
- const env = ProxyEnvironment.make((op) => {
872
- operations.push(op);
873
- });
874
-
875
- const structPrimitive = Primitive.Struct({
876
- name: Primitive.String().required(),
877
- email: Primitive.String().required(),
878
- });
879
-
880
- const partialPrimitive = structPrimitive.partial();
881
-
882
- const proxy = partialPrimitive._internal.createProxy(env, OperationPath.make(""));
883
-
884
- // This should work without providing any fields since all are optional
885
- proxy.set({});
886
-
887
- expect(operations).toHaveLength(1);
888
- expect(operations[0]!.kind).toBe("struct.set");
889
- });
890
-
891
- it("partial struct allows partial set()", () => {
892
- const operations: Operation.Operation<any, any, any>[] = [];
893
- const env = ProxyEnvironment.make((op) => {
894
- operations.push(op);
895
- });
896
-
897
- const structPrimitive = Primitive.Struct({
898
- name: Primitive.String().required(),
899
- email: Primitive.String().required(),
900
- age: Primitive.Number().required(),
901
- });
902
-
903
- const partialPrimitive = structPrimitive.partial();
904
-
905
- const proxy = partialPrimitive._internal.createProxy(env, OperationPath.make(""));
906
-
907
- // Only provide some fields
908
- proxy.set({ name: "John" });
909
-
910
- expect(operations).toHaveLength(1);
911
- expect(operations[0]!.kind).toBe("struct.set");
912
- expect(operations[0]!.payload).toHaveProperty("name", "John");
913
- });
914
-
915
- it("partial struct field access still works", () => {
916
- const operations: Operation.Operation<any, any, any>[] = [];
917
- const env = ProxyEnvironment.make((op) => {
918
- operations.push(op);
919
- });
920
-
921
- const structPrimitive = Primitive.Struct({
922
- name: Primitive.String().required(),
923
- email: Primitive.String().required(),
924
- });
925
-
926
- const partialPrimitive = structPrimitive.partial();
927
-
928
- const proxy = partialPrimitive._internal.createProxy(env, OperationPath.make(""));
929
-
930
- proxy.name.set("John");
931
-
932
- expect(operations).toHaveLength(1);
933
- expect(operations[0]!.kind).toBe("string.set");
934
- expect(operations[0]!.payload).toBe("John");
935
- expect(operations[0]!.path.toTokens()).toEqual(["name"]);
936
- });
937
-
938
- it("partial struct preserves required/default status of struct itself", () => {
939
- const structPrimitive = Primitive.Struct({
940
- name: Primitive.String().required(),
941
- }).required();
942
-
943
- const partialPrimitive = structPrimitive.partial();
944
- // The struct itself should still be required - verify via type system
945
- // Compile-time check: if the type doesn't match, this would be a type error
946
- type PartialTRequired = typeof partialPrimitive._TRequired;
947
- const _typeCheck: PartialTRequired = true as const;
948
- expect(_typeCheck).toBe(true);
949
- });
950
-
951
- it("partial struct applyOperation works correctly", () => {
952
- const structPrimitive = Primitive.Struct({
953
- name: Primitive.String().required(),
954
- email: Primitive.String().required(),
955
- });
956
-
957
- const partialPrimitive = structPrimitive.partial();
958
-
959
- const operation: Operation.Operation<any, any, any> = {
960
- kind: "string.set",
961
- path: OperationPath.make("name"),
962
- payload: "John",
963
- };
964
-
965
- const result = partialPrimitive._internal.applyOperation(undefined, operation);
966
-
967
- expect(result).toEqual({ name: "John" });
968
- });
969
-
970
- it("partial can be combined with extend", () => {
971
- const basePrimitive = Primitive.Struct({
972
- id: Primitive.String().required(),
973
- name: Primitive.String().required(),
974
- });
975
-
976
- // First extend, then partial
977
- const extendedPartial = basePrimitive
978
- .extend({
979
- email: Primitive.String().required(),
980
- })
981
- .partial();
982
-
983
- expect(extendedPartial.fields).toHaveProperty("id");
984
- expect(extendedPartial.fields).toHaveProperty("name");
985
- expect(extendedPartial.fields).toHaveProperty("email");
986
- });
987
-
988
- it("partial works with nested structs", () => {
989
- const addressPrimitive = Primitive.Struct({
990
- city: Primitive.String().required(),
991
- zip: Primitive.String().required(),
992
- });
993
-
994
- const personPrimitive = Primitive.Struct({
995
- name: Primitive.String().required(),
996
- address: addressPrimitive.required(),
997
- });
998
-
999
- const partialPrimitive = personPrimitive.partial();
1000
-
1001
- const operations: Operation.Operation<any, any, any>[] = [];
1002
- const env = ProxyEnvironment.make((op) => {
1003
- operations.push(op);
1004
- });
1005
-
1006
- const proxy = partialPrimitive._internal.createProxy(env, OperationPath.make(""));
1007
-
1008
- // Nested struct access should still work
1009
- proxy.address.city.set("New York");
1010
-
1011
- expect(operations).toHaveLength(1);
1012
- expect(operations[0]!.kind).toBe("string.set");
1013
- expect(operations[0]!.payload).toBe("New York");
1014
- expect(operations[0]!.path.toTokens()).toEqual(["address", "city"]);
1015
- });
1016
-
1017
- it("partial struct update() works correctly", () => {
1018
- const operations: Operation.Operation<any, any, any>[] = [];
1019
- const env = ProxyEnvironment.make((op) => {
1020
- operations.push(op);
1021
- });
1022
-
1023
- const structPrimitive = Primitive.Struct({
1024
- name: Primitive.String().required(),
1025
- email: Primitive.String().required(),
1026
- age: Primitive.Number().required(),
1027
- });
1028
-
1029
- const partialPrimitive = structPrimitive.partial();
1030
-
1031
- const proxy = partialPrimitive._internal.createProxy(env, OperationPath.make(""));
1032
-
1033
- proxy.update({ name: "Jane" });
1034
-
1035
- expect(operations).toHaveLength(1);
1036
- expect(operations[0]!.kind).toBe("string.set");
1037
- expect(operations[0]!.payload).toBe("Jane");
1038
- });
1039
- });
1040
- });
1041
-
1042
- // =============================================================================
1043
- // Array Primitive Tests (Ordered with ID + Fractional Index)
1044
- // =============================================================================