@oscarpalmer/jhunal 0.16.0 → 0.18.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 +283 -262
- package/dist/index.mjs +189 -56
- 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 +81 -0
- package/dist/models/validation.model.mjs +21 -0
- package/dist/schematic.d.mts +20 -6
- package/dist/schematic.mjs +7 -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 +63 -11
- package/package.json +3 -3
- package/src/constants.ts +84 -18
- 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 +123 -0
- package/src/schematic.ts +29 -18
- package/src/validation/property.validation.ts +46 -36
- package/src/validation/value.validation.ts +115 -15
- package/dist/models.d.mts +0 -507
- package/dist/models.mjs +0 -18
- package/src/models.ts +0 -691
package/dist/index.d.mts
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
import { Constructor, GenericCallback, PlainObject, Simplify } from "@oscarpalmer/atoms/models";
|
|
2
2
|
|
|
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;
|
|
3
|
+
//#region src/models/infer.model.d.ts
|
|
29
4
|
/**
|
|
30
5
|
* Infers the TypeScript type from a {@link Schema} definition
|
|
31
6
|
*
|
|
@@ -83,88 +58,278 @@ type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryVa
|
|
|
83
58
|
*
|
|
84
59
|
* @template Value - single schema entry
|
|
85
60
|
*/
|
|
86
|
-
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends
|
|
61
|
+
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;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/models/transform.model.d.ts
|
|
87
64
|
/**
|
|
88
|
-
*
|
|
65
|
+
* Maps each element of a tuple through {@link ToValueType}
|
|
89
66
|
*
|
|
90
|
-
*
|
|
67
|
+
* @template Value - Tuple of types to map
|
|
68
|
+
*/
|
|
69
|
+
type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToValueType<Head>, ...MapToValueTypes<Tail>] : [];
|
|
70
|
+
/**
|
|
71
|
+
* Maps each element of a tuple through {@link ToSchemaPropertyTypeEach}
|
|
91
72
|
*
|
|
92
|
-
* @template Value -
|
|
73
|
+
* @template Value - Tuple of types to map
|
|
93
74
|
*/
|
|
94
|
-
type
|
|
95
|
-
$required?: boolean;
|
|
96
|
-
} ? Value extends {
|
|
97
|
-
$required: false;
|
|
98
|
-
} ? true : false : false;
|
|
75
|
+
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
|
|
99
76
|
/**
|
|
100
|
-
*
|
|
77
|
+
* Converts a type into its corresponding {@link SchemaPropertyType}-representation
|
|
101
78
|
*
|
|
102
|
-
*
|
|
79
|
+
* Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
|
|
80
|
+
*
|
|
81
|
+
* @template Value - type to convert
|
|
103
82
|
*/
|
|
104
|
-
type
|
|
83
|
+
type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>>;
|
|
105
84
|
/**
|
|
106
|
-
*
|
|
85
|
+
* Converts a single type to its schema property equivalent
|
|
107
86
|
*
|
|
108
|
-
* @
|
|
87
|
+
* {@link NestedSchema} values have `$required` stripped, plain objects become {@link TypedSchema}, and primitives go through {@link ToValueType}
|
|
88
|
+
*
|
|
89
|
+
* @template Value - type to convert
|
|
109
90
|
*/
|
|
110
|
-
type
|
|
91
|
+
type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
|
|
111
92
|
/**
|
|
112
|
-
*
|
|
93
|
+
* Converts a type into its corresponding {@link ValueName}-representation
|
|
113
94
|
*
|
|
114
|
-
*
|
|
95
|
+
* Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
|
|
96
|
+
*
|
|
97
|
+
* @template Value - type to convert
|
|
115
98
|
*/
|
|
116
|
-
type
|
|
99
|
+
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
117
100
|
/**
|
|
118
|
-
*
|
|
101
|
+
* Maps a type to its {@link ValueName} string equivalent
|
|
102
|
+
*
|
|
103
|
+
* 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
|
|
104
|
+
*
|
|
105
|
+
* @template Value - type to map
|
|
119
106
|
*
|
|
120
107
|
* @example
|
|
121
108
|
* ```ts
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
|
|
109
|
+
* // ToValueType<string> => 'string'
|
|
110
|
+
* // ToValueType<number[]> => 'array'
|
|
111
|
+
* // ToValueType<Date> => 'date'
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
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;
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/models/schema.typed.model.d.ts
|
|
117
|
+
/**
|
|
118
|
+
* A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
|
|
119
|
+
*
|
|
120
|
+
* @template Value - Property's type _(including `undefined`)_
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* // For `{ name?: string }`, the `name` key produces:
|
|
125
|
+
* // TypedPropertyOptional<string | undefined>
|
|
126
|
+
* // => { $required: false; $type: 'string'; ... }
|
|
127
127
|
* ```
|
|
128
128
|
*/
|
|
129
|
-
type
|
|
129
|
+
type TypedPropertyOptional<Value> = {
|
|
130
130
|
/**
|
|
131
|
-
*
|
|
131
|
+
* The property is not required
|
|
132
132
|
*/
|
|
133
|
-
$required
|
|
134
|
-
|
|
133
|
+
$required: false;
|
|
134
|
+
/**
|
|
135
|
+
* The type(s) of the property
|
|
136
|
+
*/
|
|
137
|
+
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
138
|
+
/**
|
|
139
|
+
* Custom validators for the property and its types
|
|
140
|
+
*/
|
|
141
|
+
$validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
|
|
142
|
+
};
|
|
135
143
|
/**
|
|
136
|
-
*
|
|
144
|
+
* A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
|
|
137
145
|
*
|
|
138
|
-
* @template Value -
|
|
146
|
+
* @template Value - Property's type
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* // For `{ name: string }`, the `name` key produces:
|
|
151
|
+
* // TypedPropertyRequired<string>
|
|
152
|
+
* // => { $required?: true; $type: 'string'; ... }
|
|
153
|
+
* ```
|
|
139
154
|
*/
|
|
140
|
-
type
|
|
155
|
+
type TypedPropertyRequired<Value> = {
|
|
156
|
+
/**
|
|
157
|
+
* The property is required _(defaults to `true`)_
|
|
158
|
+
*/
|
|
159
|
+
$required?: true;
|
|
160
|
+
/**
|
|
161
|
+
* The type(s) of the property
|
|
162
|
+
*/
|
|
163
|
+
$type: ToSchemaPropertyType<Value>;
|
|
164
|
+
/**
|
|
165
|
+
* Custom validators for the property and its types
|
|
166
|
+
*/
|
|
167
|
+
$validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
|
|
168
|
+
};
|
|
141
169
|
/**
|
|
142
|
-
*
|
|
170
|
+
* Creates a schema type constrained to match a TypeScript type
|
|
171
|
+
*
|
|
172
|
+
* 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}
|
|
173
|
+
*
|
|
174
|
+
* @template Model - Object type to generate a schema for
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* type User = { name: string; age: number; bio?: string };
|
|
179
|
+
*
|
|
180
|
+
* const schema: TypedSchema<User> = {
|
|
181
|
+
* name: 'string',
|
|
182
|
+
* age: 'number',
|
|
183
|
+
* bio: { $required: false, $type: 'string' },
|
|
184
|
+
* };
|
|
185
|
+
* ```
|
|
143
186
|
*/
|
|
144
|
-
type
|
|
145
|
-
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
146
|
-
};
|
|
187
|
+
type TypedSchema<Model extends PlainObject> = Simplify<{ [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]> } & { [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> | Schematic<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]> }>;
|
|
147
188
|
/**
|
|
148
|
-
* A
|
|
189
|
+
* A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
|
|
149
190
|
*
|
|
150
|
-
*
|
|
191
|
+
* @template Model - Nested object type
|
|
192
|
+
*/
|
|
193
|
+
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
194
|
+
$required: false;
|
|
195
|
+
} & TypedSchema<Model>;
|
|
196
|
+
/**
|
|
197
|
+
* A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
|
|
151
198
|
*
|
|
152
|
-
* @template
|
|
199
|
+
* @template Model - Nested object type
|
|
200
|
+
*/
|
|
201
|
+
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
202
|
+
$required?: true;
|
|
203
|
+
} & TypedSchema<Model>;
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/models/validation.model.d.ts
|
|
206
|
+
/**
|
|
207
|
+
* A custom error class for schematic validation failures
|
|
208
|
+
*/
|
|
209
|
+
declare class SchematicError extends Error {
|
|
210
|
+
constructor(message: string);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The runtime representation of a parsed schema property, used internally during validation
|
|
153
214
|
*
|
|
154
215
|
* @example
|
|
155
216
|
* ```ts
|
|
156
|
-
* const
|
|
157
|
-
*
|
|
217
|
+
* const parsed: ValidatedProperty = {
|
|
218
|
+
* key: 'age',
|
|
219
|
+
* required: true,
|
|
220
|
+
* types: ['number'],
|
|
221
|
+
* validators: { number: [(v) => v > 0] },
|
|
158
222
|
* };
|
|
159
223
|
* ```
|
|
160
224
|
*/
|
|
161
|
-
type
|
|
225
|
+
type ValidatedProperty = {
|
|
226
|
+
/**
|
|
227
|
+
* The property name in the schema
|
|
228
|
+
*/
|
|
229
|
+
key: ValidatedPropertyKey;
|
|
230
|
+
/**
|
|
231
|
+
* Whether the property is required
|
|
232
|
+
*/
|
|
233
|
+
required: boolean;
|
|
234
|
+
/**
|
|
235
|
+
* The allowed types for this property
|
|
236
|
+
*/
|
|
237
|
+
types: ValidatedPropertyType[];
|
|
238
|
+
/**
|
|
239
|
+
* Custom validators grouped by {@link ValueName}
|
|
240
|
+
*/
|
|
241
|
+
validators: ValidatedPropertyValidators;
|
|
242
|
+
};
|
|
162
243
|
/**
|
|
163
|
-
*
|
|
244
|
+
* Property name in schema
|
|
245
|
+
*/
|
|
246
|
+
type ValidatedPropertyKey = {
|
|
247
|
+
/**
|
|
248
|
+
* Full property key, including parent keys for nested properties _(e.g., `address.street`)_
|
|
249
|
+
*/
|
|
250
|
+
full: string;
|
|
251
|
+
/**
|
|
252
|
+
* The last segment of the property key _(e.g., `street` for `address.street`)_
|
|
253
|
+
*/
|
|
254
|
+
short: string;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* A union of valid types for a {@link ValidatedProperty}'s `types` array
|
|
164
258
|
*
|
|
165
|
-
* @
|
|
259
|
+
* Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
|
|
166
260
|
*/
|
|
167
|
-
type
|
|
261
|
+
type ValidatedPropertyType = GenericCallback | ValidatedProperty[] | Schematic<unknown> | ValueName;
|
|
262
|
+
/**
|
|
263
|
+
* A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
|
|
264
|
+
*
|
|
265
|
+
* Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
|
|
266
|
+
*/
|
|
267
|
+
type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
|
|
268
|
+
declare class ValidationError extends Error {
|
|
269
|
+
readonly information: ValidationInformation[];
|
|
270
|
+
constructor(information: ValidationInformation[]);
|
|
271
|
+
}
|
|
272
|
+
type ValidationInformation = {
|
|
273
|
+
key: ValidationInformationKey;
|
|
274
|
+
message: string;
|
|
275
|
+
validator?: GenericCallback;
|
|
276
|
+
};
|
|
277
|
+
type ValidationInformationKey = ValidatedPropertyKey;
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/schematic.d.ts
|
|
280
|
+
/**
|
|
281
|
+
* A schematic for validating objects
|
|
282
|
+
*/
|
|
283
|
+
declare class Schematic<Model> {
|
|
284
|
+
#private;
|
|
285
|
+
private readonly $schematic;
|
|
286
|
+
constructor(properties: ValidatedProperty[]);
|
|
287
|
+
/**
|
|
288
|
+
* Does the value match the schema?
|
|
289
|
+
*
|
|
290
|
+
* 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.
|
|
291
|
+
* @param value Value to validate
|
|
292
|
+
* @param errors Throws an error for the first validation failure
|
|
293
|
+
* @returns `true` if the value matches the schema, otherwise throws an error
|
|
294
|
+
*/
|
|
295
|
+
is(value: unknown, errors: 'throw'): asserts value is Model;
|
|
296
|
+
/**
|
|
297
|
+
* Does the value match the schema?
|
|
298
|
+
*
|
|
299
|
+
* Will validate that the value matches the schema and return `true` or `false`, without any validation information for validation failures.
|
|
300
|
+
* @param value Value to validate
|
|
301
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
302
|
+
*/
|
|
303
|
+
is(value: unknown): value is Model;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Create a schematic from a schema
|
|
307
|
+
* @template Model Schema type
|
|
308
|
+
* @param schema Schema to create the schematic from
|
|
309
|
+
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
310
|
+
* @returns A schematic for the given schema
|
|
311
|
+
*/
|
|
312
|
+
declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer<Model>>;
|
|
313
|
+
/**
|
|
314
|
+
* Create a schematic from a typed schema
|
|
315
|
+
* @template Model Existing type
|
|
316
|
+
* @param schema Typed schema to create the schematic from
|
|
317
|
+
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
318
|
+
* @returns A schematic for the given typed schema
|
|
319
|
+
*/
|
|
320
|
+
declare function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/models/schema.plain.model.d.ts
|
|
323
|
+
/**
|
|
324
|
+
* A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
|
|
325
|
+
*/
|
|
326
|
+
type PlainSchema = {
|
|
327
|
+
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
|
|
328
|
+
} & {
|
|
329
|
+
$required?: never;
|
|
330
|
+
$type?: never;
|
|
331
|
+
$validators?: never;
|
|
332
|
+
};
|
|
168
333
|
/**
|
|
169
334
|
* A schema for validating objects
|
|
170
335
|
*
|
|
@@ -183,12 +348,12 @@ type Schema = SchemaIndex;
|
|
|
183
348
|
*
|
|
184
349
|
* Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
185
350
|
*/
|
|
186
|
-
type SchemaEntry = Constructor |
|
|
351
|
+
type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
187
352
|
/**
|
|
188
353
|
* Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
|
|
189
354
|
*/
|
|
190
355
|
interface SchemaIndex {
|
|
191
|
-
[key: string]:
|
|
356
|
+
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
|
|
192
357
|
}
|
|
193
358
|
/**
|
|
194
359
|
* A property definition with explicit type(s), an optional requirement flag, and optional validators
|
|
@@ -226,55 +391,73 @@ type SchemaProperty = {
|
|
|
226
391
|
*/
|
|
227
392
|
type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
228
393
|
/**
|
|
229
|
-
* A
|
|
394
|
+
* A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
|
|
395
|
+
*
|
|
396
|
+
* Each key may hold a single validator or an array of validators that receive the typed value
|
|
397
|
+
*
|
|
398
|
+
* @template Value - `$type` value(s) to derive validator keys from
|
|
230
399
|
*
|
|
231
400
|
* @example
|
|
232
401
|
* ```ts
|
|
233
|
-
*
|
|
402
|
+
* const validators: PropertyValidators<'string'> = {
|
|
403
|
+
* string: (value) => value.length > 0,
|
|
404
|
+
* };
|
|
234
405
|
* ```
|
|
235
406
|
*/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
407
|
+
type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/models/misc.model.d.ts
|
|
239
410
|
/**
|
|
240
|
-
*
|
|
411
|
+
* Removes duplicate types from a tuple, preserving first occurrence order
|
|
241
412
|
*
|
|
242
|
-
*
|
|
413
|
+
* @template Value - Tuple to deduplicate
|
|
414
|
+
* @template Seen - Accumulator for already-seen types _(internal)_
|
|
243
415
|
*
|
|
244
|
-
* @
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* // DeduplicateTuple<['string', 'number', 'string']>
|
|
419
|
+
* // => ['string', 'number']
|
|
420
|
+
* ```
|
|
245
421
|
*/
|
|
246
|
-
type
|
|
422
|
+
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;
|
|
247
423
|
/**
|
|
248
|
-
*
|
|
424
|
+
* Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
|
|
249
425
|
*
|
|
250
|
-
*
|
|
426
|
+
* @template Value - Type to extract value names from
|
|
251
427
|
*
|
|
252
|
-
* @
|
|
428
|
+
* @example
|
|
429
|
+
* ```ts
|
|
430
|
+
* // ExtractValueNames<'string'> => 'string'
|
|
431
|
+
* // ExtractValueNames<['string', 'number']> => 'string' | 'number'
|
|
432
|
+
* ```
|
|
253
433
|
*/
|
|
254
|
-
type
|
|
434
|
+
type ExtractValueNames<Value> = Value extends ValueName ? Value : Value extends (infer Item)[] ? ExtractValueNames<Item> : Value extends readonly (infer Item)[] ? ExtractValueNames<Item> : never;
|
|
255
435
|
/**
|
|
256
|
-
*
|
|
436
|
+
* Determines whether a schema entry is optional
|
|
257
437
|
*
|
|
258
|
-
*
|
|
438
|
+
* Returns `true` if the entry is a {@link SchemaProperty} or {@link NestedSchema} with `$required` set to `false`; otherwise returns `false`
|
|
259
439
|
*
|
|
260
|
-
* @template Value -
|
|
440
|
+
* @template Value - Schema entry to check
|
|
261
441
|
*/
|
|
262
|
-
type
|
|
442
|
+
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
|
|
263
443
|
/**
|
|
264
|
-
*
|
|
444
|
+
* Extracts the last member from a union type by leveraging intersection of function return types
|
|
265
445
|
*
|
|
266
|
-
*
|
|
446
|
+
* @template Value - Union type
|
|
447
|
+
*/
|
|
448
|
+
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
|
|
449
|
+
/**
|
|
450
|
+
* Extracts keys from an object type that are optional
|
|
267
451
|
*
|
|
268
|
-
* @template Value - type to
|
|
452
|
+
* @template Value - Object type to inspect
|
|
453
|
+
*/
|
|
454
|
+
type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
|
|
455
|
+
/**
|
|
456
|
+
* Extracts keys from an object type that are required _(i.e., not optional)_
|
|
269
457
|
*
|
|
270
|
-
* @
|
|
271
|
-
* ```ts
|
|
272
|
-
* // ToValueType<string> => 'string'
|
|
273
|
-
* // ToValueType<number[]> => 'array'
|
|
274
|
-
* // ToValueType<Date> => 'date'
|
|
275
|
-
* ```
|
|
458
|
+
* @template Value - Object type to inspect
|
|
276
459
|
*/
|
|
277
|
-
type
|
|
460
|
+
type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
278
461
|
/**
|
|
279
462
|
* Generates all permutations of a tuple type
|
|
280
463
|
*
|
|
@@ -300,93 +483,6 @@ type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> =
|
|
|
300
483
|
* @template Prefix - Accumulator for elements before the target _(internal)_
|
|
301
484
|
*/
|
|
302
485
|
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;
|
|
303
|
-
/**
|
|
304
|
-
* A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
|
|
305
|
-
*
|
|
306
|
-
* @template Value - Property's type _(including `undefined`)_
|
|
307
|
-
*
|
|
308
|
-
* @example
|
|
309
|
-
* ```ts
|
|
310
|
-
* // For `{ name?: string }`, the `name` key produces:
|
|
311
|
-
* // TypedPropertyOptional<string | undefined>
|
|
312
|
-
* // => { $required: false; $type: 'string'; ... }
|
|
313
|
-
* ```
|
|
314
|
-
*/
|
|
315
|
-
type TypedPropertyOptional<Value> = {
|
|
316
|
-
/**
|
|
317
|
-
* The property is not required
|
|
318
|
-
*/
|
|
319
|
-
$required: false;
|
|
320
|
-
/**
|
|
321
|
-
* The type(s) of the property
|
|
322
|
-
*/
|
|
323
|
-
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
324
|
-
/**
|
|
325
|
-
* Custom validators for the property and its types
|
|
326
|
-
*/
|
|
327
|
-
$validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
|
|
328
|
-
};
|
|
329
|
-
/**
|
|
330
|
-
* A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
|
|
331
|
-
*
|
|
332
|
-
* @template Value - Property's type
|
|
333
|
-
*
|
|
334
|
-
* @example
|
|
335
|
-
* ```ts
|
|
336
|
-
* // For `{ name: string }`, the `name` key produces:
|
|
337
|
-
* // TypedPropertyRequired<string>
|
|
338
|
-
* // => { $required?: true; $type: 'string'; ... }
|
|
339
|
-
* ```
|
|
340
|
-
*/
|
|
341
|
-
type TypedPropertyRequired<Value> = {
|
|
342
|
-
/**
|
|
343
|
-
* The property is required _(defaults to `true`)_
|
|
344
|
-
*/
|
|
345
|
-
$required?: true;
|
|
346
|
-
/**
|
|
347
|
-
* The type(s) of the property
|
|
348
|
-
*/
|
|
349
|
-
$type: ToSchemaPropertyType<Value>;
|
|
350
|
-
/**
|
|
351
|
-
* Custom validators for the property and its types
|
|
352
|
-
*/
|
|
353
|
-
$validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
|
|
354
|
-
};
|
|
355
|
-
/**
|
|
356
|
-
* Creates a schema type constrained to match a TypeScript type
|
|
357
|
-
*
|
|
358
|
-
* 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}
|
|
359
|
-
*
|
|
360
|
-
* @template Model - Object type to generate a schema for
|
|
361
|
-
*
|
|
362
|
-
* @example
|
|
363
|
-
* ```ts
|
|
364
|
-
* type User = { name: string; age: number; bio?: string };
|
|
365
|
-
*
|
|
366
|
-
* const schema: TypedSchema<User> = {
|
|
367
|
-
* name: 'string',
|
|
368
|
-
* age: 'number',
|
|
369
|
-
* bio: { $required: false, $type: 'string' },
|
|
370
|
-
* };
|
|
371
|
-
* ```
|
|
372
|
-
*/
|
|
373
|
-
type TypedSchema<Model extends PlainObject> = Simplify<{ [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]> } & { [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> | Schematic<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]> }>;
|
|
374
|
-
/**
|
|
375
|
-
* A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
|
|
376
|
-
*
|
|
377
|
-
* @template Model - Nested object type
|
|
378
|
-
*/
|
|
379
|
-
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
380
|
-
$required: false;
|
|
381
|
-
} & TypedSchema<Model>;
|
|
382
|
-
/**
|
|
383
|
-
* A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
|
|
384
|
-
*
|
|
385
|
-
* @template Model - Nested object type
|
|
386
|
-
*/
|
|
387
|
-
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
388
|
-
$required?: true;
|
|
389
|
-
} & TypedSchema<Model>;
|
|
390
486
|
/**
|
|
391
487
|
* Converts a union type into an intersection
|
|
392
488
|
*
|
|
@@ -430,49 +526,6 @@ type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never]
|
|
|
430
526
|
* ```
|
|
431
527
|
*/
|
|
432
528
|
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
|
|
433
|
-
/**
|
|
434
|
-
* The runtime representation of a parsed schema property, used internally during validation
|
|
435
|
-
*
|
|
436
|
-
* @example
|
|
437
|
-
* ```ts
|
|
438
|
-
* const parsed: ValidatedProperty = {
|
|
439
|
-
* key: 'age',
|
|
440
|
-
* required: true,
|
|
441
|
-
* types: ['number'],
|
|
442
|
-
* validators: { number: [(v) => v > 0] },
|
|
443
|
-
* };
|
|
444
|
-
* ```
|
|
445
|
-
*/
|
|
446
|
-
type ValidatedProperty = {
|
|
447
|
-
/**
|
|
448
|
-
* The property name in the schema
|
|
449
|
-
*/
|
|
450
|
-
key: string;
|
|
451
|
-
/**
|
|
452
|
-
* Whether the property is required
|
|
453
|
-
*/
|
|
454
|
-
required: boolean;
|
|
455
|
-
/**
|
|
456
|
-
* The allowed types for this property
|
|
457
|
-
*/
|
|
458
|
-
types: ValidatedPropertyType[];
|
|
459
|
-
/**
|
|
460
|
-
* Custom validators grouped by {@link ValueName}
|
|
461
|
-
*/
|
|
462
|
-
validators: ValidatedPropertyValidators;
|
|
463
|
-
};
|
|
464
|
-
/**
|
|
465
|
-
* A union of valid types for a {@link ValidatedProperty}'s `types` array
|
|
466
|
-
*
|
|
467
|
-
* Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
|
|
468
|
-
*/
|
|
469
|
-
type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
|
|
470
|
-
/**
|
|
471
|
-
* A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
|
|
472
|
-
*
|
|
473
|
-
* Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
|
|
474
|
-
*/
|
|
475
|
-
type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
|
|
476
529
|
/**
|
|
477
530
|
* Basic value types
|
|
478
531
|
*/
|
|
@@ -503,38 +556,6 @@ type Values = {
|
|
|
503
556
|
undefined: undefined;
|
|
504
557
|
};
|
|
505
558
|
//#endregion
|
|
506
|
-
//#region src/schematic.d.ts
|
|
507
|
-
/**
|
|
508
|
-
* A schematic for validating objects
|
|
509
|
-
*/
|
|
510
|
-
declare class Schematic<Model> {
|
|
511
|
-
#private;
|
|
512
|
-
private readonly $schematic;
|
|
513
|
-
constructor(properties: ValidatedProperty[]);
|
|
514
|
-
/**
|
|
515
|
-
* Does the value match the schema?
|
|
516
|
-
* @param value - Value to validate
|
|
517
|
-
* @returns `true` if the value matches the schema, otherwise `false`
|
|
518
|
-
*/
|
|
519
|
-
is(value: unknown): value is Model;
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Create a schematic from a schema
|
|
523
|
-
* @template Model - Schema type
|
|
524
|
-
* @param schema - Schema to create the schematic from
|
|
525
|
-
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
526
|
-
* @returns A schematic for the given schema
|
|
527
|
-
*/
|
|
528
|
-
declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer<Model>>;
|
|
529
|
-
/**
|
|
530
|
-
* Create a schematic from a typed schema
|
|
531
|
-
* @template Model - Existing type
|
|
532
|
-
* @param schema - Typed schema to create the schematic from
|
|
533
|
-
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
534
|
-
* @returns A schematic for the given typed schema
|
|
535
|
-
*/
|
|
536
|
-
declare function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
537
|
-
//#endregion
|
|
538
559
|
//#region src/helpers.d.ts
|
|
539
560
|
/**
|
|
540
561
|
* Creates a validator function for a given constructor
|
|
@@ -550,4 +571,4 @@ declare function instanceOf<Instance>(constructor: Constructor<Instance>): (valu
|
|
|
550
571
|
*/
|
|
551
572
|
declare function isSchematic(value: unknown): value is Schematic<never>;
|
|
552
573
|
//#endregion
|
|
553
|
-
export { type Schema, type Schematic, SchematicError, type TypedSchema, instanceOf, isSchematic, schematic };
|
|
574
|
+
export { type Schema, type Schematic, SchematicError, type TypedSchema, ValidationError, instanceOf, isSchematic, schematic };
|