@oscarpalmer/jhunal 0.19.0 → 0.21.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/index.mjs CHANGED
@@ -1,7 +1,12 @@
1
1
  import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
2
2
  import { join } from "@oscarpalmer/atoms/string";
3
- import { error } from "@oscarpalmer/atoms/result/misc";
3
+ import { error, ok } from "@oscarpalmer/atoms/result/misc";
4
+ import { clone } from "@oscarpalmer/atoms/value/clone";
4
5
  //#region src/constants.ts
6
+ const CONJUNCTION_OR = " or ";
7
+ const CONJUNCTION_OR_COMMA = ", or ";
8
+ const CONJUNCTION_AND = " and ";
9
+ const CONJUNCTION_AND_COMMA = ", and ";
5
10
  const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
6
11
  const NAME_SCHEMATIC = "Schematic";
7
12
  const NAME_ERROR_SCHEMATIC = "SchematicError";
@@ -15,6 +20,7 @@ const VALIDATION_MESSAGE_INVALID_REQUIRED = "Expected <> for required property '
15
20
  const VALIDATION_MESSAGE_INVALID_TYPE = "Expected <> for '<>' but received <>";
16
21
  const VALIDATION_MESSAGE_INVALID_VALUE = "Value does not satisfy validator for '<>' and type '<>'";
17
22
  const VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX = " at index <>";
23
+ const VALIDATION_MESSAGE_UNKNOWN_KEYS = "Found keys that are not defined in the schema: <>";
18
24
  const REPORTING_FIRST = "first";
19
25
  const REPORTING_NONE = "none";
20
26
  const REPORTING_THROW = "throw";
