@naturalcycles/nodejs-lib 15.60.0 → 15.61.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 { _lazyValue } from '@naturalcycles/js-lib';
1
+ import { _isBetween, _lazyValue } from '@naturalcycles/js-lib';
2
2
  import { Set2 } from '@naturalcycles/js-lib/object';
3
3
  import { _substringAfterLast } from '@naturalcycles/js-lib/string';
4
4
  import { Ajv } from 'ajv';
@@ -349,6 +349,25 @@ export function createAjv(opt) {
349
349
  return false;
350
350
  },
351
351
  });
352
+ ajv.addKeyword({
353
+ keyword: 'IsoMonth',
354
+ type: 'string',
355
+ modifying: false,
356
+ errors: true,
357
+ schemaType: 'object',
358
+ validate: function validate(_opt, data, _schema, ctx) {
359
+ const isValid = isIsoMonthValid(data);
360
+ if (isValid)
361
+ return true;
362
+ validate.errors = [
363
+ {
364
+ instancePath: ctx?.instancePath ?? '',
365
+ message: `is an invalid IsoMonth`,
366
+ },
367
+ ];
368
+ return false;
369
+ },
370
+ });
352
371
  ajv.addKeyword({
353
372
  keyword: 'errorMessages',
354
373
  schemaType: 'object',
@@ -516,3 +535,21 @@ function isIsoTimezoneValid(s) {
516
535
  return false; // min is -12:00
517
536
  return true;
518
537
  }
538
+ /**
539
+ * This is a performance optimized correct validation
540
+ * for ISO month formatted as "YYYY-MM".
541
+ */
542
+ function isIsoMonthValid(s) {
543
+ // must be exactly "YYYY-MM"
544
+ if (s.length !== 7)
545
+ return false;
546
+ if (s.charCodeAt(4) !== DASH_CODE)
547
+ return false;
548
+ // fast parse numbers without substrings/Number()
549
+ const year = (s.charCodeAt(0) - ZERO_CODE) * 1000 +
550
+ (s.charCodeAt(1) - ZERO_CODE) * 100 +
551
+ (s.charCodeAt(2) - ZERO_CODE) * 10 +
552
+ (s.charCodeAt(3) - ZERO_CODE);
553
+ const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE);
554
+ return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]');
555
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Set2 } from '@naturalcycles/js-lib/object';
2
- import { type AnyObject, type BaseDBEntity, type IANATimezone, type Inclusiveness, type IsoDate, type IsoDateTime, type NumberEnum, type StringEnum, type StringMap, type UnixTimestamp, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
2
+ import { type AnyObject, type BaseDBEntity, type IANATimezone, type Inclusiveness, type IsoDate, type IsoDateTime, type IsoMonth, type NumberEnum, type StringEnum, type StringMap, type UnixTimestamp, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
3
3
  export declare const j: {
4
4
  string(): JsonSchemaStringBuilder<string, string, false>;
5
5
  number(): JsonSchemaNumberBuilder<number, number, false>;
@@ -139,6 +139,7 @@ export declare class JsonSchemaStringBuilder<IN extends string | undefined = str
139
139
  */
140
140
  isoDate(): JsonSchemaIsoDateBuilder;
141
141
  isoDateTime(): JsonSchemaStringBuilder<IsoDateTime | IN, IsoDateTime, Opt>;
142
+ isoMonth(): JsonSchemaIsoMonthBuilder;
142
143
  /**
143
144
  * Validates the string format to be JWT.
144
145
  * Expects the JWT to be signed!
@@ -179,6 +180,11 @@ export interface JsonSchemaIsoDateOptions {
179
180
  after?: string;
180
181
  sameOrAfter?: string;
181
182
  }
183
+ export declare class JsonSchemaIsoMonthBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<string | IsoDate, IsoMonth, Opt> {
184
+ constructor();
185
+ }
186
+ export interface JsonSchemaIsoMonthOptions {
187
+ }
182
188
  export declare class JsonSchemaNumberBuilder<IN extends number | undefined = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
183
189
  constructor();
184
190
  /**
@@ -343,6 +349,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
343
349
  Buffer?: true;
344
350
  IsoDate?: JsonSchemaIsoDateOptions;
345
351
  IsoDateTime?: true;
352
+ IsoMonth?: JsonSchemaIsoMonthOptions;
346
353
  instanceof?: string | string[];
347
354
  transform?: {
348
355
  trim?: true;
@@ -322,6 +322,9 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
322
322
  isoDateTime() {
323
323
  return this.cloneAndUpdateSchema({ IsoDateTime: true }).branded();
324
324
  }
325
+ isoMonth() {
326
+ return new JsonSchemaIsoMonthBuilder();
327
+ }
325
328
  /**
326
329
  * Validates the string format to be JWT.
327
330
  * Expects the JWT to be signed!
@@ -404,6 +407,14 @@ export class JsonSchemaIsoDateBuilder extends JsonSchemaAnyBuilder {
404
407
  return this.cloneAndUpdateSchema(schemaPatch);
405
408
  }
406
409
  }
410
+ export class JsonSchemaIsoMonthBuilder extends JsonSchemaAnyBuilder {
411
+ constructor() {
412
+ super({
413
+ type: 'string',
414
+ IsoMonth: {},
415
+ });
416
+ }
417
+ }
407
418
  export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
408
419
  constructor() {
409
420
  super({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.60.0",
4
+ "version": "15.61.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
@@ -1,10 +1,14 @@
1
- import { _lazyValue } from '@naturalcycles/js-lib'
1
+ import { _isBetween, _lazyValue } from '@naturalcycles/js-lib'
2
2
  import { Set2 } from '@naturalcycles/js-lib/object'
3
3
  import { _substringAfterLast } from '@naturalcycles/js-lib/string'
4
4
  import type { AnyObject } from '@naturalcycles/js-lib/types'
5
5
  import { Ajv, type Options, type ValidateFunction } from 'ajv'
6
6
  import { validTLDs } from '../tlds.js'
7
- import type { JsonSchemaIsoDateOptions, JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
7
+ import type {
8
+ JsonSchemaIsoDateOptions,
9
+ JsonSchemaIsoMonthOptions,
10
+ JsonSchemaStringEmailOptions,
11
+ } from './jsonSchemaBuilder.js'
8
12
 
9
13
  /* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
10
14
  // oxlint-disable unicorn/prefer-code-point
@@ -383,7 +387,7 @@ export function createAjv(opt?: Options): Ajv {
383
387
  modifying: false,
384
388
  errors: true,
385
389
  schemaType: 'boolean',
386
- validate: function validate(_opt: true, data: string, _schema, ctx) {
390
+ validate: function validate(_opt: JsonSchemaIsoMonthOptions, data: string, _schema, ctx) {
387
391
  const isValid = isIsoDateTimeValid(data)
388
392
  if (isValid) return true
389
393
  ;(validate as any).errors = [
@@ -396,6 +400,25 @@ export function createAjv(opt?: Options): Ajv {
396
400
  },
397
401
  })
398
402
 
403
+ ajv.addKeyword({
404
+ keyword: 'IsoMonth',
405
+ type: 'string',
406
+ modifying: false,
407
+ errors: true,
408
+ schemaType: 'object',
409
+ validate: function validate(_opt: true, data: string, _schema, ctx) {
410
+ const isValid = isIsoMonthValid(data)
411
+ if (isValid) return true
412
+ ;(validate as any).errors = [
413
+ {
414
+ instancePath: ctx?.instancePath ?? '',
415
+ message: `is an invalid IsoMonth`,
416
+ },
417
+ ]
418
+ return false
419
+ },
420
+ })
421
+
399
422
  ajv.addKeyword({
400
423
  keyword: 'errorMessages',
401
424
  schemaType: 'object',
@@ -577,3 +600,24 @@ function isIsoTimezoneValid(s: string): boolean {
577
600
 
578
601
  return true
579
602
  }
603
+
604
+ /**
605
+ * This is a performance optimized correct validation
606
+ * for ISO month formatted as "YYYY-MM".
607
+ */
608
+ function isIsoMonthValid(s: string): boolean {
609
+ // must be exactly "YYYY-MM"
610
+ if (s.length !== 7) return false
611
+ if (s.charCodeAt(4) !== DASH_CODE) return false
612
+
613
+ // fast parse numbers without substrings/Number()
614
+ const year =
615
+ (s.charCodeAt(0) - ZERO_CODE) * 1000 +
616
+ (s.charCodeAt(1) - ZERO_CODE) * 100 +
617
+ (s.charCodeAt(2) - ZERO_CODE) * 10 +
618
+ (s.charCodeAt(3) - ZERO_CODE)
619
+
620
+ const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE)
621
+
622
+ return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]')
623
+ }
@@ -19,6 +19,7 @@ import {
19
19
  type Inclusiveness,
20
20
  type IsoDate,
21
21
  type IsoDateTime,
22
+ type IsoMonth,
22
23
  JWT_REGEX,
23
24
  type NumberEnum,
24
25
  type StringEnum,
@@ -453,6 +454,10 @@ export class JsonSchemaStringBuilder<
453
454
  return this.cloneAndUpdateSchema({ IsoDateTime: true }).branded<IsoDateTime>()
454
455
  }
455
456
 
457
+ isoMonth(): JsonSchemaIsoMonthBuilder {
458
+ return new JsonSchemaIsoMonthBuilder()
459
+ }
460
+
456
461
  /**
457
462
  * Validates the string format to be JWT.
458
463
  * Expects the JWT to be signed!
@@ -570,6 +575,21 @@ export interface JsonSchemaIsoDateOptions {
570
575
  sameOrAfter?: string
571
576
  }
572
577
 
578
+ export class JsonSchemaIsoMonthBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<
579
+ string | IsoDate,
580
+ IsoMonth,
581
+ Opt
582
+ > {
583
+ constructor() {
584
+ super({
585
+ type: 'string',
586
+ IsoMonth: {},
587
+ })
588
+ }
589
+ }
590
+
591
+ export interface JsonSchemaIsoMonthOptions {}
592
+
573
593
  export class JsonSchemaNumberBuilder<
574
594
  IN extends number | undefined = number,
575
595
  OUT = IN,
@@ -1126,6 +1146,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1126
1146
  Buffer?: true
1127
1147
  IsoDate?: JsonSchemaIsoDateOptions
1128
1148
  IsoDateTime?: true
1149
+ IsoMonth?: JsonSchemaIsoMonthOptions
1129
1150
  instanceof?: string | string[]
1130
1151
  transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
1131
1152
  errorMessages?: StringMap<string>