@naturalcycles/nodejs-lib 15.57.0 → 15.58.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.
|
@@ -374,6 +374,27 @@ export function createAjv(opt) {
|
|
|
374
374
|
return true;
|
|
375
375
|
},
|
|
376
376
|
});
|
|
377
|
+
ajv.addKeyword({
|
|
378
|
+
keyword: 'keySchema',
|
|
379
|
+
type: 'object',
|
|
380
|
+
modifying: true,
|
|
381
|
+
errors: false,
|
|
382
|
+
schemaType: 'object',
|
|
383
|
+
compile(innerSchema, _parentSchema, _it) {
|
|
384
|
+
const isValidKeyFn = ajv.compile(innerSchema);
|
|
385
|
+
function validate(data, _ctx) {
|
|
386
|
+
if (typeof data !== 'object' || data === null)
|
|
387
|
+
return true;
|
|
388
|
+
for (const key of Object.keys(data)) {
|
|
389
|
+
if (!isValidKeyFn(key)) {
|
|
390
|
+
delete data[key];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
return validate;
|
|
396
|
+
},
|
|
397
|
+
});
|
|
377
398
|
return ajv;
|
|
378
399
|
}
|
|
379
400
|
const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
@@ -9,7 +9,40 @@ export declare const j: {
|
|
|
9
9
|
infer: typeof objectInfer;
|
|
10
10
|
any(): JsonSchemaObjectBuilder<AnyObject, AnyObject, false>;
|
|
11
11
|
stringMap<S extends JsonSchemaTerminal<any, any, any>>(schema: S): JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>>;
|
|
12
|
+
/**
|
|
13
|
+
* @experimental Look around, maybe you find a rule that is better for your use-case.
|
|
14
|
+
*
|
|
15
|
+
* For Record<K, V> type of validations.
|
|
16
|
+
* ```ts
|
|
17
|
+
* const schema = j.object
|
|
18
|
+
* .record(
|
|
19
|
+
* j
|
|
20
|
+
* .string()
|
|
21
|
+
* .regex(/^\d{3,4}$/)
|
|
22
|
+
* .branded<B>(),
|
|
23
|
+
* j.number().nullable(),
|
|
24
|
+
* )
|
|
25
|
+
* .isOfType<Record<B, number | null>>()
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* When the keys of the Record are values from an Enum, prefer `j.object.withEnumKeys`!
|
|
29
|
+
*
|
|
30
|
+
* Non-matching keys will be stripped from the object, i.e. they will not cause an error.
|
|
31
|
+
*
|
|
32
|
+
* Caveat: This rule first validates values of every properties of the object, and only then validates the keys.
|
|
33
|
+
* A consequence of that is that the validation will throw when there is an unexpected property with a value not matching the value schema.
|
|
34
|
+
*/
|
|
35
|
+
record: typeof record;
|
|
36
|
+
/**
|
|
37
|
+
* For Record<ENUM, V> type of validations.
|
|
38
|
+
*
|
|
39
|
+
* When the keys of the Record are values from an Enum,
|
|
40
|
+
* this helper is more performant and behaves in a more conventional manner than `j.object.record` would.
|
|
41
|
+
*
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
12
44
|
withEnumKeys: typeof withEnumKeys;
|
|
45
|
+
withRegexKeys: typeof withRegexKeys;
|
|
13
46
|
};
|
|
14
47
|
array<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaArrayBuilder<IN, OUT, Opt>;
|
|
15
48
|
set<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaSet2Builder<IN, OUT, Opt>;
|
|
@@ -49,6 +82,8 @@ export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTermin
|
|
|
49
82
|
*
|
|
50
83
|
* const schemaBad = j.string().isOfType<number>() // ❌
|
|
51
84
|
* schemaBad.build() // TypeError: property "build" does not exist on type "never"
|
|
85
|
+
*
|
|
86
|
+
* const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
|
|
52
87
|
* ```
|
|
53
88
|
*/
|
|
54
89
|
isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never;
|
|
@@ -203,6 +238,7 @@ export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends A
|
|
|
203
238
|
interface JsonSchemaObjectBuilderOpts {
|
|
204
239
|
hasIsOfTypeCheck?: false;
|
|
205
240
|
patternProperties?: StringMap<JsonSchema<any, any>>;
|
|
241
|
+
keySchema?: JsonSchema;
|
|
206
242
|
}
|
|
207
243
|
export declare class JsonSchemaObjectInferringBuilder<PROPS extends Record<string, JsonSchemaAnyBuilder<any, any, any>>, Opt extends boolean = false> extends JsonSchemaAnyBuilder<Expand<{
|
|
208
244
|
[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;
|
|
@@ -314,6 +350,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
314
350
|
};
|
|
315
351
|
errorMessages?: StringMap<string>;
|
|
316
352
|
optionalValues?: (string | number | boolean)[];
|
|
353
|
+
keySchema?: JsonSchema;
|
|
317
354
|
}
|
|
318
355
|
declare function object(props: AnyObject): never;
|
|
319
356
|
declare function object<IN extends AnyObject>(props: {
|
|
@@ -336,6 +373,8 @@ declare function objectDbEntity<IN extends BaseDBEntity & AnyObject, EXTRA_KEYS
|
|
|
336
373
|
} : {
|
|
337
374
|
updated: BuilderFor<IN['updated']>;
|
|
338
375
|
})): JsonSchemaObjectBuilder<IN, IN, false>;
|
|
376
|
+
declare function record<KS extends JsonSchemaAnyBuilder<any, any, any>, VS extends JsonSchemaAnyBuilder<any, any, any>, Opt extends boolean = SchemaOpt<VS>>(keySchema: KS, valueSchema: VS): JsonSchemaObjectBuilder<Opt extends true ? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>> : Record<SchemaIn<KS>, SchemaIn<VS>>, Opt extends true ? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>> : Record<SchemaOut<KS>, SchemaOut<VS>>, false>;
|
|
377
|
+
declare function withRegexKeys<S extends JsonSchemaAnyBuilder<any, any, any>, Opt extends boolean = SchemaOpt<S>>(keyRegex: RegExp | string, schema: S): JsonSchemaObjectBuilder<Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>, Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>, false>;
|
|
339
378
|
/**
|
|
340
379
|
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
341
380
|
*/
|
|
@@ -376,5 +415,5 @@ interface JsonBuilderRuleOpt {
|
|
|
376
415
|
type EnumKeyUnion<T> = T extends readonly (infer U)[] ? U : T extends StringEnum | NumberEnum ? T[keyof T] : never;
|
|
377
416
|
type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
378
417
|
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
|
|
379
|
-
type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? Opt : false;
|
|
418
|
+
type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? (Opt extends true ? true : false) : false;
|
|
380
419
|
export {};
|
|
@@ -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: {
|
|
@@ -35,7 +33,40 @@ export const j = {
|
|
|
35
33
|
},
|
|
36
34
|
});
|
|
37
35
|
},
|
|
36
|
+
/**
|
|
37
|
+
* @experimental Look around, maybe you find a rule that is better for your use-case.
|
|
38
|
+
*
|
|
39
|
+
* For Record<K, V> type of validations.
|
|
40
|
+
* ```ts
|
|
41
|
+
* const schema = j.object
|
|
42
|
+
* .record(
|
|
43
|
+
* j
|
|
44
|
+
* .string()
|
|
45
|
+
* .regex(/^\d{3,4}$/)
|
|
46
|
+
* .branded<B>(),
|
|
47
|
+
* j.number().nullable(),
|
|
48
|
+
* )
|
|
49
|
+
* .isOfType<Record<B, number | null>>()
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* When the keys of the Record are values from an Enum, prefer `j.object.withEnumKeys`!
|
|
53
|
+
*
|
|
54
|
+
* Non-matching keys will be stripped from the object, i.e. they will not cause an error.
|
|
55
|
+
*
|
|
56
|
+
* Caveat: This rule first validates values of every properties of the object, and only then validates the keys.
|
|
57
|
+
* A consequence of that is that the validation will throw when there is an unexpected property with a value not matching the value schema.
|
|
58
|
+
*/
|
|
59
|
+
record,
|
|
60
|
+
/**
|
|
61
|
+
* For Record<ENUM, V> type of validations.
|
|
62
|
+
*
|
|
63
|
+
* When the keys of the Record are values from an Enum,
|
|
64
|
+
* this helper is more performant and behaves in a more conventional manner than `j.object.record` would.
|
|
65
|
+
*
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
38
68
|
withEnumKeys,
|
|
69
|
+
withRegexKeys,
|
|
39
70
|
}),
|
|
40
71
|
array(itemSchema) {
|
|
41
72
|
return new JsonSchemaArrayBuilder(itemSchema);
|
|
@@ -111,7 +142,9 @@ export class JsonSchemaTerminal {
|
|
|
111
142
|
* Same as if it would be JSON.stringified.
|
|
112
143
|
*/
|
|
113
144
|
build() {
|
|
114
|
-
|
|
145
|
+
const jsonSchema = _sortObject(JSON.parse(JSON.stringify(this.schema)), JSON_SCHEMA_ORDER);
|
|
146
|
+
delete jsonSchema.optionalField;
|
|
147
|
+
return jsonSchema;
|
|
115
148
|
}
|
|
116
149
|
clone() {
|
|
117
150
|
return new JsonSchemaAnyBuilder(_deepCopy(this.schema));
|
|
@@ -140,6 +173,8 @@ export class JsonSchemaAnyBuilder extends JsonSchemaTerminal {
|
|
|
140
173
|
*
|
|
141
174
|
* const schemaBad = j.string().isOfType<number>() // ❌
|
|
142
175
|
* schemaBad.build() // TypeError: property "build" does not exist on type "never"
|
|
176
|
+
*
|
|
177
|
+
* const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
|
|
143
178
|
* ```
|
|
144
179
|
*/
|
|
145
180
|
isOfType() {
|
|
@@ -525,6 +560,7 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
525
560
|
additionalProperties: false,
|
|
526
561
|
hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
|
|
527
562
|
patternProperties: opt?.patternProperties ?? undefined,
|
|
563
|
+
keySchema: opt?.keySchema ?? undefined,
|
|
528
564
|
});
|
|
529
565
|
if (props)
|
|
530
566
|
this.addProperties(props);
|
|
@@ -533,13 +569,11 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
533
569
|
const properties = {};
|
|
534
570
|
const required = [];
|
|
535
571
|
for (const [key, builder] of Object.entries(props)) {
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
572
|
+
const isOptional = builder.getSchema().optionalField;
|
|
573
|
+
if (!isOptional) {
|
|
538
574
|
required.push(key);
|
|
539
575
|
}
|
|
540
|
-
|
|
541
|
-
schema.optionalField = undefined;
|
|
542
|
-
}
|
|
576
|
+
const schema = builder.build();
|
|
543
577
|
properties[key] = schema;
|
|
544
578
|
}
|
|
545
579
|
this.schema.properties = properties;
|
|
@@ -595,13 +629,11 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
|
595
629
|
const properties = {};
|
|
596
630
|
const required = [];
|
|
597
631
|
for (const [key, builder] of Object.entries(props)) {
|
|
598
|
-
const
|
|
599
|
-
if (!
|
|
632
|
+
const isOptional = builder.getSchema().optionalField;
|
|
633
|
+
if (!isOptional) {
|
|
600
634
|
required.push(key);
|
|
601
635
|
}
|
|
602
|
-
|
|
603
|
-
schema.optionalField = undefined;
|
|
604
|
-
}
|
|
636
|
+
const schema = builder.build();
|
|
605
637
|
properties[key] = schema;
|
|
606
638
|
}
|
|
607
639
|
this.schema.properties = properties;
|
|
@@ -717,6 +749,27 @@ function objectDbEntity(props) {
|
|
|
717
749
|
...props,
|
|
718
750
|
});
|
|
719
751
|
}
|
|
752
|
+
function record(keySchema, valueSchema) {
|
|
753
|
+
const keyJsonSchema = keySchema.build();
|
|
754
|
+
const valueJsonSchema = valueSchema.build();
|
|
755
|
+
return new JsonSchemaObjectBuilder([], {
|
|
756
|
+
hasIsOfTypeCheck: false,
|
|
757
|
+
keySchema: keyJsonSchema,
|
|
758
|
+
patternProperties: {
|
|
759
|
+
['^.*$']: valueJsonSchema,
|
|
760
|
+
},
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
function withRegexKeys(keyRegex, schema) {
|
|
764
|
+
const pattern = keyRegex instanceof RegExp ? keyRegex.source : keyRegex;
|
|
765
|
+
const jsonSchema = schema.build();
|
|
766
|
+
return new JsonSchemaObjectBuilder([], {
|
|
767
|
+
hasIsOfTypeCheck: false,
|
|
768
|
+
patternProperties: {
|
|
769
|
+
[pattern]: jsonSchema,
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
}
|
|
720
773
|
/**
|
|
721
774
|
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
722
775
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +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 type { AnyObject } from '@naturalcycles/js-lib/types'
|
|
4
5
|
import { Ajv, type Options, type ValidateFunction } from 'ajv'
|
|
5
6
|
import { validTLDs } from '../tlds.js'
|
|
6
7
|
import type { JsonSchemaIsoDateOptions, JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
|
|
@@ -429,6 +430,31 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
429
430
|
},
|
|
430
431
|
})
|
|
431
432
|
|
|
433
|
+
ajv.addKeyword({
|
|
434
|
+
keyword: 'keySchema',
|
|
435
|
+
type: 'object',
|
|
436
|
+
modifying: true,
|
|
437
|
+
errors: false,
|
|
438
|
+
schemaType: 'object',
|
|
439
|
+
compile(innerSchema, _parentSchema, _it) {
|
|
440
|
+
const isValidKeyFn: ValidateFunction = ajv.compile(innerSchema)
|
|
441
|
+
|
|
442
|
+
function validate(data: AnyObject, _ctx: any): boolean {
|
|
443
|
+
if (typeof data !== 'object' || data === null) return true
|
|
444
|
+
|
|
445
|
+
for (const key of Object.keys(data)) {
|
|
446
|
+
if (!isValidKeyFn(key)) {
|
|
447
|
+
delete data[key]
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return validate
|
|
455
|
+
},
|
|
456
|
+
})
|
|
457
|
+
|
|
432
458
|
return ajv
|
|
433
459
|
}
|
|
434
460
|
|
|
@@ -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
|
{},
|
|
@@ -87,7 +82,41 @@ export const j = {
|
|
|
87
82
|
)
|
|
88
83
|
},
|
|
89
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @experimental Look around, maybe you find a rule that is better for your use-case.
|
|
87
|
+
*
|
|
88
|
+
* For Record<K, V> type of validations.
|
|
89
|
+
* ```ts
|
|
90
|
+
* const schema = j.object
|
|
91
|
+
* .record(
|
|
92
|
+
* j
|
|
93
|
+
* .string()
|
|
94
|
+
* .regex(/^\d{3,4}$/)
|
|
95
|
+
* .branded<B>(),
|
|
96
|
+
* j.number().nullable(),
|
|
97
|
+
* )
|
|
98
|
+
* .isOfType<Record<B, number | null>>()
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* When the keys of the Record are values from an Enum, prefer `j.object.withEnumKeys`!
|
|
102
|
+
*
|
|
103
|
+
* Non-matching keys will be stripped from the object, i.e. they will not cause an error.
|
|
104
|
+
*
|
|
105
|
+
* Caveat: This rule first validates values of every properties of the object, and only then validates the keys.
|
|
106
|
+
* A consequence of that is that the validation will throw when there is an unexpected property with a value not matching the value schema.
|
|
107
|
+
*/
|
|
108
|
+
record,
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* For Record<ENUM, V> type of validations.
|
|
112
|
+
*
|
|
113
|
+
* When the keys of the Record are values from an Enum,
|
|
114
|
+
* this helper is more performant and behaves in a more conventional manner than `j.object.record` would.
|
|
115
|
+
*
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
90
118
|
withEnumKeys,
|
|
119
|
+
withRegexKeys,
|
|
91
120
|
}),
|
|
92
121
|
|
|
93
122
|
array<IN, OUT, Opt>(
|
|
@@ -193,7 +222,14 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
193
222
|
* Same as if it would be JSON.stringified.
|
|
194
223
|
*/
|
|
195
224
|
build(): JsonSchema<IN, OUT> {
|
|
196
|
-
|
|
225
|
+
const jsonSchema = _sortObject(
|
|
226
|
+
JSON.parse(JSON.stringify(this.schema)),
|
|
227
|
+
JSON_SCHEMA_ORDER,
|
|
228
|
+
) as JsonSchema<IN, OUT>
|
|
229
|
+
|
|
230
|
+
delete jsonSchema.optionalField
|
|
231
|
+
|
|
232
|
+
return jsonSchema
|
|
197
233
|
}
|
|
198
234
|
|
|
199
235
|
clone(): JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
@@ -226,6 +262,8 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
226
262
|
*
|
|
227
263
|
* const schemaBad = j.string().isOfType<number>() // ❌
|
|
228
264
|
* schemaBad.build() // TypeError: property "build" does not exist on type "never"
|
|
265
|
+
*
|
|
266
|
+
* const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
|
|
229
267
|
* ```
|
|
230
268
|
*/
|
|
231
269
|
isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never {
|
|
@@ -745,6 +783,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
745
783
|
additionalProperties: false,
|
|
746
784
|
hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
|
|
747
785
|
patternProperties: opt?.patternProperties ?? undefined,
|
|
786
|
+
keySchema: opt?.keySchema ?? undefined,
|
|
748
787
|
})
|
|
749
788
|
|
|
750
789
|
if (props) this.addProperties(props)
|
|
@@ -755,12 +794,12 @@ export class JsonSchemaObjectBuilder<
|
|
|
755
794
|
const required: string[] = []
|
|
756
795
|
|
|
757
796
|
for (const [key, builder] of Object.entries(props)) {
|
|
758
|
-
const
|
|
759
|
-
if (!
|
|
797
|
+
const isOptional = (builder as JsonSchemaTerminal<any, any, any>).getSchema().optionalField
|
|
798
|
+
if (!isOptional) {
|
|
760
799
|
required.push(key)
|
|
761
|
-
} else {
|
|
762
|
-
schema.optionalField = undefined
|
|
763
800
|
}
|
|
801
|
+
|
|
802
|
+
const schema = builder.build()
|
|
764
803
|
properties[key] = schema
|
|
765
804
|
}
|
|
766
805
|
|
|
@@ -816,6 +855,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
816
855
|
interface JsonSchemaObjectBuilderOpts {
|
|
817
856
|
hasIsOfTypeCheck?: false
|
|
818
857
|
patternProperties?: StringMap<JsonSchema<any, any>>
|
|
858
|
+
keySchema?: JsonSchema
|
|
819
859
|
}
|
|
820
860
|
|
|
821
861
|
export class JsonSchemaObjectInferringBuilder<
|
|
@@ -870,12 +910,12 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
870
910
|
const required: string[] = []
|
|
871
911
|
|
|
872
912
|
for (const [key, builder] of Object.entries(props)) {
|
|
873
|
-
const
|
|
874
|
-
if (!
|
|
913
|
+
const isOptional = (builder as JsonSchemaTerminal<any, any, any>).getSchema().optionalField
|
|
914
|
+
if (!isOptional) {
|
|
875
915
|
required.push(key)
|
|
876
|
-
} else {
|
|
877
|
-
schema.optionalField = undefined
|
|
878
916
|
}
|
|
917
|
+
|
|
918
|
+
const schema = builder.build()
|
|
879
919
|
properties[key] = schema
|
|
880
920
|
}
|
|
881
921
|
|
|
@@ -1103,6 +1143,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1103
1143
|
transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
|
|
1104
1144
|
errorMessages?: StringMap<string>
|
|
1105
1145
|
optionalValues?: (string | number | boolean)[]
|
|
1146
|
+
keySchema?: JsonSchema
|
|
1106
1147
|
}
|
|
1107
1148
|
|
|
1108
1149
|
function object(props: AnyObject): never
|
|
@@ -1152,6 +1193,68 @@ function objectDbEntity(props: AnyObject): any {
|
|
|
1152
1193
|
})
|
|
1153
1194
|
}
|
|
1154
1195
|
|
|
1196
|
+
function record<
|
|
1197
|
+
KS extends JsonSchemaAnyBuilder<any, any, any>,
|
|
1198
|
+
VS extends JsonSchemaAnyBuilder<any, any, any>,
|
|
1199
|
+
Opt extends boolean = SchemaOpt<VS>,
|
|
1200
|
+
>(
|
|
1201
|
+
keySchema: KS,
|
|
1202
|
+
valueSchema: VS,
|
|
1203
|
+
): JsonSchemaObjectBuilder<
|
|
1204
|
+
Opt extends true
|
|
1205
|
+
? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>>
|
|
1206
|
+
: Record<SchemaIn<KS>, SchemaIn<VS>>,
|
|
1207
|
+
Opt extends true
|
|
1208
|
+
? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>>
|
|
1209
|
+
: Record<SchemaOut<KS>, SchemaOut<VS>>,
|
|
1210
|
+
false
|
|
1211
|
+
> {
|
|
1212
|
+
const keyJsonSchema = keySchema.build()
|
|
1213
|
+
const valueJsonSchema = valueSchema.build()
|
|
1214
|
+
|
|
1215
|
+
return new JsonSchemaObjectBuilder<
|
|
1216
|
+
Opt extends true
|
|
1217
|
+
? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>>
|
|
1218
|
+
: Record<SchemaIn<KS>, SchemaIn<VS>>,
|
|
1219
|
+
Opt extends true
|
|
1220
|
+
? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>>
|
|
1221
|
+
: Record<SchemaOut<KS>, SchemaOut<VS>>,
|
|
1222
|
+
false
|
|
1223
|
+
>([], {
|
|
1224
|
+
hasIsOfTypeCheck: false,
|
|
1225
|
+
keySchema: keyJsonSchema,
|
|
1226
|
+
patternProperties: {
|
|
1227
|
+
['^.*$']: valueJsonSchema,
|
|
1228
|
+
},
|
|
1229
|
+
})
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function withRegexKeys<
|
|
1233
|
+
S extends JsonSchemaAnyBuilder<any, any, any>,
|
|
1234
|
+
Opt extends boolean = SchemaOpt<S>,
|
|
1235
|
+
>(
|
|
1236
|
+
keyRegex: RegExp | string,
|
|
1237
|
+
schema: S,
|
|
1238
|
+
): JsonSchemaObjectBuilder<
|
|
1239
|
+
Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>,
|
|
1240
|
+
Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>,
|
|
1241
|
+
false
|
|
1242
|
+
> {
|
|
1243
|
+
const pattern = keyRegex instanceof RegExp ? keyRegex.source : keyRegex
|
|
1244
|
+
const jsonSchema = schema.build()
|
|
1245
|
+
|
|
1246
|
+
return new JsonSchemaObjectBuilder<
|
|
1247
|
+
Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>,
|
|
1248
|
+
Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>,
|
|
1249
|
+
false
|
|
1250
|
+
>([], {
|
|
1251
|
+
hasIsOfTypeCheck: false,
|
|
1252
|
+
patternProperties: {
|
|
1253
|
+
[pattern]: jsonSchema,
|
|
1254
|
+
},
|
|
1255
|
+
})
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1155
1258
|
/**
|
|
1156
1259
|
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
1157
1260
|
*/
|
|
@@ -1241,4 +1344,5 @@ type EnumKeyUnion<T> =
|
|
|
1241
1344
|
|
|
1242
1345
|
type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1243
1346
|
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1244
|
-
type SchemaOpt<S> =
|
|
1347
|
+
type SchemaOpt<S> =
|
|
1348
|
+
S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? (Opt extends true ? true : false) : false
|