@naturalcycles/nodejs-lib 15.85.0 → 15.87.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.
@@ -1,4 +1,4 @@
1
- import { _isBetween, _lazyValue } from '@naturalcycles/js-lib';
1
+ import { _isBetween, _lazyValue, _round } from '@naturalcycles/js-lib';
2
2
  import { _assert } from '@naturalcycles/js-lib/error';
3
3
  import { _deepCopy, _mapObject, Set2 } from '@naturalcycles/js-lib/object';
4
4
  import { _substringAfterLast } from '@naturalcycles/js-lib/string';
@@ -417,6 +417,15 @@ export function createAjv(opt) {
417
417
  return validate;
418
418
  },
419
419
  });
420
+ // Validates that the value is undefined. Used in record/stringMap with optional value schemas
421
+ // to allow undefined values in patternProperties via anyOf.
422
+ ajv.addKeyword({
423
+ keyword: 'isUndefined',
424
+ modifying: false,
425
+ errors: false,
426
+ schemaType: 'boolean',
427
+ validate: (_schema, data) => data === undefined,
428
+ });
420
429
  // This is added because Ajv validates the `min/maxProperties` before validating the properties.
421
430
  // So, in case of `minProperties(1)` and `{ foo: 'bar' }` Ajv will let it pass, even
422
431
  // if the property validation would strip `foo` from the data.
@@ -560,6 +569,20 @@ export function createAjv(opt) {
560
569
  return validate;
561
570
  },
562
571
  });
572
+ ajv.addKeyword({
573
+ keyword: 'precision',
574
+ type: ['number'],
575
+ modifying: true,
576
+ errors: false,
577
+ schemaType: 'number',
578
+ validate: function validate(numberOfDigits, data, _schema, ctx) {
579
+ if (!numberOfDigits)
580
+ return true;
581
+ _assert(ctx?.parentData && ctx.parentDataProperty !== undefined, 'You should only use `precision(n) on a property of an object, or on an element of an array due to Ajv mutation issues.');
582
+ ctx.parentData[ctx.parentDataProperty] = _round(data, 10 ** (-1 * numberOfDigits));
583
+ return true;
584
+ },
585
+ });
563
586
  return ajv;
564
587
  }
565
588
  const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
@@ -279,6 +279,12 @@ export declare class JsonSchemaNumberBuilder<IN extends number | undefined = num
279
279
  unixTimestamp2000Millis(): JsonSchemaNumberBuilder<UnixTimestampMillis, UnixTimestampMillis, Opt>;
280
280
  utcOffset(): this;
281
281
  utcOffsetHour(): this;
282
+ /**
283
+ * Specify the precision of the floating point numbers by the number of digits after the ".".
284
+ * Excess digits will be cut-off when the current schema is nested in an object or array schema,
285
+ * due to how mutability works in Ajv.
286
+ */
287
+ precision(numberOfDigits: number): this;
282
288
  }
283
289
  export declare class JsonSchemaBooleanBuilder<IN extends boolean | undefined = boolean, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
284
290
  constructor();
@@ -505,6 +511,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
505
511
  errorMessages?: StringMap<string>;
506
512
  optionalValues?: (string | number | boolean | null)[];
507
513
  keySchema?: JsonSchema;
514
+ isUndefined?: true;
508
515
  minProperties2?: number;
509
516
  exclusiveProperties?: (readonly string[])[];
510
517
  anyOfBy?: {
@@ -512,6 +519,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
512
519
  schemaDictionary: Record<string, JsonSchema>;
513
520
  };
514
521
  anyOfThese?: JsonSchema[];
522
+ precision?: number;
515
523
  }
516
524
  declare function object(props: AnyObject): never;
