@oscarpalmer/jhunal 0.14.0 → 0.15.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/helpers.js +11 -0
- package/dist/index.js +2 -2
- package/dist/jhunal.full.js +22 -1
- package/dist/models.js +8 -0
- package/dist/schematic.js +2 -0
- package/dist/validation/property.validation.js +1 -1
- package/package.json +1 -1
- package/src/helpers.ts +12 -1
- package/src/index.ts +1 -1
- package/src/models.ts +394 -36
- package/src/schematic.ts +10 -0
- package/types/helpers.d.ts +12 -1
- package/types/index.d.ts +1 -1
- package/types/models.d.ts +385 -11
- package/types/schematic.d.ts +10 -0
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/array/compact.js +0 -12
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/string.js +0 -24
package/src/models.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import type {GenericCallback, PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
1
|
+
import type {Constructor, GenericCallback, PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import {ERROR_NAME} from './constants';
|
|
3
3
|
import type {Schematic} from './schematic';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Removes duplicate types from a tuple, preserving first occurrence order
|
|
7
|
+
*
|
|
8
|
+
* @template Value - Tuple to deduplicate
|
|
9
|
+
* @template Seen - Accumulator for already-seen types _(internal)_
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // DeduplicateTuple<['string', 'number', 'string']>
|
|
14
|
+
* // => ['string', 'number']
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
7
17
|
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
|
|
8
18
|
infer Head,
|
|
9
19
|
...infer Tail,
|
|
@@ -13,6 +23,17 @@ type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Va
|
|
|
13
23
|
: DeduplicateTuple<Tail, [...Seen, Head]>
|
|
14
24
|
: Seen;
|
|
15
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
|
|
28
|
+
*
|
|
29
|
+
* @template Value - Type to extract value names from
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // ExtractValueNames<'string'> => 'string'
|
|
34
|
+
* // ExtractValueNames<['string', 'number']> => 'string' | 'number'
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
16
37
|
type ExtractValueNames<Value> = Value extends ValueName
|
|
17
38
|
? Value
|
|
18
39
|
: Value extends (infer Item)[]
|
|
@@ -22,7 +43,21 @@ type ExtractValueNames<Value> = Value extends ValueName
|
|
|
22
43
|
: never;
|
|
23
44
|
|
|
24
45
|
/**
|
|
25
|
-
*
|
|
46
|
+
* Infers the TypeScript type from a {@link Schema} definition
|
|
47
|
+
*
|
|
48
|
+
* @template Model - Schema to infer types from
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const userSchema = {
|
|
53
|
+
* name: 'string',
|
|
54
|
+
* age: 'number',
|
|
55
|
+
* address: { $required: false, $type: 'string' },
|
|
56
|
+
* } satisfies Schema;
|
|
57
|
+
*
|
|
58
|
+
* type User = Infer<typeof userSchema>;
|
|
59
|
+
* // { name: string; age: number; address?: string }
|
|
60
|
+
* ```
|
|
26
61
|
*/
|
|
27
62
|
export type Infer<Model extends Schema> = Simplify<
|
|
28
63
|
{
|
|
@@ -32,14 +67,31 @@ export type Infer<Model extends Schema> = Simplify<
|
|
|
32
67
|
}
|
|
33
68
|
>;
|
|
34
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Extracts keys from a {@link Schema} whose entries are optional _(i.e., `$required` is `false`)_
|
|
72
|
+
*
|
|
73
|
+
* @template Model - {@link Schema} to extract optional keys from
|
|
74
|
+
*/
|
|
35
75
|
type InferOptionalKeys<Model extends Schema> = keyof {
|
|
36
76
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
|
|
37
77
|
};
|
|
38
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
|
|
81
|
+
*
|
|
82
|
+
* @template Value - `$type` value _(single or array)_
|
|
83
|
+
*/
|
|
39
84
|
type InferPropertyType<Value> = Value extends (infer Item)[]
|
|
40
85
|
? InferPropertyValue<Item>
|
|
41
86
|
: InferPropertyValue<Value>;
|
|
42
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Maps a single type definition to its TypeScript equivalent
|
|
90
|
+
*
|
|
91
|
+
* Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link Schema} objects
|
|
92
|
+
*
|
|
93
|
+
* @template Value - single type definition
|
|
94
|
+
*/
|
|
43
95
|
type InferPropertyValue<Value> =
|
|
44
96
|
Value extends Constructor<infer Instance>
|
|
45
97
|
? Instance
|
|
@@ -51,14 +103,31 @@ type InferPropertyValue<Value> =
|
|
|
51
103
|
? Infer<Value>
|
|
52
104
|
: never;
|
|
53
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Extracts keys from a {@link Schema} whose entries are required _(i.e., `$required` is not `false`)_
|
|
108
|
+
*
|
|
109
|
+
* @template Model - Schema to extract required keys from
|
|
110
|
+
*/
|
|
54
111
|
type InferRequiredKeys<Model extends Schema> = keyof {
|
|
55
112
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
|
|
56
113
|
};
|
|
57
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
|
|
117
|
+
*
|
|
118
|
+
* @template Value - Schema entry value _(single or array)_
|
|
119
|
+
*/
|
|
58
120
|
type InferSchemaEntry<Value> = Value extends (infer Item)[]
|
|
59
121
|
? InferSchemaEntryValue<Item>
|
|
60
122
|
: InferSchemaEntryValue<Value>;
|
|
61
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Resolves a single schema entry to its TypeScript type
|
|
126
|
+
*
|
|
127
|
+
* Handles, in order: {@link Constructor} instances, {@link Schematic} models, {@link SchemaProperty} objects, {@link NestedSchema} objects, {@link ValueName} strings, and plain {@link Schema} objects
|
|
128
|
+
*
|
|
129
|
+
* @template Value - single schema entry
|
|
130
|
+
*/
|
|
62
131
|
type InferSchemaEntryValue<Value> =
|
|
63
132
|
Value extends Constructor<infer Instance>
|
|
64
133
|
? Instance
|
|
@@ -74,6 +143,13 @@ type InferSchemaEntryValue<Value> =
|
|
|
74
143
|
? Infer<Value>
|
|
75
144
|
: never;
|
|
76
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Determines whether a schema entry is optional
|
|
148
|
+
*
|
|
149
|
+
* Returns `true` if the entry is a {@link SchemaProperty} or {@link NestedSchema} with `$required` set to `false`; otherwise returns `false`
|
|
150
|
+
*
|
|
151
|
+
* @template Value - Schema entry to check
|
|
152
|
+
*/
|
|
77
153
|
type IsOptionalProperty<Value> = Value extends SchemaProperty
|
|
78
154
|
? Value['$required'] extends false
|
|
79
155
|
? true
|
|
@@ -84,48 +160,115 @@ type IsOptionalProperty<Value> = Value extends SchemaProperty
|
|
|
84
160
|
: false
|
|
85
161
|
: false;
|
|
86
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Extracts the last member from a union type by leveraging intersection of function return types
|
|
165
|
+
*
|
|
166
|
+
* @template Value - Union type
|
|
167
|
+
*/
|
|
87
168
|
type LastOfUnion<Value> =
|
|
88
169
|
UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item
|
|
89
170
|
? Item
|
|
90
171
|
: never;
|
|
91
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Maps each element of a tuple through {@link ToValueType}
|
|
175
|
+
*
|
|
176
|
+
* @template Value - Tuple of types to map
|
|
177
|
+
*/
|
|
92
178
|
type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail]
|
|
93
179
|
? [ToValueType<Head>, ...MapToValueTypes<Tail>]
|
|
94
180
|
: [];
|
|
95
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Maps each element of a tuple through {@link ToSchemaPropertyTypeEach}
|
|
184
|
+
*
|
|
185
|
+
* @template Value - Tuple of types to map
|
|
186
|
+
*/
|
|
96
187
|
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail]
|
|
97
188
|
? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>]
|
|
98
189
|
: [];
|
|
99
190
|
|
|
100
191
|
/**
|
|
101
|
-
* A nested schema
|
|
192
|
+
* A nested schema definition that may include a `$required` flag alongside arbitrary string-keyed properties
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const address: NestedSchema = {
|
|
197
|
+
* $required: false,
|
|
198
|
+
* street: 'string',
|
|
199
|
+
* city: 'string',
|
|
200
|
+
* };
|
|
201
|
+
* ```
|
|
102
202
|
*/
|
|
103
203
|
export type NestedSchema = {
|
|
204
|
+
/**
|
|
205
|
+
* Whether the nested schema is required (defaults to `true`)
|
|
206
|
+
*/
|
|
104
207
|
$required?: boolean;
|
|
105
|
-
|
|
106
|
-
};
|
|
208
|
+
} & Schema;
|
|
107
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Extracts keys from an object type that are optional
|
|
212
|
+
*
|
|
213
|
+
* @template Value - Object type to inspect
|
|
214
|
+
*/
|
|
108
215
|
type OptionalKeys<Value> = {
|
|
109
216
|
[Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
|
|
110
217
|
}[keyof Value];
|
|
111
218
|
|
|
219
|
+
/**
|
|
220
|
+
* A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
|
|
221
|
+
*/
|
|
112
222
|
type PlainSchema = {
|
|
113
223
|
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
114
224
|
};
|
|
115
225
|
|
|
226
|
+
/**
|
|
227
|
+
* A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
|
|
228
|
+
*
|
|
229
|
+
* Each key may hold a single validator or an array of validators that receive the typed value
|
|
230
|
+
*
|
|
231
|
+
* @template Value - `$type` value(s) to derive validator keys from
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const validators: PropertyValidators<'string'> = {
|
|
236
|
+
* string: (value) => value.length > 0,
|
|
237
|
+
* };
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
116
240
|
type PropertyValidators<Value> = {
|
|
117
241
|
[Key in ExtractValueNames<Value>]?:
|
|
118
242
|
| ((value: Values[Key]) => boolean)
|
|
119
243
|
| Array<(value: Values[Key]) => boolean>;
|
|
120
244
|
};
|
|
121
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Extracts keys from an object type that are required _(i.e., not optional)_
|
|
248
|
+
*
|
|
249
|
+
* @template Value - Object type to inspect
|
|
250
|
+
*/
|
|
122
251
|
type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
123
252
|
|
|
124
253
|
/**
|
|
125
254
|
* A schema for validating objects
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```ts
|
|
258
|
+
* const schema: Schema = {
|
|
259
|
+
* name: 'string',
|
|
260
|
+
* age: 'number',
|
|
261
|
+
* tags: ['string', 'number'],
|
|
262
|
+
* };
|
|
263
|
+
* ```
|
|
126
264
|
*/
|
|
127
265
|
export type Schema = SchemaIndex;
|
|
128
266
|
|
|
267
|
+
/**
|
|
268
|
+
* A union of all valid types for a single schema entry
|
|
269
|
+
*
|
|
270
|
+
* Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
271
|
+
*/
|
|
129
272
|
type SchemaEntry =
|
|
130
273
|
| Constructor
|
|
131
274
|
| Schema
|
|
@@ -134,19 +277,48 @@ type SchemaEntry =
|
|
|
134
277
|
| ValueName
|
|
135
278
|
| ((value: unknown) => boolean);
|
|
136
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
|
|
282
|
+
*/
|
|
137
283
|
interface SchemaIndex {
|
|
138
284
|
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
139
285
|
}
|
|
140
286
|
|
|
141
287
|
/**
|
|
142
|
-
* A property definition with explicit type(s), optional requirement flag, and optional validators
|
|
288
|
+
* A property definition with explicit type(s), an optional requirement flag, and optional validators
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* const prop: SchemaProperty = {
|
|
293
|
+
* $required: false,
|
|
294
|
+
* $type: ['string', 'number'],
|
|
295
|
+
* $validators: {
|
|
296
|
+
* string: (v) => v.length > 0,
|
|
297
|
+
* number: (v) => v > 0,
|
|
298
|
+
* },
|
|
299
|
+
* };
|
|
300
|
+
* ```
|
|
143
301
|
*/
|
|
144
302
|
export type SchemaProperty = {
|
|
303
|
+
/**
|
|
304
|
+
* Whether the property is required _(defaults to `true`)_
|
|
305
|
+
*/
|
|
145
306
|
$required?: boolean;
|
|
307
|
+
/**
|
|
308
|
+
* The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
|
|
309
|
+
*/
|
|
146
310
|
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
311
|
+
/**
|
|
312
|
+
* Optional validators keyed by {@link ValueName}, applied during validation
|
|
313
|
+
*/
|
|
147
314
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
148
315
|
};
|
|
149
316
|
|
|
317
|
+
/**
|
|
318
|
+
* A union of valid types for a {@link SchemaProperty}'s `$type` field
|
|
319
|
+
*
|
|
320
|
+
* Can be a {@link Constructor}, {@link PlainSchema}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
321
|
+
*/
|
|
150
322
|
type SchemaPropertyType =
|
|
151
323
|
| Constructor
|
|
152
324
|
| PlainSchema
|
|
@@ -154,6 +326,14 @@ type SchemaPropertyType =
|
|
|
154
326
|
| ValueName
|
|
155
327
|
| ((value: unknown) => boolean);
|
|
156
328
|
|
|
329
|
+
/**
|
|
330
|
+
* A custom error class for schema validation failures, with its `name` set to {@link ERROR_NAME}
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```ts
|
|
334
|
+
* throw new SchematicError('Expected a string, received a number');
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
157
337
|
export class SchematicError extends Error {
|
|
158
338
|
constructor(message: string) {
|
|
159
339
|
super(message);
|
|
@@ -162,44 +342,80 @@ export class SchematicError extends Error {
|
|
|
162
342
|
}
|
|
163
343
|
}
|
|
164
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Converts a type into its corresponding {@link SchemaPropertyType}-representation
|
|
347
|
+
*
|
|
348
|
+
* Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
|
|
349
|
+
*
|
|
350
|
+
* @template Value - type to convert
|
|
351
|
+
*/
|
|
165
352
|
type ToSchemaPropertyType<Value> = UnwrapSingle<
|
|
166
353
|
DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>
|
|
167
354
|
>;
|
|
168
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Converts a single type to its schema property equivalent
|
|
358
|
+
*
|
|
359
|
+
* {@link NestedSchema} values have `$required` stripped, plain objects become {@link TypedSchema}, and primitives go through {@link ToValueType}
|
|
360
|
+
*
|
|
361
|
+
* @template Value - type to convert
|
|
362
|
+
*/
|
|
169
363
|
type ToSchemaPropertyTypeEach<Value> = Value extends NestedSchema
|
|
170
364
|
? Omit<Value, '$required'>
|
|
171
365
|
: Value extends PlainObject
|
|
172
366
|
? TypedSchema<Value>
|
|
173
367
|
: ToValueType<Value>;
|
|
174
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Converts a type into its corresponding {@link ValueName}-representation
|
|
371
|
+
*
|
|
372
|
+
* Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
|
|
373
|
+
*
|
|
374
|
+
* @template Value - type to convert
|
|
375
|
+
*/
|
|
175
376
|
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
176
377
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Maps a type to its {@link ValueName} string equivalent
|
|
380
|
+
*
|
|
381
|
+
* Resolves {@link Schematic} types as-is, then performs a reverse-lookup against {@link Values} _(excluding `'object'`)_ to find a matching key. If no match is found, `object` types resolve to `'object'` or a type-guard function, and all other unrecognised types resolve to a type-guard function
|
|
382
|
+
*
|
|
383
|
+
* @template Value - type to map
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* // ToValueType<string> => 'string'
|
|
388
|
+
* // ToValueType<number[]> => 'array'
|
|
389
|
+
* // ToValueType<Date> => 'date'
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
type ToValueType<Value> =
|
|
393
|
+
Value extends Schematic<any>
|
|
394
|
+
? Value
|
|
395
|
+
: {
|
|
396
|
+
[Key in keyof Omit<Values, 'object'>]: Value extends Values[Key] ? Key : never;
|
|
397
|
+
}[keyof Omit<Values, 'object'>] extends infer Match
|
|
398
|
+
? [Match] extends [never]
|
|
399
|
+
? Value extends object
|
|
400
|
+
? 'object' | ((value: unknown) => value is Value)
|
|
401
|
+
: (value: unknown) => value is Value
|
|
402
|
+
: Match
|
|
403
|
+
: never;
|
|
202
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Generates all permutations of a tuple type
|
|
407
|
+
*
|
|
408
|
+
* Used by {@link UnwrapSingle} to allow schema types in any order for small tuples _(length ≤ 5)_
|
|
409
|
+
*
|
|
410
|
+
* @template Tuple - Tuple to permute
|
|
411
|
+
* @template Elput - Accumulator for the current permutation _(internal; name is Tuple backwards)_
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```ts
|
|
415
|
+
* // TuplePermutations<['string', 'number']>
|
|
416
|
+
* // => ['string', 'number'] | ['number', 'string']
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
203
419
|
type TuplePermutations<
|
|
204
420
|
Tuple extends unknown[],
|
|
205
421
|
Elput extends unknown[] = [],
|
|
@@ -212,6 +428,15 @@ type TuplePermutations<
|
|
|
212
428
|
>;
|
|
213
429
|
}[keyof Tuple & `${number}`];
|
|
214
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Removes the element at a given index from a tuple
|
|
433
|
+
*
|
|
434
|
+
* Used internally by {@link TuplePermutations}
|
|
435
|
+
*
|
|
436
|
+
* @template Items - Tuple to remove from
|
|
437
|
+
* @template Item - Stringified index to remove
|
|
438
|
+
* @template Prefix - Accumulator for elements before the target _(internal)_
|
|
439
|
+
*/
|
|
215
440
|
type TupleRemoveAt<
|
|
216
441
|
Items extends unknown[],
|
|
217
442
|
Item extends string,
|
|
@@ -222,6 +447,18 @@ type TupleRemoveAt<
|
|
|
222
447
|
: TupleRemoveAt<Tail, Item, [...Prefix, Head]>
|
|
223
448
|
: Prefix;
|
|
224
449
|
|
|
450
|
+
/**
|
|
451
|
+
* A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
|
|
452
|
+
*
|
|
453
|
+
* @template Value - Property's type _(including `undefined`)_
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```ts
|
|
457
|
+
* // For `{ name?: string }`, the `name` key produces:
|
|
458
|
+
* // TypedPropertyOptional<string | undefined>
|
|
459
|
+
* // => { $required: false; $type: 'string'; ... }
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
225
462
|
export type TypedPropertyOptional<Value> = {
|
|
226
463
|
/**
|
|
227
464
|
* The property is not required
|
|
@@ -237,6 +474,18 @@ export type TypedPropertyOptional<Value> = {
|
|
|
237
474
|
$validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
|
|
238
475
|
};
|
|
239
476
|
|
|
477
|
+
/**
|
|
478
|
+
* A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
|
|
479
|
+
*
|
|
480
|
+
* @template Value - Property's type
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```ts
|
|
484
|
+
* // For `{ name: string }`, the `name` key produces:
|
|
485
|
+
* // TypedPropertyRequired<string>
|
|
486
|
+
* // => { $required?: true; $type: 'string'; ... }
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
240
489
|
export type TypedPropertyRequired<Value> = {
|
|
241
490
|
/**
|
|
242
491
|
* The property is required _(defaults to `true`)_
|
|
@@ -253,7 +502,22 @@ export type TypedPropertyRequired<Value> = {
|
|
|
253
502
|
};
|
|
254
503
|
|
|
255
504
|
/**
|
|
256
|
-
*
|
|
505
|
+
* Creates a schema type constrained to match a TypeScript type
|
|
506
|
+
*
|
|
507
|
+
* Required keys map to {@link ToSchemaType} or {@link TypedPropertyRequired}; plain object values may also use {@link Schematic}. Optional keys map to {@link TypedPropertyOptional} or, for plain objects, {@link TypedSchemaOptional}
|
|
508
|
+
*
|
|
509
|
+
* @template Model - Object type to generate a schema for
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```ts
|
|
513
|
+
* type User = { name: string; age: number; bio?: string };
|
|
514
|
+
*
|
|
515
|
+
* const schema: TypedSchema<User> = {
|
|
516
|
+
* name: 'string',
|
|
517
|
+
* age: 'number',
|
|
518
|
+
* bio: { $required: false, $type: 'string' },
|
|
519
|
+
* };
|
|
520
|
+
* ```
|
|
257
521
|
*/
|
|
258
522
|
export type TypedSchema<Model extends PlainObject> = Simplify<
|
|
259
523
|
{
|
|
@@ -269,54 +533,148 @@ export type TypedSchema<Model extends PlainObject> = Simplify<
|
|
|
269
533
|
}
|
|
270
534
|
>;
|
|
271
535
|
|
|
536
|
+
/**
|
|
537
|
+
* A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
|
|
538
|
+
*
|
|
539
|
+
* @template Model - Nested object type
|
|
540
|
+
*/
|
|
272
541
|
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
273
542
|
$required: false;
|
|
274
543
|
} & TypedSchema<Model>;
|
|
275
544
|
|
|
545
|
+
/**
|
|
546
|
+
* A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
|
|
547
|
+
*
|
|
548
|
+
* @template Model - Nested object type
|
|
549
|
+
*/
|
|
276
550
|
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
277
551
|
$required?: true;
|
|
278
552
|
} & TypedSchema<Model>;
|
|
279
553
|
|
|
554
|
+
/**
|
|
555
|
+
* Converts a union type into an intersection
|
|
556
|
+
*
|
|
557
|
+
* Uses the contravariance of function parameter types to collapse a union into an intersection
|
|
558
|
+
*
|
|
559
|
+
* @template Value - Union type to convert
|
|
560
|
+
*
|
|
561
|
+
* @example
|
|
562
|
+
* ```ts
|
|
563
|
+
* // UnionToIntersection<{ a: 1 } | { b: 2 }>
|
|
564
|
+
* // => { a: 1 } & { b: 2 }
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
280
567
|
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (
|
|
281
568
|
value: infer Item,
|
|
282
569
|
) => void
|
|
283
570
|
? Item
|
|
284
571
|
: never;
|
|
285
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Converts a union type into an ordered tuple
|
|
575
|
+
*
|
|
576
|
+
* Repeatedly extracts the {@link LastOfUnion} member and prepends it to the accumulator
|
|
577
|
+
*
|
|
578
|
+
* @template Value - Union type to convert
|
|
579
|
+
* @template Items - Accumulator for the resulting tuple _(internal)_
|
|
580
|
+
*
|
|
581
|
+
* @example
|
|
582
|
+
* ```ts
|
|
583
|
+
* // UnionToTuple<'a' | 'b' | 'c'>
|
|
584
|
+
* // => ['a', 'b', 'c']
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
286
587
|
type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never]
|
|
287
588
|
? Items
|
|
288
589
|
: UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
|
|
289
590
|
|
|
591
|
+
/**
|
|
592
|
+
* Unwraps a single-element tuple to its inner type
|
|
593
|
+
*
|
|
594
|
+
* For tuples of length 2–5, returns all {@link TuplePermutations} to allow types in any order. Longer tuples are returned as-is
|
|
595
|
+
*
|
|
596
|
+
* @template Value - Tuple to potentially unwrap
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* ```ts
|
|
600
|
+
* // UnwrapSingle<['string']> => 'string'
|
|
601
|
+
* // UnwrapSingle<['string', 'number']> => ['string', 'number'] | ['number', 'string']
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
290
604
|
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
|
|
291
605
|
? Only
|
|
292
606
|
: Value['length'] extends 1 | 2 | 3 | 4 | 5
|
|
293
607
|
? TuplePermutations<Value>
|
|
294
608
|
: Value;
|
|
295
609
|
|
|
610
|
+
/**
|
|
611
|
+
* The runtime representation of a parsed schema property, used internally during validation
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* ```ts
|
|
615
|
+
* const parsed: ValidatedProperty = {
|
|
616
|
+
* key: 'age',
|
|
617
|
+
* required: true,
|
|
618
|
+
* types: ['number'],
|
|
619
|
+
* validators: { number: [(v) => v > 0] },
|
|
620
|
+
* };
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
296
623
|
export type ValidatedProperty = {
|
|
624
|
+
/**
|
|
625
|
+
* The property name in the schema
|
|
626
|
+
*/
|
|
297
627
|
key: string;
|
|
628
|
+
/**
|
|
629
|
+
* Whether the property is required
|
|
630
|
+
*/
|
|
298
631
|
required: boolean;
|
|
632
|
+
/**
|
|
633
|
+
* The allowed types for this property
|
|
634
|
+
*/
|
|
299
635
|
types: ValidatedPropertyType[];
|
|
636
|
+
/**
|
|
637
|
+
* Custom validators grouped by {@link ValueName}
|
|
638
|
+
*/
|
|
300
639
|
validators: ValidatedPropertyValidators;
|
|
301
640
|
};
|
|
302
641
|
|
|
642
|
+
/**
|
|
643
|
+
* A union of valid types for a {@link ValidatedProperty}'s `types` array
|
|
644
|
+
*
|
|
645
|
+
* Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
|
|
646
|
+
*/
|
|
303
647
|
export type ValidatedPropertyType =
|
|
304
648
|
| GenericCallback
|
|
305
649
|
| Schematic<unknown>
|
|
306
650
|
| ValidatedProperty
|
|
307
651
|
| ValueName;
|
|
308
652
|
|
|
653
|
+
/**
|
|
654
|
+
* A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
|
|
655
|
+
*
|
|
656
|
+
* Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
|
|
657
|
+
*/
|
|
309
658
|
export type ValidatedPropertyValidators = {
|
|
310
659
|
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
311
660
|
};
|
|
312
661
|
|
|
313
662
|
/**
|
|
314
|
-
*
|
|
663
|
+
* Basic value types
|
|
315
664
|
*/
|
|
316
665
|
export type ValueName = keyof Values;
|
|
317
666
|
|
|
318
667
|
/**
|
|
319
|
-
*
|
|
668
|
+
* Maps type name strings to their TypeScript equivalents
|
|
669
|
+
*
|
|
670
|
+
* Used by the type system to resolve {@link ValueName} strings into actual types
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```ts
|
|
674
|
+
* // Values['string'] => string
|
|
675
|
+
* // Values['date'] => Date
|
|
676
|
+
* // Values['null'] => null
|
|
677
|
+
* ```
|
|
320
678
|
*/
|
|
321
679
|
export type Values = {
|
|
322
680
|
array: unknown[];
|
package/src/schematic.ts
CHANGED
|
@@ -30,6 +30,8 @@ export class Schematic<Model> {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Does the value match the schema?
|
|
33
|
+
* @param value - Value to validate
|
|
34
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
33
35
|
*/
|
|
34
36
|
is(value: unknown): value is Model {
|
|
35
37
|
return validateObject(value, this.#properties);
|
|
@@ -38,11 +40,19 @@ export class Schematic<Model> {
|
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Create a schematic from a schema
|
|
43
|
+
* @template Model - Schema type
|
|
44
|
+
* @param schema - Schema to create the schematic from
|
|
45
|
+
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
46
|
+
* @returns A schematic for the given schema
|
|
41
47
|
*/
|
|
42
48
|
export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<Model>>;
|
|
43
49
|
|
|
44
50
|
/**
|
|
45
51
|
* Create a schematic from a typed schema
|
|
52
|
+
* @template Model - Existing type
|
|
53
|
+
* @param schema - Typed schema to create the schematic from
|
|
54
|
+
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
55
|
+
* @returns A schematic for the given typed schema
|
|
46
56
|
*/
|
|
47
57
|
export function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
48
58
|
|
package/types/helpers.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import type { Constructor } from '
|
|
1
|
+
import type { Constructor } from '@oscarpalmer/atoms/models';
|
|
2
2
|
import type { Schematic } from './schematic';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a validator function for a given constructor
|
|
5
|
+
* @param constructor - Constructor to check against
|
|
6
|
+
* @throws Will throw a `TypeError` if the provided argument is not a valid constructor
|
|
7
|
+
* @returns Validator function that checks if a value is an instance of the constructor
|
|
8
|
+
*/
|
|
3
9
|
export declare function instanceOf<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
|
|
10
|
+
/**
|
|
11
|
+
* Is the value a schematic?
|
|
12
|
+
* @param value Value to check
|
|
13
|
+
* @returns `true` if the value is a schematic, `false` otherwise
|
|
14
|
+
*/
|
|
4
15
|
export declare function isSchematic(value: unknown): value is Schematic<never>;
|