@voidhash/mimic 0.0.1-alpha.1 → 0.0.1-alpha.111

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.
Files changed (63) hide show
  1. package/.turbo/turbo-build.log +51 -0
  2. package/LICENSE.md +663 -0
  3. package/dist/Document-ChuFrTk1.cjs +571 -0
  4. package/dist/Document-CwiAFTIq.mjs +438 -0
  5. package/dist/Document-CwiAFTIq.mjs.map +1 -0
  6. package/dist/Presence-DKKP4v5X.d.cts +91 -0
  7. package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
  8. package/dist/Presence-DdMVKcOv.mjs +110 -0
  9. package/dist/Presence-DdMVKcOv.mjs.map +1 -0
  10. package/dist/Presence-N8u7Eppr.d.mts +91 -0
  11. package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
  12. package/dist/Presence-gWrmGBeu.cjs +126 -0
  13. package/dist/Primitive-CvFVxR8_.d.cts +1175 -0
  14. package/dist/Primitive-CvFVxR8_.d.cts.map +1 -0
  15. package/dist/Primitive-lEhQyGVL.d.mts +1175 -0
  16. package/dist/Primitive-lEhQyGVL.d.mts.map +1 -0
  17. package/dist/chunk-CLMFDpHK.mjs +18 -0
  18. package/dist/client/index.cjs +1456 -0
  19. package/dist/client/index.d.cts +692 -0
  20. package/dist/client/index.d.cts.map +1 -0
  21. package/dist/client/index.d.mts +692 -0
  22. package/dist/client/index.d.mts.map +1 -0
  23. package/dist/client/index.mjs +1413 -0
  24. package/dist/client/index.mjs.map +1 -0
  25. package/dist/index.cjs +2577 -0
  26. package/dist/index.d.cts +143 -0
  27. package/dist/index.d.cts.map +1 -0
  28. package/dist/index.d.mts +143 -0
  29. package/dist/index.d.mts.map +1 -0
  30. package/dist/index.mjs +2526 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/server/index.cjs +191 -0
  33. package/dist/server/index.d.cts +148 -0
  34. package/dist/server/index.d.cts.map +1 -0
  35. package/dist/server/index.d.mts +148 -0
  36. package/dist/server/index.d.mts.map +1 -0
  37. package/dist/server/index.mjs +182 -0
  38. package/dist/server/index.mjs.map +1 -0
  39. package/package.json +25 -13
  40. package/src/EffectSchema.ts +374 -0
  41. package/src/Primitive.ts +3 -0
  42. package/src/client/ClientDocument.ts +1 -1
  43. package/src/client/errors.ts +10 -10
  44. package/src/index.ts +1 -0
  45. package/src/primitives/Array.ts +57 -22
  46. package/src/primitives/Boolean.ts +33 -19
  47. package/src/primitives/Either.ts +379 -0
  48. package/src/primitives/Lazy.ts +16 -2
  49. package/src/primitives/Literal.ts +33 -20
  50. package/src/primitives/Number.ts +39 -26
  51. package/src/primitives/String.ts +40 -25
  52. package/src/primitives/Struct.ts +126 -29
  53. package/src/primitives/Tree.ts +119 -32
  54. package/src/primitives/TreeNode.ts +77 -30
  55. package/src/primitives/Union.ts +56 -29
  56. package/src/primitives/shared.ts +111 -9
  57. package/src/server/errors.ts +6 -6
  58. package/tests/EffectSchema.test.ts +546 -0
  59. package/tests/primitives/Array.test.ts +108 -0
  60. package/tests/primitives/Either.test.ts +707 -0
  61. package/tests/primitives/Struct.test.ts +250 -0
  62. package/tests/primitives/Tree.test.ts +250 -0
  63. package/tsdown.config.ts +1 -1
@@ -1,23 +1,65 @@
1
- import { Effect, Schema } from "effect";
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";
2
+ import type { InferSetInput, InferUpdateInput } from "./shared";
8
3
  import { StructPrimitive } from "./Struct";
9
4
 
5
+ /**
6
+ * Symbol used to identify the Self placeholder
7
+ */
8
+ const TreeNodeSelfSymbol = Symbol.for("TreeNode.Self");
10
9
 
