@naturalcycles/nodejs-lib 15.46.3 → 15.47.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.
@@ -13,6 +13,13 @@ export declare const getAjv: any;
13
13
  * and are not interested in transforming the data.
14
14
  */
15
15
  export declare const getNonMutatingAjv: any;
16
+ /**
17
+ * Returns cached instance of Ajv, which is coercing data.
18
+ *
19
+ * To be used in places where we know that we are going to receive data with the wrong type,
20
+ * typically: request path params and request query params.
21
+ */
22
+ export declare const getCoercingAjv: any;
16
23
  /**
17
24
  * Create Ajv with modified defaults.
18
25
  *
@@ -19,6 +19,10 @@ const AJV_NON_MUTATING_OPTIONS = {
19
19
  removeAdditional: false,
20
20
  useDefaults: false,
21
21
  };
22
+ const AJV_MUTATING_COERCING_OPTIONS = {
23
+ ...AJV_OPTIONS,
24
+ coerceTypes: true,
25
+ };
22
26
  /**
23
27
  * Return cached instance of Ajv with default (recommended) options.
24
28
  *
@@ -33,6 +37,13 @@ export const getAjv = _lazyValue(createAjv);
33
37
  * and are not interested in transforming the data.
34
38
  */
35
39
  export const getNonMutatingAjv = _lazyValue(() => createAjv(AJV_NON_MUTATING_OPTIONS));
40
+ /**
41
+ * Returns cached instance of Ajv, which is coercing data.
42
+ *
43
+ * To be used in places where we know that we are going to receive data with the wrong type,
44
+ * typically: request path params and request query params.
45
+ */
46
+ export const getCoercingAjv = _lazyValue(() => createAjv(AJV_MUTATING_COERCING_OPTIONS));
36
47
  /**
37
48
  * Create Ajv with modified defaults.
38
49
  *
@@ -233,17 +244,67 @@ export function createAjv(opt) {
233
244
  type: 'string',
234
245
  modifying: false,
235
246
  errors: true,
236
- schemaType: 'boolean',
237
- validate: function validate(_opt, data, _schema, ctx) {
247
+ schemaType: 'object',
248
+ validate: function validate(opt, data, _schema, ctx) {
249
+ const hasOptions = Object.keys(opt).length > 0;
238
250
  const isValid = isIsoDateValid(data);
239
- if (isValid)
251
+ if (!isValid) {
252
+ ;
253
+ validate.errors = [
254
+ {
255
+ instancePath: ctx?.instancePath ?? '',
256
+ message: `is an invalid IsoDate`,
257
+ },
258
+ ];
259
+ return false;
260
+ }
261
+ if (!hasOptions)
240
262
  return true;
241
- validate.errors = [
242
- {
243
- instancePath: ctx?.instancePath ?? '',
244
- message: `is an invalid IsoDate`,
245
- },
246
- ];
263
+ const { before, sameOrBefore, after, sameOrAfter } = opt;
264
+ const errors = [];
265
+ if (before) {
266
+ const isParamValid = isIsoDateValid(before);
267
+ const isRuleValid = isParamValid && before > data;
268
+ if (!isRuleValid) {
269
+ errors.push({
270
+ instancePath: ctx?.instancePath ?? '',
271
+ message: `is not before ${before}`,
272
+ });
273
+ }
274
+ }
275
+ if (sameOrBefore) {
276
+ const isParamValid = isIsoDateValid(sameOrBefore);
277
+ const isRuleValid = isParamValid && sameOrBefore >= data;
278
+ if (!isRuleValid) {
279
+ errors.push({
280
+ instancePath: ctx?.instancePath ?? '',
281
+ message: `is not the same or before ${sameOrBefore}`,
282
+ });
283
+ }
284
+ }
285
+ if (after) {
286
+ const isParamValid = isIsoDateValid(after);
287
+ const isRuleValid = isParamValid && after < data;
288
+ if (!isRuleValid) {
289
+ errors.push({
290
+ instancePath: ctx?.instancePath ?? '',
291
+ message: `is not after ${after}`,
292
+ });
293
+ }
294
+ }
295
+ if (sameOrAfter) {
296
+ const isParamValid = isIsoDateValid(sameOrAfter);
297
+ const isRuleValid = isParamValid && sameOrAfter <= data;
298
+ if (!isRuleValid) {
299
+ errors.push({
300
+ instancePath: ctx?.instancePath ?? '',
301
+ message: `is not the same or after ${sameOrAfter}`,
302
+ });
303
+ }
304
+ }
305
+ if (errors.length === 0)
306
+ return true;
307
+ validate.errors = errors;
247
308
  return false;
248
309
  },
249
310
  });
@@ -75,7 +75,13 @@ export declare class JsonSchemaStringBuilder<IN extends string = string, OUT = I
75
75
  toUpperCase(): this;
76
76
  truncate(toLength: number): this;
77
77
  branded<B extends string>(): JsonSchemaStringBuilder<B, B, Opt>;
78
- isoDate(): JsonSchemaStringBuilder<IsoDate | IN, IsoDate, Opt>;
78
+ /**
79
+ * Validates that the input is a fully-specified YYYY-MM-DD formatted valid IsoDate value.
80
+ *
81
+ * All previous expectations in the schema chain are dropped - including `.optional()` -
82
+ * because this call effectively starts a new schema chain.
83
+ */
84
+ isoDate(): JsonSchemaIsoDateBuilder;
79
85
  isoDateTime(): JsonSchemaStringBuilder<IsoDateTime | IN, IsoDateTime, Opt>;