517
525
  declare function object<IN extends AnyObject>(props: {
@@ -32,11 +32,15 @@ export const j = {
32
32
  return j.object({}).allowAdditionalProperties();
33
33
  },
34
34
  stringMap(schema) {
35
+ const isValueOptional = schema.getSchema().optionalField;
35
36
  const builtSchema = schema.build();
37
+ const finalValueSchema = isValueOptional
38
+ ? { anyOf: [{ isUndefined: true }, builtSchema] }
39
+ : builtSchema;
36
40
  return new JsonSchemaObjectBuilder({}, {
37
41
  hasIsOfTypeCheck: false,
38
42
  patternProperties: {
39
- '^.+$': builtSchema,
43
+ '^.+$': finalValueSchema,
40
44
  },
41
45
  });
42
46
  },
@@ -632,6 +636,14 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
632
636
  utcOffsetHour() {
633
637
  return this.integer().min(-12).max(14);
634
638
  }
639
+ /**
640
+ * Specify the precision of the floating point numbers by the number of digits after the ".".
641
+ * Excess digits will be cut-off when the current schema is nested in an object or array schema,
642
+ * due to how mutability works in Ajv.
643
+ */
644
+ precision(numberOfDigits) {
645
+ return this.cloneAndUpdateSchema({ precision: numberOfDigits });
646
+ }
635
647
  }
636
648
  export class JsonSchemaBooleanBuilder extends JsonSchemaAnyBuilder {
637
649
  constructor() {
@@ -971,12 +983,19 @@ function objectDbEntity(props) {
971
983
  }
972
984
  function record(keySchema, valueSchema) {
973
985
  const keyJsonSchema = keySchema.build();
986
+ // Check if value schema is optional before build() strips the optionalField flag
987
+ const isValueOptional = valueSchema.getSchema()
988
+ .optionalField;
974
989
  const valueJsonSchema = valueSchema.build();
990
+ // When value schema is optional, wrap in anyOf to allow undefined values
991
+ const finalValueSchema = isValueOptional
992
+ ? { anyOf: [{ isUndefined: true }, valueJsonSchema] }
993
+ : valueJsonSchema;
975
994
  return new JsonSchemaObjectBuilder([], {
976
995
  hasIsOfTypeCheck: false,
977
996
  keySchema: keyJsonSchema,
978
997
  patternProperties: {
979
- ['^.*$']: valueJsonSchema,
998
+ ['^.*$']: finalValueSchema,
980
999
  },
981
1000
  });
982
1001
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.85.0",
4
+ "version": "15.87.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
@@ -1,4 +1,4 @@
1
- import { _isBetween, _lazyValue } from '@naturalcycles/js-lib'
1
+ import { _isBetween, _lazyValue, _round } from '@naturalcycles/js-lib'
2
2
  import { _assert } from '@naturalcycles/js-lib/error'
3
3
  import { _deepCopy, _mapObject, Set2 } from '@naturalcycles/js-lib/object'
4
4
  import { _substringAfterLast } from '@naturalcycles/js-lib/string'
@@ -486,6 +486,16 @@ export function createAjv(opt?: Options): Ajv2020 {
486
486
  },
487
487
  })
488
488
 
489
+ // Validates that the value is undefined. Used in record/stringMap with optional value schemas
490
+ // to allow undefined values in patternProperties via anyOf.
491
+ ajv.addKeyword({
492
+ keyword: 'isUndefined',
493
+ modifying: false,
494
+ errors: false,
495
+ schemaType: 'boolean',
496
+ validate: (_schema: boolean, data: unknown) => data === undefined,
497
+ })
498
+
489
499
  // This is added because Ajv validates the `min/maxProperties` before validating the properties.
490
500
  // So, in case of `minProperties(1)` and `{ foo: 'bar' }` Ajv will let it pass, even
491
501
  // if the property validation would strip `foo` from the data.
@@ -639,6 +649,26 @@ export function createAjv(opt?: Options): Ajv2020 {
639
649
  },
640
650
  })
641
651
 
