@oscarpalmer/jhunal 0.10.0 → 0.11.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.js +17 -4
- package/dist/index.js +2 -1
- package/dist/is.js +2 -2
- package/dist/jhunal.full.js +58 -30
- package/dist/models.js +8 -0
- package/dist/schematic.js +7 -8
- package/dist/validation/schema.validation.js +34 -21
- package/dist/validation/value.validation.js +1 -1
- package/package.json +1 -1
- package/src/constants.ts +25 -4
- package/src/index.ts +1 -1
- package/src/is.ts +2 -2
- package/src/models.ts +50 -29
- package/src/schematic.ts +19 -12
- package/src/validation/schema.validation.ts +76 -35
- package/types/constants.d.ts +12 -2
- package/types/index.d.ts +1 -1
- package/types/models.d.ts +16 -9
- package/types/schematic.d.ts +2 -3
- package/types/validation/schema.validation.d.ts +2 -2
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/array/compact.js +0 -12
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/is.js +0 -20
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/string.js +0 -24
- package/dist/node_modules/@oscarpalmer/atoms/dist/value/smush.js +0 -36
package/dist/constants.js
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
|
+
const ERROR_NAME = "SchematicError";
|
|
1
2
|
const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
2
3
|
const EXPRESSION_INDEX = /\.\d+$/;
|
|
3
4
|
const EXPRESSION_PROPERTY = /\.\$(required|type|validators)(\.|$)/;
|
|
5
|
+
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
6
|
+
const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
7
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
8
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
9
|
+
const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
|
|
10
|
+
const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
11
|
+
const MESSAGE_VALIDATOR_INVALID_TYPE = "Validators must be an object";
|
|
12
|
+
const MESSAGE_VALIDATOR_INVALID_VALUE = "Validator '<>' must be a function or an array of functions";
|
|
4
13
|
const PROPERTY_REQUIRED = "$required";
|
|
5
14
|
const PROPERTY_TYPE = "$type";
|
|
6
15
|
const PROPERTY_VALIDATORS = "$validators";
|
|
7
16
|
const SCHEMATIC_NAME = "$schematic";
|
|
17
|
+
const TEMPLATE_PATTERN = "<>";
|
|
8
18
|
const TYPE_OBJECT = "object";
|
|
9
19
|
const TYPE_UNDEFINED = "undefined";
|
|
10
|
-
const
|
|
20
|
+
const VALIDATABLE_TYPES = new Set([
|
|
11
21
|
"array",
|
|
12
22
|
"bigint",
|
|
13
23
|
"boolean",
|
|
14
24
|
"date",
|
|
15
25
|
"function",
|
|
16
|
-
"null",
|
|
17
26
|
"number",
|
|
18
27
|
"string",
|
|
19
28
|
"symbol",
|
|
20
|
-
TYPE_OBJECT
|
|
29
|
+
TYPE_OBJECT
|
|
30
|
+
]);
|
|
31
|
+
const TYPE_ALL = new Set([
|
|
32
|
+
...VALIDATABLE_TYPES,
|
|
33
|
+
"null",
|
|
21
34
|
TYPE_UNDEFINED
|
|
22
35
|
]);
|
|
23
|
-
export { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, SCHEMATIC_NAME, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED };
|
|
36
|
+
export { ERROR_NAME, EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, MESSAGE_CONSTRUCTOR, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, MESSAGE_SCHEMA_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_KEY, MESSAGE_VALIDATOR_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_VALUE, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, SCHEMATIC_NAME, TEMPLATE_PATTERN, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES };
|
package/dist/index.js
CHANGED
package/dist/is.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import "./constants.js";
|
|
1
|
+
import { MESSAGE_CONSTRUCTOR } from "./constants.js";
|
|
2
2
|
import { isConstructor } from "@oscarpalmer/atoms/is";
|
|
3
3
|
function isInstance(constructor) {
|
|
4
|
-
if (!isConstructor(constructor)) throw new TypeError(
|
|
4
|
+
if (!isConstructor(constructor)) throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
5
5
|
return (value) => {
|
|
6
6
|
return value instanceof constructor;
|
|
7
7
|
};
|
package/dist/jhunal.full.js
CHANGED
|
@@ -58,30 +58,43 @@ function getString(value) {
|
|
|
58
58
|
function join(value, delimiter) {
|
|
59
59
|
return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
|
|
60
60
|
}
|
|
61
|
+
const ERROR_NAME = "SchematicError";
|
|
61
62
|
const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
62
63
|
const EXPRESSION_INDEX = /\.\d+$/;
|
|
63
64
|
const EXPRESSION_PROPERTY = /\.\$(required|type|validators)(\.|$)/;
|
|
65
|
+
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
66
|
+
const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
67
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
68
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
69
|
+
const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
|
|
70
|
+
const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
71
|
+
const MESSAGE_VALIDATOR_INVALID_TYPE = "Validators must be an object";
|
|
72
|
+
const MESSAGE_VALIDATOR_INVALID_VALUE = "Validator '<>' must be a function or an array of functions";
|
|
64
73
|
const PROPERTY_REQUIRED = "$required";
|
|
65
74
|
const PROPERTY_TYPE = "$type";
|
|
66
75
|
const PROPERTY_VALIDATORS = "$validators";
|
|
67
76
|
const SCHEMATIC_NAME = "$schematic";
|
|
77
|
+
const TEMPLATE_PATTERN = "<>";
|
|
68
78
|
const TYPE_OBJECT = "object";
|
|
69
79
|
const TYPE_UNDEFINED = "undefined";
|
|
70
|
-
const
|
|
80
|
+
const VALIDATABLE_TYPES = new Set([
|
|
71
81
|
"array",
|
|
72
82
|
"bigint",
|
|
73
83
|
"boolean",
|
|
74
84
|
"date",
|
|
75
85
|
"function",
|
|
76
|
-
"null",
|
|
77
86
|
"number",
|
|
78
87
|
"string",
|
|
79
88
|
"symbol",
|
|
80
|
-
TYPE_OBJECT
|
|
89
|
+
TYPE_OBJECT
|
|
90
|
+
]);
|
|
91
|
+
const TYPE_ALL = new Set([
|
|
92
|
+
...VALIDATABLE_TYPES,
|
|
93
|
+
"null",
|
|
81
94
|
TYPE_UNDEFINED
|
|
82
95
|
]);
|
|
83
96
|
function isInstance(constructor) {
|
|
84
|
-
if (!isConstructor(constructor)) throw new TypeError(
|
|
97
|
+
if (!isConstructor(constructor)) throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
85
98
|
return (value) => {
|
|
86
99
|
return value instanceof constructor;
|
|
87
100
|
};
|
|
@@ -89,6 +102,12 @@ function isInstance(constructor) {
|
|
|
89
102
|
function isSchematic(value) {
|
|
90
103
|
return typeof value === "object" && value !== null && SCHEMATIC_NAME in value && value[SCHEMATIC_NAME] === true;
|
|
91
104
|
}
|
|
105
|
+
var SchematicError = class extends Error {
|
|
106
|
+
constructor(message) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = ERROR_NAME;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
92
111
|
function flattenObject(value, depth, smushed, prefix) {
|
|
93
112
|
if (depth >= MAX_DEPTH) return {};
|
|
94
113
|
if (smushed.has(value)) return smushed.get(value);
|
|
@@ -125,7 +144,7 @@ var MAX_DEPTH = 100;
|
|
|
125
144
|
function addPropertyType(to, key, values, validators, required) {
|
|
126
145
|
if (to.keys.set.has(key)) {
|
|
127
146
|
const property = to.properties[key];
|
|
128
|
-
for (const type of values)
|
|
147
|
+
for (const type of values) property.types.push(type);
|
|
129
148
|
} else {
|
|
130
149
|
to.keys.array.push(key);
|
|
131
150
|
to.keys.set.add(key);
|
|
@@ -139,15 +158,13 @@ function addPropertyType(to, key, values, validators, required) {
|
|
|
139
158
|
to.properties[key].validators = validators;
|
|
140
159
|
}
|
|
141
160
|
function getSchema(schema) {
|
|
142
|
-
|
|
143
|
-
enabled: false,
|
|
161
|
+
return getValidatedSchema(schema, {
|
|
144
162
|
keys: {
|
|
145
163
|
array: [],
|
|
146
164
|
set: /* @__PURE__ */ new Set()
|
|
147
165
|
},
|
|
148
166
|
properties: {}
|
|
149
|
-
};
|
|
150
|
-
return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
|
|
167
|
+
});
|
|
151
168
|
}
|
|
152
169
|
function getTypes(value, validated, prefix) {
|
|
153
170
|
const propertyTypes = [];
|
|
@@ -155,23 +172,24 @@ function getTypes(value, validated, prefix) {
|
|
|
155
172
|
const { length } = values;
|
|
156
173
|
for (let index = 0; index < length; index += 1) {
|
|
157
174
|
const type = values[index];
|
|
158
|
-
|
|
159
|
-
if (isSchematic(type) || typeOfType === "string" && TYPE_ALL.has(type)) {
|
|
175
|
+
if (isSchematic(type) || TYPE_ALL.has(type)) {
|
|
160
176
|
propertyTypes.push(type);
|
|
161
177
|
continue;
|
|
162
178
|
}
|
|
163
|
-
if (
|
|
179
|
+
if (typeof type === "function") {
|
|
164
180
|
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
165
181
|
continue;
|
|
166
182
|
}
|
|
167
|
-
if (
|
|
183
|
+
if (!isPlainObject(type)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, prefix));
|
|
168
184
|
if (PROPERTY_TYPE in type) {
|
|
169
185
|
propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
|
|
170
186
|
continue;
|
|
171
187
|
}
|
|
172
|
-
|
|
188
|
+
const { [PROPERTY_REQUIRED]: required, ...nested } = type;
|
|
189
|
+
if (PROPERTY_REQUIRED in type && typeof required !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, prefix));
|
|
190
|
+
addPropertyType(validated, prefix, [TYPE_OBJECT], {}, required !== false);
|
|
173
191
|
propertyTypes.push(TYPE_OBJECT);
|
|
174
|
-
getValidatedSchema(
|
|
192
|
+
getValidatedSchema(nested, validated, prefix);
|
|
175
193
|
}
|
|
176
194
|
return propertyTypes;
|
|
177
195
|
}
|
|
@@ -190,22 +208,35 @@ function getValidatedSchema(schema, validated, prefix) {
|
|
|
190
208
|
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
|
|
191
209
|
let required = true;
|
|
192
210
|
let validators = {};
|
|
193
|
-
const isObject =
|
|
194
|
-
if (isObject && PROPERTY_REQUIRED in value)
|
|
211
|
+
const isObject = isPlainObject(value);
|
|
212
|
+
if (isObject && PROPERTY_REQUIRED in value) {
|
|
213
|
+
if (typeof value[PROPERTY_REQUIRED] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key));
|
|
214
|
+
required = value[PROPERTY_REQUIRED] === true;
|
|
215
|
+
}
|
|
195
216
|
if (isObject && PROPERTY_VALIDATORS in value) validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
196
217
|
const prefixedKey = `${prefix}${key}`;
|
|
197
218
|
const types = getTypes(value, validated, prefixedKey);
|
|
198
|
-
if (types.length
|
|
219
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key));
|
|
220
|
+
addPropertyType(validated, prefixedKey, types, validators, required);
|
|
199
221
|
}
|
|
200
222
|
if (noPrefix) validated.keys.array.sort();
|
|
223
|
+
if (noPrefix && validated.keys.array.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
201
224
|
return validated;
|
|
202
225
|
}
|
|
203
226
|
function getValidators(original) {
|
|
204
227
|
const validators = {};
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
228
|
+
if (original == null) return validators;
|
|
229
|
+
if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
230
|
+
const keys = Object.keys(original);
|
|
231
|
+
const { length } = keys;
|
|
232
|
+
for (let index = 0; index < length; index += 1) {
|
|
233
|
+
const key = keys[index];
|
|
234
|
+
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
|
|
235
|
+
const value = original[key];
|
|
236
|
+
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
237
|
+
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
|
|
238
|
+
return true;
|
|
239
|
+
});
|
|
209
240
|
}
|
|
210
241
|
return validators;
|
|
211
242
|
}
|
|
@@ -264,22 +295,19 @@ const validators = {
|
|
|
264
295
|
*/
|
|
265
296
|
var Schematic = class {
|
|
266
297
|
#schema;
|
|
267
|
-
get enabled() {
|
|
268
|
-
return this.#schema.enabled;
|
|
269
|
-
}
|
|
270
298
|
constructor(schema) {
|
|
271
299
|
Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
|
|
272
|
-
this.#schema =
|
|
273
|
-
this.#schema.enabled = this.#schema.keys.array.length > 0;
|
|
300
|
+
this.#schema = schema;
|
|
274
301
|
}
|
|
275
302
|
/**
|
|
276
303
|
* Does the value match the schema?
|
|
277
304
|
*/
|
|
278
305
|
is(value) {
|
|
279
|
-
return
|
|
306
|
+
return validateValue(this.#schema, value);
|
|
280
307
|
}
|
|
281
308
|
};
|
|
282
309
|
function schematic(schema) {
|
|
283
|
-
|
|
310
|
+
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
311
|
+
return new Schematic(getSchema(schema));
|
|
284
312
|
}
|
|
285
|
-
export { isInstance, schematic };
|
|
313
|
+
export { SchematicError, isInstance, schematic };
|
package/dist/models.js
CHANGED
package/dist/schematic.js
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
import { SCHEMATIC_NAME } from "./constants.js";
|
|
1
|
+
import { MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME } from "./constants.js";
|
|
2
|
+
import { SchematicError } from "./models.js";
|
|
2
3
|
import { getSchema } from "./validation/schema.validation.js";
|
|
3
4
|
import { validateValue } from "./validation/value.validation.js";
|
|
5
|
+
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
4
6
|
/**
|
|
5
7
|
* A schematic for validating objects
|
|
6
8
|
*/
|
|
7
9
|
var Schematic = class {
|
|
8
10
|
#schema;
|
|
9
|
-
get enabled() {
|
|
10
|
-
return this.#schema.enabled;
|
|
11
|
-
}
|
|
12
11
|
constructor(schema) {
|
|
13
12
|
Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
|
|
14
|
-
this.#schema =
|
|
15
|
-
this.#schema.enabled = this.#schema.keys.array.length > 0;
|
|
13
|
+
this.#schema = schema;
|
|
16
14
|
}
|
|
17
15
|
/**
|
|
18
16
|
* Does the value match the schema?
|
|
19
17
|
*/
|
|
20
18
|
is(value) {
|
|
21
|
-
return
|
|
19
|
+
return validateValue(this.#schema, value);
|
|
22
20
|
}
|
|
23
21
|
};
|
|
24
22
|
function schematic(schema) {
|
|
25
|
-
|
|
23
|
+
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
24
|
+
return new Schematic(getSchema(schema));
|
|
26
25
|
}
|
|
27
26
|
export { Schematic, schematic };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED } from "../constants.js";
|
|
1
|
+
import { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, MESSAGE_VALIDATOR_INVALID_KEY, MESSAGE_VALIDATOR_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_VALUE, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES } from "../constants.js";
|
|
2
2
|
import { isInstance, isSchematic } from "../is.js";
|
|
3
|
-
import {
|
|
4
|
-
import { isConstructor } from "@oscarpalmer/atoms/is";
|
|
3
|
+
import { SchematicError } from "../models.js";
|
|
4
|
+
import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
|
|
5
|
+
import { smush } from "@oscarpalmer/atoms/value/misc";
|
|
5
6
|
function addPropertyType(to, key, values, validators, required) {
|
|
6
7
|
if (to.keys.set.has(key)) {
|
|
7
8
|
const property = to.properties[key];
|
|
8
|
-
for (const type of values)
|
|
9
|
+
for (const type of values) property.types.push(type);
|
|
9
10
|
} else {
|
|
10
11
|
to.keys.array.push(key);
|
|
11
12
|
to.keys.set.add(key);
|
|
@@ -19,15 +20,13 @@ function addPropertyType(to, key, values, validators, required) {
|
|
|
19
20
|
to.properties[key].validators = validators;
|
|
20
21
|
}
|
|
21
22
|
function getSchema(schema) {
|
|
22
|
-
|
|
23
|
-
enabled: false,
|
|
23
|
+
return getValidatedSchema(schema, {
|
|
24
24
|
keys: {
|
|
25
25
|
array: [],
|
|
26
26
|
set: /* @__PURE__ */ new Set()
|
|
27
27
|
},
|
|
28
28
|
properties: {}
|
|
29
|
-
};
|
|
30
|
-
return typeof schema === "object" && schema !== null ? getValidatedSchema(schema, validated) : validated;
|
|
29
|
+
});
|
|
31
30
|
}
|
|
32
31
|
function getTypes(value, validated, prefix) {
|
|
33
32
|
const propertyTypes = [];
|
|
@@ -35,23 +34,24 @@ function getTypes(value, validated, prefix) {
|
|
|
35
34
|
const { length } = values;
|
|
36
35
|
for (let index = 0; index < length; index += 1) {
|
|
37
36
|
const type = values[index];
|
|
38
|
-
|
|
39
|
-
if (isSchematic(type) || typeOfType === "string" && TYPE_ALL.has(type)) {
|
|
37
|
+
if (isSchematic(type) || TYPE_ALL.has(type)) {
|
|
40
38
|
propertyTypes.push(type);
|
|
41
39
|
continue;
|
|
42
40
|
}
|
|
43
|
-
if (
|
|
41
|
+
if (typeof type === "function") {
|
|
44
42
|
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
45
43
|
continue;
|
|
46
44
|
}
|
|
47
|
-
if (
|
|
45
|
+
if (!isPlainObject(type)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", prefix));
|
|
48
46
|
if ("$type" in type) {
|
|
49
47
|
propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
|
|
50
48
|
continue;
|
|
51
49
|
}
|
|
52
|
-
|
|
50
|
+
const { [PROPERTY_REQUIRED]: required, ...nested } = type;
|
|
51
|
+
if ("$required" in type && typeof required !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", prefix));
|
|
52
|
+
addPropertyType(validated, prefix, [TYPE_OBJECT], {}, required !== false);
|
|
53
53
|
propertyTypes.push(TYPE_OBJECT);
|
|
54
|
-
getValidatedSchema(
|
|
54
|
+
getValidatedSchema(nested, validated, prefix);
|
|
55
55
|
}
|
|
56
56
|
return propertyTypes;
|
|
57
57
|
}
|
|
@@ -70,22 +70,35 @@ function getValidatedSchema(schema, validated, prefix) {
|
|
|
70
70
|
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
|
|
71
71
|
let required = true;
|
|
72
72
|
let validators = {};
|
|
73
|
-
const isObject =
|
|
74
|
-
if (isObject && "$required" in value)
|
|
73
|
+
const isObject = isPlainObject(value);
|
|
74
|
+
if (isObject && "$required" in value) {
|
|
75
|
+
if (typeof value["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
|
|
76
|
+
required = value[PROPERTY_REQUIRED] === true;
|
|
77
|
+
}
|
|
75
78
|
if (isObject && "$validators" in value) validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
76
79
|
const prefixedKey = `${prefix}${key}`;
|
|
77
80
|
const types = getTypes(value, validated, prefixedKey);
|
|
78
|
-
if (types.length
|
|
81
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", key));
|
|
82
|
+
addPropertyType(validated, prefixedKey, types, validators, required);
|
|
79
83
|
}
|
|
80
84
|
if (noPrefix) validated.keys.array.sort();
|
|
85
|
+
if (noPrefix && validated.keys.array.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
81
86
|
return validated;
|
|
82
87
|
}
|
|
83
88
|
function getValidators(original) {
|
|
84
89
|
const validators = {};
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
if (original == null) return validators;
|
|
91
|
+
if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
92
|
+
const keys = Object.keys(original);
|
|
93
|
+
const { length } = keys;
|
|
94
|
+
for (let index = 0; index < length; index += 1) {
|
|
95
|
+
const key = keys[index];
|
|
96
|
+
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
|
|
97
|
+
const value = original[key];
|
|
98
|
+
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
99
|
+
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
|
|
100
|
+
return true;
|
|
101
|
+
});
|
|
89
102
|
}
|
|
90
103
|
return validators;
|
|
91
104
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../constants.js";
|
|
2
|
-
import { smush } from "
|
|
2
|
+
import { smush } from "@oscarpalmer/atoms/value/misc";
|
|
3
3
|
function validateType(type, property, value) {
|
|
4
4
|
switch (true) {
|
|
5
5
|
case typeof type === "function": return type(value);
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {ValueName} from './models';
|
|
2
|
+
|
|
3
|
+
export const ERROR_NAME = 'SchematicError';
|
|
2
4
|
|
|
3
5
|
export const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
4
6
|
|
|
@@ -6,6 +8,23 @@ export const EXPRESSION_INDEX = /\.\d+$/;
|
|
|
6
8
|
|
|
7
9
|
export const EXPRESSION_PROPERTY = /\.\$(required|type|validators)(\.|$)/;
|
|
8
10
|
|
|
11
|
+
export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
|
|
12
|
+
|
|
13
|
+
export const MESSAGE_SCHEMA_INVALID_EMPTY = 'Schema must have at least one property';
|
|
14
|
+
|
|
15
|
+
export const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
16
|
+
|
|
17
|
+
export const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
18
|
+
|
|
19
|
+
export const MESSAGE_SCHEMA_INVALID_TYPE = 'Schema must be an object';
|
|
20
|
+
|
|
21
|
+
export const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
22
|
+
|
|
23
|
+
export const MESSAGE_VALIDATOR_INVALID_TYPE = 'Validators must be an object';
|
|
24
|
+
|
|
25
|
+
export const MESSAGE_VALIDATOR_INVALID_VALUE =
|
|
26
|
+
"Validator '<>' must be a function or an array of functions";
|
|
27
|
+
|
|
9
28
|
export const PROPERTY_REQUIRED = '$required';
|
|
10
29
|
|
|
11
30
|
export const PROPERTY_TYPE = '$type';
|
|
@@ -14,20 +33,22 @@ export const PROPERTY_VALIDATORS = '$validators';
|
|
|
14
33
|
|
|
15
34
|
export const SCHEMATIC_NAME = '$schematic';
|
|
16
35
|
|
|
36
|
+
export const TEMPLATE_PATTERN = '<>';
|
|
37
|
+
|
|
17
38
|
export const TYPE_OBJECT = 'object';
|
|
18
39
|
|
|
19
40
|
export const TYPE_UNDEFINED = 'undefined';
|
|
20
41
|
|
|
21
|
-
export const
|
|
42
|
+
export const VALIDATABLE_TYPES = new Set<ValueName>([
|
|
22
43
|
'array',
|
|
23
44
|
'bigint',
|
|
24
45
|
'boolean',
|
|
25
46
|
'date',
|
|
26
47
|
'function',
|
|
27
|
-
'null',
|
|
28
48
|
'number',
|
|
29
49
|
'string',
|
|
30
50
|
'symbol',
|
|
31
51
|
TYPE_OBJECT,
|
|
32
|
-
TYPE_UNDEFINED,
|
|
33
52
|
]);
|
|
53
|
+
|
|
54
|
+
export const TYPE_ALL = new Set<ValueName>([...VALIDATABLE_TYPES, 'null', TYPE_UNDEFINED]);
|
package/src/index.ts
CHANGED
package/src/is.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {isConstructor} from '@oscarpalmer/atoms/is';
|
|
2
|
-
import {SCHEMATIC_NAME} from './constants';
|
|
2
|
+
import {MESSAGE_CONSTRUCTOR, SCHEMATIC_NAME} from './constants';
|
|
3
3
|
import type {Constructor} from './models';
|
|
4
4
|
import type {Schematic} from './schematic';
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ export function isInstance<Instance>(
|
|
|
7
7
|
constructor: Constructor<Instance>,
|
|
8
8
|
): (value: unknown) => value is Instance {
|
|
9
9
|
if (!isConstructor(constructor)) {
|
|
10
|
-
throw new TypeError(
|
|
10
|
+
throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
return (value: unknown): value is Instance => {
|
package/src/models.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import {ERROR_NAME} from './constants';
|
|
2
3
|
import type {Schematic} from './schematic';
|
|
3
4
|
|
|
4
5
|
export type Constructor<Instance = any> = new (...args: any[]) => Instance;
|
|
@@ -65,17 +66,23 @@ type InferSchemaEntryValue<Value> =
|
|
|
65
66
|
? Model
|
|
66
67
|
: Value extends SchemaProperty
|
|
67
68
|
? InferPropertyType<Value['$type']>
|
|
68
|
-
: Value extends
|
|
69
|
-
?
|
|
70
|
-
: Value extends
|
|
71
|
-
?
|
|
72
|
-
:
|
|
69
|
+
: Value extends NestedSchema
|
|
70
|
+
? Infer<Omit<Value, '$required'>>
|
|
71
|
+
: Value extends ValueName
|
|
72
|
+
? Values[Value & ValueName]
|
|
73
|
+
: Value extends Schema
|
|
74
|
+
? Infer<Value>
|
|
75
|
+
: never;
|
|
73
76
|
|
|
74
77
|
type IsOptionalProperty<Value> = Value extends SchemaProperty
|
|
75
78
|
? Value['$required'] extends false
|
|
76
79
|
? true
|
|
77
80
|
: false
|
|
78
|
-
:
|
|
81
|
+
: Value extends {$required?: boolean}
|
|
82
|
+
? Value extends {$required: false}
|
|
83
|
+
? true
|
|
84
|
+
: false
|
|
85
|
+
: false;
|
|
79
86
|
|
|
80
87
|
type LastOfUnion<Value> =
|
|
81
88
|
UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item
|
|
@@ -95,7 +102,8 @@ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer He
|
|
|
95
102
|
*/
|
|
96
103
|
export type NestedSchema = {
|
|
97
104
|
$required?: boolean;
|
|
98
|
-
|
|
105
|
+
[key: string]: any;
|
|
106
|
+
};
|
|
99
107
|
|
|
100
108
|
type OptionalKeys<Value> = {
|
|
101
109
|
[Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
|
|
@@ -114,7 +122,7 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
114
122
|
*/
|
|
115
123
|
export type Schema = SchemaIndex;
|
|
116
124
|
|
|
117
|
-
type SchemaEntry = Constructor |
|
|
125
|
+
type SchemaEntry = Constructor | SchemaProperty | Schematic<unknown> | ValueName | NestedSchema;
|
|
118
126
|
|
|
119
127
|
interface SchemaIndex {
|
|
120
128
|
[key: string]: SchemaEntry | SchemaEntry[];
|
|
@@ -131,13 +139,23 @@ export type SchemaProperty = {
|
|
|
131
139
|
|
|
132
140
|
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
|
|
133
141
|
|
|
142
|
+
export class SchematicError extends Error {
|
|
143
|
+
constructor(message: string) {
|
|
144
|
+
super(message);
|
|
145
|
+
|
|
146
|
+
this.name = ERROR_NAME;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
134
150
|
type ToSchemaPropertyType<Value> = UnwrapSingle<
|
|
135
151
|
DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>
|
|
136
152
|
>;
|
|
137
153
|
|
|
138
|
-
type ToSchemaPropertyTypeEach<Value> = Value extends
|
|
139
|
-
?
|
|
140
|
-
:
|
|
154
|
+
type ToSchemaPropertyTypeEach<Value> = Value extends NestedSchema
|
|
155
|
+
? Omit<Value, '$required'>
|
|
156
|
+
: Value extends PlainObject
|
|
157
|
+
? TypedSchema<Value>
|
|
158
|
+
: ToValueType<Value>;
|
|
141
159
|
|
|
142
160
|
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
143
161
|
|
|
@@ -149,21 +167,23 @@ type ToValueType<Value> = Value extends unknown[]
|
|
|
149
167
|
? 'boolean'
|
|
150
168
|
: Value extends Date
|
|
151
169
|
? 'date'
|
|
152
|
-
: Value extends
|
|
153
|
-
?
|
|
154
|
-
: Value extends
|
|
155
|
-
? '
|
|
156
|
-
: Value extends
|
|
157
|
-
? '
|
|
158
|
-
: Value extends
|
|
159
|
-
? '
|
|
160
|
-
: Value extends
|
|
161
|
-
? '
|
|
162
|
-
: Value extends
|
|
163
|
-
? '
|
|
164
|
-
: Value extends
|
|
165
|
-
? '
|
|
166
|
-
:
|
|
170
|
+
: Value extends Schematic<any>
|
|
171
|
+
? Value
|
|
172
|
+
: Value extends Function
|
|
173
|
+
? 'function'
|
|
174
|
+
: Value extends null
|
|
175
|
+
? 'null'
|
|
176
|
+
: Value extends number
|
|
177
|
+
? 'number'
|
|
178
|
+
: Value extends object
|
|
179
|
+
? 'object' | ((value: unknown) => value is Value)
|
|
180
|
+
: Value extends string
|
|
181
|
+
? 'string'
|
|
182
|
+
: Value extends symbol
|
|
183
|
+
? 'symbol'
|
|
184
|
+
: Value extends undefined
|
|
185
|
+
? 'undefined'
|
|
186
|
+
: (value: unknown) => value is Value;
|
|
167
187
|
|
|
168
188
|
type TuplePermutations<
|
|
169
189
|
Tuple extends unknown[],
|
|
@@ -223,11 +243,13 @@ export type TypedPropertyRequired<Value> = {
|
|
|
223
243
|
export type TypedSchema<Model extends PlainObject> = Simplify<
|
|
224
244
|
{
|
|
225
245
|
[Key in RequiredKeys<Model>]: Model[Key] extends PlainObject
|
|
226
|
-
? TypedSchemaRequired<Model[Key]>
|
|
246
|
+
? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]>
|
|
227
247
|
: ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
|
|
228
248
|
} & {
|
|
229
249
|
[Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject
|
|
230
|
-
?
|
|
250
|
+
?
|
|
251
|
+
| TypedSchemaOptional<Exclude<Model[Key], undefined>>
|
|
252
|
+
| Schematic<Exclude<Model[Key], undefined>>
|
|
231
253
|
: TypedPropertyOptional<Model[Key]>;
|
|
232
254
|
}
|
|
233
255
|
>;
|
|
@@ -269,7 +291,6 @@ export type ValidatedPropertyValidators = {
|
|
|
269
291
|
};
|
|
270
292
|
|
|
271
293
|
export type ValidatedSchema = {
|
|
272
|
-
enabled: boolean;
|
|
273
294
|
keys: {
|
|
274
295
|
array: string[];
|
|
275
296
|
set: Set<string>;
|
package/src/schematic.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
1
2
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
|
-
import {SCHEMATIC_NAME} from './constants';
|
|
3
|
-
import
|
|
3
|
+
import {MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME} from './constants';
|
|
4
|
+
import {
|
|
5
|
+
SchematicError,
|
|
6
|
+
type Infer,
|
|
7
|
+
type Schema,
|
|
8
|
+
type TypedSchema,
|
|
9
|
+
type ValidatedSchema,
|
|
10
|
+
} from './models';
|
|
4
11
|
import {getSchema} from './validation/schema.validation';
|
|
5
12
|
import {validateValue} from './validation/value.validation';
|
|
6
13
|
|
|
@@ -12,25 +19,19 @@ export class Schematic<Model> {
|
|
|
12
19
|
|
|
13
20
|
#schema: ValidatedSchema;
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
return this.#schema.enabled;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
constructor(schema: Model) {
|
|
22
|
+
constructor(schema: ValidatedSchema) {
|
|
20
23
|
Object.defineProperty(this, SCHEMATIC_NAME, {
|
|
21
24
|
value: true,
|
|
22
25
|
});
|
|
23
26
|
|
|
24
|
-
this.#schema =
|
|
25
|
-
|
|
26
|
-
this.#schema.enabled = this.#schema.keys.array.length > 0;
|
|
27
|
+
this.#schema = schema;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Does the value match the schema?
|
|
31
32
|
*/
|
|
32
33
|
is(value: unknown): value is Model {
|
|
33
|
-
return
|
|
34
|
+
return validateValue(this.#schema, value);
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -45,5 +46,11 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<
|
|
|
45
46
|
export function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
46
47
|
|
|
47
48
|
export function schematic<Model extends Schema>(schema: Model): Schematic<Model> {
|
|
48
|
-
|
|
49
|
+
if (!isPlainObject(schema)) {
|
|
50
|
+
throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const validated = getSchema(schema);
|
|
54
|
+
|
|
55
|
+
return new Schematic<Model>(validated);
|
|
49
56
|
}
|
|
@@ -1,23 +1,33 @@
|
|
|
1
|
-
import {isConstructor} from '@oscarpalmer/atoms/is';
|
|
1
|
+
import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
3
3
|
import {smush} from '@oscarpalmer/atoms/value/misc';
|
|
4
4
|
import {
|
|
5
5
|
EXPRESSION_HAS_NUMBER,
|
|
6
6
|
EXPRESSION_INDEX,
|
|
7
7
|
EXPRESSION_PROPERTY,
|
|
8
|
+
MESSAGE_SCHEMA_INVALID_EMPTY,
|
|
9
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
|
|
10
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
|
|
11
|
+
MESSAGE_VALIDATOR_INVALID_KEY,
|
|
12
|
+
MESSAGE_VALIDATOR_INVALID_TYPE,
|
|
13
|
+
MESSAGE_VALIDATOR_INVALID_VALUE,
|
|
8
14
|
PROPERTY_REQUIRED,
|
|
9
15
|
PROPERTY_TYPE,
|
|
10
16
|
PROPERTY_VALIDATORS,
|
|
17
|
+
TEMPLATE_PATTERN,
|
|
11
18
|
TYPE_ALL,
|
|
12
19
|
TYPE_OBJECT,
|
|
13
20
|
TYPE_UNDEFINED,
|
|
21
|
+
VALIDATABLE_TYPES,
|
|
14
22
|
} from '../constants';
|
|
15
23
|
import {isInstance, isSchematic} from '../is';
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
import {
|
|
25
|
+
SchematicError,
|
|
26
|
+
type Schema,
|
|
27
|
+
type ValidatedPropertyType,
|
|
28
|
+
type ValidatedPropertyValidators,
|
|
29
|
+
type ValidatedSchema,
|
|
30
|
+
type ValueName,
|
|
21
31
|
} from '../models';
|
|
22
32
|
|
|
23
33
|
function addPropertyType(
|
|
@@ -31,9 +41,7 @@ function addPropertyType(
|
|
|
31
41
|
const property = to.properties[key];
|
|
32
42
|
|
|
33
43
|
for (const type of values) {
|
|
34
|
-
|
|
35
|
-
property.types.push(type);
|
|
36
|
-
}
|
|
44
|
+
property.types.push(type);
|
|
37
45
|
}
|
|
38
46
|
} else {
|
|
39
47
|
to.keys.array.push(key);
|
|
@@ -53,19 +61,14 @@ function addPropertyType(
|
|
|
53
61
|
to.properties[key].validators = validators;
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
export function getSchema(schema:
|
|
57
|
-
|
|
58
|
-
enabled: false,
|
|
64
|
+
export function getSchema(schema: Schema): ValidatedSchema {
|
|
65
|
+
return getValidatedSchema(schema, {
|
|
59
66
|
keys: {
|
|
60
67
|
array: [],
|
|
61
68
|
set: new Set<string>(),
|
|
62
69
|
},
|
|
63
70
|
properties: {},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
return typeof schema === 'object' && schema !== null
|
|
67
|
-
? getValidatedSchema(schema as Schema, validated)
|
|
68
|
-
: validated;
|
|
71
|
+
});
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
function getTypes(
|
|
@@ -80,22 +83,23 @@ function getTypes(
|
|
|
80
83
|
|
|
81
84
|
for (let index = 0; index < length; index += 1) {
|
|
82
85
|
const type = values[index];
|
|
83
|
-
const typeOfType = typeof type;
|
|
84
86
|
|
|
85
|
-
if (isSchematic(type) ||
|
|
86
|
-
propertyTypes.push(type
|
|
87
|
+
if (isSchematic(type) || TYPE_ALL.has(type as never)) {
|
|
88
|
+
propertyTypes.push(type);
|
|
87
89
|
|
|
88
90
|
continue;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
if (
|
|
93
|
+
if (typeof type === 'function') {
|
|
92
94
|
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
93
95
|
|
|
94
96
|
continue;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
+
if (!isPlainObject(type)) {
|
|
100
|
+
throw new SchematicError(
|
|
101
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, prefix),
|
|
102
|
+
);
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
if (PROPERTY_TYPE in type) {
|
|
@@ -104,11 +108,19 @@ function getTypes(
|
|
|
104
108
|
continue;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
const {[PROPERTY_REQUIRED]: required, ...nested} = type;
|
|
112
|
+
|
|
113
|
+
if (PROPERTY_REQUIRED in type && typeof required !== 'boolean') {
|
|
114
|
+
throw new SchematicError(
|
|
115
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, prefix),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
addPropertyType(validated, prefix, [TYPE_OBJECT], {}, required !== false);
|
|
108
120
|
|
|
109
121
|
propertyTypes.push(TYPE_OBJECT);
|
|
110
122
|
|
|
111
|
-
getValidatedSchema(
|
|
123
|
+
getValidatedSchema(nested as Schema, validated, prefix);
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
return propertyTypes;
|
|
@@ -147,10 +159,16 @@ function getValidatedSchema(
|
|
|
147
159
|
let required = true;
|
|
148
160
|
let validators: ValidatedPropertyValidators = {};
|
|
149
161
|
|
|
150
|
-
const isObject =
|
|
162
|
+
const isObject = isPlainObject(value);
|
|
151
163
|
|
|
152
164
|
if (isObject && PROPERTY_REQUIRED in value) {
|
|
153
|
-
|
|
165
|
+
if (typeof value[PROPERTY_REQUIRED] !== 'boolean') {
|
|
166
|
+
throw new SchematicError(
|
|
167
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
required = value[PROPERTY_REQUIRED] === true;
|
|
154
172
|
}
|
|
155
173
|
|
|
156
174
|
if (isObject && PROPERTY_VALIDATORS in value) {
|
|
@@ -161,31 +179,54 @@ function getValidatedSchema(
|
|
|
161
179
|
|
|
162
180
|
const types = getTypes(value, validated, prefixedKey);
|
|
163
181
|
|
|
164
|
-
if (types.length
|
|
165
|
-
|
|
182
|
+
if (types.length === 0) {
|
|
183
|
+
throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key));
|
|
166
184
|
}
|
|
185
|
+
|
|
186
|
+
addPropertyType(validated, prefixedKey, types, validators, required);
|
|
167
187
|
}
|
|
168
188
|
|
|
169
189
|
if (noPrefix) {
|
|
170
190
|
validated.keys.array.sort();
|
|
171
191
|
}
|
|
172
192
|
|
|
193
|
+
if (noPrefix && validated.keys.array.length === 0) {
|
|
194
|
+
throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
195
|
+
}
|
|
196
|
+
|
|
173
197
|
return validated;
|
|
174
198
|
}
|
|
175
199
|
|
|
176
200
|
function getValidators(original: unknown): ValidatedPropertyValidators {
|
|
177
201
|
const validators: ValidatedPropertyValidators = {};
|
|
178
202
|
|
|
179
|
-
if (
|
|
203
|
+
if (original == null) {
|
|
180
204
|
return validators;
|
|
181
205
|
}
|
|
182
206
|
|
|
183
|
-
|
|
184
|
-
|
|
207
|
+
if (!isPlainObject(original)) {
|
|
208
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const keys = Object.keys(original);
|
|
212
|
+
const {length} = keys;
|
|
213
|
+
|
|
214
|
+
for (let index = 0; index < length; index += 1) {
|
|
215
|
+
const key = keys[index];
|
|
216
|
+
|
|
217
|
+
if (!VALIDATABLE_TYPES.has(key as never)) {
|
|
218
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const value = (original as PlainObject)[key];
|
|
222
|
+
|
|
223
|
+
validators[key as ValueName] = (Array.isArray(value) ? value : [value]).filter(item => {
|
|
224
|
+
if (typeof item !== 'function') {
|
|
225
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
|
|
226
|
+
}
|
|
185
227
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
189
230
|
}
|
|
190
231
|
|
|
191
232
|
return validators;
|
package/types/constants.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
export declare const ERROR_NAME = "SchematicError";
|
|
2
2
|
export declare const EXPRESSION_HAS_NUMBER: RegExp;
|
|
3
3
|
export declare const EXPRESSION_INDEX: RegExp;
|
|
4
4
|
export declare const EXPRESSION_PROPERTY: RegExp;
|
|
5
|
+
export declare const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
6
|
+
export declare const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
7
|
+
export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
8
|
+
export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
9
|
+
export declare const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
|
|
10
|
+
export declare const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
11
|
+
export declare const MESSAGE_VALIDATOR_INVALID_TYPE = "Validators must be an object";
|
|
12
|
+
export declare const MESSAGE_VALIDATOR_INVALID_VALUE = "Validator '<>' must be a function or an array of functions";
|
|
5
13
|
export declare const PROPERTY_REQUIRED = "$required";
|
|
6
14
|
export declare const PROPERTY_TYPE = "$type";
|
|
7
15
|
export declare const PROPERTY_VALIDATORS = "$validators";
|
|
8
16
|
export declare const SCHEMATIC_NAME = "$schematic";
|
|
17
|
+
export declare const TEMPLATE_PATTERN = "<>";
|
|
9
18
|
export declare const TYPE_OBJECT = "object";
|
|
10
19
|
export declare const TYPE_UNDEFINED = "undefined";
|
|
11
|
-
export declare const
|
|
20
|
+
export declare const VALIDATABLE_TYPES: Set<keyof import("./models").Values>;
|
|
21
|
+
export declare const TYPE_ALL: Set<keyof import("./models").Values>;
|
package/types/index.d.ts
CHANGED
package/types/models.d.ts
CHANGED
|
@@ -23,8 +23,12 @@ type InferRequiredKeys<Model extends Schema> = keyof {
|
|
|
23
23
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
|
|
24
24
|
};
|
|
25
25
|
type InferSchemaEntry<Value> = Value extends (infer Item)[] ? InferSchemaEntryValue<Item> : InferSchemaEntryValue<Value>;
|
|
26
|
-
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
|
|
27
|
-
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false :
|
|
26
|
+
type InferSchemaEntryValue<Value> = Value extends Constructor<infer Instance> ? Instance : Value extends Schematic<infer Model> ? Model : Value extends SchemaProperty ? InferPropertyType<Value['$type']> : Value extends NestedSchema ? Infer<Omit<Value, '$required'>> : Value extends ValueName ? Values[Value & ValueName] : Value extends Schema ? Infer<Value> : never;
|
|
27
|
+
type IsOptionalProperty<Value> = Value extends SchemaProperty ? Value['$required'] extends false ? true : false : Value extends {
|
|
28
|
+
$required?: boolean;
|
|
29
|
+
} ? Value extends {
|
|
30
|
+
$required: false;
|
|
31
|
+
} ? true : false : false;
|
|
28
32
|
type LastOfUnion<Value> = UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item ? Item : never;
|
|
29
33
|
type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToValueType<Head>, ...MapToValueTypes<Tail>] : [];
|
|
30
34
|
type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail] ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>] : [];
|
|
@@ -33,7 +37,8 @@ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer He
|
|
|
33
37
|
*/
|
|
34
38
|
export type NestedSchema = {
|
|
35
39
|
$required?: boolean;
|
|
36
|
-
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
};
|
|
37
42
|
type OptionalKeys<Value> = {
|
|
38
43
|
[Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
|
|
39
44
|
}[keyof Value];
|
|
@@ -45,7 +50,7 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
45
50
|
* A schema for validating objects
|
|
46
51
|
*/
|
|
47
52
|
export type Schema = SchemaIndex;
|
|
48
|
-
type SchemaEntry = Constructor |
|
|
53
|
+
type SchemaEntry = Constructor | SchemaProperty | Schematic<unknown> | ValueName | NestedSchema;
|
|
49
54
|
interface SchemaIndex {
|
|
50
55
|
[key: string]: SchemaEntry | SchemaEntry[];
|
|
51
56
|
}
|
|
@@ -58,10 +63,13 @@ export type SchemaProperty = {
|
|
|
58
63
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
59
64
|
};
|
|
60
65
|
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
|
|
66
|
+
export declare class SchematicError extends Error {
|
|
67
|
+
constructor(message: string);
|
|
68
|
+
}
|
|
61
69
|
type ToSchemaPropertyType<Value> = UnwrapSingle<DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>>;
|
|
62
|
-
type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
|
|
70
|
+
type ToSchemaPropertyTypeEach<Value> = Value extends NestedSchema ? Omit<Value, '$required'> : Value extends PlainObject ? TypedSchema<Value> : ToValueType<Value>;
|
|
63
71
|
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
64
|
-
type ToValueType<Value> = Value extends unknown[] ? 'array' : Value extends bigint ? 'bigint' : Value extends boolean ? 'boolean' : Value extends Date ? 'date' : Value extends Function ? 'function' : Value extends null ? 'null' : Value extends number ? 'number' : Value extends object ? 'object' | ((value: unknown) => value is Value) : Value extends string ? 'string' : Value extends symbol ? 'symbol' : Value extends undefined ? 'undefined' : (value: unknown) => value is Value;
|
|
72
|
+
type ToValueType<Value> = Value extends unknown[] ? 'array' : Value extends bigint ? 'bigint' : Value extends boolean ? 'boolean' : Value extends Date ? 'date' : Value extends Schematic<any> ? Value : Value extends Function ? 'function' : Value extends null ? 'null' : Value extends number ? 'number' : Value extends object ? 'object' | ((value: unknown) => value is Value) : Value extends string ? 'string' : Value extends symbol ? 'symbol' : Value extends undefined ? 'undefined' : (value: unknown) => value is Value;
|
|
65
73
|
type TuplePermutations<Tuple extends unknown[], Elput extends unknown[] = []> = Tuple['length'] extends 0 ? Elput : {
|
|
66
74
|
[Key in keyof Tuple]: TuplePermutations<TupleRemoveAt<Tuple, Key & `${number}`>, [
|
|
67
75
|
...Elput,
|
|
@@ -101,9 +109,9 @@ export type TypedPropertyRequired<Value> = {
|
|
|
101
109
|
* Create a schema type constrained to match a TypeScript type
|
|
102
110
|
*/
|
|
103
111
|
export type TypedSchema<Model extends PlainObject> = Simplify<{
|
|
104
|
-
[Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
|
|
112
|
+
[Key in RequiredKeys<Model>]: Model[Key] extends PlainObject ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]> : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
|
|
105
113
|
} & {
|
|
106
|
-
[Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]>;
|
|
114
|
+
[Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject ? TypedSchemaOptional<Exclude<Model[Key], undefined>> | Schematic<Exclude<Model[Key], undefined>> : TypedPropertyOptional<Model[Key]>;
|
|
107
115
|
}>;
|
|
108
116
|
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
109
117
|
$required: false;
|
|
@@ -124,7 +132,6 @@ export type ValidatedPropertyValidators = {
|
|
|
124
132
|
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
125
133
|
};
|
|
126
134
|
export type ValidatedSchema = {
|
|
127
|
-
enabled: boolean;
|
|
128
135
|
keys: {
|
|
129
136
|
array: string[];
|
|
130
137
|
set: Set<string>;
|
package/types/schematic.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { PlainObject } from '@oscarpalmer/atoms/models';
|
|
2
|
-
import type
|
|
2
|
+
import { type Infer, type Schema, type TypedSchema, type ValidatedSchema } from './models';
|
|
3
3
|
/**
|
|
4
4
|
* A schematic for validating objects
|
|
5
5
|
*/
|
|
6
6
|
export declare class Schematic<Model> {
|
|
7
7
|
#private;
|
|
8
8
|
private readonly $schematic;
|
|
9
|
-
|
|
10
|
-
constructor(schema: Model);
|
|
9
|
+
constructor(schema: ValidatedSchema);
|
|
11
10
|
/**
|
|
12
11
|
* Does the value match the schema?
|
|
13
12
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export declare function getSchema(schema:
|
|
1
|
+
import { type Schema, type ValidatedSchema } from '../models';
|
|
2
|
+
export declare function getSchema(schema: Schema): ValidatedSchema;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
function compact(array, strict) {
|
|
2
|
-
if (!Array.isArray(array)) return [];
|
|
3
|
-
if (strict === true) return array.filter(Boolean);
|
|
4
|
-
const { length } = array;
|
|
5
|
-
const compacted = [];
|
|
6
|
-
for (let index = 0; index < length; index += 1) {
|
|
7
|
-
const item = array[index];
|
|
8
|
-
if (item != null) compacted.push(item);
|
|
9
|
-
}
|
|
10
|
-
return compacted;
|
|
11
|
-
}
|
|
12
|
-
export { compact };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Is the value an array or a record?
|
|
3
|
-
* @param value Value to check
|
|
4
|
-
* @returns `true` if the value is an array or a record, otherwise `false`
|
|
5
|
-
*/
|
|
6
|
-
function isArrayOrPlainObject(value) {
|
|
7
|
-
return Array.isArray(value) || isPlainObject(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
|
-
export { isArrayOrPlainObject };
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { compact } from "./array/compact.js";
|
|
2
|
-
/**
|
|
3
|
-
* Get the string value from any value
|
|
4
|
-
* @param value Original value
|
|
5
|
-
* @returns String representation of the value
|
|
6
|
-
*/
|
|
7
|
-
function getString(value) {
|
|
8
|
-
if (typeof value === "string") return value;
|
|
9
|
-
if (value == null) return "";
|
|
10
|
-
if (typeof value === "function") return getString(value());
|
|
11
|
-
if (typeof value !== "object") return String(value);
|
|
12
|
-
const asString = String(value.valueOf?.() ?? value);
|
|
13
|
-
return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Join an array of values into a string
|
|
17
|
-
* @param value Array of values
|
|
18
|
-
* @param delimiter Delimiter to use between values
|
|
19
|
-
* @returns Joined string
|
|
20
|
-
*/
|
|
21
|
-
function join(value, delimiter) {
|
|
22
|
-
return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
|
|
23
|
-
}
|
|
24
|
-
export { join };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { isArrayOrPlainObject } from "../internal/is.js";
|
|
2
|
-
import { join } from "../internal/string.js";
|
|
3
|
-
function flattenObject(value, depth, smushed, prefix) {
|
|
4
|
-
if (depth >= MAX_DEPTH) return {};
|
|
5
|
-
if (smushed.has(value)) return smushed.get(value);
|
|
6
|
-
const keys = Object.keys(value);
|
|
7
|
-
const { length } = keys;
|
|
8
|
-
const flattened = {};
|
|
9
|
-
for (let index = 0; index < length; index += 1) {
|
|
10
|
-
const key = keys[index];
|
|
11
|
-
const val = value[key];
|
|
12
|
-
if (isArrayOrPlainObject(val)) {
|
|
13
|
-
const prefixedKey = join([prefix, key], ".");
|
|
14
|
-
flattened[prefixedKey] = Array.isArray(val) ? [...val] : { ...val };
|
|
15
|
-
const nested = flattenObject(val, depth + 1, smushed, prefixedKey);
|
|
16
|
-
const nestedKeys = Object.keys(nested);
|
|
17
|
-
const nestedLength = nestedKeys.length;
|
|
18
|
-
for (let nestedIndex = 0; nestedIndex < nestedLength; nestedIndex += 1) {
|
|
19
|
-
const nestedKey = nestedKeys[nestedIndex];
|
|
20
|
-
flattened[nestedKey] = nested[nestedKey];
|
|
21
|
-
}
|
|
22
|
-
} else flattened[join([prefix, key], ".")] = val;
|
|
23
|
-
}
|
|
24
|
-
smushed.set(value, flattened);
|
|
25
|
-
return flattened;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Smush an object into a flat object that uses dot notation keys
|
|
29
|
-
* @param value Object to smush
|
|
30
|
-
* @returns Smushed object with dot notation keys
|
|
31
|
-
*/
|
|
32
|
-
function smush(value) {
|
|
33
|
-
return typeof value === "object" && value !== null ? flattenObject(value, 0, /* @__PURE__ */ new WeakMap()) : {};
|
|
34
|
-
}
|
|
35
|
-
var MAX_DEPTH = 100;
|
|
36
|
-
export { smush };
|