@oscarpalmer/jhunal 0.15.0 → 0.16.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.
@@ -1,55 +1,6 @@
1
- /**
2
- * Is the value a constructor function?
3
- * @param value Value to check
4
- * @returns `true` if the value is a constructor function, otherwise `false`
5
- */
6
- function isConstructor(value) {
7
- return typeof value === "function" && value.prototype?.constructor === value;
8
- }
9
- /**
10
- * Is the value a plain object?
11
- * @param value Value to check
12
- * @returns `true` if the value is a plain object, otherwise `false`
13
- */
14
- function isPlainObject(value) {
15
- if (value === null || typeof value !== "object") return false;
16
- if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
17
- const prototype = Object.getPrototypeOf(value);
18
- return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
19
- }
20
- function compact(array, strict) {
21
- if (!Array.isArray(array)) return [];
22
- if (strict === true) return array.filter(Boolean);
23
- const { length } = array;
24
- const compacted = [];
25
- for (let index = 0; index < length; index += 1) {
26
- const item = array[index];
27
- if (item != null) compacted.push(item);
28
- }
29
- return compacted;
30
- }
31
- /**
32
- * Get the string value from any value
33
- * @param value Original value
34
- * @returns String representation of the value
35
- */
36
- function getString(value) {
37
- if (typeof value === "string") return value;
38
- if (value == null) return "";
39
- if (typeof value === "function") return getString(value());
40
- if (typeof value !== "object") return String(value);
41
- const asString = String(value.valueOf?.() ?? value);
42
- return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
43
- }
44
- /**
45
- * Join an array of values into a string
46
- * @param value Array of values
47
- * @param delimiter Delimiter to use between values
48
- * @returns Joined string
49
- */
50
- function join(value, delimiter) {
51
- return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
52
- }
1
+ import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
2
+ import { join } from "@oscarpalmer/atoms/string";
3
+ //#region src/constants.ts
53
4
  const ERROR_NAME = "SchematicError";
54
5
  const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
55
6
  const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
@@ -65,7 +16,6 @@ const PROPERTY_REQUIRED = "$required";
65
16
  const PROPERTY_TYPE = "$type";
66
17
  const PROPERTY_VALIDATORS = "$validators";
67
18
  const SCHEMATIC_NAME = "$schematic";
68
- const TEMPLATE_PATTERN = "<>";
69
19
  const TEMPLATE_PATTERN_KEY = "<key>";
70
20
  const TEMPLATE_PATTERN_PROPERTY = "<property>";
71
21
  const TYPE_OBJECT = "object";
@@ -86,6 +36,8 @@ const TYPE_ALL = new Set([
86
36
  "null",
87
37
  TYPE_UNDEFINED
88
38
  ]);
39
+ //#endregion
40
+ //#region src/helpers.ts
89
41
  /**
90
42
  * Creates a validator function for a given constructor
91
43
  * @param constructor - Constructor to check against
@@ -104,8 +56,10 @@ function instanceOf(constructor) {
104
56
  * @returns `true` if the value is a schematic, `false` otherwise
105
57
  */
106
58
  function isSchematic(value) {
107
- return typeof value === "object" && value !== null && SCHEMATIC_NAME in value && value[SCHEMATIC_NAME] === true;
59
+ return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
108
60
  }
