@cleverbrush/schema 0.0.14 → 0.0.17

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/README.md CHANGED
@@ -274,6 +274,31 @@ There is a possibility to register a schema, give it a name and then reuse it:
274
274
 
275
275
  // { valid: true }
276
276
 
277
+ Also there is a possibility to organize schemas in modules (or even submodules):
278
+
279
+ const validator = new SchemaValidator().addSchemaType("Module1.DTOs.Address", {
280
+ properties: {
281
+ id: {
282
+ type: "number",
283
+ min: 1,
284
+ },
285
+ street: "string",
286
+ zip: "number",
287
+ }
288
+ }).addSchemaType("Module1.Models.Person", {
289
+ properties: {
290
+ firstName: "string",
291
+ lastName: "string"
292
+ },
293
+ });
294
+
295
+ const resultPerson = await validator.schemas.Module1.Models.Person.validate({
296
+ fistName: 'John',
297
+ lastName: 'Smith'
298
+ });
299
+
300
+ const resultAddress = await validator.schemas.Module1.DTOs.Address.validate({ });
301
+
277
302
  ## Examples
278
303
 
279
304
  For Examples see unit tests in the schemaValidator.tests.ts
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Merge } from '@cleverbrush/deep';
1
2
  import SchemaValidator from './schemaValidator';
2
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];
@@ -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'> & {
@@ -68,22 +69,26 @@ export declare type ArraySchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 't
68
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>;
69
70
  export declare type SingleSchema<TObj = Record<string, never>> = number | string | DefaultSchemaType | CompositeSchema<TObj>;
70
71
  export declare type Schema<TObj> = SingleSchema<TObj> | Array<SingleSchema<TObj>>;
71
- 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> {
72
74
  validate(value: any): Promise<ValidationResult>;
73
- schema: PropType<K, T>;
75
+ schema: S;
74
76
  }
75
- export interface ISchemasProvider<T = Record<string, never>> {
76
- schemas: {
77
- [K in keyof T]: ISchemaActions<T, K>;
78
- };
77
+ export interface ISchemasProvider<T extends unknown[]> {
78
+ schemas: Merge<T>;
79
79
  }
80
- export interface ISchemaValidator<T = Record<string, never>> {
80
+ export interface ISchemaValidator<T extends Record<string, never> = Record<string, never>, SchemaTypesStructures extends unknown[] = []> {
81
81
  get preprocessors(): Map<string, (value: unknown) => unknown | Promise<unknown>>;
82
- addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T>;
83
- 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 & {
84
84
  [key in keyof K]: typeof schema;
85
- }>;
85
+ }, Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>>;
86
86
  validate(schema: keyof T | DefaultSchemaType | Schema<any>, obj: any): Promise<ValidationResult>;
87
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;
88
93
  export { SchemaValidator };
89
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>;
@@ -81,10 +81,22 @@ class SchemaValidator {
81
81
  return this._schemasCache;
82
82
  const res = {};
83
83
  for (const key of this._schemasMap.keys()) {
84
- res[key] = {
85
- validate: (value) => this.validate(this._schemasMap.get(key), value),
86
- schema: this._schemasMap.get(key)
87
- };
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
+ }
88
100
  }
89
101
  this._schemasCache = res;
90
102
  return this._schemasCache;
@@ -149,12 +161,11 @@ class SchemaValidator {
149
161
  if (isDefaultType(schema)) {
150
162
  return await this.validateDefaultType(schema, obj);
151
163
  }
152
- else if (typeof this.schemas[schema] !== 'undefined') {
153
- if (Array.isArray(this.schemas[schema].schema)) {
154
- return this.validate(this.schemas[schema]
155
- .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);
156
167
  }
157
- 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));
158
169
  const res = await (0, validateObject_1.validateObject)(obj, objSchema, this);
159
170
  if (!res.valid)
160
171
  return res;
@@ -189,8 +200,10 @@ class SchemaValidator {
189
200
  return await this.validateDefaultType(schema.type, obj, schema);
190
201
  }
191
202
  else if (schema.type === 'alias') {
192
- const alias = this.schemas[schema.schemaName]
193
- .schema;
203
+ const alias = this._schemasMap.get(schema.schemaName);
204
+ if (typeof alias === 'undefined') {
205
+ throw new Error(`Unknown schema alias - ${schema.schemaName}`);
206
+ }
194
207
  if (Array.isArray(alias)) {
195
208
  if ((!schema.isRequired && typeof obj === 'undefined') ||
196
209
  (schema.isNullable && obj === null)) {
@@ -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>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleverbrush/schema",
3
- "version": "0.0.14",
3
+ "version": "0.0.17",
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.14"
22
+ "@cleverbrush/deep": "0.0.17"
23
23
  },
24
24
  "types": "./dist/index.d.ts"
25
25
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Merge } from '@cleverbrush/deep';
1
2
  import SchemaValidator from './schemaValidator';
2
3
 
3
4
  export type DefaultSchemaType =
@@ -45,7 +46,7 @@ export type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
45
46
  ?
46
47
  | ((
47
48
  value: unknown
48
- ) => PropType<T, S> | Promise<PropType<T, S>>)
49
+ ) => undefined | PropType<T, S> | Promise<PropType<T, S>>)
49
50
  | string
50
51
  : (value: T) => void | Promise<void>;
51
52
  }>;
