@voidhash/mimic 0.0.1-alpha.1

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 (57) hide show
  1. package/README.md +17 -0
  2. package/package.json +33 -0
  3. package/src/Document.ts +256 -0
  4. package/src/FractionalIndex.ts +1249 -0
  5. package/src/Operation.ts +59 -0
  6. package/src/OperationDefinition.ts +23 -0
  7. package/src/OperationPath.ts +197 -0
  8. package/src/Presence.ts +142 -0
  9. package/src/Primitive.ts +32 -0
  10. package/src/Proxy.ts +8 -0
  11. package/src/ProxyEnvironment.ts +52 -0
  12. package/src/Transaction.ts +72 -0
  13. package/src/Transform.ts +13 -0
  14. package/src/client/ClientDocument.ts +1163 -0
  15. package/src/client/Rebase.ts +309 -0
  16. package/src/client/StateMonitor.ts +307 -0
  17. package/src/client/Transport.ts +318 -0
  18. package/src/client/WebSocketTransport.ts +572 -0
  19. package/src/client/errors.ts +145 -0
  20. package/src/client/index.ts +61 -0
  21. package/src/index.ts +12 -0
  22. package/src/primitives/Array.ts +457 -0
  23. package/src/primitives/Boolean.ts +128 -0
  24. package/src/primitives/Lazy.ts +89 -0
  25. package/src/primitives/Literal.ts +128 -0
  26. package/src/primitives/Number.ts +169 -0
  27. package/src/primitives/String.ts +189 -0
  28. package/src/primitives/Struct.ts +348 -0
  29. package/src/primitives/Tree.ts +1120 -0
  30. package/src/primitives/TreeNode.ts +113 -0
  31. package/src/primitives/Union.ts +329 -0
  32. package/src/primitives/shared.ts +122 -0
  33. package/src/server/ServerDocument.ts +267 -0
  34. package/src/server/errors.ts +90 -0
  35. package/src/server/index.ts +40 -0
  36. package/tests/Document.test.ts +556 -0
  37. package/tests/FractionalIndex.test.ts +377 -0
  38. package/tests/OperationPath.test.ts +151 -0
  39. package/tests/Presence.test.ts +321 -0
  40. package/tests/Primitive.test.ts +381 -0
  41. package/tests/client/ClientDocument.test.ts +1398 -0
  42. package/tests/client/WebSocketTransport.test.ts +992 -0
  43. package/tests/primitives/Array.test.ts +418 -0
  44. package/tests/primitives/Boolean.test.ts +126 -0
  45. package/tests/primitives/Lazy.test.ts +143 -0
  46. package/tests/primitives/Literal.test.ts +122 -0
  47. package/tests/primitives/Number.test.ts +133 -0
  48. package/tests/primitives/String.test.ts +128 -0
  49. package/tests/primitives/Struct.test.ts +311 -0
  50. package/tests/primitives/Tree.test.ts +467 -0
  51. package/tests/primitives/TreeNode.test.ts +50 -0
  52. package/tests/primitives/Union.test.ts +210 -0
  53. package/tests/server/ServerDocument.test.ts +528 -0
  54. package/tsconfig.build.json +24 -0
  55. package/tsconfig.json +8 -0
  56. package/tsdown.config.ts +18 -0
  57. package/vitest.mts +11 -0
