@naturalcycles/nodejs-lib 15.54.0 → 15.56.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.
@@ -59,6 +59,12 @@ export class AjvSchema {
59
59
  else {
60
60
  jsonSchema = schema;
61
61
  }
62
+ // This is our own helper which marks a schema as optional
63
+ // in case it is going to be used in an object schema,
64
+ // where we need to mark the given property as not-required.
65
+ // But once all compilation is done, the presence of this field
66
+ // really upsets Ajv.
67
+ delete jsonSchema.optionalField;
62
68
  const ajvSchema = new AjvSchema(jsonSchema, cfg);
63
69
  AjvSchema.cacheAjvSchema(schema, ajvSchema);
64
70
  return ajvSchema;
@@ -91,7 +91,7 @@ export function createAjv(opt) {
91
91
  });
92
92
  ajv.addKeyword({
93
93
  keyword: 'instanceof',
94
- modifying: true,
94
+ modifying: false,
95
95
  schemaType: 'string',
96
96
  validate(instanceOf, data, _schema, _ctx) {
97
97
  if (typeof data !== 'object')
@@ -354,6 +354,23 @@ export function createAjv(opt) {
354
354
  keyword: 'hasIsOfTypeCheck',
355
355
  schemaType: 'boolean',
356
356
  });
357
+ ajv.addKeyword({
358
+ keyword: 'optionalValues',
359
+ type: ['string', 'number', 'boolean'],
360
+ modifying: true,
361
+ errors: false,
362
+ schemaType: 'array',
363
+ validate: function validate(optionalValues, data, _schema, ctx) {
364
+ if (!optionalValues)
365
+ return true;
366
+ if (!optionalValues.includes(data))
367
+ return true;
368
+ if (ctx?.parentData && ctx.parentDataProperty) {
369
+ ctx.parentData[ctx.parentDataProperty] = undefined;
370
+ }
371
+ return true;
372
+ },
373
+ });
357
374
  return ajv;
358
375
  }
359
376
  const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
@@ -9,16 +9,16 @@ 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
- * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
14
- */
15
- withEnumKeys<const T extends readonly (string | number)[] | StringEnum | NumberEnum, S extends JsonSchemaTerminal<any, any, any>, K extends string | number = EnumKeyUnion<T>>(keys: T, schema: S): JsonSchemaObjectBuilder<Record<K, SchemaIn<S>>, Record<K, SchemaOut<S>>>;
12
+ withEnumKeys: typeof withEnumKeys;
16
13
  };
17
14
  array<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaArrayBuilder<IN, OUT, Opt>;
18
15
  set<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaSet2Builder<IN, OUT, Opt>;
19
16
  buffer(): JsonSchemaBufferBuilder;
20
17
  enum<const T extends readonly (string | number | boolean | null)[] | StringEnum | NumberEnum>(input: T, opt?: JsonBuilderRuleOpt): JsonSchemaEnumBuilder<T extends readonly (infer U)[] ? U : T extends StringEnum ? T[keyof T] : T extends NumberEnum ? T[keyof T] : never>;
21
18
  oneOf<B extends readonly JsonSchemaAnyBuilder<any, any, boolean>[], IN = BuilderInUnion<B>, OUT = BuilderOutUnion<B>>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false>;
19
+ and(): {
20
+ silentBob: () => never;
21
+ };
22
22
  };
23
23
  export declare class JsonSchemaTerminal<IN, OUT, Opt> {
24
24
  protected schema: JsonSchema;
@@ -73,8 +73,15 @@ export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTermin
73
73
  */
74
74
  final(): JsonSchemaTerminal<IN, OUT, Opt>;
75
75
  }
76
- export declare class JsonSchemaStringBuilder<IN extends string = string, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
76
+ export declare class JsonSchemaStringBuilder<IN extends string | undefined = string, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
77
77
  constructor();
78
+ /**
79
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
80
+ *
81
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
82
+ * due to how mutability works in Ajv.
83
+ */
84
+ optional(optionalValues?: string[]): JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true>;
78
85
  regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this;
