@voidhash/mimic 0.0.1-alpha.5 → 0.0.1-alpha.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.
- package/.turbo/turbo-build.log +13 -13
- package/dist/index.cjs +140 -46
- package/dist/index.d.cts +229 -115
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +229 -115
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +140 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client/ClientDocument.ts +1 -1
- package/src/client/errors.ts +10 -10
- package/src/primitives/Array.ts +45 -21
- package/src/primitives/Boolean.ts +8 -6
- package/src/primitives/Either.ts +13 -11
- package/src/primitives/Literal.ts +7 -5
- package/src/primitives/Number.ts +13 -11
- package/src/primitives/String.ts +14 -12
- package/src/primitives/Struct.ts +66 -17
- package/src/primitives/Tree.ts +103 -32
- package/src/primitives/TreeNode.ts +49 -27
- package/src/primitives/Union.ts +44 -17
- package/src/primitives/shared.ts +106 -5
- package/src/server/errors.ts +6 -6
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +122 -0
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as OperationDefinition from "../OperationDefinition";
|
|
3
|
-
import * as Operation from "../Operation";
|
|
4
|
-
import * as OperationPath from "../OperationPath";
|
|
5
|
-
import * as ProxyEnvironment from "../ProxyEnvironment";
|
|
6
|
-
import * as Transform from "../Transform";
|
|
7
|
-
import type { AnyPrimitive, InferState } from "../Primitive";
|
|
1
|
+
import type { InferState } from "../Primitive";
|
|
8
2
|
import { StructPrimitive } from "./Struct";
|
|
9
3
|
|
|
10
4
|
/**
|
|
@@ -12,6 +6,15 @@ import { StructPrimitive } from "./Struct";
|
|
|
12
6
|
*/
|
|
13
7
|
const TreeNodeSelfSymbol = Symbol.for("TreeNode.Self");
|
|
14
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Branded type for TreeNodeSelf - distinguishable at compile time
|
|
11
|
+
*/
|
|
12
|
+
declare const SelfBrand: unique symbol;
|
|
13
|
+
export interface TreeNodeSelfType {
|
|
14
|
+
readonly _tag: "TreeNodeSelf";
|
|
15
|
+
readonly _brand: typeof SelfBrand;
|
|
16
|
+
}
|
|
17
|
+
|
|
15
18
|
/**
|
|
16
19
|
* Special placeholder for self-referential tree nodes.
|
|
17
20
|
* Use this in the children array when a node type can contain itself.
|
|
@@ -20,11 +23,11 @@ const TreeNodeSelfSymbol = Symbol.for("TreeNode.Self");
|
|
|
20
23
|
* ```typescript
|
|
21
24
|
* const FolderNode = TreeNode("folder", {
|
|
22
25
|
* data: Struct({ name: String() }),
|
|
23
|
-
* children: [
|
|
26
|
+
* children: [TreeNodeSelf], // Folder can contain other folders
|
|
24
27
|
* });
|
|
25
28
|
* ```
|
|
26
29
|
*/
|
|
27
|
-
export const TreeNodeSelf = { _tag: "TreeNodeSelf", _symbol: TreeNodeSelfSymbol } as unknown as
|
|
30
|
+
export const TreeNodeSelf: TreeNodeSelfType = { _tag: "TreeNodeSelf", _symbol: TreeNodeSelfSymbol } as unknown as TreeNodeSelfType;
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Check if a value is the Self placeholder
|
|
@@ -33,41 +36,57 @@ const isSelf = (value: unknown): boolean => {
|
|
|
33
36
|
return typeof value === "object" && value !== null && "_symbol" in value && (value as any)._symbol === TreeNodeSelfSymbol;
|
|
34
37
|
};
|
|
35
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Type utility to resolve Self placeholders to the actual node type
|
|
41
|
+
*/
|
|
42
|
+
type ResolveSelf<T, TSelf extends AnyTreeNodePrimitive> =
|
|
43
|
+
T extends TreeNodeSelfType ? TSelf : T;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Type utility to resolve all children in a tuple, replacing Self with the node type
|
|
47
|
+
*/
|
|
48
|
+
type ResolveChildrenUnion<TChildren, TSelf extends AnyTreeNodePrimitive> =
|
|
49
|
+
TChildren extends readonly (infer U)[]
|
|
50
|
+
? ResolveSelf<U, TSelf>
|
|
51
|
+
: never;
|
|
52
|
+
|
|
36
53
|
/**
|
|
37
54
|
* The type for children - either a direct array or a lazy function (for self-referential nodes).
|
|
38
|
-
* Using `Function` type allows self-references without explicit type annotations.
|
|
39
55
|
*/
|
|
40
|
-
|
|
41
|
-
export type TreeNodeChildren = readonly AnyTreeNodePrimitive[] | Function;
|
|
56
|
+
export type TreeNodeChildrenInput = readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[] | (() => readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]);
|
|
42
57
|
|
|
43
58
|
/**
|
|
44
59
|
* Any TreeNodePrimitive type - used for generic constraints.
|
|
45
60
|
*/
|
|
46
|
-
export type AnyTreeNodePrimitive = TreeNodePrimitive<string, StructPrimitive<any
|
|
61
|
+
export type AnyTreeNodePrimitive = TreeNodePrimitive<string, StructPrimitive<any>, any>;
|
|
47
62
|
|
|
48
63
|
/**
|
|
49
64
|
* Infer the data state type from a TreeNodePrimitive
|
|
50
65
|
*/
|
|
51
66
|
export type InferTreeNodeDataState<T extends AnyTreeNodePrimitive> =
|
|
52
|
-
T extends TreeNodePrimitive<any, infer TData> ? InferState<TData> : never;
|
|
67
|
+
T extends TreeNodePrimitive<any, infer TData, any> ? InferState<TData> : never;
|
|
53
68
|
|
|
54
69
|
/**
|
|
55
70
|
* Infer the type literal from a TreeNodePrimitive
|
|
56
71
|
*/
|
|
57
72
|
export type InferTreeNodeType<T extends AnyTreeNodePrimitive> =
|
|
58
|
-
T extends TreeNodePrimitive<infer TType, any> ? TType : never;
|
|
73
|
+
T extends TreeNodePrimitive<infer TType, any, any> ? TType : never;
|
|
59
74
|
|
|
60
75
|
/**
|
|
61
|
-
* Infer the allowed children from a TreeNodePrimitive
|
|
76
|
+
* Infer the allowed children from a TreeNodePrimitive
|
|
62
77
|
*/
|
|
63
|
-
export type InferTreeNodeChildren<
|
|
78
|
+
export type InferTreeNodeChildren<T> =
|
|
79
|
+
T extends TreeNodePrimitive<any, any, infer TChildren> ? TChildren : never;
|
|
64
80
|
|
|
65
81
|
/**
|
|
66
82
|
* Configuration for a TreeNode primitive
|
|
67
83
|
*/
|
|
68
|
-
export interface TreeNodeConfig<
|
|
84
|
+
export interface TreeNodeConfig<
|
|
85
|
+
TData extends StructPrimitive<any>,
|
|
86
|
+
TChildren extends readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]
|
|
87
|
+
> {
|
|
69
88
|
readonly data: TData;
|
|
70
|
-
readonly children:
|
|
89
|
+
readonly children: TChildren | (() => TChildren);
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
/**
|
|
@@ -75,18 +94,20 @@ export interface TreeNodeConfig<TData extends StructPrimitive<any>> {
|
|
|
75
94
|
*/
|
|
76
95
|
export class TreeNodePrimitive<
|
|
77
96
|
TType extends string,
|
|
78
|
-
TData extends StructPrimitive<any
|
|
97
|
+
TData extends StructPrimitive<any>,
|
|
98
|
+
TChildren extends AnyTreeNodePrimitive = AnyTreeNodePrimitive
|
|
79
99
|
> {
|
|
80
100
|
readonly _tag = "TreeNodePrimitive" as const;
|
|
81
101
|
readonly _Type!: TType;
|
|
82
102
|
readonly _Data!: TData;
|
|
103
|
+
readonly _Children!: TChildren;
|
|
83
104
|
|
|
84
105
|
private readonly _type: TType;
|
|
85
106
|
private readonly _data: TData;
|
|
86
|
-
private readonly _children:
|
|
107
|
+
private readonly _children: TreeNodeChildrenInput;
|
|
87
108
|
private _resolvedChildren: readonly AnyTreeNodePrimitive[] | undefined;
|
|
88
109
|
|
|
89
|
-
constructor(type: TType, config: TreeNodeConfig<TData>) {
|
|
110
|
+
constructor(type: TType, config: TreeNodeConfig<TData, readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]>) {
|
|
90
111
|
this._type = type;
|
|
91
112
|
this._data = config.data;
|
|
92
113
|
this._children = config.children;
|
|
@@ -109,7 +130,7 @@ export class TreeNodePrimitive<
|
|
|
109
130
|
? (this._children as () => readonly AnyTreeNodePrimitive[])()
|
|
110
131
|
: this._children;
|
|
111
132
|
// Replace Self placeholders with this node
|
|
112
|
-
this._resolvedChildren = resolved.map(child => isSelf(child) ? this : child);
|
|
133
|
+
this._resolvedChildren = resolved.map(child => isSelf(child) ? this : child) as readonly AnyTreeNodePrimitive[];
|
|
113
134
|
}
|
|
114
135
|
return this._resolvedChildren;
|
|
115
136
|
}
|
|
@@ -123,10 +144,11 @@ export class TreeNodePrimitive<
|
|
|
123
144
|
/** Creates a new TreeNodePrimitive with the given type and config */
|
|
124
145
|
export const TreeNode = <
|
|
125
146
|
TType extends string,
|
|
126
|
-
TData extends StructPrimitive<any
|
|
147
|
+
TData extends StructPrimitive<any>,
|
|
148
|
+
const TChildren extends readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]
|
|
127
149
|
>(
|
|
128
150
|
type: TType,
|
|
129
|
-
config: TreeNodeConfig<TData>
|
|
130
|
-
): TreeNodePrimitive<TType, TData
|
|
131
|
-
new TreeNodePrimitive(type, config)
|
|
151
|
+
config: TreeNodeConfig<TData, TChildren>
|
|
152
|
+
): TreeNodePrimitive<TType, TData, ResolveChildrenUnion<TChildren, TreeNodePrimitive<TType, TData, any>>> =>
|
|
153
|
+
new TreeNodePrimitive(type, config) as TreeNodePrimitive<TType, TData, ResolveChildrenUnion<TChildren, TreeNodePrimitive<TType, TData, any>>>;
|
|
132
154
|
|
package/src/primitives/Union.ts
CHANGED
|
@@ -7,14 +7,14 @@ import * as Transform from "../Transform";
|
|
|
7
7
|
import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, InferState, InferProxy, InferSnapshot } 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, StructSetInput } 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,25 @@ 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
|
+
* For each variant, uses StructSetInput to make fields with defaults optional.
|
|
36
|
+
*/
|
|
37
|
+
export type UnionSetInput<TVariants extends UnionVariants> = {
|
|
38
|
+
[K in keyof TVariants]: TVariants[K] extends StructPrimitive<infer TFields, any, any>
|
|
39
|
+
? StructSetInput<TFields>
|
|
40
|
+
: InferState<TVariants[K]>;
|
|
41
|
+
}[keyof TVariants];
|
|
42
|
+
|
|
33
43
|
/**
|
|
34
44
|
* Proxy for accessing union variants
|
|
35
45
|
*/
|
|
36
|
-
export interface UnionProxy<TVariants extends UnionVariants,
|
|
46
|
+
export interface UnionProxy<TVariants extends UnionVariants, _TDiscriminator extends string, TDefined extends boolean = false> {
|
|
37
47
|
/** Gets the current union value */
|
|
38
48
|
get(): MaybeUndefined<InferUnionState<TVariants>, TDefined>;
|
|
39
49
|
|
|
40
|
-
/** Sets the entire union value */
|
|
41
|
-
set(value:
|
|
50
|
+
/** Sets the entire union value (applies defaults for variant fields) */
|
|
51
|
+
set(value: UnionSetInput<TVariants>): void;
|
|
42
52
|
|
|
43
53
|
/** Access a specific variant's proxy (assumes the variant is active) */
|
|
44
54
|
as<K extends keyof TVariants>(variant: K): InferProxy<TVariants[K]>;
|
|
@@ -59,12 +69,14 @@ interface UnionPrimitiveSchema<TVariants extends UnionVariants, TDiscriminator e
|
|
|
59
69
|
readonly variants: TVariants;
|
|
60
70
|
}
|
|
61
71
|
|
|
62
|
-
export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator extends string = "type", TDefined extends boolean = false>
|
|
63
|
-
implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TDefined
|
|
72
|
+
export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator extends string = "type", TDefined extends boolean = false, THasDefault extends boolean = false>
|
|
73
|
+
implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TDefined>, TDefined, THasDefault>
|
|
64
74
|
{
|
|
65
75
|
readonly _tag = "UnionPrimitive" as const;
|
|
66
76
|
readonly _State!: InferUnionState<TVariants>;
|
|
67
77
|
readonly _Proxy!: UnionProxy<TVariants, TDiscriminator, TDefined>;
|
|
78
|
+
readonly _TDefined!: TDefined;
|
|
79
|
+
readonly _THasDefault!: THasDefault;
|
|
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,6 +142,17 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
128
142
|
return undefined;
|
|
129
143
|
}
|
|
130
144
|
|
|
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
|
+
|
|
131
156
|
readonly _internal: PrimitiveInternal<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TDefined>> = {
|
|
132
157
|
createProxy: (
|
|
133
158
|
env: ProxyEnvironment.ProxyEnvironment,
|
|
@@ -141,9 +166,11 @@ export class UnionPrimitive<TVariants extends UnionVariants, TDiscriminator exte
|
|
|
141
166
|
const state = env.getState(operationPath) as InferUnionState<TVariants> | undefined;
|
|
142
167
|
return (state ?? defaultValue) as MaybeUndefined<InferUnionState<TVariants>, TDefined>;
|
|
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]> => {
|
|
@@ -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,11 +11,18 @@ 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, TDefined extends boolean = false, THasDefault extends boolean = false> {
|
|
16
21
|
readonly _tag: string;
|
|
17
22
|
readonly _State: TState;
|
|
18
23
|
readonly _Proxy: TProxy;
|
|
24
|
+
readonly _TDefined: TDefined;
|
|
25
|
+
readonly _THasDefault: THasDefault;
|
|
19
26
|
readonly _internal: PrimitiveInternal<TState, TProxy>;
|
|
20
27
|
}
|
|
21
28
|
|
|
@@ -46,17 +53,17 @@ export interface Primitive<TState, TProxy> {
|
|
|
46
53
|
/**
|
|
47
54
|
* Any primitive type - used for generic constraints.
|
|
48
55
|
*/
|
|
49
|
-
export type AnyPrimitive = Primitive<any, any>;
|
|
56
|
+
export type AnyPrimitive = Primitive<any, any, any, any>;
|
|
50
57
|
|
|
51
58
|
/**
|
|
52
59
|
* Infer the state type from a primitive.
|
|
53
60
|
*/
|
|
54
|
-
export type InferState<T> = T extends Primitive<infer S, any> ? S : never;
|
|
61
|
+
export type InferState<T> = T extends Primitive<infer S, any, any, any> ? S : never;
|
|
55
62
|
|
|
56
63
|
/**
|
|
57
64
|
* Infer the proxy type from a primitive.
|
|
58
65
|
*/
|
|
59
|
-
export type InferProxy<T> = T extends Primitive<any, infer P> ? P : never;
|
|
66
|
+
export type InferProxy<T> = T extends Primitive<any, infer P, any, any> ? P : never;
|
|
60
67
|
|
|
61
68
|
/**
|
|
62
69
|
* Helper type to conditionally add undefined based on TDefined.
|
|
@@ -69,9 +76,48 @@ export interface Primitive<TState, TProxy> {
|
|
|
69
76
|
* Infer the snapshot type from a primitive.
|
|
70
77
|
* The snapshot is a readonly, type-safe structure suitable for rendering.
|
|
71
78
|
*/
|
|
72
|
-
export type InferSnapshot<T> = T extends Primitive<any, infer P>
|
|
79
|
+
export type InferSnapshot<T> = T extends Primitive<any, infer P, any, any>
|
|
73
80
|
? P extends { toSnapshot(): infer S } ? S : never
|
|
74
81
|
: never;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract THasDefault from a primitive.
|
|
85
|
+
*/
|
|
86
|
+
export type HasDefault<T> = T extends Primitive<any, any, any, infer H> ? H : false;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extract TDefined from a primitive.
|
|
90
|
+
*/
|
|
91
|
+
export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Determines if a field is required for set() operations.
|
|
95
|
+
* A field is required if: TDefined is true AND THasDefault is false
|
|
96
|
+
*/
|
|
97
|
+
export type IsRequiredForSet<T> = T extends Primitive<any, any, true, false> ? true : false;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Extract keys of fields that are required for set() (required without default).
|
|
101
|
+
*/
|
|
102
|
+
export type RequiredSetKeys<TFields extends Record<string, AnyPrimitive>> = {
|
|
103
|
+
[K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? K : never;
|
|
104
|
+
}[keyof TFields];
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract keys of fields that are optional for set() (has default OR not required).
|
|
108
|
+
*/
|
|
109
|
+
export type OptionalSetKeys<TFields extends Record<string, AnyPrimitive>> = {
|
|
110
|
+
[K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? never : K;
|
|
111
|
+
}[keyof TFields];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Compute the input type for set() operations on a struct.
|
|
115
|
+
* Required fields (required without default) must be provided.
|
|
116
|
+
* Optional fields (has default or not required) can be omitted.
|
|
117
|
+
*/
|
|
118
|
+
export type StructSetInput<TFields extends Record<string, AnyPrimitive>> =
|
|
119
|
+
{ readonly [K in RequiredSetKeys<TFields>]: InferState<TFields[K]> } &
|
|
120
|
+
{ readonly [K in OptionalSetKeys<TFields>]?: InferState<TFields[K]> };
|
|
75
121
|
|
|
76
122
|
// =============================================================================
|
|
77
123
|
// Validation Errors
|
|
@@ -120,3 +166,58 @@ export function isCompatibleOperation(operation: Operation.Operation<any, any, a
|
|
|
120
166
|
return values.some(value => value.kind === operation.kind);
|
|
121
167
|
}
|
|
122
168
|
|
|
169
|
+
// =============================================================================
|
|
170
|
+
// Default Value Utilities
|
|
171
|
+
// =============================================================================
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Applies default values to a partial input, recursively handling nested structs.
|
|
175
|
+
*
|
|
176
|
+
* Uses a two-layer approach:
|
|
177
|
+
* 1. First, get the struct's initial state (which includes struct-level defaults)
|
|
178
|
+
* 2. Then, layer the provided values on top
|
|
179
|
+
* 3. Finally, ensure nested structs are recursively processed
|
|
180
|
+
*
|
|
181
|
+
* @param primitive - The primitive definition containing field information
|
|
182
|
+
* @param value - The partial value provided by the user
|
|
183
|
+
* @returns The value with defaults applied for missing fields
|
|
184
|
+
*/
|
|
185
|
+
export function applyDefaults<T extends AnyPrimitive>(
|
|
186
|
+
primitive: T,
|
|
187
|
+
value: Partial<InferState<T>>
|
|
188
|
+
): InferState<T> {
|
|
189
|
+
// Only structs need default merging
|
|
190
|
+
if (primitive._tag === "StructPrimitive") {
|
|
191
|
+
const structPrimitive = primitive as unknown as {
|
|
192
|
+
fields: Record<string, AnyPrimitive>;
|
|
193
|
+
_internal: { getInitialState: () => Record<string, unknown> | undefined };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Start with the struct's initial state (struct-level default or field defaults)
|
|
197
|
+
const structInitialState = structPrimitive._internal.getInitialState() ?? {};
|
|
198
|
+
|
|
199
|
+
// Layer the provided values on top of initial state
|
|
200
|
+
const result: Record<string, unknown> = { ...structInitialState, ...value };
|
|
201
|
+
|
|
202
|
+
for (const key in structPrimitive.fields) {
|
|
203
|
+
const fieldPrimitive = structPrimitive.fields[key]!;
|
|
204
|
+
|
|
205
|
+
if (result[key] === undefined) {
|
|
206
|
+
// Field still not provided after merging - try individual field default
|
|
207
|
+
const fieldDefault = fieldPrimitive._internal.getInitialState();
|
|
208
|
+
if (fieldDefault !== undefined) {
|
|
209
|
+
result[key] = fieldDefault;
|
|
210
|
+
}
|
|
211
|
+
} else if (fieldPrimitive._tag === "StructPrimitive" && typeof result[key] === "object" && result[key] !== null) {
|
|
212
|
+
// Recursively apply defaults to nested structs
|
|
213
|
+
result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result as InferState<T>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// For non-struct primitives, return the value as-is
|
|
221
|
+
return value as InferState<T>;
|
|
222
|
+
}
|
|
223
|
+
|
package/src/server/errors.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class MimicServerError extends Error {
|
|
|
19
19
|
* Error thrown when a transaction fails validation.
|
|
20
20
|
*/
|
|
21
21
|
export class ValidationError extends MimicServerError {
|
|
22
|
-
readonly _tag = "ValidationError";
|
|
22
|
+
override readonly _tag = "ValidationError";
|
|
23
23
|
readonly transactionId: string;
|
|
24
24
|
|
|
25
25
|
constructor(transactionId: string, message: string) {
|
|
@@ -33,7 +33,7 @@ export class ValidationError extends MimicServerError {
|
|
|
33
33
|
* Error thrown when an operation is invalid for the current schema.
|
|
34
34
|
*/
|
|
35
35
|
export class InvalidOperationError extends MimicServerError {
|
|
36
|
-
readonly _tag = "InvalidOperationError";
|
|
36
|
+
override readonly _tag = "InvalidOperationError";
|
|
37
37
|
readonly operationKind: string;
|
|
38
38
|
readonly path: string;
|
|
39
39
|
|
|
@@ -49,9 +49,9 @@ export class InvalidOperationError extends MimicServerError {
|
|
|
49
49
|
* Error thrown when an operation cannot be applied to the current state.
|
|
50
50
|
*/
|
|
51
51
|
export class StateValidationError extends MimicServerError {
|
|
52
|
-
readonly _tag = "StateValidationError";
|
|
52
|
+
override readonly _tag = "StateValidationError";
|
|
53
53
|
readonly transactionId: string;
|
|
54
|
-
readonly cause?: Error;
|
|
54
|
+
override readonly cause?: Error;
|
|
55
55
|
|
|
56
56
|
constructor(transactionId: string, message: string, cause?: Error) {
|
|
57
57
|
super(`Transaction ${transactionId} cannot be applied to current state: ${message}`);
|
|
@@ -65,7 +65,7 @@ export class StateValidationError extends MimicServerError {
|
|
|
65
65
|
* Error thrown when attempting to apply an empty transaction.
|
|
66
66
|
*/
|
|
67
67
|
export class EmptyTransactionError extends MimicServerError {
|
|
68
|
-
readonly _tag = "EmptyTransactionError";
|
|
68
|
+
override readonly _tag = "EmptyTransactionError";
|
|
69
69
|
readonly transactionId: string;
|
|
70
70
|
|
|
71
71
|
constructor(transactionId: string) {
|
|
@@ -79,7 +79,7 @@ export class EmptyTransactionError extends MimicServerError {
|
|
|
79
79
|
* Error thrown when a duplicate transaction is submitted.
|
|
80
80
|
*/
|
|
81
81
|
export class DuplicateTransactionError extends MimicServerError {
|
|
82
|
-
readonly _tag = "DuplicateTransactionError";
|
|
82
|
+
override readonly _tag = "DuplicateTransactionError";
|
|
83
83
|
readonly transactionId: string;
|
|
84
84
|
|
|
85
85
|
constructor(transactionId: string) {
|