@voidhash/mimic 0.0.5 → 0.0.7

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.
@@ -187,12 +187,12 @@ export function isCompatibleOperation(operation: Operation.Operation<any, any, a
187
187
  // =============================================================================
188
188
 
189
189
  /**
190
- * Applies default values to a partial input, recursively handling nested structs.
190
+ * Applies default values to a partial input, recursively handling nested structs and unions.
191
191
  *
192
192
  * Uses a two-layer approach:
193
193
  * 1. First, get the struct's initial state (which includes struct-level defaults)
194
194
  * 2. Then, layer the provided values on top
195
- * 3. Finally, ensure nested structs are recursively processed
195
+ * 3. Finally, ensure nested structs and unions are recursively processed
196
196
  *
197
197
  * @param primitive - The primitive definition containing field information
198
198
  * @param value - The partial value provided by the user
@@ -202,7 +202,7 @@ export function applyDefaults<T extends AnyPrimitive>(
202
202
  primitive: T,
203
203
  value: Partial<InferState<T>>
204
204
  ): InferState<T> {
205
- // Only structs need default merging
205
+ // Handle StructPrimitive
206
206
  if (primitive._tag === "StructPrimitive") {
207
207
  const structPrimitive = primitive as unknown as {
208
208
  fields: Record<string, AnyPrimitive>;
@@ -224,16 +224,65 @@ export function applyDefaults<T extends AnyPrimitive>(
224
224
  if (fieldDefault !== undefined) {
225
225
  result[key] = fieldDefault;
226
226
  }
227
- } else if (fieldPrimitive._tag === "StructPrimitive" && typeof result[key] === "object" && result[key] !== null) {
228
- // Recursively apply defaults to nested structs
229
- result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);
227
+ } else if (typeof result[key] === "object" && result[key] !== null) {
228
+ // Recursively apply defaults to nested structs and unions
229
+ if (fieldPrimitive._tag === "StructPrimitive" || fieldPrimitive._tag === "UnionPrimitive") {
230
+ result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);
231
+ }
230
232
  }
231
233
  }
232
234
 
233
235
  return result as InferState<T>;
234
236
  }
235
237
 
236
- // For non-struct primitives, return the value as-is
238
+ // Handle UnionPrimitive
239
+ if (primitive._tag === "UnionPrimitive") {
240
+ const unionPrimitive = primitive as unknown as {
241
+ _schema: {
242
+ discriminator: string;
243
+ variants: Record<string, AnyPrimitive>;
244
+ };
245
+ };
246
+
247
+ // Validate that value is an object
248
+ if (typeof value !== "object" || value === null) {
249
+ return value as InferState<T>;
250
+ }
251
+
252
+ const discriminator = unionPrimitive._schema.discriminator;
253
+ const discriminatorValue = (value as Record<string, unknown>)[discriminator];
254
+
255
+ // Find the matching variant based on discriminator value
256
+ let matchingVariantKey: string | undefined;
257
+ for (const variantKey in unionPrimitive._schema.variants) {
258
+ const variant = unionPrimitive._schema.variants[variantKey]!;
259
+ // Variants are structs - check if the discriminator field's literal matches
260
+ if (variant._tag === "StructPrimitive") {
261
+ const variantStruct = variant as unknown as {
262
+ fields: Record<string, AnyPrimitive>;
263
+ };
264
+ const discriminatorField = variantStruct.fields[discriminator];
265
+ if (discriminatorField && discriminatorField._tag === "LiteralPrimitive") {
266
+ const literalPrimitive = discriminatorField as unknown as { literal: unknown };
267
+ if (literalPrimitive.literal === discriminatorValue) {
268
+ matchingVariantKey = variantKey;
269
+ break;
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ if (!matchingVariantKey) {
276
+ // No matching variant found - return value as-is
277
+ return value as InferState<T>;
278
+ }
279
+
280
+ // Apply defaults using the matching variant's struct
281
+ const variantPrimitive = unionPrimitive._schema.variants[matchingVariantKey]!;
282
+ return applyDefaults(variantPrimitive, value as Partial<InferState<typeof variantPrimitive>>) as InferState<T>;
283
+ }
284
+
285
+ // For other primitives, return the value as-is
237
286
  return value as InferState<T>;
238
287
  }
239
288
 
@@ -294,15 +294,20 @@ describe("TreePrimitive", () => {
294
294
  const { env } = createEnvWithState(initialState);
295
295
  const proxy = fileSystemTree._internal.createProxy(env, OperationPath.make(""));
296
296
 
297
+
297
298
  const snapshot = proxy.toSnapshot();
298
299
  expect(snapshot).toBeDefined();
299
300
  expect(snapshot!.id).toBe("root");
300
301
  expect(snapshot!.type).toBe("folder");
302
+ expect(snapshot!.parentId).toBe(null);
303
+ expect(snapshot!.pos).toBe("a0");
301
304
  expect(snapshot!.name).toBe("Root"); // Data spread at node level
302
305
  expect(snapshot!.children).toHaveLength(2);
303
306
 
304
307
  const file1Snapshot = snapshot!.children[0]!;
305
308
  expect(file1Snapshot.id).toBe("file1");
309
+ expect(file1Snapshot.parentId).toBe("root");
310
+ expect(file1Snapshot.pos).toBe("a0");
306
311
  expect(file1Snapshot.name).toBe("File1");
307
312
  expect(file1Snapshot.children).toEqual([]);
308
313
 
@@ -155,6 +155,95 @@ describe("UnionPrimitive", () => {
155
155
  content: "default",
156
156
  });
157
157
  });
158
+
159
+ it("return correct defaults for nested structs when global default is set", () => {
160
+ const stringVariableTypeSchema = Primitive.Struct({
161
+ key: Primitive.Literal("string"),
162
+ value: Primitive.String(),
163
+ });
164
+
165
+ const numberVariableTypeSchema = Primitive.Struct({
166
+ key: Primitive.Literal("number"),
167
+ value: Primitive.Number(),
168
+ });
169
+
170
+
171
+ const variableTypeSchema = Primitive.Union({
172
+ discriminator: "key",
173
+ variants: {
174
+ string: stringVariableTypeSchema,
175
+ number: numberVariableTypeSchema,
176
+ },
177
+ }).default({
178
+ key: "string",
179
+ value: "",
180
+ });
181
+
182
+ expect(variableTypeSchema._internal.getInitialState()).toEqual({
183
+ key: "string",
184
+ value: "",
185
+ });
186
+ });
187
+
188
+ it("return correct defaults for nested structs when variant default is set", () => {
189
+ const stringVariableTypeSchema = Primitive.Struct({
190
+ key: Primitive.Literal("string"),
191
+ value: Primitive.String().default(""),
192
+ });
193
+
194
+ const numberVariableTypeSchema = Primitive.Struct({
195
+ key: Primitive.Literal("number"),
196
+ value: Primitive.Number().default(10),
197
+ });
198
+
199
+
200
+ const variableTypeSchema = Primitive.Union({
201
+ discriminator: "key",
202
+ variants: {
203
+ string: stringVariableTypeSchema,
204
+ number: numberVariableTypeSchema,
205
+ },
206
+ }).default({
207
+ key: "number",
208
+ });
209
+
210
+ expect(variableTypeSchema._internal.getInitialState()).toEqual({
211
+ key: "number",
212
+ value: 10,
213
+ });
214
+ });
215
+
216
+ it("set() applies defaults to generated operation payload", () => {
217
+ const stringVariableTypeSchema = Primitive.Struct({
218
+ key: Primitive.Literal("string"),
219
+ value: Primitive.String().default(""),
220
+ });
221
+
222
+ const numberVariableTypeSchema = Primitive.Struct({
223
+ key: Primitive.Literal("number"),
224
+ value: Primitive.Number().default(10),
225
+ });
226
+
227
+
228
+ const variableTypeSchema = Primitive.Union({
229
+ discriminator: "key",
230
+ variants: {
231
+ string: stringVariableTypeSchema,
232
+ number: numberVariableTypeSchema,
233
+ },
234
+ });
235
+
236
+ const operations: Operation.Operation<any, any, any>[] = [];
237
+ const proxy = variableTypeSchema._internal.createProxy(ProxyEnvironment.make((op) => operations.push(op)), OperationPath.make(""));
238
+ proxy.set({ key: "number" });
239
+
240
+ expect(operations).toHaveLength(1);
241
+ expect(operations[0]!.kind).toBe("union.set");
242
+ expect(operations[0]!.payload).toEqual({
243
+ key: "number",
244
+ value: 10,
245
+ });
246
+ });
158
247
  });
159
248
 
160
249
  describe("custom discriminator", () => {
@@ -208,3 +297,258 @@ describe("UnionPrimitive", () => {
208
297
  // =============================================================================
209
298
  // Integration Tests - Complex Nested Structures
210
299
  // =============================================================================
300
+
301
+ describe("Union defaults in nested structures", () => {
302
+ // Schema matching the production use case
303
+ const stringVariableTypeSchema = Primitive.Struct({
304
+ key: Primitive.Literal("string"),
305
+ value: Primitive.String().default(""),
306
+ });
307
+
308
+ const numberVariableTypeSchema = Primitive.Struct({
309
+ key: Primitive.Literal("number"),
310
+ value: Primitive.Number().default(0),
311
+ });
312
+
313
+ const booleanVariableTypeSchema = Primitive.Struct({
314
+ key: Primitive.Literal("boolean"),
315
+ value: Primitive.Boolean().default(false),
316
+ });
317
+
318
+ const productVariableTypeSchema = Primitive.Struct({
319
+ key: Primitive.Literal("product"),
320
+ value: Primitive.Struct({
321
+ productId: Primitive.Either(
322
+ Primitive.String(),
323
+ Primitive.Literal(null),
324
+ ),
325
+ }).default({
326
+ productId: null,
327
+ }),
328
+ });
329
+
330
+ const variableTypeSchema = Primitive.Union({
331
+ discriminator: "key",
332
+ variants: {
333
+ string: stringVariableTypeSchema,
334
+ number: numberVariableTypeSchema,
335
+ boolean: booleanVariableTypeSchema,
336
+ product: productVariableTypeSchema,
337
+ },
338
+ });
339
+
340
+ const variableSchema = Primitive.Struct({
341
+ id: Primitive.String(),
342
+ name: Primitive.String(),
343
+ value: variableTypeSchema,
344
+ });
345
+
346
+ describe("Struct.set() with nested Union", () => {
347
+ it("applies Union variant defaults when setting partial Union value", () => {
348
+ const operations: Operation.Operation<any, any, any>[] = [];
349
+ const env = ProxyEnvironment.make((op) => operations.push(op));
350
+ const proxy = variableSchema._internal.createProxy(env, OperationPath.make(""));
351
+
352
+ proxy.set({
353
+ id: "var-1",
354
+ name: "test",
355
+ value: { key: "number" },
356
+ });
357
+
358
+ expect(operations).toHaveLength(1);
359
+ expect(operations[0]!.kind).toBe("struct.set");
360
+ expect(operations[0]!.payload).toEqual({
361
+ id: "var-1",
362
+ name: "test",
363
+ value: {
364
+ key: "number",
365
+ value: 0,
366
+ },
367
+ });
368
+ });
369
+
370
+ it("applies Union variant defaults for string type", () => {
371
+ const operations: Operation.Operation<any, any, any>[] = [];
372
+ const env = ProxyEnvironment.make((op) => operations.push(op));
373
+ const proxy = variableSchema._internal.createProxy(env, OperationPath.make(""));
374
+
375
+ proxy.set({
376
+ id: "var-1",
377
+ name: "test",
378
+ value: { key: "string" },
379
+ });
380
+
381
+ expect(operations[0]!.payload).toEqual({
382
+ id: "var-1",
383
+ name: "test",
384
+ value: {
385
+ key: "string",
386
+ value: "",
387
+ },
388
+ });
389
+ });
390
+
391
+ it("applies Union variant defaults for boolean type", () => {
392
+ const operations: Operation.Operation<any, any, any>[] = [];
393
+ const env = ProxyEnvironment.make((op) => operations.push(op));
394
+ const proxy = variableSchema._internal.createProxy(env, OperationPath.make(""));
395
+
396
+ proxy.set({
397
+ id: "var-1",
398
+ name: "test",
399
+ value: { key: "boolean" },
400
+ });
401
+
402
+ expect(operations[0]!.payload).toEqual({
403
+ id: "var-1",
404
+ name: "test",
405
+ value: {
406
+ key: "boolean",
407
+ value: false,
408
+ },
409
+ });
410
+ });
411
+
412
+ it("applies Union variant defaults for nested struct (product type)", () => {
413
+ const operations: Operation.Operation<any, any, any>[] = [];
414
+ const env = ProxyEnvironment.make((op) => operations.push(op));
415
+ const proxy = variableSchema._internal.createProxy(env, OperationPath.make(""));
416
+
417
+ proxy.set({
418
+ id: "var-1",
419
+ name: "test",
420
+ value: { key: "product" },
421
+ });
422
+
423
+ expect(operations[0]!.payload).toEqual({
424
+ id: "var-1",
425
+ name: "test",
426
+ value: {
427
+ key: "product",
428
+ value: {
429
+ productId: null,
430
+ },
431
+ },
432
+ });
433
+ });
434
+
435
+ it("preserves explicitly provided Union values", () => {
436
+ const operations: Operation.Operation<any, any, any>[] = [];
437
+ const env = ProxyEnvironment.make((op) => operations.push(op));
438
+ const proxy = variableSchema._internal.createProxy(env, OperationPath.make(""));
439
+
440
+ proxy.set({
441
+ id: "var-1",
442
+ name: "test",
443
+ value: { key: "number", value: 42 },
444
+ });
445
+
446
+ expect(operations[0]!.payload).toEqual({
447
+ id: "var-1",
448
+ name: "test",
449
+ value: {
450
+ key: "number",
451
+ value: 42,
452
+ },
453
+ });
454
+ });
455
+ });
456
+
457
+ describe("Array.push() with Struct containing Union", () => {
458
+ const variablesArraySchema = Primitive.Array(variableSchema);
459
+
460
+ it("applies Union variant defaults when pushing with partial Union value", () => {
461
+ const operations: Operation.Operation<any, any, any>[] = [];
462
+ const env = ProxyEnvironment.make((op) => operations.push(op));
463
+ const proxy = variablesArraySchema._internal.createProxy(env, OperationPath.make(""));
464
+
465
+ proxy.push({
466
+ id: "var-1",
467
+ name: "test",
468
+ value: { key: "number" },
469
+ });
470
+
471
+ expect(operations).toHaveLength(1);
472
+ expect(operations[0]!.kind).toBe("array.insert");
473
+ expect(operations[0]!.payload.value).toEqual({
474
+ id: "var-1",
475
+ name: "test",
476
+ value: {
477
+ key: "number",
478
+ value: 0,
479
+ },
480
+ });
481
+ });
482
+
483
+ it("applies Union variant defaults for all variant types", () => {
484
+ const operations: Operation.Operation<any, any, any>[] = [];
485
+ const env = ProxyEnvironment.make((op) => operations.push(op));
486
+ const proxy = variablesArraySchema._internal.createProxy(env, OperationPath.make(""));
487
+
488
+ proxy.push({ id: "var-1", name: "str", value: { key: "string" } });
489
+ proxy.push({ id: "var-2", name: "num", value: { key: "number" } });
490
+ proxy.push({ id: "var-3", name: "bool", value: { key: "boolean" } });
491
+ proxy.push({ id: "var-4", name: "prod", value: { key: "product" } });
492
+
493
+ expect(operations[0]!.payload.value.value).toEqual({ key: "string", value: "" });
494
+ expect(operations[1]!.payload.value.value).toEqual({ key: "number", value: 0 });
495
+ expect(operations[2]!.payload.value.value).toEqual({ key: "boolean", value: false });
496
+ expect(operations[3]!.payload.value.value).toEqual({ key: "product", value: { productId: null } });
497
+ });
498
+ });
499
+
500
+ describe("Array.set() with Struct containing Union", () => {
501
+ const variablesArraySchema = Primitive.Array(variableSchema);
502
+
503
+ it("applies Union variant defaults when setting array with partial Union values", () => {
504
+ const operations: Operation.Operation<any, any, any>[] = [];
505
+ const env = ProxyEnvironment.make((op) => operations.push(op));
506
+ const proxy = variablesArraySchema._internal.createProxy(env, OperationPath.make(""));
507
+
508
+ proxy.set([
509
+ { id: "var-1", name: "str", value: { key: "string" } },
510
+ { id: "var-2", name: "num", value: { key: "number" } },
511
+ ]);
512
+
513
+ expect(operations).toHaveLength(1);
514
+ expect(operations[0]!.kind).toBe("array.set");
515
+ expect(operations[0]!.payload[0].value).toEqual({
516
+ id: "var-1",
517
+ name: "str",
518
+ value: { key: "string", value: "" },
519
+ });
520
+ expect(operations[0]!.payload[1].value).toEqual({
521
+ id: "var-2",
522
+ name: "num",
523
+ value: { key: "number", value: 0 },
524
+ });
525
+ });
526
+ });
527
+
528
+ describe("Array.insertAt() with Struct containing Union", () => {
529
+ const variablesArraySchema = Primitive.Array(variableSchema);
530
+
531
+ it("applies Union variant defaults when inserting with partial Union value", () => {
532
+ const operations: Operation.Operation<any, any, any>[] = [];
533
+ const env = ProxyEnvironment.make((op) => operations.push(op));
534
+ const proxy = variablesArraySchema._internal.createProxy(env, OperationPath.make(""));
535
+
536
+ proxy.insertAt(0, {
537
+ id: "var-1",
538
+ name: "test",
539
+ value: { key: "boolean" },
540
+ });
541
+
542
+ expect(operations).toHaveLength(1);
543
+ expect(operations[0]!.kind).toBe("array.insert");
544
+ expect(operations[0]!.payload.value).toEqual({
545
+ id: "var-1",
546
+ name: "test",
547
+ value: {
548
+ key: "boolean",
549
+ value: false,
550
+ },
551
+ });
552
+ });
553
+ });
554
+ });