@oscarpalmer/jhunal 0.15.0 → 0.16.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} +2 -0
- package/{types/helpers.d.ts → dist/helpers.d.mts} +8 -4
- package/dist/{helpers.js → helpers.mjs} +3 -1
- package/dist/index.d.mts +553 -0
- package/dist/{jhunal.full.js → index.mjs} +27 -66
- package/{types/models.d.ts → dist/models.d.mts} +102 -127
- package/dist/{models.js → models.mjs} +3 -1
- package/dist/schematic.d.mts +36 -0
- package/dist/{schematic.js → schematic.mjs} +7 -5
- package/dist/validation/property.validation.d.mts +7 -0
- package/dist/validation/{property.validation.js → property.validation.mjs} +6 -4
- package/dist/validation/value.validation.d.mts +6 -0
- package/dist/validation/{value.validation.js → value.validation.mjs} +4 -2
- package/package.json +53 -48
- package/src/constants.ts +1 -2
- package/src/validation/property.validation.ts +1 -1
- package/dist/index.js +0 -4
- 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,55 +1,6 @@
|
|
|
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
5
|
const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
|
|
55
6
|
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
@@ -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,8 +56,10 @@ 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
64
|
* A custom error class for schema validation failures, with its `name` set to {@link ERROR_NAME}
|
|
111
65
|
*
|
|
@@ -120,10 +74,12 @@ var SchematicError = class extends Error {
|
|
|
120
74
|
this.name = ERROR_NAME;
|
|
121
75
|
}
|
|
122
76
|
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/validation/property.validation.ts
|
|
123
79
|
function getDisallowedProperty(obj) {
|
|
124
|
-
if (
|
|
125
|
-
if (
|
|
126
|
-
if (
|
|
80
|
+
if ("$required" in obj) return PROPERTY_REQUIRED;
|
|
81
|
+
if ("$type" in obj) return PROPERTY_TYPE;
|
|
82
|
+
if ("$validators" in obj) return PROPERTY_VALIDATORS;
|
|
127
83
|
}
|
|
128
84
|
function getProperties(original, prefix, fromType) {
|
|
129
85
|
if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
@@ -144,22 +100,22 @@ function getProperties(original, prefix, fromType) {
|
|
|
144
100
|
if (isPlainObject(value)) {
|
|
145
101
|
required = getRequired(key, value) ?? required;
|
|
146
102
|
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
147
|
-
if (
|
|
103
|
+
if ("$type" in value) types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
|
|
148
104
|
else types.push(TYPE_OBJECT, ...getTypes(key, value, prefix));
|
|
149
105
|
} else types.push(...getTypes(key, value, prefix));
|
|
150
|
-
if (!required && !types.includes(
|
|
106
|
+
if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
|
|
151
107
|
properties.push({
|
|
152
108
|
key,
|
|
153
109
|
types,
|
|
154
110
|
validators,
|
|
155
|
-
required: required && !types.includes(
|
|
111
|
+
required: required && !types.includes("undefined")
|
|
156
112
|
});
|
|
157
113
|
}
|
|
158
114
|
return properties;
|
|
159
115
|
}
|
|
160
116
|
function getRequired(key, obj) {
|
|
161
|
-
if (!(
|
|
162
|
-
if (typeof obj[
|
|
117
|
+
if (!("$required" in obj)) return;
|
|
118
|
+
if (typeof obj["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
|
|
163
119
|
return obj[PROPERTY_REQUIRED];
|
|
164
120
|
}
|
|
165
121
|
function getTypes(key, original, prefix, fromType) {
|
|
@@ -181,10 +137,10 @@ function getTypes(key, original, prefix, fromType) {
|
|
|
181
137
|
case TYPE_ALL.has(value):
|
|
182
138
|
types.push(value);
|
|
183
139
|
break;
|
|
184
|
-
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
|
|
140
|
+
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
|
|
185
141
|
}
|
|
186
142
|
}
|
|
187
|
-
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
|
|
143
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
|
|
188
144
|
return types;
|
|
189
145
|
}
|
|
190
146
|
function getValidators(original) {
|
|
@@ -195,15 +151,17 @@ function getValidators(original) {
|
|
|
195
151
|
const { length } = keys;
|
|
196
152
|
for (let index = 0; index < length; index += 1) {
|
|
197
153
|
const key = keys[index];
|
|
198
|
-
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(
|
|
154
|
+
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
|
|
199
155
|
const value = original[key];
|
|
200
156
|
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
201
|
-
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(
|
|
157
|
+
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
|
|
202
158
|
return true;
|
|
203
159
|
});
|
|
204
160
|
}
|
|
205
161
|
return validators;
|
|
206
162
|
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/validation/value.validation.ts
|
|
207
165
|
function validateObject(obj, properties) {
|
|
208
166
|
if (!isPlainObject(obj)) return false;
|
|
209
167
|
const propertiesLength = properties.length;
|
|
@@ -242,6 +200,8 @@ const validators = {
|
|
|
242
200
|
symbol: (value) => typeof value === "symbol",
|
|
243
201
|
undefined: (value) => value === void 0
|
|
244
202
|
};
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/schematic.ts
|
|
245
205
|
/**
|
|
246
206
|
* A schematic for validating objects
|
|
247
207
|
*/
|
|
@@ -265,4 +225,5 @@ function schematic(schema) {
|
|
|
265
225
|
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
266
226
|
return new Schematic(getProperties(schema));
|
|
267
227
|
}
|
|
228
|
+
//#endregion
|
|
268
229
|
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
|
*
|
|
@@ -102,16 +93,16 @@ type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ?
|
|
|
102
93
|
* @template Value - Schema entry to check
|
|
103
94
|
*/
|
|
104
95
|
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : Value extends {
|
|
105
|
-
|
|
96
|
+
$required?: boolean;
|
|
106
97
|
} ? Value extends {
|
|
107
|
-
|
|
98
|
+
$required: false;
|
|
108
99
|
} ? true : false : false;
|
|
109
100
|
/**
|
|
110
101
|
* Extracts the last member from a union type by leveraging intersection of function return types
|
|
111
102
|
*
|
|
112
103
|
* @template Value - Union type
|
|
113
104
|
*/
|
|
114
|
-
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item ? Item : never;
|
|
105
|
+
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends (() => infer Item) ? Item : never;
|
|
115
106
|
/**
|
|
116
107
|
* Maps each element of a tuple through {@link ToValueType}
|
|
117
108
|
*
|
|
@@ -136,25 +127,23 @@ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer He
|
|
|
136
127
|
* };
|
|
137
128
|
* ```
|
|
138
129
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
type NestedSchema = {
|
|
131
|
+
/**
|
|
132
|
+
* Whether the nested schema is required (defaults to `true`)
|
|
133
|
+
*/
|
|
134
|
+
$required?: boolean;
|
|
144
135
|
} & Schema;
|
|
145
136
|
/**
|
|
146
137
|
* Extracts keys from an object type that are optional
|
|
147
138
|
*
|
|
148
139
|
* @template Value - Object type to inspect
|
|
149
140
|
*/
|
|
150
|
-
type OptionalKeys<Value> = {
|
|
151
|
-
[Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
|
|
152
|
-
}[keyof Value];
|
|
141
|
+
type OptionalKeys<Value> = { [Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never }[keyof Value];
|
|
153
142
|
/**
|
|
154
143
|
* A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
|
|
155
144
|
*/
|
|
156
145
|
type PlainSchema = {
|
|
157
|
-
|
|
146
|
+
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
158
147
|
};
|
|
159
148
|
/**
|
|
160
149
|
* A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
|
|
@@ -170,9 +159,7 @@ type PlainSchema = {
|
|
|
170
159
|
* };
|
|
171
160
|
* ```
|
|
172
161
|
*/
|
|
173
|
-
type PropertyValidators<Value> = {
|
|
174
|
-
[Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean>;
|
|
175
|
-
};
|
|
162
|
+
type PropertyValidators<Value> = { [Key in ExtractValueNames<Value>]?: ((value: Values[Key]) => boolean) | Array<(value: Values[Key]) => boolean> };
|
|
176
163
|
/**
|
|
177
164
|
* Extracts keys from an object type that are required _(i.e., not optional)_
|
|
178
165
|
*
|
|
@@ -191,7 +178,7 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
191
178
|
* };
|
|
192
179
|
* ```
|
|
193
180
|
*/
|
|
194
|
-
|
|
181
|
+
type Schema = SchemaIndex;
|
|
195
182
|
/**
|
|
196
183
|
* A union of all valid types for a single schema entry
|
|
197
184
|
*
|
|
@@ -202,7 +189,7 @@ type SchemaEntry = Constructor | Schema | SchemaProperty | Schematic<unknown> |
|
|
|
202
189
|
* Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
|
|
203
190
|
*/
|
|
204
191
|
interface SchemaIndex {
|
|
205
|
-
|
|
192
|
+
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
206
193
|
}
|
|
207
194
|
/**
|
|
208
195
|
* A property definition with explicit type(s), an optional requirement flag, and optional validators
|
|
@@ -219,19 +206,19 @@ interface SchemaIndex {
|
|
|
219
206
|
* };
|
|
220
207
|
* ```
|
|
221
208
|
*/
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
209
|
+
type SchemaProperty = {
|
|
210
|
+
/**
|
|
211
|
+
* Whether the property is required _(defaults to `true`)_
|
|
212
|
+
*/
|
|
213
|
+
$required?: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
|
|
216
|
+
*/
|
|
217
|
+
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
218
|
+
/**
|
|
219
|
+
* Optional validators keyed by {@link ValueName}, applied during validation
|
|
220
|
+
*/
|
|
221
|
+
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
235
222
|
};
|
|
236
223
|
/**
|
|
237
224
|
* A union of valid types for a {@link SchemaProperty}'s `$type` field
|
|
@@ -247,8 +234,8 @@ type SchemaPropertyType = Constructor | PlainSchema | Schematic<unknown> | Value
|
|
|
247
234
|
* throw new SchematicError('Expected a string, received a number');
|
|
248
235
|
* ```
|
|
249
236
|
*/
|
|
250
|
-
|
|
251
|
-
|
|
237
|
+
declare class SchematicError extends Error {
|
|
238
|
+
constructor(message: string);
|
|
252
239
|
}
|
|
253
240
|
/**
|
|
254
241
|
* Converts a type into its corresponding {@link SchemaPropertyType}-representation
|
|
@@ -288,9 +275,7 @@ type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionTo
|
|
|
288
275
|
* // ToValueType<Date> => 'date'
|
|
289
276
|
* ```
|
|
290
277
|
*/
|
|
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;
|
|
278
|
+
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
279
|
/**
|
|
295
280
|
* Generates all permutations of a tuple type
|
|
296
281
|
*
|
|
@@ -305,12 +290,7 @@ type ToValueType<Value> = Value extends Schematic<any> ? Value : {
|
|
|
305
290
|
* // => ['string', 'number'] | ['number', 'string']
|
|
306
291
|
* ```
|
|
307
292
|
*/
|
|
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}`];
|
|
293
|
+
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
294
|
/**
|
|
315
295
|
* Removes the element at a given index from a tuple
|
|
316
296
|
*
|
|
@@ -333,19 +313,19 @@ type TupleRemoveAt<Items extends unknown[], Item extends string, Prefix extends
|
|
|
333
313
|
* // => { $required: false; $type: 'string'; ... }
|
|
334
314
|
* ```
|
|
335
315
|
*/
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
316
|
+
type TypedPropertyOptional<Value> = {
|
|
317
|
+
/**
|
|
318
|
+
* The property is not required
|
|
319
|
+
*/
|
|
320
|
+
$required: false;
|
|
321
|
+
/**
|
|
322
|
+
* The type(s) of the property
|
|
323
|
+
*/
|
|
324
|
+
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
325
|
+
/**
|
|
326
|
+
* Custom validators for the property and its types
|
|
327
|
+
*/
|
|
328
|
+
$validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
|
|
349
329
|
};
|
|
350
330
|
/**
|
|
351
331
|
* A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
|
|
@@ -359,19 +339,19 @@ export type TypedPropertyOptional<Value> = {
|
|
|
359
339
|
* // => { $required?: true; $type: 'string'; ... }
|
|
360
340
|
* ```
|
|
361
341
|
*/
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
342
|
+
type TypedPropertyRequired<Value> = {
|
|
343
|
+
/**
|
|
344
|
+
* The property is required _(defaults to `true`)_
|
|
345
|
+
*/
|
|
346
|
+
$required?: true;
|
|
347
|
+
/**
|
|
348
|
+
* The type(s) of the property
|
|
349
|
+
*/
|
|
350
|
+
$type: ToSchemaPropertyType<Value>;
|
|
351
|
+
/**
|
|
352
|
+
* Custom validators for the property and its types
|
|
353
|
+
*/
|
|
354
|
+
$validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
|
|
375
355
|
};
|
|
376
356
|
/**
|
|
377
357
|
* Creates a schema type constrained to match a TypeScript type
|
|
@@ -391,18 +371,14 @@ export type TypedPropertyRequired<Value> = {
|
|
|
391
371
|
* };
|
|
392
372
|
* ```
|
|
393
373
|
*/
|
|
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
|
-
}>;
|
|
374
|
+
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
375
|
/**
|
|
400
376
|
* A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
|
|
401
377
|
*
|
|
402
378
|
* @template Model - Nested object type
|
|
403
379
|
*/
|
|
404
380
|
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
405
|
-
|
|
381
|
+
$required: false;
|
|
406
382
|
} & TypedSchema<Model>;
|
|
407
383
|
/**
|
|
408
384
|
* A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
|
|
@@ -410,7 +386,7 @@ type TypedSchemaOptional<Model extends PlainObject> = {
|
|
|
410
386
|
* @template Model - Nested object type
|
|
411
387
|
*/
|
|
412
388
|
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
413
|
-
|
|
389
|
+
$required?: true;
|
|
414
390
|
} & TypedSchema<Model>;
|
|
415
391
|
/**
|
|
416
392
|
* Converts a union type into an intersection
|
|
@@ -425,7 +401,7 @@ type TypedSchemaRequired<Model extends PlainObject> = {
|
|
|
425
401
|
* // => { a: 1 } & { b: 2 }
|
|
426
402
|
* ```
|
|
427
403
|
*/
|
|
428
|
-
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends (value: infer Item) => void ? Item : never;
|
|
404
|
+
type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => void : never) extends ((value: infer Item) => void) ? Item : never;
|
|
429
405
|
/**
|
|
430
406
|
* Converts a union type into an ordered tuple
|
|
431
407
|
*
|
|
@@ -468,42 +444,40 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only :
|
|
|
468
444
|
* };
|
|
469
445
|
* ```
|
|
470
446
|
*/
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
447
|
+
type ValidatedProperty = {
|
|
448
|
+
/**
|
|
449
|
+
* The property name in the schema
|
|
450
|
+
*/
|
|
451
|
+
key: string;
|
|
452
|
+
/**
|
|
453
|
+
* Whether the property is required
|
|
454
|
+
*/
|
|
455
|
+
required: boolean;
|
|
456
|
+
/**
|
|
457
|
+
* The allowed types for this property
|
|
458
|
+
*/
|
|
459
|
+
types: ValidatedPropertyType[];
|
|
460
|
+
/**
|
|
461
|
+
* Custom validators grouped by {@link ValueName}
|
|
462
|
+
*/
|
|
463
|
+
validators: ValidatedPropertyValidators;
|
|
488
464
|
};
|
|
489
465
|
/**
|
|
490
466
|
* A union of valid types for a {@link ValidatedProperty}'s `types` array
|
|
491
467
|
*
|
|
492
468
|
* Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
|
|
493
469
|
*/
|
|
494
|
-
|
|
470
|
+
type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
|
|
495
471
|
/**
|
|
496
472
|
* A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
|
|
497
473
|
*
|
|
498
474
|
* Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
|
|
499
475
|
*/
|
|
500
|
-
|
|
501
|
-
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
502
|
-
};
|
|
476
|
+
type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
|
|
503
477
|
/**
|
|
504
478
|
* Basic value types
|
|
505
479
|
*/
|
|
506
|
-
|
|
480
|
+
type ValueName = keyof Values;
|
|
507
481
|
/**
|
|
508
482
|
* Maps type name strings to their TypeScript equivalents
|
|
509
483
|
*
|
|
@@ -516,17 +490,18 @@ export type ValueName = keyof Values;
|
|
|
516
490
|
* // Values['null'] => null
|
|
517
491
|
* ```
|
|
518
492
|
*/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
493
|
+
type Values = {
|
|
494
|
+
array: unknown[];
|
|
495
|
+
bigint: bigint;
|
|
496
|
+
boolean: boolean;
|
|
497
|
+
date: Date;
|
|
498
|
+
function: Function;
|
|
499
|
+
null: null;
|
|
500
|
+
number: number;
|
|
501
|
+
object: object;
|
|
502
|
+
string: string;
|
|
503
|
+
symbol: symbol;
|
|
504
|
+
undefined: undefined;
|
|
531
505
|
};
|
|
532
|
-
|
|
506
|
+
//#endregion
|
|
507
|
+
export { Infer, NestedSchema, Schema, SchemaProperty, SchematicError, TypedPropertyOptional, TypedPropertyRequired, TypedSchema, ValidatedProperty, ValidatedPropertyType, ValidatedPropertyValidators, ValueName, Values };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ERROR_NAME } from "./constants.
|
|
1
|
+
import { ERROR_NAME } from "./constants.mjs";
|
|
2
|
+
//#region src/models.ts
|
|
2
3
|
/**
|
|
3
4
|
* A custom error class for schema validation failures, with its `name` set to {@link ERROR_NAME}
|
|
4
5
|
*
|
|
@@ -13,4 +14,5 @@ var SchematicError = class extends Error {
|
|
|
13
14
|
this.name = ERROR_NAME;
|
|
14
15
|
}
|
|
15
16
|
};
|
|
17
|
+
//#endregion
|
|
16
18
|
export { SchematicError };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Infer, Schema, TypedSchema, ValidatedProperty } from "./models.mjs";
|
|
2
|
+
import { PlainObject } from "@oscarpalmer/atoms/models";
|
|
3
|
+
|
|
4
|
+
//#region src/schematic.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A schematic for validating objects
|
|
7
|
+
*/
|
|
8
|
+
declare class Schematic<Model> {
|
|
9
|
+
#private;
|
|
10
|
+
private readonly $schematic;
|
|
11
|
+
constructor(properties: ValidatedProperty[]);
|
|
12
|
+
/**
|
|
13
|
+
* Does the value match the schema?
|
|
14
|
+
* @param value - Value to validate
|
|
15
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
16
|
+
*/
|
|
17
|
+
is(value: unknown): value is Model;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a schematic from a schema
|
|
21
|
+
* @template Model - Schema type
|
|
22
|
+
* @param schema - Schema to create the schematic from
|
|
23
|
+
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
24
|
+
* @returns A schematic for the given schema
|
|
25
|
+
*/
|
|
26
|
+
declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer<Model>>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a schematic from a typed schema
|
|
29
|
+
* @template Model - Existing type
|
|
30
|
+
* @param schema - Typed schema to create the schematic from
|
|
31
|
+
* @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
|
|
32
|
+
* @returns A schematic for the given typed schema
|
|
33
|
+
*/
|
|
34
|
+
declare function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { Schematic, schematic };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME } from "./constants.
|
|
2
|
-
import { isSchematic } from "./helpers.
|
|
3
|
-
import { SchematicError } from "./models.
|
|
4
|
-
import { getProperties } from "./validation/property.validation.
|
|
5
|
-
import { validateObject } from "./validation/value.validation.
|
|
1
|
+
import { MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME } from "./constants.mjs";
|
|
2
|
+
import { isSchematic } from "./helpers.mjs";
|
|
3
|
+
import { SchematicError } from "./models.mjs";
|
|
4
|
+
import { getProperties } from "./validation/property.validation.mjs";
|
|
5
|
+
import { validateObject } from "./validation/value.validation.mjs";
|
|
6
6
|
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
7
|
+
//#region src/schematic.ts
|
|
7
8
|
/**
|
|
8
9
|
* A schematic for validating objects
|
|
9
10
|
*/
|
|
@@ -27,4 +28,5 @@ function schematic(schema) {
|
|
|
27
28
|
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
28
29
|
return new Schematic(getProperties(schema));
|
|
29
30
|
}
|
|
31
|
+
//#endregion
|
|
30
32
|
export { Schematic, schematic };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ValidatedProperty } from "../models.mjs";
|
|
2
|
+
import { PlainObject } from "@oscarpalmer/atoms/models";
|
|
3
|
+
|
|
4
|
+
//#region src/validation/property.validation.d.ts
|
|
5
|
+
declare function getProperties(original: PlainObject, prefix?: string, fromType?: boolean): ValidatedProperty[];
|
|
6
|
+
//#endregion
|
|
7
|
+
export { getProperties };
|