11
10
  /**
12
- * Any TreeNodePrimitive type - used for generic constraints.
11
+ * Branded type for TreeNodeSelf - distinguishable at compile time
12
+ */
13
+ declare const SelfBrand: unique symbol;
14
+ export interface TreeNodeSelfType {
15
+ readonly _tag: "TreeNodeSelf";
16
+ readonly _brand: typeof SelfBrand;
17
+ }
18
+
19
+ /**
20
+ * Special placeholder for self-referential tree nodes.
21
+ * Use this in the children array when a node type can contain itself.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const FolderNode = TreeNode("folder", {
26
+ * data: Struct({ name: String() }),
27
+ * children: [TreeNodeSelf], // Folder can contain other folders
28
+ * });
29
+ * ```
30
+ */
31
+ export const TreeNodeSelf: TreeNodeSelfType = { _tag: "TreeNodeSelf", _symbol: TreeNodeSelfSymbol } as unknown as TreeNodeSelfType;
32
+
33
+ /**
34
+ * Check if a value is the Self placeholder
13
35
  */
14
- export type AnyTreeNodePrimitive = TreeNodePrimitive<string, StructPrimitive<any>, readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])>;
36
+ const isSelf = (value: unknown): boolean => {
37
+ return typeof value === "object" && value !== null && "_symbol" in value && (value as any)._symbol === TreeNodeSelfSymbol;
38
+ };
15
39
 
16
40
  /**
17
- * Resolves children type - handles both array and lazy thunk
41
+ * Type utility to resolve Self placeholders to the actual node type
42
+ */
43
+ type ResolveSelf<T, TSelf extends AnyTreeNodePrimitive> =
44
+ T extends TreeNodeSelfType ? TSelf : T;
45
+
46
+ /**
47
+ * Type utility to resolve all children in a tuple, replacing Self with the node type
48
+ */
49
+ type ResolveChildrenUnion<TChildren, TSelf extends AnyTreeNodePrimitive> =
50
+ TChildren extends readonly (infer U)[]
51
+ ? ResolveSelf<U, TSelf>
52
+ : never;
53
+
54
+ /**
55
+ * The type for children - either a direct array or a lazy function (for self-referential nodes).
56
+ */
57
+ export type TreeNodeChildrenInput = readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[] | (() => readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]);
58
+
59
+ /**
60
+ * Any TreeNodePrimitive type - used for generic constraints.
18
61
  */
19
- export type ResolveChildren<TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])> =
20
- TChildren extends () => readonly AnyTreeNodePrimitive[] ? ReturnType<TChildren> : TChildren;
62
+ export type AnyTreeNodePrimitive = TreeNodePrimitive<string, StructPrimitive<any>, any>;
21
63
 
22
64
  /**
23
65
  * Infer the data state type from a TreeNodePrimitive
@@ -34,18 +76,18 @@ export type InferTreeNodeType<T extends AnyTreeNodePrimitive> =
34
76
  /**
35
77
  * Infer the allowed children from a TreeNodePrimitive
36
78
  */
37
- export type InferTreeNodeChildren<T extends AnyTreeNodePrimitive> =
38
- T extends TreeNodePrimitive<any, any, infer TChildren> ? ResolveChildren<TChildren>[number] : never;
79
+ export type InferTreeNodeChildren<T> =
80
+ T extends TreeNodePrimitive<any, any, infer TChildren> ? TChildren : never;
39
81
 
40
82
  /**
41
83
  * Configuration for a TreeNode primitive
42
84
  */
43
85
  export interface TreeNodeConfig<
44
86
  TData extends StructPrimitive<any>,
45
- TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])
87
+ TChildren extends readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]
46
88
  > {
47
89
  readonly data: TData;
48
- readonly children: TChildren;
90
+ readonly children: TChildren | (() => TChildren);
49
91
  }
50
92
 
51
93
  /**
@@ -54,19 +96,24 @@ export interface TreeNodeConfig<
54
96
  export class TreeNodePrimitive<
55
97
  TType extends string,
56
98
  TData extends StructPrimitive<any>,
57
- TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])
99
+ TChildren extends AnyTreeNodePrimitive = AnyTreeNodePrimitive
58
100
  > {
59
101
  readonly _tag = "TreeNodePrimitive" as const;
60
102
  readonly _Type!: TType;
61
103
  readonly _Data!: TData;
62
104
  readonly _Children!: TChildren;
105
+
106
+ /** For InferSetInput compatibility - delegates to the data struct's set input type */
107
+ readonly TSetInput!: InferSetInput<TData>;
108
+ /** For InferUpdateInput compatibility - delegates to the data struct's update input type */
109
+ readonly TUpdateInput!: InferUpdateInput<TData>;
63
110
 
64
111
  private readonly _type: TType;
65
112
  private readonly _data: TData;
66
- private readonly _children: TChildren;
113
+ private readonly _children: TreeNodeChildrenInput;
67
114
  private _resolvedChildren: readonly AnyTreeNodePrimitive[] | undefined;
