@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/src/models.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
1
+ import type {GenericCallback, PlainObject, Simplify} from '@oscarpalmer/atoms/models';
2
2
  import {ERROR_NAME} from './constants';
3
3
  import type {Schematic} from './schematic';
4
4
 
@@ -122,10 +122,16 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
122
122
  */
123
123
  export type Schema = SchemaIndex;
124
124
 
125
- type SchemaEntry = Constructor | SchemaProperty | Schematic<unknown> | ValueName | NestedSchema;
125
+ type SchemaEntry =
126
+ | Constructor
127
+ | Schema
128
+ | SchemaProperty
129
+ | Schematic<unknown>
130
+ | ValueName
131
+ | ((value: unknown) => boolean);
126
132
 
127
133
  interface SchemaIndex {
128
- [key: string]: SchemaEntry | SchemaEntry[];
134
+ [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
129
135
  }
130
136
 
131
137
  /**
@@ -137,7 +143,12 @@ export type SchemaProperty = {
137
143
  $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
138
144
  };
139
145
 
140
- type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
146
+ type SchemaPropertyType =
147
+ | Constructor
148
+ | Schema
149
+ | Schematic<unknown>
150
+ | ValueName
151
+ | ((value: unknown) => boolean);
141
152
 
142
153
  export class SchematicError extends Error {
143
154
  constructor(message: string) {
@@ -279,25 +290,22 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
279
290
  : Value;
280
291
 
281
292
  export type ValidatedProperty = {
293
+ key: string;
282
294
  required: boolean;
283
295
  types: ValidatedPropertyType[];
284
296
  validators: ValidatedPropertyValidators;
285
297
  };
286
298
 
287
- export type ValidatedPropertyType = Schematic<unknown> | ValueName;
299
+ export type ValidatedPropertyType =
300
+ | GenericCallback
301
+ | Schematic<unknown>
302
+ | ValidatedProperty
303
+ | ValueName;
288
304
 
289
305
  export type ValidatedPropertyValidators = {
290
306
  [Key in ValueName]?: Array<(value: unknown) => boolean>;
291
307
  };
292
308
 
293
- export type ValidatedSchema = {
294
- keys: {
295
- array: string[];
296
- set: Set<string>;
297
- };
298
- properties: Record<string, ValidatedProperty>;
299
- };
300
-
301
309
  /**
302
310
  * Valid type name strings
303
311
  */
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 './helpers';
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,187 @@
1
+ import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
3
+ import {join} from '@oscarpalmer/atoms/string/misc';
4
+ import {
5
+ EXPRESSION_INDEX,
6
+ EXPRESSION_PROPERTY,
7
+ MESSAGE_SCHEMA_INVALID_EMPTY,
8
+ MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED,
9
+ MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE,
10
+ MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
11
+ MESSAGE_VALIDATOR_INVALID_KEY,
12
+ MESSAGE_VALIDATOR_INVALID_TYPE,
13
+ MESSAGE_VALIDATOR_INVALID_VALUE,
14
+ PROPERTY_REQUIRED,
15
+ PROPERTY_TYPE,
16
+ PROPERTY_VALIDATORS,
17
+ TEMPLATE_PATTERN,
18
+ TYPE_ALL,
19
+ TYPE_UNDEFINED,
20
+ VALIDATABLE_TYPES,
21
+ } from '../constants';
22
+ import {instanceOf, isSchematic} from '../helpers';
23
+ import {
24
+ SchematicError,
25
+ type ValidatedProperty,
26
+ type ValidatedPropertyType,
27
+ type ValidatedPropertyValidators,
28
+ type ValueName,
29
+ } from '../models';
30
+
31
+ export function getProperties(
32
+ original: PlainObject,
33
+ prefix?: string,
34
+ fromTypes?: boolean,
35
+ ): ValidatedProperty[] {
36
+ if (Object.keys(original).length === 0) {
37
+ throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
38
+ }
39
+
40
+ if (PROPERTY_REQUIRED in original && (fromTypes ?? false) && prefix != null) {
41
+ throw new SchematicError(
42
+ MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED.replace(TEMPLATE_PATTERN, prefix),
43
+ );
44
+ }
45
+
46
+ const keys = Object.keys(original);
47
+ const keysLength = keys.length;
48
+
49
+ const properties: ValidatedProperty[] = [];
50
+
51
+ for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
52
+ const key = keys[keyIndex];
53
+
54
+ if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) {
55
+ continue;
56
+ }
57
+
58
+ const value = original[key];
59
+
60
+ const types: ValidatedPropertyType[] = [];
61
+
62
+ let required = true;
63
+ let validators: ValidatedPropertyValidators = {};
64
+
65
+ if (isPlainObject(value)) {
66
+ required = getRequired(key, value) ?? required;
67
+ validators = getValidators(value[PROPERTY_VALIDATORS]);
68
+
69
+ if (PROPERTY_TYPE in value) {
70
+ types.push('object', ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
71
+ } else {
72
+ types.push('object', ...getTypes(key, value, prefix));
73
+ }
74
+ } else {
75
+ types.push(...getTypes(key, value, prefix));
76
+ }
77
+
78
+ if (!required && !types.includes(TYPE_UNDEFINED)) {
79
+ types.push(TYPE_UNDEFINED);
80
+ }
81
+
82
+ properties.push({
83
+ key,
84
+ types,
85
+ validators,
86
+ required: required && !types.includes(TYPE_UNDEFINED),
87
+ });
88
+ }
89
+
90
+ return properties;
91
+ }
92
+
93
+ function getRequired(key: string, obj: PlainObject): boolean | undefined {
94
+ if (!(PROPERTY_REQUIRED in obj)) {
95
+ return;
96
+ }
97
+
98
+ if (typeof obj[PROPERTY_REQUIRED] !== 'boolean') {
99
+ throw new SchematicError(
100
+ MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE.replace(TEMPLATE_PATTERN, key),
101
+ );
102
+ }
103
+
104
+ return obj[PROPERTY_REQUIRED];
105
+ }
106
+
107
+ function getTypes(
108
+ key: string,
109
+ original: unknown,
110
+ prefix?: string,
111
+ fromTypes?: boolean,
112
+ ): ValidatedPropertyType[] {
113
+ const array = Array.isArray(original) ? original : [original];
114
+ const {length} = array;
115
+
116
+ const types: ValidatedPropertyType[] = [];
117
+
118
+ for (let index = 0; index < length; index += 1) {
119
+ const value = array[index];
120
+
121
+ switch (true) {
122
+ case typeof value === 'function':
123
+ types.push(isConstructor(value) ? instanceOf(value) : value);
124
+ break;
125
+
126
+ case isPlainObject(value):
127
+ types.push(...getProperties(value, join([prefix, key], '.'), fromTypes));
128
+ break;
129
+
130
+ case isSchematic(value):
131
+ types.push(value);
132
+ break;
133
+
134
+ case TYPE_ALL.has(value as never):
135
+ types.push(value as never);
136
+ break;
137
+
138
+ default:
139
+ throw new SchematicError(
140
+ MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], '.')),
141
+ );
142
+ }
143
+ }
144
+
145
+ if (types.length === 0) {
146
+ throw new SchematicError(
147
+ MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], '.')),
148
+ );
149
+ }
150
+
151
+ return types;
152
+ }
153
+
154
+ function getValidators(original: unknown): ValidatedPropertyValidators {
155
+ const validators: ValidatedPropertyValidators = {};
156
+
157
+ if (original == null) {
158
+ return validators;
159
+ }
160
+
161
+ if (!isPlainObject(original)) {
162
+ throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
163
+ }
164
+
165
+ const keys = Object.keys(original);
166
+ const {length} = keys;
167
+
168
+ for (let index = 0; index < length; index += 1) {
169
+ const key = keys[index];
170
+
171
+ if (!VALIDATABLE_TYPES.has(key as never)) {
172
+ throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
173
+ }
174
+
175
+ const value = (original as PlainObject)[key];
176
+
177
+ validators[key as ValueName] = (Array.isArray(value) ? value : [value]).filter(item => {
178
+ if (typeof item !== 'function') {
179
+ throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
180
+ }
181
+
182
+ return true;
183
+ });
184
+ }
185
+
186
+ return validators;
187
+ }
@@ -1,74 +1,31 @@
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 '../helpers';
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 propertiesLength = properties.length;
33
11
 
