@naturalcycles/nodejs-lib 15.56.1 → 15.57.1

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.
@@ -53,6 +53,7 @@ export class AjvSchema {
53
53
  }
54
54
  let jsonSchema;
55
55
  if (AjvSchema.isJsonSchemaBuilder(schema)) {
56
+ // oxlint-disable typescript-eslint(no-unnecessary-type-assertion)
56
57
  jsonSchema = schema.build();
57
58
  AjvSchema.requireValidJsonSchema(jsonSchema);
58
59
  }
@@ -1,7 +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 { _, Ajv } from 'ajv';
4
+ import { Ajv } from 'ajv';
5
5
  import { validTLDs } from '../tlds.js';
6
6
  /* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
7
7
  // oxlint-disable unicorn/prefer-code-point
@@ -66,27 +66,30 @@ export function createAjv(opt) {
66
66
  modifying: true,
67
67
  schemaType: 'object',
68
68
  errors: true,
69
- code(ctx) {
70
- const { gen, data, schema, it } = ctx;
71
- const { parentData, parentDataProperty } = it;
72
- if (schema.trim) {
73
- gen.assign(_ `${data}`, _ `${data}.trim()`);
69
+ validate: function validate(transform, data, _schema, ctx) {
70
+ if (!data)
71
+ return true;
72
+ let transformedData = data;
73
+ if (transform.trim) {
74
+ transformedData = transformedData.trim();
74
75
  }
75
- if (schema.toLowerCase) {
76
- gen.assign(_ `${data}`, _ `${data}.toLowerCase()`);
76
+ if (transform.toLowerCase) {
77
+ transformedData = transformedData.toLocaleLowerCase();
77
78
  }
78
- if (schema.toUpperCase) {
79
- gen.assign(_ `${data}`, _ `${data}.toUpperCase()`);
79
+ if (transform.toUpperCase) {
80
+ transformedData = transformedData.toLocaleUpperCase();
80
81
  }
81
- if (typeof schema.truncate === 'number' && schema.truncate >= 0) {
82
- gen.assign(_ `${data}`, _ `${data}.slice(0, ${schema.truncate})`);
83
- if (schema.trim) {
84
- gen.assign(_ `${data}`, _ `${data}.trim()`);
82
+ if (typeof transform.truncate === 'number' && transform.truncate >= 0) {
83
+ transformedData = transformedData.slice(0, transform.truncate);
84
+ if (transform.trim) {
85
+ transformedData = transformedData.trim();
85
86
  }
86
87
  }
87
- gen.if(_ `${parentData} !== undefined`, () => {
88
- gen.assign(_ `${parentData}[${parentDataProperty}]`, data);
89
- });
88
+ // Explicit check for `undefined` because parentDataProperty can be `0` when it comes to arrays.
89
+ if (ctx?.parentData && typeof ctx.parentDataProperty !== 'undefined') {
90
+ ctx.parentData[ctx.parentDataProperty] = transformedData;
91
+ }
92
+ return true;
90
93
  },
91
94
  });
92
95
  ajv.addKeyword({
@@ -366,7 +369,7 @@ export function createAjv(opt) {
366
369
  if (!optionalValues.includes(data))
367
370
  return true;
368
371
  if (ctx?.parentData && ctx.parentDataProperty) {
369
- ctx.parentData[ctx.parentDataProperty] = undefined;
372
+ delete ctx.parentData[ctx.parentDataProperty];
370
373
  }
371
374
  return true;
372
375
  },
@@ -49,6 +49,8 @@ export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTermin
49
49
  *
50
50
  * const schemaBad = j.string().isOfType<number>() // ❌
51
51
  * schemaBad.build() // TypeError: property "build" does not exist on type "never"
52
+ *
53
+ * const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
52
54
  * ```
53
55
  */
54
56
  isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never;
