@naturalcycles/nodejs-lib 15.83.0 → 15.85.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/stream/stream.model.d.ts +2 -2
- package/dist/validation/ajv/getAjv.js +10 -10
- package/dist/validation/ajv/jsonSchemaBuilder.d.ts +50 -4
- package/dist/validation/ajv/jsonSchemaBuilder.js +80 -3
- package/package.json +1 -1
- package/src/stream/stream.model.ts +4 -2
- package/src/validation/ajv/getAjv.ts +14 -10
- package/src/validation/ajv/jsonSchemaBuilder.ts +187 -24
|
@@ -24,9 +24,9 @@ export interface ReadableTyped<T = unknown> extends Readable {
|
|
|
24
24
|
take: (limit: number, opt?: ReadableSignalOptions) => ReadableTyped<T>;
|
|
25
25
|
drop: (limit: number, opt?: ReadableSignalOptions) => ReadableTyped<T>;
|
|
26
26
|
}
|
|
27
|
-
export interface WritableTyped<
|
|
27
|
+
export interface WritableTyped<_T> extends Writable {
|
|
28
28
|
}
|
|
29
|
-
export interface TransformTyped<
|
|
29
|
+
export interface TransformTyped<_IN = unknown, _OUT = unknown> extends Transform {
|
|
30
30
|
}
|
|
31
31
|
export interface TransformOptions {
|
|
32
32
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _isBetween, _lazyValue } from '@naturalcycles/js-lib';
|
|
2
|
+
import { _assert } from '@naturalcycles/js-lib/error';
|
|
2
3
|
import { _deepCopy, _mapObject, Set2 } from '@naturalcycles/js-lib/object';
|
|
3
4
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string';
|
|
4
5
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
@@ -165,7 +166,7 @@ export function createAjv(opt) {
|
|
|
165
166
|
}
|
|
166
167
|
idx++;
|
|
167
168
|
}
|
|
168
|
-
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
169
|
+
if (ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
169
170
|
ctx.parentData[ctx.parentDataProperty] = set;
|
|
170
171
|
}
|
|
171
172
|
return true;
|
|
@@ -215,7 +216,7 @@ export function createAjv(opt) {
|
|
|
215
216
|
];
|
|
216
217
|
return false;
|
|
217
218
|
}
|
|
218
|
-
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
219
|
+
if (ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
219
220
|
ctx.parentData[ctx.parentDataProperty] = buffer;
|
|
220
221
|
}
|
|
221
222
|
return true;
|
|
@@ -258,7 +259,7 @@ export function createAjv(opt) {
|
|
|
258
259
|
return false;
|
|
259
260
|
}
|
|
260
261
|
}
|
|
261
|
-
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
262
|
+
if (ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
262
263
|
ctx.parentData[ctx.parentDataProperty] = cleanData;
|
|
263
264
|
}
|
|
264
265
|
return true;
|
|
@@ -381,18 +382,17 @@ export function createAjv(opt) {
|
|
|
381
382
|
});
|
|
382
383
|
ajv.addKeyword({
|
|
383
384
|
keyword: 'optionalValues',
|
|
384
|
-
type: ['string', 'number', 'boolean'],
|
|
385
|
+
type: ['string', 'number', 'boolean', 'null'],
|
|
385
386
|
modifying: true,
|
|
386
387
|
errors: false,
|
|
387
388
|
schemaType: 'array',
|
|
388
389
|
validate: function validate(optionalValues, data, _schema, ctx) {
|
|
389
390
|
if (!optionalValues)
|
|
390
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.');
|
|
391
393
|
if (!optionalValues.includes(data))
|
|
392
394
|
return true;
|
|
393
|
-
|
|
394
|
-
delete ctx.parentData[ctx.parentDataProperty];
|
|
395
|
-
}
|
|
395
|
+
ctx.parentData[ctx.parentDataProperty] = undefined;
|
|
396
396
|
return true;
|
|
397
397
|
},
|
|
398
398
|
});
|
|
@@ -417,7 +417,7 @@ export function createAjv(opt) {
|
|
|
417
417
|
return validate;
|
|
418
418
|
},
|
|
419
419
|
});
|
|
420
|
-
// This
|
|
420
|
+
// This is added because Ajv validates the `min/maxProperties` before validating the properties.
|
|
421
421
|
// So, in case of `minProperties(1)` and `{ foo: 'bar' }` Ajv will let it pass, even
|
|
422
422
|
// if the property validation would strip `foo` from the data.
|
|
423
423
|
// And Ajv would return `{}` as a successful validation.
|
|
@@ -434,7 +434,7 @@ export function createAjv(opt) {
|
|
|
434
434
|
validate: function validate(minProperties, data, _schema, ctx) {
|
|
435
435
|
if (typeof data !== 'object')
|
|
436
436
|
return true;
|
|
437
|
-
const numberOfProperties = Object.
|
|
437
|
+
const numberOfProperties = Object.entries(data).filter(([, v]) => v !== undefined).length;
|
|
438
438
|
const isValid = numberOfProperties >= minProperties;
|
|
439
439
|
if (!isValid) {
|
|
440
440
|
;
|
|
@@ -533,7 +533,7 @@ export function createAjv(opt) {
|
|
|
533
533
|
break;
|
|
534
534
|
}
|
|
535
535
|
}
|
|
536
|
-
if (result && ctx?.parentData && ctx.parentDataProperty) {
|
|
536
|
+
if (result && ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
537
537
|
// If we found a validator and the data is valid and we are validating a property inside an object,
|
|
538
538
|
// then we can inject our result and be done with it.
|
|
539
539
|
ctx.parentData[ctx.parentDataProperty] = clonedData;
|
|
@@ -169,8 +169,13 @@ export declare class JsonSchemaStringBuilder<IN extends string | undefined = str
|
|
|
169
169
|
*
|
|
170
170
|
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
171
171
|
* due to how mutability works in Ajv.
|
|
172
|
+
*
|
|
173
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
174
|
+
*
|
|
175
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
176
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
172
177
|
*/
|
|
173
|
-
optional(optionalValues?:
|
|
178
|
+
optional<T extends readonly (string | null)[] | undefined = undefined>(optionalValues?: T): T extends readonly (infer U)[] ? null extends U ? JsonSchemaTerminal<IN | undefined, OUT | undefined, true> : JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true> : JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true>;
|
|
174
179
|
regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this;
|
|
175
180
|
pattern(pattern: string, opt?: JsonBuilderRuleOpt): this;
|
|
176
181
|
minLength(minLength: number): this;
|
|
@@ -244,8 +249,13 @@ export declare class JsonSchemaNumberBuilder<IN extends number | undefined = num
|
|
|
244
249
|
*
|
|
245
250
|
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
246
251
|
* due to how mutability works in Ajv.
|
|
252
|
+
*
|
|
253
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
254
|
+
*
|
|
255
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
256
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
247
257
|
*/
|
|
248
|
-
optional(optionalValues?:
|
|
258
|
+
optional<T extends readonly (number | null)[] | undefined = undefined>(optionalValues?: T): T extends readonly (infer U)[] ? null extends U ? JsonSchemaTerminal<IN | undefined, OUT | undefined, true> : JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true> : JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true>;
|
|
249
259
|
integer(): this;
|
|
250
260
|
branded<B extends number>(): JsonSchemaNumberBuilder<B, B, Opt>;
|
|
251
261
|
multipleOf(multipleOf: number): this;
|
|
@@ -283,6 +293,16 @@ export declare class JsonSchemaBooleanBuilder<IN extends boolean | undefined = b
|
|
|
283
293
|
export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends AnyObject, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
284
294
|
constructor(props?: AnyObject, opt?: JsonSchemaObjectBuilderOpts);
|
|
285
295
|
addProperties(props: AnyObject): this;
|
|
296
|
+
/**
|
|
297
|
+
* @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
|
|
298
|
+
*
|
|
299
|
+
* This `null` feature only works when the current schema is nested in an object or array schema,
|
|
300
|
+
* due to how mutability works in Ajv.
|
|
301
|
+
*
|
|
302
|
+
* When `null` is passed, the return type becomes `JsonSchemaTerminal`
|
|
303
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
304
|
+
*/
|
|
305
|
+
optional<N extends null | undefined = undefined>(nullValue?: N): N extends null ? JsonSchemaTerminal<IN | undefined, OUT | undefined, true> : JsonSchemaAnyBuilder<IN | undefined, OUT | undefined, true>;
|
|
286
306
|
/**
|
|
287
307
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
288
308
|
*/
|
|
@@ -345,6 +365,32 @@ export declare class JsonSchemaObjectInferringBuilder<PROPS extends Record<strin
|
|
|
345
365
|
}>, Opt> {
|
|
346
366
|
constructor(props?: PROPS);
|
|
347
367
|
addProperties(props: PROPS): this;
|
|
368
|
+
/**
|
|
369
|
+
* @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
|
|
370
|
+
*
|
|
371
|
+
* This `null` feature only works when the current schema is nested in an object or array schema,
|
|
372
|
+
* due to how mutability works in Ajv.
|
|
373
|
+
*
|
|
374
|
+
* When `null` is passed, the return type becomes `JsonSchemaTerminal`
|
|
375
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
376
|
+
*/
|
|
377
|
+
optional<N extends null | undefined = undefined>(nullValue?: N): N extends null ? JsonSchemaTerminal<Expand<{
|
|
378
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
379
|
+
} & {
|
|
380
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
381
|
+
}> | undefined, Expand<{
|
|
382
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
|
|
383
|
+
} & {
|
|
384
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
|
|
385
|
+
}> | undefined, true> : JsonSchemaAnyBuilder<Expand<{
|
|
386
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
387
|
+
} & {
|
|
388
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
389
|
+
}> | undefined, Expand<{
|
|
390
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
|
|
391
|
+
} & {
|
|
392
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
|
|
393
|
+
}> | undefined, true>;
|
|
348
394
|
/**
|
|
349
395
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
350
396
|
*/
|
|
@@ -457,7 +503,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
457
503
|
truncate?: number;
|
|
458
504
|
};
|
|
459
505
|
errorMessages?: StringMap<string>;
|
|
460
|
-
optionalValues?: (string | number | boolean)[];
|
|
506
|
+
optionalValues?: (string | number | boolean | null)[];
|
|
461
507
|
keySchema?: JsonSchema;
|
|
462
508
|
minProperties2?: number;
|
|
463
509
|
exclusiveProperties?: (readonly string[])[];
|
|
@@ -469,7 +515,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
469
515
|
}
|
|
470
516
|
declare function object(props: AnyObject): never;
|
|
471
517
|
declare function object<IN extends AnyObject>(props: {
|
|
472
|
-
[K in keyof Required<IN>]-?:
|
|
518
|
+
[K in keyof Required<IN>]-?: JsonSchemaTerminal<any, IN[K], any>;
|
|
473
519
|
}): JsonSchemaObjectBuilder<IN, IN, false>;
|
|
474
520
|
declare function objectInfer<P extends Record<string, JsonSchemaAnyBuilder<any, any, any>>>(props: P): JsonSchemaObjectInferringBuilder<P, false>;
|
|
475
521
|
declare function objectDbEntity(props: AnyObject): never;
|
|
@@ -4,7 +4,7 @@ import { _isUndefined, _numberEnumValues, _stringEnumValues, getEnumType, } from
|
|
|
4
4
|
import { _uniq } from '@naturalcycles/js-lib/array';
|
|
5
5
|
import { _assert } from '@naturalcycles/js-lib/error';
|
|
6
6
|
import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object';
|
|
7
|
-
import { _objectAssign, JWT_REGEX, } from '@naturalcycles/js-lib/types';
|
|
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';
|
|
10
10
|
import { isEveryItemNumber, isEveryItemPrimitive, isEveryItemString, JSON_SCHEMA_ORDER, mergeJsonSchemaObjects, } from './jsonSchemaBuilder.util.js';
|
|
@@ -326,17 +326,33 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
326
326
|
*
|
|
327
327
|
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
328
328
|
* due to how mutability works in Ajv.
|
|
329
|
+
*
|
|
330
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
331
|
+
*
|
|
332
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
333
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
329
334
|
*/
|
|
330
335
|
optional(optionalValues) {
|
|
331
336
|
if (!optionalValues) {
|
|
332
337
|
return super.optional();
|
|
333
338
|
}
|
|
334
|
-
|
|
339
|
+
_typeCast(optionalValues);
|
|
340
|
+
let newBuilder = new JsonSchemaStringBuilder().optional();
|
|
335
341
|
const alternativesSchema = j.enum(optionalValues);
|
|
336
342
|
Object.assign(newBuilder.getSchema(), {
|
|
337
343
|
anyOf: [this.build(), alternativesSchema.build()],
|
|
338
344
|
optionalValues,
|
|
339
345
|
});
|
|
346
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
347
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
348
|
+
// but the typing should not reflect that.
|
|
349
|
+
// We also cannot accept more rules attached, since we're not building a StringSchema anymore.
|
|
350
|
+
if (optionalValues.includes(null)) {
|
|
351
|
+
newBuilder = new JsonSchemaTerminal({
|
|
352
|
+
anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
|
|
353
|
+
optionalField: true,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
340
356
|
return newBuilder;
|
|
341
357
|
}
|
|
342
358
|
regex(pattern, opt) {
|
|
@@ -502,17 +518,33 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
|
|
|
502
518
|
*
|
|
503
519
|
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
504
520
|
* due to how mutability works in Ajv.
|
|
521
|
+
*
|
|
522
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
523
|
+
*
|
|
524
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
525
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
505
526
|
*/
|
|
506
527
|
optional(optionalValues) {
|
|
507
528
|
if (!optionalValues) {
|
|
508
529
|
return super.optional();
|
|
509
530
|
}
|
|
510
|
-
|
|
531
|
+
_typeCast(optionalValues);
|
|
532
|
+
let newBuilder = new JsonSchemaNumberBuilder().optional();
|
|
511
533
|
const alternativesSchema = j.enum(optionalValues);
|
|
512
534
|
Object.assign(newBuilder.getSchema(), {
|
|
513
535
|
anyOf: [this.build(), alternativesSchema.build()],
|
|
514
536
|
optionalValues,
|
|
515
537
|
});
|
|
538
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
539
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
540
|
+
// but the typing should not reflect that.
|
|
541
|
+
// We also cannot accept more rules attached, since we're not building a NumberSchema anymore.
|
|
542
|
+
if (optionalValues.includes(null)) {
|
|
543
|
+
newBuilder = new JsonSchemaTerminal({
|
|
544
|
+
anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
|
|
545
|
+
optionalField: true,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
516
548
|
return newBuilder;
|
|
517
549
|
}
|
|
518
550
|
integer() {
|
|
@@ -655,6 +687,28 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
655
687
|
this.schema.required = _uniq(required).sort();
|
|
656
688
|
return this;
|
|
657
689
|
}
|
|
690
|
+
/**
|
|
691
|
+
* @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
|
|
692
|
+
*
|
|
693
|
+
* This `null` feature only works when the current schema is nested in an object or array schema,
|
|
694
|
+
* due to how mutability works in Ajv.
|
|
695
|
+
*
|
|
696
|
+
* When `null` is passed, the return type becomes `JsonSchemaTerminal`
|
|
697
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
698
|
+
*/
|
|
699
|
+
optional(nullValue) {
|
|
700
|
+
if (typeof nullValue === 'undefined') {
|
|
701
|
+
return super.optional();
|
|
702
|
+
}
|
|
703
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
704
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
705
|
+
// but the typing should not reflect that.
|
|
706
|
+
// We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
|
|
707
|
+
return new JsonSchemaTerminal({
|
|
708
|
+
anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
|
|
709
|
+
optionalField: true,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
658
712
|
/**
|
|
659
713
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
660
714
|
*/
|
|
@@ -741,6 +795,29 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
|
741
795
|
this.schema.required = _uniq(required).sort();
|
|
742
796
|
return this;
|
|
743
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
|
|
800
|
+
*
|
|
801
|
+
* This `null` feature only works when the current schema is nested in an object or array schema,
|
|
802
|
+
* due to how mutability works in Ajv.
|
|
803
|
+
*
|
|
804
|
+
* When `null` is passed, the return type becomes `JsonSchemaTerminal`
|
|
805
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
806
|
+
*/
|
|
807
|
+
// @ts-expect-error override adds optional parameter which is compatible but TS can't verify complex mapped types
|
|
808
|
+
optional(nullValue) {
|
|
809
|
+
if (nullValue === undefined) {
|
|
810
|
+
return super.optional();
|
|
811
|
+
}
|
|
812
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
813
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
814
|
+
// but the typing should not reflect that.
|
|
815
|
+
// We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
|
|
816
|
+
return new JsonSchemaTerminal({
|
|
817
|
+
anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
|
|
818
|
+
optionalField: true,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
744
821
|
/**
|
|
745
822
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
746
823
|
*/
|
package/package.json
CHANGED
|
@@ -50,9 +50,11 @@ export interface ReadableTyped<T = unknown> extends Readable {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// oxlint-disable no-unused-vars
|
|
53
|
-
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
54
|
+
export interface WritableTyped<_T> extends Writable {}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
57
|
+
export interface TransformTyped<_IN = unknown, _OUT = unknown> extends Transform {}
|
|
56
58
|
// oxlint-enable
|
|
57
59
|
|
|
58
60
|
export interface TransformOptions {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _isBetween, _lazyValue } from '@naturalcycles/js-lib'
|
|
2
|
+
import { _assert } from '@naturalcycles/js-lib/error'
|
|
2
3
|
import { _deepCopy, _mapObject, Set2 } from '@naturalcycles/js-lib/object'
|
|
3
4
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string'
|
|
4
5
|
import type { AnyObject } from '@naturalcycles/js-lib/types'
|
|
@@ -195,7 +196,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
195
196
|
idx++
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
199
|
+
if (ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
199
200
|
ctx.parentData[ctx.parentDataProperty] = set
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -248,7 +249,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
248
249
|
return false
|
|
249
250
|
}
|
|
250
251
|
|
|
251
|
-
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
252
|
+
if (ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
252
253
|
ctx.parentData[ctx.parentDataProperty] = buffer
|
|
253
254
|
}
|
|
254
255
|
|
|
@@ -297,7 +298,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
300
|
|
|
300
|
-
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
301
|
+
if (ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
301
302
|
ctx.parentData[ctx.parentDataProperty] = cleanData
|
|
302
303
|
}
|
|
303
304
|
|
|
@@ -435,7 +436,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
435
436
|
|
|
436
437
|
ajv.addKeyword({
|
|
437
438
|
keyword: 'optionalValues',
|
|
438
|
-
type: ['string', 'number', 'boolean'],
|
|
439
|
+
type: ['string', 'number', 'boolean', 'null'],
|
|
439
440
|
modifying: true,
|
|
440
441
|
errors: false,
|
|
441
442
|
schemaType: 'array',
|
|
@@ -447,11 +448,14 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
447
448
|
) {
|
|
448
449
|
if (!optionalValues) return true
|
|
449
450
|
|
|
451
|
+
_assert(
|
|
452
|
+
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.',
|
|
454
|
+
)
|
|
455
|
+
|
|
450
456
|
if (!optionalValues.includes(data)) return true
|
|
451
457
|
|
|
452
|
-
|
|
453
|
-
delete ctx.parentData[ctx.parentDataProperty]
|
|
454
|
-
}
|
|
458
|
+
ctx.parentData[ctx.parentDataProperty] = undefined
|
|
455
459
|
|
|
456
460
|
return true
|
|
457
461
|
},
|
|
@@ -482,7 +486,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
482
486
|
},
|
|
483
487
|
})
|
|
484
488
|
|
|
485
|
-
// This
|
|
489
|
+
// This is added because Ajv validates the `min/maxProperties` before validating the properties.
|
|
486
490
|
// So, in case of `minProperties(1)` and `{ foo: 'bar' }` Ajv will let it pass, even
|
|
487
491
|
// if the property validation would strip `foo` from the data.
|
|
488
492
|
// And Ajv would return `{}` as a successful validation.
|
|
@@ -499,7 +503,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
499
503
|
validate: function validate(minProperties: number, data: AnyObject, _schema, ctx) {
|
|
500
504
|
if (typeof data !== 'object') return true
|
|
501
505
|
|
|
502
|
-
const numberOfProperties = Object.
|
|
506
|
+
const numberOfProperties = Object.entries(data).filter(([, v]) => v !== undefined).length
|
|
503
507
|
const isValid = numberOfProperties >= minProperties
|
|
504
508
|
if (!isValid) {
|
|
505
509
|
;(validate as any).errors = [
|
|
@@ -608,7 +612,7 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
608
612
|
}
|
|
609
613
|
}
|
|
610
614
|
|
|
611
|
-
if (result && ctx?.parentData && ctx.parentDataProperty) {
|
|
615
|
+
if (result && ctx?.parentData && ctx.parentDataProperty !== undefined) {
|
|
612
616
|
// If we found a validator and the data is valid and we are validating a property inside an object,
|
|
613
617
|
// then we can inject our result and be done with it.
|
|
614
618
|
ctx.parentData[ctx.parentDataProperty] = clonedData
|
|
@@ -13,6 +13,7 @@ import type { Set2 } from '@naturalcycles/js-lib/object'
|
|
|
13
13
|
import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object'
|
|
14
14
|
import {
|
|
15
15
|
_objectAssign,
|
|
16
|
+
_typeCast,
|
|
16
17
|
type AnyObject,
|
|
17
18
|
type BaseDBEntity,
|
|
18
19
|
type IANATimezone,
|
|
@@ -468,26 +469,45 @@ export class JsonSchemaStringBuilder<
|
|
|
468
469
|
*
|
|
469
470
|
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
470
471
|
* due to how mutability works in Ajv.
|
|
472
|
+
*
|
|
473
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
474
|
+
*
|
|
475
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
476
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
471
477
|
*/
|
|
472
|
-
override optional(
|
|
473
|
-
optionalValues?:
|
|
474
|
-
):
|
|
478
|
+
override optional<T extends readonly (string | null)[] | undefined = undefined>(
|
|
479
|
+
optionalValues?: T,
|
|
480
|
+
): T extends readonly (infer U)[]
|
|
481
|
+
? null extends U
|
|
482
|
+
? JsonSchemaTerminal<IN | undefined, OUT | undefined, true>
|
|
483
|
+
: JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true>
|
|
484
|
+
: JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true> {
|
|
475
485
|
if (!optionalValues) {
|
|
476
|
-
return super.optional() as
|
|
477
|
-
IN | undefined,
|
|
478
|
-
OUT | undefined,
|
|
479
|
-
true
|
|
480
|
-
>
|
|
486
|
+
return super.optional() as any
|
|
481
487
|
}
|
|
482
488
|
|
|
483
|
-
|
|
489
|
+
_typeCast<(string | null)[]>(optionalValues)
|
|
490
|
+
|
|
491
|
+
let newBuilder: JsonSchemaTerminal<IN | undefined, OUT | undefined, true> =
|
|
492
|
+
new JsonSchemaStringBuilder<IN, OUT, Opt>().optional()
|
|
484
493
|
const alternativesSchema = j.enum(optionalValues)
|
|
485
494
|
Object.assign(newBuilder.getSchema(), {
|
|
486
495
|
anyOf: [this.build(), alternativesSchema.build()],
|
|
487
496
|
optionalValues,
|
|
488
497
|
})
|
|
489
498
|
|
|
490
|
-
|
|
499
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
500
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
501
|
+
// but the typing should not reflect that.
|
|
502
|
+
// We also cannot accept more rules attached, since we're not building a StringSchema anymore.
|
|
503
|
+
if (optionalValues.includes(null)) {
|
|
504
|
+
newBuilder = new JsonSchemaTerminal({
|
|
505
|
+
anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
|
|
506
|
+
optionalField: true,
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return newBuilder as any
|
|
491
511
|
}
|
|
492
512
|
|
|
493
513
|
regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this {
|
|
@@ -716,27 +736,45 @@ export class JsonSchemaNumberBuilder<
|
|
|
716
736
|
*
|
|
717
737
|
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
718
738
|
* due to how mutability works in Ajv.
|
|
739
|
+
*
|
|
740
|
+
* Make sure this `optional()` call is at the end of your call chain.
|
|
741
|
+
*
|
|
742
|
+
* When `null` is included in optionalValues, the return type becomes `JsonSchemaTerminal`
|
|
743
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
719
744
|
*/
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
745
|
+
override optional<T extends readonly (number | null)[] | undefined = undefined>(
|
|
746
|
+
optionalValues?: T,
|
|
747
|
+
): T extends readonly (infer U)[]
|
|
748
|
+
? null extends U
|
|
749
|
+
? JsonSchemaTerminal<IN | undefined, OUT | undefined, true>
|
|
750
|
+
: JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true>
|
|
751
|
+
: JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true> {
|
|
724
752
|
if (!optionalValues) {
|
|
725
|
-
return super.optional() as
|
|
726
|
-
IN | undefined,
|
|
727
|
-
OUT | undefined,
|
|
728
|
-
true
|
|
729
|
-
>
|
|
753
|
+
return super.optional() as any
|
|
730
754
|
}
|
|
731
755
|
|
|
732
|
-
|
|
756
|
+
_typeCast<(number | null)[]>(optionalValues)
|
|
757
|
+
|
|
758
|
+
let newBuilder: JsonSchemaTerminal<IN | undefined, OUT | undefined, true> =
|
|
759
|
+
new JsonSchemaNumberBuilder<IN, OUT, Opt>().optional()
|
|
733
760
|
const alternativesSchema = j.enum(optionalValues)
|
|
734
761
|
Object.assign(newBuilder.getSchema(), {
|
|
735
762
|
anyOf: [this.build(), alternativesSchema.build()],
|
|
736
763
|
optionalValues,
|
|
737
764
|
})
|
|
738
765
|
|
|
739
|
-
|
|
766
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
767
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
768
|
+
// but the typing should not reflect that.
|
|
769
|
+
// We also cannot accept more rules attached, since we're not building a NumberSchema anymore.
|
|
770
|
+
if (optionalValues.includes(null)) {
|
|
771
|
+
newBuilder = new JsonSchemaTerminal({
|
|
772
|
+
anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
|
|
773
|
+
optionalField: true,
|
|
774
|
+
})
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return newBuilder as any
|
|
740
778
|
}
|
|
741
779
|
|
|
742
780
|
integer(): this {
|
|
@@ -930,6 +968,34 @@ export class JsonSchemaObjectBuilder<
|
|
|
930
968
|
return this
|
|
931
969
|
}
|
|
932
970
|
|
|
971
|
+
/**
|
|
972
|
+
* @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
|
|
973
|
+
*
|
|
974
|
+
* This `null` feature only works when the current schema is nested in an object or array schema,
|
|
975
|
+
* due to how mutability works in Ajv.
|
|
976
|
+
*
|
|
977
|
+
* When `null` is passed, the return type becomes `JsonSchemaTerminal`
|
|
978
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
979
|
+
*/
|
|
980
|
+
override optional<N extends null | undefined = undefined>(
|
|
981
|
+
nullValue?: N,
|
|
982
|
+
): N extends null
|
|
983
|
+
? JsonSchemaTerminal<IN | undefined, OUT | undefined, true>
|
|
984
|
+
: JsonSchemaAnyBuilder<IN | undefined, OUT | undefined, true> {
|
|
985
|
+
if (typeof nullValue === 'undefined') {
|
|
986
|
+
return super.optional()
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
990
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
991
|
+
// but the typing should not reflect that.
|
|
992
|
+
// We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
|
|
993
|
+
return new JsonSchemaTerminal({
|
|
994
|
+
anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
|
|
995
|
+
optionalField: true,
|
|
996
|
+
}) as any
|
|
997
|
+
}
|
|
998
|
+
|
|
933
999
|
/**
|
|
934
1000
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
935
1001
|
*/
|
|
@@ -1108,6 +1174,103 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
1108
1174
|
return this
|
|
1109
1175
|
}
|
|
1110
1176
|
|
|
1177
|
+
/**
|
|
1178
|
+
* @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
|
|
1179
|
+
*
|
|
1180
|
+
* This `null` feature only works when the current schema is nested in an object or array schema,
|
|
1181
|
+
* due to how mutability works in Ajv.
|
|
1182
|
+
*
|
|
1183
|
+
* When `null` is passed, the return type becomes `JsonSchemaTerminal`
|
|
1184
|
+
* (no further chaining allowed) because the schema is wrapped in an anyOf structure.
|
|
1185
|
+
*/
|
|
1186
|
+
// @ts-expect-error override adds optional parameter which is compatible but TS can't verify complex mapped types
|
|
1187
|
+
override optional<N extends null | undefined = undefined>(
|
|
1188
|
+
nullValue?: N,
|
|
1189
|
+
): N extends null
|
|
1190
|
+
? JsonSchemaTerminal<
|
|
1191
|
+
| Expand<
|
|
1192
|
+
{
|
|
1193
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1194
|
+
? IsOpt extends true
|
|
1195
|
+
? never
|
|
1196
|
+
: K
|
|
1197
|
+
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1198
|
+
} & {
|
|
1199
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1200
|
+
? IsOpt extends true
|
|
1201
|
+
? K
|
|
1202
|
+
: never
|
|
1203
|
+
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1204
|
+
}
|
|
1205
|
+
>
|
|
1206
|
+
| undefined,
|
|
1207
|
+
| Expand<
|
|
1208
|
+
{
|
|
1209
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1210
|
+
? IsOpt extends true
|
|
1211
|
+
? never
|
|
1212
|
+
: K
|
|
1213
|
+
: never]: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1214
|
+
} & {
|
|
1215
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1216
|
+
? IsOpt extends true
|
|
1217
|
+
? K
|
|
1218
|
+
: never
|
|
1219
|
+
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1220
|
+
}
|
|
1221
|
+
>
|
|
1222
|
+
| undefined,
|
|
1223
|
+
true
|
|
1224
|
+
>
|
|
1225
|
+
: JsonSchemaAnyBuilder<
|
|
1226
|
+
| Expand<
|
|
1227
|
+
{
|
|
1228
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1229
|
+
? IsOpt extends true
|
|
1230
|
+
? never
|
|
1231
|
+
: K
|
|
1232
|
+
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1233
|
+
} & {
|
|
1234
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1235
|
+
? IsOpt extends true
|
|
1236
|
+
? K
|
|
1237
|
+
: never
|
|
1238
|
+
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1239
|
+
}
|
|
1240
|
+
>
|
|
1241
|
+
| undefined,
|
|
1242
|
+
| Expand<
|
|
1243
|
+
{
|
|
1244
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1245
|
+
? IsOpt extends true
|
|
1246
|
+
? never
|
|
1247
|
+
: K
|
|
1248
|
+
: never]: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1249
|
+
} & {
|
|
1250
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1251
|
+
? IsOpt extends true
|
|
1252
|
+
? K
|
|
1253
|
+
: never
|
|
1254
|
+
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1255
|
+
}
|
|
1256
|
+
>
|
|
1257
|
+
| undefined,
|
|
1258
|
+
true
|
|
1259
|
+
> {
|
|
1260
|
+
if (nullValue === undefined) {
|
|
1261
|
+
return super.optional() as any
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
|
|
1265
|
+
// so we must allow `null` values to be parsed by Ajv,
|
|
1266
|
+
// but the typing should not reflect that.
|
|
1267
|
+
// We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
|
|
1268
|
+
return new JsonSchemaTerminal({
|
|
1269
|
+
anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
|
|
1270
|
+
optionalField: true,
|
|
1271
|
+
}) as any
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1111
1274
|
/**
|
|
1112
1275
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
1113
1276
|
*/
|
|
@@ -1400,7 +1563,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1400
1563
|
instanceof?: string | string[]
|
|
1401
1564
|
transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
|
|
1402
1565
|
errorMessages?: StringMap<string>
|
|
1403
|
-
optionalValues?: (string | number | boolean)[]
|
|
1566
|
+
optionalValues?: (string | number | boolean | null)[]
|
|
1404
1567
|
keySchema?: JsonSchema
|
|
1405
1568
|
minProperties2?: number
|
|
1406
1569
|
exclusiveProperties?: (readonly string[])[]
|
|
@@ -1413,11 +1576,11 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1413
1576
|
|
|
1414
1577
|
function object(props: AnyObject): never
|
|
1415
1578
|
function object<IN extends AnyObject>(props: {
|
|
1416
|
-
[K in keyof Required<IN>]-?:
|
|
1579
|
+
[K in keyof Required<IN>]-?: JsonSchemaTerminal<any, IN[K], any>
|
|
1417
1580
|
}): JsonSchemaObjectBuilder<IN, IN, false>
|
|
1418
1581
|
|
|
1419
1582
|
function object<IN extends AnyObject>(props: {
|
|
1420
|
-
[key in keyof IN]:
|
|
1583
|
+
[key in keyof IN]: JsonSchemaTerminal<any, IN[key], any>
|
|
1421
1584
|
}): JsonSchemaObjectBuilder<IN, IN, false> {
|
|
1422
1585
|
return new JsonSchemaObjectBuilder<IN, IN, false>(props)
|
|
1423
1586
|
}
|