34
- const ignore = new Set<string>();
12
+ outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
13
+ const property = properties[propertyIndex];
35
14
 
36
- const smushed = smush(obj as PlainObject);
15
+ const {key, required, types} = property;
37
16
 
38
- outer: for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
39
- const key = keys.array[keyIndex];
17
+ const value = obj[key];
40
18
 
41
- const prefix = key.replace(EXPRESSION_SUFFIX, '');
42
-
43
- if (ignore.has(prefix)) {
44
- continue;
45
- }
46
-
47
- const property = properties[key];
48
- const value = smushed[key];
49
-
50
- if (value === undefined && property.required && !property.types.includes(TYPE_UNDEFINED)) {
19
+ if (value === undefined && required) {
51
20
  return false;
52
21
  }
53
22
 
54
- const typesLength = property.types.length;
55
-
56
- if (typesLength === 1) {
57
- if (!validateType(property.types[0], property, value)) {
58
- return false;
59
- }
60
-
61
- continue;
62
- }
23
+ const typesLength = types.length;
63
24
 
64
25
  for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
65
- const type = property.types[typeIndex];
66
-
67
- if (validateType(type, property, value)) {
68
- if ((type as never) !== 'object') {
69
- ignore.add(key);
70
- }
26
+ const type = types[typeIndex];
71
27
 
28
+ if (validateValue(type, property, value)) {
72
29
  continue outer;
73
30
  }
74
31
  }
@@ -79,20 +36,39 @@ export function validateValue(validated: ValidatedSchema, obj: unknown): boolean
79
36
  return true;
80
37
  }
