@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.
- package/README.md +17 -0
- package/package.json +33 -0
- package/src/Document.ts +256 -0
- package/src/FractionalIndex.ts +1249 -0
- package/src/Operation.ts +59 -0
- package/src/OperationDefinition.ts +23 -0
- package/src/OperationPath.ts +197 -0
- package/src/Presence.ts +142 -0
- package/src/Primitive.ts +32 -0
- package/src/Proxy.ts +8 -0
- package/src/ProxyEnvironment.ts +52 -0
- package/src/Transaction.ts +72 -0
- package/src/Transform.ts +13 -0
- package/src/client/ClientDocument.ts +1163 -0
- package/src/client/Rebase.ts +309 -0
- package/src/client/StateMonitor.ts +307 -0
- package/src/client/Transport.ts +318 -0
- package/src/client/WebSocketTransport.ts +572 -0
- package/src/client/errors.ts +145 -0
- package/src/client/index.ts +61 -0
- package/src/index.ts +12 -0
- package/src/primitives/Array.ts +457 -0
- package/src/primitives/Boolean.ts +128 -0
- package/src/primitives/Lazy.ts +89 -0
- package/src/primitives/Literal.ts +128 -0
- package/src/primitives/Number.ts +169 -0
- package/src/primitives/String.ts +189 -0
- package/src/primitives/Struct.ts +348 -0
- package/src/primitives/Tree.ts +1120 -0
- package/src/primitives/TreeNode.ts +113 -0
- package/src/primitives/Union.ts +329 -0
- package/src/primitives/shared.ts +122 -0
- package/src/server/ServerDocument.ts +267 -0
- package/src/server/errors.ts +90 -0
- package/src/server/index.ts +40 -0
- package/tests/Document.test.ts +556 -0
- package/tests/FractionalIndex.test.ts +377 -0
- package/tests/OperationPath.test.ts +151 -0
- package/tests/Presence.test.ts +321 -0
- package/tests/Primitive.test.ts +381 -0
- package/tests/client/ClientDocument.test.ts +1398 -0
- package/tests/client/WebSocketTransport.test.ts +992 -0
- package/tests/primitives/Array.test.ts +418 -0
- package/tests/primitives/Boolean.test.ts +126 -0
- package/tests/primitives/Lazy.test.ts +143 -0
- package/tests/primitives/Literal.test.ts +122 -0
- package/tests/primitives/Number.test.ts +133 -0
- package/tests/primitives/String.test.ts +128 -0
- package/tests/primitives/Struct.test.ts +311 -0
- package/tests/primitives/Tree.test.ts +467 -0
- package/tests/primitives/TreeNode.test.ts +50 -0
- package/tests/primitives/Union.test.ts +210 -0
- package/tests/server/ServerDocument.test.ts +528 -0
- package/tsconfig.build.json +24 -0
- package/tsconfig.json +8 -0
- package/tsdown.config.ts +18 -0
- package/vitest.mts +11 -0
|
@@ -0,0 +1,210 @@
|
|
|
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("UnionPrimitive", () => {
|
|
8
|
+
const TextVariant = Primitive.Struct({
|
|
9
|
+
type: Primitive.Literal("text" as const),
|
|
10
|
+
content: Primitive.String(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const NumberVariant = Primitive.Struct({
|
|
14
|
+
type: Primitive.Literal("number" as const),
|
|
15
|
+
value: Primitive.Number(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const unionPrimitive = Primitive.Union({
|
|
19
|
+
variants: {
|
|
20
|
+
text: TextVariant,
|
|
21
|
+
number: NumberVariant,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("proxy", () => {
|
|
26
|
+
it("set() generates correct operation", () => {
|
|
27
|
+
const operations: Operation.Operation<any, any, any>[] = [];
|
|
28
|
+
const env = ProxyEnvironment.make((op) => operations.push(op));
|
|
29
|
+
|
|
30
|
+
const proxy = unionPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
31
|
+
|
|
32
|
+
proxy.set({ type: "text", content: "Hello" });
|
|
33
|
+
|
|
34
|
+
expect(operations).toHaveLength(1);
|
|
35
|
+
expect(operations[0]!.kind).toBe("union.set");
|
|
36
|
+
expect(operations[0]!.payload).toEqual({ type: "text", content: "Hello" });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("as() returns variant proxy", () => {
|
|
40
|
+
const operations: Operation.Operation<any, any, any>[] = [];
|
|
41
|
+
const env = ProxyEnvironment.make((op) => operations.push(op));
|
|
42
|
+
|
|
43
|
+
const proxy = unionPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
44
|
+
|
|
45
|
+
proxy.as("text").content.set("Updated content");
|
|
46
|
+
|
|
47
|
+
expect(operations).toHaveLength(1);
|
|
48
|
+
expect(operations[0]!.kind).toBe("string.set");
|
|
49
|
+
expect(operations[0]!.path.toTokens()).toEqual(["content"]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("as() with different variant", () => {
|
|
53
|
+
const operations: Operation.Operation<any, any, any>[] = [];
|
|
54
|
+
const env = ProxyEnvironment.make((op) => operations.push(op));
|
|
55
|
+
|
|
56
|
+
const proxy = unionPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
57
|
+
|
|
58
|
+
proxy.as("number").value.set(42);
|
|
59
|
+
|
|
60
|
+
expect(operations).toHaveLength(1);
|
|
61
|
+
expect(operations[0]!.kind).toBe("number.set");
|
|
62
|
+
expect(operations[0]!.payload).toBe(42);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("as() throws for unknown variant", () => {
|
|
66
|
+
const env = ProxyEnvironment.make(() => {});
|
|
67
|
+
const proxy = unionPrimitive._internal.createProxy(env, OperationPath.make(""));
|
|
68
|
+
|
|
69
|
+
expect(() => proxy.as("unknown" as any)).toThrow(Primitive.ValidationError);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("applyOperation", () => {
|
|
74
|
+
it("union.set replaces entire value", () => {
|
|
75
|
+
const operation: Operation.Operation<any, any, any> = {
|
|
76
|
+
kind: "union.set",
|
|
77
|
+
path: OperationPath.make(""),
|
|
78
|
+
payload: { type: "text", content: "New text" },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = unionPrimitive._internal.applyOperation(undefined, operation);
|
|
82
|
+
expect(result).toEqual({ type: "text", content: "New text" });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("union.set can change variant type", () => {
|
|
86
|
+
const operation: Operation.Operation<any, any, any> = {
|
|
87
|
+
kind: "union.set",
|
|
88
|
+
path: OperationPath.make(""),
|
|
89
|
+
payload: { type: "number", value: 100 },
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const currentState = { type: "text" as const, content: "old" };
|
|
93
|
+
const result = unionPrimitive._internal.applyOperation(currentState, operation);
|
|
94
|
+
expect(result).toEqual({ type: "number", value: 100 });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("delegates field operations to active variant", () => {
|
|
98
|
+
const operation: Operation.Operation<any, any, any> = {
|
|
99
|
+
kind: "string.set",
|
|
100
|
+
path: OperationPath.make("content"),
|
|
101
|
+
payload: "Updated",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const currentState = { type: "text" as const, content: "Original" };
|
|
105
|
+
const result = unionPrimitive._internal.applyOperation(currentState, operation);
|
|
106
|
+
expect(result).toEqual({ type: "text", content: "Updated" });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("throws ValidationError for non-object payload on set", () => {
|
|
110
|
+
const operation: Operation.Operation<any, any, any> = {
|
|
111
|
+
kind: "union.set",
|
|
112
|
+
path: OperationPath.make(""),
|
|
113
|
+
payload: "not an object",
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
expect(() => unionPrimitive._internal.applyOperation(undefined, operation)).toThrow(
|
|
117
|
+
Primitive.ValidationError
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("throws ValidationError for missing discriminator", () => {
|
|
122
|
+
const operation: Operation.Operation<any, any, any> = {
|
|
123
|
+
kind: "union.set",
|
|
124
|
+
path: OperationPath.make(""),
|
|
125
|
+
payload: { content: "no type field" },
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
expect(() => unionPrimitive._internal.applyOperation(undefined, operation)).toThrow(
|
|
129
|
+
Primitive.ValidationError
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("throws ValidationError for nested operation on undefined state", () => {
|
|
134
|
+
const operation: Operation.Operation<any, any, any> = {
|
|
135
|
+
kind: "string.set",
|
|
136
|
+
path: OperationPath.make("content"),
|
|
137
|
+
payload: "value",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
expect(() => unionPrimitive._internal.applyOperation(undefined, operation)).toThrow(
|
|
141
|
+
Primitive.ValidationError
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("getInitialState", () => {
|
|
147
|
+
it("returns undefined when no default is set", () => {
|
|
148
|
+
expect(unionPrimitive._internal.getInitialState()).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("returns the default value when set", () => {
|
|
152
|
+
const withDefault = unionPrimitive.default({ type: "text", content: "default" });
|
|
153
|
+
expect(withDefault._internal.getInitialState()).toEqual({
|
|
154
|
+
type: "text",
|
|
155
|
+
content: "default",
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("custom discriminator", () => {
|
|
161
|
+
it("supports custom discriminator field", () => {
|
|
162
|
+
const KindVariantA = Primitive.Struct({
|
|
163
|
+
kind: Primitive.Literal("a" as const),
|
|
164
|
+
data: Primitive.String(),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const KindVariantB = Primitive.Struct({
|
|
168
|
+
kind: Primitive.Literal("b" as const),
|
|
169
|
+
count: Primitive.Number(),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const customUnion = Primitive.Union({
|
|
173
|
+
discriminator: "kind",
|
|
174
|
+
variants: {
|
|
175
|
+
a: KindVariantA,
|
|
176
|
+
b: KindVariantB,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(customUnion.discriminator).toBe("kind");
|
|
181
|
+
|
|
182
|
+
const operation: Operation.Operation<any, any, any> = {
|
|
183
|
+
kind: "string.set",
|
|
184
|
+
path: OperationPath.make("data"),
|
|
185
|
+
payload: "Updated data",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const currentState = { kind: "a" as const, data: "original" };
|
|
189
|
+
const result = customUnion._internal.applyOperation(currentState, operation);
|
|
190
|
+
expect(result).toEqual({ kind: "a", data: "Updated data" });
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("type inference", () => {
|
|
195
|
+
it("infers correct state type from union definition", () => {
|
|
196
|
+
type ExpectedState = Primitive.InferState<typeof unionPrimitive>;
|
|
197
|
+
|
|
198
|
+
// This is a compile-time check
|
|
199
|
+
const textState: ExpectedState = { type: "text", content: "hello" };
|
|
200
|
+
const numberState: ExpectedState = { type: "number", value: 42 };
|
|
201
|
+
|
|
202
|
+
expect(textState.type).toBe("text");
|
|
203
|
+
expect(numberState.type).toBe("number");
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// =============================================================================
|
|
209
|
+
// Integration Tests - Complex Nested Structures
|
|
210
|
+
// =============================================================================
|