@naturalcycles/nodejs-lib 15.47.0 → 15.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/validation/ajv/getAjv.d.ts +7 -0
- package/dist/validation/ajv/getAjv.js +11 -0
- package/dist/validation/ajv/jsonSchemaBuilder.d.ts +4 -1
- package/dist/validation/ajv/jsonSchemaBuilder.js +28 -4
- package/dist/validation/ajv/jsonSchemaBuilder.util.d.ts +2 -0
- package/dist/validation/ajv/jsonSchemaBuilder.util.js +14 -0
- package/package.json +1 -1
- package/src/validation/ajv/getAjv.ts +13 -0
- package/src/validation/ajv/jsonSchemaBuilder.ts +36 -4
- package/src/validation/ajv/jsonSchemaBuilder.util.ts +14 -0
|
@@ -13,6 +13,13 @@ export declare const getAjv: any;
|
|
|
13
13
|
* and are not interested in transforming the data.
|
|
14
14
|
*/
|
|
15
15
|
export declare const getNonMutatingAjv: any;
|
|
16
|
+
/**
|
|
17
|
+
* Returns cached instance of Ajv, which is coercing data.
|
|
18
|
+
*
|
|
19
|
+
* To be used in places where we know that we are going to receive data with the wrong type,
|
|
20
|
+
* typically: request path params and request query params.
|
|
21
|
+
*/
|
|
22
|
+
export declare const getCoercingAjv: any;
|
|
16
23
|
/**
|
|
17
24
|
* Create Ajv with modified defaults.
|
|
18
25
|
*
|
|
@@ -19,6 +19,10 @@ const AJV_NON_MUTATING_OPTIONS = {
|
|
|
19
19
|
removeAdditional: false,
|
|
20
20
|
useDefaults: false,
|
|
21
21
|
};
|
|
22
|
+
const AJV_MUTATING_COERCING_OPTIONS = {
|
|
23
|
+
...AJV_OPTIONS,
|
|
24
|
+
coerceTypes: true,
|
|
25
|
+
};
|
|
22
26
|
/**
|
|
23
27
|
* Return cached instance of Ajv with default (recommended) options.
|
|
24
28
|
*
|
|
@@ -33,6 +37,13 @@ export const getAjv = _lazyValue(createAjv);
|
|
|
33
37
|
* and are not interested in transforming the data.
|
|
34
38
|
*/
|
|
35
39
|
export const getNonMutatingAjv = _lazyValue(() => createAjv(AJV_NON_MUTATING_OPTIONS));
|
|
40
|
+
/**
|
|
41
|
+
* Returns cached instance of Ajv, which is coercing data.
|
|
42
|
+
*
|
|
43
|
+
* To be used in places where we know that we are going to receive data with the wrong type,
|
|
44
|
+
* typically: request path params and request query params.
|
|
45
|
+
*/
|
|
46
|
+
export const getCoercingAjv = _lazyValue(() => createAjv(AJV_MUTATING_COERCING_OPTIONS));
|
|
36
47
|
/**
|
|
37
48
|
* Create Ajv with modified defaults.
|
|
38
49
|
*
|
|
@@ -163,6 +163,8 @@ export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends A
|
|
|
163
163
|
* Extends the current schema with `id`, `created` and `updated` according to NC DB conventions.
|
|
164
164
|
*/
|
|
165
165
|
dbEntity(): JsonSchemaObjectBuilder<any, any, Opt>;
|
|
166
|
+
minProperties(minProperties: number): this;
|
|
167
|
+
maxProperties(maxProperties: number): this;
|
|
166
168
|
}
|
|
167
169
|
export declare class JsonSchemaObjectInferringBuilder<PROPS extends Record<string, JsonSchemaAnyBuilder<any, any, any>>, Opt extends boolean = false> extends JsonSchemaAnyBuilder<Expand<{
|
|
168
170
|
[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;
|
|
@@ -208,9 +210,10 @@ export declare class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder<string
|
|
|
208
210
|
constructor();
|
|
209
211
|
}
|
|
210
212
|
export declare class JsonSchemaEnumBuilder<IN extends string | number | boolean | null, OUT extends IN = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
211
|
-
constructor(enumValues: readonly IN[], opt?: JsonBuilderRuleOpt);
|
|
213
|
+
constructor(enumValues: readonly IN[], baseType: EnumBaseType, opt?: JsonBuilderRuleOpt);
|
|
212
214
|
branded<B extends IN>(): JsonSchemaEnumBuilder<B | IN, B, Opt>;
|
|
213
215
|
}
|
|
216
|
+
type EnumBaseType = 'string' | 'number' | 'other';
|
|
214
217
|
export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
215
218
|
readonly in?: IN;
|
|
216
219
|
readonly out?: OUT;
|
|
@@ -6,7 +6,7 @@ import { _assert } from '@naturalcycles/js-lib/error';
|
|
|
6
6
|
import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object';
|
|
7
7
|
import { _objectAssign, JWT_REGEX, } from '@naturalcycles/js-lib/types';
|
|
8
8
|
import { TIMEZONES } from '../timezones.js';
|
|
9
|
-
import { JSON_SCHEMA_ORDER, mergeJsonSchemaObjects } from './jsonSchemaBuilder.util.js';
|
|
9
|
+
import { isEveryItemNumber, isEveryItemString, JSON_SCHEMA_ORDER, mergeJsonSchemaObjects, } from './jsonSchemaBuilder.util.js';
|
|
10
10
|
export const j = {
|
|
11
11
|
string() {
|
|
12
12
|
return new JsonSchemaStringBuilder();
|
|
@@ -35,20 +35,29 @@ export const j = {
|
|
|
35
35
|
},
|
|
36
36
|
enum(input, opt) {
|
|
37
37
|
let enumValues;
|
|
38
|
+
let baseType = 'other';
|
|
38
39
|
if (Array.isArray(input)) {
|
|
39
40
|
enumValues = input;
|
|
41
|
+
if (isEveryItemNumber(input)) {
|
|
42
|
+
baseType = 'number';
|
|
43
|
+
}
|
|
44
|
+
else if (isEveryItemString(input)) {
|
|
45
|
+
baseType = 'string';
|
|
46
|
+
}
|
|
40
47
|
}
|
|
41
48
|
else if (typeof input === 'object') {
|
|
42
49
|
const enumType = getEnumType(input);
|
|
43
50
|
if (enumType === 'NumberEnum') {
|
|
44
51
|
enumValues = _numberEnumValues(input);
|
|
52
|
+
baseType = 'number';
|
|
45
53
|
}
|
|
46
54
|
else if (enumType === 'StringEnum') {
|
|
47
55
|
enumValues = _stringEnumValues(input);
|
|
56
|
+
baseType = 'string';
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
_assert(enumValues, 'Unsupported enum input');
|
|
51
|
-
return new JsonSchemaEnumBuilder(enumValues, opt);
|
|
60
|
+
return new JsonSchemaEnumBuilder(enumValues, baseType, opt);
|
|
52
61
|
},
|
|
53
62
|
oneOf(items) {
|
|
54
63
|
const schemas = items.map(b => b.build());
|
|
@@ -487,6 +496,14 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
487
496
|
updated: j.number().unixTimestamp2000(),
|
|
488
497
|
});
|
|
489
498
|
}
|
|
499
|
+
minProperties(minProperties) {
|
|
500
|
+
Object.assign(this.schema, { minProperties });
|
|
501
|
+
return this;
|
|
502
|
+
}
|
|
503
|
+
maxProperties(maxProperties) {
|
|
504
|
+
Object.assign(this.schema, { maxProperties });
|
|
505
|
+
return this;
|
|
506
|
+
}
|
|
490
507
|
}
|
|
491
508
|
export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
492
509
|
constructor(props) {
|
|
@@ -592,8 +609,15 @@ export class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder {
|
|
|
592
609
|
}
|
|
593
610
|
}
|
|
594
611
|
export class JsonSchemaEnumBuilder extends JsonSchemaAnyBuilder {
|
|
595
|
-
constructor(enumValues, opt) {
|
|
596
|
-
|
|
612
|
+
constructor(enumValues, baseType, opt) {
|
|
613
|
+
const jsonSchema = { enum: enumValues };
|
|
614
|
+
// Specifying the base type helps in cases when we ask Ajv to coerce the types.
|
|
615
|
+
// Having only the `enum` in the schema does not trigger a coercion in Ajv.
|
|
616
|
+
if (baseType === 'string')
|
|
617
|
+
jsonSchema.type = 'string';
|
|
618
|
+
if (baseType === 'number')
|
|
619
|
+
jsonSchema.type = 'number';
|
|
620
|
+
super(jsonSchema);
|
|
597
621
|
if (opt?.name)
|
|
598
622
|
this.setErrorMessage('pattern', `is not a valid ${opt.name}`);
|
|
599
623
|
if (opt?.msg)
|
|
@@ -7,3 +7,5 @@ export declare const JSON_SCHEMA_ORDER: string[];
|
|
|
7
7
|
* API similar to Object.assign(s1, s2)
|
|
8
8
|
*/
|
|
9
9
|
export declare function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObject>(schema1: JsonSchema<T1>, schema2: JsonSchema<T2>): JsonSchema<T1 & T2>;
|
|
10
|
+
export declare function isEveryItemString(arr: any[]): boolean;
|
|
11
|
+
export declare function isEveryItemNumber(arr: any[]): boolean;
|
|
@@ -63,3 +63,17 @@ export function mergeJsonSchemaObjects(schema1, schema2) {
|
|
|
63
63
|
// `additionalProperties` remains the same
|
|
64
64
|
return _filterNullishValues(s1, { mutate: true });
|
|
65
65
|
}
|
|
66
|
+
export function isEveryItemString(arr) {
|
|
67
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
68
|
+
if (typeof arr[i] !== 'string')
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
export function isEveryItemNumber(arr) {
|
|
74
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
75
|
+
if (typeof arr[i] !== 'number')
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -24,6 +24,11 @@ const AJV_NON_MUTATING_OPTIONS: Options = {
|
|
|
24
24
|
useDefaults: false,
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
const AJV_MUTATING_COERCING_OPTIONS: Options = {
|
|
28
|
+
...AJV_OPTIONS,
|
|
29
|
+
coerceTypes: true,
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
/**
|
|
28
33
|
* Return cached instance of Ajv with default (recommended) options.
|
|
29
34
|
*
|
|
@@ -40,6 +45,14 @@ export const getAjv = _lazyValue(createAjv)
|
|
|
40
45
|
*/
|
|
41
46
|
export const getNonMutatingAjv = _lazyValue(() => createAjv(AJV_NON_MUTATING_OPTIONS))
|
|
42
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Returns cached instance of Ajv, which is coercing data.
|
|
50
|
+
*
|
|
51
|
+
* To be used in places where we know that we are going to receive data with the wrong type,
|
|
52
|
+
* typically: request path params and request query params.
|
|
53
|
+
*/
|
|
54
|
+
export const getCoercingAjv = _lazyValue(() => createAjv(AJV_MUTATING_COERCING_OPTIONS))
|
|
55
|
+
|
|
43
56
|
/**
|
|
44
57
|
* Create Ajv with modified defaults.
|
|
45
58
|
*
|
|
@@ -27,7 +27,12 @@ import {
|
|
|
27
27
|
type UnixTimestampMillis,
|
|
28
28
|
} from '@naturalcycles/js-lib/types'
|
|
29
29
|
import { TIMEZONES } from '../timezones.js'
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
isEveryItemNumber,
|
|
32
|
+
isEveryItemString,
|
|
33
|
+
JSON_SCHEMA_ORDER,
|
|
34
|
+
mergeJsonSchemaObjects,
|
|
35
|
+
} from './jsonSchemaBuilder.util.js'
|
|
31
36
|
|
|
32
37
|
export const j = {
|
|
33
38
|
string(): JsonSchemaStringBuilder<string, string, false> {
|
|
@@ -79,20 +84,28 @@ export const j = {
|
|
|
79
84
|
: never
|
|
80
85
|
> {
|
|
81
86
|
let enumValues: readonly (string | number | boolean | null)[] | undefined
|
|
87
|
+
let baseType: EnumBaseType = 'other'
|
|
82
88
|
|
|
83
89
|
if (Array.isArray(input)) {
|
|
84
90
|
enumValues = input
|
|
91
|
+
if (isEveryItemNumber(input)) {
|
|
92
|
+
baseType = 'number'
|
|
93
|
+
} else if (isEveryItemString(input)) {
|
|
94
|
+
baseType = 'string'
|
|
95
|
+
}
|
|
85
96
|
} else if (typeof input === 'object') {
|
|
86
97
|
const enumType = getEnumType(input)
|
|
87
98
|
if (enumType === 'NumberEnum') {
|
|
88
99
|
enumValues = _numberEnumValues(input as NumberEnum)
|
|
100
|
+
baseType = 'number'
|
|
89
101
|
} else if (enumType === 'StringEnum') {
|
|
90
102
|
enumValues = _stringEnumValues(input as StringEnum)
|
|
103
|
+
baseType = 'string'
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
_assert(enumValues, 'Unsupported enum input')
|
|
95
|
-
return new JsonSchemaEnumBuilder(enumValues as any, opt)
|
|
108
|
+
return new JsonSchemaEnumBuilder(enumValues as any, baseType, opt)
|
|
96
109
|
},
|
|
97
110
|
|
|
98
111
|
oneOf<
|
|
@@ -659,6 +672,16 @@ export class JsonSchemaObjectBuilder<
|
|
|
659
672
|
updated: j.number().unixTimestamp2000(),
|
|
660
673
|
})
|
|
661
674
|
}
|
|
675
|
+
|
|
676
|
+
minProperties(minProperties: number): this {
|
|
677
|
+
Object.assign(this.schema, { minProperties })
|
|
678
|
+
return this
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
maxProperties(maxProperties: number): this {
|
|
682
|
+
Object.assign(this.schema, { maxProperties })
|
|
683
|
+
return this
|
|
684
|
+
}
|
|
662
685
|
}
|
|
663
686
|
|
|
664
687
|
export class JsonSchemaObjectInferringBuilder<
|
|
@@ -851,8 +874,15 @@ export class JsonSchemaEnumBuilder<
|
|
|
851
874
|
OUT extends IN = IN,
|
|
852
875
|
Opt extends boolean = false,
|
|
853
876
|
> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
854
|
-
constructor(enumValues: readonly IN[], opt?: JsonBuilderRuleOpt) {
|
|
855
|
-
|
|
877
|
+
constructor(enumValues: readonly IN[], baseType: EnumBaseType, opt?: JsonBuilderRuleOpt) {
|
|
878
|
+
const jsonSchema: JsonSchema = { enum: enumValues }
|
|
879
|
+
// Specifying the base type helps in cases when we ask Ajv to coerce the types.
|
|
880
|
+
// Having only the `enum` in the schema does not trigger a coercion in Ajv.
|
|
881
|
+
if (baseType === 'string') jsonSchema.type = 'string'
|
|
882
|
+
if (baseType === 'number') jsonSchema.type = 'number'
|
|
883
|
+
|
|
884
|
+
super(jsonSchema)
|
|
885
|
+
|
|
856
886
|
if (opt?.name) this.setErrorMessage('pattern', `is not a valid ${opt.name}`)
|
|
857
887
|
if (opt?.msg) this.setErrorMessage('enum', opt.msg)
|
|
858
888
|
}
|
|
@@ -862,6 +892,8 @@ export class JsonSchemaEnumBuilder<
|
|
|
862
892
|
}
|
|
863
893
|
}
|
|
864
894
|
|
|
895
|
+
type EnumBaseType = 'string' | 'number' | 'other'
|
|
896
|
+
|
|
865
897
|
export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
866
898
|
readonly in?: IN
|
|
867
899
|
readonly out?: OUT
|
|
@@ -76,3 +76,17 @@ export function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObjec
|
|
|
76
76
|
|
|
77
77
|
return _filterNullishValues(s1, { mutate: true })
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
export function isEveryItemString(arr: any[]): boolean {
|
|
81
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
82
|
+
if (typeof arr[i] !== 'string') return false
|
|
83
|
+
}
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isEveryItemNumber(arr: any[]): boolean {
|
|
88
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
89
|
+
if (typeof arr[i] !== 'number') return false
|
|
90
|
+
}
|
|
91
|
+
return true
|
|
92
|
+
}
|