@oscarpalmer/jhunal 0.18.0 → 0.20.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/constants.d.mts +7 -1
- package/dist/constants.mjs +7 -1
- package/dist/helpers.d.mts +6 -1
- package/dist/helpers.mjs +36 -10
- package/dist/index.d.mts +138 -94
- package/dist/index.mjs +128 -43
- package/dist/models/infer.model.d.mts +12 -12
- package/dist/models/misc.model.d.mts +20 -29
- package/dist/models/schema.plain.model.d.mts +4 -4
- package/dist/models/schema.typed.model.d.mts +5 -23
- package/dist/models/transform.model.d.mts +9 -13
- package/dist/models/validation.model.d.mts +49 -13
- package/dist/models/validation.model.mjs +4 -1
- package/dist/schematic.d.mts +52 -4
- package/dist/schematic.mjs +13 -4
- package/dist/validation/value.validation.d.mts +2 -2
- package/dist/validation/value.validation.mjs +78 -33
- package/package.json +1 -1
- package/src/constants.ts +16 -0
- package/src/helpers.ts +61 -15
- package/src/models/infer.model.ts +12 -12
- package/src/models/misc.model.ts +20 -29
- package/src/models/schema.plain.model.ts +4 -4
- package/src/models/schema.typed.model.ts +5 -23
- package/src/models/transform.model.ts +9 -13
- package/src/models/validation.model.ts +51 -9
- package/src/schematic.ts +79 -9
- package/src/validation/property.validation.ts +3 -4
- package/src/validation/value.validation.ts +120 -43
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
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
|
|
5
|
+
const CONJUNCTION_OR = " or ";
|
|
6
|
+
const CONJUNCTION_OR_COMMA = ", or ";
|
|
7
|
+
const CONJUNCTION_AND = " and ";
|
|
8
|
+
const CONJUNCTION_AND_COMMA = ", and ";
|
|
4
9
|
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
5
10
|
const NAME_SCHEMATIC = "Schematic";
|
|
6
11
|
const NAME_ERROR_SCHEMATIC = "SchematicError";
|
|
@@ -14,6 +19,7 @@ const VALIDATION_MESSAGE_INVALID_REQUIRED = "Expected <> for required property '
|
|
|
14
19
|
const VALIDATION_MESSAGE_INVALID_TYPE = "Expected <> for '<>' but received <>";
|
|
15
20
|
const VALIDATION_MESSAGE_INVALID_VALUE = "Value does not satisfy validator for '<>' and type '<>'";
|
|
16
21
|
const VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX = " at index <>";
|
|
22
|
+
const VALIDATION_MESSAGE_UNKNOWN_KEYS = "Found keys that are not defined in the schema: <>";
|
|
17
23
|
const REPORTING_FIRST = "first";
|
|
18
24
|
const REPORTING_NONE = "none";
|
|
19
25
|
const REPORTING_THROW = "throw";
|
|
@@ -74,6 +80,21 @@ function getInvalidValidatorMessage(property, type, index, length) {
|
|
|
74
80
|
if (length > 1) message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace("<>", String(index));
|
|
75
81
|
return message;
|
|
76
82
|
}
|
|
83
|
+
function getOptions(input) {
|
|
84
|
+
if (typeof input === "boolean") return {
|
|
85
|
+
reporting: getReporting(REPORTING_NONE),
|
|
86
|
+
strict: input
|
|
87
|
+
};
|
|
88
|
+
if (REPORTING_TYPES.has(input)) return {
|
|
89
|
+
reporting: getReporting(input),
|
|
90
|
+
strict: false
|
|
91
|
+
};
|
|
92
|
+
const options = isPlainObject(input) ? input : {};
|
|
93
|
+
return {
|
|
94
|
+
reporting: getReporting(options.errors),
|
|
95
|
+
strict: typeof options.strict === "boolean" ? options.strict : false
|
|
96
|
+
};
|
|
97
|
+
}
|
|
77
98
|
function getPropertyType(original) {
|
|
78
99
|
if (typeof original === "function") return "a validated value";
|
|
79
100
|
if (Array.isArray(original)) return `'${TYPE_OBJECT}'`;
|
|
@@ -83,12 +104,16 @@ function getPropertyType(original) {
|
|
|
83
104
|
function getReporting(value) {
|
|
84
105
|
const type = REPORTING_TYPES.has(value) ? value : REPORTING_NONE;
|
|
85
106
|
return {
|
|
107
|
+
type,
|
|
86
108
|
["all"]: type === "all",
|
|
87
109
|
[REPORTING_FIRST]: type === REPORTING_FIRST,
|
|
88
110
|
[REPORTING_NONE]: type === REPORTING_NONE,
|
|
89
111
|
[REPORTING_THROW]: type === REPORTING_THROW
|
|
90
112
|
};
|
|
91
113
|
}
|
|
114
|
+
function getUnknownKeysMessage(keys) {
|
|
115
|
+
return VALIDATION_MESSAGE_UNKNOWN_KEYS.replace("<>", renderKeys(keys));
|
|
116
|
+
}
|
|
92
117
|
function getValueType(value) {
|
|
93
118
|
const valueType = typeof value;
|
|
94
119
|
switch (true) {
|
|
@@ -121,6 +146,20 @@ function instanceOf(constructor) {
|
|
|
121
146
|
function isSchematic(value) {
|
|
122
147
|
return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
|
|
123
148
|
}
|
|
149
|
+
function renderKeys(keys) {
|
|
150
|
+
return renderParts(keys.map((key) => `'${key}'`), CONJUNCTION_AND, CONJUNCTION_AND_COMMA);
|
|
151
|
+
}
|
|
152
|
+
function renderParts(parts, delimiterShort, delimiterLong) {
|
|
153
|
+
const { length } = parts;
|
|
154
|
+
if (length === 1) return parts[0];
|
|
155
|
+
let rendered = "";
|
|
156
|
+
for (let index = 0; index < length; index += 1) {
|
|
157
|
+
rendered += parts[index];
|
|
158
|
+
if (index < length - 2) rendered += ", ";
|
|
159
|
+
else if (index === length - 2) rendered += parts.length > 2 ? delimiterLong : delimiterShort;
|
|
160
|
+
}
|
|
161
|
+
return rendered;
|
|
162
|
+
}
|
|
124
163
|
function renderTypes(types) {
|
|
125
164
|
const unique = /* @__PURE__ */ new Set();
|
|
126
165
|
const parts = [];
|
|
@@ -130,19 +169,12 @@ function renderTypes(types) {
|
|
|
130
169
|
unique.add(rendered);
|
|
131
170
|
parts.push(rendered);
|
|
132
171
|
}
|
|
133
|
-
|
|
134
|
-
let rendered = "";
|
|
135
|
-
for (let index = 0; index < length; index += 1) {
|
|
136
|
-
rendered += parts[index];
|
|
137
|
-
if (index < length - 2) rendered += ", ";
|
|
138
|
-
else if (index === length - 2) rendered += parts.length > 2 ? ", or " : " or ";
|
|
139
|
-
}
|
|
140
|
-
return rendered;
|
|
172
|
+
return renderParts(parts, CONJUNCTION_OR, CONJUNCTION_OR_COMMA);
|
|
141
173
|
}
|
|
142
174
|
//#endregion
|
|
143
175
|
//#region src/models/validation.model.ts
|
|
144
176
|
/**
|
|
145
|
-
*
|
|
177
|
+
* Thrown when a schema definition is invalid
|
|
146
178
|
*/
|
|
147
179
|
var SchematicError = class extends Error {
|
|
148
180
|
constructor(message) {
|
|
@@ -150,6 +182,9 @@ var SchematicError = class extends Error {
|
|
|
150
182
|
this.name = NAME_ERROR_SCHEMATIC;
|
|
151
183
|
}
|
|
152
184
|
};
|
|
185
|
+
/**
|
|
186
|
+
* Thrown in `'throw'` mode when one or more properties fail validation; `information` holds all failures
|
|
187
|
+
*/
|
|
153
188
|
var ValidationError = class extends Error {
|
|
154
189
|
constructor(information) {
|
|
155
190
|
super(join(information.map((item) => item.message), "; "));
|
|
@@ -258,6 +293,7 @@ function validateNamed(property, name, value, validation) {
|
|
|
258
293
|
const validator = propertyValidators[index];
|
|
259
294
|
if (!validator(value)) {
|
|
260
295
|
validation.push({
|
|
296
|
+
value,
|
|
261
297
|
key: { ...property.key },
|
|
262
298
|
message: getInvalidValidatorMessage(property, name, index, length),
|
|
263
299
|
validator
|
|
@@ -267,63 +303,104 @@ function validateNamed(property, name, value, validation) {
|
|
|
267
303
|
}
|
|
268
304
|
return true;
|
|
269
305
|
}
|
|
270
|
-
function validateObject(obj, properties,
|
|
306
|
+
function validateObject(obj, properties, options, origin, validation) {
|
|
271
307
|
if (!isPlainObject(obj)) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
308
|
+
const key = origin == null ? {
|
|
309
|
+
full: "",
|
|
310
|
+
short: ""
|
|
311
|
+
} : { ...origin.key };
|
|
312
|
+
const information = {
|
|
313
|
+
key,
|
|
314
|
+
message: origin == null ? getInvalidInputMessage(obj) : getInvalidTypeMessage({
|
|
315
|
+
...origin,
|
|
316
|
+
key
|
|
317
|
+
}, obj),
|
|
318
|
+
value: obj
|
|
319
|
+
};
|
|
320
|
+
if (options.reporting.throw) throw new ValidationError([information]);
|
|
321
|
+
validation?.push(information);
|
|
322
|
+
return options.reporting.none ? false : [information];
|
|
280
323
|
}
|
|
324
|
+
if (options.strict) {
|
|
325
|
+
const objKeys = Object.keys(obj);
|
|
326
|
+
const propertiesKeys = new Set(properties.map((property) => property.key.short));
|
|
327
|
+
const unknownKeys = objKeys.filter((key) => !propertiesKeys.has(key));
|
|
328
|
+
if (unknownKeys.length > 0) {
|
|
329
|
+
const information = {
|
|
330
|
+
key: origin == null ? {
|
|
331
|
+
full: "",
|
|
332
|
+
short: ""
|
|
333
|
+
} : { ...origin.key },
|
|
334
|
+
message: getUnknownKeysMessage(unknownKeys.map((key) => join([origin?.key.full, key], "."))),
|
|
335
|
+
value: obj
|
|
336
|
+
};
|
|
337
|
+
if (options.reporting.throw) throw new ValidationError([information]);
|
|
338
|
+
validation?.push(information);
|
|
339
|
+
return options.reporting.none ? false : [information];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const allInformation = [];
|
|
281
343
|
const propertiesLength = properties.length;
|
|
282
344
|
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
283
|
-
|
|
345
|
+
let property = properties[propertyIndex];
|
|
346
|
+
property = {
|
|
347
|
+
...property,
|
|
348
|
+
key: {
|
|
349
|
+
full: join([origin?.key.full, property.key.short], "."),
|
|
350
|
+
short: property.key.short
|
|
351
|
+
}
|
|
352
|
+
};
|
|
284
353
|
const { key, required, types } = property;
|
|
285
354
|
const value = obj[key.short];
|
|
286
355
|
if (value === void 0 && required) {
|
|
287
356
|
const information = {
|
|
357
|
+
value,
|
|
288
358
|
key: { ...key },
|
|
289
359
|
message: getInvalidMissingMessage(property)
|
|
290
360
|
};
|
|
291
|
-
if (reporting.throw && validation == null) throw new ValidationError([information]);
|
|
361
|
+
if (options.reporting.throw && validation == null) throw new ValidationError([information]);
|
|
292
362
|
if (validation != null) validation.push(information);
|
|
293
|
-
|
|
363
|
+
if (options.reporting.all) {
|
|
364
|
+
allInformation.push(information);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
return options.reporting.none ? false : [information];
|
|
294
368
|
}
|
|
295
369
|
const typesLength = types.length;
|
|
296
370
|
const information = [];
|
|
297
371
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
298
372
|
const type = types[typeIndex];
|
|
299
|
-
if (validateValue(type, property, value,
|
|
373
|
+
if (validateValue(type, property, value, options, information)) continue outer;
|
|
300
374
|
}
|
|
301
|
-
if (
|
|
375
|
+
if (information.length === 0) information.push({
|
|
376
|
+
value,
|
|
302
377
|
key: { ...key },
|
|
303
378
|
message: getInvalidTypeMessage(property, value)
|
|
304
|
-
}
|
|
379
|
+
});
|
|
380
|
+
if (options.reporting.throw && validation == null) throw new ValidationError(information);
|
|
305
381
|
validation?.push(...information);
|
|
306
|
-
|
|
382
|
+
if (options.reporting.all) {
|
|
383
|
+
allInformation.push(...information);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
return options.reporting.none ? false : information;
|
|
307
387
|
}
|
|
308
|
-
return true;
|
|
388
|
+
return options.reporting.none || allInformation.length === 0 ? true : allInformation;
|
|
389
|
+
}
|
|
390
|
+
function validateSchematic(property, schematic, value, options, validation) {
|
|
391
|
+
const result = validateObject(value, schematicProperties.get(schematic), options, property, validation);
|
|
392
|
+
return typeof result === "boolean" ? result : result.length === 0;
|
|
309
393
|
}
|
|
310
|
-
function validateValue(type, property, value,
|
|
311
|
-
let result;
|
|
394
|
+
function validateValue(type, property, value, options, validation) {
|
|
312
395
|
switch (true) {
|
|
313
|
-
case typeof type === "function":
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
result = type.is(value, reporting);
|
|
321
|
-
break;
|
|
322
|
-
default:
|
|
323
|
-
result = validateNamed(property, type, value, validation);
|
|
324
|
-
break;
|
|
396
|
+
case typeof type === "function": return type(value);
|
|
397
|
+
case Array.isArray(type): {
|
|
398
|
+
const validated = validateObject(value, type, options, property, validation);
|
|
399
|
+
return typeof validated === "boolean" ? validated : false;
|
|
400
|
+
}
|
|
401
|
+
case isSchematic(type): return validateSchematic(property, type, value, options, validation);
|
|
402
|
+
default: return validateNamed(property, type, value, validation);
|
|
325
403
|
}
|
|
326
|
-
return result;
|
|
327
404
|
}
|
|
328
405
|
const validators = {
|
|
329
406
|
array: Array.isArray,
|
|
@@ -348,9 +425,16 @@ var Schematic = class {
|
|
|
348
425
|
constructor(properties) {
|
|
349
426
|
Object.defineProperty(this, PROPERTY_SCHEMATIC, { value: true });
|
|
350
427
|
this.#properties = properties;
|
|
428
|
+
schematicProperties.set(this, properties);
|
|
351
429
|
}
|
|
352
|
-
is(value,
|
|
353
|
-
|
|
430
|
+
is(value, options) {
|
|
431
|
+
const { reporting, strict } = getOptions(options);
|
|
432
|
+
const result = validateObject(value, this.#properties, {
|
|
433
|
+
reporting,
|
|
434
|
+
strict
|
|
435
|
+
});
|
|
436
|
+
if (typeof result === "boolean") return result;
|
|
437
|
+
return error(reporting.all ? result : result[0]);
|
|
354
438
|
}
|
|
355
439
|
};
|
|
356
440
|
function schematic(schema) {
|
|
@@ -358,5 +442,6 @@ function schematic(schema) {
|
|
|
358
442
|
if (!isPlainObject(schema)) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
|
|
359
443
|
return new Schematic(getProperties(schema));
|
|
360
444
|
}
|
|
445
|
+
const schematicProperties = /* @__PURE__ */ new WeakMap();
|
|
361
446
|
//#endregion
|
|
362
447
|
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
|
|
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
|
|
32
|
+
* Infers the TypeScript type from a {@link SchemaProperty}'s `$type` field
|
|
33
33
|
*
|
|
34
|
-
* @template Value
|
|
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
|
|
40
|
+
* Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link PlainSchema} objects
|
|
41
41
|
*
|
|
42
|
-
* @template Value
|
|
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
|
|
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
|
|
52
|
+
* Infers the TypeScript type from a top-level {@link Schema} entry
|
|
53
53
|
*
|
|
54
|
-
* @template Value
|
|
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
|
-
*
|
|
58
|
+
* Maps a single top-level schema entry to its TypeScript type
|
|
59
59
|
*
|
|
60
|
-
*
|
|
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
|
|
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
|
|
8
|
-
* @template Seen
|
|
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
|
|
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}
|
|
32
|
+
* Returns `true` if the entry is a {@link SchemaProperty} with `$required` set to `false`; otherwise returns `false`
|
|
33
33
|
*
|
|
34
|
-
* @template Value
|
|
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
|
|
38
|
+
* Extracts the last member from a union type by leveraging contravariance of function parameter types
|
|
39
39
|
*
|
|
40
|
-
* @template Value
|
|
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
|
|
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
|
|
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
|
|
61
|
-
* @template Elput
|
|
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
|
|
76
|
-
* @template Item
|
|
77
|
-
* @template Prefix
|
|
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
|
|
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
|
|
100
|
-
* @template Items
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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},
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
72
|
+
* @template Model Nested object type
|
|
91
73
|
*/
|
|
92
74
|
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
93
75
|
$required?: true;
|
|
@@ -7,37 +7,33 @@ import { PlainObject } from "@oscarpalmer/atoms/models";
|
|
|
7
7
|
/**
|
|
8
8
|
* Maps each element of a tuple through {@link ToValueType}
|
|
9
9
|
*
|
|
10
|
-
* @template Value
|
|
10
|
+
* @template Value Tuple of types to map
|
|
11
11
|
*/
|
|
12
12
|
type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToValueType<Head>, ...MapToValueTypes<Tail>] : [];
|
|
13
13
|
/**
|
|
14
14
|
* Maps each element of a tuple through {@link ToSchemaPropertyTypeEach}
|
|
15
15
|
*
|
|
16
|
-
* @template Value
|
|
16
|
+
* @template Value Tuple of types to map
|
|
17
17
|
*/
|
|
18
18
|
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
|
|
19
19
|
/**
|
|
20
|
-
* Converts a type
|
|
20
|
+
* Converts a TypeScript type to its {@link SchemaPropertyType} representation, suitable for use in a typed schema
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* @template Value - type to convert
|
|
22
|
+
* @template Value Type to convert
|
|
25
23
|
*/
|
|
26
24
|
type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>>;
|
|
27
25
|
/**
|
|
28
26
|
* Converts a single type to its schema property equivalent
|
|
29
27
|
*
|
|
30
|
-
*
|
|
28
|
+
* Plain objects become {@link TypedSchema}; primitives go through {@link ToValueType}
|
|
31
29
|
*
|
|
32
|
-
* @template Value
|
|
30
|
+
* @template Value Type to convert
|
|
33
31
|
*/
|
|
34
32
|
type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
|
|
35
33
|
/**
|
|
36
|
-
* Converts a type
|
|
37
|
-
*
|
|
38
|
-
* Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
|
|
34
|
+
* Converts a TypeScript type to its {@link ValueName} representation, suitable for use as a top-level schema entry
|
|
39
35
|
*
|
|
40
|
-
* @template Value
|
|
36
|
+
* @template Value Type to convert
|
|
41
37
|
*/
|
|
42
38
|
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
43
39
|
/**
|
|
@@ -45,7 +41,7 @@ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionTo
|
|
|
45
41
|
*
|
|
46
42
|
* Resolves {@link Schematic} types as-is, then performs a reverse-lookup against {@link Values} _(excluding `'object'`)_ to find a matching key. If no match is found, `object` types resolve to `'object'` or a type-guard function, and all other unrecognised types resolve to a type-guard function
|
|
47
43
|
*
|
|
48
|
-
* @template Value
|
|
44
|
+
* @template Value Type to map
|
|
49
45
|
*
|
|
50
46
|
* @example
|
|
51
47
|
* ```ts
|