@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 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 TYPE_ALL = new Set([
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
@@ -1,3 +1,4 @@
1
1
  import { isInstance } from "./is.js";
2
+ import { SchematicError } from "./models.js";
2
3
  import { schematic } from "./schematic.js";
3
- export { isInstance, schematic };
4
+ export { SchematicError, isInstance, schematic };
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("Expected a constructor function");
4
+ if (!isConstructor(constructor)) throw new TypeError(MESSAGE_CONSTRUCTOR);
5
5
  return (value) => {
6
6
  return value instanceof constructor;
7
7
  };
@@ -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 TYPE_ALL = new Set([
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("Expected a constructor function");
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) if (!property.types.includes(type)) property.types.push(type);
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
- const validated = {
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
- const typeOfType = typeof type;
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 (typeOfType === "function") {
179
+ if (typeof type === "function") {
164
180
  propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
165
181
  continue;
166
182
  }
167
- if (typeOfType !== "object" || type === null) continue;
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
- addPropertyType(validated, prefix, [TYPE_OBJECT], {}, type[PROPERTY_REQUIRED] !== false);
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(type, validated, prefix);
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 = typeof value === "object" && value !== null;
194
- if (isObject && PROPERTY_REQUIRED in value) required = typeof value[PROPERTY_REQUIRED] === "boolean" ? value[PROPERTY_REQUIRED] : true;
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 > 0) addPropertyType(validated, prefixedKey, types, validators, required);
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 (typeof original !== "object" || original === null) return validators;
206
- for (const type of TYPE_ALL) {
207
- const value = original[type];
208
- validators[type] = (Array.isArray(value) ? value : [value]).filter((validator) => typeof validator === "function");
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 = getSchema(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 this.#schema.enabled && validateValue(this.#schema, value);
306
+ return validateValue(this.#schema, value);
280
307
  }
281
308
  };
282
309
  function schematic(schema) {
283
- return new Schematic(schema);
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
@@ -0,0 +1,8 @@
1
+ import { ERROR_NAME } from "./constants.js";
2
+ var SchematicError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = ERROR_NAME;
6
+ }
7
+ };
8
+ export { SchematicError };
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 = getSchema(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 this.#schema.enabled && validateValue(this.#schema, value);
19
+ return validateValue(this.#schema, value);
22
20
  }
23
21
  };
24
22
  function schematic(schema) {
25
- return new Schematic(schema);
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 { smush } from "../node_modules/@oscarpalmer/atoms/dist/value/smush.js";
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) if (!property.types.includes(type)) property.types.push(type);
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
- const validated = {
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
- const typeOfType = typeof type;
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 (typeOfType === "function") {
41
+ if (typeof type === "function") {
44
42
  propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
45
43
  continue;
46
44
  }
47
- if (typeOfType !== "object" || type === null) continue;
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
- addPropertyType(validated, prefix, [TYPE_OBJECT], {}, type[PROPERTY_REQUIRED] !== false);
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(type, validated, prefix);
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 = typeof value === "object" && value !== null;
74
- if (isObject && "$required" in value) required = typeof value["$required"] === "boolean" ? value[PROPERTY_REQUIRED] : true;
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 > 0) addPropertyType(validated, prefixedKey, types, validators, required);
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 (typeof original !== "object" || original === null) return validators;
86
- for (const type of TYPE_ALL) {
87
- const value = original[type];
88
- validators[type] = (Array.isArray(value) ? value : [value]).filter((validator) => typeof validator === "function");
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 "../node_modules/@oscarpalmer/atoms/dist/value/smush.js";
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
@@ -45,5 +45,5 @@
45
45
  },
46
46
  "type": "module",
47
47
  "types": "./types/index.d.ts",
48
- "version": "0.10.0"
48
+ "version": "0.11.0"
49
49
  }
package/src/constants.ts CHANGED
@@ -1,4 +1,6 @@
1
- import type {Values} from './models';
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 TYPE_ALL = new Set<keyof Values>([
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
@@ -1,3 +1,3 @@
1
1
  export {isInstance} from './is';
2
- export type {Schema, TypedSchema} from './models';
2
+ export {SchematicError, type Schema, type TypedSchema} from './models';
3
3
  export {schematic, type Schematic} from './schematic';
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('Expected a constructor function');
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 ValueName
69
- ? Values[Value & ValueName]
70
- : Value extends Schema
71
- ? Infer<Value>
72
- : never;
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
- : false;
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
- } & Schema;
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 | NestedSchema | SchemaProperty | Schematic<unknown> | ValueName;
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 PlainObject
139
- ? TypedSchema<Value>
140
- : ToValueType<Value>;
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 Function
153
- ? 'function'
154
- : Value extends null
155
- ? 'null'
156
- : Value extends number
157
- ? 'number'
158
- : Value extends object
159
- ? 'object' | ((value: unknown) => value is Value)
160
- : Value extends string
161
- ? 'string'
162
- : Value extends symbol
163
- ? 'symbol'
164
- : Value extends undefined
165
- ? 'undefined'
166
- : (value: unknown) => value is Value;
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
- ? TypedSchemaOptional<Exclude<Model[Key], undefined>>
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 type {Infer, Schema, TypedSchema, ValidatedSchema} from './models';
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
- get enabled(): boolean {
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 = getSchema(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 this.#schema.enabled && validateValue(this.#schema, value);
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
- return new Schematic<Model>(schema);
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 type {
17
- Schema,
18
- ValidatedPropertyType,
19
- ValidatedPropertyValidators,
20
- ValidatedSchema,
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
- if (!property.types.includes(type)) {
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: unknown): ValidatedSchema {
57
- const validated: ValidatedSchema = {
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) || (typeOfType === 'string' && TYPE_ALL.has(type as never))) {
86
- propertyTypes.push(type as never);
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 (typeOfType === 'function') {
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 (typeOfType !== 'object' || type === null) {
98
- continue;
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
- addPropertyType(validated, prefix, [TYPE_OBJECT], {}, type[PROPERTY_REQUIRED] !== false);
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(type as Schema, validated, prefix);
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 = typeof value === 'object' && value !== null;
162
+ const isObject = isPlainObject(value);
151
163
 
152
164
  if (isObject && PROPERTY_REQUIRED in value) {
153
- required = typeof value[PROPERTY_REQUIRED] === 'boolean' ? value[PROPERTY_REQUIRED] : true;
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 > 0) {
165
- addPropertyType(validated, prefixedKey, types, validators, required);
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 (typeof original !== 'object' || original === null) {
203
+ if (original == null) {
180
204
  return validators;
181
205
  }
182
206
 
183
- for (const type of TYPE_ALL) {
184
- const value = (original as PlainObject)[type];
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
- validators[type] = (Array.isArray(value) ? value : [value]).filter(
187
- validator => typeof validator === 'function',
188
- );
228
+ return true;
229
+ });
189
230
  }
190
231
 
191
232
  return validators;
@@ -1,11 +1,21 @@
1
- import type { Values } from './models';
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 TYPE_ALL: Set<keyof Values>;
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
@@ -1,3 +1,3 @@
1
1
  export { isInstance } from './is';
2
- export type { Schema, TypedSchema } from './models';
2
+ export { SchematicError, type Schema, type TypedSchema } from './models';
3
3
  export { schematic, type Schematic } from './schematic';
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 : 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
- } & Schema;
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 | NestedSchema | SchemaProperty | Schematic<unknown> | ValueName;
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>;
@@ -1,13 +1,12 @@
1
1
  import type { PlainObject } from '@oscarpalmer/atoms/models';
2
- import type { Infer, Schema, TypedSchema } from './models';
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
- get enabled(): boolean;
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 { ValidatedSchema } from '../models';
2
- export declare function getSchema(schema: unknown): ValidatedSchema;
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 };