@voidhash/mimic 0.0.6 → 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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @voidhash/mimic@0.0.6 build /home/runner/work/mimic/mimic/packages/mimic
2
+ > @voidhash/mimic@0.0.7 build /home/runner/work/mimic/mimic/packages/mimic
3
3
  > tsdown
4
4
 
5
5
  ℹ tsdown v0.18.2 powered by rolldown v1.0.0-beta.55
@@ -30,9 +30,9 @@
30
30
  ℹ [CJS] dist/primitives/Boolean.cjs  4.02 kB │ gzip: 1.06 kB
31
31
  ℹ [CJS] dist/primitives/Literal.cjs  3.89 kB │ gzip: 1.06 kB
32
32
  ℹ [CJS] dist/client/StateMonitor.cjs  3.71 kB │ gzip: 1.27 kB
33
+ ℹ [CJS] dist/primitives/shared.cjs  3.69 kB │ gzip: 1.25 kB
33
34
  ℹ [CJS] dist/server/errors.cjs  3.29 kB │ gzip: 0.70 kB
34
35
  ℹ [CJS] dist/server/ServerDocument.cjs  3.16 kB │ gzip: 1.04 kB
35
- ℹ [CJS] dist/primitives/shared.cjs  2.71 kB │ gzip: 1.02 kB
36
36
  ℹ [CJS] dist/primitives/TreeNode.cjs  2.67 kB │ gzip: 0.92 kB
37
37
  ℹ [CJS] dist/Presence.cjs  2.36 kB │ gzip: 0.87 kB
38
38
  ℹ [CJS] dist/Primitive.cjs  2.34 kB │ gzip: 0.59 kB
@@ -52,7 +52,7 @@
52
52
  ℹ [CJS] dist/OperationDefinition.cjs  0.22 kB │ gzip: 0.16 kB
53
53
  ℹ [CJS] dist/client/Transport.cjs  0.21 kB │ gzip: 0.16 kB
54
54
  ℹ [CJS] dist/Transform.cjs  0.20 kB │ gzip: 0.16 kB
55
- ℹ [CJS] 44 files, total: 211.64 kB
55
+ ℹ [CJS] 44 files, total: 212.62 kB
56
56
  ℹ [CJS] dist/primitives/Tree.d.cts.map  6.90 kB │ gzip: 2.60 kB
57
57
  ℹ [CJS] dist/primitives/Struct.d.cts.map  3.85 kB │ gzip: 1.62 kB
58
58
  ℹ [CJS] dist/utils/tree-helpers.d.cts.map  3.61 kB │ gzip: 1.31 kB
@@ -91,7 +91,7 @@
91
91
  ℹ [CJS] dist/primitives/Tree.d.cts 13.50 kB │ gzip: 3.50 kB
92
92
  ℹ [CJS] dist/utils/tree-helpers.d.cts 12.18 kB │ gzip: 2.52 kB
93
93
  ℹ [CJS] dist/client/Transport.d.cts  7.30 kB │ gzip: 1.84 kB
94
- ℹ [CJS] dist/primitives/shared.d.cts  6.87 kB │ gzip: 2.09 kB
94
+ ℹ [CJS] dist/primitives/shared.d.cts  6.89 kB │ gzip: 2.09 kB
95
95
  ℹ [CJS] dist/client/ClientDocument.d.cts  6.07 kB │ gzip: 1.68 kB
96
96
  ℹ [CJS] dist/primitives/Struct.d.cts  5.96 kB │ gzip: 1.59 kB
97
97
  ℹ [CJS] dist/primitives/Either.d.cts  5.44 kB │ gzip: 1.54 kB
@@ -120,8 +120,8 @@
120
120
  ℹ [CJS] dist/client/WebSocketTransport.d.cts  1.22 kB │ gzip: 0.55 kB
121
121
  ℹ [CJS] dist/Transform.d.cts  0.48 kB │ gzip: 0.26 kB
122
122
  ℹ [CJS] dist/OperationDefinition.d.cts  0.36 kB │ gzip: 0.21 kB
123
- ℹ [CJS] 67 files, total: 179.23 kB
124
- ✔ Build complete in 6451ms
123
+ ℹ [CJS] 67 files, total: 179.25 kB
124
+ ✔ Build complete in 6277ms
125
125
  ℹ [ESM] dist/index.mjs  1.01 kB │ gzip: 0.28 kB
126
126
  ℹ [ESM] dist/client/index.mjs  0.99 kB │ gzip: 0.28 kB
127
127
  ℹ [ESM] dist/server/index.mjs  0.41 kB │ gzip: 0.17 kB
@@ -140,10 +140,10 @@
140
140
  ℹ [ESM] dist/FractionalIndex.mjs 16.49 kB │ gzip: 3.36 kB
141
141
  ℹ [ESM] dist/client/Rebase.mjs.map 14.15 kB │ gzip: 3.21 kB
142
142
  ℹ [ESM] dist/utils/tree-helpers.mjs 13.31 kB │ gzip: 3.15 kB
143
+ ℹ [ESM] dist/primitives/shared.mjs.map 13.14 kB │ gzip: 3.69 kB
143
144
  ℹ [ESM] dist/primitives/Array.mjs 11.07 kB │ gzip: 2.64 kB
144
145
  ℹ [ESM] dist/Document.mjs.map 10.89 kB │ gzip: 3.31 kB