61
+ //#endregion
62
+ //#region src/models.ts
109
63
  /**
110
64
  * A custom error class for schema validation failures, with its `name` set to {@link ERROR_NAME}
111
65
  *
@@ -120,10 +74,12 @@ var SchematicError = class extends Error {
120
74
  this.name = ERROR_NAME;
121
75
  }
122
76
  };
77
+ //#endregion
78
+ //#region src/validation/property.validation.ts
123
79
  function getDisallowedProperty(obj) {
124
- if (PROPERTY_REQUIRED in obj) return PROPERTY_REQUIRED;
125
- if (PROPERTY_TYPE in obj) return PROPERTY_TYPE;
126
- if (PROPERTY_VALIDATORS in obj) return PROPERTY_VALIDATORS;
80
+ if ("$required" in obj) return PROPERTY_REQUIRED;
81
+ if ("$type" in obj) return PROPERTY_TYPE;
82
+ if ("$validators" in obj) return PROPERTY_VALIDATORS;
127
83
  }
128
84
  function getProperties(original, prefix, fromType) {
129
85
  if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
@@ -144,22 +100,22 @@ function getProperties(original, prefix, fromType) {
144
100
  if (isPlainObject(value)) {
145
101
  required = getRequired(key, value) ?? required;
146
102
  validators = getValidators(value[PROPERTY_VALIDATORS]);
147
- if (PROPERTY_TYPE in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
103
+ if ("$type" in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
148
104
  else types.push(TYPE_OBJECT, ...getTypes(key, value, prefix));
149
105
  } else types.push(...getTypes(key, value, prefix));
150
- if (!required && !types.includes(TYPE_UNDEFINED)) types.push(TYPE_UNDEFINED);
106
+ if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
151
107
  properties.push({
152
108
  key,
153
109
  types,
154
110
  validators,
155
- required: required && !types.includes(TYPE_UNDEFINED)
111
+ required: required && !types.includes("undefined")
156
112
  });
157
113
  }
158
114
  return properties;
159
115
  }
160
116
  function getRequired(key, obj) {
161
- if (!(PROPERTY_REQUIRED in obj)) return;
162
- if (typeof obj[PROPERTY_REQUIRED] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key));
117
+ if (!("$required" in obj)) return;
118
+ if (typeof obj["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
163
119
  return obj[PROPERTY_REQUIRED];
164
120
  }
165
121
  function getTypes(key, original, prefix, fromType) {
@@ -181,10 +137,10 @@ function getTypes(key, original, prefix, fromType) {
181
137
  case TYPE_ALL.has(value):
182
138
  types.push(value);
183
139
  break;
184
- default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], ".")));
140
+ default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
185
141
  }
186
142
  }
187
- if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], ".")));
143
+ if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
188
144
  return types;
189
145
  }
190
146
  function getValidators(original) {
@@ -195,15 +151,17 @@ function getValidators(original) {
195
151
  const { length } = keys;
196
152
  for (let index = 0; index < length; index += 1) {
197
153
  const key = keys[index];
198
- if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
154
+ if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
199
155
  const value = original[key];
200
156
  validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
201
- if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
157
+ if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
202
158
  return true;
203
159
  });
204
160
  }
205
161
  return validators;
206
162
  }
163
+ //#endregion
164
+ //#region src/validation/value.validation.ts
207
165
  function validateObject(obj, properties) {
208
166
  if (!isPlainObject(obj)) return false;
209
167
  const propertiesLength = properties.length;
@@ -242,6 +200,8 @@ const validators = {
242
200
  symbol: (value) => typeof value === "symbol",
243
201
  undefined: (value) => value === void 0
244
202
  };
203
+ //#endregion
204
+ //#region src/schematic.ts
245
205
  /**
246
206
  * A schematic for validating objects
247
207
  */
@@ -265,4 +225,5 @@ function schematic(schema) {
265
225
  if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
266
226
  return new Schematic(getProperties(schema));
267
227
  }
228
+ //#endregion
268
229
  export { SchematicError, instanceOf, isSchematic, schematic };
@@ -1,5 +1,7 @@
1
- import type { Constructor, GenericCallback, PlainObject, Simplify } from '@oscarpalmer/atoms/models';
2
- import type { Schematic } from './schematic';
1
+ import { Schematic } from "./schematic.mjs";
2
+ import { Constructor, GenericCallback, PlainObject, Simplify } from "@oscarpalmer/atoms/models";
3
+
4
+ //#region src/models.d.ts
3
5
  /**
4
6
  * Removes duplicate types from a tuple, preserving first occurrence order
5
7
  *
@@ -12,10 +14,7 @@ import type { Schematic } from './schematic';
12
14
  * // => ['string', 'number']
13
15
  * ```
14
16
  */
