@voidhash/mimic 0.0.4 → 0.0.6

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 (53) 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/Presence.d.mts.map +1 -1
  10. package/dist/Primitive.d.cts +2 -2
  11. package/dist/Primitive.d.mts +2 -2
  12. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutProperties.cjs +15 -0
  13. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutProperties.mjs +15 -0
  14. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutPropertiesLoose.cjs +14 -0
  15. package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutPropertiesLoose.mjs +13 -0
  16. package/dist/client/ClientDocument.cjs +17 -12
  17. package/dist/client/ClientDocument.d.mts.map +1 -1
  18. package/dist/client/ClientDocument.mjs +17 -12
  19. package/dist/client/ClientDocument.mjs.map +1 -1
  20. package/dist/client/WebSocketTransport.cjs +6 -6
  21. package/dist/client/WebSocketTransport.mjs +6 -6
  22. package/dist/client/WebSocketTransport.mjs.map +1 -1
  23. package/dist/primitives/Tree.cjs +58 -8
  24. package/dist/primitives/Tree.d.cts +99 -10
  25. package/dist/primitives/Tree.d.cts.map +1 -1
  26. package/dist/primitives/Tree.d.mts +99 -10
  27. package/dist/primitives/Tree.d.mts.map +1 -1
  28. package/dist/primitives/Tree.mjs +58 -8
  29. package/dist/primitives/Tree.mjs.map +1 -1
  30. package/dist/primitives/shared.d.cts +9 -0
  31. package/dist/primitives/shared.d.cts.map +1 -1
  32. package/dist/primitives/shared.d.mts +9 -0
  33. package/dist/primitives/shared.d.mts.map +1 -1
  34. package/dist/primitives/shared.mjs.map +1 -1
  35. package/dist/server/ServerDocument.cjs +1 -1
  36. package/dist/server/ServerDocument.d.cts +3 -3
  37. package/dist/server/ServerDocument.d.cts.map +1 -1
  38. package/dist/server/ServerDocument.d.mts +3 -3
  39. package/dist/server/ServerDocument.d.mts.map +1 -1
  40. package/dist/server/ServerDocument.mjs +1 -1
  41. package/dist/server/ServerDocument.mjs.map +1 -1
  42. package/package.json +2 -2
  43. package/src/Document.ts +18 -5
  44. package/src/client/ClientDocument.ts +20 -21
  45. package/src/client/WebSocketTransport.ts +9 -9
  46. package/src/primitives/Tree.ts +213 -19
  47. package/src/primitives/shared.ts +10 -1
  48. package/src/server/ServerDocument.ts +4 -3
  49. package/tests/client/ClientDocument.test.ts +309 -2
  50. package/tests/client/WebSocketTransport.test.ts +228 -3
  51. package/tests/primitives/Tree.test.ts +296 -17
  52. package/tests/server/ServerDocument.test.ts +1 -1
  53. 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", () => {
@@ -279,15 +294,20 @@ describe("TreePrimitive", () => {
279
294
  const { env } = createEnvWithState(initialState);
280
295
  const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
281
296
 
297
+
282
298
  const snapshot = proxy.toSnapshot();
283
299
  expect(snapshot).toBeDefined();
284
300
  expect(snapshot!.id).toBe("root");
285
301
  expect(snapshot!.type).toBe("folder");
302
+ expect(snapshot!.parentId).toBe(null);
303
+ expect(snapshot!.pos).toBe("a0");
286
304
  expect(snapshot!.name).toBe("Root"); // Data spread at node level
287
305
  expect(snapshot!.children).toHaveLength(2);
288
306
 
289
307
  const file1Snapshot = snapshot!.children[0]!;
290
308
  expect(file1Snapshot.id).toBe("file1");
309
+ expect(file1Snapshot.parentId).toBe("root");
310
+ expect(file1Snapshot.pos).toBe("a0");
291
311
  expect(file1Snapshot.name).toBe("File1");
292
312
  expect(file1Snapshot.children).toEqual([]);
293
313
 
@@ -452,12 +472,20 @@ describe("TreePrimitive", () => {
452
472
  expect(typeof initialState![0]!.pos).toBe("string");
453
473
  });
454
474
 
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);
475
+ it("returns the default value when set (converted from nested)", () => {
476
+ const defaultInput = {
477
+ type: "folder" as const,
478
+ id: "root",
479
+ name: "Root",
480
+ children: [],
481
+ };
482
+ const withDefault = fileSystemTree.default(defaultInput);
483
+ const initialState = withDefault._internal.getInitialState();
484
+ expect(initialState).toHaveLength(1);
485
+ expect(initialState![0]!.id).toBe("root");
486
+ expect(initialState![0]!.type).toBe("folder");
487
+ expect(initialState![0]!.parentId).toBe(null);
488
+ expect(initialState![0]!.data).toEqual({ name: "Root" });
461
489
  });
462
490
  });
