@oscarpalmer/jhunal 0.17.0 → 0.19.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.
Files changed (40) hide show
  1. package/dist/constants.d.mts +28 -15
  2. package/dist/constants.mjs +31 -14
  3. package/dist/helpers.d.mts +8 -1
  4. package/dist/helpers.mjs +68 -3
  5. package/dist/index.d.mts +304 -240
  6. package/dist/index.mjs +201 -51
  7. package/dist/models/infer.model.d.mts +66 -0
  8. package/dist/models/infer.model.mjs +1 -0
  9. package/dist/models/misc.model.d.mts +153 -0
  10. package/dist/models/misc.model.mjs +1 -0
  11. package/dist/models/schema.plain.model.d.mts +92 -0
  12. package/dist/models/schema.plain.model.mjs +1 -0
  13. package/dist/models/schema.typed.model.d.mts +96 -0
  14. package/dist/models/schema.typed.model.mjs +1 -0
  15. package/dist/models/transform.model.d.mts +59 -0
  16. package/dist/models/transform.model.mjs +1 -0
  17. package/dist/models/validation.model.d.mts +82 -0
  18. package/dist/models/validation.model.mjs +21 -0
  19. package/dist/schematic.d.mts +34 -1
  20. package/dist/schematic.mjs +11 -12
  21. package/dist/validation/property.validation.d.mts +1 -1
  22. package/dist/validation/property.validation.mjs +21 -17
  23. package/dist/validation/value.validation.d.mts +2 -2
  24. package/dist/validation/value.validation.mjs +73 -12
  25. package/package.json +2 -2
  26. package/src/constants.ts +84 -19
  27. package/src/helpers.ts +162 -4
  28. package/src/index.ts +3 -1
  29. package/src/models/infer.model.ts +105 -0
  30. package/src/models/misc.model.ts +212 -0
  31. package/src/models/schema.plain.model.ts +110 -0
  32. package/src/models/schema.typed.model.ts +109 -0
  33. package/src/models/transform.model.ts +85 -0
  34. package/src/models/validation.model.ts +124 -0
  35. package/src/schematic.ts +55 -10
  36. package/src/validation/property.validation.ts +41 -36
  37. package/src/validation/value.validation.ts +133 -20
  38. package/dist/models.d.mts +0 -484
  39. package/dist/models.mjs +0 -13
  40. package/src/models.ts +0 -665
package/dist/index.mjs CHANGED
@@ -1,23 +1,40 @@
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
4
  //#region src/constants.ts
4
- const ERROR_NAME = "SchematicError";
5
5
  const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
6
- const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
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'";
9
- const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
10
- const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
11
- const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
12
- const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
13
- const MESSAGE_VALIDATOR_INVALID_TYPE = "Validators must be an object";
14
- const MESSAGE_VALIDATOR_INVALID_VALUE = "Validator '<>' must be a function or an array of functions";
6
+ const NAME_SCHEMATIC = "Schematic";
7
+ const NAME_ERROR_SCHEMATIC = "SchematicError";
8
+ const NAME_ERROR_VALIDATION = "ValidationError";
15
9
  const PROPERTY_REQUIRED = "$required";
10
+ const PROPERTY_SCHEMATIC = "$schematic";
16
11
  const PROPERTY_TYPE = "$type";
17
12
  const PROPERTY_VALIDATORS = "$validators";
18
- const SCHEMATIC_NAME = "$schematic";
19
- const TEMPLATE_PATTERN_KEY = "<key>";
20
- const TEMPLATE_PATTERN_PROPERTY = "<property>";
13
+ const VALIDATION_MESSAGE_INVALID_INPUT = "Expected 'object' as input but received <>";
14
+ const VALIDATION_MESSAGE_INVALID_REQUIRED = "Expected <> for required property '<>'";
15
+ const VALIDATION_MESSAGE_INVALID_TYPE = "Expected <> for '<>' but received <>";
16
+ const VALIDATION_MESSAGE_INVALID_VALUE = "Value does not satisfy validator for '<>' and type '<>'";
17
+ const VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX = " at index <>";
18
+ const REPORTING_FIRST = "first";
19
+ const REPORTING_NONE = "none";
20
+ const REPORTING_THROW = "throw";
21
+ const REPORTING_TYPES = new Set([
22
+ "all",
23
+ REPORTING_FIRST,
24
+ REPORTING_NONE,
25
+ REPORTING_THROW
26
+ ]);
27
+ const SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
28
+ const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED = "'<>.<>' property is not allowed for schemas in $type";
29
+ const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE = "'<>' property must not be 'null' or 'undefined'";
30
+ const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
31
+ const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
32
+ const SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
33
+ const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
34
+ const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE = "Validators must be an object";
35
+ const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE = "Validator '<>' must be a function or an array of functions";
36
+ const TYPE_ARRAY = "array";
37
+ const TYPE_NULL = "null";
21
38
  const TYPE_OBJECT = "object";