79
86
  pattern(pattern: string, opt?: JsonBuilderRuleOpt): this;
80
87
  minLength(minLength: number): this;
@@ -134,8 +141,15 @@ export interface JsonSchemaIsoDateOptions {
134
141
  after?: string;
135
142
  sameOrAfter?: string;
136
143
  }
137
- export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
144
+ export declare class JsonSchemaNumberBuilder<IN extends number | undefined = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
138
145
  constructor();
146
+ /**
147
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
148
+ *
149
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
150
+ * due to how mutability works in Ajv.
151
+ */
152
+ optional(optionalValues?: number[]): JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true>;
139
153
  integer(): this;
140
154
  branded<B extends number>(): JsonSchemaNumberBuilder<B, B, Opt>;
141
155
  multipleOf(multipleOf: number): this;
@@ -160,8 +174,15 @@ export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = I
160
174
  utcOffset(): this;
161
175
  utcOffsetHour(): this;
162
176
  }
163
- export declare class JsonSchemaBooleanBuilder<IN extends boolean = boolean, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
177
+ export declare class JsonSchemaBooleanBuilder<IN extends boolean | undefined = boolean, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
164
178
  constructor();
179
+ /**
180
+ * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
181
+ *
182
+ * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
183
+ * due to how mutability works in Ajv.
184
+ */
185
+ optional(optionalValue?: boolean): JsonSchemaBooleanBuilder<IN | undefined, OUT | undefined, true>;
165
186
  }
166
187
  export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends AnyObject, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
167
188
  constructor(props?: AnyObject, opt?: JsonSchemaObjectBuilderOpts);
@@ -276,6 +297,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
276
297
  maxItems?: number;
277
298
  uniqueItems?: boolean;
278
299
  enum?: any;
300
+ hasIsOfTypeCheck?: boolean;
279
301
  email?: JsonSchemaStringEmailOptions;
280
302
  Set2?: JsonSchema;
281
303
  Buffer?: true;
@@ -289,7 +311,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
289
311
  truncate?: number;
290
312
  };
291
313
  errorMessages?: StringMap<string>;
292
- hasIsOfTypeCheck?: boolean;
314
+ optionalValues?: (string | number | boolean)[];
293
315
  }
294
316
  declare function object(props: AnyObject): never;
295
317
  declare function object<IN extends AnyObject>(props: {
@@ -312,6 +334,18 @@ declare function objectDbEntity<IN extends BaseDBEntity & AnyObject, EXTRA_KEYS
312
334
  } : {
313
335
  updated: BuilderFor<IN['updated']>;
314
336
  })): JsonSchemaObjectBuilder<IN, IN, false>;
337
+ /**
338
+ * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
339
+ */
340
+ declare function withEnumKeys<const T extends readonly (string | number)[] | StringEnum | NumberEnum, S extends JsonSchemaAnyBuilder<any, any, any>, K extends string | number = EnumKeyUnion<T>, Opt extends boolean = SchemaOpt<S>>(keys: T, schema: S): JsonSchemaObjectBuilder<Opt extends true ? {
341
+ [P in K]?: SchemaIn<S>;
342
+ } : {
343
+ [P in K]: SchemaIn<S>;
344
+ }, Opt extends true ? {
345
+ [P in K]?: SchemaOut<S>;
346
+ } : {
347
+ [P in K]: SchemaOut<S>;
348
+ }, false>;
315
349
  type Expand<T> = T extends infer O ? {
316
350
  [K in keyof O]: O[K];
317
351
  } : never;
@@ -340,4 +374,5 @@ interface JsonBuilderRuleOpt {
340
374
  type EnumKeyUnion<T> = T extends readonly (infer U)[] ? U : T extends StringEnum | NumberEnum ? T[keyof T] : never;
341
375
  type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
342
376
  type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
377
+ type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? Opt : false;
343
378
  export {};
@@ -35,30 +35,7 @@ export const j = {
35
35
  },
36
36
  });