80
86
  /**
81
87
  * Validates the string format to be JWT.
@@ -102,6 +108,20 @@ export declare class JsonSchemaStringBuilder<IN extends string = string, OUT = I
102
108
  export interface JsonSchemaStringEmailOptions {
103
109
  checkTLD: boolean;
104
110
  }
111
+ export declare class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<string | IsoDate, IsoDate, Opt> {
112
+ constructor();
113
+ before(date: string): this;
114
+ sameOrBefore(date: string): this;
115
+ after(date: string): this;
116
+ sameOrAfter(date: string): this;
117
+ between(fromDate: string, toDate: string, incl: Inclusiveness): this;
118
+ }
119
+ export interface JsonSchemaIsoDateOptions {
120
+ before?: string;
121
+ sameOrBefore?: string;
122
+ after?: string;
123
+ sameOrAfter?: string;
124
+ }
105
125
  export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
106
126
  constructor();
107
127
  integer(): this;
@@ -177,7 +197,7 @@ export declare class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyB
177
197
  maxLength(maxItems: number): this;
178
198
  length(minItems: number, maxItems: number): this;
179
199
  exactLength(length: number): this;
180
- unique(uniqueItems: number): this;
200
+ unique(): this;
181
201
  }
182
202
  export declare class JsonSchemaSet2Builder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<Iterable<IN>, Set2<OUT>, Opt> {
183
203
  constructor(itemsSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>);
@@ -188,13 +208,14 @@ export declare class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder<string
188
208
  constructor();
189
209
  }
190
210
  export declare class JsonSchemaEnumBuilder<IN extends string | number | boolean | null, OUT extends IN = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
191
- constructor(enumValues: readonly IN[], opt?: JsonBuilderRuleOpt);
211
+ constructor(enumValues: readonly IN[], baseType: EnumBaseType, opt?: JsonBuilderRuleOpt);
192
212
  branded<B extends IN>(): JsonSchemaEnumBuilder<B | IN, B, Opt>;
193
213
  }
214
+ type EnumBaseType = 'string' | 'number' | 'other';
194
215
  export interface JsonSchema<IN = unknown, OUT = IN> {
195
216
  readonly in?: IN;
196
217
  readonly out?: OUT;
197
- $schema?: AnyObject;
218
+ $schema?: string;
198
219
  $id?: string;
199
220
  title?: string;
200
221
  description?: string;
@@ -232,10 +253,14 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
232
253
  exclusiveMinimum?: number;
233
254
  maximum?: number;
234
255
  exclusiveMaximum?: number;
256
+ minItems?: number;
257
+ maxItems?: number;
258
+ uniqueItems?: boolean;
235
259
  enum?: any;
260
+ email?: JsonSchemaStringEmailOptions;
236
261
  Set2?: JsonSchema;
237
262
  Buffer?: true;
238
- IsoDate?: true;
263
+ IsoDate?: JsonSchemaIsoDateOptions;
239
264
  IsoDateTime?: true;
240
265
  instanceof?: string | string[];
241
266
  transform?: {
@@ -4,9 +4,9 @@ import { _isUndefined, _numberEnumValues, _stringEnumValues, getEnumType, } from
4
4
  import { _uniq } from '@naturalcycles/js-lib/array';
5
5
  import { _assert } from '@naturalcycles/js-lib/error';
6
6
  import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object';
7
- import { JWT_REGEX, } from '@naturalcycles/js-lib/types';
7
+ import { _objectAssign, JWT_REGEX, } from '@naturalcycles/js-lib/types';
8
8
  import { TIMEZONES } from '../timezones.js';
9
- import { JSON_SCHEMA_ORDER, mergeJsonSchemaObjects } from './jsonSchemaBuilder.util.js';
9
+ import { isEveryItemNumber, isEveryItemString, JSON_SCHEMA_ORDER, mergeJsonSchemaObjects, } from './jsonSchemaBuilder.util.js';
10
10
  export const j = {
11
11
  string() {
12
12
  return new JsonSchemaStringBuilder();
@@ -35,20 +35,29 @@ export const j = {
35
35
  },
36
36
  enum(input, opt) {
37
37
  let enumValues;
38
+ let baseType = 'other';
38
39
  if (Array.isArray(input)) {
39
40
  enumValues = input;
41
+ if (isEveryItemNumber(input)) {
42
+ baseType = 'number';
43
+ }
44
+ else if (isEveryItemString(input)) {
45
+ baseType = 'string';
46
+ }
40
47
  }
41
48
  else if (typeof input === 'object') {
42
49
  const enumType = getEnumType(input);
43
50
  if (enumType === 'NumberEnum') {
44
51
  enumValues = _numberEnumValues(input);
52
+ baseType = 'number';
45
53
  }
46
54
  else if (enumType === 'StringEnum') {
47
55
  enumValues = _stringEnumValues(input);
56
+ baseType = 'string';
48
57
  }
49
58
  }
50
59
  _assert(enumValues, 'Unsupported enum input');
51
- return new JsonSchemaEnumBuilder(enumValues, opt);
60
+ return new JsonSchemaEnumBuilder(enumValues, baseType, opt);
52
61
  },
53
62
  oneOf(items) {
54
63
  const schemas = items.map(b => b.build());
@@ -94,14 +103,14 @@ export class JsonSchemaAnyBuilder {
94
103
  * ```
95
104
  */
96
105
  isOfType() {
97
- Object.assign(this.schema, { hasIsOfTypeCheck: true });
106
+ _objectAssign(this.schema, { hasIsOfTypeCheck: true });
98
107
  return this;
99
108
  }
100
109
  getSchema() {
101
110
  return this.schema;
102
111
  }
103
112
  $schema($schema) {
104
- Object.assign(this.schema, { $schema });
113
+ _objectAssign(this.schema, { $schema });
105
114
  return this;
106
115
  }
