@naturalcycles/nodejs-lib 15.54.0 → 15.56.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.
- package/dist/validation/ajv/ajvSchema.js +6 -0
- package/dist/validation/ajv/getAjv.js +18 -1
- package/dist/validation/ajv/jsonSchemaBuilder.d.ts +43 -8
- package/dist/validation/ajv/jsonSchemaBuilder.js +64 -24
- package/package.json +1 -1
- package/src/validation/ajv/ajvSchema.ts +7 -0
- package/src/validation/ajv/getAjv.ts +25 -1
- package/src/validation/ajv/jsonSchemaBuilder.ts +118 -45
|
@@ -59,6 +59,12 @@ export class AjvSchema {
|
|
|
59
59
|
else {
|
|
60
60
|
jsonSchema = schema;
|
|
61
61
|
}
|
|
62
|
+
// This is our own helper which marks a schema as optional
|
|
63
|
+
// in case it is going to be used in an object schema,
|
|
64
|
+
// where we need to mark the given property as not-required.
|
|
65
|
+
// But once all compilation is done, the presence of this field
|
|
66
|
+
// really upsets Ajv.
|
|
67
|
+
delete jsonSchema.optionalField;
|
|
62
68
|
const ajvSchema = new AjvSchema(jsonSchema, cfg);
|
|
63
69
|
AjvSchema.cacheAjvSchema(schema, ajvSchema);
|
|
64
70
|
return ajvSchema;
|
|
@@ -91,7 +91,7 @@ export function createAjv(opt) {
|
|
|
91
91
|
});
|
|
92
92
|
ajv.addKeyword({
|
|
93
93
|
keyword: 'instanceof',
|
|
94
|
-
modifying:
|
|
94
|
+
modifying: false,
|
|
95
95
|
schemaType: 'string',
|
|
96
96
|
validate(instanceOf, data, _schema, _ctx) {
|
|
97
97
|
if (typeof data !== 'object')
|
|
@@ -354,6 +354,23 @@ export function createAjv(opt) {
|
|
|
354
354
|
keyword: 'hasIsOfTypeCheck',
|
|
355
355
|
schemaType: 'boolean',
|
|
356
356
|
});
|
|
357
|
+
ajv.addKeyword({
|
|
358
|
+
keyword: 'optionalValues',
|
|
359
|
+
type: ['string', 'number', 'boolean'],
|
|
360
|
+
modifying: true,
|
|
361
|
+
errors: false,
|
|
362
|
+
schemaType: 'array',
|
|
363
|
+
validate: function validate(optionalValues, data, _schema, ctx) {
|
|
364
|
+
if (!optionalValues)
|
|
365
|
+
return true;
|
|
366
|
+
if (!optionalValues.includes(data))
|
|
367
|
+
return true;
|
|
368
|
+
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
369
|
+
ctx.parentData[ctx.parentDataProperty] = undefined;
|
|
370
|
+
}
|
|
371
|
+
return true;
|
|
372
|
+
},
|
|
373
|
+
});
|
|
357
374
|
return ajv;
|
|
358
375
|
}
|
|
359
376
|
const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
@@ -9,16 +9,16 @@ export declare const j: {
|
|
|
9
9
|
infer: typeof objectInfer;
|
|
10
10
|
any(): JsonSchemaObjectBuilder<AnyObject, AnyObject, false>;
|
|
11
11
|
stringMap<S extends JsonSchemaTerminal<any, any, any>>(schema: S): JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>>;
|
|
12
|
-
|
|
13
|
-
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
14
|
-
*/
|
|
15
|
-
withEnumKeys<const T extends readonly (string | number)[] | StringEnum | NumberEnum, S extends JsonSchemaTerminal<any, any, any>, K extends string | number = EnumKeyUnion<T>>(keys: T, schema: S): JsonSchemaObjectBuilder<Record<K, SchemaIn<S>>, Record<K, SchemaOut<S>>>;
|
|
12
|
+
withEnumKeys: typeof withEnumKeys;
|
|
16
13
|
};
|
|
17
14
|
array<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaArrayBuilder<IN, OUT, Opt>;
|
|
18
15
|
set<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaSet2Builder<IN, OUT, Opt>;
|
|
19
16
|
buffer(): JsonSchemaBufferBuilder;
|
|
20
17
|
enum<const T extends readonly (string | number | boolean | null)[] | StringEnum | NumberEnum>(input: T, opt?: JsonBuilderRuleOpt): JsonSchemaEnumBuilder<T extends readonly (infer U)[] ? U : T extends StringEnum ? T[keyof T] : T extends NumberEnum ? T[keyof T] : never>;
|
|
21
18
|
oneOf<B extends readonly JsonSchemaAnyBuilder<any, any, boolean>[], IN = BuilderInUnion<B>, OUT = BuilderOutUnion<B>>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false>;
|
|
19
|
+
and(): {
|
|
20
|
+
silentBob: () => never;
|
|
21
|
+
};
|
|
22
22
|
};
|
|
23
23
|
export declare class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
24
24
|
protected schema: JsonSchema;
|
|
@@ -73,8 +73,15 @@ export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTermin
|
|
|
73
73
|
*/
|
|
74
74
|
final(): JsonSchemaTerminal<IN, OUT, Opt>;
|
|
75
75
|
}
|
|
76
|
-
export declare class JsonSchemaStringBuilder<IN extends string = string, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
76
|
+
export declare class JsonSchemaStringBuilder<IN extends string | undefined = string, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
77
77
|
constructor();
|
|
78
|
+
/**
|
|
79
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
80
|
+
*
|
|
81
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
82
|
+
* due to how mutability works in Ajv.
|
|
83
|
+
*/
|
|
84
|
+
optional(optionalValues?: string[]): JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true>;
|
|
78
85
|
regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this;
|
|
79
86
|
pattern(pattern: string, opt?: JsonBuilderRuleOpt): this;
|
|
80
87
|
minLength(minLength: number): this;
|
|
@@ -134,8 +141,15 @@ export interface JsonSchemaIsoDateOptions {
|
|
|
134
141
|
after?: string;
|
|
135
142
|
sameOrAfter?: string;
|
|
136
143
|
}
|
|
137
|
-
export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
144
|
+
export declare class JsonSchemaNumberBuilder<IN extends number | undefined = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
138
145
|
constructor();
|
|
146
|
+
/**
|
|
147
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
148
|
+
*
|
|
149
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
150
|
+
* due to how mutability works in Ajv.
|
|
151
|
+
*/
|
|
152
|
+
optional(optionalValues?: number[]): JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true>;
|
|
139
153
|
integer(): this;
|
|
140
154
|
branded<B extends number>(): JsonSchemaNumberBuilder<B, B, Opt>;
|
|
141
155
|
multipleOf(multipleOf: number): this;
|
|
@@ -160,8 +174,15 @@ export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = I
|
|
|
160
174
|
utcOffset(): this;
|
|
161
175
|
utcOffsetHour(): this;
|
|
162
176
|
}
|
|
163
|
-
export declare class JsonSchemaBooleanBuilder<IN extends boolean = boolean, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
177
|
+
export declare class JsonSchemaBooleanBuilder<IN extends boolean | undefined = boolean, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
164
178
|
constructor();
|
|
179
|
+
/**
|
|
180
|
+
* @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
|
|
181
|
+
*
|
|
182
|
+
* This `optionalValue` feature only works when the current schema is nested in an object or array schema,
|
|
183
|
+
* due to how mutability works in Ajv.
|
|
184
|
+
*/
|
|
185
|
+
optional(optionalValue?: boolean): JsonSchemaBooleanBuilder<IN | undefined, OUT | undefined, true>;
|
|
165
186
|
}
|
|
166
187
|
export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends AnyObject, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
167
188
|
constructor(props?: AnyObject, opt?: JsonSchemaObjectBuilderOpts);
|
|
@@ -276,6 +297,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
276
297
|
maxItems?: number;
|
|
277
298
|
uniqueItems?: boolean;
|
|
278
299
|
enum?: any;
|
|
300
|
+
hasIsOfTypeCheck?: boolean;
|
|
279
301
|
email?: JsonSchemaStringEmailOptions;
|
|
280
302
|
Set2?: JsonSchema;
|
|
281
303
|
Buffer?: true;
|
|
@@ -289,7 +311,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
289
311
|
truncate?: number;
|
|
290
312
|
};
|
|
291
313
|
errorMessages?: StringMap<string>;
|
|
292
|
-
|
|
314
|
+
optionalValues?: (string | number | boolean)[];
|
|
293
315
|
}
|
|
294
316
|
declare function object(props: AnyObject): never;
|
|
295
317
|
declare function object<IN extends AnyObject>(props: {
|
|
@@ -312,6 +334,18 @@ declare function objectDbEntity<IN extends BaseDBEntity & AnyObject, EXTRA_KEYS
|
|
|
312
334
|
} : {
|
|
313
335
|
updated: BuilderFor<IN['updated']>;
|
|
314
336
|
})): JsonSchemaObjectBuilder<IN, IN, false>;
|
|
337
|
+
/**
|
|
338
|
+
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
339
|
+
*/
|
|
340
|
+
declare function withEnumKeys<const T extends readonly (string | number)[] | StringEnum | NumberEnum, S extends JsonSchemaAnyBuilder<any, any, any>, K extends string | number = EnumKeyUnion<T>, Opt extends boolean = SchemaOpt<S>>(keys: T, schema: S): JsonSchemaObjectBuilder<Opt extends true ? {
|
|
341
|
+
[P in K]?: SchemaIn<S>;
|
|
342
|
+
} : {
|
|
343
|
+
[P in K]: SchemaIn<S>;
|
|
344
|
+
}, Opt extends true ? {
|
|
345
|
+
[P in K]?: SchemaOut<S>;
|
|
346
|
+
} : {
|
|
347
|
+
[P in K]: SchemaOut<S>;
|
|
348
|
+
}, false>;
|
|
315
349
|
type Expand<T> = T extends infer O ? {
|
|
316
350
|
[K in keyof O]: O[K];
|
|
317
351
|
} : never;
|
|
@@ -340,4 +374,5 @@ interface JsonBuilderRuleOpt {
|
|
|
340
374
|
type EnumKeyUnion<T> = T extends readonly (infer U)[] ? U : T extends StringEnum | NumberEnum ? T[keyof T] : never;
|
|
341
375
|
type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
342
376
|
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
|
|
377
|
+
type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? Opt : false;
|
|
343
378
|
export {};
|
|
@@ -35,30 +35,7 @@ export const j = {
|
|
|
35
35
|
},
|
|
36
36
|
});
|
|
37
37
|
},
|
|
38
|
-
|
|
39
|
-
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
40
|
-
*/
|
|
41
|
-
withEnumKeys(keys, schema) {
|
|
42
|
-
let enumValues;
|
|
43
|
-
if (Array.isArray(keys)) {
|
|
44
|
-
_assert(isEveryItemPrimitive(keys), 'Every item in the key list should be string, number or symbol');
|
|
45
|
-
enumValues = keys;
|
|
46
|
-
}
|
|
47
|
-
else if (typeof keys === 'object') {
|
|
48
|
-
const enumType = getEnumType(keys);
|
|
49
|
-
_assert(enumType === 'NumberEnum' || enumType === 'StringEnum', 'The key list should be StringEnum or NumberEnum');
|
|
50
|
-
if (enumType === 'NumberEnum') {
|
|
51
|
-
enumValues = _numberEnumValues(keys);
|
|
52
|
-
}
|
|
53
|
-
else if (enumType === 'StringEnum') {
|
|
54
|
-
enumValues = _stringEnumValues(keys);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
_assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum');
|
|
58
|
-
const typedValues = enumValues;
|
|
59
|
-
const props = Object.fromEntries(typedValues.map(key => [key, schema]));
|
|
60
|
-
return new JsonSchemaObjectBuilder(props, { hasIsOfTypeCheck: false });
|
|
61
|
-
},
|
|
38
|
+
withEnumKeys,
|
|
62
39
|
}),
|
|
63
40
|
array(itemSchema) {
|
|
64
41
|
return new JsonSchemaArrayBuilder(itemSchema);
|
|
@@ -101,6 +78,13 @@ export const j = {
|
|
|
101
78
|
oneOf: schemas,
|
|
102
79
|
});
|
|
103
80
|
},
|
|
81
|
+
and() {
|
|
82
|
+
return {
|
|
83
|
+
silentBob: () => {
|
|
84
|
+
throw new Error('...strike back!');
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
104
88
|
};
|
|
105
89
|
const TS_2500 = 16725225600; // 2500-01-01
|
|
106
90
|
const TS_2500_MILLIS = TS_2500 * 1000;
|
|
@@ -227,6 +211,16 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
227
211
|
type: 'string',
|
|
228
212
|
});
|
|
229
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
216
|
+
*
|
|
217
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
218
|
+
* due to how mutability works in Ajv.
|
|
219
|
+
*/
|
|
220
|
+
optional(optionalValues) {
|
|
221
|
+
_objectAssign(this.schema, { optionalValues });
|
|
222
|
+
return super.optional();
|
|
223
|
+
}
|
|
230
224
|
regex(pattern, opt) {
|
|
231
225
|
return this.pattern(pattern.source, opt);
|
|
232
226
|
}
|
|
@@ -379,6 +373,16 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
|
|
|
379
373
|
type: 'number',
|
|
380
374
|
});
|
|
381
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
378
|
+
*
|
|
379
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
380
|
+
* due to how mutability works in Ajv.
|
|
381
|
+
*/
|
|
382
|
+
optional(optionalValues) {
|
|
383
|
+
_objectAssign(this.schema, { optionalValues });
|
|
384
|
+
return super.optional();
|
|
385
|
+
}
|
|
382
386
|
integer() {
|
|
383
387
|
_objectAssign(this.schema, { type: 'integer' });
|
|
384
388
|
return this;
|
|
@@ -477,6 +481,18 @@ export class JsonSchemaBooleanBuilder extends JsonSchemaAnyBuilder {
|
|
|
477
481
|
type: 'boolean',
|
|
478
482
|
});
|
|
479
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
|
|
486
|
+
*
|
|
487
|
+
* This `optionalValue` feature only works when the current schema is nested in an object or array schema,
|
|
488
|
+
* due to how mutability works in Ajv.
|
|
489
|
+
*/
|
|
490
|
+
optional(optionalValue) {
|
|
491
|
+
if (typeof optionalValue !== 'undefined') {
|
|
492
|
+
_objectAssign(this.schema, { optionalValues: [optionalValue] });
|
|
493
|
+
}
|
|
494
|
+
return super.optional();
|
|
495
|
+
}
|
|
480
496
|
}
|
|
481
497
|
export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
482
498
|
constructor(props, opt) {
|
|
@@ -678,3 +694,27 @@ function objectDbEntity(props) {
|
|
|
678
694
|
...props,
|
|
679
695
|
});
|
|
680
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
699
|
+
*/
|
|
700
|
+
function withEnumKeys(keys, schema) {
|
|
701
|
+
let enumValues;
|
|
702
|
+
if (Array.isArray(keys)) {
|
|
703
|
+
_assert(isEveryItemPrimitive(keys), 'Every item in the key list should be string, number or symbol');
|
|
704
|
+
enumValues = keys;
|
|
705
|
+
}
|
|
706
|
+
else if (typeof keys === 'object') {
|
|
707
|
+
const enumType = getEnumType(keys);
|
|
708
|
+
_assert(enumType === 'NumberEnum' || enumType === 'StringEnum', 'The key list should be StringEnum or NumberEnum');
|
|
709
|
+
if (enumType === 'NumberEnum') {
|
|
710
|
+
enumValues = _numberEnumValues(keys);
|
|
711
|
+
}
|
|
712
|
+
else if (enumType === 'StringEnum') {
|
|
713
|
+
enumValues = _stringEnumValues(keys);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
_assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum');
|
|
717
|
+
const typedValues = enumValues;
|
|
718
|
+
const props = Object.fromEntries(typedValues.map(key => [key, schema]));
|
|
719
|
+
return new JsonSchemaObjectBuilder(props, { hasIsOfTypeCheck: false });
|
|
720
|
+
}
|
package/package.json
CHANGED
|
@@ -79,6 +79,13 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
79
79
|
jsonSchema = schema
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// This is our own helper which marks a schema as optional
|
|
83
|
+
// in case it is going to be used in an object schema,
|
|
84
|
+
// where we need to mark the given property as not-required.
|
|
85
|
+
// But once all compilation is done, the presence of this field
|
|
86
|
+
// really upsets Ajv.
|
|
87
|
+
delete jsonSchema.optionalField
|
|
88
|
+
|
|
82
89
|
const ajvSchema = new AjvSchema<IN, OUT>(jsonSchema, cfg)
|
|
83
90
|
AjvSchema.cacheAjvSchema(schema, ajvSchema)
|
|
84
91
|
|
|
@@ -109,7 +109,7 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
109
109
|
|
|
110
110
|
ajv.addKeyword({
|
|
111
111
|
keyword: 'instanceof',
|
|
112
|
-
modifying:
|
|
112
|
+
modifying: false,
|
|
113
113
|
schemaType: 'string',
|
|
114
114
|
validate(instanceOf: string, data: unknown, _schema, _ctx) {
|
|
115
115
|
if (typeof data !== 'object') return false
|
|
@@ -396,6 +396,30 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
396
396
|
schemaType: 'boolean',
|
|
397
397
|
})
|
|
398
398
|
|
|
399
|
+
ajv.addKeyword({
|
|
400
|
+
keyword: 'optionalValues',
|
|
401
|
+
type: ['string', 'number', 'boolean'],
|
|
402
|
+
modifying: true,
|
|
403
|
+
errors: false,
|
|
404
|
+
schemaType: 'array',
|
|
405
|
+
validate: function validate(
|
|
406
|
+
optionalValues: (string | number | boolean)[],
|
|
407
|
+
data: string | number | boolean,
|
|
408
|
+
_schema,
|
|
409
|
+
ctx,
|
|
410
|
+
) {
|
|
411
|
+
if (!optionalValues) return true
|
|
412
|
+
|
|
413
|
+
if (!optionalValues.includes(data)) return true
|
|
414
|
+
|
|
415
|
+
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
416
|
+
ctx.parentData[ctx.parentDataProperty] = undefined
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return true
|
|
420
|
+
},
|
|
421
|
+
})
|
|
422
|
+
|
|
399
423
|
return ajv
|
|
400
424
|
}
|
|
401
425
|
|
|
@@ -87,47 +87,7 @@ export const j = {
|
|
|
87
87
|
)
|
|
88
88
|
},
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
92
|
-
*/
|
|
93
|
-
withEnumKeys<
|
|
94
|
-
const T extends readonly (string | number)[] | StringEnum | NumberEnum,
|
|
95
|
-
S extends JsonSchemaTerminal<any, any, any>,
|
|
96
|
-
K extends string | number = EnumKeyUnion<T>,
|
|
97
|
-
>(
|
|
98
|
-
keys: T,
|
|
99
|
-
schema: S,
|
|
100
|
-
): JsonSchemaObjectBuilder<Record<K, SchemaIn<S>>, Record<K, SchemaOut<S>>> {
|
|
101
|
-
let enumValues: readonly (string | number)[] | undefined
|
|
102
|
-
if (Array.isArray(keys)) {
|
|
103
|
-
_assert(
|
|
104
|
-
isEveryItemPrimitive(keys),
|
|
105
|
-
'Every item in the key list should be string, number or symbol',
|
|
106
|
-
)
|
|
107
|
-
enumValues = keys
|
|
108
|
-
} else if (typeof keys === 'object') {
|
|
109
|
-
const enumType = getEnumType(keys)
|
|
110
|
-
_assert(
|
|
111
|
-
enumType === 'NumberEnum' || enumType === 'StringEnum',
|
|
112
|
-
'The key list should be StringEnum or NumberEnum',
|
|
113
|
-
)
|
|
114
|
-
if (enumType === 'NumberEnum') {
|
|
115
|
-
enumValues = _numberEnumValues(keys as NumberEnum)
|
|
116
|
-
} else if (enumType === 'StringEnum') {
|
|
117
|
-
enumValues = _stringEnumValues(keys as StringEnum)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
_assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum')
|
|
122
|
-
|
|
123
|
-
const typedValues = enumValues as readonly K[]
|
|
124
|
-
const props = Object.fromEntries(typedValues.map(key => [key, schema])) as any
|
|
125
|
-
|
|
126
|
-
return new JsonSchemaObjectBuilder<Record<K, SchemaIn<S>>, Record<K, SchemaOut<S>>, false>(
|
|
127
|
-
props,
|
|
128
|
-
{ hasIsOfTypeCheck: false },
|
|
129
|
-
)
|
|
130
|
-
},
|
|
90
|
+
withEnumKeys,
|
|
131
91
|
}),
|
|
132
92
|
|
|
133
93
|
array<IN, OUT, Opt>(
|
|
@@ -193,6 +153,14 @@ export const j = {
|
|
|
193
153
|
oneOf: schemas,
|
|
194
154
|
})
|
|
195
155
|
},
|
|
156
|
+
|
|
157
|
+
and() {
|
|
158
|
+
return {
|
|
159
|
+
silentBob: () => {
|
|
160
|
+
throw new Error('...strike back!')
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
},
|
|
196
164
|
}
|
|
197
165
|
|
|
198
166
|
const TS_2500 = 16725225600 // 2500-01-01
|
|
@@ -338,7 +306,7 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
338
306
|
}
|
|
339
307
|
|
|
340
308
|
export class JsonSchemaStringBuilder<
|
|
341
|
-
IN extends string = string,
|
|
309
|
+
IN extends string | undefined = string,
|
|
342
310
|
OUT = IN,
|
|
343
311
|
Opt extends boolean = false,
|
|
344
312
|
> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
@@ -348,6 +316,23 @@ export class JsonSchemaStringBuilder<
|
|
|
348
316
|
})
|
|
349
317
|
}
|
|
350
318
|
|
|
319
|
+
/**
|
|
320
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
321
|
+
*
|
|
322
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
323
|
+
* due to how mutability works in Ajv.
|
|
324
|
+
*/
|
|
325
|
+
override optional(
|
|
326
|
+
optionalValues?: string[],
|
|
327
|
+
): JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true> {
|
|
328
|
+
_objectAssign(this.schema, { optionalValues })
|
|
329
|
+
return super.optional() as unknown as JsonSchemaStringBuilder<
|
|
330
|
+
IN | undefined,
|
|
331
|
+
OUT | undefined,
|
|
332
|
+
true
|
|
333
|
+
>
|
|
334
|
+
}
|
|
335
|
+
|
|
351
336
|
regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this {
|
|
352
337
|
return this.pattern(pattern.source, opt)
|
|
353
338
|
}
|
|
@@ -540,7 +525,7 @@ export interface JsonSchemaIsoDateOptions {
|
|
|
540
525
|
}
|
|
541
526
|
|
|
542
527
|
export class JsonSchemaNumberBuilder<
|
|
543
|
-
IN extends number = number,
|
|
528
|
+
IN extends number | undefined = number,
|
|
544
529
|
OUT = IN,
|
|
545
530
|
Opt extends boolean = false,
|
|
546
531
|
> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
@@ -550,6 +535,23 @@ export class JsonSchemaNumberBuilder<
|
|
|
550
535
|
})
|
|
551
536
|
}
|
|
552
537
|
|
|
538
|
+
/**
|
|
539
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
540
|
+
*
|
|
541
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
542
|
+
* due to how mutability works in Ajv.
|
|
543
|
+
*/
|
|
544
|
+
override optional(
|
|
545
|
+
optionalValues?: number[],
|
|
546
|
+
): JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true> {
|
|
547
|
+
_objectAssign(this.schema, { optionalValues })
|
|
548
|
+
return super.optional() as unknown as JsonSchemaNumberBuilder<
|
|
549
|
+
IN | undefined,
|
|
550
|
+
OUT | undefined,
|
|
551
|
+
true
|
|
552
|
+
>
|
|
553
|
+
}
|
|
554
|
+
|
|
553
555
|
integer(): this {
|
|
554
556
|
_objectAssign(this.schema, { type: 'integer' })
|
|
555
557
|
return this
|
|
@@ -670,7 +672,7 @@ export class JsonSchemaNumberBuilder<
|
|
|
670
672
|
}
|
|
671
673
|
|
|
672
674
|
export class JsonSchemaBooleanBuilder<
|
|
673
|
-
IN extends boolean = boolean,
|
|
675
|
+
IN extends boolean | undefined = boolean,
|
|
674
676
|
OUT = IN,
|
|
675
677
|
Opt extends boolean = false,
|
|
676
678
|
> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
@@ -679,6 +681,26 @@ export class JsonSchemaBooleanBuilder<
|
|
|
679
681
|
type: 'boolean',
|
|
680
682
|
})
|
|
681
683
|
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
|
|
687
|
+
*
|
|
688
|
+
* This `optionalValue` feature only works when the current schema is nested in an object or array schema,
|
|
689
|
+
* due to how mutability works in Ajv.
|
|
690
|
+
*/
|
|
691
|
+
override optional(
|
|
692
|
+
optionalValue?: boolean,
|
|
693
|
+
): JsonSchemaBooleanBuilder<IN | undefined, OUT | undefined, true> {
|
|
694
|
+
if (typeof optionalValue !== 'undefined') {
|
|
695
|
+
_objectAssign(this.schema, { optionalValues: [optionalValue] })
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return super.optional() as unknown as JsonSchemaBooleanBuilder<
|
|
699
|
+
IN | undefined,
|
|
700
|
+
OUT | undefined,
|
|
701
|
+
true
|
|
702
|
+
>
|
|
703
|
+
}
|
|
682
704
|
}
|
|
683
705
|
|
|
684
706
|
export class JsonSchemaObjectBuilder<
|
|
@@ -1036,6 +1058,8 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1036
1058
|
|
|
1037
1059
|
enum?: any
|
|
1038
1060
|
|
|
1061
|
+
hasIsOfTypeCheck?: boolean
|
|
1062
|
+
|
|
1039
1063
|
// Below we add custom Ajv keywords
|
|
1040
1064
|
|
|
1041
1065
|
email?: JsonSchemaStringEmailOptions
|
|
@@ -1046,7 +1070,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1046
1070
|
instanceof?: string | string[]
|
|
1047
1071
|
transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
|
|
1048
1072
|
errorMessages?: StringMap<string>
|
|
1049
|
-
|
|
1073
|
+
optionalValues?: (string | number | boolean)[]
|
|
1050
1074
|
}
|
|
1051
1075
|
|
|
1052
1076
|
function object(props: AnyObject): never
|
|
@@ -1096,6 +1120,54 @@ function objectDbEntity(props: AnyObject): any {
|
|
|
1096
1120
|
})
|
|
1097
1121
|
}
|
|
1098
1122
|
|
|
1123
|
+
/**
|
|
1124
|
+
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
1125
|
+
*/
|
|
1126
|
+
function withEnumKeys<
|
|
1127
|
+
const T extends readonly (string | number)[] | StringEnum | NumberEnum,
|
|
1128
|
+
S extends JsonSchemaAnyBuilder<any, any, any>,
|
|
1129
|
+
K extends string | number = EnumKeyUnion<T>,
|
|
1130
|
+
Opt extends boolean = SchemaOpt<S>,
|
|
1131
|
+
>(
|
|
1132
|
+
keys: T,
|
|
1133
|
+
schema: S,
|
|
1134
|
+
): JsonSchemaObjectBuilder<
|
|
1135
|
+
Opt extends true ? { [P in K]?: SchemaIn<S> } : { [P in K]: SchemaIn<S> },
|
|
1136
|
+
Opt extends true ? { [P in K]?: SchemaOut<S> } : { [P in K]: SchemaOut<S> },
|
|
1137
|
+
false
|
|
1138
|
+
> {
|
|
1139
|
+
let enumValues: readonly (string | number)[] | undefined
|
|
1140
|
+
if (Array.isArray(keys)) {
|
|
1141
|
+
_assert(
|
|
1142
|
+
isEveryItemPrimitive(keys),
|
|
1143
|
+
'Every item in the key list should be string, number or symbol',
|
|
1144
|
+
)
|
|
1145
|
+
enumValues = keys
|
|
1146
|
+
} else if (typeof keys === 'object') {
|
|
1147
|
+
const enumType = getEnumType(keys)
|
|
1148
|
+
_assert(
|
|
1149
|
+
enumType === 'NumberEnum' || enumType === 'StringEnum',
|
|
1150
|
+
'The key list should be StringEnum or NumberEnum',
|
|
1151
|
+
)
|
|
1152
|
+
if (enumType === 'NumberEnum') {
|
|
1153
|
+
enumValues = _numberEnumValues(keys as NumberEnum)
|
|
1154
|
+
} else if (enumType === 'StringEnum') {
|
|
1155
|
+
enumValues = _stringEnumValues(keys as StringEnum)
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
_assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum')
|
|
1160
|
+
|
|
1161
|
+
const typedValues = enumValues as readonly K[]
|
|
1162
|
+
const props = Object.fromEntries(typedValues.map(key => [key, schema])) as any
|
|
1163
|
+
|
|
1164
|
+
return new JsonSchemaObjectBuilder<
|
|
1165
|
+
Opt extends true ? { [P in K]?: SchemaIn<S> } : { [P in K]: SchemaIn<S> },
|
|
1166
|
+
Opt extends true ? { [P in K]?: SchemaOut<S> } : { [P in K]: SchemaOut<S> },
|
|
1167
|
+
false
|
|
1168
|
+
>(props, { hasIsOfTypeCheck: false })
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1099
1171
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
|
|
1100
1172
|
|
|
1101
1173
|
type ExactMatch<A, B> =
|
|
@@ -1137,3 +1209,4 @@ type EnumKeyUnion<T> =
|
|
|
1137
1209
|
|
|
1138
1210
|
type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1139
1211
|
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1212
|
+
type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? Opt : false
|