@oscarpalmer/jhunal 0.10.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/constants.ts CHANGED
@@ -1,10 +1,31 @@
1
- import type {Values} from './models';
1
+ import type {ValueName} from './models';
2
2
 
3
- export const EXPRESSION_HAS_NUMBER = /\d+/;
3
+ export const ERROR_NAME = 'SchematicError';
4
4
 
5
5
  export const EXPRESSION_INDEX = /\.\d+$/;
6
6
 
7
- export const EXPRESSION_PROPERTY = /\.\$(required|type|validators)(\.|$)/;
7
+ export const EXPRESSION_KEY_PREFIX = /\.\w+$/;
8
+
9
+ export const EXPRESSION_KEY_VALUE = /^.*\.(\w+)$/;
10
+
11
+ export const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
12
+
13
+ export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
14
+
15
+ export const MESSAGE_SCHEMA_INVALID_EMPTY = 'Schema must have at least one property';
16
+
17
+ export const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
18
+
19
+ export const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
20
+
21
+ export const MESSAGE_SCHEMA_INVALID_TYPE = 'Schema must be an object';
22
+
23
+ export const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
24
+
25
+ export const MESSAGE_VALIDATOR_INVALID_TYPE = 'Validators must be an object';
26
+
27
+ export const MESSAGE_VALIDATOR_INVALID_VALUE =
28
+ "Validator '<>' must be a function or an array of functions";
8
29
 
9
30
  export const PROPERTY_REQUIRED = '$required';
10
31
 
@@ -14,20 +35,22 @@ export const PROPERTY_VALIDATORS = '$validators';
14
35
 
15
36
  export const SCHEMATIC_NAME = '$schematic';
16
37
 
38
+ export const TEMPLATE_PATTERN = '<>';
39
+
17
40
  export const TYPE_OBJECT = 'object';
18
41
 
19
42
  export const TYPE_UNDEFINED = 'undefined';
20
43
 
