@cleverbrush/schema 1.0.0-beta.5 → 1.1.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.
Files changed (76) hide show
  1. package/README.md +118 -320
  2. package/dist/builders/AnySchemaBuilder.d.ts +76 -0
  3. package/dist/builders/AnySchemaBuilder.js +112 -0
  4. package/dist/builders/ArraySchemaBuilder.d.ts +112 -14
  5. package/dist/builders/ArraySchemaBuilder.js +254 -111
  6. package/dist/builders/BooleanSchemaBuilder.d.ts +69 -29
  7. package/dist/builders/BooleanSchemaBuilder.js +134 -74
  8. package/dist/builders/DateSchemaBuilder.d.ts +179 -0
  9. package/dist/builders/DateSchemaBuilder.js +433 -0
  10. package/dist/builders/FunctionSchemaBuilder.d.ts +59 -25
  11. package/dist/builders/FunctionSchemaBuilder.js +102 -58
  12. package/dist/builders/NumberSchemaBuilder.d.ts +159 -59
  13. package/dist/builders/NumberSchemaBuilder.js +345 -165
  14. package/dist/builders/ObjectSchemaBuilder.d.ts +310 -81
  15. package/dist/builders/ObjectSchemaBuilder.js +528 -283
  16. package/dist/builders/SchemaBuilder.d.ts +157 -34
  17. package/dist/builders/SchemaBuilder.js +258 -71
  18. package/dist/builders/StringSchemaBuilder.d.ts +138 -13
  19. package/dist/builders/StringSchemaBuilder.js +261 -115
  20. package/dist/builders/UnionSchemaBuilder.d.ts +132 -9
  21. package/dist/builders/UnionSchemaBuilder.js +196 -75
  22. package/dist/index.d.ts +20 -40
  23. package/dist/index.js +19 -32
  24. package/dist/utils/transaction.d.ts +46 -0
  25. package/dist/utils/transaction.js +178 -0
  26. package/package.json +27 -14
  27. package/dist/defaultSchemas.d.ts +0 -4
  28. package/dist/defaultSchemas.js +0 -45
  29. package/dist/schema.d.ts +0 -231
  30. package/dist/schema.js +0 -2
  31. package/dist/schemaRegistry.d.ts +0 -49
  32. package/dist/schemaRegistry.js +0 -278
  33. package/dist/validators/validateArray.d.ts +0 -3
  34. package/dist/validators/validateArray.js +0 -60
  35. package/dist/validators/validateBoolean.d.ts +0 -2
  36. package/dist/validators/validateBoolean.js +0 -30
  37. package/dist/validators/validateFunction.d.ts +0 -2
  38. package/dist/validators/validateFunction.js +0 -21
  39. package/dist/validators/validateNumber.d.ts +0 -2
  40. package/dist/validators/validateNumber.js +0 -69
  41. package/dist/validators/validateObject.d.ts +0 -3
  42. package/dist/validators/validateObject.js +0 -95
  43. package/dist/validators/validateString.d.ts +0 -2
  44. package/dist/validators/validateString.js +0 -50
  45. package/dist/validators/validateUnion.d.ts +0 -3
  46. package/dist/validators/validateUnion.js +0 -30
  47. package/src/builders/ArraySchemaBuilder.test.ts +0 -270
  48. package/src/builders/ArraySchemaBuilder.ts +0 -381
  49. package/src/builders/BooleanSchemaBuilder.test.ts +0 -196
  50. package/src/builders/BooleanSchemaBuilder.ts +0 -155
  51. package/src/builders/FunctionSchemaBuilder.test.ts +0 -145
  52. package/src/builders/FunctionSchemaBuilder.ts +0 -117
  53. package/src/builders/NumberSchemaBuilder.test.ts +0 -493
  54. package/src/builders/NumberSchemaBuilder.ts +0 -789
  55. package/src/builders/ObjectSchemaBuilder.test.ts +0 -657
  56. package/src/builders/ObjectSchemaBuilder.ts +0 -794
  57. package/src/builders/SchemaBuilder.test.ts +0 -73
  58. package/src/builders/SchemaBuilder.ts +0 -135
  59. package/src/builders/StringSchemaBuilder.test.ts +0 -318
  60. package/src/builders/StringSchemaBuilder.ts +0 -392
  61. package/src/builders/UnionSchemaBuilder.test.ts +0 -162
  62. package/src/builders/UnionSchemaBuilder.ts +0 -154
  63. package/src/defaultSchemas.ts +0 -44
  64. package/src/index.ts +0 -66
  65. package/src/schema.ts +0 -849
  66. package/src/schemaRegistry.builders.test.ts +0 -1393
  67. package/src/schemaRegistry.test.ts +0 -118
  68. package/src/schemaRegistry.ts +0 -461
  69. package/src/validators/validateArray.ts +0 -76
  70. package/src/validators/validateBoolean.ts +0 -36
  71. package/src/validators/validateFunction.ts +0 -25
  72. package/src/validators/validateNumber.ts +0 -82
  73. package/src/validators/validateObject.ts +0 -119
  74. package/src/validators/validateString.ts +0 -59
  75. package/src/validators/validateUnion.ts +0 -36
  76. package/tsconfig.json +0 -16
