@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,36 +1,160 @@
1
- import { Schema, Validator } from '../schema.js';
2
- export interface ISchemaBuilder<TRequired extends boolean = true, TNullable extends boolean = false> {
3
- readonly _schema: Schema;
4
- isRequired: TRequired;
5
- isNullable: TNullable;
6
- validators?: Validator[];
7
- preprocessor?: ((value: unknown) => unknown | Promise<unknown>) | string;
8
- addValidator(validator: Validator): this;
1
+ import { Transaction } from '../utils/transaction.js';
2
+ export type InferType<T> = T extends SchemaBuilder<infer TResult, infer TRequired> ? T extends {
3
+ optimize: (...args: any[]) => SchemaBuilder<infer TOptimizedType, infer TOptimizedRequired>;
4
+ } ? TOptimizedRequired extends true ? TOptimizedType : MakeOptional<TOptimizedType> : TRequired extends true ? TResult : MakeOptional<TResult> : T;
5
+ export type ValidationError = {
6
+ path: string;
7
+ message: string;
8
+ };
9
+ export type MakeRequired<T> = NonNullable<T>;
10
+ export type MakeOptional<T> = {
11
+ prop?: T;
12
+ }['prop'];
13
+ export type ValidationResult<T> = {
14
+ /**
15
+ * If `true` - object satisfies schema
16
+ */
17
+ valid: boolean;
18
+ /**
19
+ * Contains validated object. Can be different (if there are any preprocessors in the schema) from object
20
+ * passed to the `validate` method of the `SchemaBuilder` class.
21
+ */
22
+ object?: T;
23
+ errors?: ValidationError[];
24
+ };
25
+ export type PreValidationResult<T, TTransactionType> = Omit<ValidationResult<T>, 'object'> & {
26
+ context: ValidationContext;
27
+ transaction?: Transaction<TTransactionType>;
28
+ };
29
+ type ValidatorResult<T> = Omit<ValidationResult<T>, 'object' | 'errors'> & {
30
+ errors?: Omit<ValidationError, 'path'>[];
31
+ };
32
+ export type Preprocessor<T> = (object: T) => Promise<T> | T;
33
+ export type Validator<T> = (object: T) => Promise<ValidatorResult<T>> | ValidatorResult<T>;
34
+ export type SchemaBuilderProps<T> = {
35
+ type: string;
36
+ isRequired?: boolean;
37
+ preprocessors: Preprocessor<T>[];
38
+ validators: Validator<T>[];
39
+ };
40
+ export type ValidationContext = {
41
+ /**
42
+ * Path of the field. **Optional**, used to display correct error path in the {@link ValidationError}
43
+ */
44
+ path?: string;
45
+ /**
46
+ * Optional. By default validation will stop after the first validation error, in case if
47
+ * you want to receive all validation erors, please set this flag to `true`.
48
+ * You might need it to display validation errors.
49
+ */
50
+ doNotStopOnFirstError?: boolean;
51
+ };
52
+ /**
53
+ * Base class for all schema builders. Provides basic functionality for schema building.
54
+ *
55
+ * **Note:** this class is not intended to be used directly, use one of the subclasses instead.
56
+ * @typeparam TResult Type of the object that will be returned by `validate()` method.
57
+ * @typeparam TRequired If `true`, object will be required. If `false`, object will be optional.
58
+ */
59
+ export declare abstract class SchemaBuilder<TResult = any, TRequired extends boolean = true> {
60
+ #private;
61
+ /**
62
+ * Set type of schema explicitly. `notUsed` param is needed only for cas when JS is used. E.g. when you
63
+ * can't call method like `schema.hasType<Date>()`, so instead you can call `schema.hasType(new Date())`
64
+ * with the same result.
65
+ */
66
+ abstract hasType<T>(notUsed?: T): any;
67
+ /**
68
+ * Clears type set by call to `.hasType<T>()`, default schema type inference will be used
69
+ * for schema retuned by this call.
70
+ */
71
+ abstract clearHasType(): any;
72
+ /**
73
+ * Protected method used to create an new instance of the Builder
74
+ * defined by the `props` object. Should be used to instanticate new
75
+ * builders to keep builder's immutability.
76
+ * @param props arbitrary props object
77
+ */
78
+ protected abstract createFromProps(props: any): this;
79
+ protected get type(): string;
80
+ protected set type(value: string);
81
+ /**
82
+ * A list of preprocessors associated with
83
+ * the Builder
84
+ */
85
+ protected get preprocessors(): Preprocessor<TResult>[];
86
+ /**
87
+ * A list of validators associated with
88
+ * the Builder
89
+ */
90
+ protected get validators(): Validator<TResult>[];
91
+ protected get isRequired(): TRequired;
92
+ protected set isRequired(value: boolean);
93
+ protected preValidate(
94
+ /**
95
+ * Object to validate
96
+ */
97
+ object: any, context?: ValidationContext): Promise<PreValidationResult<any, {
98
+ validatedObject: any;
99
+ }>>;
100
+ /**
101
+ * Generates a serializable object describing the defined schema
102
+ */
103
+ introspect(): {
104
+ /**
105
+ * String `id` of schema type, e.g. `string', `number` or `object`.
106
+ */
107
+ type: string;
108
+ /**
109
+ * If set to `false`, schema will be optional (`null` or `undefined` values
110
+ * will be considered as valid).
111
+ */
112
+ isRequired: boolean;
113
+ /**
114
+ * Array of preprocessor functions
115
+ */
116
+ preprocessors: readonly Preprocessor<TResult>[];
117
+ /**
118
+ * Array of validator functions
119
+ */
120
+ validators: readonly Validator<TResult>[];
121
+ };
122
+ /**
123
+ * Makes schema optional (consider `null` and `undefined` as valid objects for this schema)
124
+ */
125
+ optional(): any;
126
+ /**
127
+ * Makes schema required (consider `null` and `undefined` as invalid objects for this schema)
128
+ */
129
+ required(): any;
130
+ /**
131
+ * Adds a `preprocessor` to a preprocessors list
132
+ */
133
+ addPreprocessor(preprocessor: Preprocessor<TResult>): this;
134
+ /**
135
+ * Remove all preprocessors for this schema.
136
+ */
137
+ clearPreprocessors(): this;
138
+ /**
139
+ * Adds a `validator` to validators list.
140
+ */
141
+ addValidator(validator: Validator<TResult>): this;
142
+ /**
143
+ * Remove all validators for this schema.
144
+ */
9
145
  clearValidators(): this;
10
- addPreprocessor(preprocessor: ((value: unknown) => unknown | Promise<unknown>) | string): this;
11
- clearPreprocessor(): this;
12
- clone(): this;
13
- }
14
- export declare abstract class SchemaBuilder<TRequired extends boolean = true, TNullable extends boolean = false> implements ISchemaBuilder<TRequired, TNullable> {
15
- protected _isRequired: TRequired;
16
- protected _isNullable: TNullable;
17
- validators?: Validator[];
18
- preprocessor?: ((value: unknown) => unknown | Promise<unknown>) | string;
19
- abstract clone(): this;
20
- abstract get _schema(): Schema;
21
- protected constructor(obj: {
22
- isRequired?: boolean;
23
- isNullable?: boolean;
24
- preprocessor?: ((value: unknown) => unknown | Promise<unknown>) | string;
25
- validators?: Validator[];
26
- });
27
- protected getCommonSchema(): any;
28
- addPreprocessor(preprocessor: ((value: unknown) => unknown | Promise<unknown>) | string): this;
29
- clearPreprocessor(): this;
30
- addValidator(validator: Validator): this;
31
- clearValidators(): this;
32
- get isRequired(): TRequired;
33
- protected set isRequired(val: boolean);
34
- get isNullable(): TNullable;
35
- protected set isNullable(val: boolean);
146
+ /**
147
+ * Perform schema validation on `object`.
148
+ */
149
+ abstract validate(
150
+ /**
151
+ * Object to validate
152
+ */
153
+ object: any,
154
+ /**
155
+ * Optional `ValidationContext` settings
156
+ */
157
+ context?: ValidationContext): Promise<ValidationResult<any>>;
158
+ protected constructor(props: SchemaBuilderProps<TResult>);
36
159
  }
160
+ export {};
@@ -1,88 +1,275 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SchemaBuilder = void 0;
4
- class SchemaBuilder {
5
- _isRequired = true;
6
- _isNullable = false;
7
- validators;
8
- preprocessor;
9
- constructor(obj) {
10
- if (typeof obj === 'object') {
11
- if (typeof obj.isRequired !== 'undefined') {
12
- this.isRequired = obj.isRequired;
13
- }
14
- if (typeof obj.isNullable !== 'undefined') {
15
- this.isNullable = obj.isNullable;
16
- }
17
- if (Array.isArray(obj.validators)) {
18
- this.validators = [...obj.validators];
1
+ import { transaction } from '../utils/transaction.js';
2
+ /**
3
+ * Base class for all schema builders. Provides basic functionality for schema building.
4
+ *
5
+ * **Note:** this class is not intended to be used directly, use one of the subclasses instead.
6
+ * @typeparam TResult Type of the object that will be returned by `validate()` method.
7
+ * @typeparam TRequired If `true`, object will be required. If `false`, object will be optional.
8
+ */
9
+ export class SchemaBuilder {
10
+ #isRequired = true;
11
+ #preprocessors = [];
12
+ #validators = [];
13
+ #type = 'base';
14
+ get type() {
15
+ return this.#type;
16
+ }
17
+ set type(value) {
18
+ if (typeof value !== 'string' || !value)
19
+ throw new Error('value should be non empty string');
20
+ this.#type = value;
21
+ }
22
+ /**
23
+ * A list of preprocessors associated with
24
+ * the Builder
25
+ */
26
+ get preprocessors() {
27
+ return this.#preprocessors;
28
+ }
29
+ /**
30
+ * A list of validators associated with
31
+ * the Builder
32
+ */
33
+ get validators() {
34
+ return this.#validators;
35
+ }
36
+ get isRequired() {
37
+ return this.#isRequired;
38
+ }
39
+ set isRequired(value) {
40
+ if (typeof value !== 'boolean')
41
+ throw new Error('should be a boolean value');
42
+ this.#isRequired = value;
43
+ }
44
+ async preValidate(
45
+ /**
46
+ * Object to validate
47
+ */
48
+ object, context) {
49
+ const { doNotStopOnFirstError } = context || {
50
+ doNotStopOnFirstError: false
51
+ };
52
+ let path = '$';
53
+ if (typeof context?.path === 'string' && context.path)
54
+ path = context.path;
55
+ const resultingContext = {
56
+ path,
57
+ doNotStopOnFirstError
58
+ };
59
+ let preprocessingTransaction = transaction({
60
+ validatedObject: object
61
+ });
62
+ let preprocessedObject = preprocessingTransaction.object.validatedObject;
63
+ let errors = [];
64
+ if (Array.isArray(this.preprocessors)) {
65
+ let currentPrepropIndex = 0;
66
+ for (const preprocessor of this.preprocessors) {
67
+ try {
68
+ preprocessingTransaction = transaction({
69
+ validatedObject: await Promise.resolve(preprocessor(preprocessedObject))
70
+ });
71
+ preprocessedObject =
72
+ preprocessingTransaction.object.validatedObject;
73
+ }
74
+ catch (err) {
75
+ errors.push({
76
+ message: `Preprocessor #${currentPrepropIndex}${preprocessor.name ? ` (${preprocessor.name})` : ''} thrown an error: ${err.message}`,
77
+ path: `${path}($preprocessors[${currentPrepropIndex}])`
78
+ });
79
+ if (!doNotStopOnFirstError) {
80
+ preprocessingTransaction.rollback();
81
+ return {
82
+ valid: false,
83
+ errors: [errors[0]].filter((e) => e),
84
+ context: resultingContext
85
+ };
86
+ }
87
+ }
88
+ finally {
89
+ currentPrepropIndex++;
90
+ }
19
91
  }
20
- if (typeof obj.preprocessor !== 'undefined') {
21
- this.preprocessor = obj.preprocessor;
92
+ }
93
+ if (Array.isArray(this.validators)) {
94
+ let currentValidatorIndex = 0;
95
+ for (const validator of this.validators) {
96
+ try {
97
+ const { valid, errors: validatorErrors } = await Promise.resolve(validator(preprocessedObject));
98
+ if (!valid) {
99
+ errors = [
100
+ ...errors,
101
+ ...(Array.isArray(validatorErrors) &&
102
+ validatorErrors.length
103
+ ? validatorErrors.map((err) => ({
104
+ message: err.message,
105
+ path: `${path}($validators[${currentValidatorIndex}])`
106
+ }))
107
+ : [
108
+ {
109
+ message: `Validator #${currentValidatorIndex}${validator.name
110
+ ? ` (${validator.name})`
111
+ : ''} didn't pass.`,
112
+ path: `${path}($validators[${currentValidatorIndex}])`
113
+ }
114
+ ])
115
+ ];
116
+ if (!doNotStopOnFirstError) {
117
+ preprocessingTransaction.rollback();
118
+ return {
119
+ valid: false,
120
+ errors: [errors[0]].filter((e) => e),
121
+ context: resultingContext
122
+ };
123
+ }
124
+ }
125
+ }
126
+ catch (err) {
127
+ errors.push({
128
+ message: `Validator #${currentValidatorIndex}${validator.name ? ` (${validator.name})` : ''} thrown an error: ${err.message}`,
129
+ path: `${path}($validators[${currentValidatorIndex}])`
130
+ });
131
+ if (!doNotStopOnFirstError) {
132
+ preprocessingTransaction.rollback();
133
+ return {
134
+ valid: false,
135
+ errors: [errors[0]].filter((e) => e),
136
+ context: resultingContext
137
+ };
138
+ }
139
+ }
140
+ finally {
141
+ currentValidatorIndex++;
142
+ }
22
143
  }
23
144
  }
24
- }
25
- getCommonSchema() {
26
- const schemas = [];
27
- if (typeof this.preprocessor !== 'undefined') {
28
- schemas.push({
29
- preprocessor: this.preprocessor
145
+ if (this.isRequired &&
146
+ (typeof preprocessedObject === 'undefined' ||
147
+ preprocessedObject === null)) {
148
+ errors.push({
149
+ message: 'is required',
150
+ path
30
151
  });
152
+ if (!doNotStopOnFirstError) {
153
+ preprocessingTransaction.rollback();
154
+ return {
155
+ valid: false,
156
+ errors: [errors[0]],
157
+ context: resultingContext
158
+ };
159
+ }
31
160
  }
32
- if (typeof this.isRequired !== 'undefined') {
33
- schemas.push({ isRequired: this.isRequired });
34
- }
35
- if (typeof this.isNullable !== 'undefined') {
36
- schemas.push({ isNullable: this.isNullable });
37
- }
38
- if (typeof this.validators !== 'undefined') {
39
- schemas.push({ validators: [...this.validators] });
161
+ if (errors.length > 0) {
162
+ return {
163
+ valid: false,
164
+ errors: errors
165
+ .filter((e) => e)
166
+ .filter((e, i) => (doNotStopOnFirstError ? true : i === 0)),
167
+ context: resultingContext,
168
+ transaction: preprocessingTransaction
169
+ };
40
170
  }
41
- return Object.assign({}, ...schemas);
171
+ return {
172
+ valid: true,
173
+ context: resultingContext,
174
+ transaction: preprocessingTransaction
175
+ };
42
176
  }
43
- addPreprocessor(preprocessor) {
44
- const result = this.clone();
45
- result.preprocessor = preprocessor;
46
- return result;
177
+ /**
178
+ * Generates a serializable object describing the defined schema
179
+ */
180
+ introspect() {
181
+ return {
182
+ /**
183
+ * String `id` of schema type, e.g. `string', `number` or `object`.
184
+ */
185
+ type: this.type,
186
+ /**
187
+ * If set to `false`, schema will be optional (`null` or `undefined` values
188
+ * will be considered as valid).
189
+ */
190
+ isRequired: this.#isRequired,
191
+ /**
192
+ * Array of preprocessor functions
193
+ */
194
+ preprocessors: [
195
+ ...this.preprocessors
196
+ ],
197
+ /**
198
+ * Array of validator functions
199
+ */
200
+ validators: [...this.validators]
201
+ };
202
+ }
203
+ /**
204
+ * Makes schema optional (consider `null` and `undefined` as valid objects for this schema)
205
+ */
206
+ optional() {
207
+ return this.createFromProps({
208
+ ...this.introspect(),
209
+ isRequired: false
210
+ });
211
+ }
212
+ /**
213
+ * Makes schema required (consider `null` and `undefined` as invalid objects for this schema)
214
+ */
215
+ required() {
216
+ return this.createFromProps({
217
+ ...this.introspect(),
218
+ isRequired: true
219
+ });
47
220
  }
48
- clearPreprocessor() {
49
- if (typeof this.preprocessor === 'undefined') {
50
- return this;
221
+ /**
222
+ * Adds a `preprocessor` to a preprocessors list
223
+ */
224
+ addPreprocessor(preprocessor) {
225
+ if (typeof preprocessor !== 'function') {
226
+ throw new Error('preprocessor must be a function');
51
227
  }
52
- const result = this.clone();
53
- delete result.preprocessor;
54
- return result;
228
+ return this.createFromProps({
229
+ ...this.introspect(),
230
+ preprocessors: [...this.preprocessors, preprocessor]
231
+ });
232
+ }
233
+ /**
234
+ * Remove all preprocessors for this schema.
235
+ */
236
+ clearPreprocessors() {
237
+ return this.createFromProps({
238
+ ...this.introspect(),
239
+ preprocessors: []
240
+ });
55
241
  }
242
+ /**
243
+ * Adds a `validator` to validators list.
244
+ */
56
245
  addValidator(validator) {
57
- const result = this.clone();
58
- if (typeof validator !== 'function')
59
- throw new Error('validator should be a function');
60
- if (Array.isArray(result.validators)) {
61
- result.validators.push(validator);
62
- }
63
- else {
64
- result.validators = [validator];
246
+ if (typeof validator !== 'function') {
247
+ throw new Error('validator must be a function');
65
248
  }
66
- return result;
249
+ return this.createFromProps({
250
+ ...this.introspect(),
251
+ validators: [...this.validators, validator]
252
+ });
67
253
  }
254
+ /**
255
+ * Remove all validators for this schema.
256
+ */
68
257
  clearValidators() {
69
- if (typeof this.validators === 'undefined')
70
- return this;
71
- const result = this.clone();
72
- delete result.validators;
73
- return result;
258
+ return this.createFromProps({
259
+ ...this.introspect(),
260
+ validators: []
261
+ });
74
262
  }
75
- get isRequired() {
76
- return this._isRequired;
77
- }
78
- set isRequired(val) {
79
- this._isRequired = val;
80
- }
81
- get isNullable() {
82
- return this._isNullable;
83
- }
84
- set isNullable(val) {
85
- this._isNullable = val;
263
+ constructor(props) {
264
+ const { type, preprocessors, validators, isRequired } = props;
265
+ this.type = type;
266
+ if (typeof isRequired === 'boolean')
267
+ this.isRequired = isRequired;
268
+ if (Array.isArray(preprocessors)) {
269
+ this.#preprocessors = [...preprocessors];
270
+ }
271
+ if (Array.isArray(validators)) {
272
+ this.#validators = [...validators];
273
+ }
86
274
  }
87
275
  }
88
- exports.SchemaBuilder = SchemaBuilder;