@naturalcycles/nodejs-lib 15.125.3 → 15.126.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.
@@ -4,10 +4,12 @@ export type YamlStringifyOptions = DocumentOptions & SchemaOptions & ParseOption
4
4
  declare class Yaml2 {
5
5
  readYaml<T = unknown>(filePath: string, opt?: YamlParseOptions): T;
6
6
  readYamlAsync<T = unknown>(filePath: string, opt?: YamlParseOptions): Promise<T>;
7
- writeYaml(filePath: string, data: any, opt?: YamlStringifyOptions): void;
8
- writeYamlAsync(filePath: string, data: any, opt?: YamlStringifyOptions): Promise<void>;
9
- outputYaml(filePath: string, data: any, opt?: YamlStringifyOptions): void;
10
- outputYamlAsync(filePath: string, data: any, opt?: YamlStringifyOptions): Promise<void>;
7
+ writeYaml(filePath: string, data: unknown, opt?: YamlStringifyOptions): void;
8
+ writeYamlAsync(filePath: string, data: unknown, opt?: YamlStringifyOptions): Promise<void>;
9
+ outputYaml(filePath: string, data: unknown, opt?: YamlStringifyOptions): void;
10
+ outputYamlAsync(filePath: string, data: unknown, opt?: YamlStringifyOptions): Promise<void>;
11
+ parse<T = unknown>(src: string, opt?: YamlParseOptions): T;
12
+ stringify(data: unknown, opt?: YamlStringifyOptions): string;
11
13
  }
12
14
  export declare const yaml2: Yaml2;
13
15
  export {};
package/dist/fs/yaml2.js CHANGED
@@ -25,5 +25,11 @@ class Yaml2 {
25
25
  const str = stringify(data, opt);
26
26
  await fs2.outputFileAsync(filePath, str);
27
27
  }
28
+ parse(src, opt) {
29
+ return parse(src, opt);
30
+ }
31
+ stringify(data, opt) {
32
+ return stringify(data, opt);
33
+ }
28
34
  }
29
35
  export const yaml2 = new Yaml2();
