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

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
@@ -5,9 +5,10 @@ import * as OperationPath from "../OperationPath";
5
5
  import * as ProxyEnvironment from "../ProxyEnvironment";
6
6
  import * as Transform from "../Transform";
7
7
  import * as FractionalIndex from "../FractionalIndex";
8
- import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot } from "../Primitive";
8
+ import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot, InferSetInput } from "../Primitive";
9
9
  import { ValidationError } from "../Primitive";
10
- import { runValidators } from "./shared";
10
+ import { runValidators, applyDefaults } from "./shared";
11
+ import { StructPrimitive, StructSetInput } from "./Struct";
11
12
 
12
13
 
13
14
  /**
@@ -46,15 +47,28 @@ export interface ArrayEntrySnapshot<TElement extends AnyPrimitive> {
46
47
  */
47
48
  export type ArraySnapshot<TElement extends AnyPrimitive> = readonly ArrayEntrySnapshot<TElement>[];
48
49
 
50
+ /**
51
+ * Compute the input type for array element values.
52
+ * Uses StructSetInput directly for struct elements so that:
53
+ * - Fields that are required AND have no default must be provided
54
+ * - Fields that are optional OR have defaults can be omitted
55
+ *
56
+ * For non-struct elements, falls back to InferSetInput.
57
+ */
58
+ export type ArrayElementSetInput<TElement extends AnyPrimitive> =
59
+ TElement extends StructPrimitive<infer TFields, any, any>
60
+ ? StructSetInput<TFields>
61
+ : InferSetInput<TElement>;
62
+
49
63
  export interface ArrayProxy<TElement extends AnyPrimitive> {
50
64
  /** Gets the current array entries (sorted by position) */
51
65
  get(): ArrayState<TElement>;
52
- /** Replaces the entire array with new values (generates new IDs and positions) */
53
- set(values: readonly InferState<TElement>[]): void;
54
- /** Appends a value to the end of the array */
55
- push(value: InferState<TElement>): void;
56
- /** Inserts a value at the specified visual index */
57
- insertAt(index: number, value: InferState<TElement>): void;
66
+ /** Replaces the entire array with new values (generates new IDs and positions, applies defaults) */
67
+ set(values: readonly ArrayElementSetInput<TElement>[]): void;
68
+ /** Appends a value to the end of the array (applies defaults for struct elements) */
69
+ push(value: ArrayElementSetInput<TElement>): void;
70
+ /** Inserts a value at the specified visual index (applies defaults for struct elements) */
71
+ insertAt(index: number, value: ArrayElementSetInput<TElement>): void;
58
72
  /** Removes the element with the specified ID */
59
73
  remove(id: string): void;
60
74
  /** Moves an element to a new visual index */
@@ -77,12 +91,22 @@ interface ArrayPrimitiveSchema<TElement extends AnyPrimitive> {
77
91
  readonly validators: readonly Validator<ArrayState<TElement>>[];
78
92
  }
79
93
 
80
- export class ArrayPrimitive<TElement extends AnyPrimitive>
81
- implements Primitive<ArrayState<TElement>, ArrayProxy<TElement>>
94
+ /** Input type for array set() - an array of element set inputs */
95
+ export type ArraySetInput<TElement extends AnyPrimitive> = readonly ArrayElementSetInput<TElement>[];
96
+
97
+ /** Input type for array update() - same as set() for arrays */
98
+ export type ArrayUpdateInput<TElement extends AnyPrimitive> = readonly ArrayElementSetInput<TElement>[];
99
+
100
+ export class ArrayPrimitive<TElement extends AnyPrimitive, TRequired extends boolean = false, THasDefault extends boolean = false>
101
+ implements Primitive<ArrayState<TElement>, ArrayProxy<TElement>, TRequired, THasDefault, ArraySetInput<TElement>, ArrayUpdateInput<TElement>>
82
102
  {
83
103
  readonly _tag = "ArrayPrimitive" as const;
84
104
  readonly _State!: ArrayState<TElement>;
85
105
  readonly _Proxy!: ArrayProxy<TElement>;
106
+ readonly _TRequired!: TRequired;
107
+ readonly _THasDefault!: THasDefault;
108
+ readonly TSetInput!: ArraySetInput<TElement>;
109
+ readonly TUpdateInput!: ArrayUpdateInput<TElement>;
86
110
 
87
111
  private readonly _schema: ArrayPrimitiveSchema<TElement>;
88
112
 
@@ -118,7 +142,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
118
142
  }
119
143
 
120
144
  /** Mark this array as required */
121
- required(): ArrayPrimitive<TElement> {
145
+ required(): ArrayPrimitive<TElement, true, THasDefault> {
122
146
  return new ArrayPrimitive({
123
147
  ...this._schema,
124
148
  required: true,
@@ -126,7 +150,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
126
150
  }
127
151
 
128
152
  /** Set a default value for this array */
129
- default(defaultValue: ArrayState<TElement>): ArrayPrimitive<TElement> {
153
+ default(defaultValue: ArrayState<TElement>): ArrayPrimitive<TElement, TRequired, true> {
130
154
  return new ArrayPrimitive({
131
155
  ...this._schema,
132
156
  defaultValue,
@@ -139,7 +163,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
139
163
  }
140
164
 
141
165
  /** Add a custom validation rule */
142
- refine(fn: (value: ArrayState<TElement>) => boolean, message: string): ArrayPrimitive<TElement> {
166
+ refine(fn: (value: ArrayState<TElement>) => boolean, message: string): ArrayPrimitive<TElement, TRequired, THasDefault> {
143
167
  return new ArrayPrimitive({
144
168
  ...this._schema,
145
169
  validators: [...this._schema.validators, { validate: fn, message }],
@@ -147,7 +171,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
147
171
  }
148
172
 
149
173
  /** Minimum array length */
150
- minLength(length: number): ArrayPrimitive<TElement> {
174
+ minLength(length: number): ArrayPrimitive<TElement, TRequired, THasDefault> {
151
175
  return this.refine(
152
176
  (v) => v.length >= length,
153
177
  `Array must have at least ${length} elements`
@@ -155,7 +179,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
155
179
  }
156
180
 
157
181
  /** Maximum array length */
158
- maxLength(length: number): ArrayPrimitive<TElement> {
182
+ maxLength(length: number): ArrayPrimitive<TElement, TRequired, THasDefault> {
159
183
  return this.refine(
160
184
  (v) => v.length <= length,
161
185
  `Array must have at most ${length} elements`
@@ -173,12 +197,17 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
173
197
  return sortByPos(state);
174
198
  };
175
199
 
200
+ // Helper to apply defaults for element values
201
+ const applyElementDefaults = (value: ArrayElementSetInput<TElement>): InferState<TElement> => {
202
+ return applyDefaults(elementPrimitive, value as Partial<InferState<TElement>>) as InferState<TElement>;
203
+ };
204
+
176
205
  return {
177
206
  get: (): ArrayState<TElement> => {
178
207
  return getCurrentState();
179
208
  },
180
209
 
181
- set: (values: readonly InferState<TElement>[]) => {
210
+ set: (values: readonly ArrayElementSetInput<TElement>[]) => {
182
211
  // Generate entries with new IDs and sequential positions
183
212
  const entries: ArrayEntry<InferState<TElement>>[] = [];
184
213
  let prevPos: string | null = null;
@@ -186,7 +215,9 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
186
215
  for (const value of values) {
187
216
  const id = env.generateId();
188
217
  const pos = generatePosBetween(prevPos, null);
189
- entries.push({ id, pos, value });
218
+ // Apply defaults to element value
219
+ const mergedValue = applyElementDefaults(value);
220
+ entries.push({ id, pos, value: mergedValue });
190
221
  prevPos = pos;
191
222
  }
192
223
 
@@ -195,27 +226,31 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
195
226
  );
196
227
  },
197
228
 
198
- push: (value: InferState<TElement>) => {
229
+ push: (value: ArrayElementSetInput<TElement>) => {
199
230
  const sorted = getCurrentState();
200
231
  const lastPos = sorted.length > 0 ? sorted[sorted.length - 1]!.pos : null;
201
232
  const id = env.generateId();
202
233
  const pos = generatePosBetween(lastPos, null);
234
+ // Apply defaults to element value
235
+ const mergedValue = applyElementDefaults(value);
203
236
 
204
237
  env.addOperation(
205
- Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value })
238
+ Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value: mergedValue })
206
239
  );
207
240
  },
208
241
 
209
- insertAt: (index: number, value: InferState<TElement>) => {
242
+ insertAt: (index: number, value: ArrayElementSetInput<TElement>) => {
210
243
  const sorted = getCurrentState();
211
244
  const leftPos = index > 0 && sorted[index - 1] ? sorted[index - 1]!.pos : null;
212
245
  const rightPos = index < sorted.length && sorted[index] ? sorted[index]!.pos : null;
213
246
 
214
247
  const id = env.generateId();
215
248
  const pos = generatePosBetween(leftPos, rightPos);
249
+ // Apply defaults to element value
250
+ const mergedValue = applyElementDefaults(value);
216
251
 
217
252
  env.addOperation(
218
- Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value })
253
+ Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value: mergedValue })
219
254
  );
220
255
  },
221
256
 
@@ -452,6 +487,6 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
452
487
  }
453
488
 
454
489
  /** Creates a new ArrayPrimitive with the given element type */
455
- export const Array = <TElement extends AnyPrimitive>(element: TElement): ArrayPrimitive<TElement> =>
490
+ export const Array = <TElement extends AnyPrimitive>(element: TElement): ArrayPrimitive<TElement, false, false> =>
456
491
  new ArrayPrimitive({ required: false, defaultValue: undefined, element, validators: [] });
457
492
 
@@ -4,17 +4,22 @@ 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, Validator } from "./shared";
7
+ import type { Primitive, PrimitiveInternal, MaybeUndefined, Validator, NeedsValue } from "./shared";
8
8
  import { runValidators, isCompatibleOperation, ValidationError } from "./shared";
9
9
 
10
10
 
11
- export interface BooleanProxy<TDefined extends boolean = false> {
11
+ type InferSetInput<TRequired extends boolean = false, THasDefault extends boolean = false> = NeedsValue<boolean, TRequired, THasDefault>
12
+ type InferUpdateInput<TRequired extends boolean = false, THasDefault extends boolean = false> = NeedsValue<boolean, TRequired, THasDefault>
13
+
14
+ export interface BooleanProxy<TRequired extends boolean = false, THasDefault extends boolean = false> {
12
15
  /** Gets the current boolean value */
13
- get(): MaybeUndefined<boolean, TDefined>;
16
+ get(): MaybeUndefined<boolean, TRequired, THasDefault>;
14
17
  /** Sets the boolean value, generating a boolean.set operation */
15
- set(value: boolean): void;
18
+ set(value: InferSetInput<TRequired, THasDefault>): void;
19
+ /** This is the same as set. Updates the boolean value, generating a boolean.set operation */
20
+ update(value: InferUpdateInput<TRequired, THasDefault>): void;
16
21
  /** Returns a readonly snapshot of the boolean value for rendering */
17
- toSnapshot(): MaybeUndefined<boolean, TDefined>;
22
+ toSnapshot(): MaybeUndefined<boolean, TRequired, THasDefault>;
18
23
  }
19
24
 
20
25
  interface BooleanPrimitiveSchema {
@@ -23,10 +28,14 @@ interface BooleanPrimitiveSchema {
23
28
  readonly validators: readonly Validator<boolean>[];
24
29
  }
25
30
 
26
- export class BooleanPrimitive<TDefined extends boolean = false> implements Primitive<boolean, BooleanProxy<TDefined>> {
31
+ export class BooleanPrimitive<TRequired extends boolean = false, THasDefault extends boolean = false> implements Primitive<boolean, BooleanProxy<TRequired, THasDefault>, TRequired, THasDefault, InferSetInput<TRequired, THasDefault>, InferUpdateInput<TRequired, THasDefault>> {
27
32
  readonly _tag = "BooleanPrimitive" as const;
28
33
  readonly _State!: boolean;
29
- readonly _Proxy!: BooleanProxy<TDefined>;
34
+ readonly _Proxy!: BooleanProxy<TRequired, THasDefault>;
35
+ readonly _TRequired!: TRequired;
36
+ readonly _THasDefault!: THasDefault;
37
+ readonly TUpdateInput!: InferUpdateInput<TRequired, THasDefault>;
38
+ readonly TSetInput!: InferSetInput<TRequired, THasDefault>;
30
39
 
31
40
  private readonly _schema: BooleanPrimitiveSchema;
32
41
 
@@ -44,7 +53,7 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
44
53
  }
45
54
 
46
55
  /** Mark this boolean as required */
47
- required(): BooleanPrimitive<true> {
56
+ required(): BooleanPrimitive<true, THasDefault> {
48
57
  return new BooleanPrimitive({
49
58
  ...this._schema,
50
59
  required: true,
@@ -52,7 +61,7 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
52
61
  }
53
62
 
54
63
  /** Set a default value for this boolean */
55
- default(defaultValue: boolean): BooleanPrimitive<true> {
64
+ default(defaultValue: boolean): BooleanPrimitive<TRequired, true> {
56
65
  return new BooleanPrimitive({
57
66
  ...this._schema,
58
67
  defaultValue,
@@ -60,34 +69,39 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
60
69
  }
61
70
 
62
71
  /** Add a custom validation rule */
63
- refine(fn: (value: boolean) => boolean, message: string): BooleanPrimitive<TDefined> {
72
+ refine(fn: (value: boolean) => boolean, message: string): BooleanPrimitive<TRequired, THasDefault> {
64
73
  return new BooleanPrimitive({
65
74
  ...this._schema,
66
75
  validators: [...this._schema.validators, { validate: fn, message }],
67
76
  });
68
77
  }
69
78
 
70
- readonly _internal: PrimitiveInternal<boolean, BooleanProxy<TDefined>> = {
71
- createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): BooleanProxy<TDefined> => {
79
+ readonly _internal: PrimitiveInternal<boolean, BooleanProxy<TRequired, THasDefault>> = {
80
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): BooleanProxy<TRequired, THasDefault> => {
72
81
  const defaultValue = this._schema.defaultValue;
73
82
  return {
74
- get: (): MaybeUndefined<boolean, TDefined> => {
83
+ get: (): MaybeUndefined<boolean, TRequired, THasDefault> => {
75
84
  const state = env.getState(operationPath) as boolean | undefined;
76
- return (state ?? defaultValue) as MaybeUndefined<boolean, TDefined>;
85
+ return (state ?? defaultValue) as MaybeUndefined<boolean, TRequired, THasDefault>;
86
+ },
87
+ set: (value: InferSetInput<TRequired, THasDefault>) => {
88
+ env.addOperation(
89
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
90
+ );
77
91
  },
78
- set: (value: boolean) => {
92
+ update: (value: InferUpdateInput<TRequired, THasDefault>) => {
79
93
  env.addOperation(
80
94
  Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
81
95
  );
82
96
  },
83
- toSnapshot: (): MaybeUndefined<boolean, TDefined> => {
97
+ toSnapshot: (): MaybeUndefined<boolean, TRequired, THasDefault> => {
84
98
  const state = env.getState(operationPath) as boolean | undefined;
85
- return (state ?? defaultValue) as MaybeUndefined<boolean, TDefined>;
99
+ return (state ?? defaultValue) as MaybeUndefined<boolean, TRequired, THasDefault>;
86
100
  },
87
101
  };
88
102
  },
89
103
 
90
- applyOperation: (state: boolean | undefined, operation: Operation.Operation<any, any, any>): boolean => {
104
+ applyOperation: (_state: boolean | undefined, operation: Operation.Operation<any, any, any>): boolean => {
91
105
  if (operation.kind !== "boolean.set") {
92
106
  throw new ValidationError(`BooleanPrimitive cannot apply operation of kind: ${operation.kind}`);
93
107
  }
@@ -123,6 +137,6 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
123
137
  }
124
138
 
125
139
  /** Creates a new BooleanPrimitive */
126
- export const Boolean = (): BooleanPrimitive<false> =>
140
+ export const Boolean = (): BooleanPrimitive<false, false> =>
127
141
  new BooleanPrimitive({ required: false, defaultValue: undefined, validators: [] });
128
142
 
@@ -0,0 +1,379 @@
1
+ import { 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 { Primitive, PrimitiveInternal, MaybeUndefined, InferState, NeedsValue } from "./shared";
8
+ import { ValidationError } from "./shared";
9
+ import { StringPrimitive } from "./String";
10
+ import { NumberPrimitive } from "./Number";
11
+ import { BooleanPrimitive } from "./Boolean";
12
+ import { LiteralPrimitive, LiteralValue } from "./Literal";
13
+
14
+ // =============================================================================
15
+ // Either Primitive - Simple Type Union
16
+ // =============================================================================
17
+
18
+ type InferSetInput<TVariants extends readonly ScalarPrimitive[], TRequired extends boolean = false, THasDefault extends boolean = false> = NeedsValue<InferEitherState<TVariants>, TRequired, THasDefault>
19
+ type InferUpdateInput<TVariants extends readonly ScalarPrimitive[], TRequired extends boolean = false, THasDefault extends boolean = false> = NeedsValue<InferEitherState<TVariants>, TRequired, THasDefault>
20
+
21
+ /**
22
+ * Scalar primitives that can be used as variants in Either
23
+ */
24
+ export type ScalarPrimitive =
25
+ | StringPrimitive<any, any>
26
+ | NumberPrimitive<any, any>
27
+ | BooleanPrimitive<any, any>
28
+ | LiteralPrimitive<any, any, any>;
29
+
30
+ /**
31
+ * Infer the union state type from a tuple of scalar primitives
32
+ */
33
+ export type InferEitherState<TVariants extends readonly ScalarPrimitive[]> =
34
+ InferState<TVariants[number]>;
35
+
36
+ /**
37
+ * Infer the union snapshot type from a tuple of scalar primitives
38
+ */
39
+ export type InferEitherSnapshot<TVariants extends readonly ScalarPrimitive[]> =
40
+ InferState<TVariants[number]>;
41
+
42
+ /**
43
+ * Match handlers for Either - optional handlers for each scalar type
44
+ */
45
+ export interface EitherMatchHandlers<R> {
46
+ string?: (value: string) => R;
47
+ number?: (value: number) => R;
48
+ boolean?: (value: boolean) => R;
49
+ literal?: (value: LiteralValue) => R;
50
+ }
51
+
52
+ /**
53
+ * Proxy for accessing Either values
54
+ */
55
+ export interface EitherProxy<TVariants extends readonly ScalarPrimitive[], TRequired extends boolean = false, THasDefault extends boolean = false> {
56
+ /** Gets the current value */
57
+ get(): MaybeUndefined<InferEitherState<TVariants>, TRequired, THasDefault>;
58
+
59
+ /** Sets the value to any of the allowed variant types */
60
+ set(value: InferSetInput<TVariants, TRequired, THasDefault>): void;
61
+
62
+ /** This is the same as set. Updates the value, generating an either.set operation */
63
+ update(value: InferUpdateInput<TVariants, TRequired, THasDefault>): void;
64
+
65
+ /** Pattern match on the value type */
66
+ match<R>(handlers: EitherMatchHandlers<R>): R | undefined;
67
+
68
+ /** Returns a readonly snapshot of the value for rendering */
69
+ toSnapshot(): MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault>;
70
+ }
71
+
72
+ interface EitherPrimitiveSchema<TVariants extends readonly ScalarPrimitive[]> {
73
+ readonly required: boolean;
74
+ readonly defaultValue: InferEitherState<TVariants> | undefined;
75
+ readonly variants: TVariants;
76
+ }
77
+
78
+ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TRequired extends boolean = false, THasDefault extends boolean = false>
79
+ implements Primitive<InferEitherState<TVariants>, EitherProxy<TVariants, TRequired, THasDefault>, TRequired, THasDefault, InferSetInput<TVariants, TRequired, THasDefault>, InferUpdateInput<TVariants, TRequired, THasDefault>>
80
+ {
81
+ readonly _tag = "EitherPrimitive" as const;
82
+ readonly _State!: InferEitherState<TVariants>;
83
+ readonly _Proxy!: EitherProxy<TVariants, TRequired, THasDefault>;
84
+ readonly _TRequired!: TRequired;
85
+ readonly _THasDefault!: THasDefault;
86
+ readonly TUpdateInput!: InferUpdateInput<TVariants, TRequired, THasDefault>;
87
+ readonly TSetInput!: InferSetInput<TVariants, TRequired, THasDefault>;
88
+
89
+ private readonly _schema: EitherPrimitiveSchema<TVariants>;
90
+
91
+ private readonly _opDefinitions = {
92
+ set: OperationDefinition.make({
93
+ kind: "either.set" as const,
94
+ payload: Schema.Unknown,
95
+ target: Schema.Unknown,
96
+ apply: (payload) => payload,
97
+ }),
98
+ };
99
+
100
+ constructor(schema: EitherPrimitiveSchema<TVariants>) {
101
+ this._schema = schema;
102
+ }
103
+
104
+ /** Mark this either as required */
105
+ required(): EitherPrimitive<TVariants, true, THasDefault> {
106
+ return new EitherPrimitive({
107
+ ...this._schema,
108
+ required: true,
109
+ });
110
+ }
111
+
112
+ /** Set a default value for this either */
113
+ default(defaultValue: InferEitherState<TVariants>): EitherPrimitive<TVariants, TRequired, true> {
114
+ return new EitherPrimitive({
115
+ ...this._schema,
116
+ defaultValue,
117
+ });
118
+ }
119
+
120
+ /** Get the variants */
121
+ get variants(): TVariants {
122
+ return this._schema.variants;
123
+ }
124
+
125
+ /**
126
+ * Determine the type category of a value based on the variants
127
+ */
128
+ private _getValueType(value: unknown): "string" | "number" | "boolean" | "literal" | undefined {
129
+ const valueType = typeof value;
130
+
131
+ // Check for literal matches first (they take priority)
132
+ for (const variant of this._schema.variants) {
133
+ if (variant._tag === "LiteralPrimitive") {
134
+ const literalVariant = variant as LiteralPrimitive<any, any, any>;
135
+ if (value === literalVariant.literal) {
136
+ return "literal";
137
+ }
138
+ }
139
+ }
140
+
141
+ // Check for type matches
142
+ if (valueType === "string") {
143
+ for (const variant of this._schema.variants) {
144
+ if (variant._tag === "StringPrimitive") {
145
+ return "string";
146
+ }
147
+ }
148
+ }
149
+
150
+ if (valueType === "number") {
151
+ for (const variant of this._schema.variants) {
152
+ if (variant._tag === "NumberPrimitive") {
153
+ return "number";
154
+ }
155
+ }
156
+ }
157
+
158
+ if (valueType === "boolean") {
159
+ for (const variant of this._schema.variants) {
160
+ if (variant._tag === "BooleanPrimitive") {
161
+ return "boolean";
162
+ }
163
+ }
164
+ }
165
+
166
+ return undefined;
167
+ }
168
+
169
+ /**
170
+ * Find the matching variant for a value.
171
+ * For literals, matches exact value. For other types, matches by typeof.
172
+ */
173
+ private _findMatchingVariant(value: unknown): ScalarPrimitive | undefined {
174
+ const valueType = typeof value;
175
+
176
+ // Check for literal matches first (they take priority)
177
+ for (const variant of this._schema.variants) {
178
+ if (variant._tag === "LiteralPrimitive") {
179
+ const literalVariant = variant as LiteralPrimitive<any, any, any>;
180
+ if (value === literalVariant.literal) {
181
+ return variant;
182
+ }
183
+ }
184
+ }
185
+
186
+ // Check for type matches
187
+ if (valueType === "string") {
188
+ for (const variant of this._schema.variants) {
189
+ if (variant._tag === "StringPrimitive") {
190
+ return variant;
191
+ }
192
+ }
193
+ }
194
+
195
+ if (valueType === "number") {
196
+ for (const variant of this._schema.variants) {
197
+ if (variant._tag === "NumberPrimitive") {
198
+ return variant;
199
+ }
200
+ }
201
+ }
202
+
203
+ if (valueType === "boolean") {
204
+ for (const variant of this._schema.variants) {
205
+ if (variant._tag === "BooleanPrimitive") {
206
+ return variant;
207
+ }
208
+ }
209
+ }
210
+
211
+ return undefined;
212
+ }
213
+
214
+ /**
215
+ * Get the operation kind for a variant
216
+ */
217
+ private _getVariantOperationKind(variant: ScalarPrimitive): string {
218
+ switch (variant._tag) {
219
+ case "StringPrimitive":
220
+ return "string.set";
221
+ case "NumberPrimitive":
222
+ return "number.set";
223
+ case "BooleanPrimitive":
224
+ return "boolean.set";
225
+ case "LiteralPrimitive":
226
+ return "literal.set";
227
+ default:
228
+ return "unknown.set";
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Validate a value against the matching variant, including running its validators.
234
+ * Throws ValidationError if the value doesn't match any variant or fails validation.
235
+ */
236
+ private _validateAndApplyToVariant(value: unknown, path: OperationPath.OperationPath): void {
237
+ const matchingVariant = this._findMatchingVariant(value);
238
+
239
+ if (!matchingVariant) {
240
+ const allowedTypes = this._schema.variants.map((v) => v._tag).join(", ");
241
+ throw new ValidationError(
242
+ `EitherPrimitive.set requires a value matching one of: ${allowedTypes}, got: ${typeof value}`
243
+ );
244
+ }
245
+
246
+ // Create a synthetic operation for the variant's applyOperation
247
+ const variantOpKind = this._getVariantOperationKind(matchingVariant);
248
+ const syntheticOp: Operation.Operation<any, any, any> = {
249
+ kind: variantOpKind,
250
+ path: path,
251
+ payload: value,
252
+ };
253
+
254
+ // Delegate to the variant's applyOperation which runs its validators
255
+ // This will throw ValidationError if validation fails
256
+ matchingVariant._internal.applyOperation(undefined, syntheticOp);
257
+ }
258
+
259
+ readonly _internal: PrimitiveInternal<InferEitherState<TVariants>, EitherProxy<TVariants, TRequired, THasDefault>> = {
260
+ createProxy: (
261
+ env: ProxyEnvironment.ProxyEnvironment,
262
+ operationPath: OperationPath.OperationPath
263
+ ): EitherProxy<TVariants, TRequired, THasDefault> => {
264
+ const defaultValue = this._schema.defaultValue;
265
+
266
+ return {
267
+ get: (): MaybeUndefined<InferEitherState<TVariants>, TRequired, THasDefault> => {
268
+ const state = env.getState(operationPath) as InferEitherState<TVariants> | undefined;
269
+ return (state ?? defaultValue) as MaybeUndefined<InferEitherState<TVariants>, TRequired, THasDefault>;
270
+ },
271
+ set: (value: InferSetInput<TVariants, TRequired, THasDefault>) => {
272
+ env.addOperation(
273
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
274
+ );
275
+ },
276
+ update: (value: InferUpdateInput<TVariants, TRequired, THasDefault>) => {
277
+ env.addOperation(
278
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
279
+ );
280
+ },
281
+ match: <R,>(handlers: EitherMatchHandlers<R>): R | undefined => {
282
+ const currentState = env.getState(operationPath) as InferEitherState<TVariants> | undefined;
283
+ const effectiveState = currentState ?? defaultValue;
284
+ if (effectiveState === undefined) return undefined;
285
+
286
+ const valueType = this._getValueType(effectiveState);
287
+ if (!valueType) return undefined;
288
+
289
+ switch (valueType) {
290
+ case "string":
291
+ return handlers.string?.(effectiveState as string);
292
+ case "number":
293
+ return handlers.number?.(effectiveState as number);
294
+ case "boolean":
295
+ return handlers.boolean?.(effectiveState as boolean);
296
+ case "literal":
297
+ return handlers.literal?.(effectiveState as LiteralValue);
298
+ default:
299
+ return undefined;
300
+ }
301
+ },
302
+ toSnapshot: (): MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault> => {
303
+ const state = env.getState(operationPath) as InferEitherState<TVariants> | undefined;
304
+ return (state ?? defaultValue) as MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault>;
305
+ },
306
+ };
307
+ },
308
+
309
+ applyOperation: (
310
+ _state: InferEitherState<TVariants> | undefined,
311
+ operation: Operation.Operation<any, any, any>
312
+ ): InferEitherState<TVariants> => {
313
+ if (operation.kind !== "either.set") {
314
+ throw new ValidationError(`EitherPrimitive cannot apply operation of kind: ${operation.kind}`);
315
+ }
316
+
317
+ const payload = operation.payload;
318
+
319
+ // Validate that the payload matches one of the variant types and passes its validators
320
+ this._validateAndApplyToVariant(payload, operation.path);
321
+
322
+ return payload as InferEitherState<TVariants>;
323
+ },
324
+
325
+ getInitialState: (): InferEitherState<TVariants> | undefined => {
326
+ return this._schema.defaultValue;
327
+ },
328
+
329
+ transformOperation: (
330
+ clientOp: Operation.Operation<any, any, any>,
331
+ serverOp: Operation.Operation<any, any, any>
332
+ ): Transform.TransformResult => {
333
+ // If paths don't overlap, no transformation needed
334
+ if (!OperationPath.pathsOverlap(clientOp.path, serverOp.path)) {
335
+ return { type: "transformed", operation: clientOp };
336
+ }
337
+
338
+ // For same path, client wins (last-write-wins)
339
+ return { type: "transformed", operation: clientOp };
340
+ },
341
+ };
342
+ }
343
+
344
+ /**
345
+ * Creates a new EitherPrimitive with the given scalar variant types.
346
+ * Validators defined on the variants are applied when validating values.
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * // String or number
351
+ * const value = Either(String(), Number());
352
+ *
353
+ * // String, number, or boolean
354
+ * const status = Either(String(), Number(), Boolean()).default("pending");
355
+ *
356
+ * // With literal types
357
+ * const mode = Either(Literal("auto"), Literal("manual"), Number());
358
+ *
359
+ * // With validators - validates string length and number range
360
+ * const constrained = Either(
361
+ * String().min(2).max(50),
362
+ * Number().max(255)
363
+ * );
364
+ * ```
365
+ */
366
+ export function Either<TVariants extends readonly ScalarPrimitive[]>(
367
+ ...variants: TVariants
368
+ ): EitherPrimitive<TVariants, false, false> {
369
+ if (variants.length === 0) {
370
+ throw new ValidationError("Either requires at least one variant");
371
+ }
372
+
373
+ return new EitherPrimitive({
374
+ required: false,
375
+ defaultValue: undefined,
376
+ variants,
377
+ });
378
+ }
379
+