145
146
  ℹ [ESM] dist/client/WebSocketTransport.mjs 10.81 kB │ gzip: 2.68 kB
146
- ℹ [ESM] dist/primitives/shared.mjs.map 10.65 kB │ gzip: 3.09 kB
147
147
  ℹ [ESM] dist/server/ServerDocument.mjs.map 10.44 kB │ gzip: 2.95 kB
148
148
  ℹ [ESM] dist/client/StateMonitor.mjs.map 10.39 kB │ gzip: 3.04 kB
149
149
  ℹ [ESM] dist/primitives/String.mjs.map  9.70 kB │ gzip: 2.70 kB
@@ -172,6 +172,7 @@
172
172
  ℹ [ESM] dist/types/index.mjs.map  3.74 kB │ gzip: 0.79 kB
173
173
  ℹ [ESM] dist/utils/tree-helpers.d.mts.map  3.61 kB │ gzip: 1.31 kB
174
174
  ℹ [ESM] dist/client/StateMonitor.mjs  3.60 kB │ gzip: 1.23 kB
175
+ ℹ [ESM] dist/primitives/shared.mjs  3.55 kB │ gzip: 1.25 kB
175
176
  ℹ [ESM] dist/primitives/Boolean.mjs  3.40 kB │ gzip: 1.04 kB
176
177
  ℹ [ESM] dist/primitives/Literal.mjs  3.31 kB │ gzip: 1.04 kB
177
178
  ℹ [ESM] dist/primitives/Array.d.mts.map  3.26 kB │ gzip: 1.35 kB
@@ -181,7 +182,6 @@
181
182
  ℹ [ESM] dist/server/errors.mjs  2.85 kB │ gzip: 0.70 kB
182
183
  ℹ [ESM] dist/Operation.mjs.map  2.75 kB │ gzip: 0.86 kB
183
184
  ℹ [ESM] dist/Transaction.mjs.map  2.65 kB │ gzip: 0.99 kB
184
- ℹ [ESM] dist/primitives/shared.mjs  2.57 kB │ gzip: 1.03 kB
185
185
  ℹ [ESM] dist/primitives/TreeNode.d.mts.map  2.45 kB │ gzip: 1.07 kB
186
186
  ℹ [ESM] dist/ProxyEnvironment.mjs.map  2.42 kB │ gzip: 0.93 kB
187
187
  ℹ [ESM] dist/primitives/TreeNode.mjs  2.42 kB │ gzip: 0.93 kB
@@ -237,7 +237,7 @@
237
237
  ℹ [ESM] dist/primitives/Tree.d.mts 13.50 kB │ gzip: 3.50 kB
238
238
  ℹ [ESM] dist/utils/tree-helpers.d.mts 12.18 kB │ gzip: 2.52 kB
239
239
  ℹ [ESM] dist/client/Transport.d.mts  7.30 kB │ gzip: 1.84 kB
240
- ℹ [ESM] dist/primitives/shared.d.mts  6.87 kB │ gzip: 2.09 kB
240
+ ℹ [ESM] dist/primitives/shared.d.mts  6.89 kB │ gzip: 2.09 kB
241
241
  ℹ [ESM] dist/client/ClientDocument.d.mts  6.09 kB │ gzip: 1.69 kB
242
242
  ℹ [ESM] dist/primitives/Struct.d.mts  5.99 kB │ gzip: 1.60 kB
243
243
  ℹ [ESM] dist/primitives/Either.d.mts  5.44 kB │ gzip: 1.54 kB
@@ -266,5 +266,5 @@
266
266
  ℹ [ESM] dist/client/WebSocketTransport.d.mts  1.22 kB │ gzip: 0.55 kB
267
267
  ℹ [ESM] dist/Transform.d.mts  0.48 kB │ gzip: 0.26 kB
268
268
  ℹ [ESM] dist/OperationDefinition.d.mts  0.36 kB │ gzip: 0.22 kB