37
37
  },
38
- /**
39
- * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
40
- */
41
- withEnumKeys(keys, schema) {
42
- let enumValues;
43
- if (Array.isArray(keys)) {
44
- _assert(isEveryItemPrimitive(keys), 'Every item in the key list should be string, number or symbol');
45
- enumValues = keys;
46
- }
47
- else if (typeof keys === 'object') {
48
- const enumType = getEnumType(keys);
49
- _assert(enumType === 'NumberEnum' || enumType === 'StringEnum', 'The key list should be StringEnum or NumberEnum');
50
- if (enumType === 'NumberEnum') {
51
- enumValues = _numberEnumValues(keys);
52
- }
53
- else if (enumType === 'StringEnum') {
54
- enumValues = _stringEnumValues(keys);
55
- }
56
- }
57
- _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum');
58
- const typedValues = enumValues;
59
- const props = Object.fromEntries(typedValues.map(key => [key, schema]));
60
- return new JsonSchemaObjectBuilder(props, { hasIsOfTypeCheck: false });
61
- },
38
+ withEnumKeys,
62
39
  }),
63
40
  array(itemSchema) {
64
41
  return new JsonSchemaArrayBuilder(itemSchema);
@@ -101,6 +78,13 @@ export const j = {
101
78
  oneOf: schemas,
102
79
  });
103
80
  },
81
+ and() {
82
+ return {
83
+ silentBob: () => {
84
+ throw new Error('...strike back!');
85
+ },
86
+ };
87
+ },
104
88
  };
105
89
  const TS_2500 = 16725225600; // 2500-01-01
106
90
  const TS_2500_MILLIS = TS_2500 * 1000;
@@ -227,6 +211,16 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
227
211
  type: 'string',
228
212
  });
229
213
  }
214
+ /**
215
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
216
+ *
217
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
218
+ * due to how mutability works in Ajv.
219
+ */
220
+ optional(optionalValues) {
221
+ _objectAssign(this.schema, { optionalValues });
222
+ return super.optional();
223
+ }
230
224
  regex(pattern, opt) {
231
225
  return this.pattern(pattern.source, opt);
232
226
  }
@@ -379,6 +373,16 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
379
373
  type: 'number',
380
374
  });
381
375
  }
376
+ /**
377
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
378
+ *
379
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
380
+ * due to how mutability works in Ajv.
381
+ */
382
+ optional(optionalValues) {
383
+ _objectAssign(this.schema, { optionalValues });
384
+ return super.optional();
385
+ }
382
386
  integer() {
383
387
  _objectAssign(this.schema, { type: 'integer' });
384
388
  return this;
@@ -477,6 +481,18 @@ export class JsonSchemaBooleanBuilder extends JsonSchemaAnyBuilder {
477
481
  type: 'boolean',
478
482
  });
479
483
  }
484
+ /**
485
+ * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
486
+ *
487
+ * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
488
+ * due to how mutability works in Ajv.
489
+ */
490
+ optional(optionalValue) {
491
+ if (typeof optionalValue !== 'undefined') {
492
+ _objectAssign(this.schema, { optionalValues: [optionalValue] });
493
+ }
494
+ return super.optional();
495
+ }
480
496
  }
481
497
  export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
482
498
  constructor(props, opt) {
@@ -678,3 +694,27 @@ function objectDbEntity(props) {
678
694
  ...props,
679
695
  });
680
696
  }
