@cleverbrush/schema 1.0.0-beta.4 → 1.0.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 +177 -0
  9. package/dist/builders/DateSchemaBuilder.js +433 -0
  10. package/dist/builders/FunctionSchemaBuilder.d.ts +59 -23
  11. package/dist/builders/FunctionSchemaBuilder.js +103 -56
  12. package/dist/builders/NumberSchemaBuilder.d.ts +159 -59
  13. package/dist/builders/NumberSchemaBuilder.js +345 -165
  14. package/dist/builders/ObjectSchemaBuilder.d.ts +357 -81
  15. package/dist/builders/ObjectSchemaBuilder.js +519 -283
  16. package/dist/builders/SchemaBuilder.d.ts +158 -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 +129 -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 -230
  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 -134
  52. package/src/builders/FunctionSchemaBuilder.ts +0 -109
  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 -827
  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,580 @@
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
+ optimize() {
313
+ return this.createFromProps({
314
+ ...this.introspect()
315
+ });
316
+ }
317
+ addProps(props) {
318
+ if (props instanceof ObjectSchemaBuilder) {
319
+ return this.addProps(props.introspect().properties);
151
320
  }
152
- if (!(property in this.properties)) {
153
- return this;
321
+ if (typeof props !== 'object') {
322
+ throw new Error('props should be an object');
154
323
  }
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;
324
+ if (props === null) {
325
+ throw new Error('props should not be null');
326
+ }
327
+ const newProps = { ...this.#properties };
328
+ for (const key in props) {
329
+ if (key in this.#properties) {
330
+ throw new Error(`property '${key}' already exists`);
164
331
  }
165
- else {
166
- newPreprocessors = {
167
- ...this.preprocessors
168
- };
169
- delete newPreprocessors[property];
332
+ if (!(props[key] instanceof SchemaBuilder)) {
333
+ throw new Error(`${key} is not a SchemaBuilder`);
170
334
  }
335
+ newProps[key] = props[key];
171
336
  }
172
- return ObjectSchemaBuilder.create({
173
- ...this._schema,
174
- preprocessors: newPreprocessors,
175
- properties: newProperties
337
+ return this.createFromProps({
338
+ ...this.introspect(),
339
+ properties: newProps
176
340
  });
177
341
  }
178
- setPropPreprocessor(property, validator) {
179
- if (typeof this.preprocessors === 'undefined') {
180
- this.preprocessors = {
181
- [property]: validator
182
- };
342
+ omit(propNameOrArrayOrPropsOrBuilder) {
343
+ if (typeof propNameOrArrayOrPropsOrBuilder === 'string') {
344
+ // remove one field
345
+ const propName = propNameOrArrayOrPropsOrBuilder;
346
+ if (!propName || !(propName in this.#properties)) {
347
+ throw new Error(`property ${propName.toString()} does not exists in the schema`);
348
+ }
349
+ return this.createFromProps({
350
+ ...this.introspect(),
351
+ properties: (() => {
352
+ const result = { ...this.#properties };
353
+ delete result[propName];
354
+ return result;
355
+ })()
356
+ });
183
357
  }
184
- else {
185
- this.preprocessors = {
186
- ...this.preprocessors,
187
- [property]: validator
358
+ else if (Array.isArray(propNameOrArrayOrPropsOrBuilder)) {
359
+ const propsArray = propNameOrArrayOrPropsOrBuilder;
360
+ const distinctKeys = new Map();
361
+ propsArray.forEach((key) => {
362
+ if (typeof key !== 'string' || !key) {
363
+ throw new Error('property name must be a string');
364
+ }
365
+ if (!(key in this.#properties)) {
366
+ throw new Error(`property ${key.toString()} does not exists in the schema`);
367
+ }
368
+ distinctKeys.set(key.toString(), true);
369
+ });
370
+ if (distinctKeys.size === 0) {
371
+ throw new Error('please provide at least one property to omit');
372
+ }
373
+ const props = {
374
+ ...this.introspect()
188
375
  };
189
- }
190
- return ObjectSchemaBuilder.create({
191
- ...this._schema,
192
- preprocessors: {
193
- ...this.preprocessors
376
+ for (const key of distinctKeys.keys()) {
377
+ delete props.properties[key];
194
378
  }
195
- });
196
- }
197
- unsetPropPreprocessor(property) {
198
- if (typeof this.preprocessors === 'undefined' ||
199
- !(property in this.preprocessors)) {
200
- return this;
379
+ return this.createFromProps(props);
201
380
  }
202
- let newPreprocessors;
203
- if (Object.keys(this.preprocessors).length === 1) {
204
- newPreprocessors = undefined;
205
- }
206
- else {
207
- newPreprocessors = {
208
- ...this.preprocessors
381
+ else if (propNameOrArrayOrPropsOrBuilder instanceof ObjectSchemaBuilder) {
382
+ const propsToOmit = {
383
+ ...propNameOrArrayOrPropsOrBuilder.introspect().properties
384
+ };
385
+ const props = {
386
+ ...this.introspect()
209
387
  };
210
- delete newPreprocessors[property];
388
+ for (const key in propsToOmit) {
389
+ if (key in props.properties) {
390
+ delete props.properties[key];
391
+ }
392
+ }
393
+ return this.createFromProps(props);
211
394
  }
212
- return ObjectSchemaBuilder.create({
213
- ...this._schema,
214
- preprocessors: newPreprocessors
215
- });
395
+ throw new Error('this parameter type is not supported');
216
396
  }
217
- clearPropsPreprocessors() {
218
- if (typeof this.preprocessors === 'undefined') {
219
- return this;
397
+ /**
398
+ * Adds all properties from `schema` to the current schema.
399
+ * `TSchema & TAnotherSchema` is a good example of the similar concept
400
+ * in the TS type system.
401
+ * @param schema an object schema to take properties from
402
+ */
403
+ intersect(schema) {
404
+ if (!(schema instanceof ObjectSchemaBuilder)) {
405
+ throw new Error('schema must be an instance of the ObjectSchemaBuilder class');
220
406
  }
221
- return ObjectSchemaBuilder.create({
222
- ...this._schema,
223
- preprocessors: undefined
407
+ const remoteProps = schema.introspect().properties;
408
+ const localProps = this.introspect();
409
+ const newProps = Object.keys(localProps.properties).reduce((acc, curr) => {
410
+ acc[curr] =
411
+ curr in remoteProps ? remoteProps[curr] : localProps[curr];
412
+ return acc;
413
+ }, {});
414
+ return this.createFromProps({
415
+ ...this.introspect(),
416
+ properties: newProps
224
417
  });
225
418
  }
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
- }
419
+ partial(propNameOrArray) {
420
+ if (typeof propNameOrArray === 'undefined' ||
421
+ propNameOrArray === null) {
422
+ return this.createFromProps({
423
+ ...this.introspect(),
424
+ properties: Object.keys(this.#properties).reduce((acc, key) => {
425
+ acc[key] = this.#properties[key].optional();
426
+ return acc;
427
+ }, {})
249
428
  });
250
429
  }
251
- else {
252
- return ObjectSchemaBuilder.create({
253
- ...this._schema,
254
- properties: {
255
- ...this.properties,
256
- [property]: {
257
- ...this.properties[property],
258
- isRequired: false
259
- }
430
+ if (Array.isArray(propNameOrArray)) {
431
+ const propsArray = propNameOrArray;
432
+ if (propsArray.length === 0) {
433
+ throw new Error('properties cannot be empty');
434
+ }
435
+ const newProps = {
436
+ ...this.introspect()
437
+ };
438
+ propsArray.forEach((key) => {
439
+ if (typeof key !== 'string') {
440
+ throw new Error('each propery in property list must be as string value');
260
441
  }
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()
442
+ if (!(key in newProps.properties)) {
443
+ throw new Error(`property ${key} does not exists`);
273
444
  }
445
+ newProps.properties[key] =
446
+ newProps.properties[key].optional();
274
447
  });
448
+ return this.createFromProps(newProps);
275
449
  }
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
- });
450
+ if (typeof propNameOrArray === 'string') {
451
+ return this.modifyPropSchema(propNameOrArray, (schema) => schema.optional());
287
452
  }
453
+ throw new Error('expecting string or string[] parameter');
288
454
  }
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,
455
+ pick(properties) {
456
+ if (typeof properties === 'string') {
457
+ const property = properties;
458
+ if (!property) {
459
+ throw new Error('property cannot be empty');
460
+ }
461
+ if (!(property in this.#properties)) {
462
+ throw new Error(`property ${property} does not exists`);
463
+ }
464
+ return this.createFromProps({
465
+ ...this.introspect(),
295
466
  properties: {
296
- ...this.properties,
297
- [property]: this.properties[property].nullable()
467
+ [property]: this.#properties[property]
298
468
  }
299
469
  });
300
470
  }
301
- else {
302
- return ObjectSchemaBuilder.create({
303
- ...this._schema,
304
- properties: {
305
- ...this.properties,
306
- [property]: {
307
- ...this.properties[property],
308
- isNullable: true
309
- }
471
+ if (Array.isArray(properties)) {
472
+ if (properties.length === 0) {
473
+ throw new Error('properties must be a non empty erray');
474
+ }
475
+ const newProperties = properties.reduce((acc, curr) => {
476
+ if (typeof curr !== 'string' || !curr) {
477
+ throw new Error('each property name must be a non empty string');
478
+ }
479
+ if (!(curr in this.#properties)) {
480
+ throw new Error(`property ${curr} does not exists`);
310
481
  }
482
+ acc[curr] = this.#properties[curr];
483
+ return acc;
484
+ }, {});
485
+ return this.createFromProps({
486
+ ...this.introspect(),
487
+ properties: newProperties
311
488
  });
312
489
  }
490
+ if (properties instanceof ObjectSchemaBuilder) {
491
+ const externalSchema = properties;
492
+ const props = Object.keys(externalSchema.introspect().properties).filter((p) => typeof this.#properties[p] !== 'undefined');
493
+ if (props.length === 0) {
494
+ throw new Error('there are no common properties in provided schemas');
495
+ }
496
+ return this.pick(props);
497
+ }
498
+ throw new Error('string, array or ObjectSchemaBuilder is expected');
313
499
  }
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
- });
500
+ /**
501
+ * Modify schema for `propName` and return a new schema.
502
+ * Could be useful if you want to leave all schema intact, but
503
+ * change a type of one property.
504
+ * @param propName name of the property (string)
505
+ * @param callback callback function returning a new schema fo the `propName`. As a first parameter
506
+ * you will receive an old schema for `propName`.
507
+ * @returns
508
+ */
509
+ modifyPropSchema(propName, callback) {
510
+ if (typeof propName !== 'string' || !propName) {
511
+ throw new Error('propName must be a non empty string');
325
512
  }
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
- });
513
+ if (!(propName in this.#properties)) {
514
+ throw new Error(`property ${propName} does not exists in the schema`);
515
+ }
516
+ if (typeof callback !== 'function') {
517
+ throw new Error('callback must be a function');
518
+ }
519
+ const callbackResult = callback(this.#properties[propName]);
520
+ if (!(callbackResult instanceof SchemaBuilder)) {
521
+ throw new Error('callback must return a SchemaBuilder object');
337
522
  }
523
+ const props = {
524
+ ...this.introspect()
525
+ };
526
+ props.properties = {
527
+ ...props.properties,
528
+ [propName]: callbackResult
529
+ };
530
+ return this.createFromProps(props);
531
+ }
532
+ /**
533
+ * An alias for `.partial(prop: string)`
534
+ * @param prop name of the property
535
+ */
536
+ makePropOptional(prop) {
537
+ return this.modifyPropSchema(prop, (builder) => builder.optional());
338
538
  }
539
+ /**
540
+ * Marks `prop` as required property.
541
+ * If `prop` does not exists in the current schema,
542
+ * an error will be thrown.
543
+ * @param prop name of the property
544
+ */
545
+ makePropRequired(prop) {
546
+ return this.modifyPropSchema(prop, (builder) => builder.required());
547
+ }
548
+ /**
549
+ * `Partial<T>` would be a good example of the
550
+ * same operation in the TS world.
551
+ */
552
+ makeAllPropsOptional() {
553
+ return this.createFromProps({
554
+ ...this.introspect(),
555
+ properties: Object.keys(this.#properties).reduce((acc, curr) => {
556
+ acc[curr] = this.#properties[curr].optional();
557
+ return acc;
558
+ }, {})
559
+ });
560
+ }
561
+ /**
562
+ * `Required<T>` would be a good example of the
563
+ * same operation in the TS world.
564
+ */
565
+ makeAllPropsRequired() {
566
+ return this.createFromProps({
567
+ ...this.introspect(),
568
+ properties: Object.keys(this.#properties).reduce((acc, curr) => {
569
+ acc[curr] = this.#properties[curr].required();
570
+ return acc;
571
+ }, {})
572
+ });
573
+ }
574
+ }
575
+ export function object(props) {
576
+ return ObjectSchemaBuilder.create({
577
+ isRequired: true,
578
+ properties: props
579
+ });
339
580
  }
340
- exports.ObjectSchemaBuilder = ObjectSchemaBuilder;
341
- const object = (properties) => typeof properties === 'undefined'
342
- ? ObjectSchemaBuilder.create()
343
- : ObjectSchemaBuilder.create().addProps(properties);
344
- exports.object = object;