@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.
@@ -1,60 +1,11 @@
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
- const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
55
5
  const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
56
6
  const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
57
7
  const MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED = "'<key>.<property>' property is not allowed for schemas in $type";
8
+ const MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE = "'<>' property must not be 'null' or 'undefined'";
58
9
  const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
59
10
  const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
60
11
  const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
@@ -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,15 +56,12 @@ 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
- * A custom error class for schema validation failures, with its `name` set to {@link ERROR_NAME}
111
- *
112
- * @example
113
- * ```ts
114
- * throw new SchematicError('Expected a string, received a number');
115
- * ```
64
+ * A custom error class for schematic validation failures
116
65
  */
117
66
  var SchematicError = class extends Error {
118
67
  constructor(message) {
@@ -120,10 +69,12 @@ var SchematicError = class extends Error {
120
69
  this.name = ERROR_NAME;
121
70
  }
122
71
  };
72
+ //#endregion
73
+ //#region src/validation/property.validation.ts
123
74
  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;
75
+ if ("$required" in obj) return PROPERTY_REQUIRED;
76
+ if ("$type" in obj) return PROPERTY_TYPE;
77
+ if ("$validators" in obj) return PROPERTY_VALIDATORS;
127
78
  }
128
79
  function getProperties(original, prefix, fromType) {
129
80
  if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
@@ -136,30 +87,30 @@ function getProperties(original, prefix, fromType) {
136
87
  const properties = [];
137
88
  for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
138
89
  const key = keys[keyIndex];
139
- if (EXPRESSION_PROPERTY.test(key)) continue;
140
90
  const value = original[key];
91
+ if (value == null) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", join([prefix, key], ".")));
141
92
  const types = [];
142
93
  let required = true;
143
94
  let validators = {};
144
95
  if (isPlainObject(value)) {
145
96
  required = getRequired(key, value) ?? required;
146
97
  validators = getValidators(value[PROPERTY_VALIDATORS]);
147
- if (PROPERTY_TYPE in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
98
+ if ("$type" in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
148
99
  else types.push(TYPE_OBJECT, ...getTypes(key, value, prefix));
149
100
  } else types.push(...getTypes(key, value, prefix));
150
- if (!required && !types.includes(TYPE_UNDEFINED)) types.push(TYPE_UNDEFINED);
101
+ if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
151
102
  properties.push({
152
103
  key,
153
104
  types,
154
105
  validators,
155
- required: required && !types.includes(TYPE_UNDEFINED)
106
+ required: required && !types.includes("undefined")
156
107
  });
157
108
  }
158
109
  return properties;
159
110
  }
160
111
  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));
112
+ if (!("$required" in obj)) return;
113
+ if (typeof obj["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
163
114
  return obj[PROPERTY_REQUIRED];
164
115
  }
165
116
  function getTypes(key, original, prefix, fromType) {
@@ -181,10 +132,10 @@ function getTypes(key, original, prefix, fromType) {
181
132
  case TYPE_ALL.has(value):
182
133
  types.push(value);
183
134
  break;
184
- default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], ".")));
135
+ default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
185
136
  }
186
137
  }
187
- if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], ".")));
138
+ if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
188
139
  return types;
189
140
  }
190
141
  function getValidators(original) {
@@ -195,15 +146,17 @@ function getValidators(original) {
195
146
  const { length } = keys;
196
147
  for (let index = 0; index < length; index += 1) {
197
148
  const key = keys[index];
198
- if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
149
+ if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
199
150
  const value = original[key];
200
151
  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));
152
+ if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
202
153
  return true;
203
154
  });
204
155
  }
205
156
  return validators;
206
157
  }