107
116
  $schemaDraft7() {
@@ -109,31 +118,31 @@ export class JsonSchemaAnyBuilder {
109
118
  return this;
110
119
  }
111
120
  $id($id) {
112
- Object.assign(this.schema, { $id });
121
+ _objectAssign(this.schema, { $id });
113
122
  return this;
114
123
  }
115
124
  title(title) {
116
- Object.assign(this.schema, { title });
125
+ _objectAssign(this.schema, { title });
117
126
  return this;
118
127
  }
119
128
  description(description) {
120
- Object.assign(this.schema, { description });
129
+ _objectAssign(this.schema, { description });
121
130
  return this;
122
131
  }
123
132
  deprecated(deprecated = true) {
124
- Object.assign(this.schema, { deprecated });
133
+ _objectAssign(this.schema, { deprecated });
125
134
  return this;
126
135
  }
127
136
  type(type) {
128
- Object.assign(this.schema, { type });
137
+ _objectAssign(this.schema, { type });
129
138
  return this;
130
139
  }
131
140
  default(v) {
132
- Object.assign(this.schema, { default: v });
141
+ _objectAssign(this.schema, { default: v });
133
142
  return this;
134
143
  }
135
144
  instanceof(of) {
136
- Object.assign(this.schema, { type: 'object', instanceof: of });
145
+ _objectAssign(this.schema, { type: 'object', instanceof: of });
137
146
  return this;
138
147
  }
139
148
  optional() {
@@ -182,53 +191,59 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
182
191
  this.setErrorMessage('pattern', `is not a valid ${opt.name}`);
183
192
  if (opt?.msg)
184
193
  this.setErrorMessage('pattern', opt.msg);
185
- Object.assign(this.schema, { pattern });
194
+ _objectAssign(this.schema, { pattern });
186
195
  return this;
187
196
  }
188
197
  minLength(minLength) {
189
- Object.assign(this.schema, { minLength });
198
+ _objectAssign(this.schema, { minLength });
190
199
  return this;
191
200
  }
192
201
  maxLength(maxLength) {
193
- Object.assign(this.schema, { maxLength });
202
+ _objectAssign(this.schema, { maxLength });
194
203
  return this;
195
204
  }
196
205
  length(minLength, maxLength) {
197
- Object.assign(this.schema, { minLength, maxLength });
206
+ _objectAssign(this.schema, { minLength, maxLength });
198
207
  return this;
199
208
  }
200
209
  email(opt) {
201
210
  const defaultOptions = { checkTLD: true };
202
- Object.assign(this.schema, { email: { ...defaultOptions, ...opt } });
211
+ _objectAssign(this.schema, { email: { ...defaultOptions, ...opt } });
203
212
  // from `ajv-formats`
204
213
  const regex = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
205
214
  return this.regex(regex, { msg: 'is not a valid email address' }).trim().toLowerCase();
206
215
  }
207
216
  trim() {
208
- Object.assign(this.schema, { transform: { ...this.schema.transform, trim: true } });
217
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, trim: true } });
209
218
  return this;
210
219
  }
211
220
  toLowerCase() {
212
- Object.assign(this.schema, { transform: { ...this.schema.transform, toLowerCase: true } });
221
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, toLowerCase: true } });
213
222
  return this;
214
223
  }
215
224
  toUpperCase() {
216
- Object.assign(this.schema, { transform: { ...this.schema.transform, toUpperCase: true } });
225
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, toUpperCase: true } });
217
226
  return this;
218
227
  }
219
228
  truncate(toLength) {
220
- Object.assign(this.schema, { transform: { ...this.schema.transform, truncate: toLength } });
229
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, truncate: toLength } });
221
230
  return this;
222
231
  }
223
232
  branded() {
224
233
  return this;
225
234
  }
235
+ /**
236
+ * Validates that the input is a fully-specified YYYY-MM-DD formatted valid IsoDate value.
237
+ *
238
+ * All previous expectations in the schema chain are dropped - including `.optional()` -
239
+ * because this call effectively starts a new schema chain.
240
+ */
226
241
  isoDate() {
227
- Object.assign(this.schema, { IsoDate: true });
228
- return this.branded();
242
+ _objectAssign(this.schema, { IsoDate: {} });
243
+ return new JsonSchemaIsoDateBuilder();
229
244
  }
230
245
  isoDateTime() {
231
- Object.assign(this.schema, { IsoDateTime: true });
246
+ _objectAssign(this.schema, { IsoDateTime: true });
232
247
  return this.branded();
233
248
  }
234
249
  /**
@@ -289,6 +304,39 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
289
304
  return j.enum(TIMEZONES, { msg: 'is an invalid IANA timezone' }).branded();
290
305
  }
291
306
  }
307
+ export class JsonSchemaIsoDateBuilder extends JsonSchemaAnyBuilder {
308
+ constructor() {
309
+ super({
310
+ type: 'string',
311
+ IsoDate: {},
312
+ });
313
+ }
314
+ before(date) {
315
+ _objectAssign(this.schema.IsoDate, { before: date });
316
+ return this;
317
+ }
318
+ sameOrBefore(date) {
319
+ _objectAssign(this.schema.IsoDate, { sameOrBefore: date });
320
+ return this;
321
+ }
322
+ after(date) {
323
+ _objectAssign(this.schema.IsoDate, { after: date });
324
+ return this;
325
+ }
326
+ sameOrAfter(date) {
327
+ _objectAssign(this.schema.IsoDate, { sameOrAfter: date });
328
+ return this;
329
+ }
330
+ between(fromDate, toDate, incl) {
331
+ if (incl === '[)') {
332
+ _objectAssign(this.schema.IsoDate, { sameOrAfter: fromDate, before: toDate });
333
+ }
334
+ else if (incl === '[]') {
335
+ _objectAssign(this.schema.IsoDate, { sameOrAfter: fromDate, sameOrBefore: toDate });
336
+ }
337
+ return this;
338
+ }
339
+ }
292
340
  export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
293
341
  constructor() {
294
342
  super({
@@ -296,30 +344,30 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
296
344
  });
297
345
  }
298
346
  integer() {
299
- Object.assign(this.schema, { type: 'integer' });
347
+ _objectAssign(this.schema, { type: 'integer' });
300
348
  return this;
301
349
  }
302
350
  branded() {
303
351
  return this;
304
352
  }
305
353
  multipleOf(multipleOf) {
306
- Object.assign(this.schema, { multipleOf });
354
+ _objectAssign(this.schema, { multipleOf });
307
355
  return this;
308
356
  }
309
357
  min(minimum) {
310
- Object.assign(this.schema, { minimum });
358
+ _objectAssign(this.schema, { minimum });
311
359
  return this;
312
360
  }
313
361
  exclusiveMin(exclusiveMinimum) {
314
- Object.assign(this.schema, { exclusiveMinimum });
362
+ _objectAssign(this.schema, { exclusiveMinimum });
315
363
  return this;
316
364
  }
317
365
  max(maximum) {
318
- Object.assign(this.schema, { maximum });
366
+ _objectAssign(this.schema, { maximum });
319
367
  return this;
320
368
  }
321
369
  exclusiveMax(exclusiveMaximum) {
322
- Object.assign(this.schema, { exclusiveMaximum });
370
+ _objectAssign(this.schema, { exclusiveMaximum });
323
371
  return this;
324
372
  }
325
373
  lessThan(value) {
@@ -427,12 +475,12 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
427
475
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
428
476
  */
