@voidhash/mimic 0.0.1-alpha.1 → 0.0.1-alpha.111

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 (63) hide show
  1. package/.turbo/turbo-build.log +51 -0
  2. package/LICENSE.md +663 -0
  3. package/dist/Document-ChuFrTk1.cjs +571 -0
  4. package/dist/Document-CwiAFTIq.mjs +438 -0
  5. package/dist/Document-CwiAFTIq.mjs.map +1 -0
  6. package/dist/Presence-DKKP4v5X.d.cts +91 -0
  7. package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
  8. package/dist/Presence-DdMVKcOv.mjs +110 -0
  9. package/dist/Presence-DdMVKcOv.mjs.map +1 -0
  10. package/dist/Presence-N8u7Eppr.d.mts +91 -0
  11. package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
  12. package/dist/Presence-gWrmGBeu.cjs +126 -0
  13. package/dist/Primitive-CvFVxR8_.d.cts +1175 -0
  14. package/dist/Primitive-CvFVxR8_.d.cts.map +1 -0
  15. package/dist/Primitive-lEhQyGVL.d.mts +1175 -0
  16. package/dist/Primitive-lEhQyGVL.d.mts.map +1 -0
  17. package/dist/chunk-CLMFDpHK.mjs +18 -0
  18. package/dist/client/index.cjs +1456 -0
  19. package/dist/client/index.d.cts +692 -0
  20. package/dist/client/index.d.cts.map +1 -0
  21. package/dist/client/index.d.mts +692 -0
  22. package/dist/client/index.d.mts.map +1 -0
  23. package/dist/client/index.mjs +1413 -0
  24. package/dist/client/index.mjs.map +1 -0
  25. package/dist/index.cjs +2577 -0
  26. package/dist/index.d.cts +143 -0
  27. package/dist/index.d.cts.map +1 -0
  28. package/dist/index.d.mts +143 -0
  29. package/dist/index.d.mts.map +1 -0
  30. package/dist/index.mjs +2526 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/server/index.cjs +191 -0
  33. package/dist/server/index.d.cts +148 -0
  34. package/dist/server/index.d.cts.map +1 -0
  35. package/dist/server/index.d.mts +148 -0
  36. package/dist/server/index.d.mts.map +1 -0
  37. package/dist/server/index.mjs +182 -0
  38. package/dist/server/index.mjs.map +1 -0
  39. package/package.json +25 -13
  40. package/src/EffectSchema.ts +374 -0
  41. package/src/Primitive.ts +3 -0
  42. package/src/client/ClientDocument.ts +1 -1
  43. package/src/client/errors.ts +10 -10
  44. package/src/index.ts +1 -0
  45. package/src/primitives/Array.ts +57 -22
  46. package/src/primitives/Boolean.ts +33 -19
  47. package/src/primitives/Either.ts +379 -0
  48. package/src/primitives/Lazy.ts +16 -2
  49. package/src/primitives/Literal.ts +33 -20
  50. package/src/primitives/Number.ts +39 -26
  51. package/src/primitives/String.ts +40 -25
  52. package/src/primitives/Struct.ts +126 -29
  53. package/src/primitives/Tree.ts +119 -32
  54. package/src/primitives/TreeNode.ts +77 -30
  55. package/src/primitives/Union.ts +56 -29
  56. package/src/primitives/shared.ts +111 -9
  57. package/src/server/errors.ts +6 -6
  58. package/tests/EffectSchema.test.ts +546 -0
  59. package/tests/primitives/Array.test.ts +108 -0
  60. package/tests/primitives/Either.test.ts +707 -0
  61. package/tests/primitives/Struct.test.ts +250 -0
  62. package/tests/primitives/Tree.test.ts +250 -0
  63. package/tsdown.config.ts +1 -1
@@ -66,6 +66,46 @@ describe("StructPrimitive", () => {
66
66
  expect(operations[0]!.payload).toEqual({ name: "Alice", age: "30" });
67
67
  });
68
68
 
