@voidhash/mimic 0.0.1-alpha.7 → 0.0.1-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.turbo/turbo-build.log +43 -15
  2. package/dist/Document-ChuFrTk1.cjs +571 -0
  3. package/dist/Document-CwiAFTIq.mjs +438 -0
  4. package/dist/Document-CwiAFTIq.mjs.map +1 -0
  5. package/dist/Presence-DKKP4v5X.d.cts +91 -0
  6. package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
  7. package/dist/Presence-DdMVKcOv.mjs +110 -0
  8. package/dist/Presence-DdMVKcOv.mjs.map +1 -0
  9. package/dist/Presence-N8u7Eppr.d.mts +91 -0
  10. package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
  11. package/dist/Presence-gWrmGBeu.cjs +126 -0
  12. package/dist/Primitive-BK7kfHJZ.d.cts +1165 -0
  13. package/dist/Primitive-BK7kfHJZ.d.cts.map +1 -0
  14. package/dist/Primitive-D1kdB6za.d.mts +1165 -0
  15. package/dist/Primitive-D1kdB6za.d.mts.map +1 -0
  16. package/dist/client/index.cjs +1456 -0
  17. package/dist/client/index.d.cts +692 -0
  18. package/dist/client/index.d.cts.map +1 -0
  19. package/dist/client/index.d.mts +692 -0
  20. package/dist/client/index.d.mts.map +1 -0
  21. package/dist/client/index.mjs +1413 -0
  22. package/dist/client/index.mjs.map +1 -0
  23. package/dist/index.cjs +224 -765
  24. package/dist/index.d.cts +5 -1152
  25. package/dist/index.d.cts.map +1 -1
  26. package/dist/index.d.mts +5 -1152
  27. package/dist/index.d.mts.map +1 -1
  28. package/dist/index.mjs +69 -569
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/server/index.cjs +191 -0
  31. package/dist/server/index.d.cts +148 -0
  32. package/dist/server/index.d.cts.map +1 -0
  33. package/dist/server/index.d.mts +148 -0
  34. package/dist/server/index.d.mts.map +1 -0
  35. package/dist/server/index.mjs +182 -0
  36. package/dist/server/index.mjs.map +1 -0
  37. package/package.json +16 -4
  38. package/src/primitives/Array.ts +25 -14
  39. package/src/primitives/Boolean.ts +29 -17
  40. package/src/primitives/Either.ts +30 -17
  41. package/src/primitives/Lazy.ts +16 -2
  42. package/src/primitives/Literal.ts +29 -18
  43. package/src/primitives/Number.ts +35 -24
  44. package/src/primitives/String.ts +36 -23
  45. package/src/primitives/Struct.ts +74 -26
  46. package/src/primitives/Tree.ts +30 -14
  47. package/src/primitives/Union.ts +21 -21
  48. package/src/primitives/shared.ts +27 -34
  49. package/tests/primitives/Array.test.ts +108 -0
  50. package/tests/primitives/Struct.test.ts +2 -2
  51. package/tests/primitives/Tree.test.ts +128 -0
  52. package/tsdown.config.ts +1 -1
  53. /package/dist/{chunk-C6wwvPpM.mjs → chunk-CLMFDpHK.mjs} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["Document.make","_transactionOrder: string[]","Transaction.isEmpty"],"sources":["../../src/server/ServerDocument.ts","../../src/server/errors.ts"],"sourcesContent":["import * as Document from \"../Document\";\nimport * as Transaction from \"../Transaction\";\nimport type * as Primitive from \"../Primitive\";\n\n// =============================================================================\n// Server Message Types (matching client's Transport expectations)\n// =============================================================================\n\n/**\n * Message sent when broadcasting a committed transaction.\n */\nexport interface TransactionMessage {\n readonly type: \"transaction\";\n readonly transaction: Transaction.Transaction;\n /** Server-assigned version number for ordering */\n readonly version: number;\n}\n\n/**\n * Message sent when a transaction is rejected.\n */\nexport interface ErrorMessage {\n readonly type: \"error\";\n readonly transactionId: string;\n readonly reason: string;\n}\n\n/**\n * Message sent as a full state snapshot.\n */\nexport interface SnapshotMessage {\n readonly type: \"snapshot\";\n readonly state: unknown;\n readonly version: number;\n}\n\n/**\n * Union of all server messages that can be broadcast.\n */\nexport type ServerMessage = TransactionMessage | ErrorMessage | SnapshotMessage;\n\n// =============================================================================\n// Submit Result Types\n// =============================================================================\n\n/**\n * Result of submitting a transaction to the server.\n */\nexport type SubmitResult =\n | { readonly success: true; readonly version: number }\n | { readonly success: false; readonly reason: string };\n\n// =============================================================================\n// Server Document Types\n// =============================================================================\n\n/**\n * Options for creating a ServerDocument.\n */\nexport interface ServerDocumentOptions<TSchema extends Primitive.AnyPrimitive> {\n /** The schema defining the document structure */\n readonly schema: TSchema;\n /** Initial state (optional, will use schema defaults if not provided) */\n readonly initialState?: Primitive.InferState<TSchema>;\n /** Initial version number (optional, defaults to 0) */\n readonly initialVersion?: number;\n /** Called when a transaction is successfully applied and should be broadcast */\n readonly onBroadcast: (message: TransactionMessage) => void;\n /** Called when a transaction is rejected (optional, for logging/metrics) */\n readonly onRejection?: (transactionId: string, reason: string) => void;\n /** Maximum number of processed transaction IDs to track for deduplication */\n readonly maxTransactionHistory?: number;\n}\n\n/**\n * A ServerDocument maintains the authoritative state and processes client transactions.\n */\nexport interface ServerDocument<TSchema extends Primitive.AnyPrimitive> {\n /** The schema defining this document's structure */\n readonly schema: TSchema;\n\n /** Returns the current authoritative state */\n get(): Primitive.InferState<TSchema> | undefined;\n\n /** Returns the current version number */\n getVersion(): number;\n\n /**\n * Submits a transaction for processing.\n * Validates and applies the transaction if valid, or rejects it with a reason.\n * @param transaction - The transaction to process\n * @returns SubmitResult indicating success with version or failure with reason\n */\n submit(transaction: Transaction.Transaction): SubmitResult;\n\n /**\n * Returns a snapshot of the current state and version.\n * Used to initialize new clients or resync after drift.\n */\n getSnapshot(): SnapshotMessage;\n\n /**\n * Checks if a transaction has already been processed.\n * @param transactionId - The transaction ID to check\n */\n hasProcessed(transactionId: string): boolean;\n}\n\n// =============================================================================\n// Server Document Implementation\n// =============================================================================\n\n/**\n * Creates a new ServerDocument for the given schema.\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n options: ServerDocumentOptions<TSchema>\n): ServerDocument<TSchema> => {\n const {\n schema,\n initialState,\n initialVersion = 0,\n onBroadcast,\n onRejection,\n maxTransactionHistory = 1000,\n } = options;\n\n // ==========================================================================\n // Internal State\n // ==========================================================================\n\n // The authoritative document\n let _document = Document.make(schema, { initial: initialState });\n\n // Current version number (incremented on each successful transaction)\n let _version = initialVersion;\n\n // Track processed transaction IDs for deduplication\n const _processedTransactions = new Set<string>();\n const _transactionOrder: string[] = [];\n\n // ==========================================================================\n // Helper Functions\n // ==========================================================================\n\n /**\n * Records a transaction as processed, maintaining the history limit.\n */\n const recordTransaction = (transactionId: string): void => {\n _processedTransactions.add(transactionId);\n _transactionOrder.push(transactionId);\n\n // Evict oldest transactions if over limit\n while (_transactionOrder.length > maxTransactionHistory) {\n const oldest = _transactionOrder.shift();\n if (oldest) {\n _processedTransactions.delete(oldest);\n }\n }\n };\n\n /**\n * Validates that the transaction can be applied to the current state.\n * Creates a temporary document and attempts to apply the operations.\n */\n const validateTransaction = (\n transaction: Transaction.Transaction\n ): { valid: true } | { valid: false; reason: string } => {\n // Check for empty transaction\n if (Transaction.isEmpty(transaction)) {\n return { valid: false, reason: \"Transaction is empty\" };\n }\n\n // Check for duplicate transaction\n if (_processedTransactions.has(transaction.id)) {\n return { valid: false, reason: \"Transaction has already been processed\" };\n }\n\n // Create a temporary document with current state to test the operations\n const currentState = _document.get();\n const tempDoc = Document.make(schema, { initial: currentState });\n\n try {\n // Attempt to apply all operations\n tempDoc.apply(transaction.ops);\n return { valid: true };\n } catch (error) {\n // Operations failed to apply\n const message = error instanceof Error ? error.message : String(error);\n return { valid: false, reason: message };\n }\n };\n\n // ==========================================================================\n // Public API\n // ==========================================================================\n\n const serverDocument: ServerDocument<TSchema> = {\n schema,\n\n get: (): Primitive.InferState<TSchema> | undefined => {\n return _document.get();\n },\n\n getVersion: (): number => {\n return _version;\n },\n\n submit: (transaction: Transaction.Transaction): SubmitResult => {\n // Validate the transaction\n const validation = validateTransaction(transaction);\n\n if (!validation.valid) {\n // Notify rejection callback if provided\n onRejection?.(transaction.id, validation.reason);\n\n return {\n success: false,\n reason: validation.reason,\n };\n }\n\n // Apply the transaction to the authoritative state\n try {\n _document.apply(transaction.ops);\n } catch (error) {\n // This shouldn't happen since we validated, but handle gracefully\n const reason = error instanceof Error ? error.message : String(error);\n onRejection?.(transaction.id, reason);\n return { success: false, reason };\n }\n\n // Increment version\n _version += 1;\n\n // Record as processed\n recordTransaction(transaction.id);\n\n // Broadcast the confirmed transaction\n const message: TransactionMessage = {\n type: \"transaction\",\n transaction,\n version: _version,\n };\n onBroadcast(message);\n\n return {\n success: true,\n version: _version,\n };\n },\n\n getSnapshot: (): SnapshotMessage => {\n return {\n type: \"snapshot\",\n state: _document.get(),\n version: _version,\n };\n },\n\n hasProcessed: (transactionId: string): boolean => {\n return _processedTransactions.has(transactionId);\n },\n };\n\n return serverDocument;\n};\n","import type * as Transaction from \"../Transaction\";\n\n// =============================================================================\n// Server Errors\n// =============================================================================\n\n/**\n * Base error class for all mimic-server errors.\n */\nexport class MimicServerError extends Error {\n readonly _tag: string = \"MimicServerError\";\n constructor(message: string) {\n super(message);\n this.name = \"MimicServerError\";\n }\n}\n\n/**\n * Error thrown when a transaction fails validation.\n */\nexport class ValidationError extends MimicServerError {\n override readonly _tag = \"ValidationError\";\n readonly transactionId: string;\n\n constructor(transactionId: string, message: string) {\n super(`Transaction ${transactionId} validation failed: ${message}`);\n this.name = \"ValidationError\";\n this.transactionId = transactionId;\n }\n}\n\n/**\n * Error thrown when an operation is invalid for the current schema.\n */\nexport class InvalidOperationError extends MimicServerError {\n override readonly _tag = \"InvalidOperationError\";\n readonly operationKind: string;\n readonly path: string;\n\n constructor(operationKind: string, path: string, message: string) {\n super(`Invalid operation ${operationKind} at path \"${path}\": ${message}`);\n this.name = \"InvalidOperationError\";\n this.operationKind = operationKind;\n this.path = path;\n }\n}\n\n/**\n * Error thrown when an operation cannot be applied to the current state.\n */\nexport class StateValidationError extends MimicServerError {\n override readonly _tag = \"StateValidationError\";\n readonly transactionId: string;\n override readonly cause?: Error;\n\n constructor(transactionId: string, message: string, cause?: Error) {\n super(`Transaction ${transactionId} cannot be applied to current state: ${message}`);\n this.name = \"StateValidationError\";\n this.transactionId = transactionId;\n this.cause = cause;\n }\n}\n\n/**\n * Error thrown when attempting to apply an empty transaction.\n */\nexport class EmptyTransactionError extends MimicServerError {\n override readonly _tag = \"EmptyTransactionError\";\n readonly transactionId: string;\n\n constructor(transactionId: string) {\n super(`Transaction ${transactionId} is empty and cannot be applied`);\n this.name = \"EmptyTransactionError\";\n this.transactionId = transactionId;\n }\n}\n\n/**\n * Error thrown when a duplicate transaction is submitted.\n */\nexport class DuplicateTransactionError extends MimicServerError {\n override readonly _tag = \"DuplicateTransactionError\";\n readonly transactionId: string;\n\n constructor(transactionId: string) {\n super(`Transaction ${transactionId} has already been processed`);\n this.name = \"DuplicateTransactionError\";\n this.transactionId = transactionId;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidhash/mimic",
3
- "version": "0.0.1-alpha.7",
3
+ "version": "0.0.1-alpha.8",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,9 +9,21 @@
9
9
  },