22
39
  const TYPE_UNDEFINED = "undefined";
23
40
  const VALIDATABLE_TYPES = new Set([
@@ -38,6 +55,53 @@ const TYPE_ALL = new Set([
38
55
  ]);
39
56
  //#endregion
40
57
  //#region src/helpers.ts
58
+ function getInvalidInputMessage(actual) {
59
+ return VALIDATION_MESSAGE_INVALID_INPUT.replace("<>", getValueType(actual));
60
+ }
61
+ function getInvalidMissingMessage(property) {
62
+ let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace("<>", renderTypes(property.types));
63
+ message = message.replace("<>", property.key.full);
64
+ return message;
65
+ }
66
+ function getInvalidTypeMessage(property, actual) {
67
+ let message = VALIDATION_MESSAGE_INVALID_TYPE.replace("<>", renderTypes(property.types));
68
+ message = message.replace("<>", property.key.full);
69
+ message = message.replace("<>", getValueType(actual));
70
+ return message;
71
+ }
72
+ function getInvalidValidatorMessage(property, type, index, length) {
73
+ let message = VALIDATION_MESSAGE_INVALID_VALUE.replace("<>", property.key.full);
74
+ message = message.replace("<>", type);
75
+ if (length > 1) message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace("<>", String(index));
76
+ return message;
77
+ }
78
+ function getPropertyType(original) {
79
+ if (typeof original === "function") return "a validated value";
80
+ if (Array.isArray(original)) return `'${TYPE_OBJECT}'`;
81
+ if (isSchematic(original)) return `a ${NAME_SCHEMATIC}`;
82
+ return `'${String(original)}'`;
83
+ }
84
+ function getReporting(value) {
85
+ const type = REPORTING_TYPES.has(value) ? value : REPORTING_NONE;
86
+ return {
87
+ ["all"]: type === "all",
88
+ [REPORTING_FIRST]: type === REPORTING_FIRST,
89
+ [REPORTING_NONE]: type === REPORTING_NONE,
90
+ [REPORTING_THROW]: type === REPORTING_THROW
91
+ };
92
+ }
93
+ function getValueType(value) {
94
+ const valueType = typeof value;
95
+ switch (true) {
96
+ case value === null: return `'${TYPE_NULL}'`;
97
+ case value === void 0: return `'${TYPE_UNDEFINED}'`;
98
+ case valueType !== TYPE_OBJECT: return `'${valueType}'`;
99
+ case Array.isArray(value): return `'${TYPE_ARRAY}'`;
100
+ case isPlainObject(value): return `'${TYPE_OBJECT}'`;
101
+ case isSchematic(value): return `a ${NAME_SCHEMATIC}`;
102
+ default: return value.constructor.name;
103
+ }
104
+ }
41
105
  /**
42
106
  * Creates a validator function for a given constructor
43
107
  * @param constructor - Constructor to check against
@@ -58,15 +122,40 @@ function instanceOf(constructor) {
58
122
  function isSchematic(value) {
59
123
  return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
60
124
  }
125
+ function renderTypes(types) {
126
+ const unique = /* @__PURE__ */ new Set();
127
+ const parts = [];
128
+ for (let index = 0; index < types.length; index += 1) {
129
+ const rendered = getPropertyType(types[index]);
130
+ if (unique.has(rendered)) continue;
131
+ unique.add(rendered);
132
+ parts.push(rendered);
133
+ }
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;
142
+ }
61
143
  //#endregion
62
- //#region src/models.ts
144
+ //#region src/models/validation.model.ts
63
145
  /**
64
146
  * A custom error class for schematic validation failures
65
147
  */
66
148
  var SchematicError = class extends Error {
67
149
  constructor(message) {
68
150
  super(message);
69
- this.name = ERROR_NAME;
151
+ this.name = NAME_ERROR_SCHEMATIC;
152
+ }
153
+ };
154
+ var ValidationError = class extends Error {
155
+ constructor(information) {
156
+ super(join(information.map((item) => item.message), "; "));
157
+ this.information = information;
158
+ this.name = NAME_ERROR_VALIDATION;
70
159
  }
71
160
  };
72
161
  //#endregion
@@ -77,32 +166,36 @@ function getDisallowedProperty(obj) {
77
166
  if ("$validators" in obj) return PROPERTY_VALIDATORS;
78
167
  }
79
168
  function getProperties(original, prefix, fromType) {
80
- if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
169
+ if (Object.keys(original).length === 0) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY);
81
170
  if (fromType ?? false) {
82
171
  const property = getDisallowedProperty(original);
83
- if (property != null) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(TEMPLATE_PATTERN_KEY, prefix).replace(TEMPLATE_PATTERN_PROPERTY, property));
172
+ if (property != null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace("<>", prefix).replace("<>", property));
84
173
  }