@@ -58,23 +64,41 @@ const TYPE_ALL = new Set([
58
64
  function getInvalidInputMessage(actual) {
59
65
  return VALIDATION_MESSAGE_INVALID_INPUT.replace("<>", getValueType(actual));
60
66
  }
61
- function getInvalidMissingMessage(property) {
62
- let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace("<>", renderTypes(property.types));
63
- message = message.replace("<>", property.key.full);
67
+ function getInvalidMissingMessage(key, types) {
68
+ let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace("<>", renderTypes(types));
69
+ message = message.replace("<>", key);
64
70
  return message;
65
71
  }
66
- function getInvalidTypeMessage(property, actual) {
67
- let message = VALIDATION_MESSAGE_INVALID_TYPE.replace("<>", renderTypes(property.types));
68
- message = message.replace("<>", property.key.full);
72
+ function getInvalidTypeMessage(key, types, actual) {
73
+ let message = VALIDATION_MESSAGE_INVALID_TYPE.replace("<>", renderTypes(types));
74
+ message = message.replace("<>", key);
69
75
  message = message.replace("<>", getValueType(actual));
70
76
  return message;
71
77
  }
72
- function getInvalidValidatorMessage(property, type, index, length) {
73
- let message = VALIDATION_MESSAGE_INVALID_VALUE.replace("<>", property.key.full);
78
+ function getInvalidValidatorMessage(key, type, index, length) {
79
+ let message = VALIDATION_MESSAGE_INVALID_VALUE.replace("<>", key);
74
80
  message = message.replace("<>", type);
75
81
  if (length > 1) message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace("<>", String(index));
76
82
  return message;
77
83
  }
84
+ function getParameters(input) {
85
+ if (typeof input === "boolean") return {
86
+ output: {},
87
+ reporting: getReporting(REPORTING_NONE),
88
+ strict: input
89
+ };
90
+ if (REPORTING_TYPES.has(input)) return {
91
+ output: {},
92
+ reporting: getReporting(input),
93
+ strict: false
94
+ };
95
+ const options = isPlainObject(input) ? input : {};
96
+ return {
97
+ output: {},
98
+ reporting: getReporting(options.errors),
99
+ strict: typeof options.strict === "boolean" ? options.strict : false
100
+ };
101
+ }
78
102
  function getPropertyType(original) {
79
103
  if (typeof original === "function") return "a validated value";
80
104
  if (Array.isArray(original)) return `'${TYPE_OBJECT}'`;
@@ -84,12 +108,16 @@ function getPropertyType(original) {
84
108
  function getReporting(value) {
85
109
  const type = REPORTING_TYPES.has(value) ? value : REPORTING_NONE;
86
110
  return {
111
+ type,
87
112
  ["all"]: type === "all",
88
113
  [REPORTING_FIRST]: type === REPORTING_FIRST,
89
114
  [REPORTING_NONE]: type === REPORTING_NONE,
90
115
  [REPORTING_THROW]: type === REPORTING_THROW
91
116
  };
92
117
  }
118
+ function getUnknownKeysMessage(keys) {
119
+ return VALIDATION_MESSAGE_UNKNOWN_KEYS.replace("<>", renderKeys(keys));
120
+ }
93
121
  function getValueType(value) {
94
122
  const valueType = typeof value;
95
123
  switch (true) {
@@ -122,6 +150,20 @@ function instanceOf(constructor) {
122
150
  function isSchematic(value) {
123
151
  return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
124
152
  }
153
+ function renderKeys(keys) {
154
+ return renderParts(keys.map((key) => `'${key}'`), CONJUNCTION_AND, CONJUNCTION_AND_COMMA);
155
+ }
156
+ function renderParts(parts, delimiterShort, delimiterLong) {
157
+ const { length } = parts;
158
+ if (length === 1) return parts[0];
159
+ let rendered = "";
160
+ for (let index = 0; index < length; index += 1) {
161
+ rendered += parts[index];
162
+ if (index < length - 2) rendered += ", ";
163
+ else if (index === length - 2) rendered += parts.length > 2 ? delimiterLong : delimiterShort;
164
+ }
165
+ return rendered;
166
+ }
125
167
  function renderTypes(types) {
126
168
  const unique = /* @__PURE__ */ new Set();
127
169
  const parts = [];
@@ -131,19 +173,12 @@ function renderTypes(types) {
131
173
  unique.add(rendered);
132
174
  parts.push(rendered);
133
175
  }
134
- const { length } = parts;
135
- let rendered = "";
136
- for (let index = 0; index < length; index += 1) {
137
- rendered += parts[index];
138
- if (index < length - 2) rendered += ", ";
139
- else if (index === length - 2) rendered += parts.length > 2 ? ", or " : " or ";
140
- }
141
- return rendered;
176
+ return renderParts(parts, CONJUNCTION_OR, CONJUNCTION_OR_COMMA);
142
177
  }
143
178
  //#endregion
144
179
  //#region src/models/validation.model.ts
145
180
  /**
146
- * A custom error class for schematic validation failures
181
+ * Thrown when a schema definition is invalid
147
182
  */
148
183
  var SchematicError = class extends Error {
149
184
  constructor(message) {
@@ -151,6 +186,9 @@ var SchematicError = class extends Error {
151
186
  this.name = NAME_ERROR_SCHEMATIC;
152
187
  }
153
188
  };
189
+ /**
190
+ * Thrown in `'throw'` mode when one or more properties fail validation; `information` holds all failures
191
+ */
154
192
  var ValidationError = class extends Error {
155
193
  constructor(information) {
156
194
  super(join(information.map((item) => item.message), "; "));
@@ -176,9 +214,8 @@ function getProperties(original, prefix, fromType) {
176
214
  const properties = [];
177
215
  for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
178
216
  const key = keys[keyIndex];
179
- const prefixed = join([prefix, key], ".");
180
217
  const value = original[key];
181
- if (value == null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", prefixed));
218
+ if (value == null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", join([prefix, key], ".")));
182
219
  const types = [];
183
220
  let required = true;
184
221
  let validators = {};
@@ -190,12 +227,9 @@ function getProperties(original, prefix, fromType) {
190
227
  } else types.push(...getTypes(key, value, prefix));
191
228
  if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
192
229
  properties.push({
230
+ key,
193
231
  types,
194
232
  validators,
195
- key: {
196
- full: prefixed,
197
- short: key
198
- },
199
233
  required: required && !types.includes("undefined")
200
234
  });
201
235
  }
@@ -250,18 +284,21 @@ function getValidators(original) {
250
284
  }
251
285
  //#endregion
252
286
  //#region src/validation/value.validation.ts
253
- function validateNamed(property, name, value, validation) {
287
+ function validateNamed(name, value, parameters) {
254
288
  if (!validators[name](value)) return false;
255
- const propertyValidators = property.validators[name];
289
+ const propertyValidators = parameters.origin.validators[name];
256
290
  if (propertyValidators == null || propertyValidators.length === 0) return true;
257
291
  const { length } = propertyValidators;
258
292
  for (let index = 0; index < length; index += 1) {
259
293
  const validator = propertyValidators[index];
260
294
  if (!validator(value)) {
261
- validation.push({
295
+ parameters.information.push({
262
296
  value,
263
- key: { ...property.key },
264
- message: getInvalidValidatorMessage(property, name, index, length),
297
+ key: {
298
+ full: parameters.prefix,
299
+ short: parameters.origin.key
300
+ },
301
+ message: getInvalidValidatorMessage(parameters.prefix, name, index, length),
265
302
  validator
266
303
  });
267
304
  return false;
@@ -269,69 +306,121 @@ function validateNamed(property, name, value, validation) {
269
306
  }
270
307
  return true;
271
308
  }
272
- function validateObject(obj, properties, reporting, property, validation) {
309
+ function validateObject(obj, properties, parameters, get) {
273
310
  if (!isPlainObject(obj)) {
311
+ const key = parameters?.origin == null ? {
312
+ full: "",
313
+ short: ""
314
+ } : {
315
+ full: parameters.prefix,
316
+ short: parameters.origin.key
317
+ };
274
318
  const information = {
275
- key: {
276
- full: "",
277
- short: ""
278
- },
279
- message: property == null ? getInvalidInputMessage(obj) : getInvalidTypeMessage(property, obj),
319
+ key,
320
+ message: parameters?.origin == null ? getInvalidInputMessage(obj) : getInvalidTypeMessage(key.full, parameters.origin.types, obj),
280
321
  value: obj
281
322
  };
282
- if (reporting.throw) throw new ValidationError([information]);
283
- return reporting.none ? false : [information];
323
+ if (parameters.reporting.throw) throw new ValidationError([information]);
324
+ parameters?.information?.push(information);
325
+ return parameters.reporting.none ? void 0 : [information];
326
+ }
327
+ if (parameters.strict) {
328
+ const objKeys = Object.keys(obj);
329
+ const propertiesKeys = new Set(properties.map((property) => property.key));
330
+ const unknownKeys = objKeys.filter((key) => !propertiesKeys.has(key));
331
+ if (unknownKeys.length > 0) {
332
+ const information = {
333
+ key: parameters?.origin == null ? {
334
+ full: "",
335
+ short: ""
336
+ } : {
337
+ full: join([parameters.prefix, parameters.origin?.key], "."),
338
+ short: parameters.origin.key
339
+ },
340
+ message: getUnknownKeysMessage(unknownKeys.map((key) => join([parameters?.prefix, key], "."))),
341
+ value: obj
342
+ };
343
+ if (parameters.reporting.throw) throw new ValidationError([information]);
344
+ parameters?.information?.push(information);
345
+ return parameters.reporting.none ? void 0 : [information];
346
+ }
284
347
  }
285
348
  const allInformation = [];
349
+ const output = {};
286
350
  const propertiesLength = properties.length;
287
351
  outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
288
352
  const property = properties[propertyIndex];
289
353
  const { key, required, types } = property;
290
- const value = obj[key.short];
354
+ const value = obj[key];
355
+ if (get && value === void 0 && !required) continue;
291
356
  if (value === void 0 && required) {
357
+ const prefixedKey = join([parameters.prefix, key], ".");
292
358
  const information = {
293
359
  value,
294
- key: { ...key },
295
- message: getInvalidMissingMessage(property)
360
+ key: {
361
+ full: prefixedKey,
362
+ short: key
363
+ },
364
+ message: getInvalidMissingMessage(prefixedKey, property.types)
296
365
  };
297
- if (reporting.throw && validation == null) throw new ValidationError([information]);
298
- if (validation != null) validation.push(information);
299
- if (reporting.all) {
366
+ if (parameters.reporting.throw) throw new ValidationError([information]);
367
+ parameters?.information?.push(information);
368
+ if (parameters.reporting.all) {
300
369
  allInformation.push(information);
301
370
  continue;
302
371
  }
303
- return reporting.none ? false : [information];
372
+ return parameters.reporting.none ? void 0 : [information];
304
373
  }
374
+ const prefixedKey = join([parameters.prefix, key], ".");
305
375
  const typesLength = types.length;
306
376
  const information = [];
307
377
  for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
308
378
  const type = types[typeIndex];
309
- if (validateValue(type, property, value, reporting, information)) continue outer;
379
+ if (validateValue(type, value, {
380
+ information,
381
+ output,
382
+ origin: property,
383
+ prefix: prefixedKey,
384
+ reporting: parameters.reporting,
385
+ strict: parameters.strict
386
+ }, get)) {
387
+ if (get) output[key] = clone(value);
388
+ continue outer;
389
+ }
310
390
  }
311
391
  if (information.length === 0) information.push({
312
392
  value,
313
- key: { ...key },
314
- message: getInvalidTypeMessage(property, value)
393
+ key: {
394
+ full: prefixedKey,
395
+ short: key
396
+ },
397
+ message: getInvalidTypeMessage(prefixedKey, property.types, value)
315
398
  });
316
- if (reporting.throw && validation == null) throw new ValidationError(information);
317
- validation?.push(...information);
318
- if (reporting.all) {
399
+ if (parameters.reporting.throw) throw new ValidationError(information);
400
+ parameters?.information?.push(...information);
401
+ if (parameters.reporting.all) {
319
402
  allInformation.push(...information);
320
403
  continue;
321
404
  }
322
- return reporting.none ? false : information;
405
+ return parameters.reporting.none ? void 0 : information;
323
406
  }
324
- return reporting.none ? true : allInformation.length === 0 ? true : allInformation;
407
+ if (get) if (parameters.origin == null) parameters.output = output;
408
+ else parameters.output[parameters.origin.key] = output;
409
+ return parameters.reporting.none || allInformation.length === 0 ? parameters.output : allInformation;
325
410
  }
326
- function validateValue(type, property, value, reporting, validation) {
411
+ function validateSchematic(schematic, value, parameters, get) {
412
+ const result = validateObject(value, schematicProperties.get(schematic), parameters, get);
413
+ return result == null || Array.isArray(result) ? false : true;
414
+ }
415
+ function validateValue(type, value, parameters, get) {
327
416
  switch (true) {
328
417
  case typeof type === "function": return type(value);
329
418
  case Array.isArray(type): {
330
- const nested = validateObject(value, type, reporting, property, validation);
331
- return typeof nested !== "boolean" ? false : nested;
419
+ const result = validateObject(value, type, parameters, get);
420
+ return result == null || Array.isArray(result) ? false : true;
332
421
  }
333
- case isSchematic(type): return type.is(value, reporting);
334
- default: return validateNamed(property, type, value, validation);
422
+ case isSchematic(type): return validateSchematic(type, value, parameters, get);
423
+ default: return validateNamed(type, value, parameters);
335
424
  }
336
425
  }
337
426
  const validators = {
@@ -357,12 +446,21 @@ var Schematic = class {
357
446
  constructor(properties) {
358
447
  Object.defineProperty(this, PROPERTY_SCHEMATIC, { value: true });
359
448
  this.#properties = properties;
449
+ schematicProperties.set(this, properties);
450
+ }
451
+ get(value, options) {
452
+ const parameters = getParameters(options);
453
+ const result = validateObject(value, this.#properties, parameters, true);
454
+ if (result == null) return;
455
+ if (!Array.isArray(result)) return parameters.reporting.none ? result : ok(result);
456
+ return error(parameters.reporting.all ? result : result[0]);
360
457
  }
361
- is(value, errors) {
362
- const reporting = getReporting(errors);
363
- const result = validateObject(value, this.#properties, reporting);
364
- if (typeof result === "boolean") return result;
365
- return error(reporting.all ? result : result[0]);
458
+ is(value, options) {
459
+ const parameters = getParameters(options);
460
+ const result = validateObject(value, this.#properties, parameters, false);
461
+ if (result == null) return false;
462
+ if (!Array.isArray(result)) return parameters.reporting.none ? true : ok(true);
463
+ return error(parameters.reporting.all ? result : result[0]);
366
464
  }
367
465
  };
368
466
  function schematic(schema) {
@@ -370,5 +468,6 @@ function schematic(schema) {
370
468
  if (!isPlainObject(schema)) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
371
469
  return new Schematic(getProperties(schema));
372
470
  }
471
+ const schematicProperties = /* @__PURE__ */ new WeakMap();
373
472
  //#endregion
374
473
  export { SchematicError, ValidationError, instanceOf, isSchematic, schematic };
@@ -7,7 +7,7 @@ import { Constructor, Simplify } from "@oscarpalmer/atoms/models";
7
7
  /**
8
8
  * Infers the TypeScript type from a {@link Schema} definition
9
9
  *
10
- * @template Model - Schema to infer types from
10
+ * @template Model Schema to infer types from
11
11
  *
12
12
  * @example
13
13
  * ```ts
@@ -29,37 +29,37 @@ type Infer<Model extends Schema> = Simplify<{ [Key in InferRequiredKeys<Model>]:
29
29
  */
30
30
  type InferOptionalKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never };
31
31
  /**
32
- * Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
32
+ * Infers the TypeScript type from a {@link SchemaProperty}'s `$type` field
33
33
  *
34
- * @template Value - `$type` value _(single or array)_
34
+ * @template Value `$type` value _(single or array)_
35
35
  */
36
36
  type InferPropertyType<Value> = Value extends (infer Item)[] ? InferPropertyValue<Item> : InferPropertyValue<Value>;
37
37
  /**
38
- * Maps a single type definition to its TypeScript equivalent
38
+ * Maps a single `$type` definition to its TypeScript equivalent
39
39
  *
40
- * Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link Schema} objects
40
+ * Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link PlainSchema} objects
41
41
  *
42
- * @template Value - single type definition
42
+ * @template Value single type definition
43
43
  */
44
44
  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;
45
45
  /**
46
46
  * Extracts keys from a {@link Schema} whose entries are required _(i.e., `$required` is not `false`)_
47
47
  *
48
- * @template Model - Schema to extract required keys from
48
+ * @template Model Schema to extract required keys from
49
49
  */
50
50
  type InferRequiredKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never };
51
51
  /**
52
- * Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
52
+ * Infers the TypeScript type from a top-level {@link Schema} entry
53
53
  *
54
- * @template Value - Schema entry value _(single or array)_
54
+ * @template Value Schema entry value _(single or array)_
55
55
  */
56
56
  type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryValue<Item> : InferSchemaEntryValue<Value>;
57
57
  /**
58
- * Resolves a single schema entry to its TypeScript type
58
+ * Maps a single top-level schema entry to its TypeScript type
59
59
  *
60
- * Handles, in order: {@link Constructor} instances, {@link Schematic} models, {@link SchemaProperty} objects, {@link NestedSchema} objects, {@link ValueName} strings, and plain {@link Schema} objects
60
+ * Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link SchemaProperty} objects, {@link PlainSchema} objects, and {@link ValueName} strings
61
61
  *
62
- * @template Value - single schema entry
62
+ * @template Value single schema entry
63
63
  */
64
64
  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;
65
65
  //#endregion
@@ -4,8 +4,8 @@ import { SchemaProperty } from "./schema.plain.model.mjs";
4
4
  /**
5
5
  * Removes duplicate types from a tuple, preserving first occurrence order
6
6
  *
7
- * @template Value - Tuple to deduplicate
8
- * @template Seen - Accumulator for already-seen types _(internal)_
7
+ * @template Value Tuple to deduplicate
8
+ * @template Seen Accumulator for already-seen types _(internal)_
9
9
  *
10
10
  * @example
11
11
  * ```ts
@@ -17,7 +17,7 @@ type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Va
17
17
  /**
18
18
  * Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
19
19
  *
20
- * @template Value - Type to extract value names from
20
+ * @template Value Type to extract value names from
21
21
  *
22
22
  * @example
23
23
  * ```ts
@@ -29,27 +29,27 @@ type ExtractValueNames<Value> = Value extends ValueName ? Value : Value extends
29
29
  /**
30
30
  * Determines whether a schema entry is optional
31
31
  *
32
- * Returns `true` if the entry is a {@link SchemaProperty} or {@link NestedSchema} with `$required` set to `false`; otherwise returns `false`
32
+ * Returns `true` if the entry is a {@link SchemaProperty} with `$required` set to `false`; otherwise returns `false`
33
33
  *
34
- * @template Value - Schema entry to check
34
+ * @template Value Schema entry to check
35
35
  */
36
36
  type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
37
37
  /**
38
- * Extracts the last member from a union type by leveraging intersection of function return types
38
+ * Extracts the last member from a union type by leveraging contravariance of function parameter types
39
39
  *
40
- * @template Value - Union type
40
+ * @template Value Union type
41
41
  */
42
42
  type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
43
43
  /**
44
44
  * Extracts keys from an object type that are optional
45
45
  *
46
- * @template Value - Object type to inspect
46
+ * @template Value Object type to inspect
47
47
  */
48
48
  type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
49
49
  /**
50
50
  * Extracts keys from an object type that are required _(i.e., not optional)_
51
51
  *
52
- * @template Value - Object type to inspect
52
+ * @template Value Object type to inspect
53
53
  */
54
54
  type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
55
55
  /**
@@ -57,8 +57,8 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
57
57
  *
58
58
  * Used by {@link UnwrapSingle} to allow schema types in any order for small tuples _(length ≤ 5)_
59
59
  *
60
- * @template Tuple - Tuple to permute
61
- * @template Elput - Accumulator for the current permutation _(internal; name is Tuple backwards)_
60
+ * @template Tuple Tuple to permute
61
+ * @template Elput Accumulator for the current permutation _(internal; name is Tuple backwards)_
62
62
  *
63
63
  * @example
64
64
  * ```ts
@@ -72,9 +72,9 @@ type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> =
72
72
  *
73
73
  * Used internally by {@link TuplePermutations}
74
74
  *
75
- * @template Items - Tuple to remove from
76
- * @template Item - Stringified index to remove
77
- * @template Prefix - Accumulator for elements before the target _(internal)_
75
+ * @template Items Tuple to remove from
76
+ * @template Item Index as a string literal
77
+ * @template Prefix Accumulator for elements before the target _(internal)_
78
78
  */
79
79
  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;
80
80
  /**
@@ -82,7 +82,7 @@ type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends
82
82
  *
83
83
  * Uses the contravariance of function parameter types to collapse a union into an intersection
84
84
  *
85
- * @template Value - Union type to convert
85
+ * @template Value Union type to convert
86
86
  *
87
87
  * @example
88
88
  * ```ts
@@ -96,8 +96,8 @@ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => voi
96
96
  *
97
97
  * Repeatedly extracts the {@link LastOfUnion} member and prepends it to the accumulator
98
98
  *
99
- * @template Value - Union type to convert
100
- * @template Items - Accumulator for the resulting tuple _(internal)_
99
+ * @template Value Union type to convert
100
+ * @template Items Accumulator for the resulting tuple _(internal)_
101
101
  *
102
102
  * @example
103
103
  * ```ts
@@ -111,7 +111,7 @@ type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never]
111
111
  *
112
112
  * For tuples of length 2–5, returns all {@link TuplePermutations} to allow types in any order. Longer tuples are returned as-is
113
113
  *
114
- * @template Value - Tuple to potentially unwrap
114
+ * @template Value Tuple to potentially unwrap
115
115
  *
116
116
  * @example
117
117
  * ```ts
@@ -121,20 +121,11 @@ type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never]
121
121
  */
122
122
  type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
123
123
  /**
124
- * Basic value types
124
+ * A union of valid type name strings, e.g. `'string'`, `'number'`, `'date'`
125
125
  */
126
126
  type ValueName = keyof Values;
127
127
  /**
128
- * Maps type name strings to their TypeScript equivalents
129
- *
130
- * Used by the type system to resolve {@link ValueName} strings into actual types
131
- *
132
- * @example
133
- * ```ts
134
- * // Values['string'] => string
135
- * // Values['date'] => Date
136
- * // Values['null'] => null
137
- * ```
128
+ * Maps {@link ValueName} strings to their TypeScript equivalents
138
129
  */
139
130
  type Values = {
140
131
  array: unknown[];
@@ -4,7 +4,7 @@ import { Constructor } from "@oscarpalmer/atoms/models";
4
4
 
5
5
  //#region src/models/schema.plain.model.d.ts
6
6
  /**
7
- * A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
7
+ * A generic schema allowing nested schemas, {@link SchemaEntry} values, or arrays of {@link SchemaEntry} as values
8
8
  */
9
9
  type PlainSchema = {
10
10
  [key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
@@ -29,11 +29,11 @@ type Schema = SchemaIndex;
29
29
  /**
30
30
  * A union of all valid types for a single schema entry
31
31
  *
32
- * Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
32
+ * Can be a {@link Constructor}, {@link PlainSchema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
33
33
  */
34
34
  type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
35
35
  /**
36
- * Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
36
+ * Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link PlainSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
37
37
  */
38
38
  interface SchemaIndex {
39
39
  [key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
@@ -78,7 +78,7 @@ type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | Value
78
78
  *
79
79
  * Each key may hold a single validator or an array of validators that receive the typed value
80
80
  *
81
- * @template Value - `$type` value(s) to derive validator keys from
81
+ * @template Value `$type` value(s) to derive validator keys from
82
82
  *
83
83
  * @example
84
84
  * ```ts
@@ -8,7 +8,7 @@ import { PlainObject, Simplify } from "@oscarpalmer/atoms/models";
8
8
  /**
9
9
  * A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
10
10
  *
11
- * @template Value - Property's type _(including `undefined`)_
11
+ * @template Value Property's type _(including `undefined`)_
12
12
  *
13
13
  * @example
14
14
  * ```ts
@@ -18,23 +18,14 @@ import { PlainObject, Simplify } from "@oscarpalmer/atoms/models";
18
18
  * ```
19
19
  */
20
20
  type TypedPropertyOptional<Value> = {
21
- /**
22
- * The property is not required
23
- */
24
21
  $required: false;
25
- /**
26
- * The type(s) of the property
27
- */
28
22
  $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
29
- /**
30
- * Custom validators for the property and its types
31
- */
32
23
  $validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
33
24
  };
34
25
  /**
35
26
  * A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
36
27
  *
37
- * @template Value - Property's type
28
+ * @template Value Property's type
38
29
  *
39
30
  * @example
40
31
  * ```ts
@@ -44,17 +35,8 @@ type TypedPropertyOptional<Value> = {
44
35
  * ```
45
36
  */
46
37
  type TypedPropertyRequired<Value> = {
47
- /**
48
- * The property is required _(defaults to `true`)_
49
- */
50
38
  $required?: true;
51
- /**
52
- * The type(s) of the property
53
- */
54
39
  $type: ToSchemaPropertyType<Value>;
55
- /**
56
- * Custom validators for the property and its types
57
- */
58
40
  $validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
59
41
  };
60
42
  /**
@@ -62,7 +44,7 @@ type TypedPropertyRequired<Value> = {
62
44
  *
63
45
  * 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}
64
46
  *
65
- * @template Model - Object type to generate a schema for
47
+ * @template Model Object type to generate a schema for
66
48
  *
67
49
  * @example
68
50
  * ```ts
@@ -79,7 +61,7 @@ type TypedSchema<Model extends PlainObject> = Simplify<{ [Key in RequiredKeys<Mo
79
61
  /**
80
62
  * A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
81
63
  *
82
- * @template Model - Nested object type
64
+ * @template Model Nested object type
83
65
  */
84
66
  type TypedSchemaOptional<Model extends PlainObject> = {
85
67
  $required: false;
@@ -87,7 +69,7 @@ type TypedSchemaOptional<Model extends PlainObject> = {
87
69
  /**
88
70
  * A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
89
71
  *
90
- * @template Model - Nested object type
72
+ * @template Model Nested object type
91
73
  */
92
74
  type TypedSchemaRequired<Model extends PlainObject> = {
93
75
  $required?: true;