652
+ ajv.addKeyword({
653
+ keyword: 'precision',
654
+ type: ['number'],
655
+ modifying: true,
656
+ errors: false,
657
+ schemaType: 'number',
658
+ validate: function validate(numberOfDigits: number, data: number, _schema, ctx) {
659
+ if (!numberOfDigits) return true
660
+
661
+ _assert(
662
+ ctx?.parentData && ctx.parentDataProperty !== undefined,
663
+ 'You should only use `precision(n) on a property of an object, or on an element of an array due to Ajv mutation issues.',
664
+ )
665
+
666
+ ctx.parentData[ctx.parentDataProperty] = _round(data, 10 ** (-1 * numberOfDigits))
667
+
668
+ return true
669
+ },
670
+ })
671
+
642
672
  return ajv
643
673
  }
644
674
 
@@ -80,14 +80,18 @@ export const j = {
80
80
  stringMap<S extends JsonSchemaTerminal<any, any, any>>(
81
81
  schema: S,
82
82
  ): JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>> {
83
+ const isValueOptional = schema.getSchema().optionalField
83
84
  const builtSchema = schema.build()
85
+ const finalValueSchema: JsonSchema = isValueOptional
86
+ ? { anyOf: [{ isUndefined: true }, builtSchema] }
87
+ : builtSchema
84
88
 
85
89
  return new JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>>(
86
90
  {},
87
91
  {
88
92
  hasIsOfTypeCheck: false,
89
93
  patternProperties: {
90
- '^.+$': builtSchema,
94
+ '^.+$': finalValueSchema,
91
95
  },
92
96
  },
93
97
  )
@@ -888,6 +892,15 @@ export class JsonSchemaNumberBuilder<
888
892
  utcOffsetHour(): this {
889
893
  return this.integer().min(-12).max(14)
890
894
  }
895
+
896
+ /**
897
+ * Specify the precision of the floating point numbers by the number of digits after the ".".
898
+ * Excess digits will be cut-off when the current schema is nested in an object or array schema,
899
+ * due to how mutability works in Ajv.
900
+ */
901
+ precision(numberOfDigits: number): this {
902
+ return this.cloneAndUpdateSchema({ precision: numberOfDigits })
903
+ }
891
904
  }
892
905
 
893
906
  export class JsonSchemaBooleanBuilder<
@@ -1565,6 +1578,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1565
1578
  errorMessages?: StringMap<string>
1566
1579
  optionalValues?: (string | number | boolean | null)[]
1567
1580
  keySchema?: JsonSchema
1581
+ isUndefined?: true
1568
1582
  minProperties2?: number
1569
1583
  exclusiveProperties?: (readonly string[])[]
1570
1584
  anyOfBy?: {
@@ -1572,6 +1586,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1572
1586
  schemaDictionary: Record<string, JsonSchema>
1573
1587
  }
1574
1588
  anyOfThese?: JsonSchema[]
1589
+ precision?: number
1575
1590
  }
1576
1591
 
1577
1592
  function object(props: AnyObject): never
@@ -1638,8 +1653,16 @@ function record<
1638
1653
  false
1639
1654
  > {
1640
1655
  const keyJsonSchema = keySchema.build()
1656
+ // Check if value schema is optional before build() strips the optionalField flag
1657
+ const isValueOptional = (valueSchema as JsonSchemaTerminal<any, any, any>).getSchema()
1658
+ .optionalField
1641
1659
  const valueJsonSchema = valueSchema.build()
1642
1660
 
1661
+ // When value schema is optional, wrap in anyOf to allow undefined values
1662
+ const finalValueSchema: JsonSchema = isValueOptional
1663
+ ? { anyOf: [{ isUndefined: true }, valueJsonSchema] }
1664
+ : valueJsonSchema
1665
+
1643
1666
  return new JsonSchemaObjectBuilder<
1644
1667
  Opt extends true
1645
1668
  ? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>>
@@ -1652,7 +1675,7 @@ function record<
1652
1675
  hasIsOfTypeCheck: false,
1653
1676
  keySchema: keyJsonSchema,
1654
1677
  patternProperties: {
1655
- ['^.*$']: valueJsonSchema,
1678
+ ['^.*$']: finalValueSchema,
1656
1679
  },
1657
1680
  })
1658
1681
  }