269
- ℹ [ESM] 144 files, total: 836.40 kB
270
- ✔ Build complete in 6485ms
269
+ ℹ [ESM] 144 files, total: 839.90 kB
270
+ ✔ Build complete in 6315ms
@@ -1 +1 @@
1
- {"version":3,"file":"Presence.d.mts","names":[],"sources":["../src/Presence.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UAeiB;;;mBAGE,MAAA,CAAO,OAAO;;EAHhB,SAAA,KAAQ,EAKP,KALO;;;;;AAWR,UAAA,eAAe,CAAA,KAEC,CAAA,CAAA;EAMrB;EAKA,SAAA,MAAW,EAXJ,MAAA,CAAO,MAWA,CAXO,KAWC,CAAA;AASlC;AA8BA;;;AAAwE,KA5C5D,KA4C4D,CAAA,UA5C5C,QA4C4C,CAAA,GAAA,CAAA,CAAA,GA5C3B,CA4C2B,CAAA,OAAA,CAAA;;;AAmBxE;AACqB,KA3DT,WAAA,GAAc,QA2DL,CAAA,GAAA,CAAA;;;;AAcR,UAhEI,aAyEhB,CAAA,QAAA,OAAA,CAAA,CAAA;EARoB;EAAT,SAAA,IAAA,EA/DK,KA+DL;EAET;EAAK,SAAA,MAAA,CAAA,EAAA,MAAA;AAeR;;;;;;;;;;;;;;;;;;;;cApDa,uBAAyB,gBAAgB,WAAS,SAAS;;;;;;;;;;cAmB3D,4BACD,SAAS,0BAElB;;;;;;;;;cAYU,gCACD,SAAS,0BAElB;;;;;;;;cAeU,2BACD,SAAS,kCAEV"}
1
+ {"version":3,"file":"Presence.d.mts","names":[],"sources":["../src/Presence.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UAeiB;;;mBAGE,MAAA,CAAO,OAAO;;EAHhB,SAAA,KAAQ,EAKP,KALO;;;;;AAWR,UAAA,eAAe,CAAA,KAAA,CAEC,CAAA;EAMrB;EAKA,SAAA,MAAW,EAXJ,MAAA,CAAO,MAWA,CAXO,KAWC,CAAA;AASlC;AA8BA;;;AAAwE,KA5C5D,KA4C4D,CAAA,UA5C5C,QA4C4C,CAAA,GAAA,CAAA,CAAA,GA5C3B,CA4C2B,CAAA,OAAA,CAAA;;;AAmBxE;AACqB,KA3DT,WAAA,GAAc,QA2DL,CAAA,GAAA,CAAA;;;;AAcR,UAhEI,aAyEhB,CAAA,QAAA,OAAA,CAAA,CAAA;EARoB;EAAT,SAAA,IAAA,EA/DK,KA+DL;EAET;EAAK,SAAA,MAAA,CAAA,EAAA,MAAA;AAeR;;;;;;;;;;;;;;;;;;;;cApDa,uBAAyB,gBAAgB,WAAS,SAAS;;;;;;;;;;cAmB3D,4BACD,SAAS,0BAElB;;;;;;;;;cAYU,gCACD,SAAS,0BAElB;;;;;;;;cAeU,2BACD,SAAS,kCAEV"}
@@ -25,12 +25,12 @@ function isCompatibleOperation(operation, operationDefinitions) {
25
25
  return Object.values(operationDefinitions).some((value) => value.kind === operation.kind);
26
26
  }
27
27
  /**
28
- * Applies default values to a partial input, recursively handling nested structs.
28
+ * Applies default values to a partial input, recursively handling nested structs and unions.
29
29
  *
30
30
  * Uses a two-layer approach:
31
31
  * 1. First, get the struct's initial state (which includes struct-level defaults)
32
32
  * 2. Then, layer the provided values on top
33
- * 3. Finally, ensure nested structs are recursively processed
33
+ * 3. Finally, ensure nested structs and unions are recursively processed
34
34
  *
35
35
  * @param primitive - The primitive definition containing field information
36
36
  * @param value - The partial value provided by the user
@@ -46,10 +46,34 @@ function applyDefaults(primitive, value) {
46
46
  if (result[key] === void 0) {
47
47
  const fieldDefault = fieldPrimitive._internal.getInitialState();
48
48
  if (fieldDefault !== void 0) result[key] = fieldDefault;
49
- } else if (fieldPrimitive._tag === "StructPrimitive" && typeof result[key] === "object" && result[key] !== null) result[key] = applyDefaults(fieldPrimitive, result[key]);
49
+ } else if (typeof result[key] === "object" && result[key] !== null) {
50
+ if (fieldPrimitive._tag === "StructPrimitive" || fieldPrimitive._tag === "UnionPrimitive") result[key] = applyDefaults(fieldPrimitive, result[key]);
51
+ }
50
52
  }
51
53
  return result;
52
54
  }
55
+ if (primitive._tag === "UnionPrimitive") {
56
+ const unionPrimitive = primitive;
57
+ if (typeof value !== "object" || value === null) return value;
58
+ const discriminator = unionPrimitive._schema.discriminator;
59
+ const discriminatorValue = value[discriminator];
60
+ let matchingVariantKey;
61
+ for (const variantKey in unionPrimitive._schema.variants) {
62
+ const variant = unionPrimitive._schema.variants[variantKey];
63
+ if (variant._tag === "StructPrimitive") {
64
+ const discriminatorField = variant.fields[discriminator];
65
+ if (discriminatorField && discriminatorField._tag === "LiteralPrimitive") {
66
+ if (discriminatorField.literal === discriminatorValue) {
67
+ matchingVariantKey = variantKey;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ if (!matchingVariantKey) return value;
74
+ const variantPrimitive = unionPrimitive._schema.variants[matchingVariantKey];
75
+ return applyDefaults(variantPrimitive, value);
76
+ }
53
77
  return value;
54
78
  }
55
79
 
@@ -139,12 +139,12 @@ declare function runValidators<T>(value: T, validators: readonly {
139
139
  */
140
140
  declare function isCompatibleOperation(operation: Operation<any, any, any>, operationDefinitions: Record<string, OperationDefinition<any, any, any>>): boolean;
141
141
  /**
142
- * Applies default values to a partial input, recursively handling nested structs.
142
+ * Applies default values to a partial input, recursively handling nested structs and unions.
143
143
  *
144
144
  * Uses a two-layer approach:
145
145
  * 1. First, get the struct's initial state (which includes struct-level defaults)
146
146
  * 2. Then, layer the provided values on top
147
- * 3. Finally, ensure nested structs are recursively processed
147
+ * 3. Finally, ensure nested structs and unions are recursively processed
148
148
  *
149
149
  * @param primitive - The primitive definition containing field information
150
150
  * @param value - The partial value provided by the user
@@ -139,12 +139,12 @@ declare function runValidators<T>(value: T, validators: readonly {
139
139
  */
140
140
  declare function isCompatibleOperation(operation: Operation<any, any, any>, operationDefinitions: Record<string, OperationDefinition<any, any, any>>): boolean;
141
141
  /**
142
- * Applies default values to a partial input, recursively handling nested structs.
142
+ * Applies default values to a partial input, recursively handling nested structs and unions.
143
143
  *
144
144
  * Uses a two-layer approach:
145
145
  * 1. First, get the struct's initial state (which includes struct-level defaults)
146
146
  * 2. Then, layer the provided values on top
147
- * 3. Finally, ensure nested structs are recursively processed
147
+ * 3. Finally, ensure nested structs and unions are recursively processed
148
148
  *
149
149
  * @param primitive - The primitive definition containing field information
150
150
  * @param value - The partial value provided by the user
@@ -25,12 +25,12 @@ function isCompatibleOperation(operation, operationDefinitions) {
25
25
  return Object.values(operationDefinitions).some((value) => value.kind === operation.kind);
26
26
  }
27
27
  /**
28
- * Applies default values to a partial input, recursively handling nested structs.
28
+ * Applies default values to a partial input, recursively handling nested structs and unions.
29
29
  *
30
30
  * Uses a two-layer approach:
31
31
  * 1. First, get the struct's initial state (which includes struct-level defaults)
32
32
  * 2. Then, layer the provided values on top
33
- * 3. Finally, ensure nested structs are recursively processed
33
+ * 3. Finally, ensure nested structs and unions are recursively processed
34
34
  *
35
35
  * @param primitive - The primitive definition containing field information
36
36
  * @param value - The partial value provided by the user
@@ -46,10 +46,34 @@ function applyDefaults(primitive, value) {
46
46
  if (result[key] === void 0) {
47
47
  const fieldDefault = fieldPrimitive._internal.getInitialState();
48
48
  if (fieldDefault !== void 0) result[key] = fieldDefault;
49
- } else if (fieldPrimitive._tag === "StructPrimitive" && typeof result[key] === "object" && result[key] !== null) result[key] = applyDefaults(fieldPrimitive, result[key]);
49
+ } else if (typeof result[key] === "object" && result[key] !== null) {
50
+ if (fieldPrimitive._tag === "StructPrimitive" || fieldPrimitive._tag === "UnionPrimitive") result[key] = applyDefaults(fieldPrimitive, result[key]);
51
+ }
50
52
  }
51
53
  return result;
52
54
  }
55
+ if (primitive._tag === "UnionPrimitive") {
56
+ const unionPrimitive = primitive;
57
+ if (typeof value !== "object" || value === null) return value;
58
+ const discriminator = unionPrimitive._schema.discriminator;
59
+ const discriminatorValue = value[discriminator];
60
+ let matchingVariantKey;
61
+ for (const variantKey in unionPrimitive._schema.variants) {
62
+ const variant = unionPrimitive._schema.variants[variantKey];
63
+ if (variant._tag === "StructPrimitive") {
64
+ const discriminatorField = variant.fields[discriminator];
65
+ if (discriminatorField && discriminatorField._tag === "LiteralPrimitive") {
66
+ if (discriminatorField.literal === discriminatorValue) {
67
+ matchingVariantKey = variantKey;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ if (!matchingVariantKey) return value;
74
+ const variantPrimitive = unionPrimitive._schema.variants[matchingVariantKey];
75
+ return applyDefaults(variantPrimitive, value);
76
+ }
53
77
  return value;
54
78
  }
55
79
 
@@ -1 +1 @@
1
- {"version":3,"file":"shared.mjs","names":["result: Record<string, unknown>"],"sources":["../../src/primitives/shared.ts"],"sourcesContent":["import * as Operation from \"../Operation\";\nimport * as OperationDefinition from \"../OperationDefinition\";\nimport * as ProxyEnvironment from \"../ProxyEnvironment\";\nimport * as OperationPath from \"../OperationPath\";\nimport * as Transform from \"../Transform\";\n\n// =============================================================================\n// Primitive Interface & Type Utilities\n// =============================================================================\n\n/**\n * Base interface that all primitives must implement.\n * Provides type inference helpers and internal operations.\n * \n * @typeParam TState - The state type this primitive holds\n * @typeParam TProxy - The proxy type for interacting with this primitive\n * @typeParam TDefined - Whether the value is guaranteed to be defined (via required() or default())\n * @typeParam THasDefault - Whether this primitive has a default value\n */\nexport interface Primitive<TState, TProxy, TRequired extends boolean = false, THasDefault extends boolean = false, TSetInput = unknown, TUpdateInput = unknown> {\n readonly _tag: string;\n readonly _State: TState;\n readonly _Proxy: TProxy;\n readonly _TRequired: TRequired;\n readonly _THasDefault: THasDefault;\n readonly _internal: PrimitiveInternal<TState, TProxy>;\n readonly TSetInput: TSetInput;\n readonly TUpdateInput: TUpdateInput;\n }\n \n /**\n * Internal operations that each primitive must provide.\n */\n export interface PrimitiveInternal<TState, TProxy> {\n /** Creates a proxy for generating operations */\n readonly createProxy: (env: ProxyEnvironment.ProxyEnvironment, path: OperationPath.OperationPath) => TProxy;\n /** Applies an operation to the current state, returning the new state */\n readonly applyOperation: (state: TState | undefined, operation: Operation.Operation<any, any, any>) => TState;\n /** Returns the initial/default state for this primitive */\n readonly getInitialState: () => TState | undefined;\n /**\n * Converts a set input value to state format.\n * For most primitives, this is a simple pass-through with defaults applied.\n * For Tree primitives, this converts nested input to flat TreeState.\n *\n * @param input - The set input value\n * @returns The corresponding state value\n */\n readonly convertSetInputToState?: (input: unknown) => TState;\n /**\n * Transforms a client operation against a server operation.\n * Used for operational transformation (OT) conflict resolution.\n *\n * @param clientOp - The client's operation to transform\n * @param serverOp - The server's operation that has already been applied\n * @returns TransformResult indicating how the client operation should be handled\n */\n readonly transformOperation: (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>\n ) => Transform.TransformResult;\n }\n \n /**\n * Any primitive type - used for generic constraints.\n */\n export type AnyPrimitive = Primitive<any, any, any, any>;\n \n /**\n * Infer the state type from a primitive.\n */\n export type InferState<T> = T extends Primitive<infer S, any, any, any> ? S : never;\n \n /**\n * Infer the proxy type from a primitive.\n */\n export type InferProxy<T> = T extends Primitive<any, infer P, any, any> ? P : never;\n\n /**\n * Infer the SetInput type from a primitive.\n * Works with both Primitive interface types and types with a TSetInput property (like TreeNodePrimitive).\n */\n export type InferSetInput<T> = \n T extends Primitive<any, any, any, any, infer S, any> ? S : \n T extends { TSetInput: infer S } ? S : \n never;\n\n /**\n * Infer the UpdateInput type from a primitive.\n * Works with both Primitive interface types and types with a TUpdateInput property (like TreeNodePrimitive).\n */\n export type InferUpdateInput<T> = \n T extends Primitive<any, any, any, any, any, infer U> ? U : \n T extends { TUpdateInput: infer U } ? U : \n never;\n \n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is false and THasDefault is false, the value may be undefined.\n * Otherwise, the value is guaranteed to be defined.\n */\n export type MaybeUndefined<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends false ? THasDefault extends false ? Optional<T> : T : T;\n\n export type Optional<T> = T | undefined;\n\n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is true and THasDefault is false, the value must be provided.\n * Otherwise, the value may be undefined.\n */\n export type NeedsValue<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends true ? THasDefault extends false ? T : Optional<T> : Optional<T>;\n \n /**\n * Infer the snapshot type from a primitive.\n * The snapshot is a readonly, type-safe structure suitable for rendering.\n */\n export type InferSnapshot<T> = T extends Primitive<any, infer P, any, any>\n ? P extends { toSnapshot(): infer S } ? S : never\n : never;\n\n /**\n * Extract THasDefault from a primitive.\n */\n export type HasDefault<T> = T extends Primitive<any, any, any, infer H> ? H : false;\n\n /**\n * Extract TDefined from a primitive.\n */\n export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;\n\n /**\n * Infer whether a primitive is required.\n * Alias for IsDefined for clarity.\n */\n export type IsRequired<T> = IsDefined<T>;\n\n\n // =============================================================================\n // Validation Errors\n // =============================================================================\n \n export class ValidationError extends Error {\n readonly _tag = \"ValidationError\";\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n }\n \n // =============================================================================\n // Validation Infrastructure\n // =============================================================================\n \n /**\n * A validator that checks a value and returns whether it's valid.\n */\n export interface Validator<T> {\n readonly validate: (value: T) => boolean;\n readonly message: string;\n }\n \n\n/**\n * Runs all validators against a value, throwing ValidationError if any fail.\n */\nexport function runValidators<T>(value: T, validators: readonly { validate: (value: T) => boolean; message: string }[]): void {\n for (const validator of validators) {\n if (!validator.validate(value)) {\n throw new ValidationError(validator.message);\n }\n }\n}\n\n/**\n * Checks if an operation is compatible with the given operation definitions.\n * @param operation - The operation to check.\n * @param operationDefinitions - The operation definitions to check against.\n * @returns True if the operation is compatible, false otherwise.\n */\nexport function isCompatibleOperation(operation: Operation.Operation<any, any, any>, operationDefinitions: Record<string, OperationDefinition.OperationDefinition<any, any, any>>) {\n const values = Object.values(operationDefinitions);\n return values.some(value => value.kind === operation.kind);\n}\n\n// =============================================================================\n// Default Value Utilities\n// =============================================================================\n\n/**\n * Applies default values to a partial input, recursively handling nested structs.\n * \n * Uses a two-layer approach:\n * 1. First, get the struct's initial state (which includes struct-level defaults)\n * 2. Then, layer the provided values on top\n * 3. Finally, ensure nested structs are recursively processed\n * \n * @param primitive - The primitive definition containing field information\n * @param value - The partial value provided by the user\n * @returns The value with defaults applied for missing fields\n */\nexport function applyDefaults<T extends AnyPrimitive>(\n primitive: T,\n value: Partial<InferState<T>>\n): InferState<T> {\n // Only structs need default merging\n if (primitive._tag === \"StructPrimitive\") {\n const structPrimitive = primitive as unknown as { \n fields: Record<string, AnyPrimitive>;\n _internal: { getInitialState: () => Record<string, unknown> | undefined };\n };\n \n // Start with the struct's initial state (struct-level default or field defaults)\n const structInitialState = structPrimitive._internal.getInitialState() ?? {};\n \n // Layer the provided values on top of initial state\n const result: Record<string, unknown> = { ...structInitialState, ...value };\n \n for (const key in structPrimitive.fields) {\n const fieldPrimitive = structPrimitive.fields[key]!;\n \n if (result[key] === undefined) {\n // Field still not provided after merging - try individual field default\n const fieldDefault = fieldPrimitive._internal.getInitialState();\n if (fieldDefault !== undefined) {\n result[key] = fieldDefault;\n }\n } else if (fieldPrimitive._tag === \"StructPrimitive\" && typeof result[key] === \"object\" && result[key] !== null) {\n // Recursively apply defaults to nested structs\n result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);\n }\n }\n \n return result as InferState<T>;\n }\n \n // For non-struct primitives, return the value as-is\n return value as InferState<T>;\n}\n\n"],"mappings":";;;;AA6IE,IAAa,kBAAb,cAAqC,MAAM;CAEzC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;wBAFP,QAAO;AAGd,OAAK,OAAO;;;;;;AAoBlB,SAAgB,cAAiB,OAAU,YAAmF;AAC5H,MAAK,MAAM,aAAa,WACtB,KAAI,CAAC,UAAU,SAAS,MAAM,CAC5B,OAAM,IAAI,gBAAgB,UAAU,QAAQ;;;;;;;;AAWlD,SAAgB,sBAAsB,WAA+C,sBAA8F;AAEjL,QADe,OAAO,OAAO,qBAAqB,CACpC,MAAK,UAAS,MAAM,SAAS,UAAU,KAAK;;;;;;;;;;;;;;AAmB5D,SAAgB,cACd,WACA,OACe;AAEf,KAAI,UAAU,SAAS,mBAAmB;;EACxC,MAAM,kBAAkB;EASxB,MAAMA,oEAHqB,gBAAgB,UAAU,iBAAiB,yEAAI,EAAE,GAGR;AAEpE,OAAK,MAAM,OAAO,gBAAgB,QAAQ;GACxC,MAAM,iBAAiB,gBAAgB,OAAO;AAE9C,OAAI,OAAO,SAAS,QAAW;IAE7B,MAAM,eAAe,eAAe,UAAU,iBAAiB;AAC/D,QAAI,iBAAiB,OACnB,QAAO,OAAO;cAEP,eAAe,SAAS,qBAAqB,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS,KAEzG,QAAO,OAAO,cAAc,gBAAgB,OAAO,KAAmD;;AAI1G,SAAO;;AAIT,QAAO"}
1
+ {"version":3,"file":"shared.mjs","names":["result: Record<string, unknown>","matchingVariantKey: string | undefined"],"sources":["../../src/primitives/shared.ts"],"sourcesContent":["import * as Operation from \"../Operation\";\nimport * as OperationDefinition from \"../OperationDefinition\";\nimport * as ProxyEnvironment from \"../ProxyEnvironment\";\nimport * as OperationPath from \"../OperationPath\";\nimport * as Transform from \"../Transform\";\n\n// =============================================================================\n// Primitive Interface & Type Utilities\n// =============================================================================\n\n/**\n * Base interface that all primitives must implement.\n * Provides type inference helpers and internal operations.\n * \n * @typeParam TState - The state type this primitive holds\n * @typeParam TProxy - The proxy type for interacting with this primitive\n * @typeParam TDefined - Whether the value is guaranteed to be defined (via required() or default())\n * @typeParam THasDefault - Whether this primitive has a default value\n */\nexport interface Primitive<TState, TProxy, TRequired extends boolean = false, THasDefault extends boolean = false, TSetInput = unknown, TUpdateInput = unknown> {\n readonly _tag: string;\n readonly _State: TState;\n readonly _Proxy: TProxy;\n readonly _TRequired: TRequired;\n readonly _THasDefault: THasDefault;\n readonly _internal: PrimitiveInternal<TState, TProxy>;\n readonly TSetInput: TSetInput;\n readonly TUpdateInput: TUpdateInput;\n }\n \n /**\n * Internal operations that each primitive must provide.\n */\n export interface PrimitiveInternal<TState, TProxy> {\n /** Creates a proxy for generating operations */\n readonly createProxy: (env: ProxyEnvironment.ProxyEnvironment, path: OperationPath.OperationPath) => TProxy;\n /** Applies an operation to the current state, returning the new state */\n readonly applyOperation: (state: TState | undefined, operation: Operation.Operation<any, any, any>) => TState;\n /** Returns the initial/default state for this primitive */\n readonly getInitialState: () => TState | undefined;\n /**\n * Converts a set input value to state format.\n * For most primitives, this is a simple pass-through with defaults applied.\n * For Tree primitives, this converts nested input to flat TreeState.\n *\n * @param input - The set input value\n * @returns The corresponding state value\n */\n readonly convertSetInputToState?: (input: unknown) => TState;\n /**\n * Transforms a client operation against a server operation.\n * Used for operational transformation (OT) conflict resolution.\n *\n * @param clientOp - The client's operation to transform\n * @param serverOp - The server's operation that has already been applied\n * @returns TransformResult indicating how the client operation should be handled\n */\n readonly transformOperation: (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>\n ) => Transform.TransformResult;\n }\n \n /**\n * Any primitive type - used for generic constraints.\n */\n export type AnyPrimitive = Primitive<any, any, any, any>;\n \n /**\n * Infer the state type from a primitive.\n */\n export type InferState<T> = T extends Primitive<infer S, any, any, any> ? S : never;\n \n /**\n * Infer the proxy type from a primitive.\n */\n export type InferProxy<T> = T extends Primitive<any, infer P, any, any> ? P : never;\n\n /**\n * Infer the SetInput type from a primitive.\n * Works with both Primitive interface types and types with a TSetInput property (like TreeNodePrimitive).\n */\n export type InferSetInput<T> = \n T extends Primitive<any, any, any, any, infer S, any> ? S : \n T extends { TSetInput: infer S } ? S : \n never;\n\n /**\n * Infer the UpdateInput type from a primitive.\n * Works with both Primitive interface types and types with a TUpdateInput property (like TreeNodePrimitive).\n */\n export type InferUpdateInput<T> = \n T extends Primitive<any, any, any, any, any, infer U> ? U : \n T extends { TUpdateInput: infer U } ? U : \n never;\n \n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is false and THasDefault is false, the value may be undefined.\n * Otherwise, the value is guaranteed to be defined.\n */\n export type MaybeUndefined<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends false ? THasDefault extends false ? Optional<T> : T : T;\n\n export type Optional<T> = T | undefined;\n\n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is true and THasDefault is false, the value must be provided.\n * Otherwise, the value may be undefined.\n */\n export type NeedsValue<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends true ? THasDefault extends false ? T : Optional<T> : Optional<T>;\n \n /**\n * Infer the snapshot type from a primitive.\n * The snapshot is a readonly, type-safe structure suitable for rendering.\n */\n export type InferSnapshot<T> = T extends Primitive<any, infer P, any, any>\n ? P extends { toSnapshot(): infer S } ? S : never\n : never;\n\n /**\n * Extract THasDefault from a primitive.\n */\n export type HasDefault<T> = T extends Primitive<any, any, any, infer H> ? H : false;\n\n /**\n * Extract TDefined from a primitive.\n */\n export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;\n\n /**\n * Infer whether a primitive is required.\n * Alias for IsDefined for clarity.\n */\n export type IsRequired<T> = IsDefined<T>;\n\n\n // =============================================================================\n // Validation Errors\n // =============================================================================\n \n export class ValidationError extends Error {\n readonly _tag = \"ValidationError\";\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n }\n \n // =============================================================================\n // Validation Infrastructure\n // =============================================================================\n \n /**\n * A validator that checks a value and returns whether it's valid.\n */\n export interface Validator<T> {\n readonly validate: (value: T) => boolean;\n readonly message: string;\n }\n \n\n/**\n * Runs all validators against a value, throwing ValidationError if any fail.\n */\nexport function runValidators<T>(value: T, validators: readonly { validate: (value: T) => boolean; message: string }[]): void {\n for (const validator of validators) {\n if (!validator.validate(value)) {\n throw new ValidationError(validator.message);\n }\n }\n}\n\n/**\n * Checks if an operation is compatible with the given operation definitions.\n * @param operation - The operation to check.\n * @param operationDefinitions - The operation definitions to check against.\n * @returns True if the operation is compatible, false otherwise.\n */\nexport function isCompatibleOperation(operation: Operation.Operation<any, any, any>, operationDefinitions: Record<string, OperationDefinition.OperationDefinition<any, any, any>>) {\n const values = Object.values(operationDefinitions);\n return values.some(value => value.kind === operation.kind);\n}\n\n// =============================================================================\n// Default Value Utilities\n// =============================================================================\n\n/**\n * Applies default values to a partial input, recursively handling nested structs and unions.\n * \n * Uses a two-layer approach:\n * 1. First, get the struct's initial state (which includes struct-level defaults)\n * 2. Then, layer the provided values on top\n * 3. Finally, ensure nested structs and unions are recursively processed\n * \n * @param primitive - The primitive definition containing field information\n * @param value - The partial value provided by the user\n * @returns The value with defaults applied for missing fields\n */\nexport function applyDefaults<T extends AnyPrimitive>(\n primitive: T,\n value: Partial<InferState<T>>\n): InferState<T> {\n // Handle StructPrimitive\n if (primitive._tag === \"StructPrimitive\") {\n const structPrimitive = primitive as unknown as { \n fields: Record<string, AnyPrimitive>;\n _internal: { getInitialState: () => Record<string, unknown> | undefined };\n };\n \n // Start with the struct's initial state (struct-level default or field defaults)\n const structInitialState = structPrimitive._internal.getInitialState() ?? {};\n \n // Layer the provided values on top of initial state\n const result: Record<string, unknown> = { ...structInitialState, ...value };\n \n for (const key in structPrimitive.fields) {\n const fieldPrimitive = structPrimitive.fields[key]!;\n \n if (result[key] === undefined) {\n // Field still not provided after merging - try individual field default\n const fieldDefault = fieldPrimitive._internal.getInitialState();\n if (fieldDefault !== undefined) {\n result[key] = fieldDefault;\n }\n } else if (typeof result[key] === \"object\" && result[key] !== null) {\n // Recursively apply defaults to nested structs and unions\n if (fieldPrimitive._tag === \"StructPrimitive\" || fieldPrimitive._tag === \"UnionPrimitive\") {\n result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);\n }\n }\n }\n \n return result as InferState<T>;\n }\n \n // Handle UnionPrimitive\n if (primitive._tag === \"UnionPrimitive\") {\n const unionPrimitive = primitive as unknown as {\n _schema: {\n discriminator: string;\n variants: Record<string, AnyPrimitive>;\n };\n };\n \n // Validate that value is an object\n if (typeof value !== \"object\" || value === null) {\n return value as InferState<T>;\n }\n \n const discriminator = unionPrimitive._schema.discriminator;\n const discriminatorValue = (value as Record<string, unknown>)[discriminator];\n \n // Find the matching variant based on discriminator value\n let matchingVariantKey: string | undefined;\n for (const variantKey in unionPrimitive._schema.variants) {\n const variant = unionPrimitive._schema.variants[variantKey]!;\n // Variants are structs - check if the discriminator field's literal matches\n if (variant._tag === \"StructPrimitive\") {\n const variantStruct = variant as unknown as {\n fields: Record<string, AnyPrimitive>;\n };\n const discriminatorField = variantStruct.fields[discriminator];\n if (discriminatorField && discriminatorField._tag === \"LiteralPrimitive\") {\n const literalPrimitive = discriminatorField as unknown as { literal: unknown };\n if (literalPrimitive.literal === discriminatorValue) {\n matchingVariantKey = variantKey;\n break;\n }\n }\n }\n }\n \n if (!matchingVariantKey) {\n // No matching variant found - return value as-is\n return value as InferState<T>;\n }\n \n // Apply defaults using the matching variant's struct\n const variantPrimitive = unionPrimitive._schema.variants[matchingVariantKey]!;\n return applyDefaults(variantPrimitive, value as Partial<InferState<typeof variantPrimitive>>) as InferState<T>;\n }\n \n // For other primitives, return the value as-is\n return value as InferState<T>;\n}\n\n"],"mappings":";;;;AA6IE,IAAa,kBAAb,cAAqC,MAAM;CAEzC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;wBAFP,QAAO;AAGd,OAAK,OAAO;;;;;;AAoBlB,SAAgB,cAAiB,OAAU,YAAmF;AAC5H,MAAK,MAAM,aAAa,WACtB,KAAI,CAAC,UAAU,SAAS,MAAM,CAC5B,OAAM,IAAI,gBAAgB,UAAU,QAAQ;;;;;;;;AAWlD,SAAgB,sBAAsB,WAA+C,sBAA8F;AAEjL,QADe,OAAO,OAAO,qBAAqB,CACpC,MAAK,UAAS,MAAM,SAAS,UAAU,KAAK;;;;;;;;;;;;;;AAmB5D,SAAgB,cACd,WACA,OACe;AAEf,KAAI,UAAU,SAAS,mBAAmB;;EACxC,MAAM,kBAAkB;EASxB,MAAMA,oEAHqB,gBAAgB,UAAU,iBAAiB,yEAAI,EAAE,GAGR;AAEpE,OAAK,MAAM,OAAO,gBAAgB,QAAQ;GACxC,MAAM,iBAAiB,gBAAgB,OAAO;AAE9C,OAAI,OAAO,SAAS,QAAW;IAE7B,MAAM,eAAe,eAAe,UAAU,iBAAiB;AAC/D,QAAI,iBAAiB,OACnB,QAAO,OAAO;cAEP,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS,MAE5D;QAAI,eAAe,SAAS,qBAAqB,eAAe,SAAS,iBACvE,QAAO,OAAO,cAAc,gBAAgB,OAAO,KAAmD;;;AAK5G,SAAO;;AAIT,KAAI,UAAU,SAAS,kBAAkB;EACvC,MAAM,iBAAiB;AAQvB,MAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;EAGT,MAAM,gBAAgB,eAAe,QAAQ;EAC7C,MAAM,qBAAsB,MAAkC;EAG9D,IAAIC;AACJ,OAAK,MAAM,cAAc,eAAe,QAAQ,UAAU;GACxD,MAAM,UAAU,eAAe,QAAQ,SAAS;AAEhD,OAAI,QAAQ,SAAS,mBAAmB;IAItC,MAAM,qBAHgB,QAGmB,OAAO;AAChD,QAAI,sBAAsB,mBAAmB,SAAS,oBAEpD;SADyB,mBACJ,YAAY,oBAAoB;AACnD,2BAAqB;AACrB;;;;;AAMR,MAAI,CAAC,mBAEH,QAAO;EAIT,MAAM,mBAAmB,eAAe,QAAQ,SAAS;AACzD,SAAO,cAAc,kBAAkB,MAAsD;;AAI/F,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidhash/mimic",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "typescript": "5.8.3",
32
32
  "vite-tsconfig-paths": "^5.1.4",
33
33
  "vitest": "^3.2.4",
34
- "@voidhash/tsconfig": "0.0.6"
34
+ "@voidhash/tsconfig": "0.0.7"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "effect": "^3.19.12"
@@ -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
 
@@ -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
+ });