@oscarpalmer/jhunal 0.15.0 → 0.17.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.
@@ -0,0 +1,27 @@
1
+ import { Values } from "./models.mjs";
2
+
3
+ //#region src/constants.d.ts
4
+ declare const ERROR_NAME = "SchematicError";
5
+ declare const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
6
+ declare const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
7
+ declare const MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED = "'<key>.<property>' property is not allowed for schemas in $type";
8
+ declare const MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE = "'<>' property must not be 'null' or 'undefined'";
9
+ declare const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
10
+ declare const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
11
+ declare const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
12
+ declare const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
13
+ declare const MESSAGE_VALIDATOR_INVALID_TYPE = "Validators must be an object";
14
+ declare const MESSAGE_VALIDATOR_INVALID_VALUE = "Validator '<>' must be a function or an array of functions";
15
+ declare const PROPERTY_REQUIRED = "$required";
16
+ declare const PROPERTY_TYPE = "$type";
17
+ declare const PROPERTY_VALIDATORS = "$validators";
18
+ declare const SCHEMATIC_NAME = "$schematic";
19
+ declare const TEMPLATE_PATTERN = "<>";
20
+ declare const TEMPLATE_PATTERN_KEY = "<key>";
21
+ declare const TEMPLATE_PATTERN_PROPERTY = "<property>";
22
+ declare const TYPE_OBJECT = "object";
23
+ declare const TYPE_UNDEFINED = "undefined";
24
+ declare const VALIDATABLE_TYPES: Set<keyof Values>;
25
+ declare const TYPE_ALL: Set<keyof Values>;
26
+ //#endregion
27
+ export { ERROR_NAME, MESSAGE_CONSTRUCTOR, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED, MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, MESSAGE_SCHEMA_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_KEY, MESSAGE_VALIDATOR_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_VALUE, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, SCHEMATIC_NAME, TEMPLATE_PATTERN, TEMPLATE_PATTERN_KEY, TEMPLATE_PATTERN_PROPERTY, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES };
@@ -1,8 +1,9 @@
1
+ //#region src/constants.ts
1
2
  const ERROR_NAME = "SchematicError";
2
- const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
3
3
  const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
4
4
  const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
5
5
  const MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED = "'<key>.<property>' property is not allowed for schemas in $type";
6
+ const MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE = "'<>' property must not be 'null' or 'undefined'";
6
7
  const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
7
8
  const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
8
9
  const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
@@ -34,4 +35,5 @@ const TYPE_ALL = new Set([
34
35
  "null",
35
36
  TYPE_UNDEFINED
36
37
  ]);
37
- export { ERROR_NAME, EXPRESSION_PROPERTY, MESSAGE_CONSTRUCTOR, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, MESSAGE_SCHEMA_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_KEY, MESSAGE_VALIDATOR_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_VALUE, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, SCHEMATIC_NAME, TEMPLATE_PATTERN, TEMPLATE_PATTERN_KEY, TEMPLATE_PATTERN_PROPERTY, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES };
38
+ //#endregion
39
+ export { ERROR_NAME, MESSAGE_CONSTRUCTOR, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED, MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, MESSAGE_SCHEMA_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_KEY, MESSAGE_VALIDATOR_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_VALUE, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, SCHEMATIC_NAME, TEMPLATE_PATTERN, TEMPLATE_PATTERN_KEY, TEMPLATE_PATTERN_PROPERTY, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES };
@@ -1,15 +1,19 @@
1
- import type { Constructor } from '@oscarpalmer/atoms/models';
2
- import type { Schematic } from './schematic';
1
+ import { Schematic } from "./schematic.mjs";
2
+ import { Constructor } from "@oscarpalmer/atoms/models";
3
+
4
+ //#region src/helpers.d.ts
3
5
  /**
4
6
  * Creates a validator function for a given constructor
5
7
  * @param constructor - Constructor to check against
6
8
  * @throws Will throw a `TypeError` if the provided argument is not a valid constructor
7
9
  * @returns Validator function that checks if a value is an instance of the constructor
8
10
  */
9
- export declare function instanceOf<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
11
+ declare function instanceOf<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
10
12
  /**
11
13
  * Is the value a schematic?
12
14
  * @param value Value to check
13
15
  * @returns `true` if the value is a schematic, `false` otherwise
14
16
  */
15
- export declare function isSchematic(value: unknown): value is Schematic<never>;
17
+ declare function isSchematic(value: unknown): value is Schematic<never>;
18
+ //#endregion
19
+ export { instanceOf, isSchematic };
@@ -1,5 +1,6 @@
1
- import { MESSAGE_CONSTRUCTOR } from "./constants.js";
1
+ import { MESSAGE_CONSTRUCTOR } from "./constants.mjs";
2
2
  import { isConstructor } from "@oscarpalmer/atoms/is";
