@voidhash/mimic 0.0.1-alpha.1 → 0.0.1-alpha.10
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 +51 -0
- package/LICENSE.md +663 -0
- package/dist/Document-ChuFrTk1.cjs +571 -0
- package/dist/Document-CwiAFTIq.mjs +438 -0
- package/dist/Document-CwiAFTIq.mjs.map +1 -0
- package/dist/Presence-DKKP4v5X.d.cts +91 -0
- package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
- package/dist/Presence-DdMVKcOv.mjs +110 -0
- package/dist/Presence-DdMVKcOv.mjs.map +1 -0
- package/dist/Presence-N8u7Eppr.d.mts +91 -0
- package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
- package/dist/Presence-gWrmGBeu.cjs +126 -0
- package/dist/Primitive-CvFVxR8_.d.cts +1175 -0
- package/dist/Primitive-CvFVxR8_.d.cts.map +1 -0
- package/dist/Primitive-lEhQyGVL.d.mts +1175 -0
- package/dist/Primitive-lEhQyGVL.d.mts.map +1 -0
- package/dist/chunk-CLMFDpHK.mjs +18 -0
- package/dist/client/index.cjs +1456 -0
- package/dist/client/index.d.cts +692 -0
- package/dist/client/index.d.cts.map +1 -0
- package/dist/client/index.d.mts +692 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +1413 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.cjs +2577 -0
- package/dist/index.d.cts +143 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +143 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2526 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server/index.cjs +191 -0
- package/dist/server/index.d.cts +148 -0
- package/dist/server/index.d.cts.map +1 -0
- package/dist/server/index.d.mts +148 -0
- package/dist/server/index.d.mts.map +1 -0
- package/dist/server/index.mjs +182 -0
- package/dist/server/index.mjs.map +1 -0
- package/package.json +25 -13
- package/src/EffectSchema.ts +374 -0
- package/src/Primitive.ts +3 -0
- package/src/client/ClientDocument.ts +1 -1
- package/src/client/errors.ts +10 -10
- package/src/index.ts +1 -0
- package/src/primitives/Array.ts +57 -22
- package/src/primitives/Boolean.ts +33 -19
- package/src/primitives/Either.ts +379 -0
- package/src/primitives/Lazy.ts +16 -2
- package/src/primitives/Literal.ts +33 -20
- package/src/primitives/Number.ts +39 -26
- package/src/primitives/String.ts +40 -25
- package/src/primitives/Struct.ts +126 -29
- package/src/primitives/Tree.ts +119 -32
- package/src/primitives/TreeNode.ts +77 -30
- package/src/primitives/Union.ts +56 -29
- package/src/primitives/shared.ts +111 -9
- package/src/server/errors.ts +6 -6
- package/tests/EffectSchema.test.ts +546 -0
- package/tests/primitives/Array.test.ts +108 -0
- package/tests/primitives/Either.test.ts +707 -0
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +250 -0
- package/tsdown.config.ts +1 -1
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import * as EffectSchema from "../src/EffectSchema";
|
|
4
|
+
import * as Primitive from "../src/Primitive";
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Simple Primitive Tests
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
describe("EffectSchema - Simple Primitives", () => {
|
|
11
|
+
describe("toSetSchema", () => {
|
|
12
|
+
it("converts StringPrimitive to Schema.String", () => {
|
|
13
|
+
const primitive = Primitive.String();
|
|
14
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
15
|
+
|
|
16
|
+
// Verify it accepts valid strings
|
|
17
|
+
expect(Schema.decodeUnknownSync(schema)("hello")).toBe("hello");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("converts NumberPrimitive to Schema.Number", () => {
|
|
21
|
+
const primitive = Primitive.Number();
|
|
22
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
23
|
+
|
|
24
|
+
expect(Schema.decodeUnknownSync(schema)(42)).toBe(42);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("converts BooleanPrimitive to Schema.Boolean", () => {
|
|
28
|
+
const primitive = Primitive.Boolean();
|
|
29
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
30
|
+
|
|
31
|
+
expect(Schema.decodeUnknownSync(schema)(true)).toBe(true);
|
|
32
|
+
expect(Schema.decodeUnknownSync(schema)(false)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("converts LiteralPrimitive to Schema.Literal", () => {
|
|
36
|
+
const primitive = Primitive.Literal("active");
|
|
37
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
38
|
+
|
|
39
|
+
expect(Schema.decodeUnknownSync(schema)("active")).toBe("active");
|
|
40
|
+
|
|
41
|
+
// Should reject non-matching literals
|
|
42
|
+
expect(() => Schema.decodeUnknownSync(schema)("inactive")).toThrow();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("toUpdateSchema", () => {
|
|
47
|
+
it("update schema for simple primitives is same as set schema", () => {
|
|
48
|
+
const stringPrimitive = Primitive.String();
|
|
49
|
+
const setSchema = EffectSchema.toSetSchema(stringPrimitive);
|
|
50
|
+
const updateSchema = EffectSchema.toUpdateSchema(stringPrimitive);
|
|
51
|
+
|
|
52
|
+
// Both should accept strings
|
|
53
|
+
expect(Schema.decodeUnknownSync(setSchema)("test")).toBe("test");
|
|
54
|
+
expect(Schema.decodeUnknownSync(updateSchema)("test")).toBe("test");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Struct Primitive Tests
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
describe("EffectSchema - Struct Primitives", () => {
|
|
64
|
+
describe("toSetSchema", () => {
|
|
65
|
+
it("required fields are non-optional", () => {
|
|
66
|
+
const primitive = Primitive.Struct({
|
|
67
|
+
name: Primitive.String().required(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
71
|
+
|
|
72
|
+
// Should accept object with required field
|
|
73
|
+
expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
|
|
74
|
+
|
|
75
|
+
// Should reject missing required field
|
|
76
|
+
expect(() => Schema.decodeUnknownSync(schema)({})).toThrow();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("fields with defaults are optional", () => {
|
|
80
|
+
const primitive = Primitive.Struct({
|
|
81
|
+
name: Primitive.String().default("default"),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
85
|
+
|
|
86
|
+
// Should accept object with field
|
|
87
|
+
expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
|
|
88
|
+
|
|
89
|
+
// Should accept object without field (field is optional)
|
|
90
|
+
expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("non-required fields are optional", () => {
|
|
94
|
+
const primitive = Primitive.Struct({
|
|
95
|
+
email: Primitive.String(),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
99
|
+
|
|
100
|
+
// Should accept object with field
|
|
101
|
+
expect(Schema.decodeUnknownSync(schema)({ email: "test@example.com" })).toEqual({ email: "test@example.com" });
|
|
102
|
+
|
|
103
|
+
// Should accept object without field
|
|
104
|
+
expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("handles mixed required/optional fields", () => {
|
|
108
|
+
const primitive = Primitive.Struct({
|
|
109
|
+
name: Primitive.String().required(),
|
|
110
|
+
age: Primitive.Number().default(0),
|
|
111
|
+
email: Primitive.String(),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
115
|
+
|
|
116
|
+
// Should accept with only required field
|
|
117
|
+
expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
|
|
118
|
+
|
|
119
|
+
// Should accept with all fields
|
|
120
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
121
|
+
name: "Alice",
|
|
122
|
+
age: 30,
|
|
123
|
+
email: "alice@example.com"
|
|
124
|
+
})).toEqual({
|
|
125
|
+
name: "Alice",
|
|
126
|
+
age: 30,
|
|
127
|
+
email: "alice@example.com"
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Should reject missing required field
|
|
131
|
+
expect(() => Schema.decodeUnknownSync(schema)({ age: 30 })).toThrow();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("toUpdateSchema", () => {
|
|
136
|
+
it("all fields are optional for updates", () => {
|
|
137
|
+
const primitive = Primitive.Struct({
|
|
138
|
+
name: Primitive.String().required(),
|
|
139
|
+
age: Primitive.Number().default(0),
|
|
140
|
+
email: Primitive.String(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const schema = EffectSchema.toUpdateSchema(primitive);
|
|
144
|
+
|
|
145
|
+
// Should accept empty object
|
|
146
|
+
expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
|
|
147
|
+
|
|
148
|
+
// Should accept partial updates
|
|
149
|
+
expect(Schema.decodeUnknownSync(schema)({ name: "Alice" })).toEqual({ name: "Alice" });
|
|
150
|
+
expect(Schema.decodeUnknownSync(schema)({ age: 30 })).toEqual({ age: 30 });
|
|
151
|
+
|
|
152
|
+
// Should accept full object
|
|
153
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
154
|
+
name: "Alice",
|
|
155
|
+
age: 30,
|
|
156
|
+
email: "alice@example.com"
|
|
157
|
+
})).toEqual({
|
|
158
|
+
name: "Alice",
|
|
159
|
+
age: 30,
|
|
160
|
+
email: "alice@example.com"
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// =============================================================================
|
|
167
|
+
// Nested Struct Tests
|
|
168
|
+
// =============================================================================
|
|
169
|
+
|
|
170
|
+
describe("EffectSchema - Nested Structs", () => {
|
|
171
|
+
describe("toSetSchema", () => {
|
|
172
|
+
it("handles nested struct with required fields", () => {
|
|
173
|
+
const primitive = Primitive.Struct({
|
|
174
|
+
user: Primitive.Struct({
|
|
175
|
+
name: Primitive.String().required(),
|
|
176
|
+
age: Primitive.Number(),
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
181
|
+
|
|
182
|
+
// Should accept valid nested structure
|
|
183
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
184
|
+
user: { name: "Alice" }
|
|
185
|
+
})).toEqual({
|
|
186
|
+
user: { name: "Alice" }
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Should reject missing required nested field
|
|
190
|
+
expect(() => Schema.decodeUnknownSync(schema)({ user: {} })).toThrow();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("toUpdateSchema", () => {
|
|
195
|
+
it("nested struct fields are also optional for updates", () => {
|
|
196
|
+
const primitive = Primitive.Struct({
|
|
197
|
+
user: Primitive.Struct({
|
|
198
|
+
name: Primitive.String().required(),
|
|
199
|
+
age: Primitive.Number(),
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const schema = EffectSchema.toUpdateSchema(primitive);
|
|
204
|
+
|
|
205
|
+
// Should accept empty object
|
|
206
|
+
expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
|
|
207
|
+
|
|
208
|
+
// Should accept partial nested update
|
|
209
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
210
|
+
user: { name: "Alice" }
|
|
211
|
+
})).toEqual({
|
|
212
|
+
user: { name: "Alice" }
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Should accept nested struct with empty object (all fields optional)
|
|
216
|
+
expect(Schema.decodeUnknownSync(schema)({ user: {} })).toEqual({ user: {} });
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// =============================================================================
|
|
222
|
+
// Array Primitive Tests
|
|
223
|
+
// =============================================================================
|
|
224
|
+
|
|
225
|
+
describe("EffectSchema - Array Primitives", () => {
|
|
226
|
+
describe("toSetSchema", () => {
|
|
227
|
+
it("converts simple array to Schema.Array", () => {
|
|
228
|
+
const primitive = Primitive.Array(Primitive.String());
|
|
229
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
230
|
+
|
|
231
|
+
expect(Schema.decodeUnknownSync(schema)(["a", "b", "c"])).toEqual(["a", "b", "c"]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("converts array of structs with proper field handling", () => {
|
|
235
|
+
const primitive = Primitive.Array(
|
|
236
|
+
Primitive.Struct({
|
|
237
|
+
name: Primitive.String().required(),
|
|
238
|
+
age: Primitive.Number(),
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
243
|
+
|
|
244
|
+
// Should accept array with valid elements
|
|
245
|
+
expect(Schema.decodeUnknownSync(schema)([
|
|
246
|
+
{ name: "Alice" },
|
|
247
|
+
{ name: "Bob", age: 30 },
|
|
248
|
+
])).toEqual([
|
|
249
|
+
{ name: "Alice" },
|
|
250
|
+
{ name: "Bob", age: 30 },
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
// Should reject element missing required field
|
|
254
|
+
expect(() => Schema.decodeUnknownSync(schema)([{ age: 30 }])).toThrow();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// =============================================================================
|
|
260
|
+
// Union Primitive Tests
|
|
261
|
+
// =============================================================================
|
|
262
|
+
|
|
263
|
+
describe("EffectSchema - Union Primitives", () => {
|
|
264
|
+
describe("toSetSchema", () => {
|
|
265
|
+
it("creates union schema for variants", () => {
|
|
266
|
+
const primitive = Primitive.Union({
|
|
267
|
+
variants: {
|
|
268
|
+
text: Primitive.Struct({
|
|
269
|
+
type: Primitive.Literal("text"),
|
|
270
|
+
content: Primitive.String().required(),
|
|
271
|
+
}),
|
|
272
|
+
image: Primitive.Struct({
|
|
273
|
+
type: Primitive.Literal("image"),
|
|
274
|
+
url: Primitive.String().required(),
|
|
275
|
+
}),
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
280
|
+
|
|
281
|
+
// Should accept text variant
|
|
282
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
283
|
+
type: "text",
|
|
284
|
+
content: "Hello"
|
|
285
|
+
})).toEqual({
|
|
286
|
+
type: "text",
|
|
287
|
+
content: "Hello"
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Should accept image variant
|
|
291
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
292
|
+
type: "image",
|
|
293
|
+
url: "https://example.com/image.png"
|
|
294
|
+
})).toEqual({
|
|
295
|
+
type: "image",
|
|
296
|
+
url: "https://example.com/image.png"
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("toUpdateSchema", () => {
|
|
302
|
+
it("all variant fields are optional for updates", () => {
|
|
303
|
+
const primitive = Primitive.Union({
|
|
304
|
+
variants: {
|
|
305
|
+
text: Primitive.Struct({
|
|
306
|
+
type: Primitive.Literal("text"),
|
|
307
|
+
content: Primitive.String().required(),
|
|
308
|
+
}),
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const schema = EffectSchema.toUpdateSchema(primitive);
|
|
313
|
+
|
|
314
|
+
// Should accept partial variant
|
|
315
|
+
expect(Schema.decodeUnknownSync(schema)({ type: "text" })).toEqual({ type: "text" });
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// =============================================================================
|
|
321
|
+
// Either Primitive Tests
|
|
322
|
+
// =============================================================================
|
|
323
|
+
|
|
324
|
+
describe("EffectSchema - Either Primitives", () => {
|
|
325
|
+
describe("toSetSchema", () => {
|
|
326
|
+
it("creates union of scalar types", () => {
|
|
327
|
+
const primitive = Primitive.Either(
|
|
328
|
+
Primitive.String(),
|
|
329
|
+
Primitive.Number()
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const schema = EffectSchema.toSetSchema(primitive);
|
|
333
|
+
|
|
334
|
+
// Should accept string
|
|
335
|
+
expect(Schema.decodeUnknownSync(schema)("hello")).toBe("hello");
|
|
336
|
+
|
|
337
|
+
// Should accept number
|
|
338
|
+
expect(Schema.decodeUnknownSync(schema)(42)).toBe(42);
|
|
339
|
+
|
|
340
|
+
// Should reject non-matching types
|
|
341
|
+
expect(() => Schema.decodeUnknownSync(schema)(true)).toThrow();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// =============================================================================
|
|
347
|
+
// TreeNode Primitive Tests
|
|
348
|
+
// =============================================================================
|
|
349
|
+
|
|
350
|
+
describe("EffectSchema - TreeNode Primitives", () => {
|
|
351
|
+
describe("toSetSchema", () => {
|
|
352
|
+
it("delegates to data struct for set schema", () => {
|
|
353
|
+
const CardNode = Primitive.TreeNode("card", {
|
|
354
|
+
data: Primitive.Struct({
|
|
355
|
+
title: Primitive.String().required(),
|
|
356
|
+
description: Primitive.String(),
|
|
357
|
+
}),
|
|
358
|
+
children: [Primitive.TreeNodeSelf],
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const schema = EffectSchema.toSetSchema(CardNode);
|
|
362
|
+
|
|
363
|
+
// Should accept valid data with required field
|
|
364
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
365
|
+
title: "My Card"
|
|
366
|
+
})).toEqual({
|
|
367
|
+
title: "My Card"
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Should accept valid data with all fields
|
|
371
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
372
|
+
title: "My Card",
|
|
373
|
+
description: "Card description"
|
|
374
|
+
})).toEqual({
|
|
375
|
+
title: "My Card",
|
|
376
|
+
description: "Card description"
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Should reject missing required field
|
|
380
|
+
expect(() => Schema.decodeUnknownSync(schema)({})).toThrow();
|
|
381
|
+
expect(() => Schema.decodeUnknownSync(schema)({ description: "no title" })).toThrow();
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe("toUpdateSchema", () => {
|
|
386
|
+
it("all data fields are optional for updates", () => {
|
|
387
|
+
const CardNode = Primitive.TreeNode("card", {
|
|
388
|
+
data: Primitive.Struct({
|
|
389
|
+
title: Primitive.String().required(),
|
|
390
|
+
description: Primitive.String(),
|
|
391
|
+
}),
|
|
392
|
+
children: [Primitive.TreeNodeSelf],
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const schema = EffectSchema.toUpdateSchema(CardNode);
|
|
396
|
+
|
|
397
|
+
// Should accept empty update
|
|
398
|
+
expect(Schema.decodeUnknownSync(schema)({})).toEqual({});
|
|
399
|
+
|
|
400
|
+
// Should accept partial update
|
|
401
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
402
|
+
title: "Updated Title"
|
|
403
|
+
})).toEqual({
|
|
404
|
+
title: "Updated Title"
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Should accept update with only optional field
|
|
408
|
+
expect(Schema.decodeUnknownSync(schema)({
|
|
409
|
+
description: "New description"
|
|
410
|
+
})).toEqual({
|
|
411
|
+
description: "New description"
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// =============================================================================
|
|
418
|
+
// Tree Primitive Tests
|
|
419
|
+
// =============================================================================
|
|
420
|
+
|
|
421
|
+
describe("EffectSchema - Tree Primitives", () => {
|
|
422
|
+
describe("toSetSchema", () => {
|
|
423
|
+
it("returns array of TreeNodeState schema", () => {
|
|
424
|
+
const FolderNode = Primitive.TreeNode("folder", {
|
|
425
|
+
data: Primitive.Struct({
|
|
426
|
+
name: Primitive.String().required(),
|
|
427
|
+
}),
|
|
428
|
+
children: [Primitive.TreeNodeSelf],
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const treePrimitive = Primitive.Tree({
|
|
432
|
+
root: FolderNode,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const schema = EffectSchema.toSetSchema(treePrimitive);
|
|
436
|
+
|
|
437
|
+
// Should accept valid tree state
|
|
438
|
+
expect(Schema.decodeUnknownSync(schema)([
|
|
439
|
+
{
|
|
440
|
+
id: "node-1",
|
|
441
|
+
type: "folder",
|
|
442
|
+
parentId: null,
|
|
443
|
+
pos: "a0",
|
|
444
|
+
data: { name: "Root" },
|
|
445
|
+
},
|
|
446
|
+
])).toEqual([
|
|
447
|
+
{
|
|
448
|
+
id: "node-1",
|
|
449
|
+
type: "folder",
|
|
450
|
+
parentId: null,
|
|
451
|
+
pos: "a0",
|
|
452
|
+
data: { name: "Root" },
|
|
453
|
+
},
|
|
454
|
+
]);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// =============================================================================
|
|
460
|
+
// TreeNodeStateSchema Export Tests
|
|
461
|
+
// =============================================================================
|
|
462
|
+
|
|
463
|
+
describe("EffectSchema - TreeNodeStateSchema", () => {
|
|
464
|
+
it("validates tree node state structure", () => {
|
|
465
|
+
const validNodeState = {
|
|
466
|
+
id: "node-123",
|
|
467
|
+
type: "card",
|
|
468
|
+
parentId: "parent-456",
|
|
469
|
+
pos: "a0",
|
|
470
|
+
data: { title: "Test" },
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
expect(Schema.decodeUnknownSync(EffectSchema.TreeNodeStateSchema)(validNodeState)).toEqual(validNodeState);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("accepts null parentId for root nodes", () => {
|
|
477
|
+
const rootNodeState = {
|
|
478
|
+
id: "root-1",
|
|
479
|
+
type: "folder",
|
|
480
|
+
parentId: null,
|
|
481
|
+
pos: "a0",
|
|
482
|
+
data: { name: "Root" },
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
expect(Schema.decodeUnknownSync(EffectSchema.TreeNodeStateSchema)(rootNodeState)).toEqual(rootNodeState);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it("rejects invalid node state", () => {
|
|
489
|
+
const invalidNodeState = {
|
|
490
|
+
id: 123, // should be string
|
|
491
|
+
type: "card",
|
|
492
|
+
parentId: null,
|
|
493
|
+
pos: "a0",
|
|
494
|
+
data: {},
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
expect(() => Schema.decodeUnknownSync(EffectSchema.TreeNodeStateSchema)(invalidNodeState)).toThrow();
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// =============================================================================
|
|
502
|
+
// Complex Example Tests
|
|
503
|
+
// =============================================================================
|
|
504
|
+
|
|
505
|
+
describe("EffectSchema - Complex Examples", () => {
|
|
506
|
+
it("handles the example schema from the plan", () => {
|
|
507
|
+
const UserSchema = Primitive.Struct({
|
|
508
|
+
name: Primitive.String().required(),
|
|
509
|
+
age: Primitive.Number().default(0),
|
|
510
|
+
email: Primitive.String(),
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
const setSchema = EffectSchema.toSetSchema(UserSchema);
|
|
514
|
+
const updateSchema = EffectSchema.toUpdateSchema(UserSchema);
|
|
515
|
+
|
|
516
|
+
// Set schema: name required, others optional
|
|
517
|
+
expect(Schema.decodeUnknownSync(setSchema)({ name: "Alice" })).toEqual({ name: "Alice" });
|
|
518
|
+
expect(() => Schema.decodeUnknownSync(setSchema)({})).toThrow();
|
|
519
|
+
|
|
520
|
+
// Update schema: all optional
|
|
521
|
+
expect(Schema.decodeUnknownSync(updateSchema)({})).toEqual({});
|
|
522
|
+
expect(Schema.decodeUnknownSync(updateSchema)({ age: 30 })).toEqual({ age: 30 });
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("handles the TreeNode example from the plan", () => {
|
|
526
|
+
const CardNode = Primitive.TreeNode("card", {
|
|
527
|
+
data: Primitive.Struct({
|
|
528
|
+
title: Primitive.String().required(),
|
|
529
|
+
description: Primitive.String(),
|
|
530
|
+
}),
|
|
531
|
+
children: [Primitive.TreeNodeSelf],
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const setSchema = EffectSchema.toSetSchema(CardNode);
|
|
535
|
+
const updateSchema = EffectSchema.toUpdateSchema(CardNode);
|
|
536
|
+
|
|
537
|
+
// Set schema: title required, description optional
|
|
538
|
+
expect(Schema.decodeUnknownSync(setSchema)({ title: "Test" })).toEqual({ title: "Test" });
|
|
539
|
+
expect(() => Schema.decodeUnknownSync(setSchema)({})).toThrow();
|
|
540
|
+
|
|
541
|
+
// Update schema: all optional
|
|
542
|
+
expect(Schema.decodeUnknownSync(updateSchema)({})).toEqual({});
|
|
543
|
+
expect(Schema.decodeUnknownSync(updateSchema)({ description: "Updated" })).toEqual({ description: "Updated" });
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
@@ -411,6 +411,114 @@ describe("ArrayPrimitive", () => {
|
|
|
411
411
|
expect(positions[1]! < positions[2]!).toBe(true);
|
|
412
412
|
});
|
|
413
413
|
});
|
|
414
|
+
describe("struct elements with defaults", () => {
|
|
415
|
+
// Define a struct element with required and optional fields
|
|
416
|
+
const TaskStruct = Primitive.Struct({
|
|
417
|
+
title: Primitive.String().required(), // Must provide
|
|
418
|
+
priority: Primitive.Number().default(0), // Has default, optional
|
|
419
|
+
completed: Primitive.Boolean().default(false), // Has default, optional
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const taskArray = Primitive.Array(TaskStruct);
|
|
423
|
+
|
|
424
|
+
// Helper to create environment for task array
|
|
425
|
+
const createTaskEnv = (
|
|
426
|
+
state: Primitive.ArrayEntry<{ title: string; priority: number; completed: boolean }>[] = []
|
|
427
|
+
): { env: ReturnType<typeof ProxyEnvironment.make>; operations: Operation.Operation<any, any, any>[] } => {
|
|
428
|
+
const operations: Operation.Operation<any, any, any>[] = [];
|
|
429
|
+
let currentState = [...state];
|
|
430
|
+
let idCounter = 0;
|
|
431
|
+
|
|
432
|
+
const env = ProxyEnvironment.make({
|
|
433
|
+
onOperation: (op) => {
|
|
434
|
+
operations.push(op);
|
|
435
|
+
if (op.kind === "array.insert") {
|
|
436
|
+
currentState.push(op.payload);
|
|
437
|
+
} else if (op.kind === "array.set") {
|
|
438
|
+
currentState = op.payload;
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
getState: () => currentState,
|
|
442
|
+
generateId: () => `task-${++idCounter}`,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
return { env, operations };
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
it("push() only requires fields without defaults", () => {
|
|
449
|
+
const { env, operations } = createTaskEnv();
|
|
450
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
451
|
+
|
|
452
|
+
// Only provide required 'title', priority and completed should use defaults
|
|
453
|
+
proxy.push({ title: "New Task" });
|
|
454
|
+
|
|
455
|
+
expect(operations).toHaveLength(1);
|
|
456
|
+
expect(operations[0]!.kind).toBe("array.insert");
|
|
457
|
+
|
|
458
|
+
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
459
|
+
expect(payload.value.title).toBe("New Task");
|
|
460
|
+
expect(payload.value.priority).toBe(0); // Default value
|
|
461
|
+
expect(payload.value.completed).toBe(false); // Default value
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("push() allows overriding defaults", () => {
|
|
465
|
+
const { env, operations } = createTaskEnv();
|
|
466
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
467
|
+
|
|
468
|
+
// Provide title and override priority, let completed use default
|
|
469
|
+
proxy.push({ title: "Important Task", priority: 10 });
|
|
470
|
+
|
|
471
|
+
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
472
|
+
expect(payload.value.title).toBe("Important Task");
|
|
473
|
+
expect(payload.value.priority).toBe(10); // Overridden
|
|
474
|
+
expect(payload.value.completed).toBe(false); // Default value
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("insertAt() applies defaults for omitted fields", () => {
|
|
478
|
+
const existingEntry = {
|
|
479
|
+
id: "existing",
|
|
480
|
+
pos: "a0",
|
|
481
|
+
value: { title: "Existing", priority: 5, completed: true },
|
|
482
|
+
};
|
|
483
|
+
const { env, operations } = createTaskEnv([existingEntry]);
|
|
484
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
485
|
+
|
|
486
|
+
// Insert with only required field
|
|
487
|
+
proxy.insertAt(0, { title: "First Task" });
|
|
488
|
+
|
|
489
|
+
const payload = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } };
|
|
490
|
+
expect(payload.value.title).toBe("First Task");
|
|
491
|
+
expect(payload.value.priority).toBe(0); // Default
|
|
492
|
+
expect(payload.value.completed).toBe(false); // Default
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("set() applies defaults to each element", () => {
|
|
496
|
+
const { env, operations } = createTaskEnv();
|
|
497
|
+
const proxy = taskArray._internal.createProxy(env, OperationPath.make(""));
|
|
498
|
+
|
|
499
|
+
// Set array with items that only have required fields
|
|
500
|
+
proxy.set([
|
|
501
|
+
{ title: "Task 1" },
|
|
502
|
+
{ title: "Task 2", priority: 5 },
|
|
503
|
+
{ title: "Task 3", completed: true },
|
|
504
|
+
]);
|
|
505
|
+
|
|
506
|
+
expect(operations).toHaveLength(1);
|
|
507
|
+
expect(operations[0]!.kind).toBe("array.set");
|
|
508
|
+
|
|
509
|
+
const entries = operations[0]!.payload as { value: { title: string; priority: number; completed: boolean } }[];
|
|
510
|
+
expect(entries).toHaveLength(3);
|
|
511
|
+
|
|
512
|
+
// First item: only title, defaults for others
|
|
513
|
+
expect(entries[0]!.value).toEqual({ title: "Task 1", priority: 0, completed: false });
|
|
514
|
+
|
|
515
|
+
// Second item: title and priority, default for completed
|
|
516
|
+
expect(entries[1]!.value).toEqual({ title: "Task 2", priority: 5, completed: false });
|
|
517
|
+
|
|
518
|
+
// Third item: title and completed, default for priority
|
|
519
|
+
expect(entries[2]!.value).toEqual({ title: "Task 3", priority: 0, completed: true });
|
|
520
|
+
});
|
|
521
|
+
});
|
|
414
522
|
});
|
|
415
523
|
|
|
416
524
|
// =============================================================================
|