10
10
  "main": "./src/index.ts",
11
11
  "exports": {
12
- ".": "./src/index.ts",
13
- "./server": "./src/server/index.ts",
14
- "./client": "./src/client/index.ts"
12
+ ".": {
13
+ "import": "./dist/index.mjs",
14
+ "types": "./dist/index.d.mts",
15
+ "require": "./dist/index.cjs"
16
+ },
17
+ "./server": {
18
+ "import": "./dist/server/index.mjs",
19
+ "types": "./dist/server/index.d.mts",
20
+ "require": "./dist/server/index.cjs"
21
+ },
22
+ "./client": {
23
+ "import": "./dist/client/index.mjs",
24
+ "types": "./dist/client/index.d.mts",
25
+ "require": "./dist/client/index.cjs"
26
+ }
15
27
  },
16
28
  "devDependencies": {
17
29
  "@effect/vitest": "^0.27.0",
@@ -5,10 +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, applyDefaults, StructSetInput } from "./shared";
11
- import type { StructPrimitive } from "./Struct";
10
+ import { runValidators, applyDefaults } from "./shared";
11
+ import { StructPrimitive, StructSetInput } from "./Struct";
12
12
 
13
13
 
14
14
  /**
@@ -49,13 +49,16 @@ export type ArraySnapshot<TElement extends AnyPrimitive> = readonly ArrayEntrySn
49
49
 
50
50
  /**
51
51
  * Compute the input type for array element values.
52
- * If the element is a struct, uses StructSetInput (partial with defaults).
53
- * Otherwise uses the full state type.
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.
54
57
  */
