@oscarpalmer/jhunal 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/schematic.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import type {PlainObject} from '@oscarpalmer/atoms/models';
3
3
  import {MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME} from './constants';
4
+ import {isSchematic} from './is';
4
5
  import {
5
6
  SchematicError,
6
7
  type Infer,
7
8
  type Schema,
8
9
  type TypedSchema,
9
- type ValidatedSchema,
10
+ type ValidatedProperty,
10
11
  } from './models';
11
- import {getSchema} from './validation/schema.validation';
12
- import {validateValue} from './validation/value.validation';
12
+ import {getProperties} from './validation/property.validation';
13
+ import {validateObject} from './validation/value.validation';
13
14
 
14
15
  /**
15
16
  * A schematic for validating objects
@@ -17,21 +18,21 @@ import {validateValue} from './validation/value.validation';
17
18
  export class Schematic<Model> {
18
19
  declare private readonly $schematic: true;
19
20
 
20
- #schema: ValidatedSchema;
21
+ #properties: ValidatedProperty[];
21
22
 
22
- constructor(schema: ValidatedSchema) {
23
+ constructor(properties: ValidatedProperty[]) {
23
24
  Object.defineProperty(this, SCHEMATIC_NAME, {
24
25
  value: true,
25
26
  });
26
27
 
27
- this.#schema = schema;
28
+ this.#properties = properties;
28
29
  }
29
30
 
30
31
  /**
31
32
  * Does the value match the schema?
32
33
  */