@@ -1,344 +1,589 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.object = exports.ObjectSchemaBuilder = void 0;
4
- const SchemaBuilder_js_1 = require("./SchemaBuilder.js");
5
- const defaultSchemas_js_1 = require("../defaultSchemas.js");
6
- class ObjectSchemaBuilder extends SchemaBuilder_js_1.SchemaBuilder {
7
- type = 'object';
8
- noUnknownProperties;
9
- properties;
10
- preprocessors;
11
- clone() {
12
- return ObjectSchemaBuilder.create({
13
- isNullable: this._isNullable,
14
- isRequired: this._isRequired,
15
- noUnknownProperties: this.noUnknownProperties,
16
- properties: this.properties,
17
- preprocessor: this.preprocessor,
18
- preprocessors: this.preprocessors,
19
- validators: this.validators
1
+ import { SchemaBuilder } from './SchemaBuilder.js';
2
+ /**
3
+ * Object schema builder class. Similar to the `object` type
4
+ * in JS. Allows to define a schema for `object` value.
5
+ * Should be used to validate objects with specific properties.
6
+ * Properties should be defined as their own schema builders.
7
+ * You can use any `SchemaBuilder` e.g. `string()`, `number()`,
8
+ * `boolean()`, `array()`, `object()`, etc. to define properties.
9
+ * Which means that you can define nested objects and arrays of
10
+ * any complexity.
11
+ *
12
+ * **NOTE** this class is exported only to give opportunity to extend it
13
+ * by inheriting. It is not recommended to create an instance of this class
14
+ * directly. Use {@link object | object()} function instead.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const schema = object({
19
+ * name: string(),
20
+ * age: number()
21
+ * });
22
+ *
23
+ * const result = await schema.validate({
24
+ * name: 'John',
25
+ * age: 30
26
+ * });
27
+ *
28
+ * // result.valid === true
29
+ * // result.object === { name: 'John', age: 30 }
30
+ * ```
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const schema = object({
35
+ * name: string(),
36
+ * age: number().optional()
37
+ * });
38
+ *
39
+ * const result = await schema.validate({
40
+ * name: 'John'
41
+ * });
42
+ * // result.valid === true
43
+ * // result.object === { name: 'John' }
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const schema = object({
49
+ * name: string(),
50
+ * age: number();
51
+ * });
52
+ * const result = await schema.validate({
53
+ * name: 'John'
54
+ * });
55
+ *
56
+ * // result.valid === false
57
+ * // result.errors[0].message === "is expected to have property 'age'"
58
+ * ```
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const schema = object({
63
+ * name: string(),
64
+ * address: object({
65
+ * city: string(),
66
+ * country: string()
67
+ * })
68
+ * });
69
+ * const result = await schema.validate({
70
+ * name: 'John',
71
+ * address: {
72
+ * city: 'New York',
73
+ * country: 'USA'
74
+ * }
75
+ * });
76
+ * // result.valid === true
77
+ * // result.object === {
78
+ * // name: 'John',
79
+ * // address: {
80
+ * // city: 'New York',
81
+ * // country: 'USA'
82
+ * // }
83
+ * // }
84
+ * ```
85
+ * @see {@link object}
86
+ */
87
+ export class ObjectSchemaBuilder extends SchemaBuilder {
88
+ #properties = {};
89
+ #acceptUnknownProps = false;
90
+ static create(props) {
91
+ return new ObjectSchemaBuilder({
92
+ type: 'object',
93
+ ...props
20
94
  });
21
95
  }
22
- get _schema() {
23
- // eslint-disable-next-line @typescript-eslint/no-this-alias
24
- const that = this;
25
- return Object.assign({
26
- type: this.type
27
- }, this.getCommonSchema(), typeof that.properties !== 'undefined' && that.properties
28
- ? {
29
- properties: Object.keys(that.properties).reduce((acc, curr) => {
30
- if (!that.properties)
31
- return;
32
- if (that.properties[curr] instanceof SchemaBuilder_js_1.SchemaBuilder) {
33
- acc[curr] = that.properties[curr].clone();
34
- }
35
- else {
36
- acc[curr] = that.properties[curr];
37
- }
38
- return acc;
39
- }, {})
96
+ createFromProps(props) {
97
+ return ObjectSchemaBuilder.create(props);
98
+ }
99
+ constructor(props) {
100
+ super(props);
101
+ if (typeof props.properties === 'object' && props.properties) {
102
+ this.#properties = props.properties;
103
+ }
104
+ if (typeof props.acceptUnknownProps === 'boolean') {
105
+ this.#acceptUnknownProps = props.acceptUnknownProps;
106
+ }
107
+ }
108
+ introspect() {
109
+ return {
110
+ ...super.introspect(),
111
+ /**
112
+ * Properties defined in schema
113
+ */
114
+ properties: { ...this.#properties },
115
+ /**
116
+ * If set to `true`, schema validation will not
117
+ * return errors if object contains fields which
118
+ * are not defined in the schema `properties`.
119
+ * Set to `false` by default
120
+ */
121
+ acceptUnknownProps: this.#acceptUnknownProps
122
+ };
123
+ }
124
+ /**
125
+ * @hidden
126
+ */
127
+ required() {
128
+ return super.required();
129
+ }
130
+ /**
131
+ * @hidden
132
+ */
133
+ optional() {
134
+ return super.optional();
135
+ }
136
+ /**
137
+ * Performs validion of object schema over the `object`.
138
+ * @param context Optional `ValidationContext` settings.
139
+ */
140
+ async validate(object, context) {
141
+ const prevalidatedResult = await super.preValidate(object, context);
142
+ const { valid, context: prevalidationContext, transaction: validationTransaction, errors: preValidationErrors } = prevalidatedResult;
143
+ const { path, doNotStopOnFirstError } = prevalidationContext;
144
+ let errors = prevalidatedResult.errors || [];
145
+ if (!valid && !doNotStopOnFirstError) {
146
+ return {
147
+ valid,
148
+ errors: preValidationErrors
149
+ };
150
+ }
151
+ const { object: { validatedObject: objToValidate } } = validationTransaction;
152
+ if (!this.isRequired &&
153
+ (typeof objToValidate === 'undefined' || objToValidate === null)) {
154
+ return {
155
+ valid: true,
156
+ object: validationTransaction.commit().validatedObject
157
+ };
158
+ }
159
+ if (typeof objToValidate !== 'object') {
160
+ errors.push({
161
+ message: 'must be an object',
162
+ path: path
163
+ });
164
+ if (!doNotStopOnFirstError) {
165
+ if (validationTransaction) {
166
+ validationTransaction.rollback();
167
+ }
168
+ return {
169
+ valid: false,
170
+ errors: [errors[0]]
171
+ };
40
172
  }
41
- : {}, typeof that.noUnknownProperties !== 'undefined'
42
- ? { noUnknownProperties: that.noUnknownProperties }
43
- : {}, typeof that.preprocessors !== 'undefined' && that.preprocessors
44
- ? {
45
- preprocessors: {
46
- ...that.preprocessors
173
+ }
174
+ const propKeys = Object.keys(this.#properties);
175
+ const objKeys = Object.keys(objToValidate);
176
+ if (propKeys.length === 0) {
177
+ if (objKeys.length === 0) {
178
+ if (doNotStopOnFirstError && errors.length > 0) {
179
+ return {
180
+ valid: false,
181
+ errors
182
+ };
47
183
  }
184
+ if (validationTransaction) {
185
+ validationTransaction.commit().validatedObject;
186
+ }
187
+ return {
188
+ valid: true,
189
+ object: {}
190
+ };
48
191
  }
49
- : {});
50
- }
51
- constructor(obj) {
52
- super(obj);
53
- if (typeof obj === 'object' && obj) {
54
- if (typeof obj.properties !== 'undefined') {
55
- this.properties = Object.keys(obj.properties).reduce((acc, curr) => {
56
- if (obj.properties[curr] instanceof SchemaBuilder_js_1.SchemaBuilder) {
57
- acc[curr] = obj.properties[curr].clone();
58
- }
59
- else {
60
- acc[curr] = {
61
- ...obj.properties[curr]
62
- };
192
+ }
193
+ const validationResults = await Promise.all(propKeys.map(async (key) => ({
194
+ key,
195
+ result: await this.#properties[key].validate(objToValidate[key], {
196
+ ...context,
197
+ path: `${path}.${key}`
198
+ })
199
+ })));
200
+ const notValidResults = validationResults.filter((res) => !res.result.valid);
201
+ validationResults
202
+ .filter((res) => res.result.valid)
203
+ .forEach(({ key, result }) => {
204
+ objToValidate[key] = result.object;
205
+ });
206
+ errors = [
207
+ ...errors,
208
+ ...notValidResults.reduce((acc, val) => [...acc, ...(val?.result?.errors || [])], [])
209
+ ];
210
+ for (let i = 0; i < objKeys.length; i++) {
211
+ const key = objKeys[i];
212
+ if (!(key in this.#properties) && !this.#acceptUnknownProps) {
213
+ errors.push({
214
+ message: `unknown property '${key}'`,
215
+ path: path
216
+ });
217
+ if (!doNotStopOnFirstError) {
218
+ if (validationTransaction) {
219
+ validationTransaction.rollback();
63
220
  }
64
- return acc;
65
- }, {});
66
- }
67
- if (typeof obj.preprocessors !== 'undefined') {
68
- this.preprocessors = { ...obj.preprocessors };
69
- }
70
- if (typeof obj.noUnknownProperties !== 'undefined') {
71
- this.noUnknownProperties = obj.noUnknownProperties;
221
+ return {
222
+ valid: false,
223
+ errors: [errors[0]]
224
+ };
225
+ }
72
226
  }
73
227
  }
74
- else {
75
- const defaultSchema = defaultSchemas_js_1.defaultSchemas['object'];
76
- this.isRequired = defaultSchema.isRequired;
77
- this.isNullable = defaultSchema.isNullable;
228
+ if (notValidResults.length === 0 && errors.length === 0) {
229
+ const commited = validationTransaction.commit();
230
+ return {
231
+ valid: true,
232
+ object: commited.validatedObject
233
+ };
78
234
  }
235
+ validationTransaction.rollback();
236
+ return {
237
+ valid: false,
238
+ errors: doNotStopOnFirstError
239
+ ? errors
240
+ : errors[0]
241
+ ? [errors[0]]
242
+ : []
243
+ };
79
244
  }
80
- static create(obj) {
81
- return new ObjectSchemaBuilder(obj);
82
- }
83
- canHaveUnknownProps() {
84
- if (this.noUnknownProperties === false) {
85
- return this;
86
- }
87
- return ObjectSchemaBuilder.create({
88
- ...this._schema,
89
- noUnknownProperties: false
245
+ /**
246
+ * Fields not defined in `properties` will not be validated
247
+ * and will be passed through the validation.
248
+ */
249
+ acceptUnknownProps() {
250
+ return this.createFromProps({
251
+ ...this.introspect(),
252
+ acceptUnknownProps: true
90
253
  });
91
254
  }
92
- noUnknownProps() {
93
- if (this.noUnknownProperties === true) {
94
- return this;
95
- }
96
- return ObjectSchemaBuilder.create({
97
- ...this._schema,
98
- noUnknownProperties: true
255
+ /**
256
+ * Fields not defined in `properties` will be considered
257
+ * as schema violation. This is the default behavior.
258
+ */
259
+ notAcceptUnknownProps() {
260
+ return this.createFromProps({
261
+ ...this.introspect(),
262
+ acceptUnknownProps: false
99
263
  });
100
264
  }
101
- optional() {
102
- if (this.isRequired === false) {
103
- return this;
104
- }
105
- return ObjectSchemaBuilder.create({
106
- ...this._schema,
107
- isRequired: false
265
+ /**
266
+ * @hidden
267
+ */
268
+ hasType(
269
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
270
+ notUsed) {
271
+ return this.createFromProps({
272
+ ...this.introspect()
108
273
  });
109
274
  }
110
- required() {
111
- if (this.isRequired === true) {
112
- return this;
113
- }
114
- return ObjectSchemaBuilder.create({
115
- ...this._schema,
116
- isRequired: true
275
+ /**
276
+ * @hidden
277
+ */
278
+ clearHasType() {
279
+ return this.createFromProps({
280
+ ...this.introspect()
117
281
  });
118
282
  }
119
- nullable() {
120
- if (this.isNullable === true) {
121
- return this;
283
+ /**
284
+ * Adds a new property to the object schema. The new property
285
+ * will be validated according to the provided schema.
286
+ * @param propName name of the new property
287
+ * @param schema schema builder of the new property
288
+ */
289
+ addProp(propName, schema) {
290
+ if (typeof propName !== 'string' || !propName) {
291
+ throw new Error('propName must be a non empty string');
122
292
  }
123
- return ObjectSchemaBuilder.create({
124
- ...this._schema,
125
- isNullable: true
126
- });
127
- }
128
- notNullable() {
129
- if (this.isNullable === false) {
130
- return this;
293
+ if (propName in this.#properties) {
294
+ throw new Error(`Property ${propName} already exists`);
131
295
  }
132
- return ObjectSchemaBuilder.create({
133
- ...this._schema,
134
- isNullable: false
135
- });
136
- }
137
- addProps(val) {
138
- if (typeof val === 'undefined')
139
- return this;
140
- return ObjectSchemaBuilder.create({
141
- ...this._schema,
296
+ if (!(schema instanceof SchemaBuilder)) {
297
+ throw new Error('schema must be an instance of the SchemaBuilder class');
298
+ }
299
+ return this.createFromProps({
300
+ ...this.introspect(),
142
301
  properties: {
143
- ...this.properties,
144
- ...val
302
+ ...this.introspect().properties,
303
+ [propName]: schema
145
304
  }
146
305
  });
147
306
  }
148
- removeProp(property) {
149
- if (typeof this.properties === 'undefined') {
150
- return this;
307
+ /**
308
+ * @hidden
309
+ * @deprecated this is for internal use, do not use if you are
310
+ * not sure you need it.
311
+ *
312
+ * TODO: This is used to avoid `&` in resulting types. For example,
313
+ * when you have a schema like `object({prop1: string()})` and then use `addProp({prop2: string()})` method,
314
+ * the resulting type without `optimize` will be something like `{prop1: string} & {prop2: string}`. Which
315
+ * is not we would like to have. Instead we want to have `{prop1: string, prop2: string}`. This is what
316
+ * `optimize` method does. However it is not always possible to do this optimization without losing
317
+ * JSDoc comments, which is sucks. For example, I had to disable optimization for UnionSchemas, because
318
+ * comments were lost. Hopefully it will be fixed in the future by Typescript team or somebody will
319
+ * find a workaround/fix and create a pull request.
320
+ */
321
+ optimize() {
322
+ return this.createFromProps({
323
+ ...this.introspect()
324
+ });
325
+ }
326
+ addProps(props) {
327
+ if (props instanceof ObjectSchemaBuilder) {
328
+ return this.addProps(props.introspect().properties);
151
329
  }
152
- if (!(property in this.properties)) {
153
- return this;
330
+ if (typeof props !== 'object') {
331
+ throw new Error('props should be an object');
154
332
  }
155
- const newProperties = {
156
- ...this.properties
157
- };
158
- delete newProperties[property];
159
- let newPreprocessors = this.preprocessors;
160
- if (typeof newPreprocessors !== 'undefined' &&
161
- property in newPreprocessors) {
162
- if (Object.keys(newPreprocessors).length === 1) {
163
- newPreprocessors = undefined;
333
+ if (props === null) {
334
+ throw new Error('props should not be null');
335
+ }
336
+ const newProps = { ...this.#properties };
337
+ for (const key in props) {
338
+ if (key in this.#properties) {
339
+ throw new Error(`property '${key}' already exists`);
164
340
  }
165
- else {
166
- newPreprocessors = {
167
- ...this.preprocessors
168
- };
169
- delete newPreprocessors[property];
341
+ if (!(props[key] instanceof SchemaBuilder)) {
342
+ throw new Error(`${key} is not a SchemaBuilder`);
170
343
  }
344
+ newProps[key] = props[key];
171
345
  }
172
- return ObjectSchemaBuilder.create({
173
- ...this._schema,
174
- preprocessors: newPreprocessors,
175
- properties: newProperties
346
+ return this.createFromProps({
347
+ ...this.introspect(),
348
+ properties: newProps
176
349
  });
177
350
  }
178
- setPropPreprocessor(property, validator) {
179
- if (typeof this.preprocessors === 'undefined') {
180
- this.preprocessors = {
181
- [property]: validator
182
- };
351
+ omit(propNameOrArrayOrPropsOrBuilder) {
352
+ if (typeof propNameOrArrayOrPropsOrBuilder === 'string') {
353
+ // remove one field
354
+ const propName = propNameOrArrayOrPropsOrBuilder;
355
+ if (!propName || !(propName in this.#properties)) {
356
+ throw new Error(`property ${propName.toString()} does not exists in the schema`);
357
+ }
358
+ return this.createFromProps({
359
+ ...this.introspect(),
360
+ properties: (() => {
361
+ const result = { ...this.#properties };
362
+ delete result[propName];
363
+ return result;
364
+ })()
365
+ });
183
366
  }
184
- else {
185
- this.preprocessors = {
186
- ...this.preprocessors,
187
- [property]: validator
367
+ else if (Array.isArray(propNameOrArrayOrPropsOrBuilder)) {
368
+ const propsArray = propNameOrArrayOrPropsOrBuilder;
369
+ const distinctKeys = new Map();
370
+ propsArray.forEach((key) => {
371
+ if (typeof key !== 'string' || !key) {
372
+ throw new Error('property name must be a string');
373
+ }
374
+ if (!(key in this.#properties)) {
375
+ throw new Error(`property ${key.toString()} does not exists in the schema`);
376
+ }
377
+ distinctKeys.set(key.toString(), true);
378
+ });
379
+ if (distinctKeys.size === 0) {
380
+ throw new Error('please provide at least one property to omit');
381
+ }
382
+ const props = {
383
+ ...this.introspect()
188
384
  };
189
- }
190
- return ObjectSchemaBuilder.create({
191
- ...this._schema,
192
- preprocessors: {
193
- ...this.preprocessors
385
+ for (const key of distinctKeys.keys()) {
386
+ delete props.properties[key];
194
387
  }
195
- });
196
- }
197
- unsetPropPreprocessor(property) {
198
- if (typeof this.preprocessors === 'undefined' ||
199
- !(property in this.preprocessors)) {
200
- return this;
388
+ return this.createFromProps(props);
201
389
  }
202
- let newPreprocessors;
203
- if (Object.keys(this.preprocessors).length === 1) {
204
- newPreprocessors = undefined;
205
- }
206
- else {
207
- newPreprocessors = {
208
- ...this.preprocessors
390
+ else if (propNameOrArrayOrPropsOrBuilder instanceof ObjectSchemaBuilder) {
391
+ const propsToOmit = {
392
+ ...propNameOrArrayOrPropsOrBuilder.introspect().properties
393
+ };
394
+ const props = {
395
+ ...this.introspect()
209
396
  };
210
- delete newPreprocessors[property];
397
+ for (const key in propsToOmit) {
398
+ if (key in props.properties) {
399
+ delete props.properties[key];
400
+ }
401
+ }
402
+ return this.createFromProps(props);
211
403
  }
212
- return ObjectSchemaBuilder.create({
213
- ...this._schema,
214
- preprocessors: newPreprocessors
215
- });
404
+ throw new Error('this parameter type is not supported');
216
405
  }
217
- clearPropsPreprocessors() {
218
- if (typeof this.preprocessors === 'undefined') {
219
- return this;
406
+ /**
407
+ * Adds all properties from `schema` to the current schema.
408
+ * `TSchema & TAnotherSchema` is a good example of the similar concept
409
+ * in the TS type system.
410
+ * @param schema an object schema to take properties from
411
+ */
412
+ intersect(schema) {
413
+ if (!(schema instanceof ObjectSchemaBuilder)) {
414
+ throw new Error('schema must be an instance of the ObjectSchemaBuilder class');
220
415
  }
221
- return ObjectSchemaBuilder.create({
222
- ...this._schema,
223
- preprocessors: undefined
416
+ const remoteProps = schema.introspect().properties;
417
+ const localProps = this.introspect();
418
+ const newProps = Object.keys(localProps.properties).reduce((acc, curr) => {
419
+ acc[curr] =
420
+ curr in remoteProps ? remoteProps[curr] : localProps[curr];
421
+ return acc;
422
+ }, {});
423
+ return this.createFromProps({
424
+ ...this.introspect(),
425
+ properties: newProps
224
426
  });
225
427
  }
226
- mapToType() {
227
- return this;
228
- }
229
- clearMapToType() {
230
- return this;
231
- }
232
- makeAllPropsOptional() {
233
- let res = this;
234
- for (const prop in this.properties) {
235
- res = res.makePropOptional(prop);
236
- }
237
- return res;
238
- }
239
- makePropOptional(property) {
240
- if (typeof this.properties[property] === 'undefined')
241
- throw new Error(`Property ${property} does not exists`);
242
- if (this.properties[property] instanceof SchemaBuilder_js_1.SchemaBuilder) {
243
- return ObjectSchemaBuilder.create({
244
- ...this._schema,
245
- properties: {
246
- ...this.properties,
247
- [property]: this.properties[property].optional()
248
- }
428
+ partial(propNameOrArray) {
429
+ if (typeof propNameOrArray === 'undefined' ||
430
+ propNameOrArray === null) {
431
+ return this.createFromProps({
432
+ ...this.introspect(),
433
+ properties: Object.keys(this.#properties).reduce((acc, key) => {
434
+ acc[key] = this.#properties[key].optional();
435
+ return acc;
436
+ }, {})
249
437
  });
250
438
  }
251
- else {
252
- return ObjectSchemaBuilder.create({
253
- ...this._schema,
254
- properties: {
255
- ...this.properties,
256
- [property]: {
257
- ...this.properties[property],
258
- isRequired: false
259
- }
439
+ if (Array.isArray(propNameOrArray)) {
440
+ const propsArray = propNameOrArray;
441
+ if (propsArray.length === 0) {
442
+ throw new Error('properties cannot be empty');
443
+ }
444
+ const newProps = {
445
+ ...this.introspect()
446
+ };
447
+ propsArray.forEach((key) => {
448
+ if (typeof key !== 'string') {
449
+ throw new Error('each propery in property list must be as string value');
260
450
  }
261
- });
262
- }
263
- }
264
- makePropRequired(property) {
265
- if (typeof this.properties[property] === 'undefined')
266
- throw new Error(`Property ${property} does not exists`);
267
- if (this.properties[property] instanceof SchemaBuilder_js_1.SchemaBuilder) {
268
- return ObjectSchemaBuilder.create({
269
- ...this._schema,
270
- properties: {
271
- ...this.properties,
272
- [property]: this.properties[property].required()
451
+ if (!(key in newProps.properties)) {
452
+ throw new Error(`property ${key} does not exists`);
273
453
  }
454
+ newProps.properties[key] =
455
+ newProps.properties[key].optional();
274
456
  });
457
+ return this.createFromProps(newProps);
275
458
  }
276
- else {
277
- return ObjectSchemaBuilder.create({
278
- ...this._schema,
279
- properties: {
280
- ...this.properties,
281
- [property]: {
282
- ...this.properties[property],
283
- isRequired: true
284
- }
285
- }
286
- });
459
+ if (typeof propNameOrArray === 'string') {
460
+ return this.modifyPropSchema(propNameOrArray, (schema) => schema.optional());
287
461
  }
462
+ throw new Error('expecting string or string[] parameter');
288
463
  }
289
- makePropNullable(property) {
290
- if (typeof this.properties[property] === 'undefined')
291
- throw new Error(`Property ${property} does not exists`);
292
- if (this.properties[property] instanceof SchemaBuilder_js_1.SchemaBuilder) {
293
- return ObjectSchemaBuilder.create({
294
- ...this._schema,
464
+ pick(properties) {
465
+ if (typeof properties === 'string') {
466
+ const property = properties;
467
+ if (!property) {
468
+ throw new Error('property cannot be empty');
469
+ }
470
+ if (!(property in this.#properties)) {
471
+ throw new Error(`property ${property} does not exists`);
472
+ }
473
+ return this.createFromProps({
474
+ ...this.introspect(),
295
475
  properties: {
296
- ...this.properties,
297
- [property]: this.properties[property].nullable()
476
+ [property]: this.#properties[property]
298
477
  }
299
478
  });
300
479
  }
301
- else {
302
- return ObjectSchemaBuilder.create({
303
- ...this._schema,
304
- properties: {
305
- ...this.properties,
306
- [property]: {
307
- ...this.properties[property],
308
- isNullable: true
309
- }
480
+ if (Array.isArray(properties)) {
481
+ if (properties.length === 0) {
482
+ throw new Error('properties must be a non empty erray');
483
+ }
484
+ const newProperties = properties.reduce((acc, curr) => {
485
+ if (typeof curr !== 'string' || !curr) {
486
+ throw new Error('each property name must be a non empty string');
487
+ }
488
+ if (!(curr in this.#properties)) {
489
+ throw new Error(`property ${curr} does not exists`);
310
490
  }
491
+ acc[curr] = this.#properties[curr];
492
+ return acc;
493
+ }, {});
494
+ return this.createFromProps({
495
+ ...this.introspect(),
496
+ properties: newProperties
311
497
  });
312
498
  }
499
+ if (properties instanceof ObjectSchemaBuilder) {
500
+ const externalSchema = properties;
501
+ const props = Object.keys(externalSchema.introspect().properties).filter((p) => typeof this.#properties[p] !== 'undefined');
502
+ if (props.length === 0) {
503
+ throw new Error('there are no common properties in provided schemas');
504
+ }
505
+ return this.pick(props);
506
+ }
507
+ throw new Error('string, array or ObjectSchemaBuilder is expected');
313
508
  }
314
- makePropNotNullable(property) {
315
- if (typeof this.properties[property] === 'undefined')
316
- throw new Error(`Property ${property} does not exists`);
317
- if (this.properties[property] instanceof SchemaBuilder_js_1.SchemaBuilder) {
318
- return ObjectSchemaBuilder.create({
319
- ...this._schema,
320
- properties: {
321
- ...this.properties,
322
- [property]: this.properties[property].notNullable()
323
- }
324
- });
509
+ /**
510
+ * Modify schema for `propName` and return a new schema.
511
+ * Could be useful if you want to leave all schema intact, but
512
+ * change a type of one property.
513
+ * @param propName name of the property (string)
514
+ * @param callback callback function returning a new schema fo the `propName`. As a first parameter
515
+ * you will receive an old schema for `propName`.
516
+ * @returns
517
+ */
518
+ modifyPropSchema(propName, callback) {
519
+ if (typeof propName !== 'string' || !propName) {
520
+ throw new Error('propName must be a non empty string');
325
521
  }
326
- else {
327
- return ObjectSchemaBuilder.create({
328
- ...this._schema,
329
- properties: {
330
- ...this.properties,
331
- [property]: {
332
- ...this.properties[property],
333
- isNullable: false
334
- }
335
- }
336
- });
522
+ if (!(propName in this.#properties)) {
523
+ throw new Error(`property ${propName} does not exists in the schema`);
524
+ }
525
+ if (typeof callback !== 'function') {
526
+ throw new Error('callback must be a function');
527
+ }
528
+ const callbackResult = callback(this.#properties[propName]);
529
+ if (!(callbackResult instanceof SchemaBuilder)) {
530
+ throw new Error('callback must return a SchemaBuilder object');
337
531
  }
532
+ const props = {
533
+ ...this.introspect()
534
+ };
535
+ props.properties = {
536
+ ...props.properties,
537
+ [propName]: callbackResult
538
+ };
539
+ return this.createFromProps(props);
540
+ }
541
+ /**
542
+ * An alias for `.partial(prop: string)`
543
+ * @param prop name of the property
544
+ */
545
+ makePropOptional(prop) {
546
+ return this.modifyPropSchema(prop, (builder) => builder.optional());
338
547
  }
548
+ /**
549
+ * Marks `prop` as required property.
550
+ * If `prop` does not exists in the current schema,
551
+ * an error will be thrown.
552
+ * @param prop name of the property
553
+ */
554
+ makePropRequired(prop) {
555
+ return this.modifyPropSchema(prop, (builder) => builder.required());
556
+ }
557
+ /**
558
+ * `Partial<T>` would be a good example of the
559
+ * same operation in the TS world.
560
+ */
561
+ makeAllPropsOptional() {
562
+ return this.createFromProps({
563
+ ...this.introspect(),
564
+ properties: Object.keys(this.#properties).reduce((acc, curr) => {
565
+ acc[curr] = this.#properties[curr].optional();
566
+ return acc;
567
+ }, {})
568
+ });
569
+ }
570
+ /**
571
+ * `Required<T>` would be a good example of the
572
+ * same operation in the TS world.
573
+ */
574
+ makeAllPropsRequired() {
575
+ return this.createFromProps({
576
+ ...this.introspect(),
577
+ properties: Object.keys(this.#properties).reduce((acc, curr) => {
578
+ acc[curr] = this.#properties[curr].required();
579
+ return acc;
580
+ }, {})
581
+ });
582
+ }
583
+ }
584
+ export function object(props) {
585
+ return ObjectSchemaBuilder.create({
586
+ isRequired: true,
587
+ properties: props
588
+ });
339
589
  }
340
- exports.ObjectSchemaBuilder = ObjectSchemaBuilder;
341
- const object = (properties) => typeof properties === 'undefined'
342
- ? ObjectSchemaBuilder.create()
343
- : ObjectSchemaBuilder.create().addProps(properties);
344
- exports.object = object;