@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.
- package/.turbo/turbo-build.log +202 -198
- package/dist/Document.cjs +1 -2
- package/dist/Document.d.cts +9 -3
- package/dist/Document.d.cts.map +1 -1
- package/dist/Document.d.mts +9 -3
- package/dist/Document.d.mts.map +1 -1
- package/dist/Document.mjs +1 -2
- package/dist/Document.mjs.map +1 -1
- package/dist/Presence.d.mts.map +1 -1
- package/dist/Primitive.d.cts +2 -2
- package/dist/Primitive.d.mts +2 -2
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutProperties.cjs +15 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutProperties.mjs +15 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutPropertiesLoose.cjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutPropertiesLoose.mjs +13 -0
- package/dist/client/ClientDocument.cjs +17 -12
- package/dist/client/ClientDocument.d.mts.map +1 -1
- package/dist/client/ClientDocument.mjs +17 -12
- package/dist/client/ClientDocument.mjs.map +1 -1
- package/dist/client/WebSocketTransport.cjs +6 -6
- package/dist/client/WebSocketTransport.mjs +6 -6
- package/dist/client/WebSocketTransport.mjs.map +1 -1
- package/dist/primitives/Tree.cjs +58 -8
- package/dist/primitives/Tree.d.cts +99 -10
- package/dist/primitives/Tree.d.cts.map +1 -1
- package/dist/primitives/Tree.d.mts +99 -10
- package/dist/primitives/Tree.d.mts.map +1 -1
- package/dist/primitives/Tree.mjs +58 -8
- package/dist/primitives/Tree.mjs.map +1 -1
- package/dist/primitives/shared.d.cts +9 -0
- package/dist/primitives/shared.d.cts.map +1 -1
- package/dist/primitives/shared.d.mts +9 -0
- package/dist/primitives/shared.d.mts.map +1 -1
- package/dist/primitives/shared.mjs.map +1 -1
- package/dist/server/ServerDocument.cjs +1 -1
- package/dist/server/ServerDocument.d.cts +3 -3
- package/dist/server/ServerDocument.d.cts.map +1 -1
- package/dist/server/ServerDocument.d.mts +3 -3
- package/dist/server/ServerDocument.d.mts.map +1 -1
- package/dist/server/ServerDocument.mjs +1 -1
- package/dist/server/ServerDocument.mjs.map +1 -1
- package/package.json +2 -2
- package/src/Document.ts +18 -5
- package/src/client/ClientDocument.ts +20 -21
- package/src/client/WebSocketTransport.ts +9 -9
- package/src/primitives/Tree.ts +213 -19
- package/src/primitives/shared.ts +10 -1
- package/src/server/ServerDocument.ts +4 -3
- package/tests/client/ClientDocument.test.ts +309 -2
- package/tests/client/WebSocketTransport.test.ts +228 -3
- package/tests/primitives/Tree.test.ts +296 -17
- package/tests/server/ServerDocument.test.ts +1 -1
- 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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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, {
|
|
44
|
+
const doc = Document.make(schema, { initialState: initialState });
|
|
45
45
|
doc.transaction(fn);
|
|
46
46
|
return doc.flush();
|
|
47
47
|
};
|