697
+ /**
698
+ * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
699
+ */
700
+ function withEnumKeys(keys, schema) {
701
+ let enumValues;
702
+ if (Array.isArray(keys)) {
703
+ _assert(isEveryItemPrimitive(keys), 'Every item in the key list should be string, number or symbol');
704
+ enumValues = keys;
705
+ }
706
+ else if (typeof keys === 'object') {
707
+ const enumType = getEnumType(keys);
708
+ _assert(enumType === 'NumberEnum' || enumType === 'StringEnum', 'The key list should be StringEnum or NumberEnum');
709
+ if (enumType === 'NumberEnum') {
710
+ enumValues = _numberEnumValues(keys);
711
+ }
712
+ else if (enumType === 'StringEnum') {
713
+ enumValues = _stringEnumValues(keys);
714
+ }
715
+ }
716
+ _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum');
717
+ const typedValues = enumValues;
718
+ const props = Object.fromEntries(typedValues.map(key => [key, schema]));
719
+ return new JsonSchemaObjectBuilder(props, { hasIsOfTypeCheck: false });
720
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.54.0",
4
+ "version": "15.56.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
@@ -79,6 +79,13 @@ export class AjvSchema<IN = unknown, OUT = IN> {
79
79
  jsonSchema = schema
80
80
  }
81
81
 
82
+ // This is our own helper which marks a schema as optional
83
+ // in case it is going to be used in an object schema,
84
+ // where we need to mark the given property as not-required.
85
+ // But once all compilation is done, the presence of this field
86
+ // really upsets Ajv.
87
+ delete jsonSchema.optionalField
88
+
82
89
  const ajvSchema = new AjvSchema<IN, OUT>(jsonSchema, cfg)
83
90
  AjvSchema.cacheAjvSchema(schema, ajvSchema)
84
91
 
@@ -109,7 +109,7 @@ export function createAjv(opt?: Options): Ajv {
109
109
 
110
110
  ajv.addKeyword({
111
111
  keyword: 'instanceof',
112
- modifying: true,
112
+ modifying: false,
113
113
  schemaType: 'string',
114
114
  validate(instanceOf: string, data: unknown, _schema, _ctx) {
115
115
  if (typeof data !== 'object') return false
@@ -396,6 +396,30 @@ export function createAjv(opt?: Options): Ajv {
396
396
  schemaType: 'boolean',
397
397
  })
398
398
 
399
+ ajv.addKeyword({
400
+ keyword: 'optionalValues',
401
+ type: ['string', 'number', 'boolean'],
402
+ modifying: true,
403
+ errors: false,
404
+ schemaType: 'array',
405
+ validate: function validate(
406
+ optionalValues: (string | number | boolean)[],
407
+ data: string | number | boolean,
408
+ _schema,
409
+ ctx,
410
+ ) {
411
+ if (!optionalValues) return true
412
+
413
+ if (!optionalValues.includes(data)) return true
414
+
415
+ if (ctx?.parentData && ctx.parentDataProperty) {
416
+ ctx.parentData[ctx.parentDataProperty] = undefined
417
+ }
418
+
419
+ return true
420
+ },
421
+ })
422
+
399
423
  return ajv
400
424
  }
401
425
 
@@ -87,47 +87,7 @@ export const j = {
87
87
  )
88
88
  },
89
89
 
90
- /**
91
- * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
92
- */
93
- withEnumKeys<
94
- const T extends readonly (string | number)[] | StringEnum | NumberEnum,
95
- S extends JsonSchemaTerminal<any, any, any>,
96
- K extends string | number = EnumKeyUnion<T>,
97
- >(
98
- keys: T,
99
- schema: S,
100
- ): JsonSchemaObjectBuilder<Record<K, SchemaIn<S>>, Record<K, SchemaOut<S>>> {
101
- let enumValues: readonly (string | number)[] | undefined
102
- if (Array.isArray(keys)) {
103
- _assert(
104
- isEveryItemPrimitive(keys),
105
- 'Every item in the key list should be string, number or symbol',
106
- )
107
- enumValues = keys
108
- } else if (typeof keys === 'object') {
109
- const enumType = getEnumType(keys)
110
- _assert(
111
- enumType === 'NumberEnum' || enumType === 'StringEnum',
112
- 'The key list should be StringEnum or NumberEnum',
113
- )
114
- if (enumType === 'NumberEnum') {
115
- enumValues = _numberEnumValues(keys as NumberEnum)
116
- } else if (enumType === 'StringEnum') {
117
- enumValues = _stringEnumValues(keys as StringEnum)
118
- }
119
- }
120
-
121
- _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum')
122
-
123
- const typedValues = enumValues as readonly K[]
124
- const props = Object.fromEntries(typedValues.map(key => [key, schema])) as any
125
-
126
- return new JsonSchemaObjectBuilder<Record<K, SchemaIn<S>>, Record<K, SchemaOut<S>>, false>(
127
- props,
128
- { hasIsOfTypeCheck: false },
129
- )
130
- },
90
+ withEnumKeys,
131
91
  }),