158
+ //#endregion
159
+ //#region src/validation/value.validation.ts
207
160
  function validateObject(obj, properties) {
208
161
  if (!isPlainObject(obj)) return false;
209
162
  const propertiesLength = properties.length;
@@ -242,6 +195,8 @@ const validators = {
242
195
  symbol: (value) => typeof value === "symbol",
243
196
  undefined: (value) => value === void 0
244
197
  };
198
+ //#endregion
199
+ //#region src/schematic.ts
245
200
  /**
246
201
  * A schematic for validating objects
247
202
  */
@@ -253,7 +208,7 @@ var Schematic = class {
253
208
  }
254
209
  /**
255
210
  * Does the value match the schema?
256
- * @param value - Value to validate
211
+ * @param value Value to validate
257
212
  * @returns `true` if the value matches the schema, otherwise `false`
258
213
  */
259
214
  is(value) {
@@ -265,4 +220,5 @@ function schematic(schema) {
265
220
  if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
266
221
  return new Schematic(getProperties(schema));
267
222
  }
223
+ //#endregion
268
224
  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
  *
@@ -93,7 +84,7 @@ type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryVa
93
84
  *
94
85
  * @template Value - single schema entry
95
86
  */
96
- type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends NestedSchema ? Infer<Omit<Value, '$required'>> : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
87
+ 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;
97
88
  /**
98
89
  * Determines whether a schema entry is optional
99
90
  *
@@ -101,17 +92,13 @@ type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ?
101
92
  *
102
93
  * @template Value - Schema entry to check
103
94
  */
104
- type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : Value extends {
105
- $required?: boolean;
106
- } ? Value extends {
107
- $required: false;
108
- } ? true : false : false;
95
+ type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
109
96
  /**
110
97
  * Extracts the last member from a union type by leveraging intersection of function return types
111
98
  *
112
99
  * @template Value - Union type
113
100
  */
114
- type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item ? Item : never;
101
+ type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
115
102
  /**
116
103
  * Maps each element of a tuple through {@link ToValueType}
117
104
  *
@@ -124,37 +111,21 @@ type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...in
124
111
  * @template Value - Tuple of types to map
125
112
  */
126
113
  type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
127
- /**
128
- * A nested schema definition that may include a `$required` flag alongside arbitrary string-keyed properties
129
- *
130
- * @example
131
- * ```ts
132
- * const address: NestedSchema = {
133
- * $required: false,
134
- * street: 'string',
135
- * city: 'string',
136
- * };
137
- * ```
138
- */
139
- export type NestedSchema = {
140
- /**
141
- * Whether the nested schema is required (defaults to `true`)
142
- */
143
- $required?: boolean;
144
- } & Schema;
145
114
  /**
146
115
  * Extracts keys from an object type that are optional
147
116
  *
148
117
  * @template Value - Object type to inspect
149
118
  */
150
- type OptionalKeys<Value> = {
151
- [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
152
- }[keyof Value];
119
+ type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
153
120
  /**
154
121
  * A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
155
122
  */
156
123
  type PlainSchema = {
157
- [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
124
+ [key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
125
+ } & {
126
+ $required?: never;
127
+ $type?: never;
128
+ $validators?: never;
158
129
  };
159
130
  /**
160
131
  * A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
@@ -170,9 +141,7 @@ type PlainSchema = {
170
141
  * };
171
142
  * ```
172
143
  */
173
- type PropertyValidators<Value> = {
174
- [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean>;
175
- };
144
+ type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
176
145
  /**
177
146
  * Extracts keys from an object type that are required _(i.e., not optional)_
178
147
  *
@@ -191,18 +160,18 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
191
160
  * };
192
161
  * ```
193
162
  */
194
- export type Schema = SchemaIndex;
163
+ type Schema = SchemaIndex;
195
164
  /**
196
165
  * A union of all valid types for a single schema entry
197
166
  *
198
167
  * Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
199
168
  */
200
- type SchemaEntry = Constructor | Schema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
169
+ type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
201
170
  /**
202
171
  * Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
203
172
  */
204
173
  interface SchemaIndex {
205
- [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
174
+ [key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
206
175
  }
207
176
  /**
208
177
  * A property definition with explicit type(s), an optional requirement flag, and optional validators
@@ -219,19 +188,19 @@ interface SchemaIndex {
219
188
  * };
220
189
  * ```
221
190
  */
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[]>;
191
+ type SchemaProperty = {
192
+ /**
193
+ * Whether the property is required _(defaults to `true`)_
194
+ */
195
+ $required?: boolean;
196
+ /**
197
+ * The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
198
+ */
199
+ $type: SchemaPropertyType | SchemaPropertyType[];
200
+ /**
201
+ * Optional validators keyed by {@link ValueName}, applied during validation
202
+ */
203
+ $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
235
204
  };
236
205
  /**
237
206
  * A union of valid types for a {@link SchemaProperty}'s `$type` field
@@ -240,15 +209,10 @@ export type SchemaProperty = {
240
209
  */
241
210
  type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
242
211
  /**
243
- * A custom error class for schema validation failures, with its `name` set to {@link ERROR_NAME}
244
- *
245
- * @example
246
- * ```ts
247
- * throw new SchematicError('Expected a string, received a number');
248
- * ```
212
+ * A custom error class for schematic validation failures
249
213
  */
250
- export declare class SchematicError extends Error {
251
- constructor(message: string);
214
+ declare class SchematicError extends Error {
215
+ constructor(message: string);
252
216
  }
253
217
  /**
254
218
  * Converts a type into its corresponding {@link SchemaPropertyType}-representation
@@ -265,7 +229,7 @@ type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaProp
265
229
  *
266
230
  * @template Value - type to convert
267
231
  */
268
- type ToSchemaPropertyTypeEach<Value> = Value extends NestedSchema ? Omit<Value, '$required'> : Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
232
+ type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
269
233
  /**
270
234
  * Converts a type into its corresponding {@link ValueName}-representation
271
235
  *
@@ -288,9 +252,7 @@ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionTo
288
252
  * // ToValueType<Date> => 'date'
289
253
  * ```
290
254
  */
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;
255
+ 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
256
  /**
295
257
  * Generates all permutations of a tuple type
296
258
  *
@@ -305,12 +267,7 @@ type ToValueType<Value> = Value extends Schematic<any> ? Value : {
305
267
  * // => ['string', 'number'] | ['number', 'string']
306
268
  * ```
307
269
  */
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}`];
270
+ 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
271
  /**
315
272
  * Removes the element at a given index from a tuple
316
273
  *
@@ -333,19 +290,19 @@ type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends
333
290
  * // => { $required: false; $type: 'string'; ... }
334
291
  * ```
335
292
  */
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>>>;
293
+ type TypedPropertyOptional<Value> = {
294
+ /**
295
+ * The property is not required
296
+ */
297
+ $required: false;
298
+ /**
299
+ * The type(s) of the property
300
+ */
301
+ $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
302
+ /**
303
+ * Custom validators for the property and its types
304
+ */
305
+ $validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
349
306
  };
350
307
  /**
351
308
  * A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
@@ -359,19 +316,19 @@ export type TypedPropertyOptional<Value> = {
359
316
  * // => { $required?: true; $type: 'string'; ... }
360
317
  * ```
361
318
  */
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>>;
319
+ type TypedPropertyRequired<Value> = {
320
+ /**
321
+ * The property is required _(defaults to `true`)_
322
+ */
323
+ $required?: true;
324
+ /**
325
+ * The type(s) of the property
326
+ */
327
+ $type: ToSchemaPropertyType<Value>;
328
+ /**
329
+ * Custom validators for the property and its types
330
+ */
331
+ $validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
375
332
  };
376
333
  /**
377
334
  * Creates a schema type constrained to match a TypeScript type
@@ -391,18 +348,14 @@ export type TypedPropertyRequired<Value> = {
391
348
  * };
392
349
  * ```
393
350
  */
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
- }>;
351
+ 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
352
  /**
400
353
  * A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
401
354
  *
402
355
  * @template Model - Nested object type
403
356
  */
404
357
  type TypedSchemaOptional<Model extends PlainObject> = {
405
- $required: false;
358
+ $required: false;
406
359
  } & TypedSchema<Model>;
407
360
  /**
408
361
  * A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
@@ -410,7 +363,7 @@ type TypedSchemaOptional<Model extends PlainObject> = {
410
363
  * @template Model - Nested object type
411
364
  */
412
365
  type TypedSchemaRequired<Model extends PlainObject> = {
413
- $required?: true;
366
+ $required?: true;
414
367
  } & TypedSchema<Model>;
415
368
  /**
416
369
  * Converts a union type into an intersection
@@ -425,7 +378,7 @@ type TypedSchemaRequired<Model extends PlainObject> = {
425
378
  * // => { a: 1 } & { b: 2 }
426
379
  * ```
427
380
  */
428
- type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (value: infer Item) => void ? Item : never;
381
+ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends ((value: infer Item) => void) ? Item : never;
429
382
  /**
430
383
  * Converts a union type into an ordered tuple
431
384
  *
@@ -468,42 +421,40 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only :
468
421
  * };
469
422
  * ```
470
423
  */
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;
424
+ type ValidatedProperty = {
425
+ /**
426
+ * The property name in the schema
427
+ */
428
+ key: string;
429
+ /**
430
+ * Whether the property is required
431
+ */
432
+ required: boolean;
433
+ /**
434
+ * The allowed types for this property
435
+ */
436
+ types: ValidatedPropertyType[];
437
+ /**
438
+ * Custom validators grouped by {@link ValueName}
439
+ */
440
+ validators: ValidatedPropertyValidators;
488
441
  };
489
442
  /**
490
443
  * A union of valid types for a {@link ValidatedProperty}'s `types` array
491
444
  *
492
445
  * Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
493
446
  */
494
- export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
447
+ type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
495
448
  /**
496
449
  * A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
497
450
  *
498
451
  * Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
499
452
  */
500
- export type ValidatedPropertyValidators = {
501
- [Key in ValueName]?: Array<(value: unknown) => boolean>;
502
- };
453
+ type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
503
454
  /**
504
455
  * Basic value types
505
456
  */
506
- export type ValueName = keyof Values;
457
+ type ValueName = keyof Values;
507
458
  /**
508
459
  * Maps type name strings to their TypeScript equivalents
509
460
  *
@@ -516,17 +467,18 @@ export type ValueName = keyof Values;
516
467
  * // Values['null'] => null
517
468
  * ```
518
469
  */
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;
470
+ type Values = {
471
+ array: unknown[];
472
+ bigint: bigint;
473
+ boolean: boolean;
474
+ date: Date;
475
+ function: Function;
476
+ null: null;
477
+ number: number;
478
+ object: object;
479
+ string: string;
480
+ symbol: symbol;
481
+ undefined: undefined;
531
482
  };
532
- export {};
483
+ //#endregion
484
+ export { Infer, Schema, SchemaProperty, SchematicError, TypedPropertyOptional, TypedPropertyRequired, TypedSchema, ValidatedProperty, ValidatedPropertyType, ValidatedPropertyValidators, ValueName, Values };
@@ -0,0 +1,13 @@
1
+ import { ERROR_NAME } from "./constants.mjs";
2
+ //#region src/models.ts
3
+ /**
4
+ * A custom error class for schematic validation failures
5
+ */
6
+ var SchematicError = class extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = ERROR_NAME;
10
+ }
11
+ };
12
+ //#endregion
13
+ export { SchematicError };