@cleverbrush/schema 0.0.12 → 0.0.15

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/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { Merge } from '@cleverbrush/deep';
1
2
  import SchemaValidator from './schemaValidator';
2
- export declare type DefaultSchemaType = 'object' | 'function' | 'number' | 'string' | 'array' | 'alias';
3
+ export declare type DefaultSchemaType = 'object' | 'boolean' | 'function' | 'number' | 'string' | 'array' | 'alias';
3
4
  export declare type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];
4
5
  export declare type DefaultPropertyDefinition = {
5
6
  isRequired: boolean;
@@ -24,7 +25,7 @@ export declare type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'>
24
25
  [S in keyof T]: Schema<PropType<T, S>>;
25
26
  }>;
26
27
  preprocessors?: Partial<{
27
- [S in keyof T | '*']: S extends keyof T ? ((value: unknown) => PropType<T, S> | Promise<PropType<T, S>>) | string : (value: T) => void | Promise<void>;
28
+ [S in keyof T | '*']: S extends keyof T ? ((value: unknown) => undefined | PropType<T, S> | Promise<PropType<T, S>>) | string : (value: T) => void | Promise<void>;
28
29
  }>;
29
30
  };
30
31
  export declare type AliasSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
@@ -39,6 +40,10 @@ export declare type FunctionSchemaDefinition<T extends (...args: any) => any> =
39
40
  type: 'function';
40
41
  params?: ParamsValidators<T>;
41
42
  };
43
+ export declare type BooleanSchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 'type'> & {
44
+ type: 'boolean';
45
+ equals?: boolean;
46
+ };
42
47
  export declare type NumberSchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 'type'> & {
43
48
  type: 'number';
44
49
  min?: number;
@@ -61,25 +66,29 @@ export declare type ArraySchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 't
61
66
  minLength?: number;
62
67
  maxLength?: number;
63
68
  };
64
- export declare type CompositeSchema<TObj> = TObj extends (...args: any) => any ? FunctionSchemaDefinition<TObj> : TObj extends number ? NumberSchemaDefinition<TObj> : TObj extends string ? StringSchemaDefinition<TObj> | 'string' : NumberSchemaDefinition<TObj> | StringSchemaDefinition<TObj> | ArraySchemaDefinition<TObj> | ObjectSchemaDefinition<TObj> | AliasSchemaDefinition<TObj>;
69
+ export declare type CompositeSchema<TObj> = TObj extends (...args: any) => any ? FunctionSchemaDefinition<TObj> : TObj extends number ? NumberSchemaDefinition<TObj> : TObj extends string ? StringSchemaDefinition<TObj> | 'string' : BooleanSchemaDefinition<TObj> | NumberSchemaDefinition<TObj> | StringSchemaDefinition<TObj> | ArraySchemaDefinition<TObj> | ObjectSchemaDefinition<TObj> | AliasSchemaDefinition<TObj>;
65
70
  export declare type SingleSchema<TObj = Record<string, never>> = number | string | DefaultSchemaType | CompositeSchema<TObj>;
66
71
  export declare type Schema<TObj> = SingleSchema<TObj> | Array<SingleSchema<TObj>>;
