@oscarpalmer/jhunal 0.11.0 → 0.12.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 +4 -3
- package/dist/index.js +2 -2
- package/dist/is.js +2 -2
- package/dist/jhunal.full.js +99 -109
- package/dist/schematic.js +9 -7
- package/dist/validation/property.validation.js +96 -0
- package/dist/validation/value.validation.js +28 -30
- package/package.json +1 -1
- package/src/constants.ts +5 -3
- package/src/index.ts +1 -1
- package/src/is.ts +1 -1
- package/src/models.ts +23 -13
- package/src/schematic.ts +13 -10
- package/src/validation/property.validation.ts +190 -0
- package/src/validation/value.validation.ts +45 -53
- package/types/constants.d.ts +2 -1
- package/types/index.d.ts +1 -1
- package/types/is.d.ts +1 -1
- package/types/models.d.ts +11 -12
- package/types/schematic.d.ts +2 -2
- package/types/validation/property.validation.d.ts +3 -0
- package/types/validation/value.validation.d.ts +2 -3
- package/dist/validation/schema.validation.js +0 -105
- package/src/validation/schema.validation.ts +0 -233
- package/types/validation/schema.validation.d.ts +0 -2
package/dist/constants.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const ERROR_NAME = "SchematicError";
|
|
2
|
-
const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
3
2
|
const EXPRESSION_INDEX = /\.\d+$/;
|
|
4
|
-
const
|
|
3
|
+
const EXPRESSION_KEY_PREFIX = /\.\w+$/;
|
|
4
|
+
const EXPRESSION_KEY_VALUE = /^.*\.(\w+)$/;
|
|
5
|
+
const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
|
|
5
6
|
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
6
7
|
const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
7
8
|
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
@@ -33,4 +34,4 @@ const TYPE_ALL = new Set([
|
|
|
33
34
|
"null",
|
|
34
35
|
TYPE_UNDEFINED
|
|
35
36
|
]);
|
|
36
|
-
export { ERROR_NAME,
|
|
37
|
+
export { ERROR_NAME, EXPRESSION_INDEX, EXPRESSION_KEY_PREFIX, EXPRESSION_KEY_VALUE, 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { instanceOf } from "./is.js";
|
|
2
2
|
import { SchematicError } from "./models.js";
|
|
3
3
|
import { schematic } from "./schematic.js";
|
|
4
|
-
export { SchematicError,
|
|
4
|
+
export { SchematicError, instanceOf, schematic };
|
package/dist/is.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MESSAGE_CONSTRUCTOR } from "./constants.js";
|
|
2
2
|
import { isConstructor } from "@oscarpalmer/atoms/is";
|
|
3
|
-
function
|
|
3
|
+
function instanceOf(constructor) {
|
|
4
4
|
if (!isConstructor(constructor)) throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
5
5
|
return (value) => {
|
|
6
6
|
return value instanceof constructor;
|
|
@@ -9,4 +9,4 @@ function isInstance(constructor) {
|
|
|
9
9
|
function isSchematic(value) {
|
|
10
10
|
return typeof value === "object" && value !== null && "$schematic" in value && value["$schematic"] === true;
|
|
11
11
|
}
|
|
12
|
-
export {
|
|
12
|
+
export { instanceOf, isSchematic };
|
package/dist/jhunal.full.js
CHANGED
|
@@ -59,9 +59,10 @@ function join(value, delimiter) {
|
|
|
59
59
|
return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
|
|
60
60
|
}
|
|
61
61
|
const ERROR_NAME = "SchematicError";
|
|
62
|
-
const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
63
62
|
const EXPRESSION_INDEX = /\.\d+$/;
|
|
64
|
-
const
|
|
63
|
+
const EXPRESSION_KEY_PREFIX = /\.\w+$/;
|
|
64
|
+
const EXPRESSION_KEY_VALUE = /^.*\.(\w+)$/;
|
|
65
|
+
const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
|
|
65
66
|
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
66
67
|
const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
67
68
|
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
@@ -93,7 +94,7 @@ const TYPE_ALL = new Set([
|
|
|
93
94
|
"null",
|
|
94
95
|
TYPE_UNDEFINED
|
|
95
96
|
]);
|
|
96
|
-
function
|
|
97
|
+
function instanceOf(constructor) {
|
|
97
98
|
if (!isConstructor(constructor)) throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
98
99
|
return (value) => {
|
|
99
100
|
return value instanceof constructor;
|
|
@@ -141,87 +142,77 @@ function smush(value) {
|
|
|
141
142
|
return typeof value === "object" && value !== null ? flattenObject(value, 0, /* @__PURE__ */ new WeakMap()) : {};
|
|
142
143
|
}
|
|
143
144
|
var MAX_DEPTH = 100;
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
for (const type of values) property.types.push(type);
|
|
148
|
-
} else {
|
|
149
|
-
to.keys.array.push(key);
|
|
150
|
-
to.keys.set.add(key);
|
|
151
|
-
to.properties[key] = {
|
|
152
|
-
required,
|
|
153
|
-
types: values,
|
|
154
|
-
validators: {}
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
if (!required && !to.properties[key].types.includes(TYPE_UNDEFINED)) to.properties[key].types.push(TYPE_UNDEFINED);
|
|
158
|
-
to.properties[key].validators = validators;
|
|
145
|
+
function getKeyPrefix(key) {
|
|
146
|
+
const prefix = key.replace(EXPRESSION_KEY_PREFIX, "");
|
|
147
|
+
return prefix === key ? void 0 : prefix;
|
|
159
148
|
}
|
|
160
|
-
function
|
|
161
|
-
return
|
|
162
|
-
keys: {
|
|
163
|
-
array: [],
|
|
164
|
-
set: /* @__PURE__ */ new Set()
|
|
165
|
-
},
|
|
166
|
-
properties: {}
|
|
167
|
-
});
|
|
149
|
+
function getKeyValue(key) {
|
|
150
|
+
return key.replace(EXPRESSION_KEY_VALUE, "$1");
|
|
168
151
|
}
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
const { length } = values;
|
|
173
|
-
for (let index = 0; index < length; index += 1) {
|
|
174
|
-
const type = values[index];
|
|
175
|
-
if (isSchematic(type) || TYPE_ALL.has(type)) {
|
|
176
|
-
propertyTypes.push(type);
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (typeof type === "function") {
|
|
180
|
-
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (!isPlainObject(type)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, prefix));
|
|
184
|
-
if (PROPERTY_TYPE in type) {
|
|
185
|
-
propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
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);
|
|
191
|
-
propertyTypes.push(TYPE_OBJECT);
|
|
192
|
-
getValidatedSchema(nested, validated, prefix);
|
|
193
|
-
}
|
|
194
|
-
return propertyTypes;
|
|
195
|
-
}
|
|
196
|
-
function getValidatedSchema(schema, validated, prefix) {
|
|
197
|
-
const smushed = smush(schema);
|
|
152
|
+
function getProperties(original) {
|
|
153
|
+
if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
154
|
+
const smushed = smush(original);
|
|
198
155
|
const keys = Object.keys(smushed);
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
156
|
+
const keysLength = keys.length;
|
|
157
|
+
const properties = [];
|
|
158
|
+
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
159
|
+
const key = keys[keyIndex];
|
|
160
|
+
if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) continue;
|
|
161
|
+
const keyPrefix = getKeyPrefix(key);
|
|
162
|
+
const keyValue = getKeyValue(key);
|
|
205
163
|
const value = smushed[key];
|
|
206
|
-
|
|
207
|
-
if (EXPRESSION_PROPERTY.test(key)) continue;
|
|
208
|
-
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
|
|
164
|
+
const types = [];
|
|
209
165
|
let required = true;
|
|
210
166
|
let validators = {};
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
167
|
+
if (isPlainObject(value)) {
|
|
168
|
+
required = getRequired(key, value) ?? required;
|
|
169
|
+
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
170
|
+
if (PROPERTY_TYPE in value) types.push(...getTypes(key, value[PROPERTY_TYPE]));
|
|
171
|
+
else types.push(TYPE_OBJECT);
|
|
172
|
+
} else types.push(...getTypes(key, value));
|
|
173
|
+
if (!required && !types.includes(TYPE_UNDEFINED)) types.push(TYPE_UNDEFINED);
|
|
174
|
+
properties.push({
|
|
175
|
+
types,
|
|
176
|
+
validators,
|
|
177
|
+
key: {
|
|
178
|
+
full: key,
|
|
179
|
+
prefix: keyPrefix,
|
|
180
|
+
value: keyValue
|
|
181
|
+
},
|
|
182
|
+
required: required && !types.includes(TYPE_UNDEFINED)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return properties;
|
|
186
|
+
}
|
|
187
|
+
function getRequired(key, value) {
|
|
188
|
+
if (!(PROPERTY_REQUIRED in value)) return;
|
|
189
|
+
if (typeof value[PROPERTY_REQUIRED] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key));
|
|
190
|
+
return value[PROPERTY_REQUIRED];
|
|
191
|
+
}
|
|
192
|
+
function getTypes(key, original) {
|
|
193
|
+
const array = Array.isArray(original) ? original : [original];
|
|
194
|
+
const { length } = array;
|
|
195
|
+
const types = [];
|
|
196
|
+
for (let index = 0; index < length; index += 1) {
|
|
197
|
+
const value = array[index];
|
|
198
|
+
switch (true) {
|
|
199
|
+
case typeof value === "function":
|
|
200
|
+
types.push(isConstructor(value) ? instanceOf(value) : value);
|
|
201
|
+
break;
|
|
202
|
+
case isPlainObject(value):
|
|
203
|
+
types.push(schematic(value));
|
|
204
|
+
break;
|
|
205
|
+
case isSchematic(value):
|
|
206
|
+
types.push(value);
|
|
207
|
+
break;
|
|
208
|
+
case TYPE_ALL.has(value):
|
|
209
|
+
types.push(value);
|
|
210
|
+
break;
|
|
211
|
+
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key));
|
|
215
212
|
}
|
|
216
|
-
if (isObject && PROPERTY_VALIDATORS in value) validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
217
|
-
const prefixedKey = `${prefix}${key}`;
|
|
218
|
-
const types = getTypes(value, validated, prefixedKey);
|
|
219
|
-
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key));
|
|
220
|
-
addPropertyType(validated, prefixedKey, types, validators, required);
|
|
221
213
|
}
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
return validated;
|
|
214
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key));
|
|
215
|
+
return types;
|
|
225
216
|
}
|
|
226
217
|
function getValidators(original) {
|
|
227
218
|
const validators = {};
|
|
@@ -240,35 +231,27 @@ function getValidators(original) {
|
|
|
240
231
|
}
|
|
241
232
|
return validators;
|
|
242
233
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const ignore = /* @__PURE__ */ new Set();
|
|
255
|
-
const smushed = smush(obj);
|
|
256
|
-
outer: for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
257
|
-
const key = keys.array[keyIndex];
|
|
258
|
-
const prefix = key.replace(EXPRESSION_SUFFIX, "");
|
|
259
|
-
if (ignore.has(prefix)) continue;
|
|
260
|
-
const property = properties[key];
|
|
261
|
-
const value = smushed[key];
|
|
262
|
-
if (value === void 0 && property.required && !property.types.includes(TYPE_UNDEFINED)) return false;
|
|
263
|
-
const typesLength = property.types.length;
|
|
264
|
-
if (typesLength === 1) {
|
|
265
|
-
if (!validateType(property.types[0], property, value)) return false;
|
|
234
|
+
function validateObject(obj, properties) {
|
|
235
|
+
if (!isPlainObject(obj)) return false;
|
|
236
|
+
const ignoredKeys = /* @__PURE__ */ new Set();
|
|
237
|
+
const propertiesLength = properties.length;
|
|
238
|
+
let key;
|
|
239
|
+
let value;
|
|
240
|
+
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
241
|
+
const property = properties[propertyIndex];
|
|
242
|
+
if (ignoredKeys.has(property.key.prefix)) {
|
|
243
|
+
key = void 0;
|
|
244
|
+
ignoredKeys.add(property.key.full);
|
|
266
245
|
continue;
|
|
267
246
|
}
|
|
247
|
+
key = property.key.full;
|
|
248
|
+
value = obj[key];
|
|
249
|
+
if (value === void 0 && property.required) return false;
|
|
250
|
+
const typesLength = property.types.length;
|
|
268
251
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
269
252
|
const type = property.types[typeIndex];
|
|
270
|
-
if (
|
|
271
|
-
|
|
253
|
+
if (validateValue(type, property, value)) {
|
|
254
|
+
ignoredKeys.add(property.key.full);
|
|
272
255
|
continue outer;
|
|
273
256
|
}
|
|
274
257
|
}
|
|
@@ -276,7 +259,13 @@ function validateValue(validated, obj) {
|
|
|
276
259
|
}
|
|
277
260
|
return true;
|
|
278
261
|
}
|
|
279
|
-
|
|
262
|
+
function validateValue(type, property, value) {
|
|
263
|
+
switch (true) {
|
|
264
|
+
case typeof type === "function": return type(value);
|
|
265
|
+
case isSchematic(type): return type.is(value);
|
|
266
|
+
default: return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
280
269
|
const validators = {
|
|
281
270
|
array: Array.isArray,
|
|
282
271
|
bigint: (value) => typeof value === "bigint",
|
|
@@ -284,7 +273,7 @@ const validators = {
|
|
|
284
273
|
date: (value) => value instanceof Date,
|
|
285
274
|
function: (value) => typeof value === "function",
|
|
286
275
|
null: (value) => value === null,
|
|
287
|
-
number: (value) => typeof value === "number"
|
|
276
|
+
number: (value) => typeof value === "number",
|
|
288
277
|
object: (value) => typeof value === "object" && value !== null,
|
|
289
278
|
string: (value) => typeof value === "string",
|
|
290
279
|
symbol: (value) => typeof value === "symbol",
|
|
@@ -294,20 +283,21 @@ const validators = {
|
|
|
294
283
|
* A schematic for validating objects
|
|
295
284
|
*/
|
|
296
285
|
var Schematic = class {
|
|
297
|
-
#
|
|
298
|
-
constructor(
|
|
286
|
+
#properties;
|
|
287
|
+
constructor(properties) {
|
|
299
288
|
Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
|
|
300
|
-
this.#
|
|
289
|
+
this.#properties = properties;
|
|
301
290
|
}
|
|
302
291
|
/**
|
|
303
292
|
* Does the value match the schema?
|
|
304
293
|
*/
|
|
305
294
|
is(value) {
|
|
306
|
-
return
|
|
295
|
+
return validateObject(value, this.#properties);
|
|
307
296
|
}
|
|
308
297
|
};
|
|
309
298
|
function schematic(schema) {
|
|
299
|
+
if (isSchematic(schema)) return schema;
|
|
310
300
|
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
311
|
-
return new Schematic(
|
|
301
|
+
return new Schematic(getProperties(schema));
|
|
312
302
|
}
|
|
313
|
-
export { SchematicError,
|
|
303
|
+
export { SchematicError, instanceOf, schematic };
|
package/dist/schematic.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import { MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME } from "./constants.js";
|
|
2
|
+
import { isSchematic } from "./is.js";
|
|
2
3
|
import { SchematicError } from "./models.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { getProperties } from "./validation/property.validation.js";
|
|
5
|
+
import { validateObject } from "./validation/value.validation.js";
|
|
5
6
|
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
6
7
|
/**
|
|
7
8
|
* A schematic for validating objects
|
|
8
9
|
*/
|
|
9
10
|
var Schematic = class {
|
|
10
|
-
#
|
|
11
|
-
constructor(
|
|
11
|
+
#properties;
|
|
12
|
+
constructor(properties) {
|
|
12
13
|
Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
|
|
13
|
-
this.#
|
|
14
|
+
this.#properties = properties;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Does the value match the schema?
|
|
17
18
|
*/
|
|
18
19
|
is(value) {
|
|
19
|
-
return
|
|
20
|
+
return validateObject(value, this.#properties);
|
|
20
21
|
}
|
|
21
22
|
};
|
|
22
23
|
function schematic(schema) {
|
|
24
|
+
if (isSchematic(schema)) return schema;
|
|
23
25
|
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
24
|
-
return new Schematic(
|
|
26
|
+
return new Schematic(getProperties(schema));
|
|
25
27
|
}
|
|
26
28
|
export { Schematic, schematic };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { EXPRESSION_INDEX, EXPRESSION_KEY_PREFIX, EXPRESSION_KEY_VALUE, 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
|
+
import { instanceOf, isSchematic } from "../is.js";
|
|
3
|
+
import { SchematicError } from "../models.js";
|
|
4
|
+
import { schematic } from "../schematic.js";
|
|
5
|
+
import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
|
|
6
|
+
import { smush } from "@oscarpalmer/atoms/value/misc";
|
|
7
|
+
function getKeyPrefix(key) {
|
|
8
|
+
const prefix = key.replace(EXPRESSION_KEY_PREFIX, "");
|
|
9
|
+
return prefix === key ? void 0 : prefix;
|
|
10
|
+
}
|
|
11
|
+
function getKeyValue(key) {
|
|
12
|
+
return key.replace(EXPRESSION_KEY_VALUE, "$1");
|
|
13
|
+
}
|
|
14
|
+
function getProperties(original) {
|
|
15
|
+
if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
16
|
+
const smushed = smush(original);
|
|
17
|
+
const keys = Object.keys(smushed);
|
|
18
|
+
const keysLength = keys.length;
|
|
19
|
+
const properties = [];
|
|
20
|
+
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
21
|
+
const key = keys[keyIndex];
|
|
22
|
+
if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) continue;
|
|
23
|
+
const keyPrefix = getKeyPrefix(key);
|
|
24
|
+
const keyValue = getKeyValue(key);
|
|
25
|
+
const value = smushed[key];
|
|
26
|
+
const types = [];
|
|
27
|
+
let required = true;
|
|
28
|
+
let validators = {};
|
|
29
|
+
if (isPlainObject(value)) {
|
|
30
|
+
required = getRequired(key, value) ?? required;
|
|
31
|
+
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
32
|
+
if ("$type" in value) types.push(...getTypes(key, value[PROPERTY_TYPE]));
|
|
33
|
+
else types.push(TYPE_OBJECT);
|
|
34
|
+
} else types.push(...getTypes(key, value));
|
|
35
|
+
if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
|
|
36
|
+
properties.push({
|
|
37
|
+
types,
|
|
38
|
+
validators,
|
|
39
|
+
key: {
|
|
40
|
+
full: key,
|
|
41
|
+
prefix: keyPrefix,
|
|
42
|
+
value: keyValue
|
|
43
|
+
},
|
|
44
|
+
required: required && !types.includes("undefined")
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return properties;
|
|
48
|
+
}
|
|
49
|
+
function getRequired(key, value) {
|
|
50
|
+
if (!("$required" in value)) return;
|
|
51
|
+
if (typeof value["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
|
|
52
|
+
return value[PROPERTY_REQUIRED];
|
|
53
|
+
}
|
|
54
|
+
function getTypes(key, original) {
|
|
55
|
+
const array = Array.isArray(original) ? original : [original];
|
|
56
|
+
const { length } = array;
|
|
57
|
+
const types = [];
|
|
58
|
+
for (let index = 0; index < length; index += 1) {
|
|
59
|
+
const value = array[index];
|
|
60
|
+
switch (true) {
|
|
61
|
+
case typeof value === "function":
|
|
62
|
+
types.push(isConstructor(value) ? instanceOf(value) : value);
|
|
63
|
+
break;
|
|
64
|
+
case isPlainObject(value):
|
|
65
|
+
types.push(schematic(value));
|
|
66
|
+
break;
|
|
67
|
+
case isSchematic(value):
|
|
68
|
+
types.push(value);
|
|
69
|
+
break;
|
|
70
|
+
case TYPE_ALL.has(value):
|
|
71
|
+
types.push(value);
|
|
72
|
+
break;
|
|
73
|
+
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", key));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", key));
|
|
77
|
+
return types;
|
|
78
|
+
}
|
|
79
|
+
function getValidators(original) {
|
|
80
|
+
const validators = {};
|
|
81
|
+
if (original == null) return validators;
|
|
82
|
+
if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
83
|
+
const keys = Object.keys(original);
|
|
84
|
+
const { length } = keys;
|
|
85
|
+
for (let index = 0; index < length; index += 1) {
|
|
86
|
+
const key = keys[index];
|
|
87
|
+
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
|
|
88
|
+
const value = original[key];
|
|
89
|
+
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
90
|
+
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return validators;
|
|
95
|
+
}
|
|
96
|
+
export { getProperties };
|
|
@@ -1,34 +1,26 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import {
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const ignore = /* @__PURE__ */ new Set();
|
|
15
|
-
const smushed = smush(obj);
|
|
16
|
-
outer: for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
17
|
-
const key = keys.array[keyIndex];
|
|
18
|
-
const prefix = key.replace(EXPRESSION_SUFFIX, "");
|
|
19
|
-
if (ignore.has(prefix)) continue;
|
|
20
|
-
const property = properties[key];
|
|
21
|
-
const value = smushed[key];
|
|
22
|
-
if (value === void 0 && property.required && !property.types.includes("undefined")) return false;
|
|
23
|
-
const typesLength = property.types.length;
|
|
24
|
-
if (typesLength === 1) {
|
|
25
|
-
if (!validateType(property.types[0], property, value)) return false;
|
|
1
|
+
import { isSchematic } from "../is.js";
|
|
2
|
+
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
3
|
+
function validateObject(obj, properties) {
|
|
4
|
+
if (!isPlainObject(obj)) return false;
|
|
5
|
+
const ignoredKeys = /* @__PURE__ */ new Set();
|
|
6
|
+
const propertiesLength = properties.length;
|
|
7
|
+
let key;
|
|
8
|
+
let value;
|
|
9
|
+
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
10
|
+
const property = properties[propertyIndex];
|
|
11
|
+
if (ignoredKeys.has(property.key.prefix)) {
|
|
12
|
+
key = void 0;
|
|
13
|
+
ignoredKeys.add(property.key.full);
|
|
26
14
|
continue;
|
|
27
15
|
}
|
|
16
|
+
key = property.key.full;
|
|
17
|
+
value = obj[key];
|
|
18
|
+
if (value === void 0 && property.required) return false;
|
|
19
|
+
const typesLength = property.types.length;
|
|
28
20
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
29
21
|
const type = property.types[typeIndex];
|
|
30
|
-
if (
|
|
31
|
-
|
|
22
|
+
if (validateValue(type, property, value)) {
|
|
23
|
+
ignoredKeys.add(property.key.full);
|
|
32
24
|
continue outer;
|
|
33
25
|
}
|
|
34
26
|
}
|
|
@@ -36,7 +28,13 @@ function validateValue(validated, obj) {
|
|
|
36
28
|
}
|
|
37
29
|
return true;
|
|
38
30
|
}
|
|
39
|
-
|
|
31
|
+
function validateValue(type, property, value) {
|
|
32
|
+
switch (true) {
|
|
33
|
+
case typeof type === "function": return type(value);
|
|
34
|
+
case isSchematic(type): return type.is(value);
|
|
35
|
+
default: return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
40
38
|
var validators = {
|
|
41
39
|
array: Array.isArray,
|
|
42
40
|
bigint: (value) => typeof value === "bigint",
|
|
@@ -44,10 +42,10 @@ var validators = {
|
|
|
44
42
|
date: (value) => value instanceof Date,
|
|
45
43
|
function: (value) => typeof value === "function",
|
|
46
44
|
null: (value) => value === null,
|
|
47
|
-
number: (value) => typeof value === "number"
|
|
45
|
+
number: (value) => typeof value === "number",
|
|
48
46
|
object: (value) => typeof value === "object" && value !== null,
|
|
49
47
|
string: (value) => typeof value === "string",
|
|
50
48
|
symbol: (value) => typeof value === "symbol",
|
|
51
49
|
undefined: (value) => value === void 0
|
|
52
50
|
};
|
|
53
|
-
export {
|
|
51
|
+
export { validateObject };
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -2,11 +2,13 @@ import type {ValueName} from './models';
|
|
|
2
2
|
|
|
3
3
|
export const ERROR_NAME = 'SchematicError';
|
|
4
4
|
|
|
5
|
-
export const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
6
|
-
|
|
7
5
|
export const EXPRESSION_INDEX = /\.\d+$/;
|
|
8
6
|
|
|
9
|
-
export const
|
|
7
|
+
export const EXPRESSION_KEY_PREFIX = /\.\w+$/;
|
|
8
|
+
|
|
9
|
+
export const EXPRESSION_KEY_VALUE = /^.*\.(\w+)$/;
|
|
10
|
+
|
|
11
|
+
export const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
|
|
10
12
|
|
|
11
13
|
export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
|
|
12
14
|
|
package/src/index.ts
CHANGED
package/src/is.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {MESSAGE_CONSTRUCTOR, SCHEMATIC_NAME} from './constants';
|
|
|
3
3
|
import type {Constructor} from './models';
|
|
4
4
|
import type {Schematic} from './schematic';
|
|
5
5
|
|
|
6
|
-
export function
|
|
6
|
+
export function instanceOf<Instance>(
|
|
7
7
|
constructor: Constructor<Instance>,
|
|
8
8
|
): (value: unknown) => value is Instance {
|
|
9
9
|
if (!isConstructor(constructor)) {
|
package/src/models.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
1
|
+
import type {GenericCallback, PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import {ERROR_NAME} from './constants';
|
|
3
3
|
import type {Schematic} from './schematic';
|
|
4
4
|
|
|
@@ -122,10 +122,16 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
122
122
|
*/
|
|
123
123
|
export type Schema = SchemaIndex;
|
|
124
124
|
|
|
125
|
-
type SchemaEntry =
|
|
125
|
+
type SchemaEntry =
|
|
126
|
+
| Constructor
|
|
127
|
+
| Schema
|
|
128
|
+
| SchemaProperty
|
|
129
|
+
| Schematic<unknown>
|
|
130
|
+
| ValueName
|
|
131
|
+
| ((value: unknown) => boolean);
|
|
126
132
|
|
|
127
133
|
interface SchemaIndex {
|
|
128
|
-
[key: string]: SchemaEntry | SchemaEntry[];
|
|
134
|
+
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
/**
|
|
@@ -137,7 +143,12 @@ export type SchemaProperty = {
|
|
|
137
143
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
138
144
|
};
|
|
139
145
|
|
|
140
|
-
type SchemaPropertyType =
|
|
146
|
+
type SchemaPropertyType =
|
|
147
|
+
| Constructor
|
|
148
|
+
| Schema
|
|
149
|
+
| Schematic<unknown>
|
|
150
|
+
| ValueName
|
|
151
|
+
| ((value: unknown) => boolean);
|
|
141
152
|
|
|
142
153
|
export class SchematicError extends Error {
|
|
143
154
|
constructor(message: string) {
|
|
@@ -279,25 +290,24 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
|
|
|
279
290
|
: Value;
|
|
280
291
|
|
|
281
292
|
export type ValidatedProperty = {
|
|
293
|
+
key: ValidatedPropertyKey;
|
|
282
294
|
required: boolean;
|
|
283
295
|
types: ValidatedPropertyType[];
|
|
284
296
|
validators: ValidatedPropertyValidators;
|
|
285
297
|
};
|
|
286
298
|
|
|
287
|
-
export type
|
|
299
|
+
export type ValidatedPropertyKey = {
|
|
300
|
+
full: string;
|
|
301
|
+
prefix: string | undefined;
|
|
302
|
+
value: string;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValueName;
|
|
288
306
|
|
|
289
307
|
export type ValidatedPropertyValidators = {
|
|
290
308
|
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
291
309
|
};
|
|
292
310
|
|
|
293
|
-
export type ValidatedSchema = {
|
|
294
|
-
keys: {
|
|
295
|
-
array: string[];
|
|
296
|
-
set: Set<string>;
|
|
297
|
-
};
|
|
298
|
-
properties: Record<string, ValidatedProperty>;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
311
|
/**
|
|
302
312
|
* Valid type name strings
|
|
303
313
|
*/
|