33
34
  is(value: unknown): value is Model {
34
- return validateValue(this.#schema, value);
35
+ return validateObject(value, this.#properties);
35
36
  }
36
37
  }
37
38
 
@@ -46,11 +47,13 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<
46
47
  export function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
47
48
 
48
49
  export function schematic<Model extends Schema>(schema: Model): Schematic<Model> {
50
+ if (isSchematic(schema)) {
51
+ return schema;
52
+ }
53
+
49
54
  if (!isPlainObject(schema)) {
50
55
  throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
51
56
  }
52
57
 
53
- const validated = getSchema(schema);
54
-
55
- return new Schematic<Model>(validated);
58
+ return new Schematic<Model>(getProperties(schema));
56
59
  }
@@ -0,0 +1,190 @@
1
+ import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
3
+ import {smush} from '@oscarpalmer/atoms/value/misc';
4
+ import {
5
+ EXPRESSION_INDEX,
6
+ EXPRESSION_KEY_PREFIX,
7
+ EXPRESSION_KEY_VALUE,
8
+ EXPRESSION_PROPERTY,
9
+ MESSAGE_SCHEMA_INVALID_EMPTY,
10
+ MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
11
+ MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
12
+ MESSAGE_VALIDATOR_INVALID_KEY,
13
+ MESSAGE_VALIDATOR_INVALID_TYPE,
14
+ MESSAGE_VALIDATOR_INVALID_VALUE,
15
+ PROPERTY_REQUIRED,
16
+ PROPERTY_TYPE,
17
+ PROPERTY_VALIDATORS,
18
+ TEMPLATE_PATTERN,
19
+ TYPE_ALL,
20
+ TYPE_OBJECT,
21
+ TYPE_UNDEFINED,
22
+ VALIDATABLE_TYPES,
23
+ } from '../constants';
24
+ import {instanceOf, isSchematic} from '../is';
25
+ import {
26
+ SchematicError,
27
+ type ValidatedProperty,
28
+ type ValidatedPropertyType,
29
+ type ValidatedPropertyValidators,
30
+ type ValueName,
31
+ } from '../models';
32
+ import {schematic} from '../schematic';
33
+
34
+ function getKeyPrefix(key: string): string | undefined {
35
+ const prefix = key.replace(EXPRESSION_KEY_PREFIX, '');
36
+
37
+ return prefix === key ? undefined : prefix;
38
+ }
39
+
40
+ function getKeyValue(key: string): string {
41
+ return key.replace(EXPRESSION_KEY_VALUE, '$1');
42
+ }
43
+
44
+ export function getProperties(original: PlainObject): ValidatedProperty[] {
45
+ if (Object.keys(original).length === 0) {
46
+ throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
47
+ }
48
+
49
+ const smushed = smush(original);
50
+ const keys = Object.keys(smushed);
51
+ const keysLength = keys.length;
52
+
53
+ const properties: ValidatedProperty[] = [];
54
+
55
+ for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
56
+ const key = keys[keyIndex];
57
+
58
+ if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) {
59
+ continue;
60
+ }
61
+
62
+ const keyPrefix = getKeyPrefix(key);
63
+ const keyValue = getKeyValue(key);
64
+ const value = smushed[key];
65
+
66
+ const types: ValidatedPropertyType[] = [];
67
+
68
+ let required = true;
69
+ let validators: ValidatedPropertyValidators = {};
70
+
71
+ if (isPlainObject(value)) {
72
+ required = getRequired(key, value) ?? required;
73
+ validators = getValidators(value[PROPERTY_VALIDATORS]);
74
+
75
+ if (PROPERTY_TYPE in value) {
76
+ types.push(...getTypes(key, value[PROPERTY_TYPE]));
77
+ } else {
78
+ types.push(TYPE_OBJECT);
79
+ }
80
+ } else {
81
+ types.push(...getTypes(key, value));
82
+ }
83
+
84
+ if (!required && !types.includes(TYPE_UNDEFINED)) {
85
+ types.push(TYPE_UNDEFINED);
86
+ }
87
+
88
+ properties.push({
89
+ types,
90
+ validators,
91
+ key: {
92
+ full: key,
93
+ prefix: keyPrefix,
94
+ value: keyValue,
95
+ },
96
+ required: required && !types.includes(TYPE_UNDEFINED),
97
+ });
98
+ }
99
+
100
+ return properties;
101
+ }
102
+
103
+ function getRequired(key: string, value: PlainObject): boolean | undefined {
104
+ if (!(PROPERTY_REQUIRED in value)) {
105
+ return;
106
+ }
107
+
108
+ if (typeof value[PROPERTY_REQUIRED] !== 'boolean') {
109
+ throw new SchematicError(
110
+ MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key),
111
+ );
112
+ }
113
+
114
+ return value[PROPERTY_REQUIRED];
115
+ }
116
+
117
+ function getTypes(key: string, original: unknown): ValidatedPropertyType[] {
118
+ const array = Array.isArray(original) ? original : [original];
119
+ const {length} = array;
120
+
121
+ const types: ValidatedPropertyType[] = [];
122
+
123
+ for (let index = 0; index < length; index += 1) {
124
+ const value = array[index];
125
+
126
+ switch (true) {
127
+ case typeof value === 'function':
128
+ types.push(isConstructor(value) ? instanceOf(value) : value);
129
+ break;
130
+
131
+ case isPlainObject(value):
132
+ types.push(schematic(value as never));
133
+ break;
134
+
135
+ case isSchematic(value):
136
+ types.push(value);
137
+ break;
138
+
139
+ case TYPE_ALL.has(value as never):
140
+ types.push(value as never);
141
+ break;
142
+
143
+ default:
144
+ throw new SchematicError(
145
+ MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key),
146
+ );
147
+ }
148
+ }
149
+
150
+ if (types.length === 0) {
151
+ throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key));
152
+ }
153
+
154
+ return types;
155
+ }
156
+
157
+ function getValidators(original: unknown): ValidatedPropertyValidators {
158
+ const validators: ValidatedPropertyValidators = {};
159
+
160
+ if (original == null) {
161
+ return validators;
162
+ }
163
+
164
+ if (!isPlainObject(original)) {
165
+ throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
166
+ }
167
+
168
+ const keys = Object.keys(original);
169
+ const {length} = keys;
170
+
171
+ for (let index = 0; index < length; index += 1) {
172
+ const key = keys[index];
173
+
174
+ if (!VALIDATABLE_TYPES.has(key as never)) {
175
+ throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
176
+ }
177
+
178
+ const value = (original as PlainObject)[key];
179
+
180
+ validators[key as ValueName] = (Array.isArray(value) ? value : [value]).filter(item => {
181
+ if (typeof item !== 'function') {
182
+ throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
183
+ }
184
+
185
+ return true;
186
+ });
187
+ }
188
+
189
+ return validators;
190
+ }
@@ -1,73 +1,49 @@
1
- import type {PlainObject} from '@oscarpalmer/atoms/models';
2
- import {smush} from '@oscarpalmer/atoms/value/misc';
3
- import {TYPE_UNDEFINED} from '../constants';
4
- import type {ValidatedProperty, ValidatedPropertyType, ValidatedSchema, Values} from '../models';
1
+ import {isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import {isSchematic} from '../is';
3
+ import type {ValidatedProperty, ValidatedPropertyType, ValueName} from '../models';
5
4
 
6
- export function validateType(
7
- type: ValidatedPropertyType,
8
- property: ValidatedProperty,
9
- value: unknown,
10
- ): boolean {
11
- switch (true) {
12
- case typeof type === 'function':
13
- return (type as any)(value);
14
-
15
- case typeof type === 'string':
16
- return (
17
- validators[type](value) &&
18
- (property.validators[type]?.every(validator => validator(value)) ?? true)
19
- );
20
-
21
- default:
22
- return type.is(value);
23
- }
24
- }
25
-
26
- export function validateValue(validated: ValidatedSchema, obj: unknown): boolean {
27
- if (typeof obj !== 'object' || obj === null) {
5
+ export function validateObject(obj: unknown, properties: ValidatedProperty[]): boolean {
6
+ if (!isPlainObject(obj)) {
28
7
  return false;
29
8
  }
30
9
 
31
- const {keys, properties} = validated;
32
- const keysLength = keys.array.length;
10
+ const ignoredKeys = new Set<string>();
11
+ const propertiesLength = properties.length;
33
12
 
34
- const ignore = new Set<string>();
13
+ let key!: string;
14
+ let value!: unknown;
35
15
 
36
- const smushed = smush(obj as PlainObject);
16
+ outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
17
+ const property = properties[propertyIndex];
37
18
 
38
- outer: for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
39
- const key = keys.array[keyIndex];
19
+ if (ignoredKeys.has(property.key.prefix!)) {
20
+ key = undefined as never;
40
21
 
41
- const prefix = key.replace(EXPRESSION_SUFFIX, '');
22
+ ignoredKeys.add(property.key.full);
42
23
 
43
- if (ignore.has(prefix)) {
44
24
  continue;
45
25
  }
46
26
 
47
- const property = properties[key];
48
- const value = smushed[key];
27
+ /* if (key == null || !property.key.full.startsWith(key)) {
28
+ value = obj[property.key.full];
29
+ } else {
30
+ value = (value as PlainObject)?.[property.key.value];
31
+ } */
32
+
33
+ key = property.key.full;
34
+ value = obj[key];
49
35
 
50
- if (value === undefined && property.required && !property.types.includes(TYPE_UNDEFINED)) {
36
+ if (value === undefined && property.required) {
51
37
  return false;
52
38
  }
53
39
 
54
40
  const typesLength = property.types.length;
55
41
 
56
- if (typesLength === 1) {
57
- if (!validateType(property.types[0], property, value)) {
58
- return false;
59
- }
60
-
61
- continue;
62
- }
63
-
64
42
  for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
65
43
  const type = property.types[typeIndex];
66
44
 
67
- if (validateType(type, property, value)) {
68
- if ((type as never) !== 'object') {
69
- ignore.add(key);
70
- }
45
+ if (validateValue(type, property, value)) {
46
+ ignoredKeys.add(property.key.full);
71
47
 
72
48
  continue outer;
73
49
  }
@@ -79,20 +55,36 @@ export function validateValue(validated: ValidatedSchema, obj: unknown): boolean
79
55
  return true;
80
56
  }
81
57
 
82
- //
58
+ function validateValue(
59
+ type: ValidatedPropertyType,
60
+ property: ValidatedProperty,
61
+ value: unknown,
62
+ ): boolean {
63
+ switch (true) {
64
+ case typeof type === 'function':
65
+ return (type as (value: unknown) => boolean)(value);
66
+
67
+ case isSchematic(type):
68
+ return type.is(value);
83
69
 
84
- const EXPRESSION_SUFFIX = /\.\w+$/;
70
+ default:
71
+ return (
72
+ validators[type as ValueName](value) &&
73
+ (property.validators[type as ValueName]?.every(validator => validator(value)) ?? true)
74
+ );
75
+ }
76
+ }
85
77
 
86
78
  //
87
79
 
88
- const validators: Record<keyof Values, (value: unknown) => boolean> = {
80
+ const validators: Record<ValueName, (value: unknown) => boolean> = {
89
81
  array: Array.isArray,
90
82
  bigint: value => typeof value === 'bigint',
91
83
  boolean: value => typeof value === 'boolean',
92
84
  date: value => value instanceof Date,
93
85
  function: value => typeof value === 'function',
94
86
  null: value => value === null,
95
- number: value => typeof value === 'number' && !Number.isNaN(value),
87
+ number: value => typeof value === 'number',
96
88
  object: value => typeof value === 'object' && value !== null,
97
89
  string: value => typeof value === 'string',
98
90
  symbol: value => typeof value === 'symbol',
@@ -1,6 +1,7 @@
1
1
  export declare const ERROR_NAME = "SchematicError";
2
- export declare const EXPRESSION_HAS_NUMBER: RegExp;
3
2
  export declare const EXPRESSION_INDEX: RegExp;
3
+ export declare const EXPRESSION_KEY_PREFIX: RegExp;
4
+ export declare const EXPRESSION_KEY_VALUE: RegExp;
4
5
  export declare const EXPRESSION_PROPERTY: RegExp;
5
6
  export declare const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
6
7
  export declare const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
package/types/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { isInstance } from './is';
1
+ export { instanceOf } from './is';
2
2
  export { SchematicError, type Schema, type TypedSchema } from './models';
3
3
  export { schematic, type Schematic } from './schematic';
package/types/is.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { Constructor } from './models';
2
2
  import type { Schematic } from './schematic';
3
- export declare function isInstance<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
3
+ export declare function instanceOf<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
4
4
  export declare function isSchematic(value: unknown): value is Schematic<never>;
package/types/models.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PlainObject, Simplify } from '@oscarpalmer/atoms/models';
1
+ import type { GenericCallback, PlainObject, Simplify } from '@oscarpalmer/atoms/models';
2
2
  import type { Schematic } from './schematic';
3
3
  export type Constructor<Instance = any> = new (...args: any[]) => Instance;
4
4
  type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
@@ -50,9 +50,9 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
50
50
  * A schema for validating objects
51
51
  */
52
52
  export type Schema = SchemaIndex;
53
- type SchemaEntry = Constructor | SchemaProperty | Schematic<unknown> | ValueName | NestedSchema;
53
+ type SchemaEntry = Constructor | Schema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
54
54
  interface SchemaIndex {
55
- [key: string]: SchemaEntry | SchemaEntry[];
55
+ [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
56
56
  }
57
57
  /**
58
58
  * A property definition with explicit type(s) and optional requirement flag
@@ -62,7 +62,7 @@ export type SchemaProperty = {
62
62
  $type: SchemaPropertyType | SchemaPropertyType[];
63
63
  $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
64
64
  };
65
- type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
65
+ type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
66
66
  export declare class SchematicError extends Error {
67
67
  constructor(message: string);
68
68
  }
@@ -123,21 +123,20 @@ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => voi
123
123
  type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
124
124
  type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
125
125
  export type ValidatedProperty = {
126
+ key: ValidatedPropertyKey;
126
127
  required: boolean;
127
128
  types: ValidatedPropertyType[];
128
129
  validators: ValidatedPropertyValidators;
129
130
  };
130
- export type ValidatedPropertyType = Schematic<unknown> | ValueName;
131
+ export type ValidatedPropertyKey = {
132
+ full: string;
133
+ prefix: string | undefined;
134
+ value: string;
135
+ };
136
+ export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValueName;
131
137
  export type ValidatedPropertyValidators = {
132
138
  [Key in ValueName]?: Array<(value: unknown) => boolean>;
133
139
  };
134
- export type ValidatedSchema = {
135
- keys: {
136
- array: string[];
137
- set: Set<string>;
138
- };
139
- properties: Record<string, ValidatedProperty>;
140
- };
141
140
  /**
142
141
  * Valid type name strings
143
142
  */
@@ -1,12 +1,12 @@
1
1
  import type { PlainObject } from '@oscarpalmer/atoms/models';
2
- import { type Infer, type Schema, type TypedSchema, type ValidatedSchema } from './models';
2
+ import { type Infer, type Schema, type TypedSchema, type ValidatedProperty } 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
- constructor(schema: ValidatedSchema);
9
+ constructor(properties: ValidatedProperty[]);
10
10
  /**
11
11
  * Does the value match the schema?
12
12
  */
@@ -0,0 +1,3 @@
1
+ import type { PlainObject } from '@oscarpalmer/atoms/models';
2
+ import { type ValidatedProperty } from '../models';
3
+ export declare function getProperties(original: PlainObject): ValidatedProperty[];
@@ -1,3 +1,2 @@
1
- import type { ValidatedProperty, ValidatedPropertyType, ValidatedSchema } from '../models';
2
- export declare function validateType(type: ValidatedPropertyType, property: ValidatedProperty, value: unknown): boolean;
3
- export declare function validateValue(validated: ValidatedSchema, obj: unknown): boolean;
1
+ import type { ValidatedProperty } from '../models';
2
+ export declare function validateObject(obj: unknown, properties: ValidatedProperty[]): boolean;
@@ -1,105 +0,0 @@
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
- import { isInstance, isSchematic } from "../is.js";
3
- import { SchematicError } from "../models.js";
4
- import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
5
- import { smush } from "@oscarpalmer/atoms/value/misc";
6
- function addPropertyType(to, key, values, validators, required) {
7
- if (to.keys.set.has(key)) {
8
- const property = to.properties[key];
9
- for (const type of values) property.types.push(type);
10
- } else {
11
- to.keys.array.push(key);
12
- to.keys.set.add(key);
13
- to.properties[key] = {
14
- required,
15
- types: values,
16
- validators: {}
17
- };
18
- }
19
- if (!required && !to.properties[key].types.includes("undefined")) to.properties[key].types.push(TYPE_UNDEFINED);
20
- to.properties[key].validators = validators;
21
- }
22
- function getSchema(schema) {
23
- return getValidatedSchema(schema, {
24
- keys: {
25
- array: [],
26
- set: /* @__PURE__ */ new Set()
27
- },
28
- properties: {}
29
- });
30
- }
31
- function getTypes(value, validated, prefix) {
32
- const propertyTypes = [];
33
- const values = Array.isArray(value) ? value : [value];
34
- const { length } = values;
35
- for (let index = 0; index < length; index += 1) {
36
- const type = values[index];
37
- if (isSchematic(type) || TYPE_ALL.has(type)) {
38
- propertyTypes.push(type);
39
- continue;
40
- }
41
- if (typeof type === "function") {
42
- propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
43
- continue;
44
- }
45
- if (!isPlainObject(type)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", prefix));
46
- if ("$type" in type) {
47
- propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
48
- continue;
49
- }
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
- propertyTypes.push(TYPE_OBJECT);
54
- getValidatedSchema(nested, validated, prefix);
55
- }
56
- return propertyTypes;
57
- }
58
- function getValidatedSchema(schema, validated, prefix) {
59
- const smushed = smush(schema);
60
- const keys = Object.keys(smushed);
61
- const { length } = keys;
62
- const arrayKeys = /* @__PURE__ */ new Set();
63
- const noPrefix = prefix == null;
64
- prefix = noPrefix ? "" : `${prefix}.`;
65
- for (let index = 0; index < length; index += 1) {
66
- const key = keys[index];
67
- const value = smushed[key];
68
- if (Array.isArray(value)) arrayKeys.add(key);
69
- if (EXPRESSION_PROPERTY.test(key)) continue;
70
- if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
71
- let required = true;
72
- let validators = {};
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
- }
78
- if (isObject && "$validators" in value) validators = getValidators(value[PROPERTY_VALIDATORS]);
79
- const prefixedKey = `${prefix}${key}`;
80
- const types = getTypes(value, validated, prefixedKey);
81
- if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", key));
82
- addPropertyType(validated, prefixedKey, types, validators, required);
83
- }
84
- if (noPrefix) validated.keys.array.sort();
85
- if (noPrefix && validated.keys.array.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
86
- return validated;
87
- }
88
- function getValidators(original) {
89
- const validators = {};
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
- });
102
- }
103
- return validators;
104
- }
105
- export { getSchema };