@oscarpalmer/jhunal 0.17.0 → 0.19.0
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/dist/constants.d.mts +28 -15
- package/dist/constants.mjs +31 -14
- package/dist/helpers.d.mts +8 -1
- package/dist/helpers.mjs +68 -3
- package/dist/index.d.mts +304 -240
- package/dist/index.mjs +201 -51
- package/dist/models/infer.model.d.mts +66 -0
- package/dist/models/infer.model.mjs +1 -0
- package/dist/models/misc.model.d.mts +153 -0
- package/dist/models/misc.model.mjs +1 -0
- package/dist/models/schema.plain.model.d.mts +92 -0
- package/dist/models/schema.plain.model.mjs +1 -0
- package/dist/models/schema.typed.model.d.mts +96 -0
- package/dist/models/schema.typed.model.mjs +1 -0
- package/dist/models/transform.model.d.mts +59 -0
- package/dist/models/transform.model.mjs +1 -0
- package/dist/models/validation.model.d.mts +82 -0
- package/dist/models/validation.model.mjs +21 -0
- package/dist/schematic.d.mts +34 -1
- package/dist/schematic.mjs +11 -12
- package/dist/validation/property.validation.d.mts +1 -1
- package/dist/validation/property.validation.mjs +21 -17
- package/dist/validation/value.validation.d.mts +2 -2
- package/dist/validation/value.validation.mjs +73 -12
- package/package.json +2 -2
- package/src/constants.ts +84 -19
- package/src/helpers.ts +162 -4
- package/src/index.ts +3 -1
- package/src/models/infer.model.ts +105 -0
- package/src/models/misc.model.ts +212 -0
- package/src/models/schema.plain.model.ts +110 -0
- package/src/models/schema.typed.model.ts +109 -0
- package/src/models/transform.model.ts +85 -0
- package/src/models/validation.model.ts +124 -0
- package/src/schematic.ts +55 -10
- package/src/validation/property.validation.ts +41 -36
- package/src/validation/value.validation.ts +133 -20
- package/dist/models.d.mts +0 -484
- package/dist/models.mjs +0 -13
- package/src/models.ts +0 -665
package/dist/index.d.mts
CHANGED
|
@@ -1,31 +1,7 @@
|
|
|
1
1
|
import { Constructor, GenericCallback, PlainObject, Simplify } from "@oscarpalmer/atoms/models";
|
|
2
|
+
import { Result } from "@oscarpalmer/atoms/result/models";
|
|
2
3
|
|
|
3
|
-
//#region src/models.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Removes duplicate types from a tuple, preserving first occurrence order
|
|
6
|
-
*
|
|
7
|
-
* @template Value - Tuple to deduplicate
|
|
8
|
-
* @template Seen - Accumulator for already-seen types _(internal)_
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* // DeduplicateTuple<['string', 'number', 'string']>
|
|
13
|
-
* // => ['string', 'number']
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [infer Head, ...infer Tail] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
|
|
17
|
-
/**
|
|
18
|
-
* Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
|
|
19
|
-
*
|
|
20
|
-
* @template Value - Type to extract value names from
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* // ExtractValueNames<'string'> => 'string'
|
|
25
|
-
* // ExtractValueNames<['string', 'number']> => 'string' | 'number'
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
type ExtractValueNames<Value> = Value extends ValueName ? Value : Value extends (infer Item)[] ? ExtractValueNames<Item> : Value extends readonly (infer Item)[] ? ExtractValueNames<Item> : never;
|
|
4
|
+
//#region src/models/infer.model.d.ts
|
|
29
5
|
/**
|
|
30
6
|
* Infers the TypeScript type from a {@link Schema} definition
|
|
31
7
|
*
|
|
@@ -84,20 +60,8 @@ type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryVa
|
|
|
84
60
|
* @template Value - single schema entry
|
|
85
61
|
*/
|
|
86
62
|
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends PlainSchema ? Infer<Value & Schema> : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
* Returns `true` if the entry is a {@link SchemaProperty} or {@link NestedSchema} with `$required` set to `false`; otherwise returns `false`
|
|
91
|
-
*
|
|
92
|
-
* @template Value - Schema entry to check
|
|
93
|
-
*/
|
|
94
|
-
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
|
|
95
|
-
/**
|
|
96
|
-
* Extracts the last member from a union type by leveraging intersection of function return types
|
|
97
|
-
*
|
|
98
|
-
* @template Value - Union type
|
|
99
|
-
*/
|
|
100
|
-
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/models/transform.model.d.ts
|
|
101
65
|
/**
|
|
102
66
|
* Maps each element of a tuple through {@link ToValueType}
|
|
103
67
|
*
|
|
@@ -110,109 +74,6 @@ type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...in
|
|
|
110
74
|
* @template Value - Tuple of types to map
|
|
111
75
|
*/
|
|
112
76
|
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
|
|
113
|
-
/**
|
|
114
|
-
* Extracts keys from an object type that are optional
|
|
115
|
-
*
|
|
116
|
-
* @template Value - Object type to inspect
|
|
117
|
-
*/
|
|
118
|
-
type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
|
|
119
|
-
/**
|
|
120
|
-
* A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
|
|
121
|
-
*/
|
|
122
|
-
type PlainSchema = {
|
|
123
|
-
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
|
|
124
|
-
} & {
|
|
125
|
-
$required?: never;
|
|
126
|
-
$type?: never;
|
|
127
|
-
$validators?: never;
|
|
128
|
-
};
|
|
129
|
-
/**
|
|
130
|
-
* A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
|
|
131
|
-
*
|
|
132
|
-
* Each key may hold a single validator or an array of validators that receive the typed value
|
|
133
|
-
*
|
|
134
|
-
* @template Value - `$type` value(s) to derive validator keys from
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```ts
|
|
138
|
-
* const validators: PropertyValidators<'string'> = {
|
|
139
|
-
* string: (value) => value.length > 0,
|
|
140
|
-
* };
|
|
141
|
-
* ```
|
|
142
|
-
*/
|
|
143
|
-
type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
|
|
144
|
-
/**
|
|
145
|
-
* Extracts keys from an object type that are required _(i.e., not optional)_
|
|
146
|
-
*
|
|
147
|
-
* @template Value - Object type to inspect
|
|
148
|
-
*/
|
|
149
|
-
type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
150
|
-
/**
|
|
151
|
-
* A schema for validating objects
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```ts
|
|
155
|
-
* const schema: Schema = {
|
|
156
|
-
* name: 'string',
|
|
157
|
-
* age: 'number',
|
|
158
|
-
* tags: ['string', 'number'],
|
|
159
|
-
* };
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
type Schema = SchemaIndex;
|
|
163
|
-
/**
|
|
164
|
-
* A union of all valid types for a single schema entry
|
|
165
|
-
*
|
|
166
|
-
* Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
167
|
-
*/
|
|
168
|
-
type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
169
|
-
/**
|
|
170
|
-
* Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
|
|
171
|
-
*/
|
|
172
|
-
interface SchemaIndex {
|
|
173
|
-
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* A property definition with explicit type(s), an optional requirement flag, and optional validators
|
|
177
|
-
*
|
|
178
|
-
* @example
|
|
179
|
-
* ```ts
|
|
180
|
-
* const prop: SchemaProperty = {
|
|
181
|
-
* $required: false,
|
|
182
|
-
* $type: ['string', 'number'],
|
|
183
|
-
* $validators: {
|
|
184
|
-
* string: (v) => v.length > 0,
|
|
185
|
-
* number: (v) => v > 0,
|
|
186
|
-
* },
|
|
187
|
-
* };
|
|
188
|
-
* ```
|
|
189
|
-
*/
|
|
190
|
-
type SchemaProperty = {
|
|
191
|
-
/**
|
|
192
|
-
* Whether the property is required _(defaults to `true`)_
|
|
193
|
-
*/
|
|
194
|
-
$required?: boolean;
|
|
195
|
-
/**
|
|
196
|
-
* The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
|
|
197
|
-
*/
|
|
198
|
-
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
199
|
-
/**
|
|
200
|
-
* Optional validators keyed by {@link ValueName}, applied during validation
|
|
201
|
-
*/
|
|
202
|
-
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
203
|
-
};
|
|
204
|
-
/**
|
|
205
|
-
* A union of valid types for a {@link SchemaProperty}'s `$type` field
|
|
206
|
-
*
|
|
207
|
-
* Can be a {@link Constructor}, {@link PlainSchema}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
208
|
-
*/
|
|
209
|
-
type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
210
|
-
/**
|
|
211
|
-
* A custom error class for schematic validation failures
|
|
212
|
-
*/
|
|
213
|
-
declare class SchematicError extends Error {
|
|
214
|
-
constructor(message: string);
|
|
215
|
-
}
|
|
216
77
|
/**
|
|
217
78
|
* Converts a type into its corresponding {@link SchemaPropertyType}-representation
|
|
218
79
|
*
|
|
@@ -252,31 +113,8 @@ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionTo
|
|
|
252
113
|
* ```
|
|
253
114
|
*/
|
|
254
115
|
type ToValueType<Value> = Value extends Schematic<any> ? Value : { [Key in keyof Omit<Values, 'object'>]: Value extends Values[Key] ? Key : never }[keyof Omit<Values, 'object'>] extends infer Match ? [Match] extends [never] ? Value extends object ? 'object' | ((value: unknown) => value is Value) : (value: unknown) => value is Value : Match : never;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
*
|
|
258
|
-
* Used by {@link UnwrapSingle} to allow schema types in any order for small tuples _(length ≤ 5)_
|
|
259
|
-
*
|
|
260
|
-
* @template Tuple - Tuple to permute
|
|
261
|
-
* @template Elput - Accumulator for the current permutation _(internal; name is Tuple backwards)_
|
|
262
|
-
*
|
|
263
|
-
* @example
|
|
264
|
-
* ```ts
|
|
265
|
-
* // TuplePermutations<['string', 'number']>
|
|
266
|
-
* // => ['string', 'number'] | ['number', 'string']
|
|
267
|
-
* ```
|
|
268
|
-
*/
|
|
269
|
-
type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : { [Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [...Elput, Tuple[Key]]> }[keyof Tuple & `${number}`];
|
|
270
|
-
/**
|
|
271
|
-
* Removes the element at a given index from a tuple
|
|
272
|
-
*
|
|
273
|
-
* Used internally by {@link TuplePermutations}
|
|
274
|
-
*
|
|
275
|
-
* @template Items - Tuple to remove from
|
|
276
|
-
* @template Item - Stringified index to remove
|
|
277
|
-
* @template Prefix - Accumulator for elements before the target _(internal)_
|
|
278
|
-
*/
|
|
279
|
-
type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends unknown[] = []> = Items extends [infer Head, ...infer Tail] ? `${Prefix['length']}` extends Item ? [...Prefix, ...Tail] : TupleRemoveAt<Tail, Item, [...Prefix, Head]> : Prefix;
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/models/schema.typed.model.d.ts
|
|
280
118
|
/**
|
|
281
119
|
* A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
|
|
282
120
|
*
|
|
@@ -364,49 +202,14 @@ type TypedSchemaOptional<Model extends PlainObject> = {
|
|
|
364
202
|
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
365
203
|
$required?: true;
|
|
366
204
|
} & TypedSchema<Model>;
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/models/validation.model.d.ts
|
|
367
207
|
/**
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* Uses the contravariance of function parameter types to collapse a union into an intersection
|
|
371
|
-
*
|
|
372
|
-
* @template Value - Union type to convert
|
|
373
|
-
*
|
|
374
|
-
* @example
|
|
375
|
-
* ```ts
|
|
376
|
-
* // UnionToIntersection<{ a: 1 } | { b: 2 }>
|
|
377
|
-
* // => { a: 1 } & { b: 2 }
|
|
378
|
-
* ```
|
|
379
|
-
*/
|
|
380
|
-
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends ((value: infer Item) => void) ? Item : never;
|
|
381
|
-
/**
|
|
382
|
-
* Converts a union type into an ordered tuple
|
|
383
|
-
*
|
|
384
|
-
* Repeatedly extracts the {@link LastOfUnion} member and prepends it to the accumulator
|
|
385
|
-
*
|
|
386
|
-
* @template Value - Union type to convert
|
|
387
|
-
* @template Items - Accumulator for the resulting tuple _(internal)_
|
|
388
|
-
*
|
|
389
|
-
* @example
|
|
390
|
-
* ```ts
|
|
391
|
-
* // UnionToTuple<'a' | 'b' | 'c'>
|
|
392
|
-
* // => ['a', 'b', 'c']
|
|
393
|
-
* ```
|
|
394
|
-
*/
|
|
395
|
-
type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
|
|
396
|
-
/**
|
|
397
|
-
* Unwraps a single-element tuple to its inner type
|
|
398
|
-
*
|
|
399
|
-
* For tuples of length 2–5, returns all {@link TuplePermutations} to allow types in any order. Longer tuples are returned as-is
|
|
400
|
-
*
|
|
401
|
-
* @template Value - Tuple to potentially unwrap
|
|
402
|
-
*
|
|
403
|
-
* @example
|
|
404
|
-
* ```ts
|
|
405
|
-
* // UnwrapSingle<['string']> => 'string'
|
|
406
|
-
* // UnwrapSingle<['string', 'number']> => ['string', 'number'] | ['number', 'string']
|
|
407
|
-
* ```
|
|
208
|
+
* A custom error class for schematic validation failures
|
|
408
209
|
*/
|
|
409
|
-
|
|
210
|
+
declare class SchematicError extends Error {
|
|
211
|
+
constructor(message: string);
|
|
212
|
+
}
|
|
410
213
|
/**
|
|
411
214
|
* The runtime representation of a parsed schema property, used internally during validation
|
|
412
215
|
*
|
|
@@ -424,7 +227,7 @@ type ValidatedProperty = {
|
|
|
424
227
|
/**
|
|
425
228
|
* The property name in the schema
|
|
426
229
|
*/
|
|
427
|
-
key:
|
|
230
|
+
key: ValidatedPropertyKey;
|
|
428
231
|
/**
|
|
429
232
|
* Whether the property is required
|
|
430
233
|
*/
|
|
@@ -438,47 +241,42 @@ type ValidatedProperty = {
|
|
|
438
241
|
*/
|
|
439
242
|
validators: ValidatedPropertyValidators;
|
|
440
243
|
};
|
|
244
|
+
/**
|
|
245
|
+
* Property name in schema
|
|
246
|
+
*/
|
|
247
|
+
type ValidatedPropertyKey = {
|
|
248
|
+
/**
|
|
249
|
+
* Full property key, including parent keys for nested properties _(e.g., `address.street`)_
|
|
250
|
+
*/
|
|
251
|
+
full: string;
|
|
252
|
+
/**
|
|
253
|
+
* The last segment of the property key _(e.g., `street` for `address.street`)_
|
|
254
|
+
*/
|
|
255
|
+
short: string;
|
|
256
|
+
};
|
|
441
257
|
/**
|
|
442
258
|
* A union of valid types for a {@link ValidatedProperty}'s `types` array
|
|
443
259
|
*
|
|
444
260
|
* Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
|
|
445
261
|
*/
|
|
446
|
-
type ValidatedPropertyType = GenericCallback | Schematic<unknown> |
|
|
262
|
+
type ValidatedPropertyType = GenericCallback | ValidatedProperty[] | Schematic<unknown> | ValueName;
|
|
447
263
|
/**
|
|
448
264
|
* A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
|
|
449
265
|
*
|
|
450
266
|
* Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
|
|
451
267
|
*/
|
|
452
268
|
type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
* @example
|
|
463
|
-
* ```ts
|
|
464
|
-
* // Values['string'] => string
|
|
465
|
-
* // Values['date'] => Date
|
|
466
|
-
* // Values['null'] => null
|
|
467
|
-
* ```
|
|
468
|
-
*/
|
|
469
|
-
type Values = {
|
|
470
|
-
array: unknown[];
|
|
471
|
-
bigint: bigint;
|
|
472
|
-
boolean: boolean;
|
|
473
|
-
date: Date;
|
|
474
|
-
function: Function;
|
|
475
|
-
null: null;
|
|
476
|
-
number: number;
|
|
477
|
-
object: object;
|
|
478
|
-
string: string;
|
|
479
|
-
symbol: symbol;
|
|
480
|
-
undefined: undefined;
|
|
269
|
+
declare class ValidationError extends Error {
|
|
270
|
+
readonly information: ValidationInformation[];
|
|
271
|
+
constructor(information: ValidationInformation[]);
|
|
272
|
+
}
|
|
273
|
+
type ValidationInformation = {
|
|
274
|
+
key: ValidationInformationKey;
|
|
275
|
+
message: string;
|
|
276
|
+
validator?: GenericCallback;
|
|
277
|
+
value: unknown;
|
|
481
278
|
};
|
|
279
|
+
type ValidationInformationKey = ValidatedPropertyKey;
|
|
482
280
|
//#endregion
|
|
483
281
|
//#region src/schematic.d.ts
|
|
484
282
|
/**
|
|
@@ -490,6 +288,35 @@ declare class Schematic<Model> {
|
|
|
490
288
|
constructor(properties: ValidatedProperty[]);
|
|
491
289
|
/**
|
|
492
290
|
* Does the value match the schema?
|
|
291
|
+
*
|
|
292
|
+
* Will assert that the values matches the schema and throw an error if it does not. The error will contain all validation information for the first property that fails validation.
|
|
293
|
+
* @param value Value to validate
|
|
294
|
+
* @param errors Throws an error for the first validation failure
|
|
295
|
+
* @returns `true` if the value matches the schema, otherwise throws an error
|
|
296
|
+
*/
|
|
297
|
+
is(value: unknown, errors: 'throw'): asserts value is Model;
|
|
298
|
+
/**
|
|
299
|
+
* Does the value match the schema?
|
|
300
|
+
*
|
|
301
|
+
* Will validate that the value matches the schema and return a result of `true` or all validation information for validation failures from the same depth in the object.
|
|
302
|
+
* @param value Value to validate
|
|
303
|
+
* @param errors All
|
|
304
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
305
|
+
*/
|
|
306
|
+
is(value: unknown, errors: 'all'): Result<true, ValidationInformation[]>;
|
|
307
|
+
/**
|
|
308
|
+
* Does the value match the schema?
|
|
309
|
+
*
|
|
310
|
+
* Will validate that the value matches the schema and return a result of `true` or all validation information for the failing property.
|
|
311
|
+
* @param value Value to validate
|
|
312
|
+
* @param errors First
|
|
313
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
314
|
+
*/
|
|
315
|
+
is(value: unknown, errors: 'first'): Result<true, ValidationInformation>;
|
|
316
|
+
/**
|
|
317
|
+
* Does the value match the schema?
|
|
318
|
+
*
|
|
319
|
+
* Will validate that the value matches the schema and return `true` or `false`, without any validation information for validation failures.
|
|
493
320
|
* @param value Value to validate
|
|
494
321
|
* @returns `true` if the value matches the schema, otherwise `false`
|
|
495
322
|
*/
|
|
@@ -512,6 +339,243 @@ declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer
|
|
|
512
339
|
*/
|
|
513
340
|
declare function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
514
341
|
//#endregion
|
|
342
|
+
//#region src/models/schema.plain.model.d.ts
|
|
343
|
+
/**
|
|
344
|
+
* A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
|
|
345
|
+
*/
|
|
346
|
+
type PlainSchema = {
|
|
347
|
+
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
|
|
348
|
+
} & {
|
|
349
|
+
$required?: never;
|
|
350
|
+
$type?: never;
|
|
351
|
+
$validators?: never;
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* A schema for validating objects
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* const schema: Schema = {
|
|
359
|
+
* name: 'string',
|
|
360
|
+
* age: 'number',
|
|
361
|
+
* tags: ['string', 'number'],
|
|
362
|
+
* };
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
type Schema = SchemaIndex;
|
|
366
|
+
/**
|
|
367
|
+
* A union of all valid types for a single schema entry
|
|
368
|
+
*
|
|
369
|
+
* Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
370
|
+
*/
|
|
371
|
+
type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
372
|
+
/**
|
|
373
|
+
* Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
|
|
374
|
+
*/
|
|
375
|
+
interface SchemaIndex {
|
|
376
|
+
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* A property definition with explicit type(s), an optional requirement flag, and optional validators
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```ts
|
|
383
|
+
* const prop: SchemaProperty = {
|
|
384
|
+
* $required: false,
|
|
385
|
+
* $type: ['string', 'number'],
|
|
386
|
+
* $validators: {
|
|
387
|
+
* string: (v) => v.length > 0,
|
|
388
|
+
* number: (v) => v > 0,
|
|
389
|
+
* },
|
|
390
|
+
* };
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
type SchemaProperty = {
|
|
394
|
+
/**
|
|
395
|
+
* Whether the property is required _(defaults to `true`)_
|
|
396
|
+
*/
|
|
397
|
+
$required?: boolean;
|
|
398
|
+
/**
|
|
399
|
+
* The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
|
|
400
|
+
*/
|
|
401
|
+
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
402
|
+
/**
|
|
403
|
+
* Optional validators keyed by {@link ValueName}, applied during validation
|
|
404
|
+
*/
|
|
405
|
+
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
408
|
+
* A union of valid types for a {@link SchemaProperty}'s `$type` field
|
|
409
|
+
*
|
|
410
|
+
* Can be a {@link Constructor}, {@link PlainSchema}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
411
|
+
*/
|
|
412
|
+
type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
413
|
+
/**
|
|
414
|
+
* A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
|
|
415
|
+
*
|
|
416
|
+
* Each key may hold a single validator or an array of validators that receive the typed value
|
|
417
|
+
*
|
|
418
|
+
* @template Value - `$type` value(s) to derive validator keys from
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```ts
|
|
422
|
+
* const validators: PropertyValidators<'string'> = {
|
|
423
|
+
* string: (value) => value.length > 0,
|
|
424
|
+
* };
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/models/misc.model.d.ts
|
|
430
|
+
/**
|
|
431
|
+
* Removes duplicate types from a tuple, preserving first occurrence order
|
|
432
|
+
*
|
|
433
|
+
* @template Value - Tuple to deduplicate
|
|
434
|
+
* @template Seen - Accumulator for already-seen types _(internal)_
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* ```ts
|
|
438
|
+
* // DeduplicateTuple<['string', 'number', 'string']>
|
|
439
|
+
* // => ['string', 'number']
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [infer Head, ...infer Tail] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
|
|
443
|
+
/**
|
|
444
|
+
* Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
|
|
445
|
+
*
|
|
446
|
+
* @template Value - Type to extract value names from
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* // ExtractValueNames<'string'> => 'string'
|
|
451
|
+
* // ExtractValueNames<['string', 'number']> => 'string' | 'number'
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
type ExtractValueNames<Value> = Value extends ValueName ? Value : Value extends (infer Item)[] ? ExtractValueNames<Item> : Value extends readonly (infer Item)[] ? ExtractValueNames<Item> : never;
|
|
455
|
+
/**
|
|
456
|
+
* Determines whether a schema entry is optional
|
|
457
|
+
*
|
|
458
|
+
* Returns `true` if the entry is a {@link SchemaProperty} or {@link NestedSchema} with `$required` set to `false`; otherwise returns `false`
|
|
459
|
+
*
|
|
460
|
+
* @template Value - Schema entry to check
|
|
461
|
+
*/
|
|
462
|
+
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
|
|
463
|
+
/**
|
|
464
|
+
* Extracts the last member from a union type by leveraging intersection of function return types
|
|
465
|
+
*
|
|
466
|
+
* @template Value - Union type
|
|
467
|
+
*/
|
|
468
|
+
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
|
|
469
|
+
/**
|
|
470
|
+
* Extracts keys from an object type that are optional
|
|
471
|
+
*
|
|
472
|
+
* @template Value - Object type to inspect
|
|
473
|
+
*/
|
|
474
|
+
type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
|
|
475
|
+
/**
|
|
476
|
+
* Extracts keys from an object type that are required _(i.e., not optional)_
|
|
477
|
+
*
|
|
478
|
+
* @template Value - Object type to inspect
|
|
479
|
+
*/
|
|
480
|
+
type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
481
|
+
/**
|
|
482
|
+
* Generates all permutations of a tuple type
|
|
483
|
+
*
|
|
484
|
+
* Used by {@link UnwrapSingle} to allow schema types in any order for small tuples _(length ≤ 5)_
|
|
485
|
+
*
|
|
486
|
+
* @template Tuple - Tuple to permute
|
|
487
|
+
* @template Elput - Accumulator for the current permutation _(internal; name is Tuple backwards)_
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```ts
|
|
491
|
+
* // TuplePermutations<['string', 'number']>
|
|
492
|
+
* // => ['string', 'number'] | ['number', 'string']
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : { [Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [...Elput, Tuple[Key]]> }[keyof Tuple & `${number}`];
|
|
496
|
+
/**
|
|
497
|
+
* Removes the element at a given index from a tuple
|
|
498
|
+
*
|
|
499
|
+
* Used internally by {@link TuplePermutations}
|
|
500
|
+
*
|
|
501
|
+
* @template Items - Tuple to remove from
|
|
502
|
+
* @template Item - Stringified index to remove
|
|
503
|
+
* @template Prefix - Accumulator for elements before the target _(internal)_
|
|
504
|
+
*/
|
|
505
|
+
type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends unknown[] = []> = Items extends [infer Head, ...infer Tail] ? `${Prefix['length']}` extends Item ? [...Prefix, ...Tail] : TupleRemoveAt<Tail, Item, [...Prefix, Head]> : Prefix;
|
|
506
|
+
/**
|
|
507
|
+
* Converts a union type into an intersection
|
|
508
|
+
*
|
|
509
|
+
* Uses the contravariance of function parameter types to collapse a union into an intersection
|
|
510
|
+
*
|
|
511
|
+
* @template Value - Union type to convert
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```ts
|
|
515
|
+
* // UnionToIntersection<{ a: 1 } | { b: 2 }>
|
|
516
|
+
* // => { a: 1 } & { b: 2 }
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends ((value: infer Item) => void) ? Item : never;
|
|
520
|
+
/**
|
|
521
|
+
* Converts a union type into an ordered tuple
|
|
522
|
+
*
|
|
523
|
+
* Repeatedly extracts the {@link LastOfUnion} member and prepends it to the accumulator
|
|
524
|
+
*
|
|
525
|
+
* @template Value - Union type to convert
|
|
526
|
+
* @template Items - Accumulator for the resulting tuple _(internal)_
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```ts
|
|
530
|
+
* // UnionToTuple<'a' | 'b' | 'c'>
|
|
531
|
+
* // => ['a', 'b', 'c']
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
|
|
535
|
+
/**
|
|
536
|
+
* Unwraps a single-element tuple to its inner type
|
|
537
|
+
*
|
|
538
|
+
* For tuples of length 2–5, returns all {@link TuplePermutations} to allow types in any order. Longer tuples are returned as-is
|
|
539
|
+
*
|
|
540
|
+
* @template Value - Tuple to potentially unwrap
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```ts
|
|
544
|
+
* // UnwrapSingle<['string']> => 'string'
|
|
545
|
+
* // UnwrapSingle<['string', 'number']> => ['string', 'number'] | ['number', 'string']
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
|
|
549
|
+
/**
|
|
550
|
+
* Basic value types
|
|
551
|
+
*/
|
|
552
|
+
type ValueName = keyof Values;
|
|
553
|
+
/**
|
|
554
|
+
* Maps type name strings to their TypeScript equivalents
|
|
555
|
+
*
|
|
556
|
+
* Used by the type system to resolve {@link ValueName} strings into actual types
|
|
557
|
+
*
|
|
558
|
+
* @example
|
|
559
|
+
* ```ts
|
|
560
|
+
* // Values['string'] => string
|
|
561
|
+
* // Values['date'] => Date
|
|
562
|
+
* // Values['null'] => null
|
|
563
|
+
* ```
|
|
564
|
+
*/
|
|
565
|
+
type Values = {
|
|
566
|
+
array: unknown[];
|
|
567
|
+
bigint: bigint;
|
|
568
|
+
boolean: boolean;
|
|
569
|
+
date: Date;
|
|
570
|
+
function: Function;
|
|
571
|
+
null: null;
|
|
572
|
+
number: number;
|
|
573
|
+
object: object;
|
|
574
|
+
string: string;
|
|
575
|
+
symbol: symbol;
|
|
576
|
+
undefined: undefined;
|
|
577
|
+
};
|
|
578
|
+
//#endregion
|
|
515
579
|
//#region src/helpers.d.ts
|
|
516
580
|
/**
|
|
517
581
|
* Creates a validator function for a given constructor
|
|
@@ -527,4 +591,4 @@ declare function instanceOf<Instance>(constructor: Constructor<Instance>): (valu
|
|
|
527
591
|
*/
|
|
528
592
|
declare function isSchematic(value: unknown): value is Schematic<never>;
|
|
529
593
|
//#endregion
|
|
530
|
-
export { type Schema, type Schematic, SchematicError, type TypedSchema, instanceOf, isSchematic, schematic };
|
|
594
|
+
export { type Schema, type Schematic, SchematicError, type TypedSchema, ValidationError, instanceOf, isSchematic, schematic };
|