69
+ it("set() only requires fields that are required and without defaults", () => {
70
+ const operations: Operation.Operation<any, any, any>[] = [];
71
+ const env = ProxyEnvironment.make((op) => {
72
+ operations.push(op);
73
+ });
74
+
75
+ const structPrimitive = Primitive.Struct({
76
+ name: Primitive.String().required().default("John Doe"),
77
+ age: Primitive.Number().required(),
78
+ email: Primitive.String(),
79
+ });
80
+
81
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
82
+ proxy.set({ age: 30 });
83
+
84
+ expect(operations).toHaveLength(1);
85
+ expect(operations[0]!.kind).toBe("struct.set");
86
+ expect(operations[0]!.payload).toEqual({ name: "John Doe", age: 30, email: undefined });
87
+ });
88
+
89
+ it("set() only requires fields that are required and without defaults", () => {
90
+ const operations: Operation.Operation<any, any, any>[] = [];
91
+ const env = ProxyEnvironment.make((op) => {
92
+ operations.push(op);
93
+ });
94
+
95
+ const structPrimitive = Primitive.Struct({
96
+ name: Primitive.String().required().default("John Doe"),
97
+ age: Primitive.Number().required(),
98
+ email: Primitive.String(),
99
+ }).default({ age: 30 });
100
+
101
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
102
+ proxy.set({ age: 30 });
103
+
104
+ expect(operations).toHaveLength(1);
105
+ expect(operations[0]!.kind).toBe("struct.set");
106
+ expect(operations[0]!.payload).toEqual({ name: "John Doe", age: 30, email: undefined });
107
+ });
108
+
69
109
  it("multiple field sets generate separate operations", () => {
70
110
  const operations: Operation.Operation<any, any, any>[] = [];
71
111
  const env = ProxyEnvironment.make((op) => {
@@ -304,6 +344,216 @@ describe("StructPrimitive", () => {
304
344
  expect(typeof proxy.set).toBe("function");
305
345
  });
306
346
  });
347
+
348
+ describe("update", () => {
349
+ it("update() generates individual field operations", () => {
350
+ const operations: Operation.Operation<any, any, any>[] = [];
351
+ const env = ProxyEnvironment.make((op) => {
352
+ operations.push(op);
353
+ });
354
+
355
+ const structPrimitive = Primitive.Struct({
356
+ name: Primitive.String(),
357
+ email: Primitive.String(),
358
+ });
359
+
360
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
361
+
362
+ proxy.update({ name: "John" });
363
+
364
+ expect(operations).toHaveLength(1);
365
+ expect(operations[0]!.kind).toBe("string.set");
366
+ expect(operations[0]!.payload).toBe("John");
367
+ expect(operations[0]!.path.toTokens()).toEqual(["name"]);
368
+ });
369
+
370
+ it("update() with multiple fields generates multiple operations", () => {
371
+ const operations: Operation.Operation<any, any, any>[] = [];
372
+ const env = ProxyEnvironment.make((op) => {
373
+ operations.push(op);
374
+ });
375
+
376
+ const structPrimitive = Primitive.Struct({
377
+ firstName: Primitive.String(),
378
+ lastName: Primitive.String(),
379
+ email: Primitive.String(),
380
+ });
381
+
382
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
383
+
384
+ proxy.update({ firstName: "John", lastName: "Doe" });
385
+
386
+ expect(operations).toHaveLength(2);
387
+ expect(operations.map((op) => op.payload)).toContain("John");
388
+ expect(operations.map((op) => op.payload)).toContain("Doe");
389
+ });
390
+
391
+ it("update() skips undefined values", () => {
392
+ const operations: Operation.Operation<any, any, any>[] = [];
393
+ const env = ProxyEnvironment.make((op) => {
394
+ operations.push(op);
395
+ });
396
+
397
+ const structPrimitive = Primitive.Struct({
398
+ name: Primitive.String(),
399
+ email: Primitive.String(),
400
+ });
401
+
402
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
403
+
404
+ proxy.update({ name: "John", email: undefined });
405
+
406
+ expect(operations).toHaveLength(1);
407
+ expect(operations[0]!.payload).toBe("John");
408
+ });
409
+
410
+ it("update() recursively updates nested structs", () => {
411
+ const operations: Operation.Operation<any, any, any>[] = [];
412
+ const env = ProxyEnvironment.make((op) => {
413
+ operations.push(op);
414
+ });
415
+
416
+ const addressPrimitive = Primitive.Struct({
417
+ street: Primitive.String(),
418
+ city: Primitive.String(),
419
+ zip: Primitive.String(),
420
+ });
421
+
422
+ const personPrimitive = Primitive.Struct({
423
+ name: Primitive.String(),
424
+ address: addressPrimitive,
425
+ });
426
+
427
+ const proxy = personPrimitive._internal.createProxy(env, OperationPath.make(""));
428
+
429
+ // Partial update of nested struct - only city should be updated
430
+ proxy.update({ address: { city: "New York" } });
431
+
432
+ expect(operations).toHaveLength(1);
433
+ expect(operations[0]!.kind).toBe("string.set");
434
+ expect(operations[0]!.payload).toBe("New York");
435
+ expect(operations[0]!.path.toTokens()).toEqual(["address", "city"]);
436
+ });
437
+
438
+ it("update() handles deeply nested structs", () => {
439
+ const operations: Operation.Operation<any, any, any>[] = [];
440
+ const env = ProxyEnvironment.make((op) => {
441
+ operations.push(op);
442
+ });
443
+
444
+ const coordsPrimitive = Primitive.Struct({
445
+ lat: Primitive.String(),
446
+ lng: Primitive.String(),
447
+ });
448
+
449
+ const locationPrimitive = Primitive.Struct({
450
+ name: Primitive.String(),
451
+ coords: coordsPrimitive,
452
+ });
453
+
454
+ const personPrimitive = Primitive.Struct({
455
+ name: Primitive.String(),
456
+ location: locationPrimitive,
457
+ });
458
+
459
+ const proxy = personPrimitive._internal.createProxy(env, OperationPath.make(""));
460
+
461
+ proxy.update({ location: { coords: { lat: "40.7128" } } });
462
+
463
+ expect(operations).toHaveLength(1);
464
+ expect(operations[0]!.kind).toBe("string.set");
465
+ expect(operations[0]!.payload).toBe("40.7128");
466
+ expect(operations[0]!.path.toTokens()).toEqual(["location", "coords", "lat"]);
467
+ });
468
+
469
+ it("update() can update both nested and top-level fields", () => {
470
+ const operations: Operation.Operation<any, any, any>[] = [];
471
+ const env = ProxyEnvironment.make((op) => {
472
+ operations.push(op);
473
+ });
474
+
475
+ const addressPrimitive = Primitive.Struct({
476
+ city: Primitive.String(),
477
+ zip: Primitive.String(),
478
+ });
479
+
480
+ const personPrimitive = Primitive.Struct({
481
+ name: Primitive.String(),
482
+ address: addressPrimitive,
483
+ });
484
+
485
+ const proxy = personPrimitive._internal.createProxy(env, OperationPath.make(""));
486
+
487
+ proxy.update({ name: "Jane", address: { city: "Boston" } });
488
+
489
+ expect(operations).toHaveLength(2);
490
+
491
+ const nameOp = operations.find((op) => op.path.toTokens().join("/") === "name");
492
+ const cityOp = operations.find((op) => op.path.toTokens().join("/") === "address/city");
493
+
494
+ expect(nameOp).toBeDefined();
495
+ expect(nameOp!.payload).toBe("Jane");
496
+
497
+ expect(cityOp).toBeDefined();
498
+ expect(cityOp!.payload).toBe("Boston");
499
+ });
500
+
501
+ it("update() with empty object generates no operations", () => {
502
+ const operations: Operation.Operation<any, any, any>[] = [];
503
+ const env = ProxyEnvironment.make((op) => {
504
+ operations.push(op);
505
+ });
506
+
507
+ const structPrimitive = Primitive.Struct({
508
+ name: Primitive.String(),
509
+ email: Primitive.String(),
510
+ });
511
+
512
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
513
+
514
+ proxy.update({});
515
+
516
+ expect(operations).toHaveLength(0);
517
+ });
518
+
519
+ it("update() ignores unknown fields", () => {
520
+ const operations: Operation.Operation<any, any, any>[] = [];
521
+ const env = ProxyEnvironment.make((op) => {
522
+ operations.push(op);
523
+ });
524
+
525
+ const structPrimitive = Primitive.Struct({
526
+ name: Primitive.String(),
527
+ });
528
+
529
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make(""));
530
+
531
+ // Cast to any to bypass type checking for testing unknown fields
532
+ (proxy as any).update({ name: "John", unknownField: "value" });
533
+
534
+ expect(operations).toHaveLength(1);
535
+ expect(operations[0]!.payload).toBe("John");
536
+ });
537
+
538
+ it("update() with nested path prefix", () => {
539
+ const operations: Operation.Operation<any, any, any>[] = [];
540
+ const env = ProxyEnvironment.make((op) => {
541
+ operations.push(op);
542
+ });
543
+
544
+ const structPrimitive = Primitive.Struct({
545
+ name: Primitive.String(),
546
+ email: Primitive.String(),
547
+ });
548
+
549
+ const proxy = structPrimitive._internal.createProxy(env, OperationPath.make("users/0"));
550
+
551
+ proxy.update({ name: "Updated" });
552
+
553
+ expect(operations).toHaveLength(1);
554
+ expect(operations[0]!.path.toTokens()).toEqual(["users", "0", "name"]);
555
+ });
556
+ });
307
557
  });
