@voidhash/mimic 0.0.1-alpha.5 → 0.0.1-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +13 -13
- package/dist/index.cjs +140 -46
- package/dist/index.d.cts +229 -115
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +229 -115
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +140 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client/ClientDocument.ts +1 -1
- package/src/client/errors.ts +10 -10
- package/src/primitives/Array.ts +45 -21
- package/src/primitives/Boolean.ts +8 -6
- package/src/primitives/Either.ts +13 -11
- package/src/primitives/Literal.ts +7 -5
- package/src/primitives/Number.ts +13 -11
- package/src/primitives/String.ts +14 -12
- package/src/primitives/Struct.ts +66 -17
- package/src/primitives/Tree.ts +103 -32
- package/src/primitives/TreeNode.ts +49 -27
- package/src/primitives/Union.ts +44 -17
- package/src/primitives/shared.ts +106 -5
- package/src/server/errors.ts +6 -6
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +122 -0
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.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"typescript": "5.8.3",
|
|
20
20
|
"vite-tsconfig-paths": "^5.1.4",
|
|
21
21
|
"vitest": "^3.2.4",
|
|
22
|
-
"@voidhash/tsconfig": "0.0.1-alpha.
|
|
22
|
+
"@voidhash/tsconfig": "0.0.1-alpha.7"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"effect": "^3.19.12"
|
|
@@ -43,7 +43,7 @@ type InitState =
|
|
|
43
43
|
/**
|
|
44
44
|
* Listener for presence changes.
|
|
45
45
|
*/
|
|
46
|
-
export interface PresenceListener<
|
|
46
|
+
export interface PresenceListener<_TData> {
|
|
47
47
|
/** Called when any presence changes (self or others) */
|
|
48
48
|
readonly onPresenceChange?: () => void;
|
|
49
49
|
}
|
package/src/client/errors.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class MimicClientError extends Error {
|
|
|
19
19
|
* Error thrown when a transaction is rejected by the server.
|
|
20
20
|
*/
|
|
21
21
|
export class TransactionRejectedError extends MimicClientError {
|
|
22
|
-
readonly _tag = "TransactionRejectedError";
|
|
22
|
+
override readonly _tag = "TransactionRejectedError";
|
|
23
23
|
readonly transaction: Transaction.Transaction;
|
|
24
24
|
readonly reason: string;
|
|
25
25
|
|
|
@@ -35,7 +35,7 @@ export class TransactionRejectedError extends MimicClientError {
|
|
|
35
35
|
* Error thrown when the transport is not connected.
|
|
36
36
|
*/
|
|
37
37
|
export class NotConnectedError extends MimicClientError {
|
|
38
|
-
readonly _tag = "NotConnectedError";
|
|
38
|
+
override readonly _tag = "NotConnectedError";
|
|
39
39
|
constructor() {
|
|
40
40
|
super("Transport is not connected");
|
|
41
41
|
this.name = "NotConnectedError";
|
|
@@ -46,8 +46,8 @@ export class NotConnectedError extends MimicClientError {
|
|
|
46
46
|
* Error thrown when connection to the server fails.
|
|
47
47
|
*/
|
|
48
48
|
export class ConnectionError extends MimicClientError {
|
|
49
|
-
readonly _tag = "ConnectionError";
|
|
50
|
-
readonly cause?: Error;
|
|
49
|
+
override readonly _tag = "ConnectionError";
|
|
50
|
+
override readonly cause?: Error;
|
|
51
51
|
|
|
52
52
|
constructor(message: string, cause?: Error) {
|
|
53
53
|
super(message);
|
|
@@ -60,7 +60,7 @@ export class ConnectionError extends MimicClientError {
|
|
|
60
60
|
* Error thrown when state drift is detected and cannot be recovered.
|
|
61
61
|
*/
|
|
62
62
|
export class StateDriftError extends MimicClientError {
|
|
63
|
-
readonly _tag = "StateDriftError";
|
|
63
|
+
override readonly _tag = "StateDriftError";
|
|
64
64
|
readonly expectedVersion: number;
|
|
65
65
|
readonly receivedVersion: number;
|
|
66
66
|
|
|
@@ -78,7 +78,7 @@ export class StateDriftError extends MimicClientError {
|
|
|
78
78
|
* Error thrown when a pending transaction times out waiting for confirmation.
|
|
79
79
|
*/
|
|
80
80
|
export class TransactionTimeoutError extends MimicClientError {
|
|
81
|
-
readonly _tag = "TransactionTimeoutError";
|
|
81
|
+
override readonly _tag = "TransactionTimeoutError";
|
|
82
82
|
readonly transaction: Transaction.Transaction;
|
|
83
83
|
readonly timeoutMs: number;
|
|
84
84
|
|
|
@@ -96,7 +96,7 @@ export class TransactionTimeoutError extends MimicClientError {
|
|
|
96
96
|
* Error thrown when rebasing operations fails.
|
|
97
97
|
*/
|
|
98
98
|
export class RebaseError extends MimicClientError {
|
|
99
|
-
readonly _tag = "RebaseError";
|
|
99
|
+
override readonly _tag = "RebaseError";
|
|
100
100
|
readonly transactionId: string;
|
|
101
101
|
|
|
102
102
|
constructor(transactionId: string, message: string) {
|
|
@@ -110,7 +110,7 @@ export class RebaseError extends MimicClientError {
|
|
|
110
110
|
* Error thrown when the client document is in an invalid state.
|
|
111
111
|
*/
|
|
112
112
|
export class InvalidStateError extends MimicClientError {
|
|
113
|
-
readonly _tag = "InvalidStateError";
|
|
113
|
+
override readonly _tag = "InvalidStateError";
|
|
114
114
|
constructor(message: string) {
|
|
115
115
|
super(message);
|
|
116
116
|
this.name = "InvalidStateError";
|
|
@@ -121,7 +121,7 @@ export class InvalidStateError extends MimicClientError {
|
|
|
121
121
|
* Error thrown when WebSocket connection or communication fails.
|
|
122
122
|
*/
|
|
123
123
|
export class WebSocketError extends MimicClientError {
|
|
124
|
-
readonly _tag = "WebSocketError";
|
|
124
|
+
override readonly _tag = "WebSocketError";
|
|
125
125
|
readonly code?: number;
|
|
126
126
|
readonly reason?: string;
|
|
127
127
|
|
|
@@ -137,7 +137,7 @@ export class WebSocketError extends MimicClientError {
|
|
|
137
137
|
* Error thrown when authentication fails.
|
|
138
138
|
*/
|
|
139
139
|
export class AuthenticationError extends MimicClientError {
|
|
140
|
-
readonly _tag = "AuthenticationError";
|
|
140
|
+
override readonly _tag = "AuthenticationError";
|
|
141
141
|
constructor(message: string) {
|
|
142
142
|
super(message);
|
|
143
143
|
this.name = "AuthenticationError";
|
package/src/primitives/Array.ts
CHANGED
|
@@ -7,7 +7,8 @@ import * as Transform from "../Transform";
|
|
|
7
7
|
import * as FractionalIndex from "../FractionalIndex";
|
|
8
8
|
import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot } from "../Primitive";
|
|
9
9
|
import { ValidationError } from "../Primitive";
|
|
10
|
-
import { runValidators } from "./shared";
|
|
10
|
+
import { runValidators, applyDefaults, StructSetInput } from "./shared";
|
|
11
|
+
import type { StructPrimitive } from "./Struct";
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -46,15 +47,25 @@ 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
|
+
* If the element is a struct, uses StructSetInput (partial with defaults).
|
|
53
|
+
* Otherwise uses the full state type.
|
|
54
|
+
*/
|
|
55
|
+
export type ArrayElementSetInput<TElement extends AnyPrimitive> =
|
|
56
|
+
TElement extends StructPrimitive<infer TFields, any, any>
|
|
57
|
+
? StructSetInput<TFields>
|
|
58
|
+
: InferState<TElement>;
|
|
59
|
+
|
|
49
60
|
export interface ArrayProxy<TElement extends AnyPrimitive> {
|
|
50
61
|
/** Gets the current array entries (sorted by position) */
|
|
51
62
|
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:
|
|
63
|
+
/** Replaces the entire array with new values (generates new IDs and positions, applies defaults) */
|
|
64
|
+
set(values: readonly ArrayElementSetInput<TElement>[]): void;
|
|
65
|
+
/** Appends a value to the end of the array (applies defaults for struct elements) */
|
|
66
|
+
push(value: ArrayElementSetInput<TElement>): void;
|
|
67
|
+
/** Inserts a value at the specified visual index (applies defaults for struct elements) */
|
|
68
|
+
insertAt(index: number, value: ArrayElementSetInput<TElement>): void;
|
|
58
69
|
/** Removes the element with the specified ID */
|
|
59
70
|
remove(id: string): void;
|
|
60
71
|
/** Moves an element to a new visual index */
|
|
@@ -77,12 +88,14 @@ interface ArrayPrimitiveSchema<TElement extends AnyPrimitive> {
|
|
|
77
88
|
readonly validators: readonly Validator<ArrayState<TElement>>[];
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
81
|
-
implements Primitive<ArrayState<TElement>, ArrayProxy<TElement
|
|
91
|
+
export class ArrayPrimitive<TElement extends AnyPrimitive, TDefined extends boolean = false, THasDefault extends boolean = false>
|
|
92
|
+
implements Primitive<ArrayState<TElement>, ArrayProxy<TElement>, TDefined, THasDefault>
|
|
82
93
|
{
|
|
83
94
|
readonly _tag = "ArrayPrimitive" as const;
|
|
84
95
|
readonly _State!: ArrayState<TElement>;
|
|
85
96
|
readonly _Proxy!: ArrayProxy<TElement>;
|
|
97
|
+
readonly _TDefined!: TDefined;
|
|
98
|
+
readonly _THasDefault!: THasDefault;
|
|
86
99
|
|
|
87
100
|
private readonly _schema: ArrayPrimitiveSchema<TElement>;
|
|
88
101
|
|
|
@@ -118,7 +131,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
/** Mark this array as required */
|
|
121
|
-
required(): ArrayPrimitive<TElement> {
|
|
134
|
+
required(): ArrayPrimitive<TElement, true, THasDefault> {
|
|
122
135
|
return new ArrayPrimitive({
|
|
123
136
|
...this._schema,
|
|
124
137
|
required: true,
|
|
@@ -126,7 +139,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
/** Set a default value for this array */
|
|
129
|
-
default(defaultValue: ArrayState<TElement>): ArrayPrimitive<TElement> {
|
|
142
|
+
default(defaultValue: ArrayState<TElement>): ArrayPrimitive<TElement, true, true> {
|
|
130
143
|
return new ArrayPrimitive({
|
|
131
144
|
...this._schema,
|
|
132
145
|
defaultValue,
|
|
@@ -139,7 +152,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
139
152
|
}
|
|
140
153
|
|
|
141
154
|
/** Add a custom validation rule */
|
|
142
|
-
refine(fn: (value: ArrayState<TElement>) => boolean, message: string): ArrayPrimitive<TElement> {
|
|
155
|
+
refine(fn: (value: ArrayState<TElement>) => boolean, message: string): ArrayPrimitive<TElement, TDefined, THasDefault> {
|
|
143
156
|
return new ArrayPrimitive({
|
|
144
157
|
...this._schema,
|
|
145
158
|
validators: [...this._schema.validators, { validate: fn, message }],
|
|
@@ -147,7 +160,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
147
160
|
}
|
|
148
161
|
|
|
149
162
|
/** Minimum array length */
|
|
150
|
-
minLength(length: number): ArrayPrimitive<TElement> {
|
|
163
|
+
minLength(length: number): ArrayPrimitive<TElement, TDefined, THasDefault> {
|
|
151
164
|
return this.refine(
|
|
152
165
|
(v) => v.length >= length,
|
|
153
166
|
`Array must have at least ${length} elements`
|
|
@@ -155,7 +168,7 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
/** Maximum array length */
|
|
158
|
-
maxLength(length: number): ArrayPrimitive<TElement> {
|
|
171
|
+
maxLength(length: number): ArrayPrimitive<TElement, TDefined, THasDefault> {
|
|
159
172
|
return this.refine(
|
|
160
173
|
(v) => v.length <= length,
|
|
161
174
|
`Array must have at most ${length} elements`
|
|
@@ -173,12 +186,17 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
173
186
|
return sortByPos(state);
|
|
174
187
|
};
|
|
175
188
|
|
|
189
|
+
// Helper to apply defaults for element values
|
|
190
|
+
const applyElementDefaults = (value: ArrayElementSetInput<TElement>): InferState<TElement> => {
|
|
191
|
+
return applyDefaults(elementPrimitive, value as Partial<InferState<TElement>>) as InferState<TElement>;
|
|
192
|
+
};
|
|
193
|
+
|
|
176
194
|
return {
|
|
177
195
|
get: (): ArrayState<TElement> => {
|
|
178
196
|
return getCurrentState();
|
|
179
197
|
},
|
|
180
198
|
|
|
181
|
-
set: (values: readonly
|
|
199
|
+
set: (values: readonly ArrayElementSetInput<TElement>[]) => {
|
|
182
200
|
// Generate entries with new IDs and sequential positions
|
|
183
201
|
const entries: ArrayEntry<InferState<TElement>>[] = [];
|
|
184
202
|
let prevPos: string | null = null;
|
|
@@ -186,7 +204,9 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
186
204
|
for (const value of values) {
|
|
187
205
|
const id = env.generateId();
|
|
188
206
|
const pos = generatePosBetween(prevPos, null);
|
|
189
|
-
|
|
207
|
+
// Apply defaults to element value
|
|
208
|
+
const mergedValue = applyElementDefaults(value);
|
|
209
|
+
entries.push({ id, pos, value: mergedValue });
|
|
190
210
|
prevPos = pos;
|
|
191
211
|
}
|
|
192
212
|
|
|
@@ -195,27 +215,31 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
195
215
|
);
|
|
196
216
|
},
|
|
197
217
|
|
|
198
|
-
push: (value:
|
|
218
|
+
push: (value: ArrayElementSetInput<TElement>) => {
|
|
199
219
|
const sorted = getCurrentState();
|
|
200
220
|
const lastPos = sorted.length > 0 ? sorted[sorted.length - 1]!.pos : null;
|
|
201
221
|
const id = env.generateId();
|
|
202
222
|
const pos = generatePosBetween(lastPos, null);
|
|
223
|
+
// Apply defaults to element value
|
|
224
|
+
const mergedValue = applyElementDefaults(value);
|
|
203
225
|
|
|
204
226
|
env.addOperation(
|
|
205
|
-
Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value })
|
|
227
|
+
Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value: mergedValue })
|
|
206
228
|
);
|
|
207
229
|
},
|
|
208
230
|
|
|
209
|
-
insertAt: (index: number, value:
|
|
231
|
+
insertAt: (index: number, value: ArrayElementSetInput<TElement>) => {
|
|
210
232
|
const sorted = getCurrentState();
|
|
211
233
|
const leftPos = index > 0 && sorted[index - 1] ? sorted[index - 1]!.pos : null;
|
|
212
234
|
const rightPos = index < sorted.length && sorted[index] ? sorted[index]!.pos : null;
|
|
213
235
|
|
|
214
236
|
const id = env.generateId();
|
|
215
237
|
const pos = generatePosBetween(leftPos, rightPos);
|
|
238
|
+
// Apply defaults to element value
|
|
239
|
+
const mergedValue = applyElementDefaults(value);
|
|
216
240
|
|
|
217
241
|
env.addOperation(
|
|
218
|
-
Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value })
|
|
242
|
+
Operation.fromDefinition(operationPath, this._opDefinitions.insert, { id, pos, value: mergedValue })
|
|
219
243
|
);
|
|
220
244
|
},
|
|
221
245
|
|
|
@@ -452,6 +476,6 @@ export class ArrayPrimitive<TElement extends AnyPrimitive>
|
|
|
452
476
|
}
|
|
453
477
|
|
|
454
478
|
/** Creates a new ArrayPrimitive with the given element type */
|
|
455
|
-
export const Array = <TElement extends AnyPrimitive>(element: TElement): ArrayPrimitive<TElement> =>
|
|
479
|
+
export const Array = <TElement extends AnyPrimitive>(element: TElement): ArrayPrimitive<TElement, false, false> =>
|
|
456
480
|
new ArrayPrimitive({ required: false, defaultValue: undefined, element, validators: [] });
|
|
457
481
|
|
|
@@ -23,10 +23,12 @@ interface BooleanPrimitiveSchema {
|
|
|
23
23
|
readonly validators: readonly Validator<boolean>[];
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export class BooleanPrimitive<TDefined extends boolean = false> implements Primitive<boolean, BooleanProxy<TDefined
|
|
26
|
+
export class BooleanPrimitive<TDefined extends boolean = false, THasDefault extends boolean = false> implements Primitive<boolean, BooleanProxy<TDefined>, TDefined, THasDefault> {
|
|
27
27
|
readonly _tag = "BooleanPrimitive" as const;
|
|
28
28
|
readonly _State!: boolean;
|
|
29
29
|
readonly _Proxy!: BooleanProxy<TDefined>;
|
|
30
|
+
readonly _TDefined!: TDefined;
|
|
31
|
+
readonly _THasDefault!: THasDefault;
|
|
30
32
|
|
|
31
33
|
private readonly _schema: BooleanPrimitiveSchema;
|
|
32
34
|
|
|
@@ -44,7 +46,7 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/** Mark this boolean as required */
|
|
47
|
-
required(): BooleanPrimitive<true> {
|
|
49
|
+
required(): BooleanPrimitive<true, THasDefault> {
|
|
48
50
|
return new BooleanPrimitive({
|
|
49
51
|
...this._schema,
|
|
50
52
|
required: true,
|
|
@@ -52,7 +54,7 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/** Set a default value for this boolean */
|
|
55
|
-
default(defaultValue: boolean): BooleanPrimitive<true> {
|
|
57
|
+
default(defaultValue: boolean): BooleanPrimitive<true, true> {
|
|
56
58
|
return new BooleanPrimitive({
|
|
57
59
|
...this._schema,
|
|
58
60
|
defaultValue,
|
|
@@ -60,7 +62,7 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
/** Add a custom validation rule */
|
|
63
|
-
refine(fn: (value: boolean) => boolean, message: string): BooleanPrimitive<TDefined> {
|
|
65
|
+
refine(fn: (value: boolean) => boolean, message: string): BooleanPrimitive<TDefined, THasDefault> {
|
|
64
66
|
return new BooleanPrimitive({
|
|
65
67
|
...this._schema,
|
|
66
68
|
validators: [...this._schema.validators, { validate: fn, message }],
|
|
@@ -87,7 +89,7 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
|
|
|
87
89
|
};
|
|
88
90
|
},
|
|
89
91
|
|
|
90
|
-
applyOperation: (
|
|
92
|
+
applyOperation: (_state: boolean | undefined, operation: Operation.Operation<any, any, any>): boolean => {
|
|
91
93
|
if (operation.kind !== "boolean.set") {
|
|
92
94
|
throw new ValidationError(`BooleanPrimitive cannot apply operation of kind: ${operation.kind}`);
|
|
93
95
|
}
|
|
@@ -123,6 +125,6 @@ export class BooleanPrimitive<TDefined extends boolean = false> implements Primi
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
/** Creates a new BooleanPrimitive */
|
|
126
|
-
export const Boolean = (): BooleanPrimitive<false> =>
|
|
128
|
+
export const Boolean = (): BooleanPrimitive<false, false> =>
|
|
127
129
|
new BooleanPrimitive({ required: false, defaultValue: undefined, validators: [] });
|
|
128
130
|
|
package/src/primitives/Either.ts
CHANGED
|
@@ -19,10 +19,10 @@ import { LiteralPrimitive, LiteralValue } from "./Literal";
|
|
|
19
19
|
* Scalar primitives that can be used as variants in Either
|
|
20
20
|
*/
|
|
21
21
|
export type ScalarPrimitive =
|
|
22
|
-
| StringPrimitive<any>
|
|
23
|
-
| NumberPrimitive<any>
|
|
24
|
-
| BooleanPrimitive<any>
|
|
25
|
-
| LiteralPrimitive<any, any>;
|
|
22
|
+
| StringPrimitive<any, any>
|
|
23
|
+
| NumberPrimitive<any, any>
|
|
24
|
+
| BooleanPrimitive<any, any>
|
|
25
|
+
| LiteralPrimitive<any, any, any>;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Infer the union state type from a tuple of scalar primitives
|
|
@@ -69,12 +69,14 @@ interface EitherPrimitiveSchema<TVariants extends readonly ScalarPrimitive[]> {
|
|
|
69
69
|
readonly variants: TVariants;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefined extends boolean = false>
|
|
73
|
-
implements Primitive<InferEitherState<TVariants>, EitherProxy<TVariants, TDefined
|
|
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>
|
|
74
74
|
{
|
|
75
75
|
readonly _tag = "EitherPrimitive" as const;
|
|
76
76
|
readonly _State!: InferEitherState<TVariants>;
|
|
77
77
|
readonly _Proxy!: EitherProxy<TVariants, TDefined>;
|
|
78
|
+
readonly _TDefined!: TDefined;
|
|
79
|
+
readonly _THasDefault!: THasDefault;
|
|
78
80
|
|
|
79
81
|
private readonly _schema: EitherPrimitiveSchema<TVariants>;
|
|
80
82
|
|
|
@@ -92,7 +94,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/** Mark this either as required */
|
|
95
|
-
required(): EitherPrimitive<TVariants, true> {
|
|
97
|
+
required(): EitherPrimitive<TVariants, true, THasDefault> {
|
|
96
98
|
return new EitherPrimitive({
|
|
97
99
|
...this._schema,
|
|
98
100
|
required: true,
|
|
@@ -100,7 +102,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
/** Set a default value for this either */
|
|
103
|
-
default(defaultValue: InferEitherState<TVariants>): EitherPrimitive<TVariants, true> {
|
|
105
|
+
default(defaultValue: InferEitherState<TVariants>): EitherPrimitive<TVariants, true, true> {
|
|
104
106
|
return new EitherPrimitive({
|
|
105
107
|
...this._schema,
|
|
106
108
|
defaultValue,
|
|
@@ -121,7 +123,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
121
123
|
// Check for literal matches first (they take priority)
|
|
122
124
|
for (const variant of this._schema.variants) {
|
|
123
125
|
if (variant._tag === "LiteralPrimitive") {
|
|
124
|
-
const literalVariant = variant as LiteralPrimitive<any, any>;
|
|
126
|
+
const literalVariant = variant as LiteralPrimitive<any, any, any>;
|
|
125
127
|
if (value === literalVariant.literal) {
|
|
126
128
|
return "literal";
|
|
127
129
|
}
|
|
@@ -166,7 +168,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
166
168
|
// Check for literal matches first (they take priority)
|
|
167
169
|
for (const variant of this._schema.variants) {
|
|
168
170
|
if (variant._tag === "LiteralPrimitive") {
|
|
169
|
-
const literalVariant = variant as LiteralPrimitive<any, any>;
|
|
171
|
+
const literalVariant = variant as LiteralPrimitive<any, any, any>;
|
|
170
172
|
if (value === literalVariant.literal) {
|
|
171
173
|
return variant;
|
|
172
174
|
}
|
|
@@ -350,7 +352,7 @@ export class EitherPrimitive<TVariants extends readonly ScalarPrimitive[], TDefi
|
|
|
350
352
|
*/
|
|
351
353
|
export function Either<TVariants extends readonly ScalarPrimitive[]>(
|
|
352
354
|
...variants: TVariants
|
|
353
|
-
): EitherPrimitive<TVariants, false> {
|
|
355
|
+
): EitherPrimitive<TVariants, false, false> {
|
|
354
356
|
if (variants.length === 0) {
|
|
355
357
|
throw new ValidationError("Either requires at least one variant");
|
|
356
358
|
}
|
|
@@ -27,10 +27,12 @@ interface LiteralPrimitiveSchema<T extends LiteralValue> {
|
|
|
27
27
|
readonly literal: T;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean = false> implements Primitive<T, LiteralProxy<T, TDefined
|
|
30
|
+
export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean = false, THasDefault extends boolean = false> implements Primitive<T, LiteralProxy<T, TDefined>, TDefined, THasDefault> {
|
|
31
31
|
readonly _tag = "LiteralPrimitive" as const;
|
|
32
32
|
readonly _State!: T;
|
|
33
33
|
readonly _Proxy!: LiteralProxy<T, TDefined>;
|
|
34
|
+
readonly _TDefined!: TDefined;
|
|
35
|
+
readonly _THasDefault!: THasDefault;
|
|
34
36
|
|
|
35
37
|
private readonly _schema: LiteralPrimitiveSchema<T>;
|
|
36
38
|
|
|
@@ -48,7 +50,7 @@ export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean =
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
/** Mark this literal as required */
|
|
51
|
-
required(): LiteralPrimitive<T, true> {
|
|
53
|
+
required(): LiteralPrimitive<T, true, THasDefault> {
|
|
52
54
|
return new LiteralPrimitive({
|
|
53
55
|
...this._schema,
|
|
54
56
|
required: true,
|
|
@@ -56,7 +58,7 @@ export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean =
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/** Set a default value for this literal */
|
|
59
|
-
default(defaultValue: T): LiteralPrimitive<T, true> {
|
|
61
|
+
default(defaultValue: T): LiteralPrimitive<T, true, true> {
|
|
60
62
|
return new LiteralPrimitive({
|
|
61
63
|
...this._schema,
|
|
62
64
|
defaultValue,
|
|
@@ -88,7 +90,7 @@ export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean =
|
|
|
88
90
|
};
|
|
89
91
|
},
|
|
90
92
|
|
|
91
|
-
applyOperation: (
|
|
93
|
+
applyOperation: (_state: T | undefined, operation: Operation.Operation<any, any, any>): T => {
|
|
92
94
|
if (operation.kind !== "literal.set") {
|
|
93
95
|
throw new ValidationError(`LiteralPrimitive cannot apply operation of kind: ${operation.kind}`);
|
|
94
96
|
}
|
|
@@ -123,6 +125,6 @@ export class LiteralPrimitive<T extends LiteralValue, TDefined extends boolean =
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
/** Creates a new LiteralPrimitive with the given literal value */
|
|
126
|
-
export const Literal = <T extends LiteralValue>(literal: T): LiteralPrimitive<T, false> =>
|
|
128
|
+
export const Literal = <T extends LiteralValue>(literal: T): LiteralPrimitive<T, false, false> =>
|
|
127
129
|
new LiteralPrimitive({ required: false, defaultValue: undefined, literal });
|
|
128
130
|
|
package/src/primitives/Number.ts
CHANGED
|
@@ -24,10 +24,12 @@ interface NumberPrimitiveSchema {
|
|
|
24
24
|
readonly validators: readonly Validator<number>[];
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export class NumberPrimitive<TDefined extends boolean = false> implements Primitive<number, NumberProxy<TDefined
|
|
27
|
+
export class NumberPrimitive<TDefined extends boolean = false, THasDefault extends boolean = false> implements Primitive<number, NumberProxy<TDefined>, TDefined, THasDefault> {
|
|
28
28
|
readonly _tag = "NumberPrimitive" as const;
|
|
29
29
|
readonly _State!: number;
|
|
30
30
|
readonly _Proxy!: NumberProxy<TDefined>;
|
|
31
|
+
readonly _TDefined!: TDefined;
|
|
32
|
+
readonly _THasDefault!: THasDefault;
|
|
31
33
|
|
|
32
34
|
private readonly _schema: NumberPrimitiveSchema;
|
|
33
35
|
|
|
@@ -45,7 +47,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/** Mark this number as required */
|
|
48
|
-
required(): NumberPrimitive<true> {
|
|
50
|
+
required(): NumberPrimitive<true, THasDefault> {
|
|
49
51
|
return new NumberPrimitive({
|
|
50
52
|
...this._schema,
|
|
51
53
|
required: true,
|
|
@@ -53,7 +55,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/** Set a default value for this number */
|
|
56
|
-
default(defaultValue: number): NumberPrimitive<true> {
|
|
58
|
+
default(defaultValue: number): NumberPrimitive<true, true> {
|
|
57
59
|
return new NumberPrimitive({
|
|
58
60
|
...this._schema,
|
|
59
61
|
defaultValue,
|
|
@@ -61,7 +63,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/** Add a custom validation rule */
|
|
64
|
-
refine(fn: (value: number) => boolean, message: string): NumberPrimitive<TDefined> {
|
|
66
|
+
refine(fn: (value: number) => boolean, message: string): NumberPrimitive<TDefined, THasDefault> {
|
|
65
67
|
return new NumberPrimitive({
|
|
66
68
|
...this._schema,
|
|
67
69
|
validators: [...this._schema.validators, { validate: fn, message }],
|
|
@@ -69,7 +71,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
/** Minimum value (inclusive) */
|
|
72
|
-
min(value: number): NumberPrimitive<TDefined> {
|
|
74
|
+
min(value: number): NumberPrimitive<TDefined, THasDefault> {
|
|
73
75
|
return this.refine(
|
|
74
76
|
(v) => v >= value,
|
|
75
77
|
`Number must be at least ${value}`
|
|
@@ -77,7 +79,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/** Maximum value (inclusive) */
|
|
80
|
-
max(value: number): NumberPrimitive<TDefined> {
|
|
82
|
+
max(value: number): NumberPrimitive<TDefined, THasDefault> {
|
|
81
83
|
return this.refine(
|
|
82
84
|
(v) => v <= value,
|
|
83
85
|
`Number must be at most ${value}`
|
|
@@ -85,7 +87,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
/** Must be positive (> 0) */
|
|
88
|
-
positive(): NumberPrimitive<TDefined> {
|
|
90
|
+
positive(): NumberPrimitive<TDefined, THasDefault> {
|
|
89
91
|
return this.refine(
|
|
90
92
|
(v) => v > 0,
|
|
91
93
|
"Number must be positive"
|
|
@@ -93,7 +95,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
/** Must be negative (< 0) */
|
|
96
|
-
negative(): NumberPrimitive<TDefined> {
|
|
98
|
+
negative(): NumberPrimitive<TDefined, THasDefault> {
|
|
97
99
|
return this.refine(
|
|
98
100
|
(v) => v < 0,
|
|
99
101
|
"Number must be negative"
|
|
@@ -101,7 +103,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
/** Must be an integer */
|
|
104
|
-
int(): NumberPrimitive<TDefined> {
|
|
106
|
+
int(): NumberPrimitive<TDefined, THasDefault> {
|
|
105
107
|
return this.refine(
|
|
106
108
|
(v) => globalThis.Number.isInteger(v),
|
|
107
109
|
"Number must be an integer"
|
|
@@ -128,7 +130,7 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
128
130
|
};
|
|
129
131
|
},
|
|
130
132
|
|
|
131
|
-
applyOperation: (
|
|
133
|
+
applyOperation: (_state: number | undefined, operation: Operation.Operation<any, any, any>): number => {
|
|
132
134
|
if (operation.kind !== "number.set") {
|
|
133
135
|
throw new ValidationError(`NumberPrimitive cannot apply operation of kind: ${operation.kind}`);
|
|
134
136
|
}
|
|
@@ -164,6 +166,6 @@ export class NumberPrimitive<TDefined extends boolean = false> implements Primit
|
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
/** Creates a new NumberPrimitive */
|
|
167
|
-
export const Number = (): NumberPrimitive<false> =>
|
|
169
|
+
export const Number = (): NumberPrimitive<false, false> =>
|
|
168
170
|
new NumberPrimitive({ required: false, defaultValue: undefined, validators: [] });
|
|
169
171
|
|