@voidhash/mimic 0.0.1-alpha.6 → 0.0.1-alpha.8
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 +43 -15
- 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-BK7kfHJZ.d.cts +1165 -0
- package/dist/Primitive-BK7kfHJZ.d.cts.map +1 -0
- package/dist/Primitive-D1kdB6za.d.mts +1165 -0
- package/dist/Primitive-D1kdB6za.d.mts.map +1 -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 +309 -757
- package/dist/index.d.cts +5 -1054
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -1054
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +168 -575
- package/dist/index.mjs.map +1 -1
- 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 +17 -5
- package/src/primitives/Array.ts +57 -22
- package/src/primitives/Boolean.ts +32 -18
- package/src/primitives/Either.ts +39 -24
- package/src/primitives/Lazy.ts +16 -2
- package/src/primitives/Literal.ts +32 -19
- package/src/primitives/Number.ts +38 -25
- package/src/primitives/String.ts +39 -24
- package/src/primitives/Struct.ts +124 -27
- package/src/primitives/Tree.ts +117 -30
- package/src/primitives/Union.ts +56 -29
- package/src/primitives/shared.ts +103 -9
- package/tests/primitives/Array.test.ts +108 -0
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +250 -0
- package/tsdown.config.ts +1 -1
- /package/dist/{chunk-C6wwvPpM.mjs → chunk-CLMFDpHK.mjs} +0 -0
package/src/primitives/Union.ts
CHANGED
|
@@ -4,17 +4,17 @@ import * as Operation from "../Operation";
|
|
|
4
4
|
import * as OperationPath from "../OperationPath";
|
|
5
5
|
import * as ProxyEnvironment from "../ProxyEnvironment";
|
|
6
6
|
import * as Transform from "../Transform";
|
|
7
|
-
import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, InferState, InferProxy, InferSnapshot } from "../Primitive";
|
|
7
|
+
import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, InferState, InferProxy, InferSnapshot, InferSetInput } from "../Primitive";
|
|
8
8
|
import { ValidationError } from "../Primitive";
|
|
9
9
|
import { LiteralPrimitive } from "./Literal";
|
|
10
|
-
import { StructPrimitive } from "./Struct";
|
|
11
|
-
import { runValidators } from "./shared";
|
|
10
|
+
import { StructPrimitive, InferStructState } from "./Struct";
|
|
11
|
+
import { runValidators, applyDefaults } from "./shared";
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Type constraint for union variants - must be struct primitives
|
|
16
16
|
*/
|
|
17
|
-
export type UnionVariants = Record<string, StructPrimitive<any, any>>;
|
|
17
|
+
export type UnionVariants = Record<string, StructPrimitive<any, any, any>>;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Infer the union state type from variants
|
|
@@ -30,15 +30,23 @@ export type InferUnionSnapshot<TVariants extends UnionVariants> = {
|
|
|
30
30
|
[K in keyof TVariants]: InferSnapshot<TVariants[K]>;
|
|
31
31
|
}[keyof TVariants];
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Compute the input type for union.set() operations.
|
|
35
|
+
* Uses each variant's TSetInput type.
|
|
36
|
+
*/
|
|
37
|
+
export type UnionSetInput<TVariants extends UnionVariants> = {
|
|
38
|
+
[K in keyof TVariants]: InferSetInput<TVariants[K]>;
|
|
39
|
+
}[keyof TVariants];
|
|
40
|
+
|
|
33
41
|
/**
|
|
34
42
|
* Proxy for accessing union variants
|
|
35
43
|
*/
|
|
36
|
-
export interface UnionProxy<TVariants extends UnionVariants, _TDiscriminator extends string,
|
|
44
|
+
export interface UnionProxy<TVariants extends UnionVariants, _TDiscriminator extends string, TRequired extends boolean = false, THasDefault extends boolean = false> {
|
|
37
45
|
/** Gets the current union value */
|
|
38
|
-
get(): MaybeUndefined<InferUnionState<TVariants>,
|
|
46
|
+
get(): MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault>;
|
|
39
47
|
|
|
40
|
-
/** Sets the entire union value */
|
|
41
|
-
set(value:
|
|
48
|
+
/** Sets the entire union value (applies defaults for variant fields) */
|
|
49
|
+
set(value: UnionSetInput<TVariants>): void;
|
|
42
50
|
|
|
43
51
|
/** Access a specific variant's proxy (assumes the variant is active) */
|
|
44
52
|
as<K extends keyof TVariants>(variant: K): InferProxy<TVariants[K]>;
|
|
@@ -49,7 +57,7 @@ export interface UnionProxy<TVariants extends UnionVariants, _TDiscriminator ext
|
|
|
49
57
|
}): R | undefined;
|
|
50
58
|
|
|
51
59
|
/** Returns a readonly snapshot of the union for rendering */
|
|
52
|
-
toSnapshot(): MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
60
|
+
toSnapshot(): MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
interface UnionPrimitiveSchema<TVariants extends UnionVariants, TDiscriminator extends string> {
|
|
@@ -59,12 +67,16 @@ interface UnionPrimitiveSchema<TVariants extends UnionVariants, TDiscriminator e
|
|
|
59
67
|
readonly variants: TVariants;
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator extends string = "type",
|
|
63
|
-
implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator,
|
|
70
|
+
export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator extends string = "type", TRequired extends boolean = false, THasDefault extends boolean = false>
|
|
71
|
+
implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault>, TRequired, THasDefault, UnionSetInput<TVariants>, UnionSetInput<TVariants>>
|
|
64
72
|
{
|
|
65
73
|
readonly _tag = "UnionPrimitive" as const;
|
|
66
74
|
readonly _State!: InferUnionState<TVariants>;
|
|
67
|
-
readonly _Proxy!: UnionProxy<TVariants, TDiscriminator,
|
|
75
|
+
readonly _Proxy!: UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault>;
|
|
76
|
+
readonly _TRequired!: TRequired;
|
|
77
|
+
readonly _THasDefault!: THasDefault;
|
|
78
|
+
readonly TSetInput!: UnionSetInput<TVariants>;
|
|
79
|
+
readonly TUpdateInput!: UnionSetInput<TVariants>;
|
|
68
80
|
|
|
69
81
|
private readonly _schema: UnionPrimitiveSchema<TVariants, TDiscriminator>;
|
|
70
82
|
|
|
@@ -82,7 +94,7 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
/** Mark this union as required */
|
|
85
|
-
required(): UnionPrimitive<TVariants, TDiscriminator, true> {
|
|
97
|
+
required(): UnionPrimitive<TVariants, TDiscriminator, true, THasDefault> {
|
|
86
98
|
return new UnionPrimitive({
|
|
87
99
|
...this._schema,
|
|
88
100
|
required: true,
|
|
@@ -90,10 +102,12 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
/** Set a default value for this union */
|
|
93
|
-
default(defaultValue:
|
|
105
|
+
default(defaultValue: UnionSetInput<TVariants>): UnionPrimitive<TVariants, TDiscriminator, true, true> {
|
|
106
|
+
// Apply defaults to the variant
|
|
107
|
+
const merged = this._applyVariantDefaults(defaultValue as Partial<InferUnionState<TVariants>>);
|
|
94
108
|
return new UnionPrimitive({
|
|
95
109
|
...this._schema,
|
|
96
|
-
defaultValue,
|
|
110
|
+
defaultValue: merged,
|
|
97
111
|
});
|
|
98
112
|
}
|
|
99
113
|
|
|
@@ -119,7 +133,7 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
119
133
|
const variant = this._schema.variants[key]!;
|
|
120
134
|
const discriminatorField = variant.fields[this._schema.discriminator];
|
|
121
135
|
if (discriminatorField && discriminatorField._tag === "LiteralPrimitive") {
|
|
122
|
-
const literalPrimitive = discriminatorField as LiteralPrimitive<any, any>;
|
|
136
|
+
const literalPrimitive = discriminatorField as LiteralPrimitive<any, any, any>;
|
|
123
137
|
if (literalPrimitive.literal === discriminatorValue) {
|
|
124
138
|
return key;
|
|
125
139
|
}
|
|
@@ -128,22 +142,35 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
128
142
|
return undefined;
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
|
|
145
|
+
/** Apply defaults to a variant value based on the discriminator */
|
|
146
|
+
private _applyVariantDefaults(value: Partial<InferUnionState<TVariants>>): InferUnionState<TVariants> {
|
|
147
|
+
const variantKey = this._findVariantKey(value as InferUnionState<TVariants>);
|
|
148
|
+
if (!variantKey) {
|
|
149
|
+
return value as InferUnionState<TVariants>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const variantPrimitive = this._schema.variants[variantKey]!;
|
|
153
|
+
return applyDefaults(variantPrimitive as AnyPrimitive, value) as InferUnionState<TVariants>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
readonly _internal: PrimitiveInternal<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault>> = {
|
|
132
157
|
createProxy: (
|
|
133
158
|
env: ProxyEnvironment.ProxyEnvironment,
|
|
134
159
|
operationPath: OperationPath.OperationPath
|
|
135
|
-
): UnionProxy<TVariants, TDiscriminator,
|
|
160
|
+
): UnionProxy<TVariants, TDiscriminator, TRequired, THasDefault> => {
|
|
136
161
|
const variants = this._schema.variants;
|
|
137
162
|
const defaultValue = this._schema.defaultValue;
|
|
138
163
|
|
|
139
164
|
return {
|
|
140
|
-
get: (): MaybeUndefined<InferUnionState<TVariants>,
|
|
165
|
+
get: (): MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault> => {
|
|
141
166
|
const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
|
|
142
|
-
return (state ?? defaultValue) as MaybeUndefined<InferUnionState<TVariants>,
|
|
167
|
+
return (state ?? defaultValue) as MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault>;
|
|
143
168
|
},
|
|
144
|
-
set: (value:
|
|
169
|
+
set: (value: UnionSetInput<TVariants>) => {
|
|
170
|
+
// Apply defaults for the variant
|
|
171
|
+
const merged = this._applyVariantDefaults(value as Partial<InferUnionState<TVariants>>);
|
|
145
172
|
env.addOperation(
|
|
146
|
-
Operation.fromDefinition(operationPath, this._opDefinitions.set,
|
|
173
|
+
Operation.fromDefinition(operationPath, this._opDefinitions.set, merged)
|
|
147
174
|
);
|
|
148
175
|
},
|
|
149
176
|
as: <K extends keyof TVariants>(variant: K): InferProxy<TVariants[K]> => {
|
|
@@ -166,21 +193,21 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
166
193
|
const variantProxy = variants[variantKey]!._internal.createProxy(env, operationPath) as InferProxy<TVariants[typeof variantKey]>;
|
|
167
194
|
return handler(variantProxy);
|
|
168
195
|
},
|
|
169
|
-
toSnapshot: (): MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
196
|
+
toSnapshot: (): MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault> => {
|
|
170
197
|
const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
|
|
171
198
|
const effectiveState = state ?? defaultValue;
|
|
172
199
|
if (!effectiveState) {
|
|
173
|
-
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
200
|
+
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
174
201
|
}
|
|
175
202
|
|
|
176
203
|
const variantKey = this._findVariantKey(effectiveState);
|
|
177
204
|
if (!variantKey) {
|
|
178
|
-
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
205
|
+
return undefined as MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
179
206
|
}
|
|
180
207
|
|
|
181
208
|
const variantPrimitive = variants[variantKey]!;
|
|
182
209
|
const variantProxy = variantPrimitive._internal.createProxy(env, operationPath);
|
|
183
|
-
return (variantProxy as unknown as { toSnapshot(): InferUnionSnapshot<TVariants> }).toSnapshot() as MaybeUndefined<InferUnionSnapshot<TVariants>,
|
|
210
|
+
return (variantProxy as unknown as { toSnapshot(): InferUnionSnapshot<TVariants> }).toSnapshot() as MaybeUndefined<InferUnionSnapshot<TVariants>, TRequired, THasDefault>;
|
|
184
211
|
},
|
|
185
212
|
};
|
|
186
213
|
},
|
|
@@ -311,13 +338,13 @@ export interface UnionOptions<TVariants extends UnionVariants, TDiscriminator ex
|
|
|
311
338
|
/** Creates a new UnionPrimitive with the given variants */
|
|
312
339
|
export function Union<TVariants extends UnionVariants>(
|
|
313
340
|
options: UnionOptions<TVariants, "type">
|
|
314
|
-
): UnionPrimitive<TVariants, "type", false>;
|
|
341
|
+
): UnionPrimitive<TVariants, "type", false, false>;
|
|
315
342
|
export function Union<TVariants extends UnionVariants, TDiscriminator extends string>(
|
|
316
343
|
options: UnionOptions<TVariants, TDiscriminator>
|
|
317
|
-
): UnionPrimitive<TVariants, TDiscriminator, false>;
|
|
344
|
+
): UnionPrimitive<TVariants, TDiscriminator, false, false>;
|
|
318
345
|
export function Union<TVariants extends UnionVariants, TDiscriminator extends string = "type">(
|
|
319
346
|
options: UnionOptions<TVariants, TDiscriminator>
|
|
320
|
-
): UnionPrimitive<TVariants, TDiscriminator, false> {
|
|
347
|
+
): UnionPrimitive<TVariants, TDiscriminator, false, false> {
|
|
321
348
|
const discriminator = (options.discriminator ?? "type") as TDiscriminator;
|
|
322
349
|
return new UnionPrimitive({
|
|
323
350
|
required: false,
|
package/src/primitives/shared.ts
CHANGED
|
@@ -11,12 +11,21 @@ import * as Transform from "../Transform";
|
|
|
11
11
|
/**
|
|
12
12
|
* Base interface that all primitives must implement.
|
|
13
13
|
* Provides type inference helpers and internal operations.
|
|
14
|
+
*
|
|
15
|
+
* @typeParam TState - The state type this primitive holds
|
|
16
|
+
* @typeParam TProxy - The proxy type for interacting with this primitive
|
|
17
|
+
* @typeParam TDefined - Whether the value is guaranteed to be defined (via required() or default())
|
|
18
|
+
* @typeParam THasDefault - Whether this primitive has a default value
|
|
14
19
|
*/
|
|
15
|
-
export interface Primitive<TState, TProxy> {
|
|
20
|
+
export interface Primitive<TState, TProxy, TRequired extends boolean = false, THasDefault extends boolean = false, TSetInput = unknown, TUpdateInput = unknown> {
|
|
16
21
|
readonly _tag: string;
|
|
17
22
|
readonly _State: TState;
|
|
18
23
|
readonly _Proxy: TProxy;
|
|
24
|
+
readonly _TRequired: TRequired;
|
|
25
|
+
readonly _THasDefault: THasDefault;
|
|
19
26
|
readonly _internal: PrimitiveInternal<TState, TProxy>;
|
|
27
|
+
readonly TSetInput: TSetInput;
|
|
28
|
+
readonly TUpdateInput: TUpdateInput;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
/**
|
|
@@ -46,32 +55,62 @@ export interface Primitive<TState, TProxy> {
|
|
|
46
55
|
/**
|
|
47
56
|
* Any primitive type - used for generic constraints.
|
|
48
57
|
*/
|
|
49
|
-
export type AnyPrimitive = Primitive<any, any>;
|
|
58
|
+
export type AnyPrimitive = Primitive<any, any, any, any>;
|
|
50
59
|
|
|
51
60
|
/**
|
|
52
61
|
* Infer the state type from a primitive.
|
|
53
62
|
*/
|
|
54
|
-
export type InferState<T> = T extends Primitive<infer S, any> ? S : never;
|
|
63
|
+
export type InferState<T> = T extends Primitive<infer S, any, any, any> ? S : never;
|
|
55
64
|
|
|
56
65
|
/**
|
|
57
66
|
* Infer the proxy type from a primitive.
|
|
58
67
|
*/
|
|
59
|
-
export type InferProxy<T> = T extends Primitive<any, infer P> ? P : never;
|
|
68
|
+
export type InferProxy<T> = T extends Primitive<any, infer P, any, any> ? P : never;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Infer the SetInput type from a primitive.
|
|
72
|
+
*/
|
|
73
|
+
export type InferSetInput<T> = T extends Primitive<any, any, any, any, infer S, any> ? S : never;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Infer the UpdateInput type from a primitive.
|
|
77
|
+
*/
|
|
78
|
+
export type InferUpdateInput<T> = T extends Primitive<any, any, any, any, any, infer U> ? U : never;
|
|
60
79
|
|
|
61
80
|
/**
|
|
62
|
-
* Helper type to conditionally add undefined based on
|
|
63
|
-
* When
|
|
64
|
-
*
|
|
81
|
+
* Helper type to conditionally add undefined based on TRequired and THasDefault.
|
|
82
|
+
* When TRequired is false and THasDefault is false, the value may be undefined.
|
|
83
|
+
* Otherwise, the value is guaranteed to be defined.
|
|
84
|
+
*/
|
|
85
|
+
export type MaybeUndefined<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends false ? THasDefault extends false ? Optional<T> : T : T;
|
|
86
|
+
|
|
87
|
+
export type Optional<T> = T | undefined;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Helper type to conditionally add undefined based on TRequired and THasDefault.
|
|
91
|
+
* When TRequired is true and THasDefault is false, the value must be provided.
|
|
92
|
+
* Otherwise, the value may be undefined.
|
|
65
93
|
*/
|
|
66
|
-
export type
|
|
94
|
+
export type NeedsValue<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends true ? THasDefault extends false ? T : Optional<T> : Optional<T>;
|
|
67
95
|
|
|
68
96
|
/**
|
|
69
97
|
* Infer the snapshot type from a primitive.
|
|
70
98
|
* The snapshot is a readonly, type-safe structure suitable for rendering.
|
|
71
99
|
*/
|
|
72
|
-
export type InferSnapshot<T> = T extends Primitive<any, infer P>
|
|
100
|
+
export type InferSnapshot<T> = T extends Primitive<any, infer P, any, any>
|
|
73
101
|
? P extends { toSnapshot(): infer S } ? S : never
|
|
74
102
|
: never;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract THasDefault from a primitive.
|
|
106
|
+
*/
|
|
107
|
+
export type HasDefault<T> = T extends Primitive<any, any, any, infer H> ? H : false;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extract TDefined from a primitive.
|
|
111
|
+
*/
|
|
112
|
+
export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;
|
|
113
|
+
|
|
75
114
|
|
|
76
115
|
// =============================================================================
|
|
77
116
|
// Validation Errors
|
|
@@ -120,3 +159,58 @@ export function isCompatibleOperation(operation: Operation.Operation<any, any, a
|
|
|
120
159
|
return values.some(value => value.kind === operation.kind);
|
|
121
160
|
}
|
|
122
161
|
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// Default Value Utilities
|
|
164
|
+
// =============================================================================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Applies default values to a partial input, recursively handling nested structs.
|
|
168
|
+
*
|
|
169
|
+
* Uses a two-layer approach:
|
|
170
|
+
* 1. First, get the struct's initial state (which includes struct-level defaults)
|
|
171
|
+
* 2. Then, layer the provided values on top
|
|
172
|
+
* 3. Finally, ensure nested structs are recursively processed
|
|
173
|
+
*
|
|
174
|
+
* @param primitive - The primitive definition containing field information
|
|
175
|
+
* @param value - The partial value provided by the user
|
|
176
|
+
* @returns The value with defaults applied for missing fields
|
|
177
|
+
*/
|
|
178
|
+
export function applyDefaults<T extends AnyPrimitive>(
|
|
179
|
+
primitive: T,
|
|
180
|
+
value: Partial<InferState<T>>
|
|
181
|
+
): InferState<T> {
|
|
182
|
+
// Only structs need default merging
|
|
183
|
+
if (primitive._tag === "StructPrimitive") {
|
|
184
|
+
const structPrimitive = primitive as unknown as {
|
|
185
|
+
fields: Record<string, AnyPrimitive>;
|
|
186
|
+
_internal: { getInitialState: () => Record<string, unknown> | undefined };
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Start with the struct's initial state (struct-level default or field defaults)
|
|
190
|
+
const structInitialState = structPrimitive._internal.getInitialState() ?? {};
|
|
191
|
+
|
|
192
|
+
// Layer the provided values on top of initial state
|
|
193
|
+
const result: Record<string, unknown> = { ...structInitialState, ...value };
|
|
194
|
+
|
|
195
|
+
for (const key in structPrimitive.fields) {
|
|
196
|
+
const fieldPrimitive = structPrimitive.fields[key]!;
|
|
197
|
+
|
|
198
|
+
if (result[key] === undefined) {
|
|
199
|
+
// Field still not provided after merging - try individual field default
|
|
200
|
+
const fieldDefault = fieldPrimitive._internal.getInitialState();
|
|
201
|
+
if (fieldDefault !== undefined) {
|
|
202
|
+
result[key] = fieldDefault;
|
|
203
|
+
}
|
|
204
|
+
} else if (fieldPrimitive._tag === "StructPrimitive" && typeof result[key] === "object" && result[key] !== null) {
|
|
205
|
+
// Recursively apply defaults to nested structs
|
|
206
|
+
result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result as InferState<T>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// For non-struct primitives, return the value as-is
|
|
214
|
+
return value as InferState<T>;
|
|
215
|
+
}
|
|
216
|
+
|
|
@@ -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
|
// =============================================================================
|