@@ -137,18 +138,25 @@ export type SingleSchema<TObj = Record<string, never>> =
137
138
 
138
139
  export type Schema<TObj> = SingleSchema<TObj> | Array<SingleSchema<TObj>>;
139
140
 
140
- 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> {
141
148
  validate(value: any): Promise<ValidationResult>;
142
- schema: PropType<K, T>;
149
+ schema: S;
143
150
  }
144
151
 
145
- export interface ISchemasProvider<T = Record<string, never>> {
146
- schemas: {
147
- [K in keyof T]: ISchemaActions<T, K>;
148
- };
152
+ export interface ISchemasProvider<T extends unknown[]> {
153
+ schemas: Merge<T>;
149
154
  }
150
155
 
151
- 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
+ > {
152
160
  get preprocessors(): Map<
153
161
  string,
154
162
  (value: unknown) => unknown | Promise<unknown>
@@ -156,12 +164,19 @@ export interface ISchemaValidator<T = Record<string, never>> {
156
164
  addPreprocessor(
157
165
  name: string,
158
166
  preprocessor: (value: unknown) => unknown | Promise<unknown>
159
- ): SchemaValidator<T>;
167
+ ): SchemaValidator<T, SchemaTypesStructures>;
160
168
 
161
- addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
169
+ addSchemaType<
170
+ K,
171
+ L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>,
172
+ M = any
173
+ >(
162
174
  name: keyof K,
163
- schema: L | Array<Schema<any>>
164
- ): 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
+ >;
165
180
 
166
181
  validate(
167
182
  schema: keyof T | DefaultSchemaType | Schema<any>,
@@ -169,5 +184,15 @@ export interface ISchemaValidator<T = Record<string, never>> {
169
184
  ): Promise<ValidationResult>;
170
185
  }
171
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
+
172
197
  export { SchemaValidator };
173
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();
@@ -944,7 +944,7 @@ test('Validate - object - 2', async () => {
944
944
  };
945
945
  }
946
946
  ]
947
- })
947
+ }) as any as ObjectSchemaDefinition<any>
948
948
  );
949
949
  const user: User = {
950
950
  id: 1,
@@ -1199,19 +1199,30 @@ test('Validate schema - 1', async () => {
1199
1199
  });
1200
1200
 
1201
1201
  test('Validate schema - 2', async () => {
1202
- const validator = new SchemaValidator().addSchemaType('Date', {
1203
- validators: [
1204
- (value) =>
1205
- value instanceof Date && !Number.isNaN(value)
1206
- ? {
1207
- valid: true
1208
- }
1209
- : {
1210
- valid: false,
1211
- errors: ['should be a valid Date object']
1212
- }
1213
- ]
1214
- });
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
+ });
1215
1226
 
1216
1227
  const result = await validator.validate(
1217
1228
  {
@@ -1301,8 +1312,10 @@ test('Preprocessors - 1', async () => {
1301
1312
  bornAt: 'Date'
1302
1313
  },
1303
1314
  preprocessors: {
1304
- bornAt: (value: unknown): Date => {
1305
- 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
+ );
1306
1319
  if (Number.isNaN(time)) return undefined;
1307
1320
  return new Date(time);
1308
1321
  }
@@ -1340,18 +1353,28 @@ test('Preprocessors - 2', async () => {
1340
1353
  }
1341
1354
  };
1342
1355
 
1343
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1344
- const time = Date.parse(value.toString());
1345
- if (Number.isNaN(time)) return undefined;
1346
- return new Date(time);
1347
- });
1348
-
1349
- expect(() =>
1350
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1351
- 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
+ );
1352
1362
  if (Number.isNaN(time)) return undefined;
1353
1363
  return new Date(time);
1354
- })
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
+ )
1355
1378
  ).toThrow();
