@naturalcycles/nodejs-lib 15.82.1 → 15.83.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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _isBetween, _lazyValue } from '@naturalcycles/js-lib';
|
|
2
|
-
import { _mapObject, Set2 } from '@naturalcycles/js-lib/object';
|
|
2
|
+
import { _deepCopy, _mapObject, Set2 } from '@naturalcycles/js-lib/object';
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string';
|
|
4
4
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
5
5
|
import { validTLDs } from '../tlds.js';
|
|
@@ -513,6 +513,53 @@ export function createAjv(opt) {
|
|
|
513
513
|
return validate;
|
|
514
514
|
},
|
|
515
515
|
});
|
|
516
|
+
ajv.addKeyword({
|
|
517
|
+
keyword: 'anyOfThese',
|
|
518
|
+
modifying: true,
|
|
519
|
+
errors: true,
|
|
520
|
+
schemaType: 'array',
|
|
521
|
+
compile(schemas, _parentSchema, _it) {
|
|
522
|
+
const validators = schemas.map(schema => ajv.compile(schema));
|
|
523
|
+
function validate(data, ctx) {
|
|
524
|
+
let correctValidator;
|
|
525
|
+
let result = false;
|
|
526
|
+
let clonedData;
|
|
527
|
+
// Try each validator until we find one that works!
|
|
528
|
+
for (const validator of validators) {
|
|
529
|
+
clonedData = isPrimitive(data) ? _deepCopy(data) : data;
|
|
530
|
+
result = validator(clonedData);
|
|
531
|
+
if (result) {
|
|
532
|
+
correctValidator = validator;
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (result && ctx?.parentData && ctx.parentDataProperty) {
|
|
537
|
+
// If we found a validator and the data is valid and we are validating a property inside an object,
|
|
538
|
+
// then we can inject our result and be done with it.
|
|
539
|
+
ctx.parentData[ctx.parentDataProperty] = clonedData;
|
|
540
|
+
}
|
|
541
|
+
else if (result) {
|
|
542
|
+
// If we found a validator but we are not validating a property inside an object,
|
|
543
|
+
// then we must re-run the validation so that the mutations caused by Ajv
|
|
544
|
+
// will be done on the input data, not only on the clone.
|
|
545
|
+
result = correctValidator(data);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
// If we didn't find a fitting schema,
|
|
549
|
+
// we add our own error.
|
|
550
|
+
;
|
|
551
|
+
validate.errors = [
|
|
552
|
+
{
|
|
553
|
+
instancePath: ctx?.instancePath ?? '',
|
|
554
|
+
message: `could not find a suitable schema to validate against`,
|
|
555
|
+
},
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
return validate;
|
|
561
|
+
},
|
|
562
|
+
});
|
|
516
563
|
return ajv;
|
|
517
564
|
}
|
|
518
565
|
const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
@@ -652,3 +699,6 @@ function isIsoMonthValid(s) {
|
|
|
652
699
|
const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE);
|
|
653
700
|
return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]');
|
|
654
701
|
}
|
|
702
|
+
function isPrimitive(data) {
|
|
703
|
+
return data !== null && typeof data === 'object';
|
|
704
|
+
}
|
|
@@ -68,7 +68,7 @@ export declare const j: {
|
|
|
68
68
|
oneOf<B extends readonly JsonSchemaAnyBuilder<any, any, boolean>[], IN = BuilderInUnion<B>, OUT = BuilderOutUnion<B>>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false>;
|
|
69
69
|
/**
|
|
70
70
|
* Use only with primitive values, otherwise this function will throw to avoid bugs.
|
|
71
|
-
* To validate objects, use `anyOfBy`.
|
|
71
|
+
* To validate objects, use `anyOfBy` or `anyOfThese`.
|
|
72
72
|
*
|
|
73
73
|
* Our Ajv is configured to strip unexpected properties from objects,
|
|
74
74
|
* and since Ajv is mutating the input, this means that it cannot
|
|
@@ -78,7 +78,28 @@ export declare const j: {
|
|
|
78
78
|
* Use `oneOf` when schemas are mutually exclusive.
|
|
79
79
|
*/
|
|
80
80
|
anyOf<B extends readonly JsonSchemaAnyBuilder<any, any, boolean>[], IN = BuilderInUnion<B>, OUT = BuilderOutUnion<B>>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false>;
|
|
81
|
+
/**
|
|
82
|
+
* Pick validation schema for an object based on the value of a specific property.
|
|
83
|
+
*
|
|
84
|
+
* ```
|
|
85
|
+
* const schemaMap = {
|
|
86
|
+
* true: successSchema,
|
|
87
|
+
* false: errorSchema
|
|
88
|
+
* }
|
|
89
|
+
*
|
|
90
|
+
* const schema = j.anyOfBy('success', schemaMap)
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
81
93
|
anyOfBy<P extends string, D extends Record<PropertyKey, JsonSchemaTerminal<any, any, any>>, IN = AnyOfByIn<D>, OUT = AnyOfByOut<D>>(propertyName: P, schemaDictionary: D): JsonSchemaAnyOfByBuilder<IN, OUT, P>;
|
|
94
|
+
/**
|
|
95
|
+
* Custom version of `anyOf` which - in contrast to the original function - does not mutate the input.
|
|
96
|
+
* This comes with a performance penalty, so do not use it where performance matters.
|
|
97
|
+
*
|
|
98
|
+
* ```
|
|
99
|
+
* const schema = j.anyOfThese([successSchema, errorSchema])
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
anyOfThese<B extends readonly JsonSchemaAnyBuilder<any, any, boolean>[], IN = BuilderInUnion<B>, OUT = BuilderOutUnion<B>>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false>;
|
|
82
103
|
and(): {
|
|
83
104
|
silentBob: () => never;
|
|
84
105
|
};
|
|
@@ -369,6 +390,10 @@ export declare class JsonSchemaAnyOfByBuilder<IN, OUT, _P extends string = strin
|
|
|
369
390
|
in: IN;
|
|
370
391
|
constructor(propertyName: string, schemaDictionary: Record<PropertyKey, JsonSchemaTerminal<any, any, any>>);
|
|
371
392
|
}
|
|
393
|
+
export declare class JsonSchemaAnyOfTheseBuilder<IN, OUT, _P extends string = string> extends JsonSchemaAnyBuilder<AnyOfByInput<IN, _P> | IN, OUT, false> {
|
|
394
|
+
in: IN;
|
|
395
|
+
constructor(propertyName: string, schemaDictionary: Record<PropertyKey, JsonSchemaTerminal<any, any, any>>);
|
|
396
|
+
}
|
|
372
397
|
type EnumBaseType = 'string' | 'number' | 'other';
|
|
373
398
|
export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
374
399
|
readonly in?: IN;
|
|
@@ -440,6 +465,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
440
465
|
propertyName: string;
|
|
441
466
|
schemaDictionary: Record<string, JsonSchema>;
|
|
442
467
|
};
|
|
468
|
+
anyOfThese?: JsonSchema[];
|
|
443
469
|
}
|
|
444
470
|
declare function object(props: AnyObject): never;
|
|
445
471
|
declare function object<IN extends AnyObject>(props: {
|
|
@@ -133,7 +133,7 @@ export const j = {
|
|
|
133
133
|
},
|
|
134
134
|
/**
|
|
135
135
|
* Use only with primitive values, otherwise this function will throw to avoid bugs.
|
|
136
|
-
* To validate objects, use `anyOfBy`.
|
|
136
|
+
* To validate objects, use `anyOfBy` or `anyOfThese`.
|
|
137
137
|
*
|
|
138
138
|
* Our Ajv is configured to strip unexpected properties from objects,
|
|
139
139
|
* and since Ajv is mutating the input, this means that it cannot
|
|
@@ -149,9 +149,34 @@ export const j = {
|
|
|
149
149
|
anyOf: schemas,
|
|
150
150
|
});
|
|
151
151
|
},
|
|
152
|
+
/**
|
|
153
|
+
* Pick validation schema for an object based on the value of a specific property.
|
|
154
|
+
*
|
|
155
|
+
* ```
|
|
156
|
+
* const schemaMap = {
|
|
157
|
+
* true: successSchema,
|
|
158
|
+
* false: errorSchema
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* const schema = j.anyOfBy('success', schemaMap)
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
152
164
|
anyOfBy(propertyName, schemaDictionary) {
|
|
153
165
|
return new JsonSchemaAnyOfByBuilder(propertyName, schemaDictionary);
|
|
154
166
|
},
|
|
167
|
+
/**
|
|
168
|
+
* Custom version of `anyOf` which - in contrast to the original function - does not mutate the input.
|
|
169
|
+
* This comes with a performance penalty, so do not use it where performance matters.
|
|
170
|
+
*
|
|
171
|
+
* ```
|
|
172
|
+
* const schema = j.anyOfThese([successSchema, errorSchema])
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
anyOfThese(items) {
|
|
176
|
+
return new JsonSchemaAnyBuilder({
|
|
177
|
+
anyOfThese: items.map(b => b.build()),
|
|
178
|
+
});
|
|
179
|
+
},
|
|
155
180
|
and() {
|
|
156
181
|
return {
|
|
157
182
|
silentBob: () => {
|
|
@@ -837,6 +862,22 @@ export class JsonSchemaAnyOfByBuilder extends JsonSchemaAnyBuilder {
|
|
|
837
862
|
});
|
|
838
863
|
}
|
|
839
864
|
}
|
|
865
|
+
export class JsonSchemaAnyOfTheseBuilder extends JsonSchemaAnyBuilder {
|
|
866
|
+
constructor(propertyName, schemaDictionary) {
|
|
867
|
+
const builtSchemaDictionary = {};
|
|
868
|
+
for (const [key, schema] of Object.entries(schemaDictionary)) {
|
|
869
|
+
builtSchemaDictionary[key] = schema.build();
|
|
870
|
+
}
|
|
871
|
+
super({
|
|
872
|
+
type: 'object',
|
|
873
|
+
hasIsOfTypeCheck: true,
|
|
874
|
+
anyOfBy: {
|
|
875
|
+
propertyName,
|
|
876
|
+
schemaDictionary: builtSchemaDictionary,
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
}
|
|
840
881
|
function object(props) {
|
|
841
882
|
return new JsonSchemaObjectBuilder(props);
|
|
842
883
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _isBetween, _lazyValue } from '@naturalcycles/js-lib'
|
|
2
|
-
import { _mapObject, Set2 } from '@naturalcycles/js-lib/object'
|
|
2
|
+
import { _deepCopy, _mapObject, Set2 } from '@naturalcycles/js-lib/object'
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string'
|
|
4
4
|
import type { AnyObject } from '@naturalcycles/js-lib/types'
|
|
5
5
|
import { Ajv2020, type Options, type ValidateFunction } from 'ajv/dist/2020.js'
|
|
@@ -585,6 +585,56 @@ export function createAjv(opt?: Options): Ajv2020 {
|
|
|
585
585
|
},
|
|
586
586
|
})
|
|
587
587
|
|
|
588
|
+
ajv.addKeyword({
|
|
589
|
+
keyword: 'anyOfThese',
|
|
590
|
+
modifying: true,
|
|
591
|
+
errors: true,
|
|
592
|
+
schemaType: 'array',
|
|
593
|
+
compile(schemas: JsonSchemaTerminal<any, any, any>[], _parentSchema, _it) {
|
|
594
|
+
const validators = schemas.map(schema => ajv.compile(schema))
|
|
595
|
+
|
|
596
|
+
function validate(data: AnyObject, ctx: any): boolean {
|
|
597
|
+
let correctValidator: ValidateFunction<unknown> | undefined
|
|
598
|
+
let result = false
|
|
599
|
+
let clonedData: any
|
|
600
|
+
|
|
601
|
+
// Try each validator until we find one that works!
|
|
602
|
+
for (const validator of validators) {
|
|
603
|
+
clonedData = isPrimitive(data) ? _deepCopy(data) : data
|
|
604
|
+
result = validator(clonedData)
|
|
605
|
+
if (result) {
|
|
606
|
+
correctValidator = validator
|
|
607
|
+
break
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (result && ctx?.parentData && ctx.parentDataProperty) {
|
|
612
|
+
// If we found a validator and the data is valid and we are validating a property inside an object,
|
|
613
|
+
// then we can inject our result and be done with it.
|
|
614
|
+
ctx.parentData[ctx.parentDataProperty] = clonedData
|
|
615
|
+
} else if (result) {
|
|
616
|
+
// If we found a validator but we are not validating a property inside an object,
|
|
617
|
+
// then we must re-run the validation so that the mutations caused by Ajv
|
|
618
|
+
// will be done on the input data, not only on the clone.
|
|
619
|
+
result = correctValidator!(data)
|
|
620
|
+
} else {
|
|
621
|
+
// If we didn't find a fitting schema,
|
|
622
|
+
// we add our own error.
|
|
623
|
+
;(validate as any).errors = [
|
|
624
|
+
{
|
|
625
|
+
instancePath: ctx?.instancePath ?? '',
|
|
626
|
+
message: `could not find a suitable schema to validate against`,
|
|
627
|
+
},
|
|
628
|
+
]
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return result
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return validate
|
|
635
|
+
},
|
|
636
|
+
})
|
|
637
|
+
|
|
588
638
|
return ajv
|
|
589
639
|
}
|
|
590
640
|
|
|
@@ -728,3 +778,7 @@ function isIsoMonthValid(s: string): boolean {
|
|
|
728
778
|
|
|
729
779
|
return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]')
|
|
730
780
|
}
|
|
781
|
+
|
|
782
|
+
function isPrimitive(data: any): boolean {
|
|
783
|
+
return data !== null && typeof data === 'object'
|
|
784
|
+
}
|
|
@@ -217,7 +217,7 @@ export const j = {
|
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
219
|
* Use only with primitive values, otherwise this function will throw to avoid bugs.
|
|
220
|
-
* To validate objects, use `anyOfBy`.
|
|
220
|
+
* To validate objects, use `anyOfBy` or `anyOfThese`.
|
|
221
221
|
*
|
|
222
222
|
* Our Ajv is configured to strip unexpected properties from objects,
|
|
223
223
|
* and since Ajv is mutating the input, this means that it cannot
|
|
@@ -242,6 +242,18 @@ export const j = {
|
|
|
242
242
|
})
|
|
243
243
|
},
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Pick validation schema for an object based on the value of a specific property.
|
|
247
|
+
*
|
|
248
|
+
* ```
|
|
249
|
+
* const schemaMap = {
|
|
250
|
+
* true: successSchema,
|
|
251
|
+
* false: errorSchema
|
|
252
|
+
* }
|
|
253
|
+
*
|
|
254
|
+
* const schema = j.anyOfBy('success', schemaMap)
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
245
257
|
anyOfBy<
|
|
246
258
|
P extends string,
|
|
247
259
|
D extends Record<PropertyKey, JsonSchemaTerminal<any, any, any>>,
|
|
@@ -251,6 +263,24 @@ export const j = {
|
|
|
251
263
|
return new JsonSchemaAnyOfByBuilder<IN, OUT, P>(propertyName, schemaDictionary)
|
|
252
264
|
},
|
|
253
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Custom version of `anyOf` which - in contrast to the original function - does not mutate the input.
|
|
268
|
+
* This comes with a performance penalty, so do not use it where performance matters.
|
|
269
|
+
*
|
|
270
|
+
* ```
|
|
271
|
+
* const schema = j.anyOfThese([successSchema, errorSchema])
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
anyOfThese<
|
|
275
|
+
B extends readonly JsonSchemaAnyBuilder<any, any, boolean>[],
|
|
276
|
+
IN = BuilderInUnion<B>,
|
|
277
|
+
OUT = BuilderOutUnion<B>,
|
|
278
|
+
>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false> {
|
|
279
|
+
return new JsonSchemaAnyBuilder<IN, OUT, false>({
|
|
280
|
+
anyOfThese: items.map(b => b.build()),
|
|
281
|
+
})
|
|
282
|
+
},
|
|
283
|
+
|
|
254
284
|
and() {
|
|
255
285
|
return {
|
|
256
286
|
silentBob: () => {
|
|
@@ -1267,6 +1297,34 @@ export class JsonSchemaAnyOfByBuilder<
|
|
|
1267
1297
|
}
|
|
1268
1298
|
}
|
|
1269
1299
|
|
|
1300
|
+
export class JsonSchemaAnyOfTheseBuilder<
|
|
1301
|
+
IN,
|
|
1302
|
+
OUT,
|
|
1303
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1304
|
+
_P extends string = string,
|
|
1305
|
+
> extends JsonSchemaAnyBuilder<AnyOfByInput<IN, _P> | IN, OUT, false> {
|
|
1306
|
+
declare in: IN
|
|
1307
|
+
|
|
1308
|
+
constructor(
|
|
1309
|
+
propertyName: string,
|
|
1310
|
+
schemaDictionary: Record<PropertyKey, JsonSchemaTerminal<any, any, any>>,
|
|
1311
|
+
) {
|
|
1312
|
+
const builtSchemaDictionary: Record<string, JsonSchema> = {}
|
|
1313
|
+
for (const [key, schema] of Object.entries(schemaDictionary)) {
|
|
1314
|
+
builtSchemaDictionary[key] = schema.build()
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
super({
|
|
1318
|
+
type: 'object',
|
|
1319
|
+
hasIsOfTypeCheck: true,
|
|
1320
|
+
anyOfBy: {
|
|
1321
|
+
propertyName,
|
|
1322
|
+
schemaDictionary: builtSchemaDictionary,
|
|
1323
|
+
},
|
|
1324
|
+
})
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1270
1328
|
type EnumBaseType = 'string' | 'number' | 'other'
|
|
1271
1329
|
|
|
1272
1330
|
export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
@@ -1350,6 +1408,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1350
1408
|
propertyName: string
|
|
1351
1409
|
schemaDictionary: Record<string, JsonSchema>
|
|
1352
1410
|
}
|
|
1411
|
+
anyOfThese?: JsonSchema[]
|
|
1353
1412
|
}
|
|
1354
1413
|
|
|
1355
1414
|
function object(props: AnyObject): never
|