@voidhash/mimic 0.0.3 → 0.0.5

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 (52) hide show
  1. package/.turbo/turbo-build.log +202 -198
  2. package/dist/Document.cjs +1 -2
  3. package/dist/Document.d.cts +9 -3
  4. package/dist/Document.d.cts.map +1 -1
  5. package/dist/Document.d.mts +9 -3
  6. package/dist/Document.d.mts.map +1 -1
  7. package/dist/Document.mjs +1 -2
  8. package/dist/Document.mjs.map +1 -1
  9. package/dist/Primitive.d.cts +2 -2
  10. package/dist/Primitive.d.mts +2 -2
  11. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutProperties.cjs +15 -0
  12. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutProperties.mjs +15 -0
  13. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutPropertiesLoose.cjs +14 -0
  14. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutPropertiesLoose.mjs +13 -0
  15. package/dist/client/ClientDocument.cjs +17 -12
  16. package/dist/client/ClientDocument.d.mts.map +1 -1
  17. package/dist/client/ClientDocument.mjs +17 -12
  18. package/dist/client/ClientDocument.mjs.map +1 -1
  19. package/dist/client/WebSocketTransport.cjs +6 -6
  20. package/dist/client/WebSocketTransport.mjs +6 -6
  21. package/dist/client/WebSocketTransport.mjs.map +1 -1
  22. package/dist/primitives/Tree.cjs +55 -7
  23. package/dist/primitives/Tree.d.cts +97 -10
  24. package/dist/primitives/Tree.d.cts.map +1 -1
  25. package/dist/primitives/Tree.d.mts +97 -10
  26. package/dist/primitives/Tree.d.mts.map +1 -1
  27. package/dist/primitives/Tree.mjs +55 -7
  28. package/dist/primitives/Tree.mjs.map +1 -1
  29. package/dist/primitives/shared.d.cts +9 -0
  30. package/dist/primitives/shared.d.cts.map +1 -1
  31. package/dist/primitives/shared.d.mts +9 -0
  32. package/dist/primitives/shared.d.mts.map +1 -1
  33. package/dist/primitives/shared.mjs.map +1 -1
  34. package/dist/server/ServerDocument.cjs +1 -1
  35. package/dist/server/ServerDocument.d.cts +3 -3
  36. package/dist/server/ServerDocument.d.cts.map +1 -1
  37. package/dist/server/ServerDocument.d.mts +3 -3
  38. package/dist/server/ServerDocument.d.mts.map +1 -1
  39. package/dist/server/ServerDocument.mjs +1 -1
  40. package/dist/server/ServerDocument.mjs.map +1 -1
  41. package/package.json +2 -2
  42. package/src/Document.ts +18 -5
  43. package/src/client/ClientDocument.ts +20 -21
  44. package/src/client/WebSocketTransport.ts +9 -9
  45. package/src/primitives/Tree.ts +209 -19
  46. package/src/primitives/shared.ts +10 -1
  47. package/src/server/ServerDocument.ts +4 -3
  48. package/tests/client/ClientDocument.test.ts +309 -2
  49. package/tests/client/WebSocketTransport.test.ts +228 -3
  50. package/tests/primitives/Tree.test.ts +291 -17
  51. package/tests/server/ServerDocument.test.ts +1 -1
  52. package/tsconfig.json +1 -1
@@ -54,11 +54,18 @@ describe("TreePrimitive", () => {
54
54
  });
55
55
 
56
56
  it("default() returns a new TreePrimitive with default value", () => {
57
- const defaultState: Primitive.TreeState<typeof FolderNode> = [
58
- { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
59
- ];
60
- const withDefault = fileSystemTree.default(defaultState);
61
- expect(withDefault._internal.getInitialState()).toEqual(defaultState);
57
+ const defaultInput = {
58
+ type: "folder" as const,
59
+ id: "root",
60
+ name: "Root",
61
+ children: [],
62
+ };
63
+ const withDefault = fileSystemTree.default(defaultInput);
64
+ const initialState = withDefault._internal.getInitialState();
65
+ expect(initialState).toHaveLength(1);
66
+ expect(initialState![0]!.id).toBe("root");
67
+ expect(initialState![0]!.type).toBe("folder");
68
+ expect(initialState![0]!.data).toEqual({ name: "Root" });
62
69
  });
63
70
  });