@@ -0,0 +1,467 @@
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
+ describe("TreePrimitive", () => {
8
+ // Define node types using the new TreeNode API
9
+ const FileNode = Primitive.TreeNode("file", {
10
+ data: Primitive.Struct({ name: Primitive.String(), size: Primitive.Number() }),
11
+ children: [] as const,
12
+ });
13
+
14
+ const FolderNode = Primitive.TreeNode("folder", {
15
+ data: Primitive.Struct({ name: Primitive.String() }),
16
+ children: (): readonly Primitive.AnyTreeNodePrimitive[] => [FolderNode, FileNode],
17
+ });
18
+
19
+ const fileSystemTree = Primitive.Tree({
20
+ root: FolderNode,
21
+ });
22
+
23
+ // Helper to create a mock environment with state access
24
+ const createEnvWithState = (
25
+ state: Primitive.TreeState<typeof FolderNode> = []
26
+ ): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
27
+ const operations: Operation.Operation<any, any, any>[] = [];
28
+ let currentState = [...state] as Primitive.TreeState<typeof FolderNode>;
29
+ let idCounter = 0;
30
+
31
+ const env = ProxyEnvironment.make({
32
+ onOperation: (op) => {
33
+ operations.push(op);
34
+ // Apply operation to keep state in sync
35
+ currentState = fileSystemTree._internal.applyOperation(currentState, op);
36
+ },
37
+ getState: () => currentState,
38
+ generateId: () => `node-${++idCounter}`,
39
+ });
40
+
41
+ return { env, operations };
42
+ };
43
+
44
+ describe("schema", () => {
45
+ it("exposes root node type", () => {
46
+ expect(fileSystemTree.root).toBe(FolderNode);
47
+ expect(fileSystemTree.root.type).toBe("folder");
48
+ });
49
+
50
+ it("required() returns a new TreePrimitive", () => {
51
+ const required = fileSystemTree.required();
52
+ expect(required).toBeInstanceOf(Primitive.TreePrimitive);
53
+ expect(required).not.toBe(fileSystemTree);
54
+ });
55
+
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);
62
+ });
63
+ });
64
+
65
+ describe("proxy - basic operations", () => {
66
+ it("get() returns empty array for initial state", () => {
67
+ const { env } = createEnvWithState();
68
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
69
+
70
+ expect(proxy.get()).toEqual([]);
71
+ });
72
+
73
+ it("set() generates tree.set operation", () => {
74
+ const { env, operations } = createEnvWithState();
75
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
76
+
77
+ const nodes: Primitive.TreeState<typeof FolderNode> = [
78
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
79
+ ];
80
+ proxy.set(nodes);
81
+
82
+ expect(operations).toHaveLength(1);
83
+ expect(operations[0]!.kind).toBe("tree.set");
84
+ expect(operations[0]!.payload).toEqual(nodes);
85
+ });
86
+
87
+ it("root() returns the root node", () => {
88
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
89
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
90
+ { id: "child1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
91
+ ];
92
+ const { env } = createEnvWithState(initialState);
93
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
94
+
95
+ const root = proxy.root();
96
+ expect(root).toBeDefined();
97
+ expect(root!.id).toBe("root");
98
+ expect(root!.parentId).toBe(null);
99
+ });
100
+
101
+ it("node() returns a node proxy by ID", () => {
102
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
103
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
104
+ { id: "child1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
105
+ ];
106
+ const { env } = createEnvWithState(initialState);
107
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
108
+
109
+ const node = proxy.node("child1");
110
+ expect(node).toBeDefined();
111
+ expect(node!.id).toBe("child1");
112
+ expect(node!.type).toBe("file");
113
+ expect(node!.get().data).toEqual({ name: "File1", size: 100 });
114
+ });
115
+
116
+ it("children() returns ordered children", () => {
117
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
118
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
119
+ { id: "child2", type: "file", parentId: "root", pos: "a1", data: { name: "File2", size: 200 } },
120
+ { id: "child1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
121
+ ];
122
+ const { env } = createEnvWithState(initialState);
123
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
124
+
125
+ const children = proxy.children("root");
126
+ expect(children).toHaveLength(2);
127
+ expect(children[0]!.id).toBe("child1"); // a0 comes first
128
+ expect(children[1]!.id).toBe("child2"); // a1 comes second
129
+ });
130
+ });
131
+
132
+ describe("proxy - type narrowing with is() and as()", () => {
133
+ it("is() returns true for matching node type", () => {
134
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
135
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
136
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
137
+ ];
138
+ const { env } = createEnvWithState(initialState);
139
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
140
+
141
+ const fileProxy = proxy.node("file1");
142
+ expect(fileProxy!.is(FileNode)).toBe(true);
143
+ expect(fileProxy!.is(FolderNode)).toBe(false);
144
+
145
+ const folderProxy = proxy.node("root");
146
+ expect(folderProxy!.is(FolderNode)).toBe(true);
147
+ expect(folderProxy!.is(FileNode)).toBe(false);
148
+ });
149
+
150
+ it("as() returns typed proxy for correct type", () => {
151
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
152
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
153
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
154
+ ];
155
+ const { env, operations } = createEnvWithState(initialState);
156
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
157
+
158
+ const fileProxy = proxy.node("file1")!.as(FileNode);
159
+ expect(fileProxy.id).toBe("file1");
160
+ expect(fileProxy.type).toBe("file");
161
+
162
+ // Type-safe data access
163
+ fileProxy.data.name.set("UpdatedName");
164
+ expect(operations).toHaveLength(1);
165
+ expect(operations[0]!.kind).toBe("string.set");
166
+ });
167
+
168
+ it("as() throws for wrong type", () => {
169
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
170
+ { id: "file1", type: "file", parentId: null, pos: "a0", data: { name: "File1", size: 100 } },
171
+ ];
172
+ const { env } = createEnvWithState(initialState);
173
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
174
+
175
+ const nodeProxy = proxy.node("file1");
176
+ expect(() => nodeProxy!.as(FolderNode)).toThrow(Primitive.ValidationError);
177
+ });
178
+ });
179
+
180
+ describe("proxy - insert operations with TreeNode types", () => {
181
+ it("insertFirst() creates node at beginning of children", () => {
182
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
183
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
184
+ { id: "existing", type: "file", parentId: "root", pos: "a1", data: { name: "Existing", size: 100 } },
185
+ ];
186
+ const { env, operations } = createEnvWithState(initialState);
187
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
188
+
189
+ const newId = proxy.insertFirst("root", FileNode, { name: "First", size: 50 });
190
+
191
+ expect(operations).toHaveLength(1);
192
+ expect(operations[0]!.kind).toBe("tree.insert");
193
+ expect(newId).toBe("node-1");
194
+
195
+ const payload = operations[0]!.payload as { id: string; pos: string; type: string };
196
+ expect(payload.type).toBe("file");
197
+ expect(payload.pos < "a1").toBe(true); // Should be before existing
198
+ });
199
+
200
+ it("insertLast() creates node at end of children", () => {
201
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
202
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
203
+ { id: "existing", type: "file", parentId: "root", pos: "a0", data: { name: "Existing", size: 100 } },
204
+ ];
205
+ const { env, operations } = createEnvWithState(initialState);
206
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
207
+
208
+ proxy.insertLast("root", FileNode, { name: "Last", size: 50 });
209
+
210
+ const payload = operations[0]!.payload as { pos: string };
211
+ expect(payload.pos > "a0").toBe(true); // Should be after existing
212
+ });
213
+
214
+ it("insertFirst() with null parentId creates root node", () => {
215
+ const { env, operations } = createEnvWithState();
216
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
217
+
218
+ proxy.insertFirst(null, FolderNode, { name: "Root" });
219
+
220
+ expect(operations).toHaveLength(1);
221
+ const payload = operations[0]!.payload as { parentId: string | null; type: string };
222
+ expect(payload.parentId).toBe(null);
223
+ expect(payload.type).toBe("folder");
224
+ });
225
+ });
226
+
227
+ describe("proxy - validation", () => {
228
+ it("throws when inserting invalid child type", () => {
229
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
230
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
231
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
232
+ ];
233
+ const { env } = createEnvWithState(initialState);
234
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
235
+
236
+ // Files cannot have children
237
+ expect(() => proxy.insertFirst("file1", FileNode, { name: "Child", size: 50 })).toThrow(
238
+ Primitive.ValidationError
239
+ );
240
+ });
241
+
242
+ it("throws when inserting non-root type at root level", () => {
243
+ const { env } = createEnvWithState();
244
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
245
+
246
+ expect(() => proxy.insertFirst(null, FileNode, { name: "File", size: 50 })).toThrow(
247
+ Primitive.ValidationError
248
+ );
249
+ });
250
+
251
+ it("throws when inserting second root", () => {
252
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
253
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
254
+ ];
255
+ const { env } = createEnvWithState(initialState);
256
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
257
+
258
+ expect(() => proxy.insertFirst(null, FolderNode, { name: "SecondRoot" })).toThrow(
259
+ Primitive.ValidationError
260
+ );
261
+ });
262
+ });
263
+
264
+ describe("proxy - toSnapshot()", () => {
265
+ it("returns undefined for empty tree", () => {
266
+ const { env } = createEnvWithState();
267
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
268
+
269
+ expect(proxy.toSnapshot()).toBeUndefined();
270
+ });
271
+
272
+ it("returns nested snapshot with spread data", () => {
273
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
274
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
275
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
276
+ { id: "folder1", type: "folder", parentId: "root", pos: "a1", data: { name: "Subfolder" } },
277
+ { id: "file2", type: "file", parentId: "folder1", pos: "a0", data: { name: "File2", size: 200 } },
278
+ ];
279
+ const { env } = createEnvWithState(initialState);
280
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
281
+
282
+ const snapshot = proxy.toSnapshot();
283
+ expect(snapshot).toBeDefined();
284
+ expect(snapshot!.id).toBe("root");
285
+ expect(snapshot!.type).toBe("folder");
286
+ expect(snapshot!.name).toBe("Root"); // Data spread at node level
287
+ expect(snapshot!.children).toHaveLength(2);
288
+
289
+ const file1Snapshot = snapshot!.children[0]!;
290
+ expect(file1Snapshot.id).toBe("file1");
291
+ expect(file1Snapshot.name).toBe("File1");
292
+ expect(file1Snapshot.children).toEqual([]);
293
+
294
+ const folder1Snapshot = snapshot!.children[1]!;
295
+ expect(folder1Snapshot.id).toBe("folder1");
296
+ expect(folder1Snapshot.children).toHaveLength(1);
297
+ expect(folder1Snapshot.children[0]!.name).toBe("File2");
298
+ });
299
+ });
300
+
301
+ describe("proxy - at() with typed node", () => {
302
+ it("at() returns typed proxy for node data", () => {
303
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
304
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
305
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
306
+ ];
307
+ const { env, operations } = createEnvWithState(initialState);
308
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
309
+
310
+ proxy.at("file1", FileNode).name.set("UpdatedName");
311
+
312
+ expect(operations).toHaveLength(1);
313
+ expect(operations[0]!.kind).toBe("string.set");
314
+ expect(operations[0]!.path.toTokens()).toEqual(["file1", "name"]);
315
+ expect(operations[0]!.payload).toBe("UpdatedName");
316
+ });
317
+
318
+ it("at() throws when node type mismatch", () => {
319
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
320
+ { id: "file1", type: "file", parentId: null, pos: "a0", data: { name: "File1", size: 100 } },
321
+ ];
322
+ const { env } = createEnvWithState(initialState);
323
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
324
+
325
+ expect(() => proxy.at("file1", FolderNode)).toThrow(Primitive.ValidationError);
326
+ });
327
+ });
328
+
329
+ describe("proxy - move operations", () => {
330
+ it("move() changes parent and position", () => {
331
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
332
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
333
+ { id: "folder1", type: "folder", parentId: "root", pos: "a0", data: { name: "Folder1" } },
334
+ { id: "folder2", type: "folder", parentId: "root", pos: "a1", data: { name: "Folder2" } },
335
+ { id: "file1", type: "file", parentId: "folder1", pos: "a0", data: { name: "File1", size: 100 } },
336
+ ];
337
+ const { env, operations } = createEnvWithState(initialState);
338
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
339
+
340
+ // Move file1 to folder2
341
+ proxy.move("file1", "folder2", 0);
342
+
343
+ expect(operations).toHaveLength(1);
344
+ expect(operations[0]!.kind).toBe("tree.move");
345
+ const payload = operations[0]!.payload as { id: string; parentId: string };
346
+ expect(payload.id).toBe("file1");
347
+ expect(payload.parentId).toBe("folder2");
348
+ });
349
+
350
+ it("throws when moving node to its descendant (cycle prevention)", () => {
351
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
352
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
353
+ { id: "folder1", type: "folder", parentId: "root", pos: "a0", data: { name: "Folder1" } },
354
+ { id: "folder2", type: "folder", parentId: "folder1", pos: "a0", data: { name: "Folder2" } },
355
+ ];
356
+ const { env } = createEnvWithState(initialState);
357
+ const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
358
+
359
+ expect(() => proxy.move("folder1", "folder2", 0)).toThrow(Primitive.ValidationError);
360
+ });
361
+ });
362
+
363
+ describe("applyOperation", () => {
364
+ it("tree.set replaces entire tree", () => {
365
+ const newNodes: Primitive.TreeState<typeof FolderNode> = [
366
+ { id: "new-root", type: "folder", parentId: null, pos: "a0", data: { name: "NewRoot" } },
367
+ ];
368
+ const operation: Operation.Operation<any, any, any> = {
369
+ kind: "tree.set",
370
+ path: OperationPath.make(""),
371
+ payload: newNodes,
372
+ };
373
+
374
+ const result = fileSystemTree._internal.applyOperation([], operation);
375
+ expect(result).toEqual(newNodes);
376
+ });
377
+
378
+ it("tree.insert adds a new node", () => {
379
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
380
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
381
+ ];
382
+ const operation: Operation.Operation<any, any, any> = {
383
+ kind: "tree.insert",
384
+ path: OperationPath.make(""),
385
+ payload: {
386
+ id: "file1",
387
+ type: "file",
388
+ parentId: "root",
389
+ pos: "a0",
390
+ data: { name: "File1", size: 100 },
391
+ },
392
+ };
393
+
394
+ const result = fileSystemTree._internal.applyOperation(initialState, operation);
395
+ expect(result).toHaveLength(2);
396
+ expect(result[1]).toEqual({
397
+ id: "file1",
398
+ type: "file",
399
+ parentId: "root",
400
+ pos: "a0",
401
+ data: { name: "File1", size: 100 },
402
+ });
403
+ });
404
+
405
+ it("tree.remove removes node and descendants", () => {
406
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
407
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
408
+ { id: "folder1", type: "folder", parentId: "root", pos: "a0", data: { name: "Folder1" } },
409
+ { id: "file1", type: "file", parentId: "folder1", pos: "a0", data: { name: "File1", size: 100 } },
410
+ { id: "folder2", type: "folder", parentId: "root", pos: "a1", data: { name: "Folder2" } },
411
+ ];
412
+ const operation: Operation.Operation<any, any, any> = {
413
+ kind: "tree.remove",
414
+ path: OperationPath.make(""),
415
+ payload: { id: "folder1" },
416
+ };
417
+
418
+ const result = fileSystemTree._internal.applyOperation(initialState, operation);
419
+ expect(result).toHaveLength(2);
420
+ expect(result.map(n => n.id)).toEqual(["root", "folder2"]);
421
+ });
422
+
423
+ it("delegates node data operations", () => {
424
+ const initialState: Primitive.TreeState<typeof FolderNode> = [
425
+ { id: "root", type: "folder", parentId: null, pos: "a0", data: { name: "Root" } },
426
+ { id: "file1", type: "file", parentId: "root", pos: "a0", data: { name: "File1", size: 100 } },
427
+ ];
428
+ const operation: Operation.Operation<any, any, any> = {
429
+ kind: "string.set",
430
+ path: OperationPath.make("file1/name"),
431
+ payload: "UpdatedName",
432
+ };
433
+
434
+ const result = fileSystemTree._internal.applyOperation(initialState, operation);
435
+ const updatedNode = result.find(n => n.id === "file1");
436
+ expect(updatedNode!.data).toEqual({ name: "UpdatedName", size: 100 });
437
+ });
438
+ });
439
+
440
+ describe("getInitialState", () => {
441
+ it("automatically creates a root node when no default is set", () => {
442
+ const initialState = fileSystemTree._internal.getInitialState();
443
+ expect(initialState).toBeDefined();
444
+ expect(initialState).toHaveLength(1);
445
+ expect(initialState![0]).toMatchObject({
446
+ type: "folder",
447
+ parentId: null,
448
+ data: {},
449
+ });
450
+ // Verify ID and pos are generated
451
+ expect(typeof initialState![0]!.id).toBe("string");
452
+ expect(typeof initialState![0]!.pos).toBe("string");
453
+ });
454
+
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);
461
+ });
462
+ });
463
+ });
464
+
465
+ // =============================================================================
466
+ // Integration Tests - Tree with Complex Structures
467
+ // =============================================================================
@@ -0,0 +1,50 @@
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
+ describe("TreeNodePrimitive", () => {
8
+ it("creates a TreeNode with type, data, and empty children", () => {
9
+ const FileNode = Primitive.TreeNode("file", {
10
+ data: Primitive.Struct({ name: Primitive.String(), size: Primitive.Number() }),
11
+ children: [],
12
+ });
13
+
14
+ expect(FileNode.type).toBe("file");
15
+ expect(FileNode.data).toBeInstanceOf(Primitive.StructPrimitive);
16
+ expect(FileNode.children).toEqual([]);
17
+ });
18
+
19
+ it("creates a TreeNode with lazy children for self-reference", () => {
20
+ const FolderNode: Primitive.AnyTreeNodePrimitive = Primitive.TreeNode("folder", {
21
+ data: Primitive.Struct({ name: Primitive.String() }),
22
+ children: (): readonly Primitive.AnyTreeNodePrimitive[] => [FolderNode],
23
+ });
24
+
25
+ expect(FolderNode.type).toBe("folder");
26
+ expect(FolderNode.children).toHaveLength(1);
27
+ expect(FolderNode.children[0]).toBe(FolderNode);
28
+ });
29
+
30
+ it("isChildAllowed returns true for allowed child types", () => {
31
+ const FileNode = Primitive.TreeNode("file", {
32
+ data: Primitive.Struct({ name: Primitive.String() }),
33
+ children: [],
34
+ });
35
+
36
+ const FolderNode: Primitive.AnyTreeNodePrimitive = Primitive.TreeNode("folder", {
37
+ data: Primitive.Struct({ name: Primitive.String() }),
38
+ children: (): readonly Primitive.AnyTreeNodePrimitive[] => [FolderNode, FileNode],
39
+ });
40
+
41
+ expect(FolderNode.isChildAllowed("folder")).toBe(true);
42
+ expect(FolderNode.isChildAllowed("file")).toBe(true);
43
+ expect(FolderNode.isChildAllowed("unknown")).toBe(false);
44
+ expect(FileNode.isChildAllowed("file")).toBe(false);
45
+ });
46
+ });
47
+
48
+ // =============================================================================
49
+ // Tree Primitive Tests
50
+ // =============================================================================