429
477
  allowAdditionalProperties() {
430
- Object.assign(this.schema, { additionalProperties: true });
478
+ _objectAssign(this.schema, { additionalProperties: true });
431
479
  return this;
432
480
  }
433
481
  extend(props) {
434
482
  const newBuilder = new JsonSchemaObjectBuilder();
435
- Object.assign(newBuilder.schema, _deepCopy(this.schema));
483
+ _objectAssign(newBuilder.schema, _deepCopy(this.schema));
436
484
  const incomingSchemaBuilder = new JsonSchemaObjectBuilder(props);
437
485
  mergeJsonSchemaObjects(newBuilder.schema, incomingSchemaBuilder.schema);
438
486
  return newBuilder;
@@ -481,12 +529,12 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
481
529
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
482
530
  */
483
531
  allowAdditionalProperties() {
484
- Object.assign(this.schema, { additionalProperties: true });
532
+ _objectAssign(this.schema, { additionalProperties: true });
485
533
  return this;
486
534
  }
487
535
  extend(props) {
488
536
  const newBuilder = new JsonSchemaObjectInferringBuilder();
489
- Object.assign(newBuilder.schema, _deepCopy(this.schema));
537
+ _objectAssign(newBuilder.schema, _deepCopy(this.schema));
490
538
  const incomingSchemaBuilder = new JsonSchemaObjectInferringBuilder(props);
491
539
  mergeJsonSchemaObjects(newBuilder.schema, incomingSchemaBuilder.schema);
492
540
  return newBuilder;
@@ -511,11 +559,11 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
511
559
  });
512
560
  }
513
561
  minLength(minItems) {
514
- Object.assign(this.schema, { minItems });
562
+ _objectAssign(this.schema, { minItems });
515
563
  return this;
516
564
  }
517
565
  maxLength(maxItems) {
518
- Object.assign(this.schema, { maxItems });
566
+ _objectAssign(this.schema, { maxItems });
519
567
  return this;
520
568
  }
521
569
  length(minItems, maxItems) {
@@ -524,8 +572,8 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
524
572
  exactLength(length) {
525
573
  return this.minLength(length).maxLength(length);
526
574
  }
527
- unique(uniqueItems) {
528
- Object.assign(this.schema, { uniqueItems });
575
+ unique() {
576
+ _objectAssign(this.schema, { uniqueItems: true });
529
577
  return this;
530
578
  }
531
579
  }
@@ -537,11 +585,11 @@ export class JsonSchemaSet2Builder extends JsonSchemaAnyBuilder {
537
585
  });
538
586
  }
539
587
  min(minItems) {
540
- Object.assign(this.schema, { minItems });
588
+ _objectAssign(this.schema, { minItems });
541
589
  return this;
542
590
  }
543
591
  max(maxItems) {
544
- Object.assign(this.schema, { maxItems });
592
+ _objectAssign(this.schema, { maxItems });
545
593
  return this;
546
594
  }
547
595
  }
@@ -553,8 +601,15 @@ export class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder {
553
601
  }
554
602
  }