132
92
 
133
93
  array<IN, OUT, Opt>(
@@ -193,6 +153,14 @@ export const j = {
193
153
  oneOf: schemas,
194
154
  })
195
155
  },
156
+
157
+ and() {
158
+ return {
159
+ silentBob: () => {
160
+ throw new Error('...strike back!')
161
+ },
162
+ }
163
+ },
196
164
  }
197
165
 
198
166
  const TS_2500 = 16725225600 // 2500-01-01
@@ -338,7 +306,7 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
338
306
  }
339
307
 
340
308
  export class JsonSchemaStringBuilder<
341
- IN extends string = string,
309
+ IN extends string | undefined = string,
342
310
  OUT = IN,
343
311
  Opt extends boolean = false,
344
312
  > extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
@@ -348,6 +316,23 @@ export class JsonSchemaStringBuilder<
348
316
  })
349
317
  }
350
318
 
319
+ /**
320
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
321
+ *
322
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
323
+ * due to how mutability works in Ajv.
324
+ */
325
+ override optional(
326
+ optionalValues?: string[],
327
+ ): JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true> {
328
+ _objectAssign(this.schema, { optionalValues })
329
+ return super.optional() as unknown as JsonSchemaStringBuilder<
330
+ IN | undefined,
331
+ OUT | undefined,
332
+ true
333
+ >
334
+ }
335
+
351
336
  regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this {
352
337
  return this.pattern(pattern.source, opt)
353
338
  }
@@ -540,7 +525,7 @@ export interface JsonSchemaIsoDateOptions {
540
525
  }
541
526
 
542
527
  export class JsonSchemaNumberBuilder<
543
- IN extends number = number,
528
+ IN extends number | undefined = number,
544
529
  OUT = IN,
545
530
  Opt extends boolean = false,
546
531
  > extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
@@ -550,6 +535,23 @@ export class JsonSchemaNumberBuilder<
550
535
  })
551
536
  }
552
537
 
538
+ /**
539
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
540
+ *
541
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
542
+ * due to how mutability works in Ajv.
543
+ */
544
+ override optional(
545
+ optionalValues?: number[],
546
+ ): JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true> {
547
+ _objectAssign(this.schema, { optionalValues })
548
+ return super.optional() as unknown as JsonSchemaNumberBuilder<
549
+ IN | undefined,
550
+ OUT | undefined,
551
+ true
552
+ >
553
+ }
554
+
553
555
  integer(): this {
554
556
  _objectAssign(this.schema, { type: 'integer' })
555
557
  return this
@@ -670,7 +672,7 @@ export class JsonSchemaNumberBuilder<
670
672
  }
671
673
 
672
674
  export class JsonSchemaBooleanBuilder<
673
- IN extends boolean = boolean,
675
+ IN extends boolean | undefined = boolean,
674
676
  OUT = IN,
675
677
  Opt extends boolean = false,
676
678
  > extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
@@ -679,6 +681,26 @@ export class JsonSchemaBooleanBuilder<
679
681
  type: 'boolean',
680
682
  })
681
683
  }