1356
1379
 
1357
1380
  const result = await validator.validate(schema, {
@@ -1392,11 +1415,16 @@ test('Preprocessors - 3', async () => {
1392
1415
  ]
1393
1416
  });
1394
1417
 
1395
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1396
- const time = Date.parse(value.toString());
1397
- if (Number.isNaN(time)) return undefined;
1398
- return new Date(time);
1399
- });
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
+ );
1400
1428
 
1401
1429
  let obj = [new Date().toJSON()];
1402
1430
 
@@ -1413,8 +1441,10 @@ test('Preprocessors - 3', async () => {
1413
1441
  obj = [new Date().toJSON()];
1414
1442
  result = await validator.validate(
1415
1443
  {
1416
- preprocessor: (value: unknown): Date => {
1417
- 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
+ );
1418
1448
  if (Number.isNaN(time)) return undefined;
1419
1449
  return new Date(time);
1420
1450
  },
@@ -1428,8 +1458,10 @@ test('Preprocessors - 3', async () => {
1428
1458
  obj = ['sdfsdf12', new Date().toJSON()];
1429
1459
  result = await validator.validate(
1430
1460
  {
1431
- preprocessor: (value: unknown): Date => {
1432
- 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
+ );
1433
1465
  if (Number.isNaN(time)) return undefined;
1434
1466
  return new Date(time);
1435
1467
  },
@@ -1485,11 +1517,16 @@ test('Preprocessors - 4', async () => {
1485
1517
  ]
1486
1518
  });
1487
1519
 
1488
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1489
- const time = Date.parse(value.toString());
1490
- if (Number.isNaN(time)) return undefined;
1491
- return new Date(time);
1492
- });
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
+ );
1493
1530
 
1494
1531
  let obj = [new Date().toJSON()];
1495
1532
 
@@ -1506,8 +1543,10 @@ test('Preprocessors - 4', async () => {
1506
1543
  obj = [new Date().toJSON()];
1507
1544
  result = await validator.validate(
1508
1545
  {
1509
- preprocessor: (value: unknown): Date => {
1510
- 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
+ );
1511
1550
  if (Number.isNaN(time)) return undefined;
1512
1551
  return new Date(time);
1513
1552
  },
@@ -1521,8 +1560,10 @@ test('Preprocessors - 4', async () => {
1521
1560
  obj = ['sdfsdf12', new Date().toJSON()];
1522
1561
  result = await validator.validate(
1523
1562
  {
1524
- preprocessor: (value: unknown): Date => {
1525
- 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
+ );
1526
1567
  if (Number.isNaN(time)) return undefined;
1527
1568
  return new Date(time);
1528
1569
  },
@@ -1533,3 +1574,83 @@ test('Preprocessors - 4', async () => {
1533
1574
  );
1534
1575
  expect(result).toHaveProperty('valid', false);
1535
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
+ });
1610
+
1611
+ test('Submodules - 3', async () => {
1612
+ const validator = new SchemaValidator()
1613
+ .addSchemaType('Module1.Schema1', {
1614
+ properties: {
1615
+ b: 'number'
1616
+ }
1617
+ })
1618
+ .addSchemaType('Module1.Schema2', {
1619
+ properties: {
1620
+ a: {
1621
+ type: 'alias',
1622
+ schemaName: 'Module1.Schema1'
1623
+ }
1624
+ }
1625
+ });
1626
+
1627
+ const result2 = await validator.schemas.Module1.Schema2.validate({
1628
+ a: {
1629
+ b: 20
1630
+ }
1631
+ });
1632
+ expect(result2).toHaveProperty('valid', true);
1633
+ });
1634
+
1635
+ test('Submodules - 4', async () => {
1636
+ const validator = new SchemaValidator()
1637
+ .addSchemaType('Module1.Schema1', {
1638
+ properties: {
1639
+ b: 'number'
1640
+ }
1641
+ })
1642
+ .addSchemaType('Module1.Schema2', {
1643
+ properties: {
1644
+ a: {
1645
+ type: 'alias',
1646
+ schemaName: 'Module1.Schema3'
1647
+ }
1648
+ }
1649
+ });
1650
+
1651
+ await validator.schemas.Module1.Schema2.validate({
1652
+ a: {
1653
+ b: 20
1654
+ }
1655
+ }).catch((e) => expect(e).toBeInstanceOf(Error));
1656
+ });
@@ -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,7 +13,9 @@ import {
13
13
  ISchemaValidator,
14
14
  DefaultSchemaType,
15
15
  ObjectSchemaDefinition,
16
- BooleanSchemaDefinition
16
+ BooleanSchemaDefinition,
17
+ Cons,
18
+ Unfold
17
19
  } from './index';
18
20
  import { validateNumber } from './validators/validateNumber';
19
21
  import { validateBoolean } from './validators/validateBoolean';
@@ -55,36 +57,40 @@ const defaultSchemasValidationStrategies = {
55
57
  number: (
56
58
  obj: any,
57
59
  schema: NumberSchemaDefinition<any>,
58
- validator: ISchemaValidator<any>
60
+ validator: ISchemaValidator<any, unknown[]>
59
61
  ): Promise<ValidationResult> => validateNumber(obj, schema, validator),
60
62
  boolean: (
61
63
  obj: any,
62
64
  schema: BooleanSchemaDefinition<any>,
63
- validator: ISchemaValidator<any>
65
+ validator: ISchemaValidator<any, unknown[]>
64
66
  ): Promise<ValidationResult> => validateBoolean(obj, schema, validator),
65
67
  string: (
66
68
  obj: any,
67
69
  schema: StringSchemaDefinition<any>,
68
- validator: ISchemaValidator<any>
70
+ validator: ISchemaValidator<any, unknown[]>
69
71
  ): Promise<ValidationResult> => validateString(obj, schema, validator),
70
72
  array: (
71
73
  obj: any,
72
74
  schema: ArraySchemaDefinition<any>,
73
- validator: ISchemaValidator<any>
75
+ validator: ISchemaValidator<any, unknown[]>
74
76
  ): Promise<ValidationResult> => validateArray(obj, schema, validator)
75
77
  };
76
78
 
77
79
  const isDefaultType = (name: string): boolean =>
78
80
  defaultSchemaNames.indexOf(name) !== -1;
79
81
 
80
- export default class SchemaValidator<T = Record<string, never>>
81
- 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>
82
88
  {
83
89
  private _schemasMap = new Map<string, Schema<any>>();
84
- private _schemasCache: { [K in keyof T]: ISchemaActions<T, K> } = null;
90
+ private _schemasCache: Merge<SchemaTypesStructures> = null;
85
91
  private _preprocessorsMap = new Map<
86
92
  string,
87
- (value: unknown) => unknown | Promise<unknown>
93
+ (value: unknown) => unknown | Promise<unknown> | undefined
88
94
  >();
89
95
 
90
96
  public get preprocessors(): Map<string, (value: unknown) => unknown> {
@@ -94,17 +100,24 @@ export default class SchemaValidator<T = Record<string, never>>
94
100
  public addPreprocessor(
95
101
  name: string,
96
102
  preprocessor: (value: unknown) => unknown | Promise<unknown>
97
- ): SchemaValidator<T> {
103
+ ): SchemaValidator<T, SchemaTypesStructures> {
98
104
  if (this._preprocessorsMap.has(name))
99
105
  throw new Error(`Preprocessor '${name}' is already registered`);
100
106
  this._preprocessorsMap.set(name, preprocessor);
101
107
  return this;
102
108
  }
103
109
 
104
- 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
+ >(
105
115
  name: keyof K,
106
- schema: L | Array<Schema<any>>
107
- ): 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
+ > {
108
121
  if (typeof name !== 'string' || !name)
109
122
  throw new Error('Name is required');
110
123
 
@@ -119,7 +132,10 @@ export default class SchemaValidator<T = Record<string, never>>
119
132
  if (Array.isArray(schema)) {
120
133
  this._schemasMap.set(name.toString(), schema as Schema<any>);
121
134
  this._schemasCache = null;
122
- 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
+ >;
123
139
  }
124
140
 
125
141
  if (typeof schema !== 'object')
@@ -130,20 +146,34 @@ export default class SchemaValidator<T = Record<string, never>>
130
146
  } as Schema<any>);
131
147
  this._schemasCache = null;
132
148
 
133
- 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
+ >;
134
153
  }
135
154
 
136
- public get schemas(): { [K in keyof T]: ISchemaActions<T, K> } {
155
+ public get schemas(): Merge<SchemaTypesStructures> {
137
156
  if (this._schemasCache) return this._schemasCache;
138
157
  const res = {};
139
158
  for (const key of this._schemasMap.keys()) {
140
- res[key] = {
141
- validate: (value: any): Promise<any> =>
142
- this.validate(this._schemasMap.get(key), value),
143
- schema: this._schemasMap.get(key)
144
- };
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
+ }
145
175
  }
146
- this._schemasCache = res as { [K in keyof T]: ISchemaActions<T, K> };
176
+ this._schemasCache = res as Merge<SchemaTypesStructures>;
147
177
  return this._schemasCache;
148
178
  }
149
179
 
@@ -155,14 +185,14 @@ export default class SchemaValidator<T = Record<string, never>>
155
185
  const strategy = defaultSchemasValidationStrategies[name] as (
156
186
  obj: any,
157
187
  schema: Schema<any>,
158
- validator: ISchemaValidator<any>
188
+ validator: ISchemaValidator<any, unknown[]>
159
189
  ) => ValidationResult;
160
190
  let finalSchema = defaultSchemas[name] as CompositeSchema<
161
191
  Record<string, never>
162
192
  >;
163
193
  if (strategy) {
164
194
  if (typeof mergeSchema === 'object') {
165
- finalSchema = deepExtend(finalSchema, mergeSchema);
195
+ finalSchema = deepExtend(finalSchema, mergeSchema) as any;
166
196
  }
167
197
  if (typeof mergeSchema === 'number') {
168
198
  finalSchema = {
@@ -172,7 +202,11 @@ export default class SchemaValidator<T = Record<string, never>>
172
202
  mergeSchema;
173
203
  }
174
204
 
175
- let preliminaryResult = await strategy(value, finalSchema, this);
205
+ let preliminaryResult = await strategy(
206
+ value,
207
+ finalSchema,
208
+ this as any
209
+ );
176
210
  if (!preliminaryResult.valid) return preliminaryResult;
177
211
 
178
212
  preliminaryResult = await this.checkValidators(finalSchema, value);
@@ -236,19 +270,15 @@ export default class SchemaValidator<T = Record<string, never>>
236
270
  schema as DefaultSchemaType,
237
271
  obj
238
272
  );
239
- } else if (typeof this.schemas[schema as keyof T] !== 'undefined') {
240
- if (Array.isArray(this.schemas[schema as keyof T].schema)) {
241
- return this.validate(
242
- this.schemas[schema as keyof T]
243
- .schema as any as Schema<any>,
244
- obj
245
- );
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);
246
276
  }
247
277
  const objSchema = deepExtend(
248
278
  defaultSchemas.object,
249
- this.schemas[schema as keyof T].schema
279
+ this._schemasMap.get(schema)
250
280
  ) as ObjectSchemaDefinition<Record<string, never>>;
251
- const res = await validateObject(obj, objSchema, this);
281
+ const res = await validateObject(obj, objSchema, this as any);
252
282
  if (!res.valid) return res;
253
283
  return await this.checkValidators(objSchema, obj);
254
284
  } else {
@@ -287,8 +317,12 @@ export default class SchemaValidator<T = Record<string, never>>
287
317
  schema
288
318
  );
289
319
  } else if (schema.type === 'alias') {
290
- const alias = this.schemas[schema.schemaName]
291
- .schema as Schema<any>;
320
+ const alias = this._schemasMap.get(schema.schemaName);
321
+ if (typeof alias === 'undefined') {
322
+ throw new Error(
323
+ `Unknown schema alias - ${schema.schemaName}`
324
+ );
325
+ }
292
326
  if (Array.isArray(alias)) {
293
327
  if (
294
328
  (!schema.isRequired && typeof obj === 'undefined') ||
@@ -317,7 +351,7 @@ export default class SchemaValidator<T = Record<string, never>>
317
351
  const preliminaryResult = await validateObject(
318
352
  obj,
319
353
  schema,
320
- this
354
+ this as any
321
355
  );
322
356
  if (!preliminaryResult.valid) return preliminaryResult;
323
357
 
@@ -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' &&