55
- export type ArrayElementSetInput<TElement extends AnyPrimitive> =
58
+ export type ArrayElementSetInput<TElement extends AnyPrimitive> =
56
59
  TElement extends StructPrimitive<infer TFields, any, any>
57
60
  ? StructSetInput<TFields>
58
- : InferState<TElement>;
61
+ : InferSetInput<TElement>;
59
62
 
60
63
  export interface ArrayProxy<TElement extends AnyPrimitive> {
61
64
  /** Gets the current array entries (sorted by position) */
@@ -88,14 +91,22 @@ interface ArrayPrimitiveSchema<TElement extends AnyPrimitive> {
88
91
  readonly validators: readonly Validator<ArrayState<TElement>>[];
89
92
  }
90
93
 
91
- export class ArrayPrimitive<TElement extends AnyPrimitive, TDefined extends boolean = false, THasDefault extends boolean = false>
92
- implements Primitive<ArrayState<TElement>, ArrayProxy<TElement>, TDefined, THasDefault>
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>>
93
102
  {
94
103
  readonly _tag = "ArrayPrimitive" as const;
95
104
  readonly _State!: ArrayState<TElement>;
96
105
  readonly _Proxy!: ArrayProxy<TElement>;
97
- readonly _TDefined!: TDefined;
106
+ readonly _TRequired!: TRequired;
98
107
  readonly _THasDefault!: THasDefault;
108
+ readonly TSetInput!: ArraySetInput<TElement>;
109
+ readonly TUpdateInput!: ArrayUpdateInput<TElement>;
99
110
 
100
111
  private readonly _schema: ArrayPrimitiveSchema<TElement>;
101
112
 
@@ -139,7 +150,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive, TDefined extends bool
139
150
  }
140
151
 
141
152
  /** Set a default value for this array */
142
- default(defaultValue: ArrayState<TElement>): ArrayPrimitive<TElement, true, true> {
153
+ default(defaultValue: ArrayState<TElement>): ArrayPrimitive<TElement, TRequired, true> {
143
154
  return new ArrayPrimitive({
144
155
  ...this._schema,
145
156
  defaultValue,
@@ -152,7 +163,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive, TDefined extends bool
152
163
  }
153
164
 
154
165
  /** Add a custom validation rule */
155
- refine(fn: (value: ArrayState<TElement>) => boolean, message: string): ArrayPrimitive<TElement, TDefined, THasDefault> {
166
+ refine(fn: (value: ArrayState<TElement>) => boolean, message: string): ArrayPrimitive<TElement, TRequired, THasDefault> {
156
167
  return new ArrayPrimitive({
157
168
  ...this._schema,
158
169
  validators: [...this._schema.validators, { validate: fn, message }],
@@ -160,7 +171,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive, TDefined extends bool
160
171
  }
161
172
 
162
173
  /** Minimum array length */
163
- minLength(length: number): ArrayPrimitive<TElement, TDefined, THasDefault> {
174
+ minLength(length: number): ArrayPrimitive<TElement, TRequired, THasDefault> {
164
175
  return this.refine(
165
176
  (v) => v.length >= length,
166
177
  `Array must have at least ${length} elements`
@@ -168,7 +179,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive, TDefined extends bool
168
179
  }
169
180
 
170
181
  /** Maximum array length */
171
- maxLength(length: number): ArrayPrimitive<TElement, TDefined, THasDefault> {
182
+ maxLength(length: number): ArrayPrimitive<TElement, TRequired, THasDefault> {
172
183
  return this.refine(
173
184
  (v) => v.length <= length,
174
185
  `Array must have at most ${length} elements`
@@ -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,12 +28,14 @@ interface BooleanPrimitiveSchema {
23
28
  readonly validators: readonly Validator<boolean>[];
24
29
  }
25
30
 
26
- export class BooleanPrimitive<TDefined extends boolean = false, THasDefault extends boolean = false> implements Primitive<boolean, BooleanProxy<TDefined>, TDefined, THasDefault> {
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>;
30
- readonly _TDefined!: TDefined;
34
+ readonly _Proxy!: BooleanProxy<TRequired, THasDefault>;
35
+ readonly _TRequired!: TRequired;
31
36
  readonly _THasDefault!: THasDefault;
37
+ readonly TUpdateInput!: InferUpdateInput<TRequired, THasDefault>;
38
+ readonly TSetInput!: InferSetInput<TRequired, THasDefault>;
32
39
 
33
40
  private readonly _schema: BooleanPrimitiveSchema;
34
41
 
@@ -54,7 +61,7 @@ export class BooleanPrimitive<TDefined extends boolean = false, THasDefault exte
54
61
  }
55
62
 
56
63
  /** Set a default value for this boolean */
57
- default(defaultValue: boolean): BooleanPrimitive<true, true> {
64
+ default(defaultValue: boolean): BooleanPrimitive<TRequired, true> {
58
65
  return new BooleanPrimitive({
59
66
  ...this._schema,
60
67
  defaultValue,
@@ -62,29 +69,34 @@ export class BooleanPrimitive<TDefined extends boolean = false, THasDefault exte
62
69
  }
63
70
 
64
71
  /** Add a custom validation rule */
65
- refine(fn: (value: boolean) => boolean, message: string): BooleanPrimitive<TDefined, THasDefault> {
72
+ refine(fn: (value: boolean) => boolean, message: string): BooleanPrimitive<TRequired, THasDefault> {
66
73
  return new BooleanPrimitive({
67
74
  ...this._schema,
68
75
  validators: [...this._schema.validators, { validate: fn, message }],
69
76
  });
70
77
  }
71
78
 
72
- readonly _internal: PrimitiveInternal<boolean, BooleanProxy<TDefined>> = {
73
- 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> => {
74
81
  const defaultValue = this._schema.defaultValue;
75
82
  return {
76
- get: (): MaybeUndefined<boolean, TDefined> => {
83
+ get: (): MaybeUndefined<boolean, TRequired, THasDefault> => {
77
84
  const state = env.getState(operationPath) as boolean | undefined;
78
- 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
+ );
79
91
  },
80
- set: (value: boolean) => {
92
+ update: (value: InferUpdateInput<TRequired, THasDefault>) => {
81
93
  env.addOperation(
82
94
  Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
83
95
  );
84
96
  },
85
- toSnapshot: (): MaybeUndefined<boolean, TDefined> => {
97
+ toSnapshot: (): MaybeUndefined<boolean, TRequired, THasDefault> => {
86
98
  const state = env.getState(operationPath) as boolean | undefined;
87
- return (state ?? defaultValue) as MaybeUndefined<boolean, TDefined>;
99
+ return (state ?? defaultValue) as MaybeUndefined<boolean, TRequired, THasDefault>;
88
100
  },
89
101
  };
90
102
  },
@@ -4,7 +4,7 @@ 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, InferState } from "./shared";
7
+ import type { Primitive, PrimitiveInternal, MaybeUndefined, InferState, NeedsValue } from "./shared";
8
8
  import { ValidationError } from "./shared";
9
9
  import { StringPrimitive } from "./String";
10
10
  import { NumberPrimitive } from "./Number";
@@ -15,6 +15,9 @@ import { LiteralPrimitive, LiteralValue } from "./Literal";
15
15
  // Either Primitive - Simple Type Union
16
16
  // =============================================================================
17
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
+
18
21
  /**
19
22
  * Scalar primitives that can be used as variants in Either
20
23
  */
@@ -49,18 +52,21 @@ export interface EitherMatchHandlers<R> {
49
52
  /**
50
53
  * Proxy for accessing Either values
51
54
  */
52
- export interface EitherProxy<TVariants extends readonly ScalarPrimitive[], TDefined extends boolean = false> {
55
+ export interface EitherProxy<TVariants extends readonly ScalarPrimitive[], TRequired extends boolean = false, THasDefault extends boolean = false> {
53
56
  /** Gets the current value */
54
- get(): MaybeUndefined<InferEitherState<TVariants>, TDefined>;
57
+ get(): MaybeUndefined<InferEitherState<TVariants>, TRequired, THasDefault>;
55
58
 
56
59
  /** Sets the value to any of the allowed variant types */
57
- set(value: InferEitherState<TVariants>): void;
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;
58
64
 
59
65
  /** Pattern match on the value type */
60
66
  match<R>(handlers: EitherMatchHandlers<R>): R | undefined;
61
67
 
62
68
  /** Returns a readonly snapshot of the value for rendering */
63
- toSnapshot(): MaybeUndefined<InferEitherSnapshot<TVariants>, TDefined>;
69
+ toSnapshot(): MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault>;
64
70
  }
65
71
 
66
72
  interface EitherPrimitiveSchema<TVariants extends readonly ScalarPrimitive[]> {
@@ -69,14 +75,16 @@ interface EitherPrimitiveSchema<TVariants extends readonly ScalarPrimitive[]> {
69
75
  readonly variants: TVariants;
70
76
  }
71
77
 
72
- export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefined extends boolean = false, THasDefault extends boolean = false>
73
- implements Primitive<InferEitherState<TVariants>, EitherProxy<TVariants, TDefined>, TDefined, THasDefault>
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>>
74
80
  {
75
81
  readonly _tag = "EitherPrimitive" as const;
76
82
  readonly _State!: InferEitherState<TVariants>;
77
- readonly _Proxy!: EitherProxy<TVariants, TDefined>;
78
- readonly _TDefined!: TDefined;
83
+ readonly _Proxy!: EitherProxy<TVariants, TRequired, THasDefault>;
84
+ readonly _TRequired!: TRequired;
79
85
  readonly _THasDefault!: THasDefault;
86
+ readonly TUpdateInput!: InferUpdateInput<TVariants, TRequired, THasDefault>;
87
+ readonly TSetInput!: InferSetInput<TVariants, TRequired, THasDefault>;
80
88
 
81
89
  private readonly _schema: EitherPrimitiveSchema<TVariants>;
82
90
 
@@ -102,7 +110,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
102
110
  }
103
111
 
104
112
  /** Set a default value for this either */
105
- default(defaultValue: InferEitherState<TVariants>): EitherPrimitive<TVariants, true, true> {
113
+ default(defaultValue: InferEitherState<TVariants>): EitherPrimitive<TVariants, TRequired, true> {
106
114
  return new EitherPrimitive({
107
115
  ...this._schema,
108
116
  defaultValue,
@@ -248,19 +256,24 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
248
256
  matchingVariant._internal.applyOperation(undefined, syntheticOp);
249
257
  }
250
258
 
251
- readonly _internal: PrimitiveInternal<InferEitherState<TVariants>, EitherProxy<TVariants, TDefined>> = {
259
+ readonly _internal: PrimitiveInternal<InferEitherState<TVariants>, EitherProxy<TVariants, TRequired, THasDefault>> = {
252
260
  createProxy: (
253
261
  env: ProxyEnvironment.ProxyEnvironment,
254
262
  operationPath: OperationPath.OperationPath
255
- ): EitherProxy<TVariants, TDefined> => {
263
+ ): EitherProxy<TVariants, TRequired, THasDefault> => {
256
264
  const defaultValue = this._schema.defaultValue;
257
265
 
258
266
  return {
259
- get: (): MaybeUndefined<InferEitherState<TVariants>, TDefined> => {
267
+ get: (): MaybeUndefined<InferEitherState<TVariants>, TRequired, THasDefault> => {
260
268
  const state = env.getState(operationPath) as InferEitherState<TVariants> | undefined;
261
- return (state ?? defaultValue) as MaybeUndefined<InferEitherState<TVariants>, TDefined>;
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
+ );
262
275
  },
263
- set: (value: InferEitherState<TVariants>) => {
276
+ update: (value: InferUpdateInput<TVariants, TRequired, THasDefault>) => {
264
277
  env.addOperation(
265
278
  Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
266
279
  );
@@ -286,9 +299,9 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
286
299
  return undefined;
287
300
  }
288
301
  },
289
- toSnapshot: (): MaybeUndefined<InferEitherSnapshot<TVariants>, TDefined> => {
302
+ toSnapshot: (): MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault> => {
290
303
  const state = env.getState(operationPath) as InferEitherState<TVariants> | undefined;
291
- return (state ?? defaultValue) as MaybeUndefined<InferEitherSnapshot<TVariants>, TDefined>;
304
+ return (state ?? defaultValue) as MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault>;
292
305
  },
293
306
  };
294
307
  },
@@ -4,10 +4,20 @@ 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, AnyPrimitive, InferState, InferProxy, InferSnapshot } from "../Primitive";
7
+ import type { Primitive, PrimitiveInternal, AnyPrimitive, InferState, InferProxy, InferSnapshot, InferSetInput, InferUpdateInput } from "../Primitive";
8
8
  import { ValidationError } from "../Primitive";
9
9
  import { runValidators } from "./shared";
10
10
 
11
+ /**
12
+ * Type to infer SetInput from a lazy thunk
13
+ */
14
+ export type InferLazySetInput<T extends () => AnyPrimitive> = InferSetInput<ReturnType<T>>;
15
+
16
+ /**
17
+ * Type to infer UpdateInput from a lazy thunk
18
+ */
19
+ export type InferLazyUpdateInput<T extends () => AnyPrimitive> = InferUpdateInput<ReturnType<T>>;
20
+
11
21
 
12
22
  /**
13
23
  * Type to infer state from a lazy thunk
@@ -25,11 +35,15 @@ export type InferLazyProxy<T extends () => AnyPrimitive> = InferProxy<ReturnType
25
35
  export type InferLazySnapshot<T extends () => AnyPrimitive> = InferSnapshot<ReturnType<T>>;
26
36
 
27
37
  export class LazyPrimitive<TThunk extends () => AnyPrimitive>
28
- implements Primitive<InferLazyState<TThunk>, InferLazyProxy<TThunk>>
38
+ implements Primitive<InferLazyState<TThunk>, InferLazyProxy<TThunk>, false, false, InferLazySetInput<TThunk>, InferLazyUpdateInput<TThunk>>
29
39
  {
30
40
  readonly _tag = "LazyPrimitive" as const;
31
41
  readonly _State!: InferLazyState<TThunk>;
32
42
  readonly _Proxy!: InferLazyProxy<TThunk>;
43
+ readonly _TRequired!: false;
44
+ readonly _THasDefault!: false;
45
+ readonly TSetInput!: InferLazySetInput<TThunk>;
46
+ readonly TUpdateInput!: InferLazyUpdateInput<TThunk>;
33
47
 
34
48
  private readonly _thunk: TThunk;
35
49
  private _resolved: ReturnType<TThunk> | undefined;
@@ -4,21 +4,25 @@ 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, Validator } from "../Primitive";
8
- import { ValidationError } from "../Primitive";
9
- import { runValidators, isCompatibleOperation } from "./shared";
7
+ import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, NeedsValue } from "./shared";
8
+ import { ValidationError, runValidators, isCompatibleOperation } from "./shared";
10
9
 
11
10
 
12
11
  /** Valid literal types */
13
12
  export type LiteralValue = string | number | boolean | null;
14
13
 
15
- export interface LiteralProxy<T extends LiteralValue, TDefined extends boolean = false> {
14
+ type InferSetInput<T extends LiteralValue, TRequired extends boolean = false, THasDefault extends boolean = false> = NeedsValue<T, TRequired, THasDefault>
15
+ type InferUpdateInput<T extends LiteralValue, TRequired extends boolean = false, THasDefault extends boolean = false> = NeedsValue<T, TRequired, THasDefault>
16
+
17
+ export interface LiteralProxy<T extends LiteralValue, TRequired extends boolean = false, THasDefault extends boolean = false> {
16
18
  /** Gets the current literal value */
17
- get(): MaybeUndefined<T, TDefined>;
19
+ get(): MaybeUndefined<T, TRequired, THasDefault>;
18
20
  /** Sets the literal value (must match the exact literal type) */
19
- set(value: T): void;
21
+ set(value: InferSetInput<T, TRequired, THasDefault>): void;
22
+ /** This is the same as set. Updates the literal value, generating a literal.set operation */
23
+ update(value: InferUpdateInput<T, TRequired, THasDefault>): void;
20
24
  /** Returns a readonly snapshot of the literal value for rendering */
21
- toSnapshot(): MaybeUndefined<T, TDefined>;
25
+ toSnapshot(): MaybeUndefined<T, TRequired, THasDefault>;
22
26
  }
23
27
 
24
28
  interface LiteralPrimitiveSchema<T extends LiteralValue> {
@@ -27,12 +31,14 @@ interface LiteralPrimitiveSchema<T extends LiteralValue> {
27
31
  readonly literal: T;
28
32
  }
29
33
 
30
- export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean = false, THasDefault extends boolean = false> implements Primitive<T, LiteralProxy<T, TDefined>, TDefined, THasDefault> {
34
+ export class LiteralPrimitive<T extends LiteralValue, TRequired extends boolean = false, THasDefault extends boolean = false> implements Primitive<T, LiteralProxy<T, TRequired, THasDefault>, TRequired, THasDefault, InferSetInput<T, TRequired, THasDefault>, InferUpdateInput<T, TRequired, THasDefault>> {
31
35
  readonly _tag = "LiteralPrimitive" as const;
32
36
  readonly _State!: T;
33
- readonly _Proxy!: LiteralProxy<T, TDefined>;
34
- readonly _TDefined!: TDefined;
37
+ readonly _Proxy!: LiteralProxy<T, TRequired, THasDefault>;
38
+ readonly _TRequired!: TRequired;
35
39
  readonly _THasDefault!: THasDefault;
40
+ readonly TUpdateInput!: InferUpdateInput<T, TRequired, THasDefault>;
41
+ readonly TSetInput!: InferSetInput<T, TRequired, THasDefault>;
36
42
 
37
43
  private readonly _schema: LiteralPrimitiveSchema<T>;
38
44
 
@@ -58,7 +64,7 @@ export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean =
58
64
  }
59
65
 
60
66
  /** Set a default value for this literal */
61
- default(defaultValue: T): LiteralPrimitive<T, true, true> {
67
+ default(defaultValue: T): LiteralPrimitive<T, TRequired, true> {
62
68
  return new LiteralPrimitive({
63
69
  ...this._schema,
64
70
  defaultValue,
@@ -70,22 +76,27 @@ export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean =
70
76
  return this._schema.literal;
71
77
  }
72
78
 
73
- readonly _internal: PrimitiveInternal<T, LiteralProxy<T, TDefined>> = {
74
- createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): LiteralProxy<T, TDefined> => {
79
+ readonly _internal: PrimitiveInternal<T, LiteralProxy<T, TRequired, THasDefault>> = {
80
+ createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): LiteralProxy<T, TRequired, THasDefault> => {
75
81
  const defaultValue = this._schema.defaultValue;
76
82
  return {
77
- get: (): MaybeUndefined<T, TDefined> => {
83
+ get: (): MaybeUndefined<T, TRequired, THasDefault> => {
78
84
  const state = env.getState(operationPath) as T | undefined;
79
- return (state ?? defaultValue) as MaybeUndefined<T, TDefined>;
85
+ return (state ?? defaultValue) as MaybeUndefined<T, TRequired, THasDefault>;
86
+ },
87
+ set: (value: InferSetInput<T, TRequired, THasDefault>) => {
88
+ env.addOperation(
89
+ Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
90
+ );
80
91
  },
81
- set: (value: T) => {
92
+ update: (value: InferUpdateInput<T, TRequired, THasDefault>) => {
82
93
  env.addOperation(
83
94
  Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
84
95
  );
85
96
  },
86
- toSnapshot: (): MaybeUndefined<T, TDefined> => {
97
+ toSnapshot: (): MaybeUndefined<T, TRequired, THasDefault> => {
87
98
  const state = env.getState(operationPath) as T | undefined;
88
- return (state ?? defaultValue) as MaybeUndefined<T, TDefined>;
99
+ return (state ?? defaultValue) as MaybeUndefined<T, TRequired, THasDefault>;
89
100
  },
90
101
  };
91
102
  },