@naturalcycles/nodejs-lib 15.53.0 → 15.55.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];
@@ -8,6 +8,7 @@ export declare const j: {
8
8
  dbEntity: typeof objectDbEntity;
9
9
  infer: typeof objectInfer;
10
10
  any(): JsonSchemaObjectBuilder<AnyObject, AnyObject, false>;
11
+ stringMap<S extends JsonSchemaTerminal<any, any, any>>(schema: S): JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>>;
11
12
  /**
12
13
  * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
13
14
  */
@@ -18,6 +19,9 @@ export declare const j: {
18
19
  buffer(): JsonSchemaBufferBuilder;
19
20
  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>;
20
21
  oneOf<B extends readonly JsonSchemaAnyBuilder<any, any, boolean>[], IN = BuilderInUnion<B>, OUT = BuilderOutUnion<B>>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false>;
22
+ and(): {
23
+ silentBob: () => never;
24
+ };
21
25
  };
22
26
  export declare class JsonSchemaTerminal<IN, OUT, Opt> {
23
27
  protected schema: JsonSchema;
@@ -72,8 +76,15 @@ export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTermin
72
76
  */
73
77
  final(): JsonSchemaTerminal<IN, OUT, Opt>;
74
78
  }
75
- export declare class JsonSchemaStringBuilder<IN extends string = string, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
79
+ export declare class JsonSchemaStringBuilder<IN extends string | undefined = string, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
76
80
  constructor();
81
+ /**
82
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
83
+ *
84
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
85
+ * due to how mutability works in Ajv.
86
+ */
87
+ optional(optionalValues?: string[]): JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true>;
77
88
  regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this;
78
89
  pattern(pattern: string, opt?: JsonBuilderRuleOpt): this;
79
90
  minLength(minLength: number): this;
@@ -133,8 +144,15 @@ export interface JsonSchemaIsoDateOptions {
133
144
  after?: string;
134
145
  sameOrAfter?: string;
135
146
  }
136
- export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
147
+ export declare class JsonSchemaNumberBuilder<IN extends number | undefined = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
137
148
  constructor();
149
+ /**
150
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
151
+ *
152
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
153
+ * due to how mutability works in Ajv.
154
+ */
155
+ optional(optionalValues?: number[]): JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true>;
138
156
  integer(): this;
139
157
  branded<B extends number>(): JsonSchemaNumberBuilder<B, B, Opt>;
140
158
  multipleOf(multipleOf: number): this;
@@ -159,8 +177,15 @@ export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = I
159
177
  utcOffset(): this;
160
178
  utcOffsetHour(): this;
161
179
  }
162
- export declare class JsonSchemaBooleanBuilder<IN extends boolean = boolean, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
180
+ export declare class JsonSchemaBooleanBuilder<IN extends boolean | undefined = boolean, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
163
181
  constructor();
182
+ /**
183
+ * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
184
+ *
185
+ * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
186
+ * due to how mutability works in Ajv.
187
+ */
188
+ optional(optionalValue?: boolean): JsonSchemaBooleanBuilder<IN | undefined, OUT | undefined, true>;
164
189
  }
165
190
  export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends AnyObject, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
166
191
  constructor(props?: AnyObject, opt?: JsonSchemaObjectBuilderOpts);
@@ -179,6 +204,7 @@ export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends A
179
204
  }
180
205
  interface JsonSchemaObjectBuilderOpts {
181
206
  hasIsOfTypeCheck?: false;
207
+ patternProperties?: StringMap<JsonSchema<any, any>>;
182
208
  }
183
209
  export declare class JsonSchemaObjectInferringBuilder<PROPS extends Record<string, JsonSchemaAnyBuilder<any, any, any>>, Opt extends boolean = false> extends JsonSchemaAnyBuilder<Expand<{
184
210
  [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;
@@ -243,6 +269,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
243
269
  properties?: {
244
270
  [K in keyof IN & keyof OUT]: JsonSchema<IN[K], OUT[K]>;
245
271
  };
272
+ patternProperties?: StringMap<JsonSchema<any, any>>;
246
273
  required?: string[];
247
274
  additionalProperties?: boolean;
248
275
  minProperties?: number;
@@ -273,6 +300,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
273
300
  maxItems?: number;
274
301
  uniqueItems?: boolean;
275
302
  enum?: any;
303
+ hasIsOfTypeCheck?: boolean;
276
304
  email?: JsonSchemaStringEmailOptions;
277
305
  Set2?: JsonSchema;
278
306
  Buffer?: true;
@@ -286,7 +314,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
286
314
  truncate?: number;
287
315
  };
288
316
  errorMessages?: StringMap<string>;
289
- hasIsOfTypeCheck?: boolean;
317
+ optionalValues?: (string | number | boolean)[];
290
318
  }
291
319
  declare function object(props: AnyObject): never;
