@naturalcycles/nodejs-lib 15.56.1 → 15.57.1
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 +1 -0
- package/dist/validation/ajv/getAjv.js +21 -18
- package/dist/validation/ajv/jsonSchemaBuilder.d.ts +4 -0
- package/dist/validation/ajv/jsonSchemaBuilder.js +17 -18
- package/package.json +1 -1
- package/src/validation/ajv/ajvSchema.ts +1 -0
- package/src/validation/ajv/getAjv.ts +27 -18
- package/src/validation/ajv/jsonSchemaBuilder.ts +28 -19
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { _lazyValue } from '@naturalcycles/js-lib';
|
|
2
2
|
import { Set2 } from '@naturalcycles/js-lib/object';
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string';
|
|
4
|
-
import {
|
|
4
|
+
import { Ajv } from 'ajv';
|
|
5
5
|
import { validTLDs } from '../tlds.js';
|
|
6
6
|
/* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
|
|
7
7
|
// oxlint-disable unicorn/prefer-code-point
|
|
@@ -66,27 +66,30 @@ export function createAjv(opt) {
|
|
|
66
66
|
modifying: true,
|
|
67
67
|
schemaType: 'object',
|
|
68
68
|
errors: true,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
validate: function validate(transform, data, _schema, ctx) {
|
|
70
|
+
if (!data)
|
|
71
|
+
return true;
|
|
72
|
+
let transformedData = data;
|
|
73
|
+
if (transform.trim) {
|
|
74
|
+
transformedData = transformedData.trim();
|
|
74
75
|
}
|
|
75
|
-
if (
|
|
76
|
-
|
|
76
|
+
if (transform.toLowerCase) {
|
|
77
|
+
transformedData = transformedData.toLocaleLowerCase();
|
|
77
78
|
}
|
|
78
|
-
if (
|
|
79
|
-
|
|
79
|
+
if (transform.toUpperCase) {
|
|
80
|
+
transformedData = transformedData.toLocaleUpperCase();
|
|
80
81
|
}
|
|
81
|
-
if (typeof
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
82
|
+
if (typeof transform.truncate === 'number' && transform.truncate >= 0) {
|
|
83
|
+
transformedData = transformedData.slice(0, transform.truncate);
|
|
84
|
+
if (transform.trim) {
|
|
85
|
+
transformedData = transformedData.trim();
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// Explicit check for `undefined` because parentDataProperty can be `0` when it comes to arrays.
|
|
89
|
+
if (ctx?.parentData && typeof ctx.parentDataProperty !== 'undefined') {
|
|
90
|
+
ctx.parentData[ctx.parentDataProperty] = transformedData;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
90
93
|
},
|
|
91
94
|
});
|
|
92
95
|
ajv.addKeyword({
|
|
@@ -366,7 +369,7 @@ export function createAjv(opt) {
|
|
|
366
369
|
if (!optionalValues.includes(data))
|
|
367
370
|
return true;
|
|
368
371
|
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
369
|
-
ctx.parentData[ctx.parentDataProperty]
|
|
372
|
+
delete ctx.parentData[ctx.parentDataProperty];
|
|
370
373
|
}
|
|
371
374
|
return true;
|
|
372
375
|
},
|
|
@@ -49,6 +49,8 @@ export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTermin
|
|
|
49
49
|
*
|
|
50
50
|
* const schemaBad = j.string().isOfType<number>() // ❌
|
|
51
51
|
* schemaBad.build() // TypeError: property "build" does not exist on type "never"
|
|
52
|
+
*
|
|
53
|
+
* const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
|
|
52
54
|
* ```
|
|
53
55
|
*/
|
|
54
56
|
isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never;
|
|
@@ -86,6 +88,7 @@ export declare class JsonSchemaStringBuilder<IN extends string | undefined = str
|
|
|
86
88
|
pattern(pattern: string, opt?: JsonBuilderRuleOpt): this;
|
|
87
89
|
minLength(minLength: number): this;
|
|
88
90
|
maxLength(maxLength: number): this;
|
|
91
|
+
length(exactLength: number): this;
|
|
89
92
|
length(minLength: number, maxLength: number): this;
|
|
90
93
|
email(opt?: Partial<JsonSchemaStringEmailOptions>): this;
|
|
91
94
|
trim(): this;
|
|
@@ -234,6 +237,7 @@ export declare class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyB
|
|
|
234
237
|
constructor(itemsSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>);
|
|
235
238
|
minLength(minItems: number): this;
|
|
236
239
|
maxLength(maxItems: number): this;
|
|
240
|
+
length(exactLength: number): this;
|
|
237
241
|
length(minItems: number, maxItems: number): this;
|
|
238
242
|
exactLength(length: number): this;
|
|
239
243
|
unique(): this;
|
|
@@ -26,8 +26,6 @@ export const j = {
|
|
|
26
26
|
},
|
|
27
27
|
stringMap(schema) {
|
|
28
28
|
const builtSchema = schema.build();
|
|
29
|
-
_assert(!builtSchema?.optionalField, 'In a StringMap schema the value cannot be `undefined`, because `undefined` is not a valid JSON Schema value.');
|
|
30
|
-
builtSchema.optionalField = undefined;
|
|
31
29
|
return new JsonSchemaObjectBuilder({}, {
|
|
32
30
|
hasIsOfTypeCheck: false,
|
|
33
31
|
patternProperties: {
|
|
@@ -111,7 +109,9 @@ export class JsonSchemaTerminal {
|
|
|
111
109
|
* Same as if it would be JSON.stringified.
|
|
112
110
|
*/
|
|
113
111
|
build() {
|
|
114
|
-
|
|
112
|
+
const jsonSchema = _sortObject(JSON.parse(JSON.stringify(this.schema)), JSON_SCHEMA_ORDER);
|
|
113
|
+
delete jsonSchema.optionalField;
|
|
114
|
+
return jsonSchema;
|
|
115
115
|
}
|
|
116
116
|
clone() {
|
|
117
117
|
return new JsonSchemaAnyBuilder(_deepCopy(this.schema));
|
|
@@ -140,6 +140,8 @@ export class JsonSchemaAnyBuilder extends JsonSchemaTerminal {
|
|
|
140
140
|
*
|
|
141
141
|
* const schemaBad = j.string().isOfType<number>() // ❌
|
|
142
142
|
* schemaBad.build() // TypeError: property "build" does not exist on type "never"
|
|
143
|
+
*
|
|
144
|
+
* const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
|
|
143
145
|
* ```
|
|
144
146
|
*/
|
|
145
147
|
isOfType() {
|
|
@@ -248,9 +250,9 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
248
250
|
_objectAssign(this.schema, { maxLength });
|
|
249
251
|
return this;
|
|
250
252
|
}
|
|
251
|
-
length(
|
|
252
|
-
|
|
253
|
-
return this;
|
|
253
|
+
length(minLengthOrExactLength, maxLength) {
|
|
254
|
+
const maxLengthActual = maxLength ?? minLengthOrExactLength;
|
|
255
|
+
return this.minLength(minLengthOrExactLength).maxLength(maxLengthActual);
|
|
254
256
|
}
|
|
255
257
|
email(opt) {
|
|
256
258
|
const defaultOptions = { checkTLD: true };
|
|
@@ -533,13 +535,11 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
533
535
|
const properties = {};
|
|
534
536
|
const required = [];
|
|
535
537
|
for (const [key, builder] of Object.entries(props)) {
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
538
|
+
const isOptional = builder.getSchema().optionalField;
|
|
539
|
+
if (!isOptional) {
|
|
538
540
|
required.push(key);
|
|
539
541
|
}
|
|
540
|
-
|
|
541
|
-
schema.optionalField = undefined;
|
|
542
|
-
}
|
|
542
|
+
const schema = builder.build();
|
|
543
543
|
properties[key] = schema;
|
|
544
544
|
}
|
|
545
545
|
this.schema.properties = properties;
|
|
@@ -595,13 +595,11 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
|
595
595
|
const properties = {};
|
|
596
596
|
const required = [];
|
|
597
597
|
for (const [key, builder] of Object.entries(props)) {
|
|
598
|
-
const
|
|
599
|
-
if (!
|
|
598
|
+
const isOptional = builder.getSchema().optionalField;
|
|
599
|
+
if (!isOptional) {
|
|
600
600
|
required.push(key);
|
|
601
601
|
}
|
|
602
|
-
|
|
603
|
-
schema.optionalField = undefined;
|
|
604
|
-
}
|
|
602
|
+
const schema = builder.build();
|
|
605
603
|
properties[key] = schema;
|
|
606
604
|
}
|
|
607
605
|
this.schema.properties = properties;
|
|
@@ -649,8 +647,9 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
|
|
|
649
647
|
_objectAssign(this.schema, { maxItems });
|
|
650
648
|
return this;
|
|
651
649
|
}
|
|
652
|
-
length(
|
|
653
|
-
|
|
650
|
+
length(minItemsOrExact, maxItems) {
|
|
651
|
+
const maxItemsActual = maxItems ?? minItemsOrExact;
|
|
652
|
+
return this.minLength(minItemsOrExact).maxLength(maxItemsActual);
|
|
654
653
|
}
|
|
655
654
|
exactLength(length) {
|
|
656
655
|
return this.minLength(length).maxLength(length);
|
package/package.json
CHANGED
|
@@ -73,6 +73,7 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
73
73
|
let jsonSchema: JsonSchema<IN, OUT>
|
|
74
74
|
|
|
75
75
|
if (AjvSchema.isJsonSchemaBuilder(schema)) {
|
|
76
|
+
// oxlint-disable typescript-eslint(no-unnecessary-type-assertion)
|
|
76
77
|
jsonSchema = (schema as JsonSchemaTerminal<IN, OUT, any>).build()
|
|
77
78
|
AjvSchema.requireValidJsonSchema(jsonSchema)
|
|
78
79
|
} else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { _lazyValue } from '@naturalcycles/js-lib'
|
|
2
2
|
import { Set2 } from '@naturalcycles/js-lib/object'
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string'
|
|
4
|
-
import {
|
|
4
|
+
import { Ajv, type Options, type ValidateFunction } from 'ajv'
|
|
5
5
|
import { validTLDs } from '../tlds.js'
|
|
6
6
|
import type { JsonSchemaIsoDateOptions, JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
|
|
7
7
|
|
|
@@ -77,33 +77,42 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
77
77
|
modifying: true,
|
|
78
78
|
schemaType: 'object',
|
|
79
79
|
errors: true,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
validate: function validate(
|
|
81
|
+
transform: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number },
|
|
82
|
+
data: string,
|
|
83
|
+
_schema,
|
|
84
|
+
ctx,
|
|
85
|
+
) {
|
|
86
|
+
if (!data) return true
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
let transformedData = data
|
|
89
|
+
|
|
90
|
+
if (transform.trim) {
|
|
91
|
+
transformedData = transformedData.trim()
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
if (
|
|
89
|
-
|
|
94
|
+
if (transform.toLowerCase) {
|
|
95
|
+
transformedData = transformedData.toLocaleLowerCase()
|
|
90
96
|
}
|
|
91
97
|
|
|
92
|
-
if (
|
|
93
|
-
|
|
98
|
+
if (transform.toUpperCase) {
|
|
99
|
+
transformedData = transformedData.toLocaleUpperCase()
|
|
94
100
|
}
|
|
95
101
|
|
|
96
|
-
if (typeof
|
|
97
|
-
|
|
102
|
+
if (typeof transform.truncate === 'number' && transform.truncate >= 0) {
|
|
103
|
+
transformedData = transformedData.slice(0, transform.truncate)
|
|
98
104
|
|
|
99
|
-
if (
|
|
100
|
-
|
|
105
|
+
if (transform.trim) {
|
|
106
|
+
transformedData = transformedData.trim()
|
|
101
107
|
}
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
// Explicit check for `undefined` because parentDataProperty can be `0` when it comes to arrays.
|
|
111
|
+
if (ctx?.parentData && typeof ctx.parentDataProperty !== 'undefined') {
|
|
112
|
+
ctx.parentData[ctx.parentDataProperty] = transformedData
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true
|
|
107
116
|
},
|
|
108
117
|
})
|
|
109
118
|
|
|
@@ -413,7 +422,7 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
413
422
|
if (!optionalValues.includes(data)) return true
|
|
414
423
|
|
|
415
424
|
if (ctx?.parentData && ctx.parentDataProperty) {
|
|
416
|
-
ctx.parentData[ctx.parentDataProperty]
|
|
425
|
+
delete ctx.parentData[ctx.parentDataProperty]
|
|
417
426
|
}
|
|
418
427
|
|
|
419
428
|
return true
|
|
@@ -70,11 +70,6 @@ export const j = {
|
|
|
70
70
|
schema: S,
|
|
71
71
|
): JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>> {
|
|
72
72
|
const builtSchema = schema.build()
|
|
73
|
-
_assert(
|
|
74
|
-
!builtSchema?.optionalField,
|
|
75
|
-
'In a StringMap schema the value cannot be `undefined`, because `undefined` is not a valid JSON Schema value.',
|
|
76
|
-
)
|
|
77
|
-
builtSchema.optionalField = undefined
|
|
78
73
|
|
|
79
74
|
return new JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>>(
|
|
80
75
|
{},
|
|
@@ -193,7 +188,14 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
193
188
|
* Same as if it would be JSON.stringified.
|
|
194
189
|
*/
|
|
195
190
|
build(): JsonSchema<IN, OUT> {
|
|
196
|
-
|
|
191
|
+
const jsonSchema = _sortObject(
|
|
192
|
+
JSON.parse(JSON.stringify(this.schema)),
|
|
193
|
+
JSON_SCHEMA_ORDER,
|
|
194
|
+
) as JsonSchema<IN, OUT>
|
|
195
|
+
|
|
196
|
+
delete jsonSchema.optionalField
|
|
197
|
+
|
|
198
|
+
return jsonSchema
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
clone(): JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
@@ -226,6 +228,8 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
226
228
|
*
|
|
227
229
|
* const schemaBad = j.string().isOfType<number>() // ❌
|
|
228
230
|
* schemaBad.build() // TypeError: property "build" does not exist on type "never"
|
|
231
|
+
*
|
|
232
|
+
* const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
|
|
229
233
|
* ```
|
|
230
234
|
*/
|
|
231
235
|
isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never {
|
|
@@ -364,9 +368,11 @@ export class JsonSchemaStringBuilder<
|
|
|
364
368
|
return this
|
|
365
369
|
}
|
|
366
370
|
|
|
367
|
-
length(
|
|
368
|
-
|
|
369
|
-
|
|
371
|
+
length(exactLength: number): this
|
|
372
|
+
length(minLength: number, maxLength: number): this
|
|
373
|
+
length(minLengthOrExactLength: number, maxLength?: number): this {
|
|
374
|
+
const maxLengthActual = maxLength ?? minLengthOrExactLength
|
|
375
|
+
return this.minLength(minLengthOrExactLength).maxLength(maxLengthActual)
|
|
370
376
|
}
|
|
371
377
|
|
|
372
378
|
email(opt?: Partial<JsonSchemaStringEmailOptions>): this {
|
|
@@ -753,12 +759,12 @@ export class JsonSchemaObjectBuilder<
|
|
|
753
759
|
const required: string[] = []
|
|
754
760
|
|
|
755
761
|
for (const [key, builder] of Object.entries(props)) {
|
|
756
|
-
const
|
|
757
|
-
if (!
|
|
762
|
+
const isOptional = (builder as JsonSchemaTerminal<any, any, any>).getSchema().optionalField
|
|
763
|
+
if (!isOptional) {
|
|
758
764
|
required.push(key)
|
|
759
|
-
} else {
|
|
760
|
-
schema.optionalField = undefined
|
|
761
765
|
}
|
|
766
|
+
|
|
767
|
+
const schema = builder.build()
|
|
762
768
|
properties[key] = schema
|
|
763
769
|
}
|
|
764
770
|
|
|
@@ -868,12 +874,12 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
868
874
|
const required: string[] = []
|
|
869
875
|
|
|
870
876
|
for (const [key, builder] of Object.entries(props)) {
|
|
871
|
-
const
|
|
872
|
-
if (!
|
|
877
|
+
const isOptional = (builder as JsonSchemaTerminal<any, any, any>).getSchema().optionalField
|
|
878
|
+
if (!isOptional) {
|
|
873
879
|
required.push(key)
|
|
874
|
-
} else {
|
|
875
|
-
schema.optionalField = undefined
|
|
876
880
|
}
|
|
881
|
+
|
|
882
|
+
const schema = builder.build()
|
|
877
883
|
properties[key] = schema
|
|
878
884
|
}
|
|
879
885
|
|
|
@@ -952,8 +958,11 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
|
|
|
952
958
|
return this
|
|
953
959
|
}
|
|
954
960
|
|
|
955
|
-
length(
|
|
956
|
-
|
|
961
|
+
length(exactLength: number): this
|
|
962
|
+
length(minItems: number, maxItems: number): this
|
|
963
|
+
length(minItemsOrExact: number, maxItems?: number): this {
|
|
964
|
+
const maxItemsActual = maxItems ?? minItemsOrExact
|
|
965
|
+
return this.minLength(minItemsOrExact).maxLength(maxItemsActual)
|
|
957
966
|
}
|
|
958
967
|
|
|
959
968
|
exactLength(length: number): this {
|