@@ -86,6 +88,7 @@ export declare class JsonSchemaStringBuilder<IN extends string | undefined = str
86
88
  pattern(pattern: string, opt?: JsonBuilderRuleOpt): this;
87
89
  minLength(minLength: number): this;
88
90
  maxLength(maxLength: number): this;
91
+ length(exactLength: number): this;
89
92
  length(minLength: number, maxLength: number): this;
90
93
  email(opt?: Partial<JsonSchemaStringEmailOptions>): this;
91
94
  trim(): this;
@@ -234,6 +237,7 @@ export declare class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyB
234
237
  constructor(itemsSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>);
235
238
  minLength(minItems: number): this;
236
239
  maxLength(maxItems: number): this;
240
+ length(exactLength: number): this;
237
241
  length(minItems: number, maxItems: number): this;
238
242
  exactLength(length: number): this;
239
243
  unique(): this;
@@ -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: {
@@ -111,7 +109,9 @@ export class JsonSchemaTerminal {
111
109
  * Same as if it would be JSON.stringified.
112
110
  */
113
111
  build() {
114
- return _sortObject(JSON.parse(JSON.stringify(this.schema)), JSON_SCHEMA_ORDER);
112
+ const jsonSchema = _sortObject(JSON.parse(JSON.stringify(this.schema)), JSON_SCHEMA_ORDER);
113
+ delete jsonSchema.optionalField;
114
+ return jsonSchema;
115
115
  }
116
116
  clone() {
117
117
  return new JsonSchemaAnyBuilder(_deepCopy(this.schema));
@@ -140,6 +140,8 @@ export class JsonSchemaAnyBuilder extends JsonSchemaTerminal {
140
140
  *
141
141
  * const schemaBad = j.string().isOfType<number>() // ❌
142
142
  * schemaBad.build() // TypeError: property "build" does not exist on type "never"
143
+ *
144
+ * const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
143
145
  * ```
144
146
  */
145
147
  isOfType() {
@@ -248,9 +250,9 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
248
250
  _objectAssign(this.schema, { maxLength });
249
251
  return this;
250
252
  }
251
- length(minLength, maxLength) {
252
- _objectAssign(this.schema, { minLength, maxLength });
253
- return this;
253
+ length(minLengthOrExactLength, maxLength) {
254
+ const maxLengthActual = maxLength ?? minLengthOrExactLength;
255
+ return this.minLength(minLengthOrExactLength).maxLength(maxLengthActual);
254
256
  }
255
257
  email(opt) {
256
258
  const defaultOptions = { checkTLD: true };
@@ -533,13 +535,11 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
533
535
  const properties = {};
534
536
  const required = [];
535
537
  for (const [key, builder] of Object.entries(props)) {
536
- const schema = builder.build();
537
- if (!schema.optionalField) {
538
+ const isOptional = builder.getSchema().optionalField;
539
+ if (!isOptional) {
538
540
  required.push(key);
539
541
  }
540
- else {
541
- schema.optionalField = undefined;
542
- }
542
+ const schema = builder.build();
543
543
  properties[key] = schema;
544
544
  }
545
545
  this.schema.properties = properties;
@@ -595,13 +595,11 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
595
595
  const properties = {};
596
596
  const required = [];
597
597
  for (const [key, builder] of Object.entries(props)) {
598
- const schema = builder.build();
599
- if (!schema.optionalField) {
598
+ const isOptional = builder.getSchema().optionalField;
599
+ if (!isOptional) {
600
600
  required.push(key);
601
601
  }
602
- else {
603
- schema.optionalField = undefined;
604
- }
602
+ const schema = builder.build();
605
603
  properties[key] = schema;
606
604
  }
607
605
  this.schema.properties = properties;
@@ -649,8 +647,9 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
649
647
  _objectAssign(this.schema, { maxItems });
650
648
  return this;
651
649
  }
652
- length(minItems, maxItems) {
653
- return this.minLength(minItems).maxLength(maxItems);
650
+ length(minItemsOrExact, maxItems) {
651
+ const maxItemsActual = maxItems ?? minItemsOrExact;
652
+ return this.minLength(minItemsOrExact).maxLength(maxItemsActual);
654
653
  }
655
654
  exactLength(length) {
656
655
  return this.minLength(length).maxLength(length);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.56.1",
4
+ "version": "15.57.1",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
@@ -73,6 +73,7 @@ export class AjvSchema<IN = unknown, OUT = IN> {
73
73
  let jsonSchema: JsonSchema<IN, OUT>
74
74
 
75
75
  if (AjvSchema.isJsonSchemaBuilder(schema)) {
76
+ // oxlint-disable typescript-eslint(no-unnecessary-type-assertion)
76
77
  jsonSchema = (schema as JsonSchemaTerminal<IN, OUT, any>).build()
77
78
  AjvSchema.requireValidJsonSchema(jsonSchema)
78
79
  } else {
@@ -1,7 +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 { _, Ajv, type Options, type ValidateFunction } from 'ajv'
4
+ import { Ajv, type Options, type ValidateFunction } from 'ajv'
5
5
  import { validTLDs } from '../tlds.js'
6
6
  import type { JsonSchemaIsoDateOptions, JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
7
7
 
@@ -77,33 +77,42 @@ export function createAjv(opt?: Options): Ajv {
77
77
  modifying: true,
78
78
  schemaType: 'object',
79
79
  errors: true,
80
- code(ctx) {
81
- const { gen, data, schema, it } = ctx
82
- const { parentData, parentDataProperty } = it
80
+ validate: function validate(
81
+ transform: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number },
82
+ data: string,
83
+ _schema,
84
+ ctx,
85
+ ) {
86
+ if (!data) return true
83
87
 
84
- if (schema.trim) {
85
- gen.assign(_`${data}`, _`${data}.trim()`)
88
+ let transformedData = data
89
+
90
+ if (transform.trim) {
91
+ transformedData = transformedData.trim()
86
92
  }
87
93
 
88
- if (schema.toLowerCase) {
89
- gen.assign(_`${data}`, _`${data}.toLowerCase()`)
94
+ if (transform.toLowerCase) {
95
+ transformedData = transformedData.toLocaleLowerCase()
90
96
  }
91
97
 
92
- if (schema.toUpperCase) {
93
- gen.assign(_`${data}`, _`${data}.toUpperCase()`)
98
+ if (transform.toUpperCase) {
99
+ transformedData = transformedData.toLocaleUpperCase()
94
100
  }
95
101
 
96
- if (typeof schema.truncate === 'number' && schema.truncate >= 0) {
97
- gen.assign(_`${data}`, _`${data}.slice(0, ${schema.truncate})`)
102
+ if (typeof transform.truncate === 'number' && transform.truncate >= 0) {
103
+ transformedData = transformedData.slice(0, transform.truncate)
98
104
 
99
- if (schema.trim) {
100
- gen.assign(_`${data}`, _`${data}.trim()`)
105
+ if (transform.trim) {
106
+ transformedData = transformedData.trim()
101
107
  }
102
108
  }
103
109
 
104
- gen.if(_`${parentData} !== undefined`, () => {
105
- gen.assign(_`${parentData}[${parentDataProperty}]`, data)
106
- })
110
+ // Explicit check for `undefined` because parentDataProperty can be `0` when it comes to arrays.
111
+ if (ctx?.parentData && typeof ctx.parentDataProperty !== 'undefined') {
112
+ ctx.parentData[ctx.parentDataProperty] = transformedData
113
+ }
114
+
115
+ return true
107
116
  },
108
117
  })
109
118
 
@@ -413,7 +422,7 @@ export function createAjv(opt?: Options): Ajv {
413
422
  if (!optionalValues.includes(data)) return true
414
423
 
415
424
  if (ctx?.parentData && ctx.parentDataProperty) {
416
- ctx.parentData[ctx.parentDataProperty] = undefined
425
+ delete ctx.parentData[ctx.parentDataProperty]
417
426
  }
418
427
 
419
428
  return true
@@ -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
  {},
@@ -193,7 +188,14 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
193
188
  * Same as if it would be JSON.stringified.
194
189
  */
195
190
  build(): JsonSchema<IN, OUT> {
196
- return _sortObject(JSON.parse(JSON.stringify(this.schema)), JSON_SCHEMA_ORDER)
191
+ const jsonSchema = _sortObject(
192
+ JSON.parse(JSON.stringify(this.schema)),
193
+ JSON_SCHEMA_ORDER,
194
+ ) as JsonSchema<IN, OUT>
195
+
196
+ delete jsonSchema.optionalField
197
+
198
+ return jsonSchema
197
199
  }
198
200
 
199
201
  clone(): JsonSchemaAnyBuilder<IN, OUT, Opt> {
@@ -226,6 +228,8 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
226
228
  *
227
229
  * const schemaBad = j.string().isOfType<number>() // ❌
228
230
  * schemaBad.build() // TypeError: property "build" does not exist on type "never"
231
+ *
232
+ * const result = ajvValidateRequest.body(req, schemaBad) // result will have `unknown` type
229
233
  * ```
230
234
  */
231
235
  isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never {
@@ -364,9 +368,11 @@ export class JsonSchemaStringBuilder<
364
368
  return this
365
369
  }
366
370
 
367
- length(minLength: number, maxLength: number): this {
368
- _objectAssign(this.schema, { minLength, maxLength })
369
- return this
371
+ length(exactLength: number): this
372
+ length(minLength: number, maxLength: number): this
373
+ length(minLengthOrExactLength: number, maxLength?: number): this {
374
+ const maxLengthActual = maxLength ?? minLengthOrExactLength
375
+ return this.minLength(minLengthOrExactLength).maxLength(maxLengthActual)
370
376
  }
371
377
 
372
378
  email(opt?: Partial<JsonSchemaStringEmailOptions>): this {
@@ -753,12 +759,12 @@ export class JsonSchemaObjectBuilder<
753
759
  const required: string[] = []
754
760
 
755
761
  for (const [key, builder] of Object.entries(props)) {
756
- const schema = builder.build()
757
- if (!schema.optionalField) {
762
+ const isOptional = (builder as JsonSchemaTerminal<any, any, any>).getSchema().optionalField
763
+ if (!isOptional) {
758
764
  required.push(key)
759
- } else {
760
- schema.optionalField = undefined
761
765
  }
766
+
767
+ const schema = builder.build()
762
768
  properties[key] = schema
763
769
  }
764
770
 
@@ -868,12 +874,12 @@ export class JsonSchemaObjectInferringBuilder<
868
874
  const required: string[] = []
869
875
 
870
876
  for (const [key, builder] of Object.entries(props)) {
871
- const schema = builder.build()
872
- if (!schema.optionalField) {
877
+ const isOptional = (builder as JsonSchemaTerminal<any, any, any>).getSchema().optionalField
878
+ if (!isOptional) {
873
879
  required.push(key)
874
- } else {
875
- schema.optionalField = undefined
876
880
  }
881
+
882
+ const schema = builder.build()
877
883
  properties[key] = schema
878
884
  }
879
885
 
@@ -952,8 +958,11 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
952
958
  return this
953
959
  }
954
960
 
955
- length(minItems: number, maxItems: number): this {
956
- return this.minLength(minItems).maxLength(maxItems)
961
+ length(exactLength: number): this
962
+ length(minItems: number, maxItems: number): this
963
+ length(minItemsOrExact: number, maxItems?: number): this {
964
+ const maxItemsActual = maxItems ?? minItemsOrExact
965
+ return this.minLength(minItemsOrExact).maxLength(maxItemsActual)
957
966
  }
958
967
 
959
968
  exactLength(length: number): this {