555
603
  export class JsonSchemaEnumBuilder extends JsonSchemaAnyBuilder {
556
- constructor(enumValues, opt) {
557
- super({ enum: enumValues });
604
+ constructor(enumValues, baseType, opt) {
605
+ const jsonSchema = { enum: enumValues };
606
+ // Specifying the base type helps in cases when we ask Ajv to coerce the types.
607
+ // Having only the `enum` in the schema does not trigger a coercion in Ajv.
608
+ if (baseType === 'string')
609
+ jsonSchema.type = 'string';
610
+ if (baseType === 'number')
611
+ jsonSchema.type = 'number';
612
+ super(jsonSchema);
558
613
  if (opt?.name)
559
614
  this.setErrorMessage('pattern', `is not a valid ${opt.name}`);
560
615
  if (opt?.msg)
@@ -7,3 +7,5 @@ export declare const JSON_SCHEMA_ORDER: string[];
7
7
  * API similar to Object.assign(s1, s2)
8
8
  */
9
9
  export declare function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObject>(schema1: JsonSchema<T1>, schema2: JsonSchema<T2>): JsonSchema<T1 & T2>;
10
+ export declare function isEveryItemString(arr: any[]): boolean;
11
+ export declare function isEveryItemNumber(arr: any[]): boolean;
@@ -63,3 +63,17 @@ export function mergeJsonSchemaObjects(schema1, schema2) {
63
63
  // `additionalProperties` remains the same
64
64
  return _filterNullishValues(s1, { mutate: true });
65
65
  }
66
+ export function isEveryItemString(arr) {
67
+ for (let i = 0; i <= arr.length; ++i) {
68
+ if (typeof arr[i] !== 'string')
69
+ return false;
70
+ }
71
+ return true;
72
+ }
73
+ export function isEveryItemNumber(arr) {
74
+ for (let i = 0; i <= arr.length; ++i) {
75
+ if (typeof arr[i] !== 'number')
76
+ return false;
77
+ }
78
+ return true;
79
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.46.3",
4
+ "version": "15.47.1",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
@@ -3,7 +3,7 @@ import { Set2 } from '@naturalcycles/js-lib/object'
3
3
  import { _substringAfterLast } from '@naturalcycles/js-lib/string'
4
4
  import { _, Ajv, type Options, type ValidateFunction } from 'ajv'
5
5
  import { validTLDs } from '../tlds.js'
6
- import type { JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
6
+ import type { JsonSchemaIsoDateOptions, JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
7
7
 
8
8
  /* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
9
9
  // oxlint-disable unicorn/prefer-code-point
@@ -24,6 +24,11 @@ const AJV_NON_MUTATING_OPTIONS: Options = {
24
24
  useDefaults: false,
25
25
  }
26
26
 
27
+ const AJV_MUTATING_COERCING_OPTIONS: Options = {
28
+ ...AJV_OPTIONS,
29
+ coerceTypes: true,
30
+ }
31
+
27
32
  /**
28
33
  * Return cached instance of Ajv with default (recommended) options.
29
34
  *
@@ -40,6 +45,14 @@ export const getAjv = _lazyValue(createAjv)
40
45
  */
41
46
  export const getNonMutatingAjv = _lazyValue(() => createAjv(AJV_NON_MUTATING_OPTIONS))
42
47
 
48
+ /**
49
+ * Returns cached instance of Ajv, which is coercing data.
50
+ *
51
+ * To be used in places where we know that we are going to receive data with the wrong type,
52
+ * typically: request path params and request query params.
53
+ */
54
+ export const getCoercingAjv = _lazyValue(() => createAjv(AJV_MUTATING_COERCING_OPTIONS))
55
+
43
56
  /**
44
57
  * Create Ajv with modified defaults.
45
58
  *
@@ -255,16 +268,77 @@ export function createAjv(opt?: Options): Ajv {
255
268
  type: 'string',
256
269
  modifying: false,
257
270
  errors: true,
258
- schemaType: 'boolean',
259
- validate: function validate(_opt: true, data: string, _schema, ctx) {
271
+ schemaType: 'object',
272
+ validate: function validate(opt: JsonSchemaIsoDateOptions, data: string, _schema, ctx) {
273
+ const hasOptions = Object.keys(opt).length > 0
274
+
260
275
  const isValid = isIsoDateValid(data)
261
- if (isValid) return true
262
- ;(validate as any).errors = [
263
- {
264
- instancePath: ctx?.instancePath ?? '',
265
- message: `is an invalid IsoDate`,
266
- },
267
- ]
276
+ if (!isValid) {
277
+ ;(validate as any).errors = [
278
+ {
279
+ instancePath: ctx?.instancePath ?? '',
280
+ message: `is an invalid IsoDate`,
281
+ },
282
+ ]
283
+
284
+ return false
285
+ }
286
+
287
+ if (!hasOptions) return true
288
+
289
+ const { before, sameOrBefore, after, sameOrAfter } = opt
290
+ const errors: any[] = []
291
+
292
+ if (before) {
293
+ const isParamValid = isIsoDateValid(before)
294
+ const isRuleValid = isParamValid && before > data
295
+
296
+ if (!isRuleValid) {
297
+ errors.push({
298
+ instancePath: ctx?.instancePath ?? '',
299
+ message: `is not before ${before}`,
300
+ })
301
+ }
302
+ }
303
+
304
+ if (sameOrBefore) {
305
+ const isParamValid = isIsoDateValid(sameOrBefore)
306
+ const isRuleValid = isParamValid && sameOrBefore >= data
307
+
308
+ if (!isRuleValid) {
309
+ errors.push({
310
+ instancePath: ctx?.instancePath ?? '',
311
+ message: `is not the same or before ${sameOrBefore}`,
312
+ })
313
+ }
314
+ }
315
+
316
+ if (after) {
317
+ const isParamValid = isIsoDateValid(after)
318
+ const isRuleValid = isParamValid && after < data
319
+
320
+ if (!isRuleValid) {
321
+ errors.push({
322
+ instancePath: ctx?.instancePath ?? '',
323
+ message: `is not after ${after}`,
324
+ })
325
+ }
326
+ }
327
+
328
+ if (sameOrAfter) {
329
+ const isParamValid = isIsoDateValid(sameOrAfter)
330
+ const isRuleValid = isParamValid && sameOrAfter <= data
331
+
332
+ if (!isRuleValid) {
333
+ errors.push({
334
+ instancePath: ctx?.instancePath ?? '',
335
+ message: `is not the same or after ${sameOrAfter}`,
336
+ })
337
+ }
338
+ }
339
+
340
+ if (errors.length === 0) return true
341
+ ;(validate as any).errors = errors
268
342
  return false
269
343
  },
270
344
  })
@@ -12,6 +12,7 @@ import { _assert } from '@naturalcycles/js-lib/error'
12
12
  import type { Set2 } from '@naturalcycles/js-lib/object'
13
13
  import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object'
14
14
  import {
15
+ _objectAssign,
15
16
  type AnyObject,
16
17
  type BaseDBEntity,
17
18
  type IANATimezone,
@@ -26,7 +27,12 @@ import {
26
27
  type UnixTimestampMillis,
27
28
  } from '@naturalcycles/js-lib/types'
28
29
  import { TIMEZONES } from '../timezones.js'
29
- import { JSON_SCHEMA_ORDER, mergeJsonSchemaObjects } from './jsonSchemaBuilder.util.js'
30
+ import {
31
+ isEveryItemNumber,
32
+ isEveryItemString,
33
+ JSON_SCHEMA_ORDER,
34
+ mergeJsonSchemaObjects,
35
+ } from './jsonSchemaBuilder.util.js'
30
36
 
31
37
  export const j = {
32
38
  string(): JsonSchemaStringBuilder<string, string, false> {
@@ -78,20 +84,28 @@ export const j = {
78
84
  : never
79
85
  > {
80
86
  let enumValues: readonly (string | number | boolean | null)[] | undefined
87
+ let baseType: EnumBaseType = 'other'
81
88
 
82
89
  if (Array.isArray(input)) {
83
90
  enumValues = input
91
+ if (isEveryItemNumber(input)) {
92
+ baseType = 'number'
93
+ } else if (isEveryItemString(input)) {
94
+ baseType = 'string'
95
+ }
84
96
  } else if (typeof input === 'object') {
85
97
  const enumType = getEnumType(input)
86
98
  if (enumType === 'NumberEnum') {
87
99
  enumValues = _numberEnumValues(input as NumberEnum)
100
+ baseType = 'number'
88
101
  } else if (enumType === 'StringEnum') {
89
102
  enumValues = _stringEnumValues(input as StringEnum)
103
+ baseType = 'string'
90
104
  }
91
105
  }
92
106
 
93
107
  _assert(enumValues, 'Unsupported enum input')
94
- return new JsonSchemaEnumBuilder(enumValues as any, opt)
108
+ return new JsonSchemaEnumBuilder(enumValues as any, baseType, opt)
95
109
  },
96
110
 
97
111
  oneOf<
@@ -144,7 +158,7 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> {
144
158
  * ```
145
159
  */
146
160
  isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never {
147
- Object.assign(this.schema, { hasIsOfTypeCheck: true })
161
+ _objectAssign(this.schema, { hasIsOfTypeCheck: true })
148
162
  return this as any
149
163
  }
150
164
 
@@ -153,7 +167,7 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> {
153
167
  }
154
168
 
155
169
  $schema($schema: string): this {
156
- Object.assign(this.schema, { $schema })
170
+ _objectAssign(this.schema, { $schema })
157
171
  return this
158
172
  }
159
173
 
@@ -163,37 +177,37 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> {
163
177
  }
164
178
 
165
179
  $id($id: string): this {
166
- Object.assign(this.schema, { $id })
180
+ _objectAssign(this.schema, { $id })
167
181
  return this
168
182
  }
169
183
 
170
184
  title(title: string): this {
171
- Object.assign(this.schema, { title })
185
+ _objectAssign(this.schema, { title })
172
186
  return this
173
187
  }
174
188
 
175
189
  description(description: string): this {
176
- Object.assign(this.schema, { description })
190
+ _objectAssign(this.schema, { description })
177
191
  return this
178
192
  }
179
193
 
180
194
  deprecated(deprecated = true): this {
181
- Object.assign(this.schema, { deprecated })
195
+ _objectAssign(this.schema, { deprecated })
182
196
  return this
183
197
  }
184
198
 
185
199
  type(type: string): this {
186
- Object.assign(this.schema, { type })
200
+ _objectAssign(this.schema, { type })
187
201
  return this
188
202
  }
189
203
 
190
204
  default(v: any): this {
191
- Object.assign(this.schema, { default: v })
205
+ _objectAssign(this.schema, { default: v })
192
206
  return this
193
207
  }
194
208
 
195
209
  instanceof(of: string): this {
196
- Object.assign(this.schema, { type: 'object', instanceof: of })
210
+ _objectAssign(this.schema, { type: 'object', instanceof: of })
197
211
  return this
198
212
  }
199
213
 
@@ -253,28 +267,28 @@ export class JsonSchemaStringBuilder<
253
267
  pattern(pattern: string, opt?: JsonBuilderRuleOpt): this {
254
268
  if (opt?.name) this.setErrorMessage('pattern', `is not a valid ${opt.name}`)
255
269
  if (opt?.msg) this.setErrorMessage('pattern', opt.msg)
256
- Object.assign(this.schema, { pattern })
270
+ _objectAssign(this.schema, { pattern })
257
271
  return this
258
272
  }
259
273
 
260
274
  minLength(minLength: number): this {
261
- Object.assign(this.schema, { minLength })
275
+ _objectAssign(this.schema, { minLength })
262
276
  return this
263
277
  }
264
278
 
265
279
  maxLength(maxLength: number): this {
266
- Object.assign(this.schema, { maxLength })
280
+ _objectAssign(this.schema, { maxLength })
267
281
  return this
268
282
  }
269
283
 
270
284
  length(minLength: number, maxLength: number): this {
271
- Object.assign(this.schema, { minLength, maxLength })
285
+ _objectAssign(this.schema, { minLength, maxLength })
272
286
  return this
273
287
  }
274
288
 
275
289
  email(opt?: Partial<JsonSchemaStringEmailOptions>): this {
276
290
  const defaultOptions: JsonSchemaStringEmailOptions = { checkTLD: true }
277
- Object.assign(this.schema, { email: { ...defaultOptions, ...opt } })
291
+ _objectAssign(this.schema, { email: { ...defaultOptions, ...opt } })
278
292
 
279
293
  // from `ajv-formats`
280
294
  const regex =
@@ -283,22 +297,22 @@ export class JsonSchemaStringBuilder<
283
297
  }
284
298
 
285
299
  trim(): this {
286
- Object.assign(this.schema, { transform: { ...this.schema.transform, trim: true } })
300
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, trim: true } })
287
301
  return this
288
302
  }
289
303
 
290
304
  toLowerCase(): this {
291
- Object.assign(this.schema, { transform: { ...this.schema.transform, toLowerCase: true } })
305
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, toLowerCase: true } })
292
306
  return this
293
307
  }