67
- export interface ISchemaActions<K, T extends keyof K> {
72
+ export declare type Cons<H, T extends unknown[] = []> = T['length'] extends 0 ? [H] : ((h: H, ...t: T) => void) extends (...r: infer R) => void ? R : never;
73
+ export interface ISchemaActions<S> {
68
74
  validate(value: any): Promise<ValidationResult>;
69
- schema: PropType<K, T>;
75
+ schema: S;
70
76
  }
71
- export interface ISchemasProvider<T = Record<string, never>> {
72
- schemas: {
73
- [K in keyof T]: ISchemaActions<T, K>;
74
- };
77
+ export interface ISchemasProvider<T extends unknown[]> {
78
+ schemas: Merge<T>;
75
79
  }
76
- export interface ISchemaValidator<T = Record<string, never>> {
80
+ export interface ISchemaValidator<T extends Record<string, never> = Record<string, never>, SchemaTypesStructures extends unknown[] = []> {
77
81
  get preprocessors(): Map<string, (value: unknown) => unknown | Promise<unknown>>;
78
- addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T>;
79
- addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
82
+ addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T, SchemaTypesStructures>;
83
+ addSchemaType<K, L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>, M = any>(name: keyof K, schema: L): SchemaValidator<T & {
80
84
  [key in keyof K]: typeof schema;
81
- }>;
85
+ }, Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>>;
82
86
  validate(schema: keyof T | DefaultSchemaType | Schema<any>, obj: any): Promise<ValidationResult>;
83
87
  }
88
+ export declare type Unfold<T, K> = T extends string ? T extends `${infer F}.${infer L}` ? {
89
+ [k in F]: Unfold<L, K>;
90
+ } : {
91
+ [k in T]: K;
92
+ } : never;
84
93
  export { SchemaValidator };
85
94
  export default SchemaValidator;
@@ -1,16 +1,15 @@
1
- import { ISchemasProvider, Schema, ISchemaActions, ObjectSchemaDefinitionParam, ValidationResult, ISchemaValidator, DefaultSchemaType } from './index';
2
- export default class SchemaValidator<T = Record<string, never>> implements ISchemasProvider<T>, ISchemaValidator<T> {
1
+ import { Merge } from '@cleverbrush/deep';
2
+ import { ISchemasProvider, Schema, ISchemaActions, ObjectSchemaDefinitionParam, ValidationResult, ISchemaValidator, DefaultSchemaType, Cons, Unfold } from './index';
3
+ export default class SchemaValidator<T extends Record<string, never> = Record<string, never>, SchemaTypesStructures extends unknown[] = []> implements ISchemasProvider<SchemaTypesStructures>, ISchemaValidator<T, SchemaTypesStructures> {
3
4
  private _schemasMap;
4
5
  private _schemasCache;
5
6
  private _preprocessorsMap;
6
7
  get preprocessors(): Map<string, (value: unknown) => unknown>;
7
- addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T>;
8
- addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
8
+ addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T, SchemaTypesStructures>;
9
+ addSchemaType<K, L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>, M = any>(name: keyof K, schema: L): SchemaValidator<T & {
9
10
  [key in keyof K]: L;
10
- }>;
11
- get schemas(): {
12
- [K in keyof T]: ISchemaActions<T, K>;
13
- };
11
+ }, Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>>;
12
+ get schemas(): Merge<SchemaTypesStructures>;
14
13
  private validateDefaultType;
15
14
  private checkValidators;
16
15
  validate<K>(schema: keyof T | DefaultSchemaType | Schema<K>, obj: any): Promise<ValidationResult>;
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const deep_1 = require("@cleverbrush/deep");
4
4
  const validateNumber_1 = require("./validators/validateNumber");
5
+ const validateBoolean_1 = require("./validators/validateBoolean");
5
6
  const validateString_1 = require("./validators/validateString");
6
7
  const validateArray_1 = require("./validators/validateArray");
7
8
  const validateObject_1 = require("./validators/validateObject");
8
- const defaultSchemaNames = ['string', 'number', 'date', 'array'];
9
+ const defaultSchemaNames = ['string', 'boolean', 'number', 'date', 'array'];
9
10
  const defaultSchemas = {
10
11
  number: {
11
12
  type: 'number',
@@ -14,6 +15,11 @@ const defaultSchemas = {
14
15
  ensureNotNaN: true,
15
16
  ensureIsFinite: true
16
17
  },
18
+ boolean: {
19
+ type: 'boolean',
20
+ isRequired: true,
21
+ isNullable: false
22
+ },
17
23
  string: {
18
24
  type: 'string',
19
25
  isNullable: false,
@@ -30,6 +36,7 @@ const defaultSchemas = {
30
36
  };
31
37
  const defaultSchemasValidationStrategies = {
32
38
  number: (obj, schema, validator) => (0, validateNumber_1.validateNumber)(obj, schema, validator),
39
+ boolean: (obj, schema, validator) => (0, validateBoolean_1.validateBoolean)(obj, schema, validator),
33
40
  string: (obj, schema, validator) => (0, validateString_1.validateString)(obj, schema, validator),
34
41
  array: (obj, schema, validator) => (0, validateArray_1.validateArray)(obj, schema, validator)
35
42
  };
@@ -74,10 +81,22 @@ class SchemaValidator {
74
81
  return this._schemasCache;
75
82
  const res = {};
76
83
  for (const key of this._schemasMap.keys()) {
77
- res[key] = {
78
- validate: (value) => this.validate(this._schemasMap.get(key), value),
79
- schema: this._schemasMap.get(key)
80
- };
84
+ const parts = key.split('.');
85
+ let curr = res;
86
+ for (let i = 0; i < parts.length; i++) {
87
+ if (i === parts.length - 1) {
88
+ curr[parts[i]] = {
89
+ validate: (value) => this.validate(this._schemasMap.get(key), value),
90
+ schema: this._schemasMap.get(key)
91
+ };
92
+ }
93
+ else {
94
+ if (typeof curr[parts[i]] === 'undefined') {
95
+ curr[parts[i]] = {};
96
+ }
97
+ }
98
+ curr = curr[parts[i]];
99
+ }
81
100
  }
82
101
  this._schemasCache = res;
83
102
  return this._schemasCache;
@@ -142,12 +161,11 @@ class SchemaValidator {
142
161
  if (isDefaultType(schema)) {
143
162
  return await this.validateDefaultType(schema, obj);
144
163
  }
145
- else if (typeof this.schemas[schema] !== 'undefined') {
146
- if (Array.isArray(this.schemas[schema].schema)) {
147
- return this.validate(this.schemas[schema]
148
- .schema, obj);
164
+ else if (typeof this._schemasMap.get(schema) !== 'undefined') {
165
+ if (Array.isArray(this._schemasMap.get(schema))) {
166
+ return this.validate(this._schemasMap.get(schema), obj);
149
167
  }
150
- const objSchema = (0, deep_1.deepExtend)(defaultSchemas.object, this.schemas[schema].schema);
168
+ const objSchema = (0, deep_1.deepExtend)(defaultSchemas.object, this._schemasMap.get(schema));
151
169
  const res = await (0, validateObject_1.validateObject)(obj, objSchema, this);
152
170
  if (!res.valid)
153
171
  return res;
@@ -0,0 +1,2 @@
1
+ import { ValidationResult, BooleanSchemaDefinition, ISchemaValidator } from '../index';
2
+ export declare const validateBoolean: (obj: any, schema: BooleanSchemaDefinition<any>, validator: ISchemaValidator<any>) => Promise<ValidationResult>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateBoolean = void 0;
4
+ const validateBoolean = async (obj, schema, validator) => {
5
+ if (typeof obj === 'undefined' &&
6
+ typeof schema === 'object' &&
7
+ schema.isRequired === false) {
8
+ return {
9
+ valid: true
10
+ };
11
+ }
12
+ if (typeof obj !== 'boolean')
13
+ return {
14
+ valid: false,
15
+ errors: [`expected type boolean, but saw ${typeof obj}`]
16
+ };
17
+ if (typeof schema === 'boolean') {
18
+ return { valid: true };
19
+ }
20
+ const bool = obj;
21
+ if (typeof schema.equals === 'boolean') {
22
+ return schema.equals === bool
23
+ ? { valid: true }
24
+ : {
25
+ valid: false,
26
+ errors: [`must be equal to ${schema.equals}`]
27
+ };
28
+ }
29
+ return {
30
+ valid: true
31
+ };
32
+ };
33
+ exports.validateBoolean = validateBoolean;
@@ -1,2 +1,2 @@
1
1
  import { ValidationResult, ObjectSchemaDefinition, ISchemaValidator } from '../index';
2
- export declare const validateObject: (obj: any, schema: ObjectSchemaDefinition<any>, validator: ISchemaValidator<any>) => Promise<ValidationResult>;
2
+ export declare const validateObject: (obj: any, schema: ObjectSchemaDefinition<any>, validator: ISchemaValidator<any, unknown[]>) => Promise<ValidationResult>;
@@ -19,8 +19,10 @@ const validateObject = async (obj, schema, validator) => {
19
19
  }
20
20
  if (typeof schema.preprocessors === 'object' && schema.preprocessors) {
21
21
  await Promise.all(Object.keys(schema.preprocessors).map(async (key) => {
22
+ if (key === '*')
23
+ return;
22
24
  if (typeof schema.preprocessors[key] === 'function') {
23
- obj[key] = await Promise.resolve(schema.preprocessors[key](key === '*' ? obj : obj[key]));
25
+ obj[key] = await Promise.resolve(schema.preprocessors[key](obj[key]));
24
26
  }
25
27
  else {
26
28
  const preprocessor = validator.preprocessors.get(schema.preprocessors[key]);
@@ -30,6 +32,19 @@ const validateObject = async (obj, schema, validator) => {
30
32
  obj[key] = await Promise.resolve(preprocessor(obj[key]));
31
33
  }
32
34
  }));
35
+ if ('*' in schema.preprocessors) {
36
+ const objectPreprocessor = schema.preprocessors['*'];
37
+ if (typeof objectPreprocessor === 'function') {
38
+ await Promise.resolve(objectPreprocessor(obj));
39
+ }
40
+ else {
41
+ const preprocessor = validator.preprocessors.get(objectPreprocessor);
42
+ if (typeof preprocessor !== 'function') {
43
+ throw new Error(`preprocessor '${objectPreprocessor}' is unknown`);
44
+ }
45
+ await Promise.resolve(preprocessor(obj));
46
+ }
47
+ }
33
48
  }
34
49
  if (typeof schema.properties === 'object' && schema.properties) {
35
50
  const errors = (await Promise.all(Object.entries(schema.properties).map(async ([name, schema]) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleverbrush/schema",
3
- "version": "0.0.12",
3
+ "version": "0.0.15",
4
4
  "keywords": [
5
5
  "object schema validator",
6
6
  "schema",
@@ -19,7 +19,7 @@
19
19
  "build": "tsc"
20
20
  },
21
21
  "dependencies": {
22
- "@cleverbrush/deep": "0.0.12"
22
+ "@cleverbrush/deep": "0.0.15"
23
23
  },
24
24
  "types": "./dist/index.d.ts"
25
25
  }
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import { Merge } from '@cleverbrush/deep';
1
2
  import SchemaValidator from './schemaValidator';
2
3
 
3
4
  export type DefaultSchemaType =
4
5
  | 'object'
6
+ | 'boolean'
5
7
  | 'function'
6
8
  | 'number'
7
9
  | 'string'
@@ -44,7 +46,7 @@ export type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
44
46
  ?
45
47
  | ((
46
48
  value: unknown
47
- ) => PropType<T, S> | Promise<PropType<T, S>>)
49
+ ) => undefined | PropType<T, S> | Promise<PropType<T, S>>)
48
50
  | string
49
51
  : (value: T) => void | Promise<void>;
50
52
  }>;
@@ -72,6 +74,14 @@ export type FunctionSchemaDefinition<T extends (...args: any) => any> = Omit<
72
74
  params?: ParamsValidators<T>;
73
75
  };
74
76
 
77
+ export type BooleanSchemaDefinition<TObj> = Omit<
78
+ SchemaDefintion<TObj>,
79
+ 'type'
80
+ > & {
81
+ type: 'boolean';
82
+ equals?: boolean;
83
+ };
84
+
75
85
  export type NumberSchemaDefinition<TObj> = Omit<
76
86
  SchemaDefintion<TObj>,
77
87
  'type'
@@ -113,6 +123,7 @@ export type CompositeSchema<TObj> = TObj extends (...args: any) => any
113
123
  : TObj extends string
114
124
  ? StringSchemaDefinition<TObj> | 'string'
115
125
  :
126
+ | BooleanSchemaDefinition<TObj>
116
127
  | NumberSchemaDefinition<TObj>
117
128
  | StringSchemaDefinition<TObj>
118
129
  | ArraySchemaDefinition<TObj>
@@ -127,18 +138,25 @@ export type SingleSchema<TObj = Record<string, never>> =
127
138
 
128
139
  export type Schema<TObj> = SingleSchema<TObj> | Array<SingleSchema<TObj>>;
129
140
 
130
- export interface ISchemaActions<K, T extends keyof K> {
141
+ export type Cons<H, T extends unknown[] = []> = T['length'] extends 0
142
+ ? [H]
143
+ : ((h: H, ...t: T) => void) extends (...r: infer R) => void
144
+ ? R
145
+ : never;
146
+
147
+ export interface ISchemaActions<S> {
131
148
  validate(value: any): Promise<ValidationResult>;
132
- schema: PropType<K, T>;
149
+ schema: S;
133
150
  }
134
151
 
135
- export interface ISchemasProvider<T = Record<string, never>> {
136
- schemas: {
137
- [K in keyof T]: ISchemaActions<T, K>;
138
- };
152
+ export interface ISchemasProvider<T extends unknown[]> {
153
+ schemas: Merge<T>;
139
154
  }
140
155
 
141
- export interface ISchemaValidator<T = Record<string, never>> {
156
+ export interface ISchemaValidator<
157
+ T extends Record<string, never> = Record<string, never>,
158
+ SchemaTypesStructures extends unknown[] = []
159
+ > {
142
160
  get preprocessors(): Map<
143
161
  string,
144
162
  (value: unknown) => unknown | Promise<unknown>
@@ -146,12 +164,19 @@ export interface ISchemaValidator<T = Record<string, never>> {
146
164
  addPreprocessor(
147
165
  name: string,
148
166
  preprocessor: (value: unknown) => unknown | Promise<unknown>
149
- ): SchemaValidator<T>;
167
+ ): SchemaValidator<T, SchemaTypesStructures>;
150
168
 
151
- addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
169
+ addSchemaType<
170
+ K,
171
+ L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>,
172
+ M = any
173
+ >(
152
174
  name: keyof K,
153
- schema: L | Array<Schema<any>>
154
- ): SchemaValidator<T & { [key in keyof K]: typeof schema }>;
175
+ schema: L
176
+ ): SchemaValidator<
177
+ T & { [key in keyof K]: typeof schema },
178
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
179
+ >;
155
180
 
156
181
  validate(
157
182
  schema: keyof T | DefaultSchemaType | Schema<any>,
@@ -159,5 +184,15 @@ export interface ISchemaValidator<T = Record<string, never>> {
159
184
  ): Promise<ValidationResult>;
160
185
  }
161
186
 
187
+ export type Unfold<T, K> = T extends string
188
+ ? T extends `${infer F}.${infer L}`
189
+ ? {
190
+ [k in F]: Unfold<L, K>;
191
+ }
192
+ : {
193
+ [k in T]: K;
194
+ }
195
+ : never;
196
+
162
197
  export { SchemaValidator };
163
198
  export default SchemaValidator;
@@ -170,7 +170,7 @@ test('Validate - no schema', async () => {
170
170
  const validator = new SchemaValidator();
171
171
  const cth = jest.fn();
172
172
  validator
173
- .validate(null, 10)
173
+ .validate(null as any, 10)
174
174
  .catch(cth)
175
175
  .then(() => {
176
176
  expect(cth).toBeCalled();
@@ -554,6 +554,82 @@ test('Validate - number by schema - custom validators - 2', async () => {
554
554
  expect(result).toHaveProperty('valid', false);
555
555
  });
556
556
 
557
+ test('Validate - boolean - 1', async () => {
558
+ const validator = new SchemaValidator();
559
+ const result = await validator.validate(
560
+ {
561
+ type: 'boolean'
562
+ },
563
+ 300
564
+ );
565
+
566
+ expect(result).toHaveProperty('valid', false);
567
+ });
568
+
569
+ test('Validate - boolean - 2', async () => {
570
+ const validator = new SchemaValidator();
571
+ const result = await validator.validate(
572
+ {
573
+ type: 'boolean'
574
+ },
575
+ true
576
+ );
577
+
578
+ expect(result).toHaveProperty('valid', true);
579
+ });
580
+
581
+ test('Validate - boolean - 3', async () => {
582
+ const validator = new SchemaValidator();
583
+ const result = await validator.validate(
584
+ {
585
+ type: 'boolean'
586
+ },
587
+ false
588
+ );
589
+
590
+ expect(result).toHaveProperty('valid', true);
591
+ });
592
+
593
+ test('Validate - boolean - 4', async () => {
594
+ const validator = new SchemaValidator();
595
+ const result = await validator.validate(
596
+ {
597
+ type: 'boolean',
598
+ equals: true
599
+ },
600
+ false
601
+ );
602
+
603
+ expect(result).toHaveProperty('valid', false);
604
+ });
605
+
606
+ test('Validate - boolean - 5', async () => {
607
+ const validator = new SchemaValidator();
608
+ const result = await validator.validate(
609
+ {
610
+ type: 'boolean',
611
+ equals: false
612
+ },
613
+ false
614
+ );
615
+
616
+ expect(result).toHaveProperty('valid', true);
617
+ });
618
+
619
+ test('Validate - boolean - 6', async () => {
620
+ const validator = new SchemaValidator();
621
+ const result = await validator.validate('boolean', false);
622
+
623
+ expect(result).toHaveProperty('valid', true);
624
+ });
625
+
626
+ test('Validate - boolean - 7', async () => {
627
+ const validator = new SchemaValidator();
628
+ const result = await validator.validate('boolean', '123');
629
+
630
+ expect(result).toHaveProperty('valid', false);
631
+ });
632
+
557
633
  test('Validate - string - 1', async () => {
558
634
  const validator = new SchemaValidator();
559
635
  const result = await validator.validate('string', '12345');
@@ -868,7 +944,7 @@ test('Validate - object - 2', async () => {
868
944
  };
869
945
  }
870
946
  ]
871
- })
947
+ }) as any as ObjectSchemaDefinition<any>
872
948
  );
873
949
  const user: User = {
874
950
  id: 1,
@@ -1123,19 +1199,30 @@ test('Validate schema - 1', async () => {
1123
1199
  });
1124
1200
 
1125
1201
  test('Validate schema - 2', async () => {
1126
- const validator = new SchemaValidator().addSchemaType('Date', {
1127
- validators: [
1128
- (value) =>
1129
- value instanceof Date && !Number.isNaN(value)
1130
- ? {
1131
- valid: true
1132
- }
1133
- : {
1134
- valid: false,
1135
- errors: ['should be a valid Date object']
1136
- }
1137
- ]
1138
- });
1202
+ const validator = new SchemaValidator()
1203
+ .addSchemaType('Date', {
1204
+ validators: [
1205
+ (value) =>
1206
+ value instanceof Date && !Number.isNaN(value)
1207
+ ? {
1208
+ valid: true
1209
+ }
1210
+ : {
1211
+ valid: false,
1212
+ errors: ['should be a valid Date object']
1213
+ }
1214
+ ]
1215
+ })
1216
+ .addSchemaType('Module.Schema1', {
1217
+ properties: {
1218
+ a: 'string'
1219
+ }
1220
+ })
1221
+ .addSchemaType('Module.Schema2', {
1222
+ properties: {
1223
+ b: 'number'
1224
+ }
1225
+ });
1139
1226
 
1140
1227
  const result = await validator.validate(
1141
1228
  {
@@ -1225,8 +1312,10 @@ test('Preprocessors - 1', async () => {
1225
1312
  bornAt: 'Date'
1226
1313
  },
1227
1314
  preprocessors: {
1228
- bornAt: (value: unknown): Date => {
1229
- const time = Date.parse(value.toString());
1315
+ bornAt: (value: unknown): Date | undefined => {
1316
+ const time = Date.parse(
1317
+ (value as Record<string, unknown>).toString()
1318
+ );
1230
1319
  if (Number.isNaN(time)) return undefined;
1231
1320
  return new Date(time);
1232
1321
  }
@@ -1264,18 +1353,28 @@ test('Preprocessors - 2', async () => {
1264
1353
  }
1265
1354
  };
1266
1355
 
1267
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1268
- const time = Date.parse(value.toString());
1269
- if (Number.isNaN(time)) return undefined;
1270
- return new Date(time);
1271
- });
1272
-
1273
- expect(() =>
1274
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1275
- const time = Date.parse(value.toString());
1356
+ validator.addPreprocessor(
1357
+ 'StringToDate',
1358
+ (value: unknown): Date | undefined => {
1359
+ const time = Date.parse(
1360
+ (value as Record<string, unknown>).toString()
1361
+ );
1276
1362
  if (Number.isNaN(time)) return undefined;
1277
1363
  return new Date(time);
1278
- })
1364
+ }
1365
+ );
1366
+
1367
+ expect(() =>
1368
+ validator.addPreprocessor(
1369
+ 'StringToDate',
1370
+ (value: unknown): Date | undefined => {
1371
+ const time = Date.parse(
1372
+ (value as Record<string, unknown>).toString()
1373
+ );
1374
+ if (Number.isNaN(time)) return undefined;
1375
+ return new Date(time);
1376
+ }
1377
+ )
1279
1378
  ).toThrow();
1280
1379
 
1281
1380
  const result = await validator.validate(schema, {
@@ -1316,11 +1415,16 @@ test('Preprocessors - 3', async () => {
1316
1415
  ]
1317
1416
  });
1318
1417
 
1319
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1320
- const time = Date.parse(value.toString());
1321
- if (Number.isNaN(time)) return undefined;
1322
- return new Date(time);
1323
- });
1418
+ validator.addPreprocessor(
1419
+ 'StringToDate',
1420
+ (value: unknown): Date | undefined => {
1421
+ const time = Date.parse(
1422
+ (value as Record<string, unknown>).toString()
1423
+ );
1424
+ if (Number.isNaN(time)) return undefined;
1425
+ return new Date(time);
1426
+ }
1427
+ );
1324
1428
 
1325
1429
  let obj = [new Date().toJSON()];
1326
1430
 
@@ -1337,8 +1441,10 @@ test('Preprocessors - 3', async () => {
1337
1441
  obj = [new Date().toJSON()];
1338
1442
  result = await validator.validate(
1339
1443
  {
1340
- preprocessor: (value: unknown): Date => {
1341
- const time = Date.parse(value.toString());
1444
+ preprocessor: (value: unknown): Date | undefined => {
1445
+ const time = Date.parse(
1446
+ (value as Record<string, unknown>).toString()
1447
+ );
1342
1448
  if (Number.isNaN(time)) return undefined;
1343
1449
  return new Date(time);
1344
1450
  },
@@ -1352,8 +1458,10 @@ test('Preprocessors - 3', async () => {
1352
1458
  obj = ['sdfsdf12', new Date().toJSON()];
1353
1459
  result = await validator.validate(
1354
1460
  {
1355
- preprocessor: (value: unknown): Date => {
1356
- const time = Date.parse(value.toString());
1461
+ preprocessor: (value: unknown): Date | undefined => {
1462
+ const time = Date.parse(
1463
+ (value as Record<string, unknown>).toString()
1464
+ );
1357
1465
  if (Number.isNaN(time)) return undefined;
1358
1466
  return new Date(time);
1359
1467
  },
@@ -1377,6 +1485,7 @@ test('Preprocessors - 3', async () => {
1377
1485
  marriedAt: 'number'
1378
1486
  },
1379
1487
  preprocessors: {
1488
+ age: (value): number => 40,
1380
1489
  '*': (value: SomeType): void => {
1381
1490
  if (value.marriedAt > value.age) {
1382
1491
  value.marriedAt = value.age;
@@ -1390,7 +1499,7 @@ test('Preprocessors - 3', async () => {
1390
1499
  marriedAt: 80
1391
1500
  };
1392
1501
  await validator.validate(schema, obj);
1393
- expect(obj).toHaveProperty('marriedAt', 50);
1502
+ expect(obj).toHaveProperty('marriedAt', 40);
1394
1503
  });
1395
1504
 
1396
1505
  test('Preprocessors - 4', async () => {
@@ -1408,11 +1517,16 @@ test('Preprocessors - 4', async () => {
1408
1517
  ]
1409
1518
  });
1410
1519
 
1411
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1412
- const time = Date.parse(value.toString());
1413
- if (Number.isNaN(time)) return undefined;
1414
- return new Date(time);
1415
- });
1520
+ validator.addPreprocessor(
1521
+ 'StringToDate',
1522
+ (value: unknown): Date | undefined => {
1523
+ const time = Date.parse(
1524
+ (value as Record<string, unknown>).toString()
1525
+ );
1526
+ if (Number.isNaN(time)) return undefined;
1527
+ return new Date(time);
1528
+ }
1529
+ );
1416
1530
 
1417
1531
  let obj = [new Date().toJSON()];
1418
1532
 
@@ -1429,8 +1543,10 @@ test('Preprocessors - 4', async () => {
1429
1543
  obj = [new Date().toJSON()];
1430
1544
  result = await validator.validate(
1431
1545
  {
1432
- preprocessor: (value: unknown): Date => {
1433
- const time = Date.parse(value.toString());
1546
+ preprocessor: (value: unknown): Date | undefined => {
1547
+ const time = Date.parse(
1548
+ (value as Record<string, unknown>).toString()
1549
+ );
1434
1550
  if (Number.isNaN(time)) return undefined;
1435
1551
  return new Date(time);
1436
1552
  },
@@ -1444,8 +1560,10 @@ test('Preprocessors - 4', async () => {
1444
1560
  obj = ['sdfsdf12', new Date().toJSON()];
1445
1561
  result = await validator.validate(
1446
1562
  {
1447
- preprocessor: (value: unknown): Date => {
1448
- const time = Date.parse(value.toString());
1563
+ preprocessor: (value: unknown): Date | undefined => {
1564
+ const time = Date.parse(
1565
+ (value as Record<string, unknown>).toString()
1566
+ );
1449
1567
  if (Number.isNaN(time)) return undefined;
1450
1568
  return new Date(time);
1451
1569
  },
@@ -1456,3 +1574,36 @@ test('Preprocessors - 4', async () => {
1456
1574
  );
1457
1575
  expect(result).toHaveProperty('valid', false);
1458
1576
  });
1577
+
1578
+ test('Submodules - 1', async () => {
1579
+ const validator = new SchemaValidator().addSchemaType(
1580
+ 'Module1.Schema1',
1581
+ {}
1582
+ );
1583
+
1584
+ const result = validator.schemas;
1585
+
1586
+ expect(result).toHaveProperty('Module1');
1587
+ expect(result).toHaveProperty('Module1.Schema1');
1588
+ });
1589
+
1590
+ test('Submodules - 2', async () => {
1591
+ const validator = new SchemaValidator()
1592
+ .addSchemaType('Module1.Schema1', {})
1593
+ .addSchemaType('Module1.Schema2', {
1594
+ properties: {
1595
+ a: 'number'
1596
+ }
1597
+ });
1598
+
1599
+ const result = validator.schemas;
1600
+
1601
+ expect(result).toHaveProperty('Module1');
1602
+ expect(result).toHaveProperty('Module1.Schema1');
1603
+ expect(result).toHaveProperty('Module1.Schema2');
1604
+
1605
+ const result2 = await validator.schemas.Module1.Schema2.validate({
1606
+ a: 234
1607
+ });
1608
+ expect(result2).toHaveProperty('valid', true);
1609
+ });
@@ -1,4 +1,4 @@
1
- import { deepExtend } from '@cleverbrush/deep';
1
+ import { deepExtend, Merge } from '@cleverbrush/deep';
2
2
  import {
3
3
  ISchemasProvider,
4
4
  Schema,
@@ -13,14 +13,17 @@ import {
13
13
  ISchemaValidator,
14
14
  DefaultSchemaType,
15
15
  ObjectSchemaDefinition,
16
- SchemaDefintion
16
+ BooleanSchemaDefinition,
17
+ Cons,
18
+ Unfold
17
19
  } from './index';
18
20
  import { validateNumber } from './validators/validateNumber';
21
+ import { validateBoolean } from './validators/validateBoolean';
19
22
  import { validateString } from './validators/validateString';
20
23
  import { validateArray } from './validators/validateArray';
21
24
  import { validateObject } from './validators/validateObject';
22
25
 
23
- const defaultSchemaNames = ['string', 'number', 'date', 'array'];
26
+ const defaultSchemaNames = ['string', 'boolean', 'number', 'date', 'array'];
24
27
 
25
28
  const defaultSchemas: { [key in DefaultSchemaType]?: Schema<any> } = {
26
29
  number: {
@@ -30,6 +33,11 @@ const defaultSchemas: { [key in DefaultSchemaType]?: Schema<any> } = {
30
33
  ensureNotNaN: true,
31
34
  ensureIsFinite: true
32
35
  },
36
+ boolean: {
37
+ type: 'boolean',
38
+ isRequired: true,
39
+ isNullable: false
40
+ },
33
41
  string: {
34
42
  type: 'string',
35
43
  isNullable: false,
@@ -49,31 +57,40 @@ const defaultSchemasValidationStrategies = {
49
57
  number: (
50
58
  obj: any,
51
59
  schema: NumberSchemaDefinition<any>,
52
- validator: ISchemaValidator<any>
60
+ validator: ISchemaValidator<any, unknown[]>
53
61
  ): Promise<ValidationResult> => validateNumber(obj, schema, validator),
62
+ boolean: (
63
+ obj: any,
64
+ schema: BooleanSchemaDefinition<any>,
65
+ validator: ISchemaValidator<any, unknown[]>
66
+ ): Promise<ValidationResult> => validateBoolean(obj, schema, validator),
54
67
  string: (
55
68
  obj: any,
56
69
  schema: StringSchemaDefinition<any>,
57
- validator: ISchemaValidator<any>
70
+ validator: ISchemaValidator<any, unknown[]>
58
71
  ): Promise<ValidationResult> => validateString(obj, schema, validator),
59
72
  array: (
60
73
  obj: any,
61
74
  schema: ArraySchemaDefinition<any>,
62
- validator: ISchemaValidator<any>
75
+ validator: ISchemaValidator<any, unknown[]>
63
76
  ): Promise<ValidationResult> => validateArray(obj, schema, validator)
64
77
  };
65
78
 
66
79
  const isDefaultType = (name: string): boolean =>
67
80
  defaultSchemaNames.indexOf(name) !== -1;
68
81
 
69
- export default class SchemaValidator<T = Record<string, never>>
70
- implements ISchemasProvider<T>, ISchemaValidator<T>
82
+ export default class SchemaValidator<
83
+ T extends Record<string, never> = Record<string, never>,
84
+ SchemaTypesStructures extends unknown[] = []
85
+ > implements
86
+ ISchemasProvider<SchemaTypesStructures>,
87
+ ISchemaValidator<T, SchemaTypesStructures>
71
88
  {
72
89
  private _schemasMap = new Map<string, Schema<any>>();
73
- private _schemasCache: { [K in keyof T]: ISchemaActions<T, K> } = null;
90
+ private _schemasCache: Merge<SchemaTypesStructures> = null;
74
91
  private _preprocessorsMap = new Map<
75
92
  string,
76
- (value: unknown) => unknown | Promise<unknown>
93
+ (value: unknown) => unknown | Promise<unknown> | undefined
77
94
  >();
78
95
 
79
96
  public get preprocessors(): Map<string, (value: unknown) => unknown> {
@@ -83,17 +100,24 @@ export default class SchemaValidator<T = Record<string, never>>
83
100
  public addPreprocessor(
84
101
  name: string,
85
102
  preprocessor: (value: unknown) => unknown | Promise<unknown>
86
- ): SchemaValidator<T> {
103
+ ): SchemaValidator<T, SchemaTypesStructures> {
87
104
  if (this._preprocessorsMap.has(name))
88
105
  throw new Error(`Preprocessor '${name}' is already registered`);
89
106
  this._preprocessorsMap.set(name, preprocessor);
90
107
  return this;
91
108
  }
92
109
 
93
- public addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
110
+ public addSchemaType<
111
+ K,
112
+ L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>,
113
+ M = any
114
+ >(
94
115
  name: keyof K,
95
- schema: L | Array<Schema<any>>
96
- ): SchemaValidator<T & { [key in keyof K]: L }> {
116
+ schema: L
117
+ ): SchemaValidator<
118
+ T & { [key in keyof K]: L },
119
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
120
+ > {
97
121
  if (typeof name !== 'string' || !name)
98
122
  throw new Error('Name is required');
99
123
 
@@ -108,7 +132,10 @@ export default class SchemaValidator<T = Record<string, never>>
108
132
  if (Array.isArray(schema)) {
109
133
  this._schemasMap.set(name.toString(), schema as Schema<any>);
110
134
  this._schemasCache = null;
111
- return this as any as SchemaValidator<T & { [key in keyof K]: L }>;
135
+ return this as any as SchemaValidator<
136
+ T & { [key in keyof K]: L },
137
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
138
+ >;
112
139
  }
113
140
 
114
141
  if (typeof schema !== 'object')
@@ -119,20 +146,34 @@ export default class SchemaValidator<T = Record<string, never>>
119
146
  } as Schema<any>);
120
147
  this._schemasCache = null;
121
148
 
122
- return this as any as SchemaValidator<T & { [key in keyof K]: L }>;
149
+ return this as any as SchemaValidator<
150
+ T & { [key in keyof K]: L },
151
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
152
+ >;
123
153
  }
124
154
 
125
- public get schemas(): { [K in keyof T]: ISchemaActions<T, K> } {
155
+ public get schemas(): Merge<SchemaTypesStructures> {
126
156
  if (this._schemasCache) return this._schemasCache;
127
157
  const res = {};
128
158
  for (const key of this._schemasMap.keys()) {
129
- res[key] = {
130
- validate: (value: any): Promise<any> =>
131
- this.validate(this._schemasMap.get(key), value),
132
- schema: this._schemasMap.get(key)
133
- };
159
+ const parts = key.split('.');
160
+ let curr = res;
161
+ for (let i = 0; i < parts.length; i++) {
162
+ if (i === parts.length - 1) {
163
+ curr[parts[i]] = {
164
+ validate: (value: any): Promise<any> =>
165
+ this.validate(this._schemasMap.get(key), value),
166
+ schema: this._schemasMap.get(key)
167
+ };
168
+ } else {
169
+ if (typeof curr[parts[i]] === 'undefined') {
170
+ curr[parts[i]] = {};
171
+ }
172
+ }
173
+ curr = curr[parts[i]];
174
+ }
134
175
  }
135
- this._schemasCache = res as { [K in keyof T]: ISchemaActions<T, K> };
176
+ this._schemasCache = res as Merge<SchemaTypesStructures>;
136
177
  return this._schemasCache;
137
178
  }
138
179
 
@@ -144,14 +185,14 @@ export default class SchemaValidator<T = Record<string, never>>
144
185
  const strategy = defaultSchemasValidationStrategies[name] as (
145
186
  obj: any,
146
187
  schema: Schema<any>,
147
- validator: ISchemaValidator<any>
188
+ validator: ISchemaValidator<any, unknown[]>
148
189
  ) => ValidationResult;
149
190
  let finalSchema = defaultSchemas[name] as CompositeSchema<
150
191
  Record<string, never>
151
192
  >;
152
193
  if (strategy) {
153
194
  if (typeof mergeSchema === 'object') {
154
- finalSchema = deepExtend(finalSchema, mergeSchema);
195
+ finalSchema = deepExtend(finalSchema, mergeSchema) as any;
155
196
  }
156
197
  if (typeof mergeSchema === 'number') {
157
198
  finalSchema = {
@@ -161,7 +202,11 @@ export default class SchemaValidator<T = Record<string, never>>
161
202
  mergeSchema;
162
203
  }
163
204
 
164
- let preliminaryResult = await strategy(value, finalSchema, this);
205
+ let preliminaryResult = await strategy(
206
+ value,
207
+ finalSchema,
208
+ this as any
209
+ );
165
210
  if (!preliminaryResult.valid) return preliminaryResult;
166
211
 
167
212
  preliminaryResult = await this.checkValidators(finalSchema, value);
@@ -225,19 +270,15 @@ export default class SchemaValidator<T = Record<string, never>>
225
270
  schema as DefaultSchemaType,
226
271
  obj
227
272
  );
228
- } else if (typeof this.schemas[schema as keyof T] !== 'undefined') {
229
- if (Array.isArray(this.schemas[schema as keyof T].schema)) {
230
- return this.validate(
231
- this.schemas[schema as keyof T]
232
- .schema as any as Schema<any>,
233
- obj
234
- );
273
+ } else if (typeof this._schemasMap.get(schema) !== 'undefined') {
274
+ if (Array.isArray(this._schemasMap.get(schema))) {
275
+ return this.validate(this._schemasMap.get(schema), obj);
235
276
  }
236
277
  const objSchema = deepExtend(
237
278
  defaultSchemas.object,
238
- this.schemas[schema as keyof T].schema
279
+ this._schemasMap.get(schema)
239
280
  ) as ObjectSchemaDefinition<Record<string, never>>;
240
- const res = await validateObject(obj, objSchema, this);
281
+ const res = await validateObject(obj, objSchema, this as any);
241
282
  if (!res.valid) return res;
242
283
  return await this.checkValidators(objSchema, obj);
243
284
  } else {
@@ -306,7 +347,7 @@ export default class SchemaValidator<T = Record<string, never>>
306
347
  const preliminaryResult = await validateObject(
307
348
  obj,
308
349
  schema,
309
- this
350
+ this as any
310
351
  );
311
352
  if (!preliminaryResult.valid) return preliminaryResult;
312
353
 
@@ -0,0 +1,45 @@
1
+ import {
2
+ ValidationResult,
3
+ BooleanSchemaDefinition,
4
+ ISchemaValidator
5
+ } from '../index';
6
+
7
+ export const validateBoolean = async (
8
+ obj: any,
9
+ schema: BooleanSchemaDefinition<any>,
10
+ validator: ISchemaValidator<any>
11
+ ): Promise<ValidationResult> => {
12
+ if (
13
+ typeof obj === 'undefined' &&
14
+ typeof schema === 'object' &&
15
+ schema.isRequired === false
16
+ ) {
17
+ return {
18
+ valid: true
19
+ };
20
+ }
21
+ if (typeof obj !== 'boolean')
22
+ return {
23
+ valid: false,
24
+ errors: [`expected type boolean, but saw ${typeof obj}`]
25
+ };
26
+
27
+ if (typeof schema === 'boolean') {
28
+ return { valid: true };
29
+ }
30
+
31
+ const bool = obj as boolean;
32
+
33
+ if (typeof schema.equals === 'boolean') {
34
+ return schema.equals === bool
35
+ ? { valid: true }
36
+ : {
37
+ valid: false,
38
+ errors: [`must be equal to ${schema.equals}`]
39
+ };
40
+ }
41
+
42
+ return {
43
+ valid: true
44
+ };
45
+ };
@@ -8,7 +8,7 @@ import {
8
8
  export const validateObject = async (
9
9
  obj: any,
10
10
  schema: ObjectSchemaDefinition<any>,
11
- validator: ISchemaValidator<any>
11
+ validator: ISchemaValidator<any, unknown[]>
12
12
  ): Promise<ValidationResult> => {
13
13
  if (
14
14
  typeof obj === 'undefined' &&
@@ -31,10 +31,11 @@ export const validateObject = async (
31
31
  if (typeof schema.preprocessors === 'object' && schema.preprocessors) {
32
32
  await Promise.all(
33
33
  Object.keys(schema.preprocessors).map(async (key: string) => {
34
+ if (key === '*') return;
34
35
  if (typeof schema.preprocessors[key] === 'function') {
35
36
  obj[key] = await Promise.resolve(
36
37
  (schema.preprocessors[key] as (unknown) => unknown)(
37
- key === '*' ? obj : obj[key]
38
+ obj[key]
38
39
  )
39
40
  );
40
41
  } else {
@@ -50,6 +51,24 @@ export const validateObject = async (
50
51
  }
51
52
  })
52
53
  );
54
+ if ('*' in schema.preprocessors) {
55
+ const objectPreprocessor = schema.preprocessors['*'];
56
+ if (typeof objectPreprocessor === 'function') {
57
+ await Promise.resolve(
58
+ (objectPreprocessor as (unknown) => unknown)(obj)
59
+ );
60
+ } else {
61
+ const preprocessor = validator.preprocessors.get(
62
+ objectPreprocessor as string
63
+ );
64
+ if (typeof preprocessor !== 'function') {
65
+ throw new Error(
66
+ `preprocessor '${objectPreprocessor}' is unknown`
67
+ );
68
+ }
69
+ await Promise.resolve(preprocessor(obj));
70
+ }
71
+ }
53
72
  }
54
73
 
55
74
  if (typeof schema.properties === 'object' && schema.properties) {