81
38
 
82
- //
39
+ function validateValue(
40
+ type: ValidatedPropertyType,
41
+ property: ValidatedProperty,
42
+ value: unknown,
43
+ ): boolean {
44
+ switch (true) {
45
+ case isSchematic(type):
46
+ return type.is(value);
47
+
48
+ case typeof type === 'function':
49
+ return (type as (value: unknown) => boolean)(value);
83
50
 
84
- const EXPRESSION_SUFFIX = /\.\w+$/;
51
+ case typeof type === 'object':
52
+ return validateObject(value, [type] as ValidatedProperty[]);
53
+
54
+ default:
55
+ return (
56
+ validators[type as ValueName](value) &&
57
+ (property.validators[type as ValueName]?.every(validator => validator(value)) ?? true)
58
+ );
59
+ }
60
+ }
85
61
 
86
62
  //
87
63
 
88
- const validators: Record<keyof Values, (value: unknown) => boolean> = {
64
+ const validators: Record<ValueName, (value: unknown) => boolean> = {
89
65
  array: Array.isArray,
90
66
  bigint: value => typeof value === 'bigint',
91
67
  boolean: value => typeof value === 'boolean',
92
68
  date: value => value instanceof Date,
93
69
  function: value => typeof value === 'function',
94
70
  null: value => value === null,
95
- number: value => typeof value === 'number' && !Number.isNaN(value),
71
+ number: value => typeof value === 'number',
96
72
  object: value => typeof value === 'object' && value !== null,
97
73
  string: value => typeof value === 'string',
98
74
  symbol: value => typeof value === 'symbol',
@@ -1,10 +1,12 @@
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";
7
- export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
8
+ export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_DISALLOWED = "'<>.$required' property is not allowed for schemas in $type";
9
+ export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED_TYPE = "'<>.$required' property must be a boolean";
8
10
  export declare const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
9
11
  export declare const MESSAGE_SCHEMA_INVALID_TYPE = "Schema must be an object";
10
12
  export declare const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
@@ -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/index.d.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';
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,15 @@ 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: string;
126
127
  required: boolean;
127
128
  types: ValidatedPropertyType[];
128
129
  validators: ValidatedPropertyValidators;
129
130
  };
130
- export type ValidatedPropertyType = Schematic<unknown> | ValueName;
131
+ export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValidatedProperty | ValueName;
131
132
  export type ValidatedPropertyValidators = {
132
133
  [Key in ValueName]?: Array<(value: unknown) => boolean>;
133
134
  };
134
- export type ValidatedSchema = {
135
- keys: {
136
- array: string[];
137
- set: Set<string>;
138
- };
139
- properties: Record<string, ValidatedProperty>;
140
- };
141
135
  /**
142
136
  * Valid type name strings
143
137
  */
@@ -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, prefix?: string, fromTypes?: boolean): 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 };