15
- type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
16
- infer Head,
17
- ...infer Tail
18
- ] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
17
+ 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;
19
18
  /**
20
19
  * Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
21
20
  *
@@ -45,19 +44,13 @@ type ExtractValueNames<Value> = Value extends ValueName ? Value : Value extends
45
44
  * // { name: string; age: number; address?: string }
46
45
  * ```
47
46
  */
48
- export type Infer<Model extends Schema> = Simplify<{
49
- [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
50
- } & {
51
- [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
52
- }>;
47
+ type Infer<Model extends Schema> = Simplify<{ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]> } & { [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]> }>;
53
48
  /**
54
49
  * Extracts keys from a {@link Schema} whose entries are optional _(i.e., `$required` is `false`)_
55
50
  *
56
51
  * @template Model - {@link Schema} to extract optional keys from
57
52
  */
58
- type InferOptionalKeys<Model extends Schema> = keyof {
59
- [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
60
- };
53
+ type InferOptionalKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never };
61
54
  /**
62
55
  * Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
63
56
  *
@@ -77,9 +70,7 @@ type InferPropertyValue<Value> = Value extends Constructor<infer Instance> ? Ins
77
70
  *
78
71
  * @template Model - Schema to extract required keys from
79
72
  */
80
- type InferRequiredKeys<Model extends Schema> = keyof {
81
- [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
82
- };
73
+ type InferRequiredKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never };
83
74
  /**
84
75
  * Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
85
76
  *
@@ -102,16 +93,16 @@ type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ?
102
93
  * @template Value - Schema entry to check
103
94
  */
104
95
  type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : Value extends {
105
- $required?: boolean;
96
+ $required?: boolean;
106
97
  } ? Value extends {
107
- $required: false;
98
+ $required: false;
108
99
  } ? true : false : false;
109
100
  /**
110
101
  * Extracts the last member from a union type by leveraging intersection of function return types
111
102
  *
112
103
  * @template Value - Union type
113
104
  */
114
- type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item ? Item : never;
105
+ type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
115
106
  /**
116
107
  * Maps each element of a tuple through {@link ToValueType}
117
108
  *
@@ -136,25 +127,23 @@ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer He
136
127
  * };
137
128
  * ```
138
129
  */
139
- export type NestedSchema = {
140
- /**
141
- * Whether the nested schema is required (defaults to `true`)
142
- */
143
- $required?: boolean;
130
+ type NestedSchema = {
131
+ /**
132
+ * Whether the nested schema is required (defaults to `true`)
133
+ */
134
+ $required?: boolean;
144
135
  } & Schema;
145
136
  /**
146
137
  * Extracts keys from an object type that are optional
147
138
  *
148
139
  * @template Value - Object type to inspect
149
140
  */
150
- type OptionalKeys<Value> = {
151
- [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
152
- }[keyof Value];
141
+ type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
153
142
  /**
154
143
  * A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
155
144
  */
156
145
  type PlainSchema = {
157
- [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
146
+ [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
158
147
  };
159
148
  /**
160
149
  * A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
@@ -170,9 +159,7 @@ type PlainSchema = {
170
159
  * };
171
160
  * ```
172
161
  */
173
- type PropertyValidators<Value> = {
174
- [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean>;
175
- };
162
+ type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
176
163
  /**
177
164
  * Extracts keys from an object type that are required _(i.e., not optional)_
178
165
  *
@@ -191,7 +178,7 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
191
178
  * };
192
179
  * ```
193
180
  */
194
- export type Schema = SchemaIndex;
181
+ type Schema = SchemaIndex;
195
182
  /**
196
183
  * A union of all valid types for a single schema entry
197
184
  *
@@ -202,7 +189,7 @@ type SchemaEntry = Constructor | Schema | SchemaProperty | Schematic<unknown> |
202
189
  * Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
203
190
  */
204
191
  interface SchemaIndex {
205
- [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
192
+ [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
206
193
  }
207
194
  /**
208
195
  * A property definition with explicit type(s), an optional requirement flag, and optional validators
@@ -219,19 +206,19 @@ interface SchemaIndex {
219
206
  * };
220
207
  * ```
221
208
  */
222
- export type SchemaProperty = {
223
- /**
224
- * Whether the property is required _(defaults to `true`)_
225
- */
226
- $required?: boolean;
227
- /**
228
- * The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
229
- */
230
- $type: SchemaPropertyType | SchemaPropertyType[];
231
- /**
232
- * Optional validators keyed by {@link ValueName}, applied during validation
233
- */
234
- $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
209
+ type SchemaProperty = {
210
+ /**
211
+ * Whether the property is required _(defaults to `true`)_
212
+ */
213
+ $required?: boolean;
214
+ /**
215
+ * The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
216
+ */
217
+ $type: SchemaPropertyType | SchemaPropertyType[];
218
+ /**
219
+ * Optional validators keyed by {@link ValueName}, applied during validation
220
+ */
221
+ $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
235
222
  };
236
223
  /**
237
224
  * A union of valid types for a {@link SchemaProperty}'s `$type` field
@@ -247,8 +234,8 @@ type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | Value
247
234
  * throw new SchematicError('Expected a string, received a number');
248
235
  * ```
249
236
  */
250
- export declare class SchematicError extends Error {
251
- constructor(message: string);
237
+ declare class SchematicError extends Error {
238
+ constructor(message: string);
252
239
  }
253
240
  /**
254
241
  * Converts a type into its corresponding {@link SchemaPropertyType}-representation
@@ -288,9 +275,7 @@ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionTo
288
275
  * // ToValueType<Date> => 'date'
289
276
  * ```
290
277
  */
291
- type ToValueType<Value> = Value extends Schematic<any> ? Value : {
292
- [Key in keyof Omit<Values, 'object'>]: Value extends Values[Key] ? Key : never;
293
- }[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;
278
+ 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;
294
279
  /**
295
280
  * Generates all permutations of a tuple type
296
281
  *
@@ -305,12 +290,7 @@ type ToValueType<Value> = Value extends Schematic<any> ? Value : {
305
290
  * // => ['string', 'number'] | ['number', 'string']
306
291
  * ```
307
292
  */
308
- type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : {
309
- [Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [
310
- ...Elput,
311
- Tuple[Key]
312
- ]>;
313
- }[keyof Tuple & `${number}`];
293
+ 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}`];
314
294
  /**
315
295
  * Removes the element at a given index from a tuple
316
296
  *
@@ -333,19 +313,19 @@ type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends
333
313
  * // => { $required: false; $type: 'string'; ... }
334
314
  * ```
335
315
  */
336
- export type TypedPropertyOptional<Value> = {
337
- /**
338
- * The property is not required
339
- */
340
- $required: false;
341
- /**
342
- * The type(s) of the property
343
- */
344
- $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
345
- /**
346
- * Custom validators for the property and its types
347
- */
348
- $validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
316
+ type TypedPropertyOptional<Value> = {
317
+ /**
318
+ * The property is not required
319
+ */
320
+ $required: false;
321
+ /**
322
+ * The type(s) of the property
323
+ */
324
+ $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
325
+ /**
326
+ * Custom validators for the property and its types
327
+ */
328
+ $validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
349
329
  };
350
330
  /**
351
331
  * A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
@@ -359,19 +339,19 @@ export type TypedPropertyOptional<Value> = {
359
339
  * // => { $required?: true; $type: 'string'; ... }
360
340
  * ```
361
341
  */
362
- export type TypedPropertyRequired<Value> = {
363
- /**
364
- * The property is required _(defaults to `true`)_
365
- */
366
- $required?: true;
367
- /**
368
- * The type(s) of the property
369
- */
370
- $type: ToSchemaPropertyType<Value>;
371
- /**
372
- * Custom validators for the property and its types
373
- */
374
- $validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
342
+ type TypedPropertyRequired<Value> = {
343
+ /**
344
+ * The property is required _(defaults to `true`)_
345
+ */
346
+ $required?: true;
347
+ /**
348
+ * The type(s) of the property
349
+ */
350
+ $type: ToSchemaPropertyType<Value>;
351
+ /**
352
+ * Custom validators for the property and its types
353
+ */
354
+ $validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
375
355
  };
376
356
  /**
377
357
  * Creates a schema type constrained to match a TypeScript type
@@ -391,18 +371,14 @@ export type TypedPropertyRequired<Value> = {
391
371
  * };
392
372
  * ```
393
373
  */
394
- export type TypedSchema<Model extends PlainObject> = Simplify<{
395
- [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
396
- } & {
397
- [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> | Schematic<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]>;
398
- }>;
374
+ 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]> }>;
399
375
  /**
400
376
  * A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
401
377
  *
402
378
  * @template Model - Nested object type
403
379
  */
404
380
  type TypedSchemaOptional<Model extends PlainObject> = {
405
- $required: false;
381
+ $required: false;
406
382
  } & TypedSchema<Model>;
407
383
  /**
408
384
  * A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
@@ -410,7 +386,7 @@ type TypedSchemaOptional<Model extends PlainObject> = {
410
386
  * @template Model - Nested object type
411
387
  */
412
388
  type TypedSchemaRequired<Model extends PlainObject> = {
413
- $required?: true;
389
+ $required?: true;
414
390
  } & TypedSchema<Model>;
415
391
  /**
416
392
  * Converts a union type into an intersection
@@ -425,7 +401,7 @@ type TypedSchemaRequired<Model extends PlainObject> = {
425
401
  * // => { a: 1 } & { b: 2 }
426
402
  * ```
427
403
  */
428
- type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (value: infer Item) => void ? Item : never;
404
+ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends ((value: infer Item) => void) ? Item : never;
429
405
  /**
430
406
  * Converts a union type into an ordered tuple
431
407
  *
@@ -468,42 +444,40 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only :
468
444
  * };
469
445
  * ```
470
446
  */
471
- export type ValidatedProperty = {
472
- /**
473
- * The property name in the schema
474
- */
475
- key: string;
476
- /**
477
- * Whether the property is required
478
- */
479
- required: boolean;
480
- /**
481
- * The allowed types for this property
482
- */
483
- types: ValidatedPropertyType[];
484
- /**
485
- * Custom validators grouped by {@link ValueName}
486
- */
487
- validators: ValidatedPropertyValidators;
447
+ type ValidatedProperty = {
448
+ /**
449
+ * The property name in the schema
450
+ */
451
+ key: string;
452
+ /**
453
+ * Whether the property is required
454
+ */
455
+ required: boolean;
456
+ /**
457
+ * The allowed types for this property
458
+ */
459
+ types: ValidatedPropertyType[];
460
+ /**
461
+ * Custom validators grouped by {@link ValueName}
462
+ */
463
+ validators: ValidatedPropertyValidators;
488
464
  };
489
465
  /**
490
466
  * A union of valid types for a {@link ValidatedProperty}'s `types` array
491
467
  *
492
468
  * Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
493
469
  */
494
- export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
470
+ type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
495
471
  /**
496
472
  * A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
497
473
  *
498
474
  * Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
499
475
  */
500
- export type ValidatedPropertyValidators = {
501
- [Key in ValueName]?: Array<(value: unknown) => boolean>;
502
- };
476
+ type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
503
477
  /**
504
478
  * Basic value types
505
479
  */
506
- export type ValueName = keyof Values;
480
+ type ValueName = keyof Values;
507
481
  /**
508
482
  * Maps type name strings to their TypeScript equivalents
509
483
  *
@@ -516,17 +490,18 @@ export type ValueName = keyof Values;
516
490
  * // Values['null'] => null
517
491
  * ```
518
492
  */
519
- export type Values = {
520
- array: unknown[];
521
- bigint: bigint;
522
- boolean: boolean;
523
- date: Date;
524
- function: Function;
525
- null: null;
526
- number: number;
527
- object: object;
528
- string: string;
529
- symbol: symbol;
530
- undefined: undefined;
493
+ type Values = {
494
+ array: unknown[];
495
+ bigint: bigint;
496
+ boolean: boolean;
497
+ date: Date;
498
+ function: Function;
499
+ null: null;
500
+ number: number;
501
+ object: object;
502
+ string: string;
503
+ symbol: symbol;
504
+ undefined: undefined;
531
505
  };
532
- export {};
506
+ //#endregion
507
+ export { Infer, NestedSchema, Schema, SchemaProperty, SchematicError, TypedPropertyOptional, TypedPropertyRequired, TypedSchema, ValidatedProperty, ValidatedPropertyType, ValidatedPropertyValidators, ValueName, Values };
@@ -1,4 +1,5 @@
1
- import { ERROR_NAME } from "./constants.js";
1
+ import { ERROR_NAME } from "./constants.mjs";
2
+ //#region src/models.ts
2
3
  /**
3
4
  * A custom error class for schema validation failures, with its `name` set to {@link ERROR_NAME}
4
5
  *
@@ -13,4 +14,5 @@ var SchematicError = class extends Error {
13
14
  this.name = ERROR_NAME;
14
15
  }
15
16
  };
17
+ //#endregion
16
18
  export { SchematicError };
@@ -0,0 +1,36 @@
1
+ import { Infer, Schema, TypedSchema, ValidatedProperty } from "./models.mjs";
2
+ import { PlainObject } from "@oscarpalmer/atoms/models";
3
+
4
+ //#region src/schematic.d.ts
5
+ /**
6
+ * A schematic for validating objects
7
+ */
8
+ declare class Schematic<Model> {
9
+ #private;
10
+ private readonly $schematic;
11
+ constructor(properties: ValidatedProperty[]);
12
+ /**
13
+ * Does the value match the schema?
14
+ * @param value - Value to validate
15
+ * @returns `true` if the value matches the schema, otherwise `false`
16
+ */
17
+ is(value: unknown): value is Model;
18
+ }
19
+ /**
20
+ * Create a schematic from a schema
21
+ * @template Model - Schema type
22
+ * @param schema - Schema to create the schematic from
23
+ * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
24
+ * @returns A schematic for the given schema
25
+ */
26
+ declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer<Model>>;
27
+ /**
28
+ * Create a schematic from a typed schema
29
+ * @template Model - Existing type
30
+ * @param schema - Typed schema to create the schematic from
31
+ * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
32
+ * @returns A schematic for the given typed schema
33
+ */
34
+ declare function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
35
+ //#endregion
36
+ export { Schematic, schematic };
@@ -1,9 +1,10 @@
1
- import { MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME } from "./constants.js";
2
- import { isSchematic } from "./helpers.js";
3
- import { SchematicError } from "./models.js";
4
- import { getProperties } from "./validation/property.validation.js";
5
- import { validateObject } from "./validation/value.validation.js";
1
+ import { MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME } from "./constants.mjs";
2
+ import { isSchematic } from "./helpers.mjs";
3
+ import { SchematicError } from "./models.mjs";
4
+ import { getProperties } from "./validation/property.validation.mjs";
5
+ import { validateObject } from "./validation/value.validation.mjs";
6
6
  import { isPlainObject } from "@oscarpalmer/atoms/is";
7
+ //#region src/schematic.ts
7
8
  /**
8
9
  * A schematic for validating objects
9
10
  */
@@ -27,4 +28,5 @@ function schematic(schema) {
27
28
  if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
28
29
  return new Schematic(getProperties(schema));
29
30
  }
31
+ //#endregion
30
32
  export { Schematic, schematic };
@@ -0,0 +1,7 @@
1
+ import { ValidatedProperty } from "../models.mjs";
2
+ import { PlainObject } from "@oscarpalmer/atoms/models";
3
+
4
+ //#region src/validation/property.validation.d.ts
5
+ declare function getProperties(original: PlainObject, prefix?: string, fromType?: boolean): ValidatedProperty[];
6
+ //#endregion
7
+ export { getProperties };