85
174
  const keys = Object.keys(original);
86
175
  const keysLength = keys.length;
87
176
  const properties = [];
88
177
  for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
89
178
  const key = keys[keyIndex];
179
+ const prefixed = join([prefix, key], ".");
90
180
  const value = original[key];
91
- if (value == null) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", join([prefix, key], ".")));
181
+ if (value == null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", prefixed));
92
182
  const types = [];
93
183
  let required = true;
94
184
  let validators = {};
95
185
  if (isPlainObject(value)) {
96
186
  required = getRequired(key, value) ?? required;
97
187
  validators = getValidators(value[PROPERTY_VALIDATORS]);
98
- if ("$type" in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
99
- else types.push(TYPE_OBJECT, ...getTypes(key, value, prefix));
188
+ const hasType = PROPERTY_TYPE in value;
189
+ types.push(...getTypes(key, hasType ? value[PROPERTY_TYPE] : value, prefix, hasType));
100
190
  } else types.push(...getTypes(key, value, prefix));
101
191
  if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
102
192
  properties.push({
103
- key,
104
193
  types,
105
194
  validators,
195
+ key: {
196
+ full: prefixed,
197
+ short: key
198
+ },
106
199
  required: required && !types.includes("undefined")
107
200
  });
108
201
  }
@@ -110,7 +203,7 @@ function getProperties(original, prefix, fromType) {
110
203
  }
111
204
  function getRequired(key, obj) {
112
205
  if (!("$required" in obj)) return;
113
- if (typeof obj["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
206
+ if (typeof obj["$required"] !== "boolean") throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
114
207
  return obj[PROPERTY_REQUIRED];
115
208
  }
116
209
  function getTypes(key, original, prefix, fromType) {
@@ -124,7 +217,7 @@ function getTypes(key, original, prefix, fromType) {
124
217
  types.push(isConstructor(value) ? instanceOf(value) : value);
125
218
  break;
126
219
  case isPlainObject(value):
127
- types.push(...getProperties(value, join([prefix, key], "."), fromType));
220
+ types.push(getProperties(value, join([prefix, key], "."), fromType));
128
221
  break;
129
222
  case isSchematic(value):
130
223
  types.push(value);
@@ -132,54 +225,113 @@ function getTypes(key, original, prefix, fromType) {
132
225
  case TYPE_ALL.has(value):
133
226
  types.push(value);
134
227
  break;
135
- default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
228
+ default: throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
136
229
  }
137
230
  }
138
- if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
231
+ if (types.length === 0) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
139
232
  return types;
140
233
  }
141
234
  function getValidators(original) {
142
235
  const validators = {};
143
236
  if (original == null) return validators;
144
- if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
237
+ if (!isPlainObject(original)) throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
145
238
  const keys = Object.keys(original);
146
239
  const { length } = keys;
147
240
  for (let index = 0; index < length; index += 1) {
148
241
  const key = keys[index];
149
- if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
242
+ if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
150
243
  const value = original[key];
151
- validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
152
- if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
153
- return true;
244
+ validators[key] = (Array.isArray(value) ? value : [value]).map((item) => {
245
+ if (typeof item !== "function") throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
246
+ return item;
154
247
  });
155
248
  }
156
249
  return validators;
157
250
  }
158
251
  //#endregion
159
252
  //#region src/validation/value.validation.ts
160
- function validateObject(obj, properties) {
161
- if (!isPlainObject(obj)) return false;
253
+ function validateNamed(property, name, value, validation) {
254
+ if (!validators[name](value)) return false;
255
+ const propertyValidators = property.validators[name];
256
+ if (propertyValidators == null || propertyValidators.length === 0) return true;
257
+ const { length } = propertyValidators;
258
+ for (let index = 0; index < length; index += 1) {
259
+ const validator = propertyValidators[index];
260
+ if (!validator(value)) {
261
+ validation.push({
262
+ value,
263
+ key: { ...property.key },
264
+ message: getInvalidValidatorMessage(property, name, index, length),
265
+ validator
266
+ });
267
+ return false;
268
+ }
269
+ }
270
+ return true;
271
+ }
272
+ function validateObject(obj, properties, reporting, property, validation) {
273
+ if (!isPlainObject(obj)) {
274
+ const information = {
275
+ key: {
276
+ full: "",
277
+ short: ""
278
+ },
279
+ message: property == null ? getInvalidInputMessage(obj) : getInvalidTypeMessage(property, obj),
280
+ value: obj
281
+ };
282
+ if (reporting.throw) throw new ValidationError([information]);
283
+ return reporting.none ? false : [information];
284
+ }
285
+ const allInformation = [];
162
286
  const propertiesLength = properties.length;
163
287
  outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
164
288
  const property = properties[propertyIndex];
165
289
  const { key, required, types } = property;
166
- const value = obj[key];
167
- if (value === void 0 && required) return false;
290
+ const value = obj[key.short];
291
+ if (value === void 0 && required) {
292
+ const information = {
293
+ value,
294
+ key: { ...key },
295
+ message: getInvalidMissingMessage(property)
296
+ };
297
+ if (reporting.throw && validation == null) throw new ValidationError([information]);
298
+ if (validation != null) validation.push(information);
299
+ if (reporting.all) {
300
+ allInformation.push(information);
301
+ continue;
302
+ }
303
+ return reporting.none ? false : [information];
304
+ }
168
305
  const typesLength = types.length;
306
+ const information = [];
169
307
  for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
170
308
  const type = types[typeIndex];
171
- if (validateValue(type, property, value)) continue outer;
309
+ if (validateValue(type, property, value, reporting, information)) continue outer;
172
310
  }
173
- return false;
311
+ if (information.length === 0) information.push({
312
+ value,
313
+ key: { ...key },
314
+ message: getInvalidTypeMessage(property, value)
315
+ });
316
+ if (reporting.throw && validation == null) throw new ValidationError(information);
317
+ validation?.push(...information);
318
+ if (reporting.all) {
319
+ allInformation.push(...information);
320
+ continue;
321
+ }
322
+ return reporting.none ? false : information;
174
323
  }
175
- return true;
324
+ return reporting.none ? true : allInformation.length === 0 ? true : allInformation;
176
325
  }
177
- function validateValue(type, property, value) {
326
+ function validateValue(type, property, value, reporting, validation) {
178
327
  switch (true) {
179
- case isSchematic(type): return type.is(value);
180
328
  case typeof type === "function": return type(value);
181
- case typeof type === "object": return validateObject(value, [type]);
182
- default: return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
329
+ case Array.isArray(type): {
330
+ const nested = validateObject(value, type, reporting, property, validation);
331
+ return typeof nested !== "boolean" ? false : nested;
332
+ }
333
+ case isSchematic(type): return type.is(value, reporting);
334
+ default: return validateNamed(property, type, value, validation);
183
335
  }
184
336
  }
185
337
  const validators = {
@@ -203,22 +355,20 @@ const validators = {
203
355
  var Schematic = class {
204
356
  #properties;
205
357
  constructor(properties) {
206
- Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
358
+ Object.defineProperty(this, PROPERTY_SCHEMATIC, { value: true });
207
359
  this.#properties = properties;
208
360
  }
209
- /**
210
- * Does the value match the schema?
211
- * @param value Value to validate
212
- * @returns `true` if the value matches the schema, otherwise `false`
213
- */
214
- is(value) {
215
- return validateObject(value, this.#properties);
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]);
216
366
  }
217
367
  };
218
368
  function schematic(schema) {
219
369
  if (isSchematic(schema)) return schema;
220
- if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
370
+ if (!isPlainObject(schema)) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
221
371
  return new Schematic(getProperties(schema));
222
372
  }
223
373
  //#endregion
224
- export { SchematicError, instanceOf, isSchematic, schematic };
374
+ export { SchematicError, ValidationError, instanceOf, isSchematic, schematic };
@@ -0,0 +1,66 @@
1
+ import { Schematic } from "../schematic.mjs";
2
+ import { PlainSchema, Schema, SchemaProperty } from "./schema.plain.model.mjs";
3
+ import { IsOptionalProperty, ValueName, Values } from "./misc.model.mjs";
4
+ import { Constructor, Simplify } from "@oscarpalmer/atoms/models";
5
+
6
+ //#region src/models/infer.model.d.ts
7
+ /**
8
+ * Infers the TypeScript type from a {@link Schema} definition
9
+ *
10
+ * @template Model - Schema to infer types from
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const userSchema = {
15
+ * name: 'string',
16
+ * age: 'number',
17
+ * address: { $required: false, $type: 'string' },
18
+ * } satisfies Schema;
19
+ *
20
+ * type User = Infer<typeof userSchema>;
21
+ * // { name: string; age: number; address?: string }
22
+ * ```
23
+ */
24
+ type Infer<Model extends Schema> = Simplify<{ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]> } & { [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]> }>;
25
+ /**
26
+ * Extracts keys from a {@link Schema} whose entries are optional _(i.e., `$required` is `false`)_
27
+ *
28
+ * @template Model - {@link Schema} to extract optional keys from
29
+ */
30
+ type InferOptionalKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never };
31
+ /**
32
+ * Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
33
+ *
34
+ * @template Value - `$type` value _(single or array)_
35
+ */
36
+ type InferPropertyType<Value> = Value extends (infer Item)[] ? InferPropertyValue<Item> : InferPropertyValue<Value>;
37
+ /**
38
+ * Maps a single type definition to its TypeScript equivalent
39
+ *
40
+ * Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link Schema} objects
41
+ *
42
+ * @template Value - single type definition
43
+ */
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
+ /**
46
+ * Extracts keys from a {@link Schema} whose entries are required _(i.e., `$required` is not `false`)_
47
+ *
48
+ * @template Model - Schema to extract required keys from
49
+ */
50
+ type InferRequiredKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never };
51
+ /**
52
+ * Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
53
+ *
54
+ * @template Value - Schema entry value _(single or array)_
55
+ */
56
+ type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryValue<Item> : InferSchemaEntryValue<Value>;
57
+ /**
58
+ * Resolves a single schema entry to its TypeScript type
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
61
+ *
62
+ * @template Value - single schema entry
63
+ */
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
+ //#endregion
66
+ export { Infer, InferOptionalKeys, InferPropertyType, InferPropertyValue, InferRequiredKeys, InferSchemaEntry, InferSchemaEntryValue };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,153 @@
1
+ import { SchemaProperty } from "./schema.plain.model.mjs";
2
+
3
+ //#region src/models/misc.model.d.ts
4
+ /**
5
+ * Removes duplicate types from a tuple, preserving first occurrence order
6
+ *
7
+ * @template Value - Tuple to deduplicate
8
+ * @template Seen - Accumulator for already-seen types _(internal)_
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // DeduplicateTuple<['string', 'number', 'string']>
13
+ * // => ['string', 'number']
14
+ * ```
15
+ */
16
+ type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [infer Head, ...infer Tail] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
17
+ /**
18
+ * Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
19
+ *
20
+ * @template Value - Type to extract value names from
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // ExtractValueNames<'string'> => 'string'
25
+ * // ExtractValueNames<['string', 'number']> => 'string' | 'number'
26
+ * ```
27
+ */
28
+ type ExtractValueNames<Value> = Value extends ValueName ? Value : Value extends (infer Item)[] ? ExtractValueNames<Item> : Value extends readonly (infer Item)[] ? ExtractValueNames<Item> : never;
29
+ /**
30
+ * Determines whether a schema entry is optional
31
+ *
32
+ * Returns `true` if the entry is a {@link SchemaProperty} or {@link NestedSchema} with `$required` set to `false`; otherwise returns `false`
33
+ *
34
+ * @template Value - Schema entry to check
35
+ */
36
+ type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
37
+ /**
38
+ * Extracts the last member from a union type by leveraging intersection of function return types
39
+ *
40
+ * @template Value - Union type
41
+ */
42
+ type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
43
+ /**
44
+ * Extracts keys from an object type that are optional
45
+ *
46
+ * @template Value - Object type to inspect
47
+ */
48
+ type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
49
+ /**
50
+ * Extracts keys from an object type that are required _(i.e., not optional)_
51
+ *
52
+ * @template Value - Object type to inspect
53
+ */
54
+ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
55
+ /**
56
+ * Generates all permutations of a tuple type
57
+ *
58
+ * Used by {@link UnwrapSingle} to allow schema types in any order for small tuples _(length ≤ 5)_
59
+ *
60
+ * @template Tuple - Tuple to permute
61
+ * @template Elput - Accumulator for the current permutation _(internal; name is Tuple backwards)_
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * // TuplePermutations<['string', 'number']>
66
+ * // => ['string', 'number'] | ['number', 'string']
67
+ * ```
68
+ */
69
+ 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}`];
70
+ /**
71
+ * Removes the element at a given index from a tuple
72
+ *
73
+ * Used internally by {@link TuplePermutations}
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)_
78
+ */
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
+ /**
81
+ * Converts a union type into an intersection
82
+ *
83
+ * Uses the contravariance of function parameter types to collapse a union into an intersection
84
+ *
85
+ * @template Value - Union type to convert
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * // UnionToIntersection<{ a: 1 } | { b: 2 }>
90
+ * // => { a: 1 } & { b: 2 }
91
+ * ```
92
+ */
93
+ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends ((value: infer Item) => void) ? Item : never;
94
+ /**
95
+ * Converts a union type into an ordered tuple
96
+ *
97
+ * Repeatedly extracts the {@link LastOfUnion} member and prepends it to the accumulator
98
+ *
99
+ * @template Value - Union type to convert
100
+ * @template Items - Accumulator for the resulting tuple _(internal)_
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * // UnionToTuple<'a' | 'b' | 'c'>
105
+ * // => ['a', 'b', 'c']
106
+ * ```
107
+ */
108
+ type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
109
+ /**
110
+ * Unwraps a single-element tuple to its inner type
111
+ *
112
+ * For tuples of length 2–5, returns all {@link TuplePermutations} to allow types in any order. Longer tuples are returned as-is
113
+ *
114
+ * @template Value - Tuple to potentially unwrap
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * // UnwrapSingle<['string']> => 'string'
119
+ * // UnwrapSingle<['string', 'number']> => ['string', 'number'] | ['number', 'string']
120
+ * ```
121
+ */
122
+ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
123
+ /**
124
+ * Basic value types
125
+ */
126
+ type ValueName = keyof Values;
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
+ * ```
138
+ */
139
+ type Values = {
140
+ array: unknown[];
141
+ bigint: bigint;
142
+ boolean: boolean;
143
+ date: Date;
144
+ function: Function;
145
+ null: null;
146
+ number: number;
147
+ object: object;
148
+ string: string;
149
+ symbol: symbol;
150
+ undefined: undefined;
151
+ };
152
+ //#endregion
153
+ export { DeduplicateTuple, ExtractValueNames, IsOptionalProperty, LastOfUnion, OptionalKeys, RequiredKeys, TuplePermutations, TupleRemoveAt, UnionToIntersection, UnionToTuple, UnwrapSingle, ValueName, Values };
@@ -0,0 +1 @@
1
+ export {};