3
+ //#region src/helpers.ts
3
4
  /**
4
5
  * Creates a validator function for a given constructor
5
6
  * @param constructor - Constructor to check against
@@ -20,4 +21,5 @@ function instanceOf(constructor) {
20
21
  function isSchematic(value) {
21
22
  return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
22
23
  }
24
+ //#endregion
23
25
  export { instanceOf, isSchematic };
@@ -0,0 +1,530 @@
1
+ import { Constructor, GenericCallback, PlainObject, Simplify } from "@oscarpalmer/atoms/models";
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;
29
+ /**
30
+ * Infers the TypeScript type from a {@link Schema} definition
31
+ *
32
+ * @template Model - Schema to infer types from
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const userSchema = {
37
+ * name: 'string',
38
+ * age: 'number',
39
+ * address: { $required: false, $type: 'string' },
40
+ * } satisfies Schema;
41
+ *
42
+ * type User = Infer<typeof userSchema>;
43
+ * // { name: string; age: number; address?: string }
44
+ * ```
45
+ */
46
+ type Infer<Model extends Schema> = Simplify<{ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]> } & { [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]> }>;
47
+ /**
48
+ * Extracts keys from a {@link Schema} whose entries are optional _(i.e., `$required` is `false`)_
49
+ *
50
+ * @template Model - {@link Schema} to extract optional keys from
51
+ */
52
+ type InferOptionalKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never };
53
+ /**
54
+ * Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
55
+ *
56
+ * @template Value - `$type` value _(single or array)_
57
+ */
58
+ type InferPropertyType<Value> = Value extends (infer Item)[] ? InferPropertyValue<Item> : InferPropertyValue<Value>;
59
+ /**
60
+ * Maps a single type definition to its TypeScript equivalent
61
+ *
62
+ * Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link Schema} objects
63
+ *
64
+ * @template Value - single type definition
65
+ */
66
+ type InferPropertyValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
67
+ /**
68
+ * Extracts keys from a {@link Schema} whose entries are required _(i.e., `$required` is not `false`)_
69
+ *
70
+ * @template Model - Schema to extract required keys from
71
+ */
72
+ type InferRequiredKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never };
73
+ /**
74
+ * Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
75
+ *
76
+ * @template Value - Schema entry value _(single or array)_
77
+ */
78
+ type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryValue<Item> : InferSchemaEntryValue<Value>;
79
+ /**
80
+ * Resolves a single schema entry to its TypeScript type
81
+ *
82
+ * Handles, in order: {@link Constructor} instances, {@link Schematic} models, {@link SchemaProperty} objects, {@link NestedSchema} objects, {@link ValueName} strings, and plain {@link Schema} objects
83
+ *
84
+ * @template Value - single schema entry
85
+ */
86
+ 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
+ * Determines whether a schema entry is optional
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;
101
+ /**
102
+ * Maps each element of a tuple through {@link ToValueType}
103
+ *
104
+ * @template Value - Tuple of types to map
105
+ */
106
+ type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToValueType<Head>, ...MapToValueTypes<Tail>] : [];
107
+ /**
108
+ * Maps each element of a tuple through {@link ToSchemaPropertyTypeEach}
109
+ *
110
+ * @template Value - Tuple of types to map
111
+ */
112
+ 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
+ /**
217
+ * Converts a type into its corresponding {@link SchemaPropertyType}-representation
218
+ *
219
+ * Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
220
+ *
221
+ * @template Value - type to convert
222
+ */
223
+ type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>>;
224
+ /**
225
+ * Converts a single type to its schema property equivalent
226
+ *
227
+ * {@link NestedSchema} values have `$required` stripped, plain objects become {@link TypedSchema}, and primitives go through {@link ToValueType}
228
+ *
229
+ * @template Value - type to convert
230
+ */
231
+ type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
232
+ /**
233
+ * Converts a type into its corresponding {@link ValueName}-representation
234
+ *
235
+ * Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
236
+ *
237
+ * @template Value - type to convert
238
+ */
239
+ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
240
+ /**
241
+ * Maps a type to its {@link ValueName} string equivalent
242
+ *
243
+ * 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
244
+ *
245
+ * @template Value - type to map
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * // ToValueType<string> => 'string'
250
+ * // ToValueType<number[]> => 'array'
251
+ * // ToValueType<Date> => 'date'
252
+ * ```
253
+ */
254
+ 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
+ * Generates all permutations of a tuple type
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;
280
+ /**
281
+ * A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
282
+ *
283
+ * @template Value - Property's type _(including `undefined`)_
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * // For `{ name?: string }`, the `name` key produces:
288
+ * // TypedPropertyOptional<string | undefined>
289
+ * // => { $required: false; $type: 'string'; ... }
290
+ * ```
291
+ */
292
+ type TypedPropertyOptional<Value> = {
293
+ /**
294
+ * The property is not required
295
+ */
296
+ $required: false;
297
+ /**
298
+ * The type(s) of the property
299
+ */
300
+ $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
301
+ /**
302
+ * Custom validators for the property and its types
303
+ */
304
+ $validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
305
+ };
306
+ /**
307
+ * A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
308
+ *
309
+ * @template Value - Property's type
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * // For `{ name: string }`, the `name` key produces:
314
+ * // TypedPropertyRequired<string>
315
+ * // => { $required?: true; $type: 'string'; ... }
316
+ * ```
317
+ */
318
+ type TypedPropertyRequired<Value> = {
319
+ /**
320
+ * The property is required _(defaults to `true`)_
321
+ */
322
+ $required?: true;
323
+ /**
324
+ * The type(s) of the property
325
+ */
326
+ $type: ToSchemaPropertyType<Value>;
327
+ /**
328
+ * Custom validators for the property and its types
329
+ */
330
+ $validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
331
+ };
332
+ /**
333
+ * Creates a schema type constrained to match a TypeScript type
334
+ *
335
+ * 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}
336
+ *
337
+ * @template Model - Object type to generate a schema for
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * type User = { name: string; age: number; bio?: string };
342
+ *
343
+ * const schema: TypedSchema<User> = {
344
+ * name: 'string',
345
+ * age: 'number',
346
+ * bio: { $required: false, $type: 'string' },
347
+ * };
348
+ * ```
349
+ */
350
+ 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]> }>;
351
+ /**
352
+ * A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
353
+ *
354
+ * @template Model - Nested object type
355
+ */
356
+ type TypedSchemaOptional<Model extends PlainObject> = {
357
+ $required: false;
358
+ } & TypedSchema<Model>;
359
+ /**
360
+ * A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
361
+ *
362
+ * @template Model - Nested object type
363
+ */
364
+ type TypedSchemaRequired<Model extends PlainObject> = {
365
+ $required?: true;
366
+ } & TypedSchema<Model>;
367
+ /**
368
+ * Converts a union type into an intersection
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
+ * ```
408
+ */
409
+ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
410
+ /**
411
+ * The runtime representation of a parsed schema property, used internally during validation
412
+ *
413
+ * @example
414
+ * ```ts
415
+ * const parsed: ValidatedProperty = {
416
+ * key: 'age',
417
+ * required: true,
418
+ * types: ['number'],
419
+ * validators: { number: [(v) => v > 0] },
420
+ * };
421
+ * ```
422
+ */
423
+ type ValidatedProperty = {
424
+ /**
425
+ * The property name in the schema
426
+ */
427
+ key: string;
428
+ /**
429
+ * Whether the property is required
430
+ */
431
+ required: boolean;
432
+ /**
433
+ * The allowed types for this property
434
+ */
435
+ types: ValidatedPropertyType[];
436
+ /**
437
+ * Custom validators grouped by {@link ValueName}
438
+ */
439
+ validators: ValidatedPropertyValidators;
440
+ };
441
+ /**
442
+ * A union of valid types for a {@link ValidatedProperty}'s `types` array
443
+ *
444
+ * Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
445
+ */
446
+ type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
447
+ /**
448
+ * A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
449
+ *
450
+ * Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
451
+ */
452
+ type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
453
+ /**
454
+ * Basic value types
455
+ */
456
+ type ValueName = keyof Values;
457
+ /**
458
+ * Maps type name strings to their TypeScript equivalents
459
+ *
460
+ * Used by the type system to resolve {@link ValueName} strings into actual types
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;
481
+ };
482
+ //#endregion
483
+ //#region src/schematic.d.ts
484
+ /**
485
+ * A schematic for validating objects
486
+ */
487
+ declare class Schematic<Model> {
488
+ #private;
489
+ private readonly $schematic;
490
+ constructor(properties: ValidatedProperty[]);
491
+ /**
492
+ * Does the value match the schema?
493
+ * @param value Value to validate
494
+ * @returns `true` if the value matches the schema, otherwise `false`
495
+ */
496
+ is(value: unknown): value is Model;
497
+ }
498
+ /**
499
+ * Create a schematic from a schema
500
+ * @template Model Schema type
501
+ * @param schema Schema to create the schematic from
502
+ * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
503
+ * @returns A schematic for the given schema
504
+ */
505
+ declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer<Model>>;
506
+ /**
507
+ * Create a schematic from a typed schema
508
+ * @template Model Existing type
509
+ * @param schema Typed schema to create the schematic from
510
+ * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
511
+ * @returns A schematic for the given typed schema
512
+ */
513
+ declare function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
514
+ //#endregion
515
+ //#region src/helpers.d.ts
516
+ /**
517
+ * Creates a validator function for a given constructor
518
+ * @param constructor - Constructor to check against
519
+ * @throws Will throw a `TypeError` if the provided argument is not a valid constructor
520
+ * @returns Validator function that checks if a value is an instance of the constructor
521
+ */
522
+ declare function instanceOf<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
523
+ /**
524
+ * Is the value a schematic?
525
+ * @param value Value to check
526
+ * @returns `true` if the value is a schematic, `false` otherwise
527
+ */
528
+ declare function isSchematic(value: unknown): value is Schematic<never>;
529
+ //#endregion
530
+ export { type Schema, type Schematic, SchematicError, type TypedSchema, instanceOf, isSchematic, schematic };