@naturalcycles/nodejs-lib 15.60.0 → 15.61.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.
@@ -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;
@@ -389,10 +396,10 @@ declare function withEnumKeys<const T extends readonly (string | number)[] | Str
389
396
  } : {
390
397
  [P in K]: SchemaOut<S>;
391
398
  }, false>;
392
- type Expand<T> = T extends infer O ? {
393
- [K in keyof O]: O[K];
394
- } : never;
395
- type ExactMatch<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
399
+ type Expand<T> = {
400
+ [K in keyof T]: T[K];
401
+ };
402
+ type ExactMatch<A, B> = (<T>() => T extends Expand<A> ? 1 : 2) extends <T>() => T extends Expand<B> ? 1 : 2 ? (<T>() => T extends Expand<B> ? 1 : 2) extends <T>() => T extends Expand<A> ? 1 : 2 ? true : false : false;
396
403
  type BuilderOutUnion<B extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
397
404
  [K in keyof B]: B[K] extends JsonSchemaAnyBuilder<any, infer O, any> ? O : never;
398
405
  }[number];
@@ -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({
@@ -575,7 +586,7 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
575
586
  return this.cloneAndUpdateSchema({ additionalProperties: true });
576
587
  }
577
588
  extend(props) {
578
- const clone = this.cloneAndUpdateSchema(_deepCopy(this.schema));
589
+ const clone = this.clone();
579
590
  const incomingSchemaBuilder = new JsonSchemaObjectBuilder(props);
580
591
  mergeJsonSchemaObjects(clone.schema, incomingSchemaBuilder.schema);
581
592
  return clone;
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.1",
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,
@@ -811,11 +831,7 @@ export class JsonSchemaObjectBuilder<
811
831
  extend<IN2 extends AnyObject>(
812
832
  props: AnyObject,
813
833
  ): JsonSchemaObjectBuilder<IN & IN2, OUT & IN2, Opt> {
814
- const clone = this.cloneAndUpdateSchema(_deepCopy(this.schema)) as JsonSchemaObjectBuilder<
815
- IN & IN2,
816
- OUT & IN2,
817
- Opt
818
- >
834
+ const clone = this.clone() as JsonSchemaObjectBuilder<IN & IN2, OUT & IN2, Opt>
819
835
 
820
836
  const incomingSchemaBuilder = new JsonSchemaObjectBuilder<IN2, IN2, false>(props)
821
837
  mergeJsonSchemaObjects(clone.schema as any, incomingSchemaBuilder.schema as any)
@@ -1126,6 +1142,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
1126
1142
  Buffer?: true
1127
1143
  IsoDate?: JsonSchemaIsoDateOptions
1128
1144
  IsoDateTime?: true
1145
+ IsoMonth?: JsonSchemaIsoMonthOptions
1129
1146
  instanceof?: string | string[]
1130
1147
  transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
1131
1148
  errorMessages?: StringMap<string>
@@ -1290,10 +1307,14 @@ function withEnumKeys<
1290
1307
  >(props, { hasIsOfTypeCheck: false })
1291
1308
  }
1292
1309
 
1293
- type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
1310
+ type Expand<T> = { [K in keyof T]: T[K] }
1294
1311
 
1295
1312
  type ExactMatch<A, B> =
1296
- (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
1313
+ (<T>() => T extends Expand<A> ? 1 : 2) extends <T>() => T extends Expand<B> ? 1 : 2
1314
+ ? (<T>() => T extends Expand<B> ? 1 : 2) extends <T>() => T extends Expand<A> ? 1 : 2
1315
+ ? true
1316
+ : false
1317
+ : false
1297
1318
 
1298
1319
  type BuilderOutUnion<B extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
1299
1320
  [K in keyof B]: B[K] extends JsonSchemaAnyBuilder<any, infer O, any> ? O : never