463
491
 
@@ -712,6 +740,257 @@ describe("TreePrimitive", () => {
712
740
  });
713
741
  });
714
742
 
743
+ describe("TreePrimitive - nested input for set() and default()", () => {
744
+ // Define node types using the new TreeNode API
745
+ // Using TreeNodeSelf for self-referential nodes preserves type safety
746
+ const FileNode = Primitive.TreeNode("file", {
747
+ data: Primitive.Struct({ name: Primitive.String(), size: Primitive.Number().default(0) }),
748
+ children: [] as const,
749
+ });
750
+
751
+ const FolderNode = Primitive.TreeNode("folder", {
752
+ data: Primitive.Struct({ name: Primitive.String() }),
753
+ children: [Primitive.TreeNodeSelf, FileNode],
754
+ });
755
+
756
+ const fileSystemTree = Primitive.Tree({
757
+ root: FolderNode,
758
+ });
759
+
760
+ // Helper to create a mock environment with state access
761
+ const createEnvWithState = (
762
+ state: Primitive.TreeState<typeof FolderNode> = []
763
+ ): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
764
+ const operations: Operation.Operation<any, any, any>[] = [];
765
+ let currentState = [...state] as Primitive.TreeState<typeof FolderNode>;
766
+ let idCounter = 0;
767
+
768
+ const env = ProxyEnvironment.make({
769
+ onOperation: (op) => {
770
+ operations.push(op);
771
+ // Apply operation to keep state in sync
772
+ currentState = fileSystemTree._internal.applyOperation(currentState, op);
773
+ },
774
+ getState: () => currentState,
775
+ generateId: () => `node-${++idCounter}`,
776
+ });
777
+
778
+ return { env, operations };
779
+ };
780
+
781
+ describe("set() with nested input", () => {
782
+ it("converts nested input to flat TreeState", () => {
783
+ const { env, operations } = createEnvWithState();
784
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
785
+
786
+ proxy.set({
787
+ type: "folder",
788
+ name: "Root",
789
+ children: [
790
+ { type: "file", name: "file1.txt", children: [] },
791
+ { type: "file", name: "file2.txt", children: [] },
792
+ ],
793
+ });
794
+
795
+ expect(operations).toHaveLength(1);
796
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
797
+ expect(payload).toHaveLength(3);
798
+
799
+ // Root
800
+ expect(payload[0]!.type).toBe("folder");
801
+ expect(payload[0]!.parentId).toBe(null);
802
+ expect(payload[0]!.data).toEqual({ name: "Root" });
803
+
804
+ // First file child
805
+ expect(payload[1]!.type).toBe("file");
806
+ expect(payload[1]!.parentId).toBe(payload[0]!.id);
807
+ expect(payload[1]!.data).toEqual({ name: "file1.txt", size: 0 }); // size has default
808
+
809
+ // Second file child
810
+ expect(payload[2]!.type).toBe("file");
811
+ expect(payload[2]!.parentId).toBe(payload[0]!.id);
812
+ expect(payload[2]!.data).toEqual({ name: "file2.txt", size: 0 });
813
+
814
+ // Positions should be in order
815
+ expect(payload[1]!.pos < payload[2]!.pos).toBe(true);
816
+ });
817
+
818
+ it("allows explicit IDs in nested input", () => {
819
+ const { env, operations } = createEnvWithState();
820
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
821
+
822
+ proxy.set({
823
+ type: "folder",
824
+ id: "my-root",
825
+ name: "Root",
826
+ children: [
827
+ { type: "file", id: "my-file", name: "file.txt", children: [] },
828
+ ],
829
+ });
830
+
831
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
832
+ expect(payload[0]!.id).toBe("my-root");
833
+ expect(payload[1]!.id).toBe("my-file");
834
+ });
835
+
836
+ it("throws on duplicate IDs", () => {
837
+ const { env } = createEnvWithState();
838
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
839
+
840
+ expect(() =>
841
+ proxy.set({
842
+ type: "folder",
843
+ id: "dup",
844
+ name: "Root",
845
+ children: [
846
+ { type: "file", id: "dup", name: "file.txt", children: [] },
847
+ ],
848
+ })
849
+ ).toThrow(Primitive.ValidationError);
850
+ });
851
+
852
+ it("validates child types against schema", () => {
853
+ const { env } = createEnvWithState();
854
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
855
+
856
+ // File nodes cannot have children with type "folder"
857
+ // But since we can't test FileNode as root (wrong type), let's test folder with wrong child
858
+ // Actually FolderNode accepts both FolderNode and FileNode, so this won't fail on types
859
+ // Let's test wrong root type instead - but the input is typed, so this is caught at compile time
860
+ // We can test runtime by forcing wrong type
861
+ const invalidInput = {
862
+ type: "file" as "folder", // Cast to bypass type check
863
+ name: "WrongRoot",
864
+ children: [],
865
+ };
866
+
867
+ expect(() => proxy.set(invalidInput as any)).toThrow(Primitive.ValidationError);
868
+ });
869
+
870
+ it("handles deeply nested structures", () => {
871
+ const { env, operations } = createEnvWithState();
872
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
873
+
874
+ proxy.set({
875
+ type: "folder",
876
+ name: "Root",
877
+ children: [
878
+ {
879
+ type: "folder" as const,
880
+ name: "Level1",
881
+ children: [
882
+ {
883
+ type: "folder",
884
+ name: "Level2",
885
+ children: [
886
+ { type: "file", name: "deep.txt", children: [] },
887
+ ],
888
+ },
889
+ ],
890
+ },
891
+ ],
892
+ });
893
+
894
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
895
+ expect(payload).toHaveLength(4);
896
+
897
+ // Verify parent chain (cast data to access name since TreeNodeState.data is unknown)
898
+ const root = payload.find(n => (n.data as { name: string }).name === "Root");
899
+ const level1 = payload.find(n => (n.data as { name: string }).name === "Level1");
900
+ const level2 = payload.find(n => (n.data as { name: string }).name === "Level2");
901
+ const deep = payload.find(n => (n.data as { name: string }).name === "deep.txt");
902
+
903
+ expect(root!.parentId).toBe(null);
904
+ expect(level1!.parentId).toBe(root!.id);
905
+ expect(level2!.parentId).toBe(level1!.id);
906
+ expect(deep!.parentId).toBe(level2!.id);
907
+ });
908
+
909
+ it("applies data defaults to nested input", () => {
910
+ const { env, operations } = createEnvWithState();
911
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
912
+
913
+ proxy.set({
914
+ type: "folder",
915
+ name: "Root",
916
+ children: [
917
+ { type: "file", name: "file.txt", children: [] }, // size omitted, should use default
918
+ ],
919
+ });
920
+
921
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
922
+ const fileNode = payload.find(n => n.type === "file");
923
+ expect((fileNode!.data as any).size).toBe(0); // Default value
924
+ });
925
+ });
926
+
927
+ describe("default() with nested input", () => {
928
+ it("creates initial state from nested default", () => {
929
+ const treeWithDefault = fileSystemTree.default({
930
+ type: "folder",
931
+ id: "default-root",
932
+ name: "Default Root",
933
+ children: [
934
+ { type: "file", id: "default-file", name: "readme.txt", children: [] },
935
+ ],
936
+ });
937
+
938
+ const initialState = treeWithDefault._internal.getInitialState();
939
+ expect(initialState).toHaveLength(2);
940
+
941
+ const root = initialState!.find(n => n.id === "default-root");
942
+ const file = initialState!.find(n => n.id === "default-file");
943
+
944
+ expect(root!.type).toBe("folder");
945
+ expect(root!.parentId).toBe(null);
946
+ expect(root!.data).toEqual({ name: "Default Root" });
947
+
948
+ expect(file!.type).toBe("file");
949
+ expect(file!.parentId).toBe("default-root");
950
+ expect((file!.data as any).size).toBe(0); // Default applied
951
+ });
952
+
953
+ it("generates IDs for default when not provided", () => {
954
+ const treeWithDefault = fileSystemTree.default({
955
+ type: "folder",
956
+ name: "Root",
957
+ children: [],
958
+ });
959
+
960
+ const initialState = treeWithDefault._internal.getInitialState();
961
+ expect(initialState).toHaveLength(1);
962
+ expect(typeof initialState![0]!.id).toBe("string");
963
+ expect(initialState![0]!.id.length).toBeGreaterThan(0);
964
+ });
965
+ });
966
+
967
+ describe("sibling ordering", () => {
968
+ it("maintains children order with correct positions", () => {
969
+ const { env, operations } = createEnvWithState();
970
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
971
+
972
+ proxy.set({
973
+ type: "folder",
974
+ name: "Root",
975
+ children: [
976
+ { type: "file", id: "first", name: "a.txt", children: [] },
977
+ { type: "file", id: "second", name: "b.txt", children: [] },
978
+ { type: "file", id: "third", name: "c.txt", children: [] },
979
+ ],
980
+ });
981
+
982
+ const payload = operations[0]!.payload as Primitive.TreeState<typeof FolderNode>;
983
+ const first = payload.find(n => n.id === "first")!;
984
+ const second = payload.find(n => n.id === "second")!;
985
+ const third = payload.find(n => n.id === "third")!;
986
+
987
+ // Positions should be in ascending order
988
+ expect(first.pos < second.pos).toBe(true);
989
+ expect(second.pos < third.pos).toBe(true);
990
+ });
991
+ });
992
+ });
993
+
715
994
  // =============================================================================
716
995
  // Integration Tests - Tree with Complex Structures
717
996
  // =============================================================================
@@ -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