68
115
 
69
- constructor(type: TType, config: TreeNodeConfig<TData, TChildren>) {
116
+ constructor(type: TType, config: TreeNodeConfig<TData, readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]>) {
70
117
  this._type = type;
71
118
  this._data = config.data;
72
119
  this._children = config.children;
@@ -82,16 +129,16 @@ export class TreeNodePrimitive<
82
129
  return this._data;
83
130
  }
84
131
 
85
- /** Get resolved children (resolves lazy thunk if needed) */
86
- get children(): ResolveChildren<TChildren> {
132
+ /** Get resolved children (resolves lazy thunk if needed, replaces Self with this node) */
133
+ get children(): readonly AnyTreeNodePrimitive[] {
87
134
  if (this._resolvedChildren === undefined) {
88
- if (typeof this._children === "function") {
89
- this._resolvedChildren = (this._children as () => readonly AnyTreeNodePrimitive[])();
90
- } else {
91
- this._resolvedChildren = this._children as readonly AnyTreeNodePrimitive[];
92
- }
135
+ const resolved = typeof this._children === "function"
136
+ ? (this._children as () => readonly AnyTreeNodePrimitive[])()
137
+ : this._children;
138
+ // Replace Self placeholders with this node
139
+ this._resolvedChildren = resolved.map(child => isSelf(child) ? this : child) as readonly AnyTreeNodePrimitive[];
93
140
  }
94
- return this._resolvedChildren as ResolveChildren<TChildren>;
141
+ return this._resolvedChildren;
95
142
  }
96
143
 
97
144
  /** Check if a child type is allowed */
@@ -104,10 +151,10 @@ export class TreeNodePrimitive<
104
151
  export const TreeNode = <
105
152
  TType extends string,
106
153
  TData extends StructPrimitive<any>,
107
- TChildren extends readonly AnyTreeNodePrimitive[] | (() => readonly AnyTreeNodePrimitive[])
154
+ const TChildren extends readonly (AnyTreeNodePrimitive | TreeNodeSelfType)[]
108
155
  >(
109
156
  type: TType,
110
157
  config: TreeNodeConfig<TData, TChildren>
111
- ): TreeNodePrimitive<TType, TData, TChildren> =>
112
- new TreeNodePrimitive(type, config);
158
+ ): TreeNodePrimitive<TType, TData, ResolveChildrenUnion<TChildren, TreeNodePrimitive<TType, TData, any>>> =>
159
+ new TreeNodePrimitive(type, config) as TreeNodePrimitive<TType, TData, ResolveChildrenUnion<TChildren, TreeNodePrimitive<TType, TData, any>>>;
113
160
 
@@ -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, TDefined extends boolean = false> {
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>, TDefined>;
46
+ get(): MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault>;
39
47
 
40
- /** Sets the entire union value */
41
- set(value: InferUnionState<TVariants>): void;
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 exte
49
57
  }): R | undefined;
50
58
 
51
59
  /** Returns a readonly snapshot of the union for rendering */
