@voidhash/mimic 0.0.1-alpha.6 → 0.0.1-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +43 -15
- package/dist/Document-ChuFrTk1.cjs +571 -0
- package/dist/Document-CwiAFTIq.mjs +438 -0
- package/dist/Document-CwiAFTIq.mjs.map +1 -0
- package/dist/Presence-DKKP4v5X.d.cts +91 -0
- package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
- package/dist/Presence-DdMVKcOv.mjs +110 -0
- package/dist/Presence-DdMVKcOv.mjs.map +1 -0
- package/dist/Presence-N8u7Eppr.d.mts +91 -0
- package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
- package/dist/Presence-gWrmGBeu.cjs +126 -0
- package/dist/Primitive-BK7kfHJZ.d.cts +1165 -0
- package/dist/Primitive-BK7kfHJZ.d.cts.map +1 -0
- package/dist/Primitive-D1kdB6za.d.mts +1165 -0
- package/dist/Primitive-D1kdB6za.d.mts.map +1 -0
- package/dist/client/index.cjs +1456 -0
- package/dist/client/index.d.cts +692 -0
- package/dist/client/index.d.cts.map +1 -0
- package/dist/client/index.d.mts +692 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +1413 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.cjs +309 -757
- package/dist/index.d.cts +5 -1054
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -1054
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +168 -575
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.cjs +191 -0
- package/dist/server/index.d.cts +148 -0
- package/dist/server/index.d.cts.map +1 -0
- package/dist/server/index.d.mts +148 -0
- package/dist/server/index.d.mts.map +1 -0
- package/dist/server/index.mjs +182 -0
- package/dist/server/index.mjs.map +1 -0
- package/package.json +17 -5
- package/src/primitives/Array.ts +57 -22
- package/src/primitives/Boolean.ts +32 -18
- package/src/primitives/Either.ts +39 -24
- package/src/primitives/Lazy.ts +16 -2
- package/src/primitives/Literal.ts +32 -19
- package/src/primitives/Number.ts +38 -25
- package/src/primitives/String.ts +39 -24
- package/src/primitives/Struct.ts +124 -27
- package/src/primitives/Tree.ts +117 -30
- package/src/primitives/Union.ts +56 -29
- package/src/primitives/shared.ts +103 -9
- package/tests/primitives/Array.test.ts +108 -0
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +250 -0
- package/tsdown.config.ts +1 -1
- /package/dist/{chunk-C6wwvPpM.mjs → chunk-CLMFDpHK.mjs} +0 -0
|
@@ -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.
|
|
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
|
-
".":
|
|
13
|
-
|
|
14
|
-
|
|
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",
|
|
@@ -19,7 +31,7 @@
|
|
|
19
31
|
"typescript": "5.8.3",
|
|
20
32
|
"vite-tsconfig-paths": "^5.1.4",
|
|
21
33
|
"vitest": "^3.2.4",
|
|
22
|
-
"@voidhash/tsconfig": "0.0.1-alpha.
|
|
34
|
+
"@voidhash/tsconfig": "0.0.1-alpha.7"
|
|
23
35
|
},
|
|
24
36
|
"peerDependencies": {
|
|
25
37
|
"effect": "^3.19.12"
|
package/src/primitives/Array.ts
CHANGED
|
@@ -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
|
|
54
|
-
/** Appends a value to the end of the array */
|
|
55
|
-
push(value:
|
|
56
|
-
/** Inserts a value at the specified visual index */
|
|
57
|
-
insertAt(index: number, value:
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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,
|
|
16
|
+
get(): MaybeUndefined<boolean, TRequired, THasDefault>;
|
|
14
17
|
/** Sets the boolean value, generating a boolean.set operation */
|
|
15
|
-
set(value:
|
|
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,
|
|
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<
|
|
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<
|
|
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,29 +69,34 @@ 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<
|
|
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<
|
|
71
|
-
createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): BooleanProxy<
|
|
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,
|
|
83
|
+
get: (): MaybeUndefined<boolean, TRequired, THasDefault> => {
|
|
75
84
|
const state = env.getState(operationPath) as boolean | undefined;
|
|
76
|
-
return (state ?? defaultValue) as MaybeUndefined<boolean,
|
|
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
|
-
|
|
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,
|
|
97
|
+
toSnapshot: (): MaybeUndefined<boolean, TRequired, THasDefault> => {
|
|
84
98
|
const state = env.getState(operationPath) as boolean | undefined;
|
|
85
|
-
return (state ?? defaultValue) as MaybeUndefined<boolean,
|
|
99
|
+
return (state ?? defaultValue) as MaybeUndefined<boolean, TRequired, THasDefault>;
|
|
86
100
|
},
|
|
87
101
|
};
|
|
88
102
|
},
|
|
@@ -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
|
|
package/src/primitives/Either.ts
CHANGED
|
@@ -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,14 +15,17 @@ 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
|
*/
|
|
21
24
|
export type ScalarPrimitive =
|
|
22
|
-
| StringPrimitive<any>
|
|
23
|
-
| NumberPrimitive<any>
|
|
24
|
-
| BooleanPrimitive<any>
|
|
25
|
-
| LiteralPrimitive<any, any>;
|
|
25
|
+
| StringPrimitive<any, any>
|
|
26
|
+
| NumberPrimitive<any, any>
|
|
27
|
+
| BooleanPrimitive<any, any>
|
|
28
|
+
| LiteralPrimitive<any, any, any>;
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Infer the union state type from a tuple of scalar primitives
|
|
@@ -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[],
|
|
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>,
|
|
57
|
+
get(): MaybeUndefined<InferEitherState<TVariants>, TRequired, THasDefault>;
|
|
55
58
|
|
|
56
59
|
/** Sets the value to any of the allowed variant types */
|
|
57
|
-
set(value:
|
|
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>,
|
|
69
|
+
toSnapshot(): MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault>;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
interface EitherPrimitiveSchema<TVariants extends readonly ScalarPrimitive[]> {
|
|
@@ -69,12 +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[],
|
|
73
|
-
implements Primitive<InferEitherState<TVariants>, EitherProxy<TVariants,
|
|
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,
|
|
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>;
|
|
78
88
|
|
|
79
89
|
private readonly _schema: EitherPrimitiveSchema<TVariants>;
|
|
80
90
|
|
|
@@ -92,7 +102,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
/** Mark this either as required */
|
|
95
|
-
required(): EitherPrimitive<TVariants, true> {
|
|
105
|
+
required(): EitherPrimitive<TVariants, true, THasDefault> {
|
|
96
106
|
return new EitherPrimitive({
|
|
97
107
|
...this._schema,
|
|
98
108
|
required: true,
|
|
@@ -100,7 +110,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
/** Set a default value for this either */
|
|
103
|
-
default(defaultValue: InferEitherState<TVariants>): EitherPrimitive<TVariants, true> {
|
|
113
|
+
default(defaultValue: InferEitherState<TVariants>): EitherPrimitive<TVariants, TRequired, true> {
|
|
104
114
|
return new EitherPrimitive({
|
|
105
115
|
...this._schema,
|
|
106
116
|
defaultValue,
|
|
@@ -121,7 +131,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
121
131
|
// Check for literal matches first (they take priority)
|
|
122
132
|
for (const variant of this._schema.variants) {
|
|
123
133
|
if (variant._tag === "LiteralPrimitive") {
|
|
124
|
-
const literalVariant = variant as LiteralPrimitive<any, any>;
|
|
134
|
+
const literalVariant = variant as LiteralPrimitive<any, any, any>;
|
|
125
135
|
if (value === literalVariant.literal) {
|
|
126
136
|
return "literal";
|
|
127
137
|
}
|
|
@@ -166,7 +176,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
166
176
|
// Check for literal matches first (they take priority)
|
|
167
177
|
for (const variant of this._schema.variants) {
|
|
168
178
|
if (variant._tag === "LiteralPrimitive") {
|
|
169
|
-
const literalVariant = variant as LiteralPrimitive<any, any>;
|
|
179
|
+
const literalVariant = variant as LiteralPrimitive<any, any, any>;
|
|
170
180
|
if (value === literalVariant.literal) {
|
|
171
181
|
return variant;
|
|
172
182
|
}
|
|
@@ -246,19 +256,24 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
246
256
|
matchingVariant._internal.applyOperation(undefined, syntheticOp);
|
|
247
257
|
}
|
|
248
258
|
|
|
249
|
-
readonly _internal: PrimitiveInternal<InferEitherState<TVariants>, EitherProxy<TVariants,
|
|
259
|
+
readonly _internal: PrimitiveInternal<InferEitherState<TVariants>, EitherProxy<TVariants, TRequired, THasDefault>> = {
|
|
250
260
|
createProxy: (
|
|
251
261
|
env: ProxyEnvironment.ProxyEnvironment,
|
|
252
262
|
operationPath: OperationPath.OperationPath
|
|
253
|
-
): EitherProxy<TVariants,
|
|
263
|
+
): EitherProxy<TVariants, TRequired, THasDefault> => {
|
|
254
264
|
const defaultValue = this._schema.defaultValue;
|
|
255
265
|
|
|
256
266
|
return {
|
|
257
|
-
get: (): MaybeUndefined<InferEitherState<TVariants>,
|
|
267
|
+
get: (): MaybeUndefined<InferEitherState<TVariants>, TRequired, THasDefault> => {
|
|
258
268
|
const state = env.getState(operationPath) as InferEitherState<TVariants> | undefined;
|
|
259
|
-
return (state ?? defaultValue) as MaybeUndefined<InferEitherState<TVariants>,
|
|
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
|
+
);
|
|
260
275
|
},
|
|
261
|
-
|
|
276
|
+
update: (value: InferUpdateInput<TVariants, TRequired, THasDefault>) => {
|
|
262
277
|
env.addOperation(
|
|
263
278
|
Operation.fromDefinition(operationPath, this._opDefinitions.set, value)
|
|
264
279
|
);
|
|
@@ -284,9 +299,9 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
284
299
|
return undefined;
|
|
285
300
|
}
|
|
286
301
|
},
|
|
287
|
-
toSnapshot: (): MaybeUndefined<InferEitherSnapshot<TVariants>,
|
|
302
|
+
toSnapshot: (): MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault> => {
|
|
288
303
|
const state = env.getState(operationPath) as InferEitherState<TVariants> | undefined;
|
|
289
|
-
return (state ?? defaultValue) as MaybeUndefined<InferEitherSnapshot<TVariants>,
|
|
304
|
+
return (state ?? defaultValue) as MaybeUndefined<InferEitherSnapshot<TVariants>, TRequired, THasDefault>;
|
|
290
305
|
},
|
|
291
306
|
};
|
|
292
307
|
},
|
|
@@ -350,7 +365,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
350
365
|
*/
|
|
351
366
|
export function Either<TVariants extends readonly ScalarPrimitive[]>(
|
|
352
367
|
...variants: TVariants
|
|
353
|
-
): EitherPrimitive<TVariants, false> {
|
|
368
|
+
): EitherPrimitive<TVariants, false, false> {
|
|
354
369
|
if (variants.length === 0) {
|
|
355
370
|
throw new ValidationError("Either requires at least one variant");
|
|
356
371
|
}
|
package/src/primitives/Lazy.ts
CHANGED
|
@@ -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;
|