308
558
 
309
559
  // =============================================================================
@@ -460,6 +460,256 @@ describe("TreePrimitive", () => {
460
460
  expect(withDefault._internal.getInitialState()).toEqual(defaultState);
461
461
  });
462
462
  });
463
+
464
+ describe("proxy - partial update", () => {
465
+ it("update() on TypedNodeProxy updates only specified fields", () => {
466
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
467
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
468
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
469
+ ];
470
+ const { env, operations } = createEnvWithState(initialState);
471
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
472
+
473
+ // Use the update method via as()
474
+ const fileProxy = proxy.node("file1")!.as(FileNode);
475
+ fileProxy.update({ name: "UpdatedName" });
476
+
477
+ // Should generate only a string.set operation for the name field
478
+ expect(operations).toHaveLength(1);
479
+ expect(operations[0]!.kind).toBe("string.set");
480
+ expect(operations[0]!.path.toTokens()).toEqual(["file1", "name"]);
481
+ expect(operations[0]!.payload).toBe("UpdatedName");
482
+ });
483
+
484
+ it("update() preserves other fields when updating partial data", () => {
485
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
486
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
487
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
488
+ ];
489
+ const { env, operations } = createEnvWithState(initialState);
490
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
491
+
492
+ // Update only the size field
493
+ proxy.node("file1")!.as(FileNode).update({ size: 200 });
494
+
495
+ // Should generate only a number.set operation for the size field
496
+ expect(operations).toHaveLength(1);
497
+ expect(operations[0]!.kind).toBe("number.set");
498
+ expect(operations[0]!.path.toTokens()).toEqual(["file1", "size"]);
499
+ expect(operations[0]!.payload).toBe(200);
500
+
501
+ // The name should remain unchanged in the state
502
+ const updatedState = proxy.get();
503
+ const file1 = updatedState.find(n => n.id === "file1");
504
+ expect(file1!.data).toEqual({ name: "File1", size: 200 });
505
+ });
506
+
507
+ it("update() handles multiple fields at once", () => {
508
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
509
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
510
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
511
+ ];
512
+ const { env, operations } = createEnvWithState(initialState);
513
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
514
+
515
+ // Update both name and size
516
+ proxy.node("file1")!.as(FileNode).update({ name: "NewFile", size: 500 });
517
+
518
+ // Should generate two operations
519
+ expect(operations).toHaveLength(2);
520
+
521
+ // Verify both fields were updated
522
+ const updatedState = proxy.get();
523
+ const file1 = updatedState.find(n => n.id === "file1");
524
+ expect(file1!.data).toEqual({ name: "NewFile", size: 500 });
525
+ });
526
+
527
+ it("updateAt() provides convenient partial update by node id", () => {
528
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
529
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
530
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
531
+ ];
532
+ const { env, operations } = createEnvWithState(initialState);
533
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
534
+
535
+ // Use updateAt for convenience
536
+ proxy.updateAt("file1", FileNode, { name: "QuickUpdate" });
537
+
538
+ expect(operations).toHaveLength(1);
539
+ expect(operations[0]!.kind).toBe("string.set");
540
+ expect(operations[0]!.path.toTokens()).toEqual(["file1", "name"]);
541
+ expect(operations[0]!.payload).toBe("QuickUpdate");
542
+ });
543
+
544
+ it("updateAt() throws for wrong node type", () => {
545
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
546
+ { id: "file1", type: "file", parentId: null, pos: "a0", data: { name: "File1", size: 100 } },
547
+ ];
548
+ const { env } = createEnvWithState(initialState);
549
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
550
+
551
+ // Trying to update a file node as a folder should throw
552
+ expect(() => proxy.updateAt("file1", FolderNode, { name: "NewName" })).toThrow(
553
+ Primitive.ValidationError
554
+ );
555
+ });
556
+
557
+ it("updateAt() throws for non-existent node", () => {
558
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
559
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
560
+ ];
561
+ const { env } = createEnvWithState(initialState);
562
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
563
+
564
+ expect(() => proxy.updateAt("nonexistent", FileNode, { name: "Name" })).toThrow(
565
+ Primitive.ValidationError
566
+ );
567
+ });
568
+
569
+ it("data.update() on at() proxy also works for partial updates", () => {
570
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
571
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
572
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
573
+ ];
574
+ const { env, operations } = createEnvWithState(initialState);
575
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
576
+
577
+ // The at() method returns the data proxy which has update()
578
+ proxy.at("file1", FileNode).update({ size: 999 });
579
+
580
+ expect(operations).toHaveLength(1);
581
+ expect(operations[0]!.kind).toBe("number.set");
582
+ expect(operations[0]!.path.toTokens()).toEqual(["file1", "size"]);
583
+ });
584
+ });
585
+
586
+ describe("proxy - insert with defaults", () => {
587
+ // Define node types with defaults
588
+ const ItemNodeWithDefaults = Primitive.TreeNode("item", {
589
+ data: Primitive.Struct({
590
+ title: Primitive.String().required(), // Must provide
591
+ count: Primitive.Number().default(0), // Has default, optional
592
+ active: Primitive.Boolean().default(true), // Has default, optional
593
+ }),
594
+ children: [] as const,
595
+ });
596
+
597
+ const ContainerNodeWithDefaults = Primitive.TreeNode("container", {
598
+ data: Primitive.Struct({
599
+ name: Primitive.String().required(), // Must provide
600
+ }),
601
+ children: (): readonly Primitive.AnyTreeNodePrimitive[] => [ContainerNodeWithDefaults, ItemNodeWithDefaults],
602
+ });
603
+
604
+ const treeWithDefaults = Primitive.Tree({
605
+ root: ContainerNodeWithDefaults,
606
+ });
607
+
608
+ // Helper to create a mock environment
609
+ const createEnvWithDefaultsTree = (
610
+ state: Primitive.TreeState<typeof ContainerNodeWithDefaults> = []
611
+ ): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
612
+ const operations: Operation.Operation<any, any, any>[] = [];
613
+ let currentState = [...state] as Primitive.TreeState<typeof ContainerNodeWithDefaults>;
614
+ let idCounter = 0;
615
+
616
+ const env = ProxyEnvironment.make({
617
+ onOperation: (op) => {
618
+ operations.push(op);
619
+ currentState = treeWithDefaults._internal.applyOperation(currentState, op);
620
+ },
621
+ getState: () => currentState,
622
+ generateId: () => `node-${++idCounter}`,
623
+ });
624
+
625
+ return { env, operations };
626
+ };
627
+
628
+ it("insertFirst() only requires fields without defaults", () => {
629
+ const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
630
+ { id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
631
+ ];
632
+ const { env, operations } = createEnvWithDefaultsTree(initialState);
633
+ const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
634
+
635
+ // Only provide required field 'title', count and active should use defaults
636
+ const newId = proxy.insertFirst("root", ItemNodeWithDefaults, { title: "New Item" });
637
+
638
+ expect(operations).toHaveLength(1);
639
+ expect(operations[0]!.kind).toBe("tree.insert");
640
+ expect(newId).toBe("node-1");
641
+
642
+ const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
643
+ expect(payload.data.title).toBe("New Item");
644
+ expect(payload.data.count).toBe(0); // Default value
645
+ expect(payload.data.active).toBe(true); // Default value
646
+ });
647
+
648
+ it("insertLast() applies defaults for omitted fields", () => {
649
+ const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
650
+ { id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
651
+ ];
652
+ const { env, operations } = createEnvWithDefaultsTree(initialState);
653
+ const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
654
+
655
+ // Provide title and override count, let active use default
656
+ proxy.insertLast("root", ItemNodeWithDefaults, { title: "Item", count: 42 });
657
+
658
+ const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
659
+ expect(payload.data.title).toBe("Item");
660
+ expect(payload.data.count).toBe(42); // Overridden
661
+ expect(payload.data.active).toBe(true); // Default value
662
+ });
663
+
664
+ it("insertAt() allows omitting all optional fields with defaults", () => {
665
+ const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
666
+ { id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
667
+ ];
668
+ const { env, operations } = createEnvWithDefaultsTree(initialState);
669
+ const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
670
+
671
+ // Only provide required 'name' for container
672
+ proxy.insertAt("root", 0, ContainerNodeWithDefaults, { name: "Subfolder" });
673
+
674
+ const payload = operations[0]!.payload as { type: string; data: { name: string } };
675
+ expect(payload.type).toBe("container");
676
+ expect(payload.data.name).toBe("Subfolder");
677
+ });
678
+
679
+ it("insertAfter() uses defaults when fields are omitted", () => {
680
+ const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
681
+ { id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
682
+ { id: "item1", type: "item", parentId: "root", pos: "a0", data: { title: "First", count: 1, active: false } },
683
+ ];
684
+ const { env, operations } = createEnvWithDefaultsTree(initialState);
685
+ const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
686
+
687
+ // Insert after sibling with only required field
688
+ proxy.insertAfter("item1", ItemNodeWithDefaults, { title: "Second" });
689
+
690
+ const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
691
+ expect(payload.data.title).toBe("Second");
692
+ expect(payload.data.count).toBe(0); // Default
693
+ expect(payload.data.active).toBe(true); // Default
694
+ });
695
+
696
+ it("insertBefore() uses defaults when fields are omitted", () => {
697
+ const initialState: Primitive.TreeState<typeof ContainerNodeWithDefaults> = [
698
+ { id: "root", type: "container", parentId: null, pos: "a0", data: { name: "Root" } },
699
+ { id: "item1", type: "item", parentId: "root", pos: "a0", data: { title: "First", count: 1, active: false } },
700
+ ];
701
+ const { env, operations } = createEnvWithDefaultsTree(initialState);
702
+ const proxy = treeWithDefaults._internal.createProxy(env, OperationPath.make(""));
703
+
704
+ // Insert before sibling with only required field, override active
705
+ proxy.insertBefore("item1", ItemNodeWithDefaults, { title: "Zeroth", active: false });
706
+
707
+ const payload = operations[0]!.payload as { data: { title: string; count: number; active: boolean } };
708
+ expect(payload.data.title).toBe("Zeroth");
709
+ expect(payload.data.count).toBe(0); // Default
710
+ expect(payload.data.active).toBe(false); // Overridden
711
+ });
712
+ });
463
713
  });
464
714
 
465
715
  // =============================================================================
package/tsdown.config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineConfig } from "tsdown";
2
2
 
3
- export const input = ["./src/index.ts"];
3
+ export const input = ["./src/index.ts", "./src/server/index.ts", "./src/client/index.ts"];
4
4
 
5
5
  export default defineConfig({
6
6
  target: ["es2017"],