64
71
 
@@ -70,18 +77,26 @@ describe("TreePrimitive", () => {
70
77
  expect(proxy.get()).toEqual([]);
71
78
  });
72
79
 
73
- it("set() generates tree.set operation", () => {
80
+ it("set() generates tree.set operation with nested input converted to flat", () => {
74
81
  const { env, operations } = createEnvWithState();
75
82
  const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
76
83
 
77
- const nodes: Primitive.TreeState<typeof FolderNode> = [
78
- { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
79
- ];
80
- proxy.set(nodes);
84
+ const nestedInput = {
85
+ type: "folder" as const,
86
+ name: "Root",
87
+ children: [],
88
+ };
89
+ proxy.set(nestedInput);
81
90
 
82
91
  expect(operations).toHaveLength(1);
83
92
  expect(operations[0]!.kind).toBe("tree.set");
84
- expect(operations[0]!.payload).toEqual(nodes);
93
+ // The payload should be flat format
94
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
95
+ expect(payload).toHaveLength(1);
96
+ expect(payload[0]!.type).toBe("folder");
97
+ expect(payload[0]!.data).toEqual({ name: "Root" });
98
+ expect(payload[0]!.parentId).toBe(null);
99
+ expect(payload[0]!.id).toBe("node-1"); // Generated by env.generateId
85
100
  });
86
101
 
87
102
  it("root() returns the root node", () => {
@@ -452,12 +467,20 @@ describe("TreePrimitive", () => {
452
467
  expect(typeof initialState![0]!.pos).toBe("string");
453
468
  });
454
469
 
455
- it("returns the default value when set", () => {
456
- const defaultState: Primitive.TreeState<typeof FolderNode> = [
457
- { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
458
- ];
459
- const withDefault = fileSystemTree.default(defaultState);
460
- expect(withDefault._internal.getInitialState()).toEqual(defaultState);
470
+ it("returns the default value when set (converted from nested)", () => {
471
+ const defaultInput = {
472
+ type: "folder" as const,
473
+ id: "root",
474
+ name: "Root",
475
+ children: [],
476
+ };
477
+ const withDefault = fileSystemTree.default(defaultInput);
478
+ const initialState = withDefault._internal.getInitialState();
479
+ expect(initialState).toHaveLength(1);
480
+ expect(initialState![0]!.id).toBe("root");
481
+ expect(initialState![0]!.type).toBe("folder");
482
+ expect(initialState![0]!.parentId).toBe(null);
483
+ expect(initialState![0]!.data).toEqual({ name: "Root" });
461
484
  });
462
485
  });
463
486
 
@@ -712,6 +735,257 @@ describe("TreePrimitive", () => {
712
735
  });
713
736
  });
714
737
 
738
+ describe("TreePrimitive - nested input for set() and default()", () => {
739
+ // Define node types using the new TreeNode API
740
+ // Using TreeNodeSelf for self-referential nodes preserves type safety
741
+ const FileNode = Primitive.TreeNode("file", {
742
+ data: Primitive.Struct({ name: Primitive.String(), size: Primitive.Number().default(0) }),
743
+ children: [] as const,
744
+ });
745
+
746
+ const FolderNode = Primitive.TreeNode("folder", {
747
+ data: Primitive.Struct({ name: Primitive.String() }),
748
+ children: [Primitive.TreeNodeSelf, FileNode],
749
+ });
750
+
751
+ const fileSystemTree = Primitive.Tree({
752
+ root: FolderNode,
753
+ });
754
+
755
+ // Helper to create a mock environment with state access
756
+ const createEnvWithState = (
757
+ state: Primitive.TreeState<typeof FolderNode> = []
758
+ ): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
759
+ const operations: Operation.Operation<any, any, any>[] = [];
760
+ let currentState = [...state] as Primitive.TreeState<typeof FolderNode>;
761
+ let idCounter = 0;
762
+
763
+ const env = ProxyEnvironment.make({
764
+ onOperation: (op) => {
765
+ operations.push(op);
766
+ // Apply operation to keep state in sync
767
+ currentState = fileSystemTree._internal.applyOperation(currentState, op);
768
+ },
769
+ getState: () => currentState,
770
+ generateId: () => `node-${++idCounter}`,
771
+ });
772
+
773
+ return { env, operations };
774
+ };
775
+
776
+ describe("set() with nested input", () => {
777
+ it("converts nested input to flat TreeState", () => {
778
+ const { env, operations } = createEnvWithState();
779
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
780
+
781
+ proxy.set({
782
+ type: "folder",
783
+ name: "Root",
784
+ children: [
785
+ { type: "file", name: "file1.txt", children: [] },
786
+ { type: "file", name: "file2.txt", children: [] },
787
+ ],
788
+ });
789
+
790
+ expect(operations).toHaveLength(1);
791
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
792
+ expect(payload).toHaveLength(3);
793
+
794
+ // Root
795
+ expect(payload[0]!.type).toBe("folder");
796
+ expect(payload[0]!.parentId).toBe(null);
797
+ expect(payload[0]!.data).toEqual({ name: "Root" });
798
+
799
+ // First file child
800
+ expect(payload[1]!.type).toBe("file");
801
+ expect(payload[1]!.parentId).toBe(payload[0]!.id);
802
+ expect(payload[1]!.data).toEqual({ name: "file1.txt", size: 0 }); // size has default
803
+
804
+ // Second file child
805
+ expect(payload[2]!.type).toBe("file");
806
+ expect(payload[2]!.parentId).toBe(payload[0]!.id);
807
+ expect(payload[2]!.data).toEqual({ name: "file2.txt", size: 0 });
808
+
809
+ // Positions should be in order
810
+ expect(payload[1]!.pos < payload[2]!.pos).toBe(true);
811
+ });
812
+
813
+ it("allows explicit IDs in nested input", () => {
814
+ const { env, operations } = createEnvWithState();
815
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
816
+
817
+ proxy.set({
818
+ type: "folder",
819
+ id: "my-root",
820
+ name: "Root",
821
+ children: [
822
+ { type: "file", id: "my-file", name: "file.txt", children: [] },
823
+ ],
824
+ });
825
+
826
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
827
+ expect(payload[0]!.id).toBe("my-root");
828
+ expect(payload[1]!.id).toBe("my-file");
829
+ });
830
+
831
+ it("throws on duplicate IDs", () => {
832
+ const { env } = createEnvWithState();
833
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
834
+
835
+ expect(() =>
836
+ proxy.set({
837
+ type: "folder",
838
+ id: "dup",
839
+ name: "Root",
840
+ children: [
841
+ { type: "file", id: "dup", name: "file.txt", children: [] },
842
+ ],
843
+ })
844
+ ).toThrow(Primitive.ValidationError);
845
+ });
846
+
847
+ it("validates child types against schema", () => {
848
+ const { env } = createEnvWithState();
849
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
850
+
851
+ // File nodes cannot have children with type "folder"
852
+ // But since we can't test FileNode as root (wrong type), let's test folder with wrong child
853
+ // Actually FolderNode accepts both FolderNode and FileNode, so this won't fail on types
854
+ // Let's test wrong root type instead - but the input is typed, so this is caught at compile time
855
+ // We can test runtime by forcing wrong type
856
+ const invalidInput = {
857
+ type: "file" as "folder", // Cast to bypass type check
858
+ name: "WrongRoot",
859
+ children: [],
860
+ };
861
+
862
+ expect(() => proxy.set(invalidInput as any)).toThrow(Primitive.ValidationError);
863
+ });
864
+
865
+ it("handles deeply nested structures", () => {
866
+ const { env, operations } = createEnvWithState();
867
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
868
+
869
+ proxy.set({
870
+ type: "folder",
871
+ name: "Root",
872
+ children: [
873
+ {
874
+ type: "folder" as const,
875
+ name: "Level1",
876
+ children: [
877
+ {
878
+ type: "folder",
879
+ name: "Level2",
880
+ children: [
881
+ { type: "file", name: "deep.txt", children: [] },
882
+ ],
883
+ },
884
+ ],
885
+ },
886
+ ],
887
+ });
888
+
889
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
890
+ expect(payload).toHaveLength(4);
891
+
892
+ // Verify parent chain (cast data to access name since TreeNodeState.data is unknown)
893
+ const root = payload.find(n => (n.data as { name: string }).name === "Root");
894
+ const level1 = payload.find(n => (n.data as { name: string }).name === "Level1");
895
+ const level2 = payload.find(n => (n.data as { name: string }).name === "Level2");
896
+ const deep = payload.find(n => (n.data as { name: string }).name === "deep.txt");
897
+
898
+ expect(root!.parentId).toBe(null);
899
+ expect(level1!.parentId).toBe(root!.id);
900
+ expect(level2!.parentId).toBe(level1!.id);
901
+ expect(deep!.parentId).toBe(level2!.id);
902
+ });
903
+
904
+ it("applies data defaults to nested input", () => {
905
+ const { env, operations } = createEnvWithState();
906
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
907
+
908
+ proxy.set({
909
+ type: "folder",
910
+ name: "Root",
911
+ children: [
912
+ { type: "file", name: "file.txt", children: [] }, // size omitted, should use default
913
+ ],
914
+ });
915
+
916
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
917
+ const fileNode = payload.find(n => n.type === "file");
918
+ expect((fileNode!.data as any).size).toBe(0); // Default value
919
+ });
920
+ });
921
+
922
+ describe("default() with nested input", () => {
923
+ it("creates initial state from nested default", () => {
924
+ const treeWithDefault = fileSystemTree.default({
925
+ type: "folder",
926
+ id: "default-root",
927
+ name: "Default Root",
928
+ children: [
929
+ { type: "file", id: "default-file", name: "readme.txt", children: [] },
930
+ ],
931
+ });
932
+
933
+ const initialState = treeWithDefault._internal.getInitialState();
934
+ expect(initialState).toHaveLength(2);
935
+
936
+ const root = initialState!.find(n => n.id === "default-root");
937
+ const file = initialState!.find(n => n.id === "default-file");
938
+
939
+ expect(root!.type).toBe("folder");
940
+ expect(root!.parentId).toBe(null);
941
+ expect(root!.data).toEqual({ name: "Default Root" });
942
+
943
+ expect(file!.type).toBe("file");
944
+ expect(file!.parentId).toBe("default-root");
945
+ expect((file!.data as any).size).toBe(0); // Default applied
946
+ });
947
+
948
+ it("generates IDs for default when not provided", () => {
949
+ const treeWithDefault = fileSystemTree.default({
950
+ type: "folder",
951
+ name: "Root",
952
+ children: [],
953
+ });
954
+
955
+ const initialState = treeWithDefault._internal.getInitialState();
956
+ expect(initialState).toHaveLength(1);
957
+ expect(typeof initialState![0]!.id).toBe("string");
958
+ expect(initialState![0]!.id.length).toBeGreaterThan(0);
959
+ });
960
+ });
961
+
962
+ describe("sibling ordering", () => {
963
+ it("maintains children order with correct positions", () => {
964
+ const { env, operations } = createEnvWithState();
965
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
966
+
967
+ proxy.set({
968
+ type: "folder",
969
+ name: "Root",
970
+ children: [
971
+ { type: "file", id: "first", name: "a.txt", children: [] },
972
+ { type: "file", id: "second", name: "b.txt", children: [] },
973
+ { type: "file", id: "third", name: "c.txt", children: [] },
974
+ ],
975
+ });
976
+
977
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
978
+ const first = payload.find(n => n.id === "first")!;
979
+ const second = payload.find(n => n.id === "second")!;
980
+ const third = payload.find(n => n.id === "third")!;
981
+
982
+ // Positions should be in ascending order
983
+ expect(first.pos < second.pos).toBe(true);
984
+ expect(second.pos < third.pos).toBe(true);
985
+ });
986
+ });
987
+ });
988
+
715
989
  // =============================================================================
716
990
  // Integration Tests - Tree with Complex Structures
717
991
  // =============================================================================
@@ -41,7 +41,7 @@ const createTransactionFromDoc = <TSchema extends Primitive.AnyPrimitive>(
41
41
  initialState: Primitive.InferState<TSchema> | undefined,
42
42
  fn: (root: Primitive.InferProxy<TSchema>) => void
43
43
  ): Transaction.Transaction => {
44
- const doc = Document.make(schema, { initial: initialState });
44
+ const doc = Document.make(schema, { initialState: initialState });
45
45
  doc.transaction(fn);
46
46
  return doc.flush();
47
47
  };
package/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "extends": "./tsconfig.build.json",
3
- "include": ["src", "test"],
3
+ "include": ["src", "tests"],
4
4
  "compilerOptions": {
5
5
  "allowJs": false,
6
6
  "strictNullChecks": true