294
308
 
295
309
  toUpperCase(): this {
296
- Object.assign(this.schema, { transform: { ...this.schema.transform, toUpperCase: true } })
310
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, toUpperCase: true } })
297
311
  return this
298
312
  }
299
313
 
300
314
  truncate(toLength: number): this {
301
- Object.assign(this.schema, { transform: { ...this.schema.transform, truncate: toLength } })
315
+ _objectAssign(this.schema, { transform: { ...this.schema.transform, truncate: toLength } })
302
316
  return this
303
317
  }
304
318
 
@@ -306,13 +320,19 @@ export class JsonSchemaStringBuilder<
306
320
  return this as unknown as JsonSchemaStringBuilder<B, B, Opt>
307
321
  }
308
322
 
309
- isoDate(): JsonSchemaStringBuilder<IsoDate | IN, IsoDate, Opt> {
310
- Object.assign(this.schema, { IsoDate: true })
311
- return this.branded<IsoDate>()
323
+ /**
324
+ * Validates that the input is a fully-specified YYYY-MM-DD formatted valid IsoDate value.
325
+ *
326
+ * All previous expectations in the schema chain are dropped - including `.optional()` -
327
+ * because this call effectively starts a new schema chain.
328
+ */
329
+ isoDate(): JsonSchemaIsoDateBuilder {
330
+ _objectAssign(this.schema, { IsoDate: {} })
331
+ return new JsonSchemaIsoDateBuilder()
312
332
  }
313
333
 
314
334
  isoDateTime(): JsonSchemaStringBuilder<IsoDateTime | IN, IsoDateTime, Opt> {
315
- Object.assign(this.schema, { IsoDateTime: true })
335
+ _objectAssign(this.schema, { IsoDateTime: true })
316
336
  return this.branded<IsoDateTime>()
317
337
  }
318
338
 
@@ -392,6 +412,56 @@ export interface JsonSchemaStringEmailOptions {
392
412
  checkTLD: boolean
393
413
  }
394
414
 
415
+ export class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<
416
+ string | IsoDate,
417
+ IsoDate,
418
+ Opt
419
+ > {
420
+ constructor() {
421
+ super({
422
+ type: 'string',
423
+ IsoDate: {},
424
+ })
425
+ }
426
+
427
+ before(date: string): this {
428
+ _objectAssign(this.schema.IsoDate!, { before: date })
429
+ return this
430
+ }
431
+
432
+ sameOrBefore(date: string): this {
433
+ _objectAssign(this.schema.IsoDate!, { sameOrBefore: date })
434
+ return this
435
+ }
436
+
437
+ after(date: string): this {
438
+ _objectAssign(this.schema.IsoDate!, { after: date })
439
+ return this
440
+ }
441
+
442
+ sameOrAfter(date: string): this {
443
+ _objectAssign(this.schema.IsoDate!, { sameOrAfter: date })
444
+ return this
445
+ }
446
+
447
+ between(fromDate: string, toDate: string, incl: Inclusiveness): this {
448
+ if (incl === '[)') {
449
+ _objectAssign(this.schema.IsoDate!, { sameOrAfter: fromDate, before: toDate })
450
+ } else if (incl === '[]') {
451
+ _objectAssign(this.schema.IsoDate!, { sameOrAfter: fromDate, sameOrBefore: toDate })
452
+ }
453
+
454
+ return this
455
+ }
456
+ }
457
+
458
+ export interface JsonSchemaIsoDateOptions {
459
+ before?: string
460
+ sameOrBefore?: string
461
+ after?: string
462
+ sameOrAfter?: string
463
+ }
464
+
395
465
  export class JsonSchemaNumberBuilder<
396
466
  IN extends number = number,
397
467
  OUT = IN,
@@ -404,7 +474,7 @@ export class JsonSchemaNumberBuilder<
404
474
  }
