@oscarpalmer/jhunal 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.d.mts +27 -0
- package/dist/{constants.js → constants.mjs} +4 -2
- package/{types/helpers.d.ts → dist/helpers.d.mts} +8 -4
- package/dist/{helpers.js → helpers.mjs} +3 -1
- package/dist/index.d.mts +530 -0
- package/dist/{jhunal.full.js → index.mjs} +31 -75
- package/{types/models.d.ts → dist/models.d.mts} +104 -152
- package/dist/models.mjs +13 -0
- package/dist/schematic.d.mts +36 -0
- package/dist/{schematic.js → schematic.mjs} +8 -6
- package/dist/validation/property.validation.d.mts +7 -0
- package/dist/validation/{property.validation.js → property.validation.mjs} +7 -5
- package/dist/validation/value.validation.d.mts +6 -0
- package/dist/validation/{value.validation.js → value.validation.mjs} +4 -2
- package/package.json +52 -47
- package/src/constants.ts +4 -4
- package/src/models.ts +14 -40
- package/src/schematic.ts +5 -5
- package/src/validation/property.validation.ts +11 -6
- package/dist/index.js +0 -4
- package/dist/models.js +0 -16
- package/types/constants.d.ts +0 -22
- package/types/index.d.ts +0 -3
- package/types/schematic.d.ts +0 -32
- package/types/validation/property.validation.d.ts +0 -3
- package/types/validation/value.validation.d.ts +0 -2
|
@@ -1,60 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* @returns `true` if the value is a constructor function, otherwise `false`
|
|
5
|
-
*/
|
|
6
|
-
function isConstructor(value) {
|
|
7
|
-
return typeof value === "function" && value.prototype?.constructor === value;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Is the value a plain object?
|
|
11
|
-
* @param value Value to check
|
|
12
|
-
* @returns `true` if the value is a plain object, otherwise `false`
|
|
13
|
-
*/
|
|
14
|
-
function isPlainObject(value) {
|
|
15
|
-
if (value === null || typeof value !== "object") return false;
|
|
16
|
-
if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
|
|
17
|
-
const prototype = Object.getPrototypeOf(value);
|
|
18
|
-
return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
|
|
19
|
-
}
|
|
20
|
-
function compact(array, strict) {
|
|
21
|
-
if (!Array.isArray(array)) return [];
|
|
22
|
-
if (strict === true) return array.filter(Boolean);
|
|
23
|
-
const { length } = array;
|
|
24
|
-
const compacted = [];
|
|
25
|
-
for (let index = 0; index < length; index += 1) {
|
|
26
|
-
const item = array[index];
|
|
27
|
-
if (item != null) compacted.push(item);
|
|
28
|
-
}
|
|
29
|
-
return compacted;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Get the string value from any value
|
|
33
|
-
* @param value Original value
|
|
34
|
-
* @returns String representation of the value
|
|
35
|
-
*/
|
|
36
|
-
function getString(value) {
|
|
37
|
-
if (typeof value === "string") return value;
|
|
38
|
-
if (value == null) return "";
|
|
39
|
-
if (typeof value === "function") return getString(value());
|
|
40
|
-
if (typeof value !== "object") return String(value);
|
|
41
|
-
const asString = String(value.valueOf?.() ?? value);
|
|
42
|
-
return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Join an array of values into a string
|
|
46
|
-
* @param value Array of values
|
|
47
|
-
* @param delimiter Delimiter to use between values
|
|
48
|
-
* @returns Joined string
|
|
49
|
-
*/
|
|
50
|
-
function join(value, delimiter) {
|
|
51
|
-
return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
|
|
52
|
-
}
|
|
1
|
+
import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
|
|
2
|
+
import { join } from "@oscarpalmer/atoms/string";
|
|
3
|
+
//#region src/constants.ts
|
|
53
4
|
const ERROR_NAME = "SchematicError";
|
|
54
|
-
const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
|
|
55
5
|
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
56
6
|
const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
57
7
|
const MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED = "'<key>.<property>' property is not allowed for schemas in $type";
|
|
8
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE = "'<>' property must not be 'null' or 'undefined'";
|
|
58
9
|
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
59
10
|
const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
60
11
|
const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
|
|
@@ -65,7 +16,6 @@ const PROPERTY_REQUIRED = "$required";
|
|
|
65
16
|
const PROPERTY_TYPE = "$type";
|
|
66
17
|
const PROPERTY_VALIDATORS = "$validators";
|
|
67
18
|
const SCHEMATIC_NAME = "$schematic";
|
|
68
|
-
const TEMPLATE_PATTERN = "<>";
|
|
69
19
|
const TEMPLATE_PATTERN_KEY = "<key>";
|
|
70
20
|
const TEMPLATE_PATTERN_PROPERTY = "<property>";
|
|
71
21
|
const TYPE_OBJECT = "object";
|
|
@@ -86,6 +36,8 @@ const TYPE_ALL = new Set([
|
|
|
86
36
|
"null",
|
|
87
37
|
TYPE_UNDEFINED
|
|
88
38
|
]);
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/helpers.ts
|
|
89
41
|
/**
|
|
90
42
|
* Creates a validator function for a given constructor
|
|
91
43
|
* @param constructor - Constructor to check against
|
|
@@ -104,15 +56,12 @@ function instanceOf(constructor) {
|
|
|
104
56
|
* @returns `true` if the value is a schematic, `false` otherwise
|
|
105
57
|
*/
|
|
106
58
|
function isSchematic(value) {
|
|
107
|
-
return typeof value === "object" && value !== null &&
|
|
59
|
+
return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
|
|
108
60
|
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/models.ts
|
|
109
63
|
/**
|
|
110
|
-
* A custom error class for
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* ```ts
|
|
114
|
-
* throw new SchematicError('Expected a string, received a number');
|
|
115
|
-
* ```
|
|
64
|
+
* A custom error class for schematic validation failures
|
|
116
65
|
*/
|
|
117
66
|
var SchematicError = class extends Error {
|
|
118
67
|
constructor(message) {
|
|
@@ -120,10 +69,12 @@ var SchematicError = class extends Error {
|
|
|
120
69
|
this.name = ERROR_NAME;
|
|
121
70
|
}
|
|
122
71
|
};
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/validation/property.validation.ts
|
|
123
74
|
function getDisallowedProperty(obj) {
|
|
124
|
-
if (
|
|
125
|
-
if (
|
|
126
|
-
if (
|
|
75
|
+
if ("$required" in obj) return PROPERTY_REQUIRED;
|
|
76
|
+
if ("$type" in obj) return PROPERTY_TYPE;
|
|
77
|
+
if ("$validators" in obj) return PROPERTY_VALIDATORS;
|
|
127
78
|
}
|
|
128
79
|
function getProperties(original, prefix, fromType) {
|
|
129
80
|
if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
@@ -136,30 +87,30 @@ function getProperties(original, prefix, fromType) {
|
|
|
136
87
|
const properties = [];
|
|
137
88
|
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
138
89
|
const key = keys[keyIndex];
|
|
139
|
-
if (EXPRESSION_PROPERTY.test(key)) continue;
|
|
140
90
|
const value = original[key];
|
|
91
|
+
if (value == null) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", join([prefix, key], ".")));
|
|
141
92
|
const types = [];
|
|
142
93
|
let required = true;
|
|
143
94
|
let validators = {};
|
|
144
95
|
if (isPlainObject(value)) {
|
|
145
96
|
required = getRequired(key, value) ?? required;
|
|
146
97
|
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
147
|
-
if (
|
|
98
|
+
if ("$type" in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
|
|
148
99
|
else types.push(TYPE_OBJECT, ...getTypes(key, value, prefix));
|
|
149
100
|
} else types.push(...getTypes(key, value, prefix));
|
|
150
|
-
if (!required && !types.includes(
|
|
101
|
+
if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
|
|
151
102
|
properties.push({
|
|
152
103
|
key,
|
|
153
104
|
types,
|
|
154
105
|
validators,
|
|
155
|
-
required: required && !types.includes(
|
|
106
|
+
required: required && !types.includes("undefined")
|
|
156
107
|
});
|
|
157
108
|
}
|
|
158
109
|
return properties;
|
|
159
110
|
}
|
|
160
111
|
function getRequired(key, obj) {
|
|
161
|
-
if (!(
|
|
162
|
-
if (typeof obj[
|
|
112
|
+
if (!("$required" in obj)) return;
|
|
113
|
+
if (typeof obj["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
|
|
163
114
|
return obj[PROPERTY_REQUIRED];
|
|
164
115
|
}
|
|
165
116
|
function getTypes(key, original, prefix, fromType) {
|
|
@@ -181,10 +132,10 @@ function getTypes(key, original, prefix, fromType) {
|
|
|
181
132
|
case TYPE_ALL.has(value):
|
|
182
133
|
types.push(value);
|
|
183
134
|
break;
|
|
184
|
-
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
|
|
135
|
+
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
|
|
185
136
|
}
|
|
186
137
|
}
|
|
187
|
-
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
|
|
138
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
|
|
188
139
|
return types;
|
|
189
140
|
}
|
|
190
141
|
function getValidators(original) {
|
|
@@ -195,15 +146,17 @@ function getValidators(original) {
|
|
|
195
146
|
const { length } = keys;
|
|
196
147
|
for (let index = 0; index < length; index += 1) {
|
|
197
148
|
const key = keys[index];
|
|
198
|
-
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(
|
|
149
|
+
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
|
|
199
150
|
const value = original[key];
|
|
200
151
|
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
201
|
-
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(
|
|
152
|
+
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
|
|
202
153
|
return true;
|
|
203
154
|
});
|
|
204
155
|
}
|
|
205
156
|
return validators;
|
|
206
157
|
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/validation/value.validation.ts
|
|
207
160
|
function validateObject(obj, properties) {
|
|
208
161
|
if (!isPlainObject(obj)) return false;
|
|
209
162
|
const propertiesLength = properties.length;
|
|
@@ -242,6 +195,8 @@ const validators = {
|
|
|
242
195
|
symbol: (value) => typeof value === "symbol",
|
|
243
196
|
undefined: (value) => value === void 0
|
|
244
197
|
};
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/schematic.ts
|
|
245
200
|
/**
|
|
246
201
|
* A schematic for validating objects
|
|
247
202
|
*/
|
|
@@ -253,7 +208,7 @@ var Schematic = class {
|
|
|
253
208
|
}
|
|
254
209
|
/**
|
|
255
210
|
* Does the value match the schema?
|
|
256
|
-
* @param value
|
|
211
|
+
* @param value Value to validate
|
|
257
212
|
* @returns `true` if the value matches the schema, otherwise `false`
|
|
258
213
|
*/
|
|
259
214
|
is(value) {
|
|
@@ -265,4 +220,5 @@ function schematic(schema) {
|
|
|
265
220
|
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
266
221
|
return new Schematic(getProperties(schema));
|
|
267
222
|
}
|
|
223
|
+
//#endregion
|
|
268
224
|
export { SchematicError, instanceOf, isSchematic, schematic };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Schematic } from "./schematic.mjs";
|
|
2
|
+
import { Constructor, GenericCallback, PlainObject, Simplify } from "@oscarpalmer/atoms/models";
|
|
3
|
+
|
|
4
|
+
//#region src/models.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* Removes duplicate types from a tuple, preserving first occurrence order
|
|
5
7
|
*
|
|
@@ -12,10 +14,7 @@ import type { Schematic } from './schematic';
|
|
|
12
14
|
* // => ['string', 'number']
|
|
13
15
|
* ```
|
|
14
16
|
*/
|
|
15
|
-
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
|
|
16
|
-
infer Head,
|
|
17
|
-
...infer Tail
|
|
18
|
-
] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
|
|
17
|
+
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [infer Head, ...infer Tail] ? Head extends Seen[number] ? DeduplicateTuple<Tail, Seen> : DeduplicateTuple<Tail, [...Seen, Head]> : Seen;
|
|
19
18
|
/**
|
|
20
19
|
* Recursively extracts {@link ValueName} strings from a type, unwrapping arrays and readonly arrays
|
|
21
20
|
*
|
|
@@ -45,19 +44,13 @@ type ExtractValueNames<Value> = Value extends ValueName ? Value : Value extends
|
|
|
45
44
|
* // { name: string; age: number; address?: string }
|
|
46
45
|
* ```
|
|
47
46
|
*/
|
|
48
|
-
|
|
49
|
-
[Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]>;
|
|
50
|
-
} & {
|
|
51
|
-
[Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]>;
|
|
52
|
-
}>;
|
|
47
|
+
type Infer<Model extends Schema> = Simplify<{ [Key in InferRequiredKeys<Model>]: InferSchemaEntry<Model[Key]> } & { [Key in InferOptionalKeys<Model>]?: InferSchemaEntry<Model[Key]> }>;
|
|
53
48
|
/**
|
|
54
49
|
* Extracts keys from a {@link Schema} whose entries are optional _(i.e., `$required` is `false`)_
|
|
55
50
|
*
|
|
56
51
|
* @template Model - {@link Schema} to extract optional keys from
|
|
57
52
|
*/
|
|
58
|
-
type InferOptionalKeys<Model extends Schema> = keyof {
|
|
59
|
-
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never;
|
|
60
|
-
};
|
|
53
|
+
type InferOptionalKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? Key : never]: never };
|
|
61
54
|
/**
|
|
62
55
|
* Infers the TypeScript type of a {@link SchemaProperty}'s `$type` field, unwrapping arrays to infer their item type
|
|
63
56
|
*
|
|
@@ -77,9 +70,7 @@ type InferPropertyValue<Value> = Value extends Constructor<infer Instance> ? Ins
|
|
|
77
70
|
*
|
|
78
71
|
* @template Model - Schema to extract required keys from
|
|
79
72
|
*/
|
|
80
|
-
type InferRequiredKeys<Model extends Schema> = keyof {
|
|
81
|
-
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
|
|
82
|
-
};
|
|
73
|
+
type InferRequiredKeys<Model extends Schema> = keyof { [Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never };
|
|
83
74
|
/**
|
|
84
75
|
* Infers the type for a top-level {@link Schema} entry, unwrapping arrays to infer their item type
|
|
85
76
|
*
|
|
@@ -93,7 +84,7 @@ type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryVa
|
|
|
93
84
|
*
|
|
94
85
|
* @template Value - single schema entry
|
|
95
86
|
*/
|
|
96
|
-
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends
|
|
87
|
+
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends PlainSchema ? Infer<Value & Schema> : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
|
|
97
88
|
/**
|
|
98
89
|
* Determines whether a schema entry is optional
|
|
99
90
|
*
|
|
@@ -101,17 +92,13 @@ type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ?
|
|
|
101
92
|
*
|
|
102
93
|
* @template Value - Schema entry to check
|
|
103
94
|
*/
|
|
104
|
-
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false :
|
|
105
|
-
$required?: boolean;
|
|
106
|
-
} ? Value extends {
|
|
107
|
-
$required: false;
|
|
108
|
-
} ? true : false : false;
|
|
95
|
+
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : false;
|
|
109
96
|
/**
|
|
110
97
|
* Extracts the last member from a union type by leveraging intersection of function return types
|
|
111
98
|
*
|
|
112
99
|
* @template Value - Union type
|
|
113
100
|
*/
|
|
114
|
-
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item ? Item : never;
|
|
101
|
+
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
|
|
115
102
|
/**
|
|
116
103
|
* Maps each element of a tuple through {@link ToValueType}
|
|
117
104
|
*
|
|
@@ -124,37 +111,21 @@ type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...in
|
|
|
124
111
|
* @template Value - Tuple of types to map
|
|
125
112
|
*/
|
|
126
113
|
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
|
|
127
|
-
/**
|
|
128
|
-
* A nested schema definition that may include a `$required` flag alongside arbitrary string-keyed properties
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```ts
|
|
132
|
-
* const address: NestedSchema = {
|
|
133
|
-
* $required: false,
|
|
134
|
-
* street: 'string',
|
|
135
|
-
* city: 'string',
|
|
136
|
-
* };
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
|
-
export type NestedSchema = {
|
|
140
|
-
/**
|
|
141
|
-
* Whether the nested schema is required (defaults to `true`)
|
|
142
|
-
*/
|
|
143
|
-
$required?: boolean;
|
|
144
|
-
} & Schema;
|
|
145
114
|
/**
|
|
146
115
|
* Extracts keys from an object type that are optional
|
|
147
116
|
*
|
|
148
117
|
* @template Value - Object type to inspect
|
|
149
118
|
*/
|
|
150
|
-
type OptionalKeys<Value> = {
|
|
151
|
-
[Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
|
|
152
|
-
}[keyof Value];
|
|
119
|
+
type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
|
|
153
120
|
/**
|
|
154
121
|
* A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
|
|
155
122
|
*/
|
|
156
123
|
type PlainSchema = {
|
|
157
|
-
|
|
124
|
+
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
|
|
125
|
+
} & {
|
|
126
|
+
$required?: never;
|
|
127
|
+
$type?: never;
|
|
128
|
+
$validators?: never;
|
|
158
129
|
};
|
|
159
130
|
/**
|
|
160
131
|
* A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
|
|
@@ -170,9 +141,7 @@ type PlainSchema = {
|
|
|
170
141
|
* };
|
|
171
142
|
* ```
|
|
172
143
|
*/
|
|
173
|
-
type PropertyValidators<Value> = {
|
|
174
|
-
[Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean>;
|
|
175
|
-
};
|
|
144
|
+
type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
|
|
176
145
|
/**
|
|
177
146
|
* Extracts keys from an object type that are required _(i.e., not optional)_
|
|
178
147
|
*
|
|
@@ -191,18 +160,18 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
191
160
|
* };
|
|
192
161
|
* ```
|
|
193
162
|
*/
|
|
194
|
-
|
|
163
|
+
type Schema = SchemaIndex;
|
|
195
164
|
/**
|
|
196
165
|
* A union of all valid types for a single schema entry
|
|
197
166
|
*
|
|
198
167
|
* Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
|
|
199
168
|
*/
|
|
200
|
-
type SchemaEntry = Constructor |
|
|
169
|
+
type SchemaEntry = Constructor | PlainSchema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
201
170
|
/**
|
|
202
171
|
* Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
|
|
203
172
|
*/
|
|
204
173
|
interface SchemaIndex {
|
|
205
|
-
|
|
174
|
+
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
|
|
206
175
|
}
|
|
207
176
|
/**
|
|
208
177
|
* A property definition with explicit type(s), an optional requirement flag, and optional validators
|
|
@@ -219,19 +188,19 @@ interface SchemaIndex {
|
|
|
219
188
|
* };
|
|
220
189
|
* ```
|
|
221
190
|
*/
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
191
|
+
type SchemaProperty = {
|
|
192
|
+
/**
|
|
193
|
+
* Whether the property is required _(defaults to `true`)_
|
|
194
|
+
*/
|
|
195
|
+
$required?: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
|
|
198
|
+
*/
|
|
199
|
+
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
200
|
+
/**
|
|
201
|
+
* Optional validators keyed by {@link ValueName}, applied during validation
|
|
202
|
+
*/
|
|
203
|
+
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
235
204
|
};
|
|
236
205
|
/**
|
|
237
206
|
* A union of valid types for a {@link SchemaProperty}'s `$type` field
|
|
@@ -240,15 +209,10 @@ export type SchemaProperty = {
|
|
|
240
209
|
*/
|
|
241
210
|
type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
242
211
|
/**
|
|
243
|
-
* A custom error class for
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* ```ts
|
|
247
|
-
* throw new SchematicError('Expected a string, received a number');
|
|
248
|
-
* ```
|
|
212
|
+
* A custom error class for schematic validation failures
|
|
249
213
|
*/
|
|
250
|
-
|
|
251
|
-
|
|
214
|
+
declare class SchematicError extends Error {
|
|
215
|
+
constructor(message: string);
|
|
252
216
|
}
|
|
253
217
|
/**
|
|
254
218
|
* Converts a type into its corresponding {@link SchemaPropertyType}-representation
|
|
@@ -265,7 +229,7 @@ type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaProp
|
|
|
265
229
|
*
|
|
266
230
|
* @template Value - type to convert
|
|
267
231
|
*/
|
|
268
|
-
type ToSchemaPropertyTypeEach<Value> = Value extends
|
|
232
|
+
type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
|
|
269
233
|
/**
|
|
270
234
|
* Converts a type into its corresponding {@link ValueName}-representation
|
|
271
235
|
*
|
|
@@ -288,9 +252,7 @@ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionTo
|
|
|
288
252
|
* // ToValueType<Date> => 'date'
|
|
289
253
|
* ```
|
|
290
254
|
*/
|
|
291
|
-
type ToValueType<Value> = Value extends Schematic<any> ? Value : {
|
|
292
|
-
[Key in keyof Omit<Values, 'object'>]: Value extends Values[Key] ? Key : never;
|
|
293
|
-
}[keyof Omit<Values, 'object'>] extends infer Match ? [Match] extends [never] ? Value extends object ? 'object' | ((value: unknown) => value is Value) : (value: unknown) => value is Value : Match : never;
|
|
255
|
+
type ToValueType<Value> = Value extends Schematic<any> ? Value : { [Key in keyof Omit<Values, 'object'>]: Value extends Values[Key] ? Key : never }[keyof Omit<Values, 'object'>] extends infer Match ? [Match] extends [never] ? Value extends object ? 'object' | ((value: unknown) => value is Value) : (value: unknown) => value is Value : Match : never;
|
|
294
256
|
/**
|
|
295
257
|
* Generates all permutations of a tuple type
|
|
296
258
|
*
|
|
@@ -305,12 +267,7 @@ type ToValueType<Value> = Value extends Schematic<any> ? Value : {
|
|
|
305
267
|
* // => ['string', 'number'] | ['number', 'string']
|
|
306
268
|
* ```
|
|
307
269
|
*/
|
|
308
|
-
type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : {
|
|
309
|
-
[Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [
|
|
310
|
-
...Elput,
|
|
311
|
-
Tuple[Key]
|
|
312
|
-
]>;
|
|
313
|
-
}[keyof Tuple & `${number}`];
|
|
270
|
+
type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : { [Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [...Elput, Tuple[Key]]> }[keyof Tuple & `${number}`];
|
|
314
271
|
/**
|
|
315
272
|
* Removes the element at a given index from a tuple
|
|
316
273
|
*
|
|
@@ -333,19 +290,19 @@ type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends
|
|
|
333
290
|
* // => { $required: false; $type: 'string'; ... }
|
|
334
291
|
* ```
|
|
335
292
|
*/
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
293
|
+
type TypedPropertyOptional<Value> = {
|
|
294
|
+
/**
|
|
295
|
+
* The property is not required
|
|
296
|
+
*/
|
|
297
|
+
$required: false;
|
|
298
|
+
/**
|
|
299
|
+
* The type(s) of the property
|
|
300
|
+
*/
|
|
301
|
+
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
302
|
+
/**
|
|
303
|
+
* Custom validators for the property and its types
|
|
304
|
+
*/
|
|
305
|
+
$validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
|
|
349
306
|
};
|
|
350
307
|
/**
|
|
351
308
|
* A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
|
|
@@ -359,19 +316,19 @@ export type TypedPropertyOptional<Value> = {
|
|
|
359
316
|
* // => { $required?: true; $type: 'string'; ... }
|
|
360
317
|
* ```
|
|
361
318
|
*/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
319
|
+
type TypedPropertyRequired<Value> = {
|
|
320
|
+
/**
|
|
321
|
+
* The property is required _(defaults to `true`)_
|
|
322
|
+
*/
|
|
323
|
+
$required?: true;
|
|
324
|
+
/**
|
|
325
|
+
* The type(s) of the property
|
|
326
|
+
*/
|
|
327
|
+
$type: ToSchemaPropertyType<Value>;
|
|
328
|
+
/**
|
|
329
|
+
* Custom validators for the property and its types
|
|
330
|
+
*/
|
|
331
|
+
$validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
|
|
375
332
|
};
|
|
376
333
|
/**
|
|
377
334
|
* Creates a schema type constrained to match a TypeScript type
|
|
@@ -391,18 +348,14 @@ export type TypedPropertyRequired<Value> = {
|
|
|
391
348
|
* };
|
|
392
349
|
* ```
|
|
393
350
|
*/
|
|
394
|
-
|
|
395
|
-
[Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
|
|
396
|
-
} & {
|
|
397
|
-
[Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> | Schematic<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]>;
|
|
398
|
-
}>;
|
|
351
|
+
type TypedSchema<Model extends PlainObject> = Simplify<{ [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]> } & { [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> | Schematic<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]> }>;
|
|
399
352
|
/**
|
|
400
353
|
* A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
|
|
401
354
|
*
|
|
402
355
|
* @template Model - Nested object type
|
|
403
356
|
*/
|
|
404
357
|
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
405
|
-
|
|
358
|
+
$required: false;
|
|
406
359
|
} & TypedSchema<Model>;
|
|
407
360
|
/**
|
|
408
361
|
* A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
|
|
@@ -410,7 +363,7 @@ type TypedSchemaOptional<Model extends PlainObject> = {
|
|
|
410
363
|
* @template Model - Nested object type
|
|
411
364
|
*/
|
|
412
365
|
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
413
|
-
|
|
366
|
+
$required?: true;
|
|
414
367
|
} & TypedSchema<Model>;
|
|
415
368
|
/**
|
|
416
369
|
* Converts a union type into an intersection
|
|
@@ -425,7 +378,7 @@ type TypedSchemaRequired<Model extends PlainObject> = {
|
|
|
425
378
|
* // => { a: 1 } & { b: 2 }
|
|
426
379
|
* ```
|
|
427
380
|
*/
|
|
428
|
-
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (value: infer Item) => void ? Item : never;
|
|
381
|
+
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends ((value: infer Item) => void) ? Item : never;
|
|
429
382
|
/**
|
|
430
383
|
* Converts a union type into an ordered tuple
|
|
431
384
|
*
|
|
@@ -468,42 +421,40 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only :
|
|
|
468
421
|
* };
|
|
469
422
|
* ```
|
|
470
423
|
*/
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
424
|
+
type ValidatedProperty = {
|
|
425
|
+
/**
|
|
426
|
+
* The property name in the schema
|
|
427
|
+
*/
|
|
428
|
+
key: string;
|
|
429
|
+
/**
|
|
430
|
+
* Whether the property is required
|
|
431
|
+
*/
|
|
432
|
+
required: boolean;
|
|
433
|
+
/**
|
|
434
|
+
* The allowed types for this property
|
|
435
|
+
*/
|
|
436
|
+
types: ValidatedPropertyType[];
|
|
437
|
+
/**
|
|
438
|
+
* Custom validators grouped by {@link ValueName}
|
|
439
|
+
*/
|
|
440
|
+
validators: ValidatedPropertyValidators;
|
|
488
441
|
};
|
|
489
442
|
/**
|
|
490
443
|
* A union of valid types for a {@link ValidatedProperty}'s `types` array
|
|
491
444
|
*
|
|
492
445
|
* Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
|
|
493
446
|
*/
|
|
494
|
-
|
|
447
|
+
type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
|
|
495
448
|
/**
|
|
496
449
|
* A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
|
|
497
450
|
*
|
|
498
451
|
* Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
|
|
499
452
|
*/
|
|
500
|
-
|
|
501
|
-
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
502
|
-
};
|
|
453
|
+
type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
|
|
503
454
|
/**
|
|
504
455
|
* Basic value types
|
|
505
456
|
*/
|
|
506
|
-
|
|
457
|
+
type ValueName = keyof Values;
|
|
507
458
|
/**
|
|
508
459
|
* Maps type name strings to their TypeScript equivalents
|
|
509
460
|
*
|
|
@@ -516,17 +467,18 @@ export type ValueName = keyof Values;
|
|
|
516
467
|
* // Values['null'] => null
|
|
517
468
|
* ```
|
|
518
469
|
*/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
470
|
+
type Values = {
|
|
471
|
+
array: unknown[];
|
|
472
|
+
bigint: bigint;
|
|
473
|
+
boolean: boolean;
|
|
474
|
+
date: Date;
|
|
475
|
+
function: Function;
|
|
476
|
+
null: null;
|
|
477
|
+
number: number;
|
|
478
|
+
object: object;
|
|
479
|
+
string: string;
|
|
480
|
+
symbol: symbol;
|
|
481
|
+
undefined: undefined;
|
|
531
482
|
};
|
|
532
|
-
|
|
483
|
+
//#endregion
|
|
484
|
+
export { Infer, Schema, SchemaProperty, SchematicError, TypedPropertyOptional, TypedPropertyRequired, TypedSchema, ValidatedProperty, ValidatedPropertyType, ValidatedPropertyValidators, ValueName, Values };
|
package/dist/models.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ERROR_NAME } from "./constants.mjs";
|
|
2
|
+
//#region src/models.ts
|
|
3
|
+
/**
|
|
4
|
+
* A custom error class for schematic validation failures
|
|
5
|
+
*/
|
|
6
|
+
var SchematicError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = ERROR_NAME;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { SchematicError };
|