21
- export const TYPE_ALL = new Set<keyof Values>([
44
+ export const VALIDATABLE_TYPES = new Set<ValueName>([
22
45
  'array',
23
46
  'bigint',
24
47
  'boolean',
25
48
  'date',
26
49
  'function',
27
- 'null',
28
50
  'number',
29
51
  'string',
30
52
  'symbol',
31
53
  TYPE_OBJECT,
32
- TYPE_UNDEFINED,
33
54
  ]);
55
+
56
+ export const TYPE_ALL = new Set<ValueName>([...VALIDATABLE_TYPES, 'null', TYPE_UNDEFINED]);
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export {isInstance} from './is';
2
- export type {Schema, TypedSchema} from './models';
1
+ export {instanceOf} from './is';
2
+ export {SchematicError, type Schema, type TypedSchema} from './models';
3
3
  export {schematic, type Schematic} from './schematic';
package/src/is.ts CHANGED
@@ -1,13 +1,13 @@
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
 
6
- export function isInstance<Instance>(
6
+ export function instanceOf<Instance>(
7
7
  constructor: Constructor<Instance>,
8
8
  ): (value: unknown) => value is Instance {
9
9
  if (!isConstructor(constructor)) {
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
- import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
1
+ import type {GenericCallback, 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,10 +122,16 @@ 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 =
126
+ | Constructor
127
+ | Schema
128
+ | SchemaProperty
129
+ | Schematic<unknown>
130
+ | ValueName
131
+ | ((value: unknown) => boolean);
118
132
 
119
133
  interface SchemaIndex {
120
- [key: string]: SchemaEntry | SchemaEntry[];
134
+ [key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
121
135
  }
122
136
 
123
137
  /**
@@ -129,15 +143,30 @@ export type SchemaProperty = {
129
143
  $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
130
144
  };
131
145
 
132
- type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
146
+ type SchemaPropertyType =
147
+ | Constructor
148
+ | Schema
149
+ | Schematic<unknown>
150
+ | ValueName
151
+ | ((value: unknown) => boolean);
152
+
153
+ export class SchematicError extends Error {
154
+ constructor(message: string) {
155
+ super(message);
156
+
157
+ this.name = ERROR_NAME;
158
+ }
159
+ }
133
160
 
134
161
  type ToSchemaPropertyType<Value> = UnwrapSingle<
135
162
  DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>
136
163
  >;
137
164
 
138
- type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject
139
- ? TypedSchema<Value>
140
- : ToValueType<Value>;
165
+ type ToSchemaPropertyTypeEach<Value> = Value extends NestedSchema
166
+ ? Omit<Value, '$required'>
167
+ : Value extends PlainObject
168
+ ? TypedSchema<Value>
169
+ : ToValueType<Value>;
141
170
 
142
171
  type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
143
172
 
@@ -149,21 +178,23 @@ type ToValueType<Value> = Value extends unknown[]
149
178
  ? 'boolean'
150
179
  : Value extends Date
151
180
  ? '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;
181
+ : Value extends Schematic<any>
182
+ ? Value
183
+ : Value extends Function
184
+ ? 'function'
185
+ : Value extends null
186
+ ? 'null'
187
+ : Value extends number
188
+ ? 'number'
189
+ : Value extends object
190
+ ? 'object' | ((value: unknown) => value is Value)
191
+ : Value extends string
192
+ ? 'string'
193
+ : Value extends symbol
194
+ ? 'symbol'
195
+ : Value extends undefined
196
+ ? 'undefined'
197
+ : (value: unknown) => value is Value;
167
198
 
168
199
  type TuplePermutations<
169
200
  Tuple extends unknown[],
@@ -223,11 +254,13 @@ export type TypedPropertyRequired<Value> = {
223
254
  export type TypedSchema<Model extends PlainObject> = Simplify<
224
255
  {
225
256
  [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject
226
- ? TypedSchemaRequired<Model[Key]>
257
+ ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]>
227
258
  : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
228
259
  } & {
229
260
  [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject
230
- ? TypedSchemaOptional<Exclude<Model[Key], undefined>>
261
+ ?
262
+ | TypedSchemaOptional<Exclude<Model[Key], undefined>>
263
+ | Schematic<Exclude<Model[Key], undefined>>
231
264
  : TypedPropertyOptional<Model[Key]>;
232
265
  }
233
266
  >;
@@ -257,26 +290,24 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
257
290
  : Value;
258
291
 
259
292
  export type ValidatedProperty = {
293
+ key: ValidatedPropertyKey;
260
294
  required: boolean;
261
295
  types: ValidatedPropertyType[];
262
296
  validators: ValidatedPropertyValidators;
263
297
  };
264
298
 
265
- export type ValidatedPropertyType = Schematic<unknown> | ValueName;
299
+ export type ValidatedPropertyKey = {
300
+ full: string;
301
+ prefix: string | undefined;
302
+ value: string;
303
+ };
304
+
305
+ export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValueName;
266
306
 
267
307
  export type ValidatedPropertyValidators = {
268
308
  [Key in ValueName]?: Array<(value: unknown) => boolean>;
269
309
  };
270
310
 
271
- export type ValidatedSchema = {
272
- enabled: boolean;
273
- keys: {
274
- array: string[];
275
- set: Set<string>;
276
- };
277
- properties: Record<string, ValidatedProperty>;
278
- };
279
-
280
311
  /**
281
312
  * Valid type name strings
282
313
  */
package/src/schematic.ts CHANGED
@@ -1,8 +1,16 @@
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';
4
- import {getSchema} from './validation/schema.validation';
5
- import {validateValue} from './validation/value.validation';
3
+ import {MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME} from './constants';
4
+ import {isSchematic} from './is';
5
+ import {
6
+ SchematicError,
7
+ type Infer,
8
+ type Schema,
9
+ type TypedSchema,
10
+ type ValidatedProperty,
11
+ } from './models';
12
+ import {getProperties} from './validation/property.validation';
13
+ import {validateObject} from './validation/value.validation';
6
14
 
7
15
  /**
8
16
  * A schematic for validating objects
@@ -10,27 +18,21 @@ import {validateValue} from './validation/value.validation';
10
18
  export class Schematic<Model> {
11
19
  declare private readonly $schematic: true;
12
20
 
13
- #schema: ValidatedSchema;
21
+ #properties: ValidatedProperty[];
14
22
 
15
- get enabled(): boolean {
16
- return this.#schema.enabled;
17
- }
18
-
19
- constructor(schema: Model) {
23
+ constructor(properties: ValidatedProperty[]) {
20
24
  Object.defineProperty(this, SCHEMATIC_NAME, {
21
25
  value: true,
22
26
  });
23
27
 
24
- this.#schema = getSchema(schema);
25
-
26
- this.#schema.enabled = this.#schema.keys.array.length > 0;
28
+ this.#properties = properties;
27
29
  }
28
30
 
29
31
  /**
30
32
  * Does the value match the schema?
31
33
  */
32
34
  is(value: unknown): value is Model {
33
- return this.#schema.enabled && validateValue(this.#schema, value);
35
+ return validateObject(value, this.#properties);
34
36
  }
35
37
  }
36
38
 
@@ -45,5 +47,13 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<
45
47
  export function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
46
48
 
47
49
  export function schematic<Model extends Schema>(schema: Model): Schematic<Model> {
48
- return new Schematic<Model>(schema);
50
+ if (isSchematic(schema)) {
51
+ return schema;
52
+ }
53
+
54
+ if (!isPlainObject(schema)) {
55
+ throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
56
+ }
57
+
58
+ return new Schematic<Model>(getProperties(schema));
49
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',