@oscarpalmer/jhunal 0.23.0 → 0.25.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 +8 -5
- package/dist/constants.mjs +20 -23
- package/dist/helpers/message.helper.d.mts +11 -5
- package/dist/helpers/message.helper.mjs +31 -9
- package/dist/helpers/misc.helper.d.mts +4 -4
- package/dist/helpers/misc.helper.mjs +4 -4
- package/dist/index.d.mts +66 -78
- package/dist/index.mjs +100 -62
- package/dist/models/infer.model.d.mts +21 -21
- package/dist/models/misc.model.d.mts +3 -3
- package/dist/models/{schema.plain.model.d.mts → schematic.plain.model.d.mts} +20 -18
- package/dist/models/{schema.typed.model.d.mts → schematic.typed.model.d.mts} +10 -24
- package/dist/models/transform.model.d.mts +6 -6
- package/dist/models/validation.model.d.mts +13 -3
- package/dist/{schematic.d.mts → schema.d.mts} +18 -18
- package/dist/{schematic.mjs → schema.mjs} +12 -12
- package/dist/validator/named.handler.d.mts +1 -1
- package/dist/validator/named.handler.mjs +3 -2
- package/dist/validator/named.validator.mjs +2 -3
- package/dist/validator/object.validator.mjs +40 -22
- package/dist/validator/schematic.validator.d.mts +3 -3
- package/dist/validator/schematic.validator.mjs +4 -4
- package/package.json +1 -1
- package/src/constants.ts +24 -28
- package/src/helpers/message.helper.ts +74 -9
- package/src/helpers/misc.helper.ts +9 -10
- package/src/index.ts +4 -4
- package/src/models/infer.model.ts +26 -28
- package/src/models/misc.model.ts +3 -3
- package/src/models/{schema.plain.model.ts → schematic.plain.model.ts} +22 -20
- package/src/models/{schema.typed.model.ts → schematic.typed.model.ts} +10 -28
- package/src/models/transform.model.ts +6 -6
- package/src/models/validation.model.ts +14 -2
- package/src/{schematic.ts → schema.ts} +23 -23
- package/src/validator/named.handler.ts +16 -1
- package/src/validator/named.validator.ts +3 -4
- package/src/validator/object.validator.ts +81 -55
- package/src/validator/schematic.validator.ts +3 -3
- /package/dist/models/{schema.plain.model.mjs → schematic.plain.model.mjs} +0 -0
- /package/dist/models/{schema.typed.model.mjs → schematic.typed.model.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -10,8 +10,9 @@ const CONJUNCTION_AND_COMMA = ", and ";
|
|
|
10
10
|
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
11
11
|
const NAME_ERROR_SCHEMATIC = "SchematicError";
|
|
12
12
|
const NAME_ERROR_VALIDATION = "ValidationError";
|
|
13
|
+
const PROPERTY_DEFAULT = "$default";
|
|
13
14
|
const PROPERTY_REQUIRED = "$required";
|
|
14
|
-
const
|
|
15
|
+
const PROPERTY_SCHEMA = "$schema";
|
|
15
16
|
const PROPERTY_TYPE = "$type";
|
|
16
17
|
const PROPERTY_VALIDATORS = "$validators";
|
|
17
18
|
const VALIDATION_MESSAGE_INVALID_INPUT = "Expected an object as input but received <>";
|
|
@@ -29,8 +30,10 @@ const REPORTING_TYPES = new Set([
|
|
|
29
30
|
REPORTING_NONE,
|
|
30
31
|
REPORTING_THROW
|
|
31
32
|
]);
|
|
33
|
+
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_REQUIRED = "'<>' has a default value but is not required";
|
|
34
|
+
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_TYPE = "Expected default value for property '<>' to be <>";
|
|
32
35
|
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
33
|
-
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED = "'<>.<>' property is not allowed for schemas
|
|
36
|
+
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED = "'<>.<>' property is not allowed for plain schemas";
|
|
34
37
|
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE = "'<>' property must not be 'null' or 'undefined'";
|
|
35
38
|
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
36
39
|
const SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
@@ -39,42 +42,36 @@ const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
|
39
42
|
const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE = "Validators must be an object";
|
|
40
43
|
const SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE = "Validator '<>' must be a function or an array of functions";
|
|
41
44
|
const TYPE_ARRAY = "array";
|
|
42
|
-
const TYPE_BIGINT = "bigint";
|
|
43
|
-
const TYPE_BOOLEAN = "boolean";
|
|
44
|
-
const TYPE_DATE = "date";
|
|
45
45
|
const TYPE_FUNCTION = "function";
|
|
46
46
|
const TYPE_FUNCTION_RESULT = "a validated value";
|
|
47
47
|
const TYPE_NULL = "null";
|
|
48
|
-
const TYPE_NUMBER = "number";
|
|
49
48
|
const TYPE_OBJECT = "object";
|
|
50
|
-
const TYPE_STRING = "string";
|
|
51
|
-
const TYPE_SYMBOL = "symbol";
|
|
52
49
|
const TYPE_UNDEFINED = "undefined";
|
|
53
50
|
const TYPE_ALL = new Set([
|
|
54
51
|
...new Set([
|
|
55
52
|
TYPE_ARRAY,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
"bigint",
|
|
54
|
+
"boolean",
|
|
55
|
+
"date",
|
|
59
56
|
TYPE_FUNCTION,
|
|
60
|
-
|
|
57
|
+
"number",
|
|
61
58
|
TYPE_OBJECT,
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
"string",
|
|
60
|
+
"symbol"
|
|
64
61
|
]),
|
|
65
62
|
TYPE_NULL,
|
|
66
63
|
TYPE_UNDEFINED
|
|
67
64
|
]);
|
|
68
65
|
const PREFIXED_TYPES = {
|
|
69
66
|
[TYPE_ARRAY]: `an ${TYPE_ARRAY}`,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
bigint: `a bigint`,
|
|
68
|
+
boolean: `a boolean`,
|
|
69
|
+
date: `a date`,
|
|
73
70
|
[TYPE_FUNCTION]: `a ${TYPE_FUNCTION}`,
|
|
74
71
|
[TYPE_NULL]: TYPE_NULL,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
number: `a number`,
|
|
73
|
+
string: `a string`,
|
|
74
|
+
symbol: `a symbol`,
|
|
78
75
|
[TYPE_OBJECT]: `an ${TYPE_OBJECT}`,
|
|
79
76
|
[TYPE_UNDEFINED]: TYPE_UNDEFINED
|
|
80
77
|
};
|
|
@@ -84,7 +81,7 @@ function getParameters(input) {
|
|
|
84
81
|
if (typeof input === "boolean") return {
|
|
85
82
|
clone: true,
|
|
86
83
|
output: {},
|
|
87
|
-
reporting: getReporting(
|
|
84
|
+
reporting: getReporting(),
|
|
88
85
|
strict: input
|
|
89
86
|
};
|
|
90
87
|
if (REPORTING_TYPES.has(input)) return {
|
|
@@ -128,8 +125,8 @@ function instanceOf(constructor) {
|
|
|
128
125
|
* @param value Value to check
|
|
129
126
|
* @returns `true` if the value is a schematic, `false` otherwise
|
|
130
127
|
*/
|
|
131
|
-
function
|
|
132
|
-
return typeof value === "object" && value !== null && "$
|
|
128
|
+
function isSchema(value) {
|
|
129
|
+
return typeof value === "object" && value !== null && "$schema" in value && value["$schema"] === true;
|
|
133
130
|
}
|
|
134
131
|
//#endregion
|
|
135
132
|
//#region src/models/validation.model.ts
|
|
@@ -154,26 +151,45 @@ var ValidationError = class extends Error {
|
|
|
154
151
|
};
|
|
155
152
|
//#endregion
|
|
156
153
|
//#region src/helpers/message.helper.ts
|
|
157
|
-
function
|
|
154
|
+
function getDefaultRequiredMessage(key) {
|
|
155
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_REQUIRED.replace("<>", key);
|
|
156
|
+
}
|
|
157
|
+
function getDefaultTypeMessage(key, types) {
|
|
158
|
+
let message = SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_TYPE.replace("<>", key);
|
|
159
|
+
message = message.replace("<>", renderTypes(types));
|
|
160
|
+
return message;
|
|
161
|
+
}
|
|
162
|
+
function getDisallowedMessage(key, property) {
|
|
163
|
+
let message = SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace("<>", key);
|
|
164
|
+
message = message.replace("<>", property);
|
|
165
|
+
return message;
|
|
166
|
+
}
|
|
167
|
+
function getInputTypeMessage(actual) {
|
|
158
168
|
return VALIDATION_MESSAGE_INVALID_INPUT.replace("<>", getValueType(actual));
|
|
159
169
|
}
|
|
160
|
-
function
|
|
170
|
+
function getInputPropertyMissingMessage(key, types) {
|
|
161
171
|
let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace("<>", renderTypes(types));
|
|
162
172
|
message = message.replace("<>", key);
|
|
163
173
|
return message;
|
|
164
174
|
}
|
|
165
|
-
function
|
|
175
|
+
function getInputPropertyTypeMessage(key, types, actual) {
|
|
166
176
|
let message = VALIDATION_MESSAGE_INVALID_TYPE.replace("<>", renderTypes(types));
|
|
167
177
|
message = message.replace("<>", key);
|
|
168
178
|
message = message.replace("<>", getValueType(actual));
|
|
169
179
|
return message;
|
|
170
180
|
}
|
|
171
|
-
function
|
|
181
|
+
function getInputPropertyValidatorMessage(key, type, index, length) {
|
|
172
182
|
let message = VALIDATION_MESSAGE_INVALID_VALUE.replace("<>", key);
|
|
173
183
|
message = message.replace("<>", type);
|
|
174
184
|
if (length > 1) message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace("<>", String(index));
|
|
175
185
|
return message;
|
|
176
186
|
}
|
|
187
|
+
function getSchematicPropertyNullableMessage(key) {
|
|
188
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", key);
|
|
189
|
+
}
|
|
190
|
+
function getSchematicPropertyTypeMessage(key) {
|
|
191
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", key);
|
|
192
|
+
}
|
|
177
193
|
function getPropertyType(type) {
|
|
178
194
|
switch (true) {
|
|
179
195
|
case typeof type === "function": return isConstructor(type) ? type.name : TYPE_FUNCTION_RESULT;
|
|
@@ -181,9 +197,6 @@ function getPropertyType(type) {
|
|
|
181
197
|
default: return PREFIXED_TYPES[TYPE_OBJECT];
|
|
182
198
|
}
|
|
183
199
|
}
|
|
184
|
-
function getUnknownKeysMessage(keys) {
|
|
185
|
-
return VALIDATION_MESSAGE_UNKNOWN_KEYS.replace("<>", renderKeys(keys));
|
|
186
|
-
}
|
|
187
200
|
function getValueType(value) {
|
|
188
201
|
const valueType = typeof value;
|
|
189
202
|
switch (true) {
|
|
@@ -219,6 +232,12 @@ function renderTypes(types) {
|
|
|
219
232
|
}
|
|
220
233
|
return renderParts(parts, CONJUNCTION_OR, CONJUNCTION_OR_COMMA);
|
|
221
234
|
}
|
|
235
|
+
function getRequiredMessage(key) {
|
|
236
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key);
|
|
237
|
+
}
|
|
238
|
+
function getUnknownKeysMessage(keys) {
|
|
239
|
+
return VALIDATION_MESSAGE_UNKNOWN_KEYS.replace("<>", renderKeys(keys));
|
|
240
|
+
}
|
|
222
241
|
//#endregion
|
|
223
242
|
//#region src/validator/base.validator.ts
|
|
224
243
|
function getBaseValidator(validators) {
|
|
@@ -245,9 +264,10 @@ function getFunctionValidator(fn) {
|
|
|
245
264
|
}
|
|
246
265
|
//#endregion
|
|
247
266
|
//#region src/validator/named.handler.ts
|
|
248
|
-
function getNamedHandlers(original, prefix) {
|
|
267
|
+
function getNamedHandlers(original, prefix, allowed) {
|
|
249
268
|
const handlers = {};
|
|
250
269
|
if (original == null) return handlers;
|
|
270
|
+
if (!allowed) throw new TypeError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace("<>", prefix).replace("<>", PROPERTY_VALIDATORS));
|
|
251
271
|
if (!isPlainObject(original)) throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
252
272
|
const keys = Object.keys(original);
|
|
253
273
|
const { length } = keys;
|
|
@@ -276,7 +296,7 @@ function getNamedValidator(key, name, handlers) {
|
|
|
276
296
|
const information = {
|
|
277
297
|
key,
|
|
278
298
|
validator,
|
|
279
|
-
message:
|
|
299
|
+
message: getInputPropertyValidatorMessage(key.full, name, index, length),
|
|
280
300
|
value: input
|
|
281
301
|
};
|
|
282
302
|
parameters.information?.push(information);
|
|
@@ -300,8 +320,8 @@ const namedValidators = {
|
|
|
300
320
|
};
|
|
301
321
|
//#endregion
|
|
302
322
|
//#region src/validator/schematic.validator.ts
|
|
303
|
-
function
|
|
304
|
-
const validator =
|
|
323
|
+
function getSchemaValidator(schematic) {
|
|
324
|
+
const validator = schemaValidators.get(schematic);
|
|
305
325
|
return (input, parameters, get) => {
|
|
306
326
|
let result;
|
|
307
327
|
if (isPlainObject(input)) result = validator(input, parameters, get);
|
|
@@ -313,7 +333,13 @@ function getSchematicValidator(schematic) {
|
|
|
313
333
|
}
|
|
314
334
|
//#endregion
|
|
315
335
|
//#region src/validator/object.validator.ts
|
|
336
|
+
function getDefaults(obj, key, allowed) {
|
|
337
|
+
if (!("$default" in obj)) return;
|
|
338
|
+
if (!allowed) throw new SchematicError(getDisallowedMessage(key, PROPERTY_DEFAULT));
|
|
339
|
+
return { value: obj[PROPERTY_DEFAULT] };
|
|
340
|
+
}
|
|
316
341
|
function getDisallowedProperty(obj) {
|
|
342
|
+
if ("$default" in obj) return PROPERTY_DEFAULT;
|
|
317
343
|
if ("$required" in obj) return PROPERTY_REQUIRED;
|
|
318
344
|
if ("$type" in obj) return PROPERTY_TYPE;
|
|
319
345
|
if ("$validators" in obj) return PROPERTY_VALIDATORS;
|
|
@@ -324,14 +350,14 @@ function getObjectValidator(original, origin, fromType) {
|
|
|
324
350
|
if (keysLength === 0) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
325
351
|
if (fromType ?? false) {
|
|
326
352
|
const property = getDisallowedProperty(original);
|
|
327
|
-
if (property != null) throw new SchematicError(
|
|
353
|
+
if (property != null) throw new SchematicError(getDisallowedMessage(origin.full, property));
|
|
328
354
|
}
|
|
329
355
|
const set = /* @__PURE__ */ new Set();
|
|
330
356
|
const items = [];
|
|
331
357
|
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
332
358
|
const key = keys[keyIndex];
|
|
333
359
|
const value = original[key];
|
|
334
|
-
if (value == null) throw new SchematicError(
|
|
360
|
+
if (value == null) throw new SchematicError(getSchematicPropertyNullableMessage(join([origin?.full, key], ".")));
|
|
335
361
|
const prefixedKey = origin == null ? key : join([origin.full, key], ".");
|
|
336
362
|
const fullKey = {
|
|
337
363
|
full: prefixedKey,
|
|
@@ -340,16 +366,18 @@ function getObjectValidator(original, origin, fromType) {
|
|
|
340
366
|
let handlers = {};
|
|
341
367
|
let required = true;
|
|
342
368
|
let typed = false;
|
|
369
|
+
let defaults;
|
|
343
370
|
let types;
|
|
344
371
|
const validators = [];
|
|
345
372
|
if (isPlainObject(value)) {
|
|
346
373
|
typed = PROPERTY_TYPE in value;
|
|
347
374
|
const type = typed ? value[PROPERTY_TYPE] : value;
|
|
348
|
-
|
|
349
|
-
|
|
375
|
+
defaults = getDefaults(value, prefixedKey, typed);
|
|
376
|
+
handlers = getNamedHandlers(value[PROPERTY_VALIDATORS], prefixedKey, typed);
|
|
377
|
+
required = getRequired(value, prefixedKey, typed) ?? required;
|
|
350
378
|
types = Array.isArray(type) ? type : [type];
|
|
351
379
|
} else types = Array.isArray(value) ? value : [value];
|
|
352
|
-
if (types.length === 0) throw new SchematicError(
|
|
380
|
+
if (types.length === 0) throw new SchematicError(getSchematicPropertyTypeMessage(prefixedKey));
|
|
353
381
|
const typesLength = types.length;
|
|
354
382
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
355
383
|
const type = types[typeIndex];
|
|
@@ -361,23 +389,28 @@ function getObjectValidator(original, origin, fromType) {
|
|
|
361
389
|
case isPlainObject(type):
|
|
362
390
|
validator = getObjectValidator(type, fullKey, typed);
|
|
363
391
|
break;
|
|
364
|
-
case
|
|
365
|
-
validator =
|
|
392
|
+
case isSchema(type):
|
|
393
|
+
validator = getSchemaValidator(type);
|
|
366
394
|
break;
|
|
367
395
|
case TYPE_ALL.has(type):
|
|
368
396
|
validator = getNamedValidator(fullKey, type, handlers);
|
|
369
397
|
break;
|
|
370
|
-
default: throw new SchematicError(
|
|
398
|
+
default: throw new SchematicError(getSchematicPropertyTypeMessage(prefixedKey));
|
|
371
399
|
}
|
|
372
400
|
validators.push(validator);
|
|
373
401
|
}
|
|
374
|
-
|
|
402
|
+
required = required && !types.includes("undefined");
|
|
403
|
+
if (defaults != null && !required) throw new SchematicError(getDefaultRequiredMessage(prefixedKey));
|
|
404
|
+
const validator = getBaseValidator(validators);
|
|
405
|
+
if (defaults != null && Array.isArray(validator(defaults.value, getParameters(), false))) throw new SchematicError(getDefaultTypeMessage(prefixedKey, types));
|
|
375
406
|
items.push({
|
|
407
|
+
defaults,
|
|
408
|
+
required,
|
|
376
409
|
types,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
validator: getBaseValidator(validators)
|
|
410
|
+
validator,
|
|
411
|
+
key: fullKey
|
|
380
412
|
});
|
|
413
|
+
set.add(key);
|
|
381
414
|
}
|
|
382
415
|
const validatorsLength = items.length;
|
|
383
416
|
return (input, parameters, get) => {
|
|
@@ -389,7 +422,7 @@ function getObjectValidator(original, origin, fromType) {
|
|
|
389
422
|
short: ""
|
|
390
423
|
},
|
|
391
424
|
value: input,
|
|
392
|
-
message:
|
|
425
|
+
message: getInputTypeMessage(input)
|
|
393
426
|
};
|
|
394
427
|
if (parameters.reporting.throw) throw new ValidationError([information]);
|
|
395
428
|
parameters.information?.push(information);
|
|
@@ -414,15 +447,19 @@ function getObjectValidator(original, origin, fromType) {
|
|
|
414
447
|
const allInformation = [];
|
|
415
448
|
const output = {};
|
|
416
449
|
for (let validatorIndex = 0; validatorIndex < validatorsLength; validatorIndex += 1) {
|
|
417
|
-
const { key, required, types, validator } = items[validatorIndex];
|
|
450
|
+
const { defaults, key, required, types, validator } = items[validatorIndex];
|
|
418
451
|
const value = input[key.short];
|
|
419
452
|
if (value === void 0) {
|
|
420
453
|
if (required) {
|
|
454
|
+
if (get && defaults != null) {
|
|
455
|
+
output[key.short] = clone(defaults.value);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
421
458
|
if (parameters.reporting.none) return [];
|
|
422
459
|
const information = {
|
|
423
460
|
key,
|
|
424
461
|
value,
|
|
425
|
-
message:
|
|
462
|
+
message: getInputPropertyMissingMessage(key.full, types)
|
|
426
463
|
};
|
|
427
464
|
if (parameters.reporting.throw) throw new ValidationError([information]);
|
|
428
465
|
parameters.information?.push(information);
|
|
@@ -446,7 +483,7 @@ function getObjectValidator(original, origin, fromType) {
|
|
|
446
483
|
const information = typeof result !== "boolean" && result.length > 0 ? result : [{
|
|
447
484
|
key,
|
|
448
485
|
value,
|
|
449
|
-
message:
|
|
486
|
+
message: getInputPropertyTypeMessage(key.full, types, value)
|
|
450
487
|
}];
|
|
451
488
|
if (parameters.reporting.throw) throw new ValidationError(information);
|
|
452
489
|
if (parameters.reporting.all) {
|
|
@@ -460,22 +497,23 @@ function getObjectValidator(original, origin, fromType) {
|
|
|
460
497
|
return allInformation.length === 0 ? true : allInformation;
|
|
461
498
|
};
|
|
462
499
|
}
|
|
463
|
-
function getRequired(key,
|
|
500
|
+
function getRequired(obj, key, allowed) {
|
|
464
501
|
if (!("$required" in obj)) return;
|
|
465
|
-
if (
|
|
502
|
+
if (!allowed) throw new SchematicError(getDisallowedMessage(key, PROPERTY_REQUIRED));
|
|
503
|
+
if (typeof obj["$required"] !== "boolean") throw new SchematicError(getRequiredMessage(key));
|
|
466
504
|
return obj[PROPERTY_REQUIRED];
|
|
467
505
|
}
|
|
468
506
|
//#endregion
|
|
469
|
-
//#region src/
|
|
507
|
+
//#region src/schema.ts
|
|
470
508
|
/**
|
|
471
|
-
* A
|
|
509
|
+
* A schema for validating objects
|
|
472
510
|
*/
|
|
473
|
-
var
|
|
511
|
+
var Schema = class {
|
|
474
512
|
#validator;
|
|
475
513
|
constructor(validator) {
|
|
476
|
-
Object.defineProperty(this,
|
|
514
|
+
Object.defineProperty(this, PROPERTY_SCHEMA, { value: true });
|
|
477
515
|
this.#validator = validator;
|
|
478
|
-
|
|
516
|
+
schemaValidators.set(this, validator);
|
|
479
517
|
}
|
|
480
518
|
get(value, options) {
|
|
481
519
|
const parameters = getParameters(options);
|
|
@@ -492,11 +530,11 @@ var Schematic = class {
|
|
|
492
530
|
return error(parameters.reporting.all ? result : result[0]);
|
|
493
531
|
}
|
|
494
532
|
};
|
|
495
|
-
function
|
|
496
|
-
if (
|
|
533
|
+
function schema(schema) {
|
|
534
|
+
if (isSchema(schema)) return schema;
|
|
497
535
|
if (!isPlainObject(schema)) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
|
|
498
|
-
return new
|
|
536
|
+
return new Schema(getObjectValidator(schema));
|
|
499
537
|
}
|
|
500
|
-
const
|
|
538
|
+
const schemaValidators = /* @__PURE__ */ new WeakMap();
|
|
501
539
|
//#endregion
|
|
502
|
-
export { SchematicError, ValidationError, instanceOf,
|
|
540
|
+
export { SchematicError, ValidationError, instanceOf, isSchema, schema };
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Schema } from "../schema.mjs";
|
|
2
|
+
import { PlainSchematic, Schematic, SchematicProperty } from "./schematic.plain.model.mjs";
|
|
3
3
|
import { IsOptionalProperty, ValueName, Values } from "./misc.model.mjs";
|
|
4
4
|
import { Constructor, Simplify } from "@oscarpalmer/atoms/models";
|
|
5
5
|
|
|
6
6
|
//#region src/models/infer.model.d.ts
|
|
7
7
|
/**
|
|
8
|
-
* Infers the TypeScript type from a {@link
|
|
8
|
+
* Infers the TypeScript type from a {@link Schematic} definition
|
|
9
9
|
*
|
|
10
|
-
* @template Model
|
|
10
|
+
* @template Model Schematic to infer types from
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```ts
|
|
14
|
-
* const
|
|
14
|
+
* const userSchematic = {
|
|
15
15
|
* name: 'string',
|
|
16
16
|
* age: 'number',
|
|
17
17
|
* address: { $required: false, $type: 'string' },
|
|
18
|
-
* } satisfies
|
|
18
|
+
* } satisfies Schematic;
|
|
19
19
|
*
|
|
20
|
-
* type User = Infer<typeof
|
|
20
|
+
* type User = Infer<typeof userSchematic>;
|
|
21
21
|
* // { name: string; age: number; address?: string }
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
type Infer<Model extends
|
|
24
|
+
type Infer<Model extends Schematic> = Simplify<{ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]> } & { [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]> }>;
|
|
25
25
|
/**
|
|
26
|
-
* Extracts keys from a {@link
|
|
26
|
+
* Extracts keys from a {@link Schematic} whose entries are optional _(i.e., `$required` is `false`)_
|
|
27
27
|
*
|
|
28
|
-
* @template Model - {@link
|
|
28
|
+
* @template Model - {@link Schematic} to extract optional keys from
|
|
29
29
|
*/
|
|
30
|
-
type InferOptionalKeys<Model extends
|
|
30
|
+
type InferOptionalKeys<Model extends Schematic> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never };
|
|
31
31
|
/**
|
|
32
|
-
* Infers the TypeScript type from a {@link
|
|
32
|
+
* Infers the TypeScript type from a {@link SchematicProperty}'s `$type` field
|
|
33
33
|
*
|
|
34
34
|
* @template Value `$type` value _(single or array)_
|
|
35
35
|
*/
|
|
@@ -37,30 +37,30 @@ type InferPropertyType<Value> = Value extends (infer Item)[] ? InferPropertyValu
|
|
|
37
37
|
/**
|
|
38
38
|
* Maps a single `$type` definition to its TypeScript equivalent
|
|
39
39
|
*
|
|
40
|
-
* Resolves, in order: {@link Constructor}
|
|
40
|
+
* Resolves, in order: {@link Constructor}s, {@link Schema} instances, {@link ValueName} values, and nested {@link PlainSchematic} objects
|
|
41
41
|
*
|
|
42
42
|
* @template Value single type definition
|
|
43
43
|
*/
|
|
44
|
-
type InferPropertyValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends
|
|
44
|
+
type InferPropertyValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schema<infer Model> ? Model : Value extends ValueName ? Values[Value & ValueName] : Value extends PlainSchematic ? Infer<Value> : never;
|
|
45
45
|
/**
|
|
46
|
-
* Extracts keys from a {@link
|
|
46
|
+
* Extracts keys from a {@link Schematic} whose entries are required _(i.e., `$required` is not `false`)_
|
|
47
47
|
*
|
|
48
|
-
* @template Model
|
|
48
|
+
* @template Model Schematic to extract required keys from
|
|
49
49
|
*/
|
|
50
|
-
type InferRequiredKeys<Model extends
|
|
50
|
+
type InferRequiredKeys<Model extends Schematic> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never };
|
|
51
51
|
/**
|
|
52
|
-
* Infers the TypeScript type from a top-level {@link
|
|
52
|
+
* Infers the TypeScript type from a top-level {@link Schematic} entry
|
|
53
53
|
*
|
|
54
|
-
* @template Value
|
|
54
|
+
* @template Value Schematic 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
|
-
* Resolves, in order: {@link Constructor}
|
|
60
|
+
* Resolves, in order: {@link Constructor}s, {@link Schema} instances, {@link SchemaProperty} objects, {@link PlainSchematic} objects, and {@link ValueName} values
|
|
61
61
|
*
|
|
62
62
|
* @template Value single schema entry
|
|
63
63
|
*/
|
|
64
|
-
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends
|
|
64
|
+
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schema<infer Model> ? Model : Value extends SchematicProperty ? InferPropertyType<Value['$type']> : Value extends PlainSchematic ? Infer<Value & Schematic> : Value extends ValueName ? Values[Value & ValueName] : never;
|
|
65
65
|
//#endregion
|
|
66
66
|
export { Infer, InferOptionalKeys, InferPropertyType, InferPropertyValue, InferRequiredKeys, InferSchemaEntry, InferSchemaEntryValue };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SchematicProperty } from "./schematic.plain.model.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/models/misc.model.d.ts
|
|
4
4
|
/**
|
|
@@ -29,11 +29,11 @@ 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
|
|
32
|
+
* Returns `true` if the entry is a {@link SchematicProperty} with `$required` set to `false`; otherwise returns `false`
|
|
33
33
|
*
|
|
34
34
|
* @template Value Schema entry to check
|
|
35
35
|
*/
|
|
36
|
-
type IsOptionalProperty<Value> = Value extends
|
|
36
|
+
type IsOptionalProperty<Value> = Value extends SchematicProperty ? Value['$required'] extends false ? true : false : false;
|
|
37
37
|
/**
|
|
38
38
|
* Extracts the last member from a union type by leveraging contravariance of function parameter types
|
|
39
39
|
*
|
|
@@ -1,43 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema } from "../schema.mjs";
|
|
2
2
|
import { ExtractValueNames, ValueName, Values } from "./misc.model.mjs";
|
|
3
3
|
import { Constructor } from "@oscarpalmer/atoms/models";
|
|
4
4
|
|
|
5
|
-
//#region src/models/
|
|
5
|
+
//#region src/models/schematic.plain.model.d.ts
|
|
6
6
|
/**
|
|
7
|
-
* A generic
|
|
7
|
+
* A generic schematic allowing nested schematics, {@link SchematicEntry} values, or arrays of {@link SchematicEntry} as values
|
|
8
8
|
*/
|
|
9
|
-
type
|
|
10
|
-
[key: string]:
|
|
9
|
+
type PlainSchematic = {
|
|
10
|
+
[key: string]: PlainSchematic | SchematicEntry | SchematicEntry[] | undefined;
|
|
11
11
|
} & {
|
|
12
|
+
$default?: never;
|
|
12
13
|
$required?: never;
|
|
13
14
|
$type?: never;
|
|
14
15
|
$validators?: never;
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
|
-
* A
|
|
18
|
+
* A schematic for validating objects
|
|
18
19
|
*
|
|
19
20
|
* @example
|
|
20
21
|
* ```ts
|
|
21
|
-
* const
|
|
22
|
+
* const schematic = {
|
|
22
23
|
* name: 'string',
|
|
23
24
|
* age: 'number',
|
|
24
25
|
* tags: ['string', 'number'],
|
|
25
|
-
* };
|
|
26
|
+
* } satisfies Schematic;
|
|
26
27
|
* ```
|
|
27
28
|
*/
|
|
28
|
-
type
|
|
29
|
+
type Schematic = PlainSchematic;
|
|
29
30
|
/**
|
|
30
|
-
* A union of all valid types for a single
|
|
31
|
+
* A union of all valid types for a single schematic entry
|
|
31
32
|
*
|
|
32
|
-
* Can be a {@link Constructor}, {@link
|
|
33
|
+
* Can be a {@link Constructor}, {@link PlainSchematic}, {@link SchematicProperty}, {@link Schema}, {@link ValueName}, or a custom validator function
|
|
33
34
|
*/
|
|
34
|
-
type
|
|
35
|
+
type SchematicEntry = Constructor | PlainSchematic | Schema<unknown> | SchematicProperty | ValueName | ((value: unknown) => boolean);
|
|
35
36
|
/**
|
|
36
37
|
* A property definition with explicit type(s), an optional requirement flag, and optional validators
|
|
37
38
|
*
|
|
38
39
|
* @example
|
|
39
40
|
* ```ts
|
|
40
|
-
* const prop:
|
|
41
|
+
* const prop: SchematicProperty = {
|
|
41
42
|
* $required: false,
|
|
42
43
|
* $type: ['string', 'number'],
|
|
43
44
|
* $validators: {
|
|
@@ -47,7 +48,8 @@ type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknow
|
|
|
47
48
|
* };
|
|
48
49
|
* ```
|
|
49
50
|
*/
|
|
50
|
-
type
|
|
51
|
+
type SchematicProperty = {
|
|
52
|
+
$default?: unknown;
|
|
51
53
|
/**
|
|
52
54
|
* Whether the property is required _(defaults to `true`)_
|
|
53
55
|
*/
|
|
@@ -62,11 +64,11 @@ type SchemaProperty = {
|
|
|
62
64
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
63
65
|
};
|
|
64
66
|
/**
|
|
65
|
-
* A union of valid types for a {@link
|
|
67
|
+
* A union of valid types for a {@link SchematicProperty}'s `$type` field
|
|
66
68
|
*
|
|
67
|
-
* Can be a {@link Constructor}, {@link
|
|
69
|
+
* Can be a {@link Constructor}, {@link PlainSchematic}, {@link Schema}, {@link ValueName} string, or a custom validator function
|
|
68
70
|
*/
|
|
69
|
-
type SchemaPropertyType = Constructor |
|
|
71
|
+
type SchemaPropertyType = Constructor | PlainSchematic | Schema<unknown> | ValueName | ((value: unknown) => boolean);
|
|
70
72
|
/**
|
|
71
73
|
* A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
|
|
72
74
|
*
|
|
@@ -83,4 +85,4 @@ type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | Value
|
|
|
83
85
|
*/
|
|
84
86
|
type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
|
|
85
87
|
//#endregion
|
|
86
|
-
export {
|
|
88
|
+
export { PlainSchematic, PropertyValidators, SchemaPropertyType, Schematic, SchematicEntry, SchematicProperty };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ToSchemaPropertyType, ToSchemaType } from "./transform.model.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { PropertyValidators } from "./
|
|
2
|
+
import { Schema } from "../schema.mjs";
|
|
3
|
+
import { PropertyValidators } from "./schematic.plain.model.mjs";
|
|
4
4
|
import { OptionalKeys, RequiredKeys } from "./misc.model.mjs";
|
|
5
5
|
import { PlainObject, Simplify } from "@oscarpalmer/atoms/models";
|
|
6
6
|
|
|
7
|
-
//#region src/models/
|
|
7
|
+
//#region src/models/schematic.typed.model.d.ts
|
|
8
8
|
/**
|
|
9
|
-
* A typed optional property definition generated by {@link
|
|
9
|
+
* A typed optional property definition generated by {@link TypedSchematic} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
|
|
10
10
|
*
|
|
11
11
|
* @template Value Property's type _(including `undefined`)_
|
|
12
12
|
*
|
|
@@ -18,12 +18,13 @@ import { PlainObject, Simplify } from "@oscarpalmer/atoms/models";
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
type TypedPropertyOptional<Value> = {
|
|
21
|
+
$default?: never;
|
|
21
22
|
$required: false;
|
|
22
23
|
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
23
24
|
$validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
|
|
24
25
|
};
|
|
25
26
|
/**
|
|
26
|
-
* A typed required property definition generated by {@link
|
|
27
|
+
* A typed required property definition generated by {@link TypedSchematic} for required keys, with `$required` defaulting to `true`
|
|
27
28
|
*
|
|
28
29
|
* @template Value Property's type
|
|
29
30
|
*
|
|
@@ -35,6 +36,7 @@ type TypedPropertyOptional<Value> = {
|
|
|
35
36
|
* ```
|
|
36
37
|
*/
|
|
37
38
|
type TypedPropertyRequired<Value> = {
|
|
39
|
+
$default?: unknown;
|
|
38
40
|
$required?: true;
|
|
39
41
|
$type: ToSchemaPropertyType<Value>;
|
|
40
42
|
$validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
|
|
@@ -42,7 +44,7 @@ type TypedPropertyRequired<Value> = {
|
|
|
42
44
|
/**
|
|
43
45
|
* Creates a schema type constrained to match a TypeScript type
|
|
44
46
|
*
|
|
45
|
-
* Required keys map to {@link ToSchemaType} or {@link TypedPropertyRequired}; plain object values may also use {@link
|
|
47
|
+
* Required keys map to {@link ToSchemaType} or {@link TypedPropertyRequired}; plain object values may also use {@link Schema}
|
|
46
48
|
*
|
|
47
49
|
* @template Model Object type to generate a schema for
|
|
48
50
|
*
|
|
@@ -57,22 +59,6 @@ type TypedPropertyRequired<Value> = {
|
|
|
57
59
|
* };
|
|
58
60
|
* ```
|
|
59
61
|
*/
|
|
60
|
-
type
|
|
61
|
-
/**
|
|
62
|
-
* A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
|
|
63
|
-
*
|
|
64
|
-
* @template Model Nested object type
|
|
65
|
-
*/
|
|
66
|
-
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
67
|
-
$required: false;
|
|
68
|
-
} & TypedSchema<Model>;
|
|
69
|
-
/**
|
|
70
|
-
* A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
|
|
71
|
-
*
|
|
72
|
-
* @template Model Nested object type
|
|
73
|
-
*/
|
|
74
|
-
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
75
|
-
$required?: true;
|
|
76
|
-
} & TypedSchema<Model>;
|
|
62
|
+
type TypedSchematic<Model extends PlainObject> = Simplify<{ [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? Schema<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]> } & { [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? Schema<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]> }>;
|
|
77
63
|
//#endregion
|
|
78
|
-
export { TypedPropertyOptional, TypedPropertyRequired,
|
|
64
|
+
export { TypedPropertyOptional, TypedPropertyRequired, TypedSchematic };
|