684
+
685
+ /**
686
+ * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
687
+ *
688
+ * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
689
+ * due to how mutability works in Ajv.
690
+ */
691
+ override optional(
692
+ optionalValue?: boolean,
693
+ ): JsonSchemaBooleanBuilder<IN | undefined, OUT | undefined, true> {
694
+ if (typeof optionalValue !== 'undefined') {
695
+ _objectAssign(this.schema, { optionalValues: [optionalValue] })
696
+ }
697
+
698
+ return super.optional() as unknown as JsonSchemaBooleanBuilder<
699
+ IN | undefined,
700
+ OUT | undefined,
701
+ true
702
+ >
703
+ }
682
704
  }
683
705
 
684
706
  export class JsonSchemaObjectBuilder<
@@ -1036,6 +1058,8 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1036
1058
 
1037
1059
  enum?: any
1038
1060
 
1061
+ hasIsOfTypeCheck?: boolean
1062
+
1039
1063
  // Below we add custom Ajv keywords
1040
1064
 
1041
1065
  email?: JsonSchemaStringEmailOptions
@@ -1046,7 +1070,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1046
1070
  instanceof?: string | string[]
1047
1071
  transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
1048
1072
  errorMessages?: StringMap<string>
1049
- hasIsOfTypeCheck?: boolean
1073
+ optionalValues?: (string | number | boolean)[]
1050
1074
  }
1051
1075
 
1052
1076
  function object(props: AnyObject): never
@@ -1096,6 +1120,54 @@ function objectDbEntity(props: AnyObject): any {
1096
1120
  })
1097
1121
  }
1098
1122
 
1123
+ /**
1124
+ * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
1125
+ */
1126
+ function withEnumKeys<
1127
+ const T extends readonly (string | number)[] | StringEnum | NumberEnum,
1128
+ S extends JsonSchemaAnyBuilder<any, any, any>,
1129
+ K extends string | number = EnumKeyUnion<T>,
1130
+ Opt extends boolean = SchemaOpt<S>,
1131
+ >(
1132
+ keys: T,
1133
+ schema: S,
1134
+ ): JsonSchemaObjectBuilder<
1135
+ Opt extends true ? { [P in K]?: SchemaIn<S> } : { [P in K]: SchemaIn<S> },
1136
+ Opt extends true ? { [P in K]?: SchemaOut<S> } : { [P in K]: SchemaOut<S> },
1137
+ false
1138
+ > {
1139
+ let enumValues: readonly (string | number)[] | undefined
1140
+ if (Array.isArray(keys)) {
1141
+ _assert(
1142
+ isEveryItemPrimitive(keys),
1143
+ 'Every item in the key list should be string, number or symbol',
1144
+ )
1145
+ enumValues = keys
1146
+ } else if (typeof keys === 'object') {
1147
+ const enumType = getEnumType(keys)
1148
+ _assert(
1149
+ enumType === 'NumberEnum' || enumType === 'StringEnum',
1150
+ 'The key list should be StringEnum or NumberEnum',
1151
+ )
1152
+ if (enumType === 'NumberEnum') {
1153
+ enumValues = _numberEnumValues(keys as NumberEnum)
1154
+ } else if (enumType === 'StringEnum') {
1155
+ enumValues = _stringEnumValues(keys as StringEnum)
1156
+ }
1157
+ }
1158
+
1159
+ _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum')
1160
+
1161
+ const typedValues = enumValues as readonly K[]
1162
+ const props = Object.fromEntries(typedValues.map(key => [key, schema])) as any
1163
+
1164
+ return new JsonSchemaObjectBuilder<
1165
+ Opt extends true ? { [P in K]?: SchemaIn<S> } : { [P in K]: SchemaIn<S> },
1166
+ Opt extends true ? { [P in K]?: SchemaOut<S> } : { [P in K]: SchemaOut<S> },
1167
+ false
1168
+ >(props, { hasIsOfTypeCheck: false })
1169
+ }
1170
+
1099
1171
  type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
1100
1172
 
1101
1173
  type ExactMatch<A, B> =
@@ -1137,3 +1209,4 @@ type EnumKeyUnion<T> =
1137
1209
 
1138
1210
  type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
1139
1211
  type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
1212
+ type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? Opt : false