@@ -633,8 +633,10 @@ export function createAjv(opt) {
633
633
  const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
634
634
  const DASH_CODE = '-'.charCodeAt(0);
635
635
  const ZERO_CODE = '0'.charCodeAt(0);
636
+ const NINE_CODE = '9'.charCodeAt(0);
636
637
  const PLUS_CODE = '+'.charCodeAt(0);
637
638
  const COLON_CODE = ':'.charCodeAt(0);
639
+ const DOT_CODE = '.'.charCodeAt(0);
638
640
  /**
639
641
  * This is a performance optimized correct validation
640
642
  * for ISO dates formatted as YYYY-MM-DD.
@@ -650,13 +652,10 @@ function isIsoDateValid(s) {
650
652
  if (s.charCodeAt(4) !== DASH_CODE || s.charCodeAt(7) !== DASH_CODE)
651
653
  return false;
652
654
  // fast parse numbers without substrings/Number()
653
- const year = (s.charCodeAt(0) - ZERO_CODE) * 1000 +
654
- (s.charCodeAt(1) - ZERO_CODE) * 100 +
655
- (s.charCodeAt(2) - ZERO_CODE) * 10 +
656
- (s.charCodeAt(3) - ZERO_CODE);
657
- const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE);
658
- const day = (s.charCodeAt(8) - ZERO_CODE) * 10 + (s.charCodeAt(9) - ZERO_CODE);
659
- if (month < 1 || month > 12 || day < 1)
655
+ const year = parseDigits(s, 0, 4);
656
+ const month = parseDigits(s, 5, 2);
657
+ const day = parseDigits(s, 8, 2);
658
+ if (year < 0 || month < 1 || month > 12 || day < 1)
660
659
  return false;
661
660
  if (month !== 2) {
662
661
  return day <= monthLengths[month];
@@ -666,15 +665,16 @@ function isIsoDateValid(s) {
666
665
  }
667
666
  /**
668
667
  * This is a performance optimized correct validation
669
- * for ISO datetimes formatted as "YYYY-MM-DDTHH:MM:SS" followed by
670
- * nothing, "Z" or "+hh:mm" or "-hh:mm".
668
+ * for ISO datetimes formatted as "YYYY-MM-DDTHH:MM:SS",
669
+ * optionally followed by fractional seconds of 1 to 9 digits (e.g. ".231" or ".231263"),
670
+ * followed by nothing, "Z" or "+hh:mm" or "-hh:mm".
671
671
  *
672
672
  * - Slightly more performant than using `localTime`.
673
673
  * - More performant than string splitting and `Number()` conversions
674
674
  * - Less performant than regex, but it does not allow invalid dates.
675
675
  */
676
676
  function isIsoDateTimeValid(s) {
677
- if (s.length < 19 || s.length > 25)
677
+ if (s.length < 19 || s.length > 35)
678
678
  return false;
679
679
  if (s.charCodeAt(10) !== 84)
680
680
  return false; // 'T'
@@ -684,7 +684,16 @@ function isIsoDateTimeValid(s) {
684
684
  const timePart = s.slice(11, 19); // HH:MM:SS
685
685
  if (!isIsoTimeValid(timePart))
686
686
  return false;
687
- const zonePart = s.slice(19); // nothing or Z or +/-hh:mm
687
+ let zoneStart = 19;
688
+ if (s.charCodeAt(19) === DOT_CODE) {
689
+ zoneStart = 20;
690
+ while (isDigitCode(s.charCodeAt(zoneStart)))
691
+ zoneStart++;
692
+ const fractionDigits = zoneStart - 20;
693
+ if (fractionDigits < 1 || fractionDigits > 9)
694
+ return false;
695
+ }
696
+ const zonePart = s.slice(zoneStart); // nothing or Z or +/-hh:mm
688
697
  if (!isIsoTimezoneValid(zonePart))
689
698
  return false;
690
699
  return true;
@@ -702,13 +711,13 @@ function isIsoTimeValid(s) {
702
711
  return false;
703
712
  if (s.charCodeAt(2) !== COLON_CODE || s.charCodeAt(5) !== COLON_CODE)
704
713
  return false;
705
- const hour = (s.charCodeAt(0) - ZERO_CODE) * 10 + (s.charCodeAt(1) - ZERO_CODE);
714
+ const hour = parseDigits(s, 0, 2);
706
715
  if (hour < 0 || hour > 23)
707
716
  return false;
708
- const minute = (s.charCodeAt(3) - ZERO_CODE) * 10 + (s.charCodeAt(4) - ZERO_CODE);
717
+ const minute = parseDigits(s, 3, 2);
709
718
  if (minute < 0 || minute > 59)
710
719
  return false;
711
- const second = (s.charCodeAt(6) - ZERO_CODE) * 10 + (s.charCodeAt(7) - ZERO_CODE);
720
+ const second = parseDigits(s, 6, 2);
712
721
  if (second < 0 || second > 59)
713
722
  return false;
714
723
  return true;
@@ -733,14 +742,14 @@ function isIsoTimezoneValid(s) {
733
742
  return false;
734
743
  const isWestern = s[0] === '-';
735
744
  const isEastern = s[0] === '+';
736
- const hour = (s.charCodeAt(1) - ZERO_CODE) * 10 + (s.charCodeAt(2) - ZERO_CODE);
745
+ const hour = parseDigits(s, 1, 2);
737
746
  if (hour < 0)
738
747
  return false;
739
748
  if (isWestern && hour > 12)
740
749
  return false;
741
750
  if (isEastern && hour > 14)
742
751
  return false;
743
- const minute = (s.charCodeAt(4) - ZERO_CODE) * 10 + (s.charCodeAt(5) - ZERO_CODE);
752
+ const minute = parseDigits(s, 4, 2);
744
753
  if (minute < 0 || minute > 59)
745
754
  return false;
746
755
  if (isEastern && hour === 14 && minute > 0)
@@ -760,13 +769,23 @@ function isIsoMonthValid(s) {
760
769
  if (s.charCodeAt(4) !== DASH_CODE)
761
770
  return false;
762
771
  // fast parse numbers without substrings/Number()
763
- const year = (s.charCodeAt(0) - ZERO_CODE) * 1000 +
764
- (s.charCodeAt(1) - ZERO_CODE) * 100 +
765
- (s.charCodeAt(2) - ZERO_CODE) * 10 +
766
- (s.charCodeAt(3) - ZERO_CODE);
767
- const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE);
772
+ const year = parseDigits(s, 0, 4);
773
+ const month = parseDigits(s, 5, 2);
768
774
  return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]');
769
775
  }
776
+ function parseDigits(s, start, count) {
777
+ let value = 0;
778
+ for (let i = start; i < start + count; i++) {
779
+ const code = s.charCodeAt(i);
780
+ if (!isDigitCode(code))
781
+ return -1;
782
+ value = value * 10 + code - ZERO_CODE;
783
+ }
784
+ return value;
785
+ }
786
+ function isDigitCode(code) {
787
+ return code >= ZERO_CODE && code <= NINE_CODE;
788
+ }
770
789
  function isPrimitive(data) {
771
790
  return data !== null && typeof data === 'object';
772
791
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.125.3",
4
+ "version": "15.126.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@standard-schema/spec": "^1",
@@ -14,7 +14,7 @@
14
14
  "yaml": "^2"
15
15
  },
16
16
  "devDependencies": {
17
- "@naturalcycles/dev-lib": "20.65.1",
17
+ "@naturalcycles/dev-lib": "0.0.0",
18
18
  "typescript": "^7"
19
19
  },
20
20
  "exports": {
package/src/fs/yaml2.ts CHANGED
@@ -27,25 +27,37 @@ class Yaml2 {
27
27
  return parse(await fsp.readFile(filePath, 'utf8'), opt) as T
28
28
  }
29
29
 
30
- writeYaml(filePath: string, data: any, opt?: YamlStringifyOptions): void {
30
+ writeYaml(filePath: string, data: unknown, opt?: YamlStringifyOptions): void {
31
31
  const str = stringify(data, opt)
32
32
  fs.writeFileSync(filePath, str)
33
33
  }
34
34
 
35
- async writeYamlAsync(filePath: string, data: any, opt?: YamlStringifyOptions): Promise<void> {
35
+ async writeYamlAsync(filePath: string, data: unknown, opt?: YamlStringifyOptions): Promise<void> {
36
36
  const str = stringify(data, opt)
37
37
  await fsp.writeFile(filePath, str)
38
38
  }
39
39
 
40
- outputYaml(filePath: string, data: any, opt?: YamlStringifyOptions): void {
40
+ outputYaml(filePath: string, data: unknown, opt?: YamlStringifyOptions): void {
41
41
  const str = stringify(data, opt)
42
42
  fs2.outputFile(filePath, str)
43
43
  }
44
44
 
45
- async outputYamlAsync(filePath: string, data: any, opt?: YamlStringifyOptions): Promise<void> {
45
+ async outputYamlAsync(
46
+ filePath: string,
47
+ data: unknown,
48
+ opt?: YamlStringifyOptions,
49
+ ): Promise<void> {
46
50
  const str = stringify(data, opt)
47
51
  await fs2.outputFileAsync(filePath, str)
48
52
  }
53
+
54
+ parse<T = unknown>(src: string, opt?: YamlParseOptions): T {
55
+ return parse(src, opt)
56
+ }
57
+
58
+ stringify(data: unknown, opt?: YamlStringifyOptions): string {
59
+ return stringify(data, opt)
60
+ }
49
61
  }
50
62
 
51
63
  export const yaml2 = new Yaml2()
@@ -738,8 +738,10 @@ const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
738
738
 
739
739
  const DASH_CODE = '-'.charCodeAt(0)
740
740
  const ZERO_CODE = '0'.charCodeAt(0)
741
+ const NINE_CODE = '9'.charCodeAt(0)
741
742
  const PLUS_CODE = '+'.charCodeAt(0)
742
743
  const COLON_CODE = ':'.charCodeAt(0)
744
+ const DOT_CODE = '.'.charCodeAt(0)
743
745
 
744
746
  /**
745
747
  * This is a performance optimized correct validation
@@ -755,16 +757,12 @@ function isIsoDateValid(s: string): boolean {
755
757
  if (s.charCodeAt(4) !== DASH_CODE || s.charCodeAt(7) !== DASH_CODE) return false
756
758
 
757
759
  // fast parse numbers without substrings/Number()
758
- const year =
759
- (s.charCodeAt(0) - ZERO_CODE) * 1000 +
760
- (s.charCodeAt(1) - ZERO_CODE) * 100 +
761
- (s.charCodeAt(2) - ZERO_CODE) * 10 +
762
- (s.charCodeAt(3) - ZERO_CODE)
760
+ const year = parseDigits(s, 0, 4)
763
761
 
764
- const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE)
765
- const day = (s.charCodeAt(8) - ZERO_CODE) * 10 + (s.charCodeAt(9) - ZERO_CODE)
762
+ const month = parseDigits(s, 5, 2)
763
+ const day = parseDigits(s, 8, 2)
766
764
 
767
- if (month < 1 || month > 12 || day < 1) return false
765
+ if (year < 0 || month < 1 || month > 12 || day < 1) return false
768
766
 
769
767
  if (month !== 2) {
770
768
  return day <= monthLengths[month]!
@@ -776,15 +774,16 @@ function isIsoDateValid(s: string): boolean {
776
774
 
777
775
  /**
778
776
  * This is a performance optimized correct validation
779
- * for ISO datetimes formatted as "YYYY-MM-DDTHH:MM:SS" followed by
780
- * nothing, "Z" or "+hh:mm" or "-hh:mm".
777
+ * for ISO datetimes formatted as "YYYY-MM-DDTHH:MM:SS",
778
+ * optionally followed by fractional seconds of 1 to 9 digits (e.g. ".231" or ".231263"),
779
+ * followed by nothing, "Z" or "+hh:mm" or "-hh:mm".
781
780
  *
782
781
  * - Slightly more performant than using `localTime`.
783
782
  * - More performant than string splitting and `Number()` conversions
784
783
  * - Less performant than regex, but it does not allow invalid dates.
785
784
  */
786
785
  function isIsoDateTimeValid(s: string): boolean {
787
- if (s.length < 19 || s.length > 25) return false
786
+ if (s.length < 19 || s.length > 35) return false
788
787
  if (s.charCodeAt(10) !== 84) return false // 'T'
789
788
 
790
789
  const datePart = s.slice(0, 10) // YYYY-MM-DD
@@ -793,7 +792,15 @@ function isIsoDateTimeValid(s: string): boolean {
793
792
  const timePart = s.slice(11, 19) // HH:MM:SS
794
793
  if (!isIsoTimeValid(timePart)) return false
795
794
 
796
- const zonePart = s.slice(19) // nothing or Z or +/-hh:mm
795
+ let zoneStart = 19
796
+ if (s.charCodeAt(19) === DOT_CODE) {
797
+ zoneStart = 20
798
+ while (isDigitCode(s.charCodeAt(zoneStart))) zoneStart++
799
+ const fractionDigits = zoneStart - 20
800
+ if (fractionDigits < 1 || fractionDigits > 9) return false
801
+ }
802
+
803
+ const zonePart = s.slice(zoneStart) // nothing or Z or +/-hh:mm
797
804
  if (!isIsoTimezoneValid(zonePart)) return false
798
805
 
799
806
  return true
@@ -811,13 +818,13 @@ function isIsoTimeValid(s: string): boolean {
811
818
  if (s.length !== 8) return false
812
819
  if (s.charCodeAt(2) !== COLON_CODE || s.charCodeAt(5) !== COLON_CODE) return false
813
820
 
814
- const hour = (s.charCodeAt(0) - ZERO_CODE) * 10 + (s.charCodeAt(1) - ZERO_CODE)
821
+ const hour = parseDigits(s, 0, 2)
815
822
  if (hour < 0 || hour > 23) return false
816
823
 
817
- const minute = (s.charCodeAt(3) - ZERO_CODE) * 10 + (s.charCodeAt(4) - ZERO_CODE)
824
+ const minute = parseDigits(s, 3, 2)
818
825
  if (minute < 0 || minute > 59) return false
819
826
 
820
- const second = (s.charCodeAt(6) - ZERO_CODE) * 10 + (s.charCodeAt(7) - ZERO_CODE)
827
+ const second = parseDigits(s, 6, 2)
821
828
  if (second < 0 || second > 59) return false
822
829
 
823
830
  return true
@@ -840,12 +847,12 @@ function isIsoTimezoneValid(s: string): boolean {
840
847
  const isWestern = s[0] === '-'
841
848
  const isEastern = s[0] === '+'
842
849
 
843
- const hour = (s.charCodeAt(1) - ZERO_CODE) * 10 + (s.charCodeAt(2) - ZERO_CODE)
850
+ const hour = parseDigits(s, 1, 2)
844
851
  if (hour < 0) return false
845
852
  if (isWestern && hour > 12) return false
846
853
  if (isEastern && hour > 14) return false
847
854
 
848
- const minute = (s.charCodeAt(4) - ZERO_CODE) * 10 + (s.charCodeAt(5) - ZERO_CODE)
855
+ const minute = parseDigits(s, 4, 2)
849
856
  if (minute < 0 || minute > 59) return false
850
857
 
851
858
  if (isEastern && hour === 14 && minute > 0) return false // max is +14:00
@@ -864,17 +871,27 @@ function isIsoMonthValid(s: string): boolean {
864
871
  if (s.charCodeAt(4) !== DASH_CODE) return false
865
872
 
866
873
  // fast parse numbers without substrings/Number()
867
- const year =
868
- (s.charCodeAt(0) - ZERO_CODE) * 1000 +
869
- (s.charCodeAt(1) - ZERO_CODE) * 100 +
870
- (s.charCodeAt(2) - ZERO_CODE) * 10 +
871
- (s.charCodeAt(3) - ZERO_CODE)
874
+ const year = parseDigits(s, 0, 4)
872
875
 
873
- const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE)
876
+ const month = parseDigits(s, 5, 2)
874
877
 
875
878
  return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]')
876
879
  }
877
880
 
881
+ function parseDigits(s: string, start: number, count: number): number {
882
+ let value = 0
883
+ for (let i = start; i < start + count; i++) {
884
+ const code = s.charCodeAt(i)
885
+ if (!isDigitCode(code)) return -1
886
+ value = value * 10 + code - ZERO_CODE
887
+ }
888
+ return value
889
+ }
890
+
891
+ function isDigitCode(code: number): boolean {
892
+ return code >= ZERO_CODE && code <= NINE_CODE
893
+ }
894
+
878
895
  function isPrimitive(data: any): boolean {
879
896
  return data !== null && typeof data === 'object'
880
897
  }