52
- toSnapshot(): MaybeUndefined<InferUnionSnapshot<TVariants>, TDefined>;
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", TDefined extends boolean = false>
63
- implements Primitive<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TDefined>>
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, TDefined>;
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: InferUnionState<TVariants>): UnionPrimitive<TVariants, TDiscriminator, true> {
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
- readonly _internal: PrimitiveInternal<InferUnionState<TVariants>, UnionProxy<TVariants, TDiscriminator, TDefined>> = {
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, TDefined> => {
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>, TDefined> => {
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>, TDefined>;
167
+ return (state ?? defaultValue) as MaybeUndefined<InferUnionState<TVariants>, TRequired, THasDefault>;
143
168
  },
144
- set: (value: InferUnionState<TVariants>) => {
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, value)
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>, TDefined> => {
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>, TDefined>;
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>, TDefined>;
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>, TDefined>;
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,
@@ -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,70 @@ 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
+ * Works with both Primitive interface types and types with a TSetInput property (like TreeNodePrimitive).
73
+ */
74
+ export type InferSetInput<T> =
75
+ T extends Primitive<any, any, any, any, infer S, any> ? S :
76
+ T extends { TSetInput: infer S } ? S :
77
+ never;
78
+
79
+ /**
80
+ * Infer the UpdateInput type from a primitive.
81
+ * Works with both Primitive interface types and types with a TUpdateInput property (like TreeNodePrimitive).
82
+ */
83
+ export type InferUpdateInput<T> =
84
+ T extends Primitive<any, any, any, any, any, infer U> ? U :
85
+ T extends { TUpdateInput: infer U } ? U :
86
+ never;
60
87
 
61
88
  /**
62
- * Helper type to conditionally add undefined based on TDefined.
63
- * When TDefined is true, the value is guaranteed to be defined (via required() or default()).
64
- * When TDefined is false, the value may be undefined.
89
+ * Helper type to conditionally add undefined based on TRequired and THasDefault.
90
+ * When TRequired is false and THasDefault is false, the value may be undefined.
91
+ * Otherwise, the value is guaranteed to be defined.
92
+ */
93
+ export type MaybeUndefined<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends false ? THasDefault extends false ? Optional<T> : T : T;
94
+
95
+ export type Optional<T> = T | undefined;
96
+
97
+ /**
98
+ * Helper type to conditionally add undefined based on TRequired and THasDefault.
99
+ * When TRequired is true and THasDefault is false, the value must be provided.
100
+ * Otherwise, the value may be undefined.
65
101
  */
66
- export type MaybeUndefined<T, TDefined extends boolean> = TDefined extends true ? T : T | undefined;
102
+ export type NeedsValue<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends true ? THasDefault extends false ? T : Optional<T> : Optional<T>;
67
103
 
68
104
  /**
69
105
  * Infer the snapshot type from a primitive.
70
106
  * The snapshot is a readonly, type-safe structure suitable for rendering.
71
107
  */
72
- export type InferSnapshot<T> = T extends Primitive<any, infer P>
108
+ export type InferSnapshot<T> = T extends Primitive<any, infer P, any, any>
73
109
  ? P extends { toSnapshot(): infer S } ? S : never
74
110
  : never;
111
+
112
+ /**
113
+ * Extract THasDefault from a primitive.
114
+ */
115
+ export type HasDefault<T> = T extends Primitive<any, any, any, infer H> ? H : false;
116
+
117
+ /**
118
+ * Extract TDefined from a primitive.
119
+ */
120
+ export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;
121
+
75
122
 
76
123
  // =============================================================================
77
124
  // Validation Errors
@@ -120,3 +167,58 @@ export function isCompatibleOperation(operation: Operation.Operation<any, any, a
120
167
  return values.some(value => value.kind === operation.kind);
121
168
  }
122
169
 
170
+ // =============================================================================
171
+ // Default Value Utilities
172
+ // =============================================================================
173
+
174
+ /**
175
+ * Applies default values to a partial input, recursively handling nested structs.
176
+ *
177
+ * Uses a two-layer approach:
178
+ * 1. First, get the struct's initial state (which includes struct-level defaults)
179
+ * 2. Then, layer the provided values on top
180
+ * 3. Finally, ensure nested structs are recursively processed
181
+ *
182
+ * @param primitive - The primitive definition containing field information
183
+ * @param value - The partial value provided by the user
184
+ * @returns The value with defaults applied for missing fields
185
+ */
186
+ export function applyDefaults<T extends AnyPrimitive>(
187
+ primitive: T,
188
+ value: Partial<InferState<T>>
189
+ ): InferState<T> {
190
+ // Only structs need default merging
191
+ if (primitive._tag === "StructPrimitive") {
192
+ const structPrimitive = primitive as unknown as {
193
+ fields: Record<string, AnyPrimitive>;
194
+ _internal: { getInitialState: () => Record<string, unknown> | undefined };
195
+ };
196
+
197
+ // Start with the struct's initial state (struct-level default or field defaults)
198
+ const structInitialState = structPrimitive._internal.getInitialState() ?? {};
199
+
200
+ // Layer the provided values on top of initial state
201
+ const result: Record<string, unknown> = { ...structInitialState, ...value };
202
+
203
+ for (const key in structPrimitive.fields) {
204
+ const fieldPrimitive = structPrimitive.fields[key]!;
205
+
206
+ if (result[key] === undefined) {
207
+ // Field still not provided after merging - try individual field default
208
+ const fieldDefault = fieldPrimitive._internal.getInitialState();
209
+ if (fieldDefault !== undefined) {
210
+ result[key] = fieldDefault;
211
+ }
212
+ } else if (fieldPrimitive._tag === "StructPrimitive" && typeof result[key] === "object" && result[key] !== null) {
213
+ // Recursively apply defaults to nested structs
214
+ result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);
215
+ }
216
+ }
217
+
218
+ return result as InferState<T>;
219
+ }
220
+
221
+ // For non-struct primitives, return the value as-is
222
+ return value as InferState<T>;
223
+ }
224
+
@@ -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) {