@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 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 EXPRESSION_PROPERTY = /\.\$(required|type|validators)(\.|$)/;
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 MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
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, 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 };
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 isInstance(constructor) {
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 { isInstance, isSchematic };
12
+ export { instanceOf, isSchematic };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isInstance } from "./is.js";
1
+ import { instanceOf } from "./helpers.js";
2
2
  import { SchematicError } from "./models.js";
3
3
  import { schematic } from "./schematic.js";
4
- export { SchematicError, isInstance, schematic };
4
+ export { SchematicError, instanceOf, schematic };
@@ -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 = /\.\$(required|type|validators)(\.|$)/;
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 MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
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 isInstance(constructor) {
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 flattenObject(value, depth, smushed, prefix) {
112
- if (depth >= MAX_DEPTH) return {};
113
- if (smushed.has(value)) return smushed.get(value);
114
- const keys = Object.keys(value);
115
- const { length } = keys;
116
- const flattened = {};
117
- for (let index = 0; index < length; index += 1) {
118
- const key = keys[index];
119
- const val = value[key];
120
- if (isArrayOrPlainObject(val)) {
121
- const prefixedKey = join([prefix, key], ".");
122
- flattened[prefixedKey] = Array.isArray(val) ? [...val] : { ...val };
123
- const nested = flattenObject(val, depth + 1, smushed, prefixedKey);
124
- const nestedKeys = Object.keys(nested);
125
- const nestedLength = nestedKeys.length;
126
- for (let nestedIndex = 0; nestedIndex < nestedLength; nestedIndex += 1) {
127
- const nestedKey = nestedKeys[nestedIndex];
128
- flattened[nestedKey] = nested[nestedKey];
129
- }
130
- } else flattened[join([prefix, key], ".")] = val;
131
- }
132
- smushed.set(value, flattened);
133
- return flattened;
134
- }
135
- /**
136
- * Smush an object into a flat object that uses dot notation keys
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
- if (!required && !to.properties[key].types.includes(TYPE_UNDEFINED)) to.properties[key].types.push(TYPE_UNDEFINED);
158
- to.properties[key].validators = validators;
130
+ return properties;
159
131
  }
160
- function getSchema(schema) {
161
- return getValidatedSchema(schema, {
162
- keys: {
163
- array: [],
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(value, validated, prefix) {
170
- const propertyTypes = [];
171
- const values = Array.isArray(value) ? value : [value];
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);
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 key = keys[index];
205
- const value = smushed[key];
206
- if (Array.isArray(value)) arrayKeys.add(key);
207
- if (EXPRESSION_PROPERTY.test(key)) continue;
208
- if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
209
- let required = true;
210
- let validators = {};
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;
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 (noPrefix) validated.keys.array.sort();
223
- if (noPrefix && validated.keys.array.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
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 validateType(type, property, value) {
244
- switch (true) {
245
- case typeof type === "function": return type(value);
246
- case typeof type === "string": return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
247
- default: return type.is(value);
248
- }
249
- }
250
- function validateValue(validated, obj) {
251
- if (typeof obj !== "object" || obj === null) return false;
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 = property.types[typeIndex];
270
- if (validateType(type, property, value)) {
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
- const EXPRESSION_SUFFIX = /\.\w+$/;
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" && !Number.isNaN(value),
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
- #schema;
298
- constructor(schema) {
221
+ #properties;
222
+ constructor(properties) {
299
223
  Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
300
- this.#schema = schema;
224
+ this.#properties = properties;
301
225
  }
302
226
  /**
303
227
  * Does the value match the schema?
304
228
  */
305
229
  is(value) {
306
- return validateValue(this.#schema, value);
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(getSchema(schema));
236
+ return new Schematic(getProperties(schema));
312
237
  }
313
- export { SchematicError, isInstance, schematic };
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 { getSchema } from "./validation/schema.validation.js";
4
- import { validateValue } from "./validation/value.validation.js";
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
- #schema;
11
- constructor(schema) {
11
+ #properties;
12
+ constructor(properties) {
12
13
  Object.defineProperty(this, SCHEMATIC_NAME, { value: true });
13
- this.#schema = schema;
14
+ this.#properties = properties;
14
15
  }
15
16
  /**
16
17
  * Does the value match the schema?
17
18
  */
18
19
  is(value) {
19
- return validateValue(this.#schema, value);
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(getSchema(schema));
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 "../constants.js";
2
- import { smush } from "@oscarpalmer/atoms/value/misc";
3
- function validateType(type, property, value) {
4
- switch (true) {
5
- case typeof type === "function": return type(value);
6
- case typeof type === "string": return validators[type](value) && (property.validators[type]?.every((validator) => validator(value)) ?? true);
7
- default: return type.is(value);
8
- }
9
- }
10
- function validateValue(validated, obj) {
11
- if (typeof obj !== "object" || obj === null) return false;
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 = property.types[typeIndex];
30
- if (validateType(type, property, value)) {
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
- var EXPRESSION_SUFFIX = /\.\w+$/;
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" && !Number.isNaN(value),
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 { validateType, validateValue };
41
+ export { validateObject };
package/package.json CHANGED
@@ -45,5 +45,5 @@
45
45
  },
46
46
  "type": "module",
47
47
  "types": "./types/index.d.ts",
48
- "version": "0.11.0"
48
+ "version": "0.13.0"
49
49
  }
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 EXPRESSION_PROPERTY = /\.\$(required|type|validators)(\.|$)/;
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 MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
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
 
@@ -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 isInstance<Instance>(
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
@@ -1,3 +1,3 @@
1
- export {isInstance} from './is';
1
+ export {instanceOf} from './helpers';
2
2
  export {SchematicError, type Schema, type TypedSchema} from './models';
3
3
  export {schematic, type Schematic} from './schematic';