292
320
  declare function object<IN extends AnyObject>(props: {
@@ -24,6 +24,17 @@ export const j = {
24
24
  any() {
25
25
  return j.object({}).allowAdditionalProperties();
26
26
  },
27
+ stringMap(schema) {
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
+ return new JsonSchemaObjectBuilder({}, {
32
+ hasIsOfTypeCheck: false,
33
+ patternProperties: {
34
+ '^.+$': builtSchema,
35
+ },
36
+ });
37
+ },
27
38
  /**
28
39
  * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
29
40
  */
@@ -43,7 +54,7 @@ export const j = {
43
54
  enumValues = _stringEnumValues(keys);
44
55
  }
45
56
  }
46
- _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StrinEnum');
57
+ _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum');
47
58
  const typedValues = enumValues;
48
59
  const props = Object.fromEntries(typedValues.map(key => [key, schema]));
49
60
  return new JsonSchemaObjectBuilder(props, { hasIsOfTypeCheck: false });
@@ -90,6 +101,13 @@ export const j = {
90
101
  oneOf: schemas,
91
102
  });
92
103
  },
104
+ and() {
105
+ return {
106
+ silentBob: () => {
107
+ throw new Error('...strike back!');
108
+ },
109
+ };
110
+ },
93
111
  };
94
112
  const TS_2500 = 16725225600; // 2500-01-01
95
113
  const TS_2500_MILLIS = TS_2500 * 1000;
@@ -216,6 +234,16 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
216
234
  type: 'string',
217
235
  });
218
236
  }
237
+ /**
238
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
239
+ *
240
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
241
+ * due to how mutability works in Ajv.
242
+ */
243
+ optional(optionalValues) {
244
+ _objectAssign(this.schema, { optionalValues });
245
+ return super.optional();
246
+ }
219
247
  regex(pattern, opt) {
220
248
  return this.pattern(pattern.source, opt);
221
249
  }
@@ -368,6 +396,16 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
368
396
  type: 'number',
369
397
  });
370
398
  }
399
+ /**
400
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
401
+ *
402
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
403
+ * due to how mutability works in Ajv.
404
+ */
405
+ optional(optionalValues) {
406
+ _objectAssign(this.schema, { optionalValues });
407
+ return super.optional();
408
+ }
371
409
  integer() {
372
410
  _objectAssign(this.schema, { type: 'integer' });
373
411
  return this;
@@ -466,6 +504,18 @@ export class JsonSchemaBooleanBuilder extends JsonSchemaAnyBuilder {
466
504
  type: 'boolean',
467
505
  });
468
506
  }
507
+ /**
508
+ * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
509
+ *
510
+ * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
511
+ * due to how mutability works in Ajv.
512
+ */
513
+ optional(optionalValue) {
514
+ if (typeof optionalValue !== 'undefined') {
515
+ _objectAssign(this.schema, { optionalValues: [optionalValue] });
516
+ }
517
+ return super.optional();
518
+ }
469
519
  }
470
520
  export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
471
521
  constructor(props, opt) {
@@ -475,6 +525,7 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
475
525
  required: [],
476
526
  additionalProperties: false,
477
527
  hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
528
+ patternProperties: opt?.patternProperties ?? undefined,
478
529
  });
479
530
  if (props)
480
531
  this.addProperties(props);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.53.0",
4
+ "version": "15.55.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
 
@@ -66,6 +66,27 @@ export const j = {
66
66
  return j.object<AnyObject>({}).allowAdditionalProperties()
67
67
  },
68
68
 
69
+ stringMap<S extends JsonSchemaTerminal<any, any, any>>(
70
+ schema: S,
71
+ ): JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>> {
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
+
79
+ return new JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>>(
80
+ {},
81
+ {
82
+ hasIsOfTypeCheck: false,
83
+ patternProperties: {
84
+ '^.+$': builtSchema,
85
+ },
86
+ },
87
+ )
88
+ },
89
+
69
90
  /**
70
91
  * Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
71
92
  */
@@ -97,7 +118,7 @@ export const j = {
97
118
  }
98
119
  }
99
120
 
100
- _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StrinEnum')
121
+ _assert(enumValues, 'The key list should be an array of values, NumberEnum or a StringEnum')
101
122
 
102
123
  const typedValues = enumValues as readonly K[]
103
124
  const props = Object.fromEntries(typedValues.map(key => [key, schema])) as any
@@ -172,6 +193,14 @@ export const j = {
172
193
  oneOf: schemas,
173
194
  })
174
195
  },
196
+
197
+ and() {
198
+ return {
199
+ silentBob: () => {
200
+ throw new Error('...strike back!')
201
+ },
202
+ }
203
+ },
175
204
  }
176
205
 
177
206
  const TS_2500 = 16725225600 // 2500-01-01
@@ -317,7 +346,7 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
317
346
  }
318
347
 
319
348
  export class JsonSchemaStringBuilder<
320
- IN extends string = string,
349
+ IN extends string | undefined = string,
321
350
  OUT = IN,
322
351
  Opt extends boolean = false,
