@naturalcycles/nodejs-lib 15.87.0 → 15.89.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.
|
@@ -389,7 +389,7 @@ export function createAjv(opt) {
|
|
|
389
389
|
validate: function validate(optionalValues, data, _schema, ctx) {
|
|
390
390
|
if (!optionalValues)
|
|
391
391
|
return true;
|
|
392
|
-
_assert(ctx?.parentData && ctx.parentDataProperty !== undefined, 'You should only use `optional([x, y, z]) on a property of an object, or on an element of an array due to Ajv mutation issues.');
|
|
392
|
+
_assert(ctx?.parentData && ctx.parentDataProperty !== undefined, 'You should only use `optional([x, y, z])` on a property of an object, or on an element of an array due to Ajv mutation issues.');
|
|
393
393
|
if (!optionalValues.includes(data))
|
|
394
394
|
return true;
|
|
395
395
|
ctx.parentData[ctx.parentDataProperty] = undefined;
|
|
@@ -578,11 +578,51 @@ export function createAjv(opt) {
|
|
|
578
578
|
validate: function validate(numberOfDigits, data, _schema, ctx) {
|
|
579
579
|
if (!numberOfDigits)
|
|
580
580
|
return true;
|
|
581
|
-
_assert(ctx?.parentData && ctx.parentDataProperty !== undefined, 'You should only use `precision(n) on a property of an object, or on an element of an array due to Ajv mutation issues.');
|
|
581
|
+
_assert(ctx?.parentData && ctx.parentDataProperty !== undefined, 'You should only use `precision(n)` on a property of an object, or on an element of an array due to Ajv mutation issues.');
|
|
582
582
|
ctx.parentData[ctx.parentDataProperty] = _round(data, 10 ** (-1 * numberOfDigits));
|
|
583
583
|
return true;
|
|
584
584
|
},
|
|
585
585
|
});
|
|
586
|
+
ajv.addKeyword({
|
|
587
|
+
keyword: 'customValidations',
|
|
588
|
+
modifying: false,
|
|
589
|
+
errors: true,
|
|
590
|
+
schemaType: 'array',
|
|
591
|
+
validate: function validate(customValidations, data, _schema, ctx) {
|
|
592
|
+
if (!customValidations?.length)
|
|
593
|
+
return true;
|
|
594
|
+
for (const validator of customValidations) {
|
|
595
|
+
const error = validator(data);
|
|
596
|
+
if (error) {
|
|
597
|
+
;
|
|
598
|
+
validate.errors = [
|
|
599
|
+
{
|
|
600
|
+
instancePath: ctx?.instancePath ?? '',
|
|
601
|
+
message: error,
|
|
602
|
+
},
|
|
603
|
+
];
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return true;
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
ajv.addKeyword({
|
|
611
|
+
keyword: 'customConversions',
|
|
612
|
+
modifying: true,
|
|
613
|
+
errors: false,
|
|
614
|
+
schemaType: 'array',
|
|
615
|
+
validate: function validate(customConversions, data, _schema, ctx) {
|
|
616
|
+
if (!customConversions?.length)
|
|
617
|
+
return true;
|
|
618
|
+
_assert(ctx?.parentData && ctx.parentDataProperty !== undefined, 'You should only use `convert()` on a property of an object, or on an element of an array due to Ajv mutation issues.');
|
|
619
|
+
for (const converter of customConversions) {
|
|
620
|
+
data = converter(data);
|
|
621
|
+
}
|
|
622
|
+
ctx.parentData[ctx.parentDataProperty] = data;
|
|
623
|
+
return true;
|
|
624
|
+
},
|
|
625
|
+
});
|
|
586
626
|
return ajv;
|
|
587
627
|
}
|
|
588
628
|
const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
@@ -161,6 +161,24 @@ export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTermin
|
|
|
161
161
|
* Locks the given schema chain and no other modification can be done to it.
|
|
162
162
|
*/
|
|
163
163
|
final(): JsonSchemaTerminal<IN, OUT, Opt>;
|
|
164
|
+
/**
|
|
165
|
+
*
|
|
166
|
+
* @param validator A validator function that returns an error message or undefined.
|
|
167
|
+
*
|
|
168
|
+
* You may add multiple custom validators and they will be executed in the order you added them.
|
|
169
|
+
*/
|
|
170
|
+
custom<OUT2 = OUT>(validator: CustomValidatorFn): JsonSchemaAnyBuilder<IN, OUT2, Opt>;
|
|
171
|
+
/**
|
|
172
|
+
*
|
|
173
|
+
* @param converter A converter function that returns a new value.
|
|
174
|
+
*
|
|
175
|
+
* You may add multiple converters and they will be executed in the order you added them,
|
|
176
|
+
* each converter receiving the result from the previous one.
|
|
177
|
+
*
|
|
178
|
+
* This feature only works when the current schema is nested in an object or array schema,
|
|
179
|
+
* due to how mutability works in Ajv.
|
|
180
|
+
*/
|
|
181
|
+
convert<OUT2>(converter: CustomConverterFn<OUT2>): JsonSchemaAnyBuilder<IN, OUT2, Opt>;
|
|
164
182
|
}
|
|
165
183
|
export declare class JsonSchemaStringBuilder<IN extends string | undefined = string, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
166
184
|
constructor();
|
|
@@ -225,6 +243,18 @@ export interface JsonSchemaStringEmailOptions {
|
|
|
225
243
|
}
|
|
226
244
|
export declare class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<string | IsoDate, IsoDate, Opt> {
|
|
227
245
|
constructor();
|
|
246
|
+
/**
|
|
247
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
248
|
+
*
|
|
249
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
250
|
+
* due to how mutability works in Ajv.
|
|
251
|
+
*
|
|
252
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
253
|
+
*
|
|
254
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
255
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
256
|
+
*/
|
|
257
|
+
optional<T extends readonly null[] | undefined = undefined>(optionalValues?: T): T extends readonly null[] ? JsonSchemaTerminal<string | IsoDate | undefined, IsoDate | undefined, true> : JsonSchemaIsoDateBuilder<true>;
|
|
228
258
|
before(date: string): this;
|
|
229
259
|
sameOrBefore(date: string): this;
|
|
230
260
|
after(date: string): this;
|
|
@@ -520,6 +550,8 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
520
550
|
};
|
|
521
551
|
anyOfThese?: JsonSchema[];
|
|
522
552
|
precision?: number;
|
|
553
|
+
customValidations?: CustomValidatorFn[];
|
|
554
|
+
customConversions?: CustomConverterFn<any>[];
|
|
523
555
|
}
|
|
524
556
|
declare function object(props: AnyObject): never;
|
|
525
557
|
declare function object<IN extends AnyObject>(props: {
|
|
@@ -618,4 +650,6 @@ type TupleIn<T extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
|
618
650
|
type TupleOut<T extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
619
651
|
[K in keyof T]: T[K] extends JsonSchemaAnyBuilder<any, infer O, any> ? O : never;
|
|
620
652
|
};
|
|
653
|
+
export type CustomValidatorFn = (v: any) => string | undefined;
|
|
654
|
+
export type CustomConverterFn<OUT> = (v: any) => OUT;
|
|
621
655
|
export {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { _isUndefined, _numberEnumValues, _stringEnumValues, getEnumType, } from '@naturalcycles/js-lib';
|
|
4
4
|
import { _uniq } from '@naturalcycles/js-lib/array';
|
|
5
5
|
import { _assert } from '@naturalcycles/js-lib/error';
|
|
6
|
-
import {
|
|
6
|
+
import { _sortObject } from '@naturalcycles/js-lib/object';
|
|
7
7
|
import { _objectAssign, _typeCast, JWT_REGEX, } from '@naturalcycles/js-lib/types';
|
|
8
8
|
import { BASE64URL_REGEX, COUNTRY_CODE_REGEX, CURRENCY_REGEX, IPV4_REGEX, IPV6_REGEX, LANGUAGE_TAG_REGEX, SEMVER_REGEX, SLUG_REGEX, URL_REGEX, UUID_REGEX, } from '../regexes.js';
|
|
9
9
|
import { TIMEZONES } from '../timezones.js';
|
|
@@ -223,13 +223,13 @@ export class JsonSchemaTerminal {
|
|
|
223
223
|
*/
|
|
224
224
|
build() {
|
|
225
225
|
_assert(!(this.schema.optionalField && this.schema.default !== undefined), '.optional() and .default() should not be used together - the default value makes .optional() redundant and causes incorrect type inference');
|
|
226
|
-
const jsonSchema = _sortObject(
|
|
226
|
+
const jsonSchema = _sortObject(deepCopyPreservingFunctions(this.schema), JSON_SCHEMA_ORDER);
|
|
227
227
|
delete jsonSchema.optionalField;
|
|
228
228
|
return jsonSchema;
|
|
229
229
|
}
|
|
230
230
|
clone() {
|
|
231
231
|
const cloned = Object.create(Object.getPrototypeOf(this));
|
|
232
|
-
cloned.schema =
|
|
232
|
+
cloned.schema = deepCopyPreservingFunctions(this.schema);
|
|
233
233
|
return cloned;
|
|
234
234
|
}
|
|
235
235
|
cloneAndUpdateSchema(schema) {
|
|
@@ -318,6 +318,34 @@ export class JsonSchemaAnyBuilder extends JsonSchemaTerminal {
|
|
|
318
318
|
final() {
|
|
319
319
|
return new JsonSchemaTerminal(this.schema);
|
|
320
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
*
|
|
323
|
+
* @param validator A validator function that returns an error message or undefined.
|
|
324
|
+
*
|
|
325
|
+
* You may add multiple custom validators and they will be executed in the order you added them.
|
|
326
|
+
*/
|
|
327
|
+
custom(validator) {
|
|
328
|
+
const { customValidations = [] } = this.schema;
|
|
329
|
+
return this.cloneAndUpdateSchema({
|
|
330
|
+
customValidations: [...customValidations, validator],
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
*
|
|
335
|
+
* @param converter A converter function that returns a new value.
|
|
336
|
+
*
|
|
337
|
+
* You may add multiple converters and they will be executed in the order you added them,
|
|
338
|
+
* each converter receiving the result from the previous one.
|
|
339
|
+
*
|
|
340
|
+
* This feature only works when the current schema is nested in an object or array schema,
|
|
341
|
+
* due to how mutability works in Ajv.
|
|
342
|
+
*/
|
|
343
|
+
convert(converter) {
|
|
344
|
+
const { customConversions = [] } = this.schema;
|
|
345
|
+
return this.cloneAndUpdateSchema({
|
|
346
|
+
customConversions: [...customConversions, converter],
|
|
347
|
+
});
|
|
348
|
+
}
|
|
321
349
|
}
|
|
322
350
|
export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
323
351
|
constructor() {
|
|
@@ -480,6 +508,31 @@ export class JsonSchemaIsoDateBuilder extends JsonSchemaAnyBuilder {
|
|
|
480
508
|
IsoDate: {},
|
|
481
509
|
});
|
|
482
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
513
|
+
*
|
|
514
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
515
|
+
* due to how mutability works in Ajv.
|
|
516
|
+
*
|
|
517
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
518
|
+
*
|
|
519
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
520
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
521
|
+
*/
|
|
522
|
+
optional(optionalValues) {
|
|
523
|
+
if (!optionalValues) {
|
|
524
|
+
return super.optional();
|
|
525
|
+
}
|
|
526
|
+
_typeCast(optionalValues);
|
|
527
|
+
const newBuilder = new JsonSchemaTerminal({
|
|
528
|
+
anyOf: [
|
|
529
|
+
{ type: 'null', optionalValues },
|
|
530
|
+
this.cloneAndUpdateSchema({ optionalField: true }).build(),
|
|
531
|
+
],
|
|
532
|
+
optionalField: true,
|
|
533
|
+
});
|
|
534
|
+
return newBuilder;
|
|
535
|
+
}
|
|
483
536
|
before(date) {
|
|
484
537
|
return this.cloneAndUpdateSchema({ IsoDate: { before: date } });
|
|
485
538
|
}
|
|
@@ -729,7 +782,7 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
729
782
|
}
|
|
730
783
|
extend(props) {
|
|
731
784
|
const newBuilder = new JsonSchemaObjectBuilder();
|
|
732
|
-
_objectAssign(newBuilder.schema,
|
|
785
|
+
_objectAssign(newBuilder.schema, deepCopyPreservingFunctions(this.schema));
|
|
733
786
|
const incomingSchemaBuilder = new JsonSchemaObjectBuilder(props);
|
|
734
787
|
mergeJsonSchemaObjects(newBuilder.schema, incomingSchemaBuilder.schema);
|
|
735
788
|
_objectAssign(newBuilder.schema, { hasIsOfTypeCheck: false });
|
|
@@ -838,7 +891,7 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
|
838
891
|
}
|
|
839
892
|
extend(props) {
|
|
840
893
|
const newBuilder = new JsonSchemaObjectInferringBuilder();
|
|
841
|
-
_objectAssign(newBuilder.schema,
|
|
894
|
+
_objectAssign(newBuilder.schema, deepCopyPreservingFunctions(this.schema));
|
|
842
895
|
const incomingSchemaBuilder = new JsonSchemaObjectInferringBuilder(props);
|
|
843
896
|
mergeJsonSchemaObjects(newBuilder.schema, incomingSchemaBuilder.schema);
|
|
844
897
|
// This extend function is not type-safe as it is inferring,
|
|
@@ -1057,3 +1110,22 @@ function hasNoObjectSchemas(schema) {
|
|
|
1057
1110
|
}
|
|
1058
1111
|
return false;
|
|
1059
1112
|
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Deep copy that preserves functions in customValidations/customConversions.
|
|
1115
|
+
* Unlike structuredClone, this handles function references (which only exist in those two properties).
|
|
1116
|
+
*/
|
|
1117
|
+
function deepCopyPreservingFunctions(obj) {
|
|
1118
|
+
if (obj === null || typeof obj !== 'object')
|
|
1119
|
+
return obj;
|
|
1120
|
+
if (Array.isArray(obj))
|
|
1121
|
+
return obj.map(deepCopyPreservingFunctions);
|
|
1122
|
+
const copy = {};
|
|
1123
|
+
for (const key of Object.keys(obj)) {
|
|
1124
|
+
const value = obj[key];
|
|
1125
|
+
copy[key] =
|
|
1126
|
+
(key === 'customValidations' || key === 'customConversions') && Array.isArray(value)
|
|
1127
|
+
? [...value]
|
|
1128
|
+
: deepCopyPreservingFunctions(value);
|
|
1129
|
+
}
|
|
1130
|
+
return copy;
|
|
1131
|
+
}
|
package/package.json
CHANGED
|
@@ -6,6 +6,8 @@ import type { AnyObject } from '@naturalcycles/js-lib/types'
|
|
|
6
6
|
import { Ajv2020, type Options, type ValidateFunction } from 'ajv/dist/2020.js'
|
|
7
7
|
import { validTLDs } from '../tlds.js'
|
|
8
8
|
import type {
|
|
9
|
+
CustomConverterFn,
|
|
10
|
+
CustomValidatorFn,
|
|
9
11
|
JsonSchemaIsoDateOptions,
|
|
10
12
|
JsonSchemaIsoMonthOptions,
|
|
11
13
|
JsonSchemaStringEmailOptions,
|
|
@@ -450,7 +452,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
450
452
|
|
|
451
453
|
_assert(
|
|
452
454
|
ctx?.parentData && ctx.parentDataProperty !== undefined,
|
|
453
|
-
'You should only use `optional([x, y, z]) on a property of an object, or on an element of an array due to Ajv mutation issues.',
|
|
455
|
+
'You should only use `optional([x, y, z])` on a property of an object, or on an element of an array due to Ajv mutation issues.',
|
|
454
456
|
)
|
|
455
457
|
|
|
456
458
|
if (!optionalValues.includes(data)) return true
|
|
@@ -660,7 +662,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
660
662
|
|
|
661
663
|
_assert(
|
|
662
664
|
ctx?.parentData && ctx.parentDataProperty !== undefined,
|
|
663
|
-
'You should only use `precision(n) on a property of an object, or on an element of an array due to Ajv mutation issues.',
|
|
665
|
+
'You should only use `precision(n)` on a property of an object, or on an element of an array due to Ajv mutation issues.',
|
|
664
666
|
)
|
|
665
667
|
|
|
666
668
|
ctx.parentData[ctx.parentDataProperty] = _round(data, 10 ** (-1 * numberOfDigits))
|
|
@@ -669,6 +671,59 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
669
671
|
},
|
|
670
672
|
})
|
|
671
673
|
|
|
674
|
+
ajv.addKeyword({
|
|
675
|
+
keyword: 'customValidations',
|
|
676
|
+
modifying: false,
|
|
677
|
+
errors: true,
|
|
678
|
+
schemaType: 'array',
|
|
679
|
+
validate: function validate(customValidations: CustomValidatorFn[], data: any, _schema, ctx) {
|
|
680
|
+
if (!customValidations?.length) return true
|
|
681
|
+
|
|
682
|
+
for (const validator of customValidations) {
|
|
683
|
+
const error = validator(data)
|
|
684
|
+
if (error) {
|
|
685
|
+
;(validate as any).errors = [
|
|
686
|
+
{
|
|
687
|
+
instancePath: ctx?.instancePath ?? '',
|
|
688
|
+
message: error,
|
|
689
|
+
},
|
|
690
|
+
]
|
|
691
|
+
return false
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return true
|
|
696
|
+
},
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
ajv.addKeyword({
|
|
700
|
+
keyword: 'customConversions',
|
|
701
|
+
modifying: true,
|
|
702
|
+
errors: false,
|
|
703
|
+
schemaType: 'array',
|
|
704
|
+
validate: function validate(
|
|
705
|
+
customConversions: CustomConverterFn<any>[],
|
|
706
|
+
data: any,
|
|
707
|
+
_schema,
|
|
708
|
+
ctx,
|
|
709
|
+
) {
|
|
710
|
+
if (!customConversions?.length) return true
|
|
711
|
+
|
|
712
|
+
_assert(
|
|
713
|
+
ctx?.parentData && ctx.parentDataProperty !== undefined,
|
|
714
|
+
'You should only use `convert()` on a property of an object, or on an element of an array due to Ajv mutation issues.',
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
for (const converter of customConversions) {
|
|
718
|
+
data = converter(data)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
ctx.parentData[ctx.parentDataProperty] = data
|
|
722
|
+
|
|
723
|
+
return true
|
|
724
|
+
},
|
|
725
|
+
})
|
|
726
|
+
|
|
672
727
|
return ajv
|
|
673
728
|
}
|
|
674
729
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { _uniq } from '@naturalcycles/js-lib/array'
|
|
11
11
|
import { _assert } from '@naturalcycles/js-lib/error'
|
|
12
12
|
import type { Set2 } from '@naturalcycles/js-lib/object'
|
|
13
|
-
import {
|
|
13
|
+
import { _sortObject } from '@naturalcycles/js-lib/object'
|
|
14
14
|
import {
|
|
15
15
|
_objectAssign,
|
|
16
16
|
_typeCast,
|
|
@@ -338,7 +338,7 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
338
338
|
)
|
|
339
339
|
|
|
340
340
|
const jsonSchema = _sortObject(
|
|
341
|
-
|
|
341
|
+
deepCopyPreservingFunctions(this.schema) as AnyObject,
|
|
342
342
|
JSON_SCHEMA_ORDER,
|
|
343
343
|
) as JsonSchema<IN, OUT>
|
|
344
344
|
|
|
@@ -349,7 +349,7 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
349
349
|
|
|
350
350
|
clone(): this {
|
|
351
351
|
const cloned = Object.create(Object.getPrototypeOf(this))
|
|
352
|
-
cloned.schema =
|
|
352
|
+
cloned.schema = deepCopyPreservingFunctions(this.schema)
|
|
353
353
|
return cloned
|
|
354
354
|
}
|
|
355
355
|
|
|
@@ -455,6 +455,36 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
455
455
|
final(): JsonSchemaTerminal<IN, OUT, Opt> {
|
|
456
456
|
return new JsonSchemaTerminal<IN, OUT, Opt>(this.schema)
|
|
457
457
|
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
*
|
|
461
|
+
* @param validator A validator function that returns an error message or undefined.
|
|
462
|
+
*
|
|
463
|
+
* You may add multiple custom validators and they will be executed in the order you added them.
|
|
464
|
+
*/
|
|
465
|
+
custom<OUT2 = OUT>(validator: CustomValidatorFn): JsonSchemaAnyBuilder<IN, OUT2, Opt> {
|
|
466
|
+
const { customValidations = [] } = this.schema
|
|
467
|
+
return this.cloneAndUpdateSchema({
|
|
468
|
+
customValidations: [...customValidations, validator],
|
|
469
|
+
}) as unknown as JsonSchemaAnyBuilder<IN, OUT2, Opt>
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
*
|
|
474
|
+
* @param converter A converter function that returns a new value.
|
|
475
|
+
*
|
|
476
|
+
* You may add multiple converters and they will be executed in the order you added them,
|
|
477
|
+
* each converter receiving the result from the previous one.
|
|
478
|
+
*
|
|
479
|
+
* This feature only works when the current schema is nested in an object or array schema,
|
|
480
|
+
* due to how mutability works in Ajv.
|
|
481
|
+
*/
|
|
482
|
+
convert<OUT2>(converter: CustomConverterFn<OUT2>): JsonSchemaAnyBuilder<IN, OUT2, Opt> {
|
|
483
|
+
const { customConversions = [] } = this.schema
|
|
484
|
+
return this.cloneAndUpdateSchema({
|
|
485
|
+
customConversions: [...customConversions, converter],
|
|
486
|
+
}) as unknown as JsonSchemaAnyBuilder<IN, OUT2, Opt>
|
|
487
|
+
}
|
|
458
488
|
}
|
|
459
489
|
|
|
460
490
|
export class JsonSchemaStringBuilder<
|
|
@@ -673,6 +703,43 @@ export class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonS
|
|
|
673
703
|
})
|
|
674
704
|
}
|
|
675
705
|
|
|
706
|
+
/**
|
|
707
|
+
* @param optionalValues List of values that should be considered/converted as `undefined`.
|
|
708
|
+
*
|
|
709
|
+
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
710
|
+
* due to how mutability works in Ajv.
|
|
711
|
+
*
|
|
712
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
713
|
+
*
|
|
714
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
715
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
716
|
+
*/
|
|
717
|
+
override optional<T extends readonly null[] | undefined = undefined>(
|
|
718
|
+
optionalValues?: T,
|
|
719
|
+
): T extends readonly null[]
|
|
720
|
+
? JsonSchemaTerminal<string | IsoDate | undefined, IsoDate | undefined, true>
|
|
721
|
+
: JsonSchemaIsoDateBuilder<true> {
|
|
722
|
+
if (!optionalValues) {
|
|
723
|
+
return super.optional() as any
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
_typeCast<null[]>(optionalValues)
|
|
727
|
+
|
|
728
|
+
const newBuilder = new JsonSchemaTerminal<
|
|
729
|
+
string | IsoDate | undefined,
|
|
730
|
+
IsoDate | undefined,
|
|
731
|
+
true
|
|
732
|
+
>({
|
|
733
|
+
anyOf: [
|
|
734
|
+
{ type: 'null', optionalValues },
|
|
735
|
+
this.cloneAndUpdateSchema({ optionalField: true }).build(),
|
|
736
|
+
],
|
|
737
|
+
optionalField: true,
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
return newBuilder as any
|
|
741
|
+
}
|
|
742
|
+
|
|
676
743
|
before(date: string): this {
|
|
677
744
|
return this.cloneAndUpdateSchema({ IsoDate: { before: date } })
|
|
678
745
|
}
|
|
@@ -1051,7 +1118,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
1051
1118
|
false
|
|
1052
1119
|
> {
|
|
1053
1120
|
const newBuilder = new JsonSchemaObjectBuilder()
|
|
1054
|
-
_objectAssign(newBuilder.schema,
|
|
1121
|
+
_objectAssign(newBuilder.schema, deepCopyPreservingFunctions(this.schema))
|
|
1055
1122
|
|
|
1056
1123
|
const incomingSchemaBuilder = new JsonSchemaObjectBuilder(props)
|
|
1057
1124
|
mergeJsonSchemaObjects(newBuilder.schema as any, incomingSchemaBuilder.schema as any)
|
|
@@ -1305,7 +1372,7 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
1305
1372
|
Opt
|
|
1306
1373
|
> {
|
|
1307
1374
|
const newBuilder = new JsonSchemaObjectInferringBuilder<PROPS, Opt>()
|
|
1308
|
-
_objectAssign(newBuilder.schema,
|
|
1375
|
+
_objectAssign(newBuilder.schema, deepCopyPreservingFunctions(this.schema))
|
|
1309
1376
|
|
|
1310
1377
|
const incomingSchemaBuilder = new JsonSchemaObjectInferringBuilder<NEW_PROPS, false>(props)
|
|
1311
1378
|
mergeJsonSchemaObjects(newBuilder.schema as any, incomingSchemaBuilder.schema as any)
|
|
@@ -1587,6 +1654,8 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1587
1654
|
}
|
|
1588
1655
|
anyOfThese?: JsonSchema[]
|
|
1589
1656
|
precision?: number
|
|
1657
|
+
customValidations?: CustomValidatorFn[]
|
|
1658
|
+
customConversions?: CustomConverterFn<any>[]
|
|
1590
1659
|
}
|
|
1591
1660
|
|
|
1592
1661
|
function object(props: AnyObject): never
|
|
@@ -1893,3 +1962,25 @@ type TupleIn<T extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
|
1893
1962
|
type TupleOut<T extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
1894
1963
|
[K in keyof T]: T[K] extends JsonSchemaAnyBuilder<any, infer O, any> ? O : never
|
|
1895
1964
|
}
|
|
1965
|
+
|
|
1966
|
+
export type CustomValidatorFn = (v: any) => string | undefined
|
|
1967
|
+
export type CustomConverterFn<OUT> = (v: any) => OUT
|
|
1968
|
+
|
|
1969
|
+
/**
|
|
1970
|
+
* Deep copy that preserves functions in customValidations/customConversions.
|
|
1971
|
+
* Unlike structuredClone, this handles function references (which only exist in those two properties).
|
|
1972
|
+
*/
|
|
1973
|
+
function deepCopyPreservingFunctions<T>(obj: T): T {
|
|
1974
|
+
if (obj === null || typeof obj !== 'object') return obj
|
|
1975
|
+
if (Array.isArray(obj)) return obj.map(deepCopyPreservingFunctions) as T
|
|
1976
|
+
const copy = {} as T
|
|
1977
|
+
for (const key of Object.keys(obj)) {
|
|
1978
|
+
const value = (obj as any)[key]
|
|
1979
|
+
// customValidations/customConversions are arrays of functions - shallow copy the array
|
|
1980
|
+
;(copy as any)[key] =
|
|
1981
|
+
(key === 'customValidations' || key === 'customConversions') && Array.isArray(value)
|
|
1982
|
+
? [...value]
|
|
1983
|
+
: deepCopyPreservingFunctions(value)
|
|
1984
|
+
}
|
|
1985
|
+
return copy
|
|
1986
|
+
}
|