405
475
 
406
476
  integer(): this {
407
- Object.assign(this.schema, { type: 'integer' })
477
+ _objectAssign(this.schema, { type: 'integer' })
408
478
  return this
409
479
  }
410
480
 
@@ -413,27 +483,27 @@ export class JsonSchemaNumberBuilder<
413
483
  }
414
484
 
415
485
  multipleOf(multipleOf: number): this {
416
- Object.assign(this.schema, { multipleOf })
486
+ _objectAssign(this.schema, { multipleOf })
417
487
  return this
418
488
  }
419
489
 
420
490
  min(minimum: number): this {
421
- Object.assign(this.schema, { minimum })
491
+ _objectAssign(this.schema, { minimum })
422
492
  return this
423
493
  }
424
494
 
425
495
  exclusiveMin(exclusiveMinimum: number): this {
426
- Object.assign(this.schema, { exclusiveMinimum })
496
+ _objectAssign(this.schema, { exclusiveMinimum })
427
497
  return this
428
498
  }
429
499
 
430
500
  max(maximum: number): this {
431
- Object.assign(this.schema, { maximum })
501
+ _objectAssign(this.schema, { maximum })
432
502
  return this
433
503
  }
434
504
 
435
505
  exclusiveMax(exclusiveMaximum: number): this {
436
- Object.assign(this.schema, { exclusiveMaximum })
506
+ _objectAssign(this.schema, { exclusiveMaximum })
437
507
  return this
438
508
  }
439
509
 
@@ -575,7 +645,7 @@ export class JsonSchemaObjectBuilder<
575
645
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
576
646
  */
577
647
  allowAdditionalProperties(): this {
578
- Object.assign(this.schema, { additionalProperties: true })
648
+ _objectAssign(this.schema, { additionalProperties: true })
579
649
  return this
580
650
  }
581
651
 
@@ -583,7 +653,7 @@ export class JsonSchemaObjectBuilder<
583
653
  props: AnyObject,