323
352
  > extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
@@ -327,6 +356,23 @@ export class JsonSchemaStringBuilder<
327
356
  })
328
357
  }
329
358
 
359
+ /**
360
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
361
+ *
362
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
363
+ * due to how mutability works in Ajv.
364
+ */
365
+ override optional(
366
+ optionalValues?: string[],
367
+ ): JsonSchemaStringBuilder<IN | undefined, OUT | undefined, true> {
368
+ _objectAssign(this.schema, { optionalValues })
369
+ return super.optional() as unknown as JsonSchemaStringBuilder<
370
+ IN | undefined,
371
+ OUT | undefined,
372
+ true
373
+ >
374
+ }
375
+
330
376
  regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this {
331
377
  return this.pattern(pattern.source, opt)
332
378
  }
@@ -519,7 +565,7 @@ export interface JsonSchemaIsoDateOptions {
519
565
  }
520
566
 
521
567
  export class JsonSchemaNumberBuilder<
522
- IN extends number = number,
568
+ IN extends number | undefined = number,
523
569
  OUT = IN,
524
570
  Opt extends boolean = false,
525
571
  > extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
@@ -529,6 +575,23 @@ export class JsonSchemaNumberBuilder<
529
575
  })
530
576
  }
531
577
 
578
+ /**
579
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
580
+ *
581
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
582
+ * due to how mutability works in Ajv.
583
+ */
584
+ override optional(
585
+ optionalValues?: number[],
586
+ ): JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true> {
587
+ _objectAssign(this.schema, { optionalValues })
588
+ return super.optional() as unknown as JsonSchemaNumberBuilder<
589
+ IN | undefined,
590
+ OUT | undefined,
591
+ true
592
+ >
593
+ }
594
+
532
595
  integer(): this {
533
596
  _objectAssign(this.schema, { type: 'integer' })
534
597
  return this
@@ -649,7 +712,7 @@ export class JsonSchemaNumberBuilder<
649
712
  }
650
713
 
651
714
  export class JsonSchemaBooleanBuilder<
652
- IN extends boolean = boolean,
715
+ IN extends boolean | undefined = boolean,
653
716
  OUT = IN,
654
717
  Opt extends boolean = false,
655
718
  > extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
@@ -658,6 +721,26 @@ export class JsonSchemaBooleanBuilder<
658
721
  type: 'boolean',
659
722
  })
660
723
  }
724
+
725
+ /**
726
+ * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
727
+ *
728
+ * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
729
+ * due to how mutability works in Ajv.
730
+ */
731
+ override optional(
732
+ optionalValue?: boolean,
733
+ ): JsonSchemaBooleanBuilder<IN | undefined, OUT | undefined, true> {
734
+ if (typeof optionalValue !== 'undefined') {
735
+ _objectAssign(this.schema, { optionalValues: [optionalValue] })
736
+ }
737
+
738
+ return super.optional() as unknown as JsonSchemaBooleanBuilder<
739
+ IN | undefined,
740
+ OUT | undefined,
741
+ true
742
+ >
743
+ }
661
744
  }
662
745
 
663
746
  export class JsonSchemaObjectBuilder<
@@ -672,6 +755,7 @@ export class JsonSchemaObjectBuilder<
672
755
  required: [],
673
756
  additionalProperties: false,
674
757
  hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
758
+ patternProperties: opt?.patternProperties ?? undefined,
675
759
  })
676
760
 
677
761
  if (props) this.addProperties(props)
@@ -742,6 +826,7 @@ export class JsonSchemaObjectBuilder<
742
826
 
743
827
  interface JsonSchemaObjectBuilderOpts {
744
828
  hasIsOfTypeCheck?: false
829
+ patternProperties?: StringMap<JsonSchema<any, any>>
745
830
  }
746
831
 
747
832
  export class JsonSchemaObjectInferringBuilder<
@@ -972,6 +1057,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
972
1057
  properties?: {
973
1058
  [K in keyof IN & keyof OUT]: JsonSchema<IN[K], OUT[K]>
974
1059
  }
1060
+ patternProperties?: StringMap<JsonSchema<any, any>>
975
1061
  required?: string[]
976
1062
  additionalProperties?: boolean
977
1063
  minProperties?: number
@@ -1012,6 +1098,8 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1012
1098
 
1013
1099
  enum?: any
1014
1100
 
1101
+ hasIsOfTypeCheck?: boolean
1102
+
1015
1103
  // Below we add custom Ajv keywords
1016
1104
 
1017
1105
  email?: JsonSchemaStringEmailOptions
@@ -1022,7 +1110,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1022
1110
  instanceof?: string | string[]
1023
1111
  transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
1024
1112
  errorMessages?: StringMap<string>
1025
- hasIsOfTypeCheck?: boolean
1113
+ optionalValues?: (string | number | boolean)[]
1026
1114
  }
1027
1115
 
1028
1116
  function object(props: AnyObject): never