@oscarpalmer/jhunal 0.11.0 → 0.13.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 +6 -4
- package/dist/{is.js → helpers.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/jhunal.full.js +83 -158
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/array/compact.js +12 -0
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/string.js +24 -0
- package/dist/schematic.js +9 -7
- package/dist/validation/property.validation.js +82 -0
- package/dist/validation/value.validation.js +23 -35
- package/package.json +1 -1
- package/src/constants.ts +10 -4
- package/src/{is.ts → helpers.ts} +1 -1
- package/src/index.ts +1 -1
- package/src/models.ts +21 -13
- package/src/schematic.ts +13 -10
- package/src/validation/property.validation.ts +187 -0
- package/src/validation/value.validation.ts +37 -61
- package/types/constants.d.ts +4 -2
- package/types/{is.d.ts → helpers.d.ts} +1 -1
- package/types/index.d.ts +1 -1
- package/types/models.d.ts +6 -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,10 +1,12 @@
|
|
|
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
|
-
const
|
|
8
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED = "'<>.$required' property is not allowed for schemas in $type";
|
|
9
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE = "'<>.$required' property must be a boolean";
|
|
8
10
|
const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
9
11
|
const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
|
|
10
12
|
const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
@@ -33,4 +35,4 @@ const TYPE_ALL = new Set([
|
|
|
33
35
|
"null",
|
|
34
36
|
TYPE_UNDEFINED
|
|
35
37
|
]);
|
|
36
|
-
export { ERROR_NAME,
|
|
38
|
+
export { ERROR_NAME, EXPRESSION_INDEX, EXPRESSION_KEY_PREFIX, EXPRESSION_KEY_VALUE, EXPRESSION_PROPERTY, MESSAGE_CONSTRUCTOR, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE, 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 };
|
|
@@ -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/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { instanceOf } from "./helpers.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/jhunal.full.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
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
2
|
* Is the value a constructor function?
|
|
11
3
|
* @param value Value to check
|
|
12
4
|
* @returns `true` if the value is a constructor function, otherwise `false`
|
|
@@ -59,12 +51,12 @@ function join(value, delimiter) {
|
|
|
59
51
|
return compact(value).map(getString).join(typeof delimiter === "string" ? delimiter : "");
|
|
60
52
|
}
|
|
61
53
|
const ERROR_NAME = "SchematicError";
|
|
62
|
-
const EXPRESSION_HAS_NUMBER = /\d+/;
|
|
63
54
|
const EXPRESSION_INDEX = /\.\d+$/;
|
|
64
|
-
const EXPRESSION_PROPERTY =
|
|
55
|
+
const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
|
|
65
56
|
const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
66
57
|
const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
|
67
|
-
const
|
|
58
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED = "'<>.$required' property is not allowed for schemas in $type";
|
|
59
|
+
const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE = "'<>.$required' property must be a boolean";
|
|
68
60
|
const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
69
61
|
const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
|
|
70
62
|
const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
@@ -93,7 +85,7 @@ const TYPE_ALL = new Set([
|
|
|
93
85
|
"null",
|
|
94
86
|
TYPE_UNDEFINED
|
|
95
87
|
]);
|
|
96
|
-
function
|
|
88
|
+
function instanceOf(constructor) {
|
|
97
89
|
if (!isConstructor(constructor)) throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
98
90
|
return (value) => {
|
|
99
91
|
return value instanceof constructor;
|
|
@@ -108,120 +100,64 @@ var SchematicError = class extends Error {
|
|
|
108
100
|
this.name = ERROR_NAME;
|
|
109
101
|
}
|
|
110
102
|
};
|
|
111
|
-
function
|
|
112
|
-
if (
|
|
113
|
-
if (
|
|
114
|
-
const keys = Object.keys(
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
for (let
|
|
118
|
-
const key = keys[
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
* @param value Object to smush
|
|
138
|
-
* @returns Smushed object with dot notation keys
|
|
139
|
-
*/
|
|
140
|
-
function smush(value) {
|
|
141
|
-
return typeof value === "object" && value !== null ? flattenObject(value, 0, /* @__PURE__ */ new WeakMap()) : {};
|
|
142
|
-
}
|
|
143
|
-
var MAX_DEPTH = 100;
|
|
144
|
-
function addPropertyType(to, key, values, validators, required) {
|
|
145
|
-
if (to.keys.set.has(key)) {
|
|
146
|
-
const property = to.properties[key];
|
|
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
|
-
};
|
|
103
|
+
function getProperties(original, prefix, fromTypes) {
|
|
104
|
+
if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
105
|
+
if (PROPERTY_REQUIRED in original && (fromTypes ?? false) && prefix != null) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED.replace(TEMPLATE_PATTERN, prefix));
|
|
106
|
+
const keys = Object.keys(original);
|
|
107
|
+
const keysLength = keys.length;
|
|
108
|
+
const properties = [];
|
|
109
|
+
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
110
|
+
const key = keys[keyIndex];
|
|
111
|
+
if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) continue;
|
|
112
|
+
const value = original[key];
|
|
113
|
+
const types = [];
|
|
114
|
+
let required = true;
|
|
115
|
+
let validators = {};
|
|
116
|
+
if (isPlainObject(value)) {
|
|
117
|
+
required = getRequired(key, value) ?? required;
|
|
118
|
+
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
119
|
+
if (PROPERTY_TYPE in value) types.push("object", ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
|
|
120
|
+
else types.push("object", ...getTypes(key, value, prefix));
|
|
121
|
+
} else types.push(...getTypes(key, value, prefix));
|
|
122
|
+
if (!required && !types.includes(TYPE_UNDEFINED)) types.push(TYPE_UNDEFINED);
|
|
123
|
+
properties.push({
|
|
124
|
+
key,
|
|
125
|
+
types,
|
|
126
|
+
validators,
|
|
127
|
+
required: required && !types.includes(TYPE_UNDEFINED)
|
|
128
|
+
});
|
|
156
129
|
}
|
|
157
|
-
|
|
158
|
-
to.properties[key].validators = validators;
|
|
130
|
+
return properties;
|
|
159
131
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
set: /* @__PURE__ */ new Set()
|
|
165
|
-
},
|
|
166
|
-
properties: {}
|
|
167
|
-
});
|
|
132
|
+
function getRequired(key, obj) {
|
|
133
|
+
if (!(PROPERTY_REQUIRED in obj)) return;
|
|
134
|
+
if (typeof obj[PROPERTY_REQUIRED] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE.replace(TEMPLATE_PATTERN, key));
|
|
135
|
+
return obj[PROPERTY_REQUIRED];
|
|
168
136
|
}
|
|
169
|
-
function getTypes(
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
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);
|
|
198
|
-
const keys = Object.keys(smushed);
|
|
199
|
-
const { length } = keys;
|
|
200
|
-
const arrayKeys = /* @__PURE__ */ new Set();
|
|
201
|
-
const noPrefix = prefix == null;
|
|
202
|
-
prefix = noPrefix ? "" : `${prefix}.`;
|
|
137
|
+
function getTypes(key, original, prefix, fromTypes) {
|
|
138
|
+
const array = Array.isArray(original) ? original : [original];
|
|
139
|
+
const { length } = array;
|
|
140
|
+
const types = [];
|
|
203
141
|
for (let index = 0; index < length; index += 1) {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
142
|
+
const value = array[index];
|
|
143
|
+
switch (true) {
|
|
144
|
+
case typeof value === "function":
|
|
145
|
+
types.push(isConstructor(value) ? instanceOf(value) : value);
|
|
146
|
+
break;
|
|
147
|
+
case isPlainObject(value):
|
|
148
|
+
types.push(...getProperties(value, join([prefix, key], "."), fromTypes));
|
|
149
|
+
break;
|
|
150
|
+
case isSchematic(value):
|
|
151
|
+
types.push(value);
|
|
152
|
+
break;
|
|
153
|
+
case TYPE_ALL.has(value):
|
|
154
|
+
types.push(value);
|
|
155
|
+
break;
|
|
156
|
+
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], ".")));
|
|
215
157
|
}
|
|
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
158
|
}
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
return validated;
|
|
159
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], ".")));
|
|
160
|
+
return types;
|
|
225
161
|
}
|
|
226
162
|
function getValidators(original) {
|
|
227
163
|
const validators = {};
|
|
@@ -240,43 +176,31 @@ function getValidators(original) {
|
|
|
240
176
|
}
|
|
241
177
|
return validators;
|
|
242
178
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const { keys, properties } = validated;
|
|
253
|
-
const keysLength = keys.array.length;
|
|
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;
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
179
|
+
function validateObject(obj, properties) {
|
|
180
|
+
if (!isPlainObject(obj)) return false;
|
|
181
|
+
const propertiesLength = properties.length;
|
|
182
|
+
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
183
|
+
const property = properties[propertyIndex];
|
|
184
|
+
const { key, required, types } = property;
|
|
185
|
+
const value = obj[key];
|
|
186
|
+
if (value === void 0 && required) return false;
|
|
187
|
+
const typesLength = types.length;
|
|
268
188
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
269
|
-
const type =
|
|
270
|
-
if (
|
|
271
|
-
if (type !== "object") ignore.add(key);
|
|
272
|
-
continue outer;
|
|
273
|
-
}
|
|
189
|
+
const type = types[typeIndex];
|
|
190
|
+
if (validateValue(type, property, value)) continue outer;
|
|
274
191
|
}
|
|
275
192
|
return false;
|
|
276
193
|
}
|
|
277
194
|
return true;
|
|
278
195
|
}
|
|
279
|
-
|
|
196
|
+
function validateValue(type, property, value) {
|
|
197
|
+
switch (true) {
|
|
198
|
+
case isSchematic(type): return type.is(value);
|
|
199
|
+
case typeof type === "function": return type(value);
|
|
200
|
+
case typeof type === "object": return validateObject(value, [type]);
|
|
201
|
+
default: return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
280
204
|
const validators = {
|
|
281
205
|
array: Array.isArray,
|
|
282
206
|
bigint: (value) => typeof value === "bigint",
|
|
@@ -284,7 +208,7 @@ const validators = {
|
|
|
284
208
|
date: (value) => value instanceof Date,
|
|
285
209
|
function: (value) => typeof value === "function",
|
|
286
210
|
null: (value) => value === null,
|
|
287
|
-
number: (value) => typeof value === "number"
|
|
211
|
+
number: (value) => typeof value === "number",
|
|
288
212
|
object: (value) => typeof value === "object" && value !== null,
|
|
289
213
|
string: (value) => typeof value === "string",
|
|
290
214
|
symbol: (value) => typeof value === "symbol",
|
|
@@ -294,20 +218,21 @@ const validators = {
|
|
|
294
218
|
* A schematic for validating objects
|
|
295
219
|
*/
|
|
296
220
|
var Schematic = class {
|
|
297
|
-
#
|
|
298
|
-
constructor(
|
|
221
|
+
#properties;
|
|
222
|
+
constructor(properties) {
|
|
299
223
|
Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
|
|
300
|
-
this.#
|
|
224
|
+
this.#properties = properties;
|
|
301
225
|
}
|
|
302
226
|
/**
|
|
303
227
|
* Does the value match the schema?
|
|
304
228
|
*/
|
|
305
229
|
is(value) {
|
|
306
|
-
return
|
|
230
|
+
return validateObject(value, this.#properties);
|
|
307
231
|
}
|
|
308
232
|
};
|
|
309
233
|
function schematic(schema) {
|
|
234
|
+
if (isSchematic(schema)) return schema;
|
|
310
235
|
if (!isPlainObject(schema)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
311
|
-
return new Schematic(
|
|
236
|
+
return new Schematic(getProperties(schema));
|
|
312
237
|
}
|
|
313
|
-
export { SchematicError,
|
|
238
|
+
export { SchematicError, instanceOf, schematic };
|
|
@@ -0,0 +1,12 @@
|
|
|
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 };
|
|
@@ -0,0 +1,24 @@
|
|
|
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 };
|
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 "./helpers.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,82 @@
|
|
|
1
|
+
import { EXPRESSION_INDEX, EXPRESSION_PROPERTY, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE, 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_UNDEFINED, VALIDATABLE_TYPES } from "../constants.js";
|
|
2
|
+
import { instanceOf, isSchematic } from "../helpers.js";
|
|
3
|
+
import { SchematicError } from "../models.js";
|
|
4
|
+
import { join } from "../node_modules/@oscarpalmer/atoms/dist/internal/string.js";
|
|
5
|
+
import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
|
|
6
|
+
function getProperties(original, prefix, fromTypes) {
|
|
7
|
+
if (Object.keys(original).length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
8
|
+
if ("$required" in original && (fromTypes ?? false) && prefix != null) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED.replace("<>", prefix));
|
|
9
|
+
const keys = Object.keys(original);
|
|
10
|
+
const keysLength = keys.length;
|
|
11
|
+
const properties = [];
|
|
12
|
+
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
13
|
+
const key = keys[keyIndex];
|
|
14
|
+
if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) continue;
|
|
15
|
+
const value = original[key];
|
|
16
|
+
const types = [];
|
|
17
|
+
let required = true;
|
|
18
|
+
let validators = {};
|
|
19
|
+
if (isPlainObject(value)) {
|
|
20
|
+
required = getRequired(key, value) ?? required;
|
|
21
|
+
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
22
|
+
if ("$type" in value) types.push("object", ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
|
|
23
|
+
else types.push("object", ...getTypes(key, value, prefix));
|
|
24
|
+
} else types.push(...getTypes(key, value, prefix));
|
|
25
|
+
if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
|
|
26
|
+
properties.push({
|
|
27
|
+
key,
|
|
28
|
+
types,
|
|
29
|
+
validators,
|
|
30
|
+
required: required && !types.includes("undefined")
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return properties;
|
|
34
|
+
}
|
|
35
|
+
function getRequired(key, obj) {
|
|
36
|
+
if (!("$required" in obj)) return;
|
|
37
|
+
if (typeof obj["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE.replace("<>", key));
|
|
38
|
+
return obj[PROPERTY_REQUIRED];
|
|
39
|
+
}
|
|
40
|
+
function getTypes(key, original, prefix, fromTypes) {
|
|
41
|
+
const array = Array.isArray(original) ? original : [original];
|
|
42
|
+
const { length } = array;
|
|
43
|
+
const types = [];
|
|
44
|
+
for (let index = 0; index < length; index += 1) {
|
|
45
|
+
const value = array[index];
|
|
46
|
+
switch (true) {
|
|
47
|
+
case typeof value === "function":
|
|
48
|
+
types.push(isConstructor(value) ? instanceOf(value) : value);
|
|
49
|
+
break;
|
|
50
|
+
case isPlainObject(value):
|
|
51
|
+
types.push(...getProperties(value, join([prefix, key], "."), fromTypes));
|
|
52
|
+
break;
|
|
53
|
+
case isSchematic(value):
|
|
54
|
+
types.push(value);
|
|
55
|
+
break;
|
|
56
|
+
case TYPE_ALL.has(value):
|
|
57
|
+
types.push(value);
|
|
58
|
+
break;
|
|
59
|
+
default: throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
|
|
63
|
+
return types;
|
|
64
|
+
}
|
|
65
|
+
function getValidators(original) {
|
|
66
|
+
const validators = {};
|
|
67
|
+
if (original == null) return validators;
|
|
68
|
+
if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
69
|
+
const keys = Object.keys(original);
|
|
70
|
+
const { length } = keys;
|
|
71
|
+
for (let index = 0; index < length; index += 1) {
|
|
72
|
+
const key = keys[index];
|
|
73
|
+
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
|
|
74
|
+
const value = original[key];
|
|
75
|
+
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
76
|
+
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return validators;
|
|
81
|
+
}
|
|
82
|
+
export { getProperties };
|
|
@@ -1,42 +1,30 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import {
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const { keys, properties } = validated;
|
|
13
|
-
const keysLength = keys.array.length;
|
|
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;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
1
|
+
import { isSchematic } from "../helpers.js";
|
|
2
|
+
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
3
|
+
function validateObject(obj, properties) {
|
|
4
|
+
if (!isPlainObject(obj)) return false;
|
|
5
|
+
const propertiesLength = properties.length;
|
|
6
|
+
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
7
|
+
const property = properties[propertyIndex];
|
|
8
|
+
const { key, required, types } = property;
|
|
9
|
+
const value = obj[key];
|
|
10
|
+
if (value === void 0 && required) return false;
|
|
11
|
+
const typesLength = types.length;
|
|
28
12
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
29
|
-
const type =
|
|
30
|
-
if (
|
|
31
|
-
if (type !== "object") ignore.add(key);
|
|
32
|
-
continue outer;
|
|
33
|
-
}
|
|
13
|
+
const type = types[typeIndex];
|
|
14
|
+
if (validateValue(type, property, value)) continue outer;
|
|
34
15
|
}
|
|
35
16
|
return false;
|
|
36
17
|
}
|
|
37
18
|
return true;
|
|
38
19
|
}
|
|
39
|
-
|
|
20
|
+
function validateValue(type, property, value) {
|
|
21
|
+
switch (true) {
|
|
22
|
+
case isSchematic(type): return type.is(value);
|
|
23
|
+
case typeof type === "function": return type(value);
|
|
24
|
+
case typeof type === "object": return validateObject(value, [type]);
|
|
25
|
+
default: return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
40
28
|
var validators = {
|
|
41
29
|
array: Array.isArray,
|
|
42
30
|
bigint: (value) => typeof value === "bigint",
|
|
@@ -44,10 +32,10 @@ var validators = {
|
|
|
44
32
|
date: (value) => value instanceof Date,
|
|
45
33
|
function: (value) => typeof value === "function",
|
|
46
34
|
null: (value) => value === null,
|
|
47
|
-
number: (value) => typeof value === "number"
|
|
35
|
+
number: (value) => typeof value === "number",
|
|
48
36
|
object: (value) => typeof value === "object" && value !== null,
|
|
49
37
|
string: (value) => typeof value === "string",
|
|
50
38
|
symbol: (value) => typeof value === "symbol",
|
|
51
39
|
undefined: (value) => value === void 0
|
|
52
40
|
};
|
|
53
|
-
export {
|
|
41
|
+
export { validateObject };
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -2,17 +2,23 @@ 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
|
|
|
13
15
|
export const MESSAGE_SCHEMA_INVALID_EMPTY = 'Schema must have at least one property';
|
|
14
16
|
|
|
15
|
-
export const
|
|
17
|
+
export const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED =
|
|
18
|
+
"'<>.$required' property is not allowed for schemas in $type";
|
|
19
|
+
|
|
20
|
+
export const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE =
|
|
21
|
+
"'<>.$required' property must be a boolean";
|
|
16
22
|
|
|
17
23
|
export const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
18
24
|
|
package/src/{is.ts → helpers.ts}
RENAMED
|
@@ -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/index.ts
CHANGED