584
654
  ): JsonSchemaObjectBuilder<IN & IN2, OUT & IN2, Opt> {
585
655
  const newBuilder = new JsonSchemaObjectBuilder<IN & IN2, OUT & IN2, Opt>()
586
- Object.assign(newBuilder.schema, _deepCopy(this.schema))
656
+ _objectAssign(newBuilder.schema, _deepCopy(this.schema))
587
657
 
588
658
  const incomingSchemaBuilder = new JsonSchemaObjectBuilder<IN2, IN2, false>(props)
589
659
  mergeJsonSchemaObjects(newBuilder.schema as any, incomingSchemaBuilder.schema as any)
@@ -675,7 +745,7 @@ export class JsonSchemaObjectInferringBuilder<
675
745
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
676
746
  */
677
747
  allowAdditionalProperties(): this {
678
- Object.assign(this.schema, { additionalProperties: true })
748
+ _objectAssign(this.schema, { additionalProperties: true })
679
749
  return this
680
750
  }
681
751
 
@@ -692,7 +762,7 @@ export class JsonSchemaObjectInferringBuilder<
692
762
  Opt
693
763
  > {
694
764
  const newBuilder = new JsonSchemaObjectInferringBuilder<PROPS, Opt>()
695
- Object.assign(newBuilder.schema, _deepCopy(this.schema))
765
+ _objectAssign(newBuilder.schema, _deepCopy(this.schema))
696
766
 
697
767
  const incomingSchemaBuilder = new JsonSchemaObjectInferringBuilder<NEW_PROPS, false>(props)
698
768
  mergeJsonSchemaObjects(newBuilder.schema as any, incomingSchemaBuilder.schema as any)
@@ -731,12 +801,12 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
731
801
  }
732
802
 
733
803
  minLength(minItems: number): this {
734
- Object.assign(this.schema, { minItems })
804
+ _objectAssign(this.schema, { minItems })
735
805
  return this
736
806
  }
737
807
 
738
808
  maxLength(maxItems: number): this {
739
- Object.assign(this.schema, { maxItems })
809
+ _objectAssign(this.schema, { maxItems })
740
810
  return this
741
811
  }
742
812
 
@@ -748,8 +818,8 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
748
818
  return this.minLength(length).maxLength(length)
749
819
  }
750
820
 
751
- unique(uniqueItems: number): this {
752
- Object.assign(this.schema, { uniqueItems })
821
+ unique(): this {
822
+ _objectAssign(this.schema, { uniqueItems: true })
753
823
  return this
754
824
  }
755
825
  }
@@ -767,12 +837,12 @@ export class JsonSchemaSet2Builder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<
767
837
  }
768
838
 
769
839
  min(minItems: number): this {
770
- Object.assign(this.schema, { minItems })
840
+ _objectAssign(this.schema, { minItems })
771
841
  return this
772
842
  }
773
843
 
774
844
  max(maxItems: number): this {
775
- Object.assign(this.schema, { maxItems })
845
+ _objectAssign(this.schema, { maxItems })
776
846
  return this
777
847
  }
778
848
  }
@@ -794,8 +864,15 @@ export class JsonSchemaEnumBuilder<
794
864
  OUT extends IN = IN,
795
865
  Opt extends boolean = false,
796
866
  > extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
797
- constructor(enumValues: readonly IN[], opt?: JsonBuilderRuleOpt) {
798
- super({ enum: enumValues })
867
+ constructor(enumValues: readonly IN[], baseType: EnumBaseType, opt?: JsonBuilderRuleOpt) {
868
+ const jsonSchema: JsonSchema = { enum: enumValues }
869
+ // Specifying the base type helps in cases when we ask Ajv to coerce the types.
870
+ // Having only the `enum` in the schema does not trigger a coercion in Ajv.
871
+ if (baseType === 'string') jsonSchema.type = 'string'
872
+ if (baseType === 'number') jsonSchema.type = 'number'
873
+
874
+ super(jsonSchema)
875
+
799
876
  if (opt?.name) this.setErrorMessage('pattern', `is not a valid ${opt.name}`)
800
877
  if (opt?.msg) this.setErrorMessage('enum', opt.msg)
801
878
  }
@@ -805,11 +882,13 @@ export class JsonSchemaEnumBuilder<
805
882
  }
806
883
  }
807
884
 
885
+ type EnumBaseType = 'string' | 'number' | 'other'
886
+
808
887
  export interface JsonSchema<IN = unknown, OUT = IN> {
809
888
  readonly in?: IN
810
889
  readonly out?: OUT
811
890
 
812
- $schema?: AnyObject
891
+ $schema?: string
813
892
  $id?: string
814
893
  title?: string
815
894
  description?: string
@@ -857,14 +936,18 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
857
936
  exclusiveMinimum?: number
858
937
  maximum?: number
859
938
  exclusiveMaximum?: number
939
+ minItems?: number
940
+ maxItems?: number
941
+ uniqueItems?: boolean
860
942
 
861
943
  enum?: any
862
944
 
863
945
  // Below we add custom Ajv keywords
864
946
 
947
+ email?: JsonSchemaStringEmailOptions
865
948
  Set2?: JsonSchema
866
949
  Buffer?: true
867
- IsoDate?: true
950
+ IsoDate?: JsonSchemaIsoDateOptions
868
951
  IsoDateTime?: true
869
952
  instanceof?: string | string[]
870
953
  transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
@@ -76,3 +76,17 @@ export function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObjec
76
76
 
77
77
  return _filterNullishValues(s1, { mutate: true })
78
78
  }
79
+
80
+ export function isEveryItemString(arr: any[]): boolean {
81
+ for (let i = 0; i <= arr.length; ++i) {
82
+ if (typeof arr[i] !== 'string') return false
83
+ }
84
+ return true
85
+ }
86
+
87
+ export function isEveryItemNumber(arr: any[]): boolean {
88
+ for (let i = 0; i <= arr.length; ++i) {
89
+ if (typeof arr[i] !== 'number') return false
90
+ }
91
+ return true
92
+ }