minotor 3.0.0 → 3.0.2

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.
Files changed (81) hide show
  1. package/.cspell.json +14 -1
  2. package/.gitattributes +3 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +3 -0
  4. package/.github/workflows/minotor.yml +17 -1
  5. package/CHANGELOG.md +3 -9
  6. package/README.md +47 -17
  7. package/dist/__e2e__/router.test.d.ts +1 -0
  8. package/dist/cli/perf.d.ts +28 -0
  9. package/dist/cli/utils.d.ts +6 -2
  10. package/dist/cli.mjs +1967 -823
  11. package/dist/cli.mjs.map +1 -1
  12. package/dist/gtfs/trips.d.ts +1 -0
  13. package/dist/gtfs/utils.d.ts +1 -1
  14. package/dist/parser.cjs.js +1030 -627
  15. package/dist/parser.cjs.js.map +1 -1
  16. package/dist/parser.d.ts +4 -2
  17. package/dist/parser.esm.js +1030 -627
  18. package/dist/parser.esm.js.map +1 -1
  19. package/dist/router.cjs.js +1 -1
  20. package/dist/router.cjs.js.map +1 -1
  21. package/dist/router.d.ts +10 -5
  22. package/dist/router.esm.js +1 -1
  23. package/dist/router.esm.js.map +1 -1
  24. package/dist/router.umd.js +1 -1
  25. package/dist/router.umd.js.map +1 -1
  26. package/dist/routing/__tests__/result.test.d.ts +1 -0
  27. package/dist/routing/query.d.ts +27 -6
  28. package/dist/routing/result.d.ts +1 -1
  29. package/dist/routing/route.d.ts +47 -2
  30. package/dist/routing/router.d.ts +15 -1
  31. package/dist/stops/stopsIndex.d.ts +3 -3
  32. package/dist/timetable/__tests__/route.test.d.ts +1 -0
  33. package/dist/timetable/__tests__/time.test.d.ts +1 -0
  34. package/dist/timetable/io.d.ts +7 -1
  35. package/dist/timetable/proto/timetable.d.ts +1 -1
  36. package/dist/timetable/route.d.ts +155 -0
  37. package/dist/timetable/time.d.ts +21 -0
  38. package/dist/timetable/timetable.d.ts +41 -61
  39. package/package.json +36 -35
  40. package/src/__e2e__/benchmark.json +22 -0
  41. package/src/__e2e__/router.test.ts +209 -0
  42. package/src/__e2e__/timetable/stops.bin +3 -0
  43. package/src/__e2e__/timetable/timetable.bin +3 -0
  44. package/src/cli/minotor.ts +51 -1
  45. package/src/cli/perf.ts +136 -0
  46. package/src/cli/repl.ts +26 -13
  47. package/src/cli/utils.ts +6 -28
  48. package/src/gtfs/__tests__/parser.test.ts +12 -15
  49. package/src/gtfs/__tests__/services.test.ts +1 -0
  50. package/src/gtfs/__tests__/transfers.test.ts +0 -1
  51. package/src/gtfs/__tests__/trips.test.ts +67 -74
  52. package/src/gtfs/profiles/ch.ts +1 -1
  53. package/src/gtfs/routes.ts +4 -4
  54. package/src/gtfs/services.ts +15 -2
  55. package/src/gtfs/stops.ts +7 -3
  56. package/src/gtfs/transfers.ts +6 -3
  57. package/src/gtfs/trips.ts +33 -16
  58. package/src/gtfs/utils.ts +13 -2
  59. package/src/parser.ts +4 -2
  60. package/src/router.ts +17 -11
  61. package/src/routing/__tests__/result.test.ts +392 -0
  62. package/src/routing/__tests__/router.test.ts +94 -137
  63. package/src/routing/query.ts +28 -7
  64. package/src/routing/result.ts +10 -5
  65. package/src/routing/route.ts +95 -9
  66. package/src/routing/router.ts +82 -66
  67. package/src/stops/__tests__/io.test.ts +1 -1
  68. package/src/stops/__tests__/stopFinder.test.ts +1 -1
  69. package/src/stops/proto/stops.ts +4 -4
  70. package/src/stops/stopsIndex.ts +3 -3
  71. package/src/timetable/__tests__/io.test.ts +16 -23
  72. package/src/timetable/__tests__/route.test.ts +317 -0
  73. package/src/timetable/__tests__/time.test.ts +494 -0
  74. package/src/timetable/__tests__/timetable.test.ts +64 -75
  75. package/src/timetable/io.ts +32 -26
  76. package/src/timetable/proto/timetable.proto +1 -1
  77. package/src/timetable/proto/timetable.ts +13 -13
  78. package/src/timetable/route.ts +347 -0
  79. package/src/timetable/time.ts +40 -8
  80. package/src/timetable/timetable.ts +74 -165
  81. package/tsconfig.build.json +1 -1
package/dist/cli.mjs CHANGED
@@ -10,6 +10,7 @@ import require$$2$1 from 'path';
10
10
  import require$$3$1 from 'events';
11
11
  import require$$4$1 from 'zlib';
12
12
  import require$$5, { Transform } from 'stream';
13
+ import { performance } from 'perf_hooks';
13
14
  import repl from 'node:repl';
14
15
 
15
16
  /******************************************************************************
@@ -44,7 +45,7 @@ function __values(o) {
44
45
  if (m) return m.call(o);
45
46
  if (o && typeof o.length === "number") return {
46
47
  next: function () {
47
- if (o && i >= o.length) o = undefined;
48
+ if (o && i >= o.length) o = void 0;
48
49
  return { value: o && o[i++], done: !o };
49
50
  }
50
51
  };
@@ -302,11 +303,24 @@ function requireHelp () {
302
303
  class Help {
303
304
  constructor() {
304
305
  this.helpWidth = undefined;
306
+ this.minWidthToWrap = 40;
305
307
  this.sortSubcommands = false;
306
308
  this.sortOptions = false;
307
309
  this.showGlobalOptions = false;
308
310
  }
309
311
 
312
+ /**
313
+ * prepareContext is called by Commander after applying overrides from `Command.configureHelp()`
314
+ * and just before calling `formatHelp()`.
315
+ *
316
+ * Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses.
317
+ *
318
+ * @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions
319
+ */
320
+ prepareContext(contextOptions) {
321
+ this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
322
+ }
323
+
310
324
  /**
311
325
  * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
312
326
  *
@@ -481,7 +495,12 @@ function requireHelp () {
481
495
 
482
496
  longestSubcommandTermLength(cmd, helper) {
483
497
  return helper.visibleCommands(cmd).reduce((max, command) => {
484
- return Math.max(max, helper.subcommandTerm(command).length);
498
+ return Math.max(
499
+ max,
500
+ this.displayWidth(
501
+ helper.styleSubcommandTerm(helper.subcommandTerm(command)),
502
+ ),
503
+ );
485
504
  }, 0);
486
505
  }
487
506
 
@@ -495,7 +514,10 @@ function requireHelp () {
495
514
 
496
515
  longestOptionTermLength(cmd, helper) {
497
516
  return helper.visibleOptions(cmd).reduce((max, option) => {
498
- return Math.max(max, helper.optionTerm(option).length);
517
+ return Math.max(
518
+ max,
519
+ this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),
520
+ );
499
521
  }, 0);
500
522
  }
501
523
 
@@ -509,7 +531,10 @@ function requireHelp () {
509
531
 
510
532
  longestGlobalOptionTermLength(cmd, helper) {
511
533
  return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
512
- return Math.max(max, helper.optionTerm(option).length);
534
+ return Math.max(
535
+ max,
536
+ this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),
537
+ );
513
538
  }, 0);
514
539
  }
515
540
 
@@ -523,7 +548,12 @@ function requireHelp () {
523
548
 
524
549
  longestArgumentTermLength(cmd, helper) {
525
550
  return helper.visibleArguments(cmd).reduce((max, argument) => {
526
- return Math.max(max, helper.argumentTerm(argument).length);
551
+ return Math.max(
552
+ max,
553
+ this.displayWidth(
554
+ helper.styleArgumentTerm(helper.argumentTerm(argument)),
555
+ ),
556
+ );
527
557
  }, 0);
528
558
  }
529
559
 
@@ -613,7 +643,11 @@ function requireHelp () {
613
643
  extraInfo.push(`env: ${option.envVar}`);
614
644
  }
615
645
  if (extraInfo.length > 0) {
616
- return `${option.description} (${extraInfo.join(', ')})`;
646
+ const extraDescription = `(${extraInfo.join(', ')})`;
647
+ if (option.description) {
648
+ return `${option.description} ${extraDescription}`;
649
+ }
650
+ return extraDescription;
617
651
  }
618
652
 
619
653
  return option.description;
@@ -640,15 +674,55 @@ function requireHelp () {
640
674
  );
641
675
  }
642
676
  if (extraInfo.length > 0) {
643
- const extraDescripton = `(${extraInfo.join(', ')})`;
677
+ const extraDescription = `(${extraInfo.join(', ')})`;
644
678
  if (argument.description) {
645
- return `${argument.description} ${extraDescripton}`;
679
+ return `${argument.description} ${extraDescription}`;
646
680
  }
647
- return extraDescripton;
681
+ return extraDescription;
648
682
  }
649
683
  return argument.description;
650
684
  }
651
685
 
686
+ /**
687
+ * Format a list of items, given a heading and an array of formatted items.
688
+ *
689
+ * @param {string} heading
690
+ * @param {string[]} items
691
+ * @param {Help} helper
692
+ * @returns string[]
693
+ */
694
+ formatItemList(heading, items, helper) {
695
+ if (items.length === 0) return [];
696
+
697
+ return [helper.styleTitle(heading), ...items, ''];
698
+ }
699
+
700
+ /**
701
+ * Group items by their help group heading.
702
+ *
703
+ * @param {Command[] | Option[]} unsortedItems
704
+ * @param {Command[] | Option[]} visibleItems
705
+ * @param {Function} getGroup
706
+ * @returns {Map<string, Command[] | Option[]>}
707
+ */
708
+ groupItems(unsortedItems, visibleItems, getGroup) {
709
+ const result = new Map();
710
+ // Add groups in order of appearance in unsortedItems.
711
+ unsortedItems.forEach((item) => {
712
+ const group = getGroup(item);
713
+ if (!result.has(group)) result.set(group, []);
714
+ });
715
+ // Add items in order of appearance in visibleItems.
716
+ visibleItems.forEach((item) => {
717
+ const group = getGroup(item);
718
+ if (!result.has(group)) {
719
+ result.set(group, []);
720
+ }
721
+ result.get(group).push(item);
722
+ });
723
+ return result;
724
+ }
725
+
652
726
  /**
653
727
  * Generate the built-in help text.
654
728
  *
@@ -659,90 +733,171 @@ function requireHelp () {
659
733
 
660
734
  formatHelp(cmd, helper) {
661
735
  const termWidth = helper.padWidth(cmd, helper);
662
- const helpWidth = helper.helpWidth || 80;
663
- const itemIndentWidth = 2;
664
- const itemSeparatorWidth = 2; // between term and description
665
- function formatItem(term, description) {
666
- if (description) {
667
- const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
668
- return helper.wrap(
669
- fullText,
670
- helpWidth - itemIndentWidth,
671
- termWidth + itemSeparatorWidth,
672
- );
673
- }
674
- return term;
675
- }
676
- function formatList(textArray) {
677
- return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
736
+ const helpWidth = helper.helpWidth ?? 80; // in case prepareContext() was not called
737
+
738
+ function callFormatItem(term, description) {
739
+ return helper.formatItem(term, termWidth, description, helper);
678
740
  }
679
741
 
680
742
  // Usage
681
- let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
743
+ let output = [
744
+ `${helper.styleTitle('Usage:')} ${helper.styleUsage(helper.commandUsage(cmd))}`,
745
+ '',
746
+ ];
682
747
 
683
748
  // Description
684
749
  const commandDescription = helper.commandDescription(cmd);
685
750
  if (commandDescription.length > 0) {
686
751
  output = output.concat([
687
- helper.wrap(commandDescription, helpWidth, 0),
752
+ helper.boxWrap(
753
+ helper.styleCommandDescription(commandDescription),
754
+ helpWidth,
755
+ ),
688
756
  '',
689
757
  ]);
690
758
  }
691
759
 
692
760
  // Arguments
693
761
  const argumentList = helper.visibleArguments(cmd).map((argument) => {
694
- return formatItem(
695
- helper.argumentTerm(argument),
696
- helper.argumentDescription(argument),
762
+ return callFormatItem(
763
+ helper.styleArgumentTerm(helper.argumentTerm(argument)),
764
+ helper.styleArgumentDescription(helper.argumentDescription(argument)),
697
765
  );
698
766
  });
699
- if (argumentList.length > 0) {
700
- output = output.concat(['Arguments:', formatList(argumentList), '']);
701
- }
767
+ output = output.concat(
768
+ this.formatItemList('Arguments:', argumentList, helper),
769
+ );
702
770
 
703
771
  // Options
704
- const optionList = helper.visibleOptions(cmd).map((option) => {
705
- return formatItem(
706
- helper.optionTerm(option),
707
- helper.optionDescription(option),
708
- );
772
+ const optionGroups = this.groupItems(
773
+ cmd.options,
774
+ helper.visibleOptions(cmd),
775
+ (option) => option.helpGroupHeading ?? 'Options:',
776
+ );
777
+ optionGroups.forEach((options, group) => {
778
+ const optionList = options.map((option) => {
779
+ return callFormatItem(
780
+ helper.styleOptionTerm(helper.optionTerm(option)),
781
+ helper.styleOptionDescription(helper.optionDescription(option)),
782
+ );
783
+ });
784
+ output = output.concat(this.formatItemList(group, optionList, helper));
709
785
  });
710
- if (optionList.length > 0) {
711
- output = output.concat(['Options:', formatList(optionList), '']);
712
- }
713
786
 
714
- if (this.showGlobalOptions) {
787
+ if (helper.showGlobalOptions) {
715
788
  const globalOptionList = helper
716
789
  .visibleGlobalOptions(cmd)
717
790
  .map((option) => {
718
- return formatItem(
719
- helper.optionTerm(option),
720
- helper.optionDescription(option),
791
+ return callFormatItem(
792
+ helper.styleOptionTerm(helper.optionTerm(option)),
793
+ helper.styleOptionDescription(helper.optionDescription(option)),
721
794
  );
722
795
  });
723
- if (globalOptionList.length > 0) {
724
- output = output.concat([
725
- 'Global Options:',
726
- formatList(globalOptionList),
727
- '',
728
- ]);
729
- }
796
+ output = output.concat(
797
+ this.formatItemList('Global Options:', globalOptionList, helper),
798
+ );
730
799
  }
731
800
 
732
801
  // Commands
733
- const commandList = helper.visibleCommands(cmd).map((cmd) => {
734
- return formatItem(
735
- helper.subcommandTerm(cmd),
736
- helper.subcommandDescription(cmd),
737
- );
802
+ const commandGroups = this.groupItems(
803
+ cmd.commands,
804
+ helper.visibleCommands(cmd),
805
+ (sub) => sub.helpGroup() || 'Commands:',
806
+ );
807
+ commandGroups.forEach((commands, group) => {
808
+ const commandList = commands.map((sub) => {
809
+ return callFormatItem(
810
+ helper.styleSubcommandTerm(helper.subcommandTerm(sub)),
811
+ helper.styleSubcommandDescription(helper.subcommandDescription(sub)),
812
+ );
813
+ });
814
+ output = output.concat(this.formatItemList(group, commandList, helper));
738
815
  });
739
- if (commandList.length > 0) {
740
- output = output.concat(['Commands:', formatList(commandList), '']);
741
- }
742
816
 
743
817
  return output.join('\n');
744
818
  }
745
819
 
820
+ /**
821
+ * Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations.
822
+ *
823
+ * @param {string} str
824
+ * @returns {number}
825
+ */
826
+ displayWidth(str) {
827
+ return stripColor(str).length;
828
+ }
829
+
830
+ /**
831
+ * Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.
832
+ *
833
+ * @param {string} str
834
+ * @returns {string}
835
+ */
836
+ styleTitle(str) {
837
+ return str;
838
+ }
839
+
840
+ styleUsage(str) {
841
+ // Usage has lots of parts the user might like to color separately! Assume default usage string which is formed like:
842
+ // command subcommand [options] [command] <foo> [bar]
843
+ return str
844
+ .split(' ')
845
+ .map((word) => {
846
+ if (word === '[options]') return this.styleOptionText(word);
847
+ if (word === '[command]') return this.styleSubcommandText(word);
848
+ if (word[0] === '[' || word[0] === '<')
849
+ return this.styleArgumentText(word);
850
+ return this.styleCommandText(word); // Restrict to initial words?
851
+ })
852
+ .join(' ');
853
+ }
854
+ styleCommandDescription(str) {
855
+ return this.styleDescriptionText(str);
856
+ }
857
+ styleOptionDescription(str) {
858
+ return this.styleDescriptionText(str);
859
+ }
860
+ styleSubcommandDescription(str) {
861
+ return this.styleDescriptionText(str);
862
+ }
863
+ styleArgumentDescription(str) {
864
+ return this.styleDescriptionText(str);
865
+ }
866
+ styleDescriptionText(str) {
867
+ return str;
868
+ }
869
+ styleOptionTerm(str) {
870
+ return this.styleOptionText(str);
871
+ }
872
+ styleSubcommandTerm(str) {
873
+ // This is very like usage with lots of parts! Assume default string which is formed like:
874
+ // subcommand [options] <foo> [bar]
875
+ return str
876
+ .split(' ')
877
+ .map((word) => {
878
+ if (word === '[options]') return this.styleOptionText(word);
879
+ if (word[0] === '[' || word[0] === '<')
880
+ return this.styleArgumentText(word);
881
+ return this.styleSubcommandText(word); // Restrict to initial words?
882
+ })
883
+ .join(' ');
884
+ }
885
+ styleArgumentTerm(str) {
886
+ return this.styleArgumentText(str);
887
+ }
888
+ styleOptionText(str) {
889
+ return str;
890
+ }
891
+ styleArgumentText(str) {
892
+ return str;
893
+ }
894
+ styleSubcommandText(str) {
895
+ return str;
896
+ }
897
+ styleCommandText(str) {
898
+ return str;
899
+ }
900
+
746
901
  /**
747
902
  * Calculate the pad width from the maximum term length.
748
903
  *
@@ -761,53 +916,126 @@ function requireHelp () {
761
916
  }
762
917
 
763
918
  /**
764
- * Wrap the given string to width characters per line, with lines after the first indented.
765
- * Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
919
+ * Detect manually wrapped and indented strings by checking for line break followed by whitespace.
766
920
  *
767
921
  * @param {string} str
768
- * @param {number} width
769
- * @param {number} indent
770
- * @param {number} [minColumnWidth=40]
771
- * @return {string}
772
- *
922
+ * @returns {boolean}
773
923
  */
924
+ preformatted(str) {
925
+ return /\n[^\S\r\n]/.test(str);
926
+ }
774
927
 
775
- wrap(str, width, indent, minColumnWidth = 40) {
776
- // Full \s characters, minus the linefeeds.
777
- const indents =
778
- ' \\f\\t\\v\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff';
779
- // Detect manually wrapped and indented strings by searching for line break followed by spaces.
780
- const manualIndent = new RegExp(`[\\n][${indents}]+`);
781
- if (str.match(manualIndent)) return str;
782
- // Do not wrap if not enough room for a wrapped column of text (as could end up with a word per line).
783
- const columnWidth = width - indent;
784
- if (columnWidth < minColumnWidth) return str;
785
-
786
- const leadingStr = str.slice(0, indent);
787
- const columnText = str.slice(indent).replace('\r\n', '\n');
788
- const indentString = ' '.repeat(indent);
789
- const zeroWidthSpace = '\u200B';
790
- const breaks = `\\s${zeroWidthSpace}`;
791
- // Match line end (so empty lines don't collapse),
792
- // or as much text as will fit in column, or excess text up to first break.
793
- const regex = new RegExp(
794
- `\n|.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`,
795
- 'g',
928
+ /**
929
+ * Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
930
+ *
931
+ * So "TTT", 5, "DDD DDDD DD DDD" might be formatted for this.helpWidth=17 like so:
932
+ * TTT DDD DDDD
933
+ * DD DDD
934
+ *
935
+ * @param {string} term
936
+ * @param {number} termWidth
937
+ * @param {string} description
938
+ * @param {Help} helper
939
+ * @returns {string}
940
+ */
941
+ formatItem(term, termWidth, description, helper) {
942
+ const itemIndent = 2;
943
+ const itemIndentStr = ' '.repeat(itemIndent);
944
+ if (!description) return itemIndentStr + term;
945
+
946
+ // Pad the term out to a consistent width, so descriptions are aligned.
947
+ const paddedTerm = term.padEnd(
948
+ termWidth + term.length - helper.displayWidth(term),
796
949
  );
797
- const lines = columnText.match(regex) || [];
950
+
951
+ // Format the description.
952
+ const spacerWidth = 2; // between term and description
953
+ const helpWidth = this.helpWidth ?? 80; // in case prepareContext() was not called
954
+ const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
955
+ let formattedDescription;
956
+ if (
957
+ remainingWidth < this.minWidthToWrap ||
958
+ helper.preformatted(description)
959
+ ) {
960
+ formattedDescription = description;
961
+ } else {
962
+ const wrappedDescription = helper.boxWrap(description, remainingWidth);
963
+ formattedDescription = wrappedDescription.replace(
964
+ /\n/g,
965
+ '\n' + ' '.repeat(termWidth + spacerWidth),
966
+ );
967
+ }
968
+
969
+ // Construct and overall indent.
798
970
  return (
799
- leadingStr +
800
- lines
801
- .map((line, i) => {
802
- if (line === '\n') return ''; // preserve empty lines
803
- return (i > 0 ? indentString : '') + line.trimEnd();
804
- })
805
- .join('\n')
971
+ itemIndentStr +
972
+ paddedTerm +
973
+ ' '.repeat(spacerWidth) +
974
+ formattedDescription.replace(/\n/g, `\n${itemIndentStr}`)
806
975
  );
807
976
  }
977
+
978
+ /**
979
+ * Wrap a string at whitespace, preserving existing line breaks.
980
+ * Wrapping is skipped if the width is less than `minWidthToWrap`.
981
+ *
982
+ * @param {string} str
983
+ * @param {number} width
984
+ * @returns {string}
985
+ */
986
+ boxWrap(str, width) {
987
+ if (width < this.minWidthToWrap) return str;
988
+
989
+ const rawLines = str.split(/\r\n|\n/);
990
+ // split up text by whitespace
991
+ const chunkPattern = /[\s]*[^\s]+/g;
992
+ const wrappedLines = [];
993
+ rawLines.forEach((line) => {
994
+ const chunks = line.match(chunkPattern);
995
+ if (chunks === null) {
996
+ wrappedLines.push('');
997
+ return;
998
+ }
999
+
1000
+ let sumChunks = [chunks.shift()];
1001
+ let sumWidth = this.displayWidth(sumChunks[0]);
1002
+ chunks.forEach((chunk) => {
1003
+ const visibleWidth = this.displayWidth(chunk);
1004
+ // Accumulate chunks while they fit into width.
1005
+ if (sumWidth + visibleWidth <= width) {
1006
+ sumChunks.push(chunk);
1007
+ sumWidth += visibleWidth;
1008
+ return;
1009
+ }
1010
+ wrappedLines.push(sumChunks.join(''));
1011
+
1012
+ const nextChunk = chunk.trimStart(); // trim space at line break
1013
+ sumChunks = [nextChunk];
1014
+ sumWidth = this.displayWidth(nextChunk);
1015
+ });
1016
+ wrappedLines.push(sumChunks.join(''));
1017
+ });
1018
+
1019
+ return wrappedLines.join('\n');
1020
+ }
1021
+ }
1022
+
1023
+ /**
1024
+ * Strip style ANSI escape sequences from the string. In particular, SGR (Select Graphic Rendition) codes.
1025
+ *
1026
+ * @param {string} str
1027
+ * @returns {string}
1028
+ * @package
1029
+ */
1030
+
1031
+ function stripColor(str) {
1032
+ // eslint-disable-next-line no-control-regex
1033
+ const sgrPattern = /\x1b\[\d*(;\d*)*m/g;
1034
+ return str.replace(sgrPattern, '');
808
1035
  }
809
1036
 
810
1037
  help.Help = Help;
1038
+ help.stripColor = stripColor;
811
1039
  return help;
812
1040
  }
813
1041
 
@@ -838,7 +1066,7 @@ function requireOption () {
838
1066
  this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
839
1067
  this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
840
1068
  const optionFlags = splitOptionFlags(flags);
841
- this.short = optionFlags.shortFlag;
1069
+ this.short = optionFlags.shortFlag; // May be a short flag, undefined, or even a long flag (if option has two long flags).
842
1070
  this.long = optionFlags.longFlag;
843
1071
  this.negate = false;
844
1072
  if (this.long) {
@@ -853,6 +1081,7 @@ function requireOption () {
853
1081
  this.argChoices = undefined;
854
1082
  this.conflictsWith = [];
855
1083
  this.implied = undefined;
1084
+ this.helpGroupHeading = undefined; // soft initialised when option added to command
856
1085
  }
857
1086
 
858
1087
  /**
@@ -1027,13 +1256,27 @@ function requireOption () {
1027
1256
 
1028
1257
  /**
1029
1258
  * Return option name, in a camelcase format that can be used
1030
- * as a object attribute key.
1259
+ * as an object attribute key.
1031
1260
  *
1032
1261
  * @return {string}
1033
1262
  */
1034
1263
 
1035
1264
  attributeName() {
1036
- return camelcase(this.name().replace(/^no-/, ''));
1265
+ if (this.negate) {
1266
+ return camelcase(this.name().replace(/^no-/, ''));
1267
+ }
1268
+ return camelcase(this.name());
1269
+ }
1270
+
1271
+ /**
1272
+ * Set the help group heading.
1273
+ *
1274
+ * @param {string} heading
1275
+ * @return {Option}
1276
+ */
1277
+ helpGroup(heading) {
1278
+ this.helpGroupHeading = heading;
1279
+ return this;
1037
1280
  }
1038
1281
 
1039
1282
  /**
@@ -1132,17 +1375,51 @@ function requireOption () {
1132
1375
  function splitOptionFlags(flags) {
1133
1376
  let shortFlag;
1134
1377
  let longFlag;
1135
- // Use original very loose parsing to maintain backwards compatibility for now,
1136
- // which allowed for example unintended `-sw, --short-word` [sic].
1137
- const flagParts = flags.split(/[ |,]+/);
1138
- if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1]))
1378
+ // short flag, single dash and single character
1379
+ const shortFlagExp = /^-[^-]$/;
1380
+ // long flag, double dash and at least one character
1381
+ const longFlagExp = /^--[^-]/;
1382
+
1383
+ const flagParts = flags.split(/[ |,]+/).concat('guard');
1384
+ // Normal is short and/or long.
1385
+ if (shortFlagExp.test(flagParts[0])) shortFlag = flagParts.shift();
1386
+ if (longFlagExp.test(flagParts[0])) longFlag = flagParts.shift();
1387
+ // Long then short. Rarely used but fine.
1388
+ if (!shortFlag && shortFlagExp.test(flagParts[0]))
1139
1389
  shortFlag = flagParts.shift();
1140
- longFlag = flagParts.shift();
1141
- // Add support for lone short flag without significantly changing parsing!
1142
- if (!shortFlag && /^-[^-]$/.test(longFlag)) {
1390
+ // Allow two long flags, like '--ws, --workspace'
1391
+ // This is the supported way to have a shortish option flag.
1392
+ if (!shortFlag && longFlagExp.test(flagParts[0])) {
1143
1393
  shortFlag = longFlag;
1144
- longFlag = undefined;
1394
+ longFlag = flagParts.shift();
1395
+ }
1396
+
1397
+ // Check for unprocessed flag. Fail noisily rather than silently ignore.
1398
+ if (flagParts[0].startsWith('-')) {
1399
+ const unsupportedFlag = flagParts[0];
1400
+ const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
1401
+ if (/^-[^-][^-]/.test(unsupportedFlag))
1402
+ throw new Error(
1403
+ `${baseError}
1404
+ - a short flag is a single dash and a single character
1405
+ - either use a single dash and a single character (for a short flag)
1406
+ - or use a double dash for a long option (and can have two, like '--ws, --workspace')`,
1407
+ );
1408
+ if (shortFlagExp.test(unsupportedFlag))
1409
+ throw new Error(`${baseError}
1410
+ - too many short flags`);
1411
+ if (longFlagExp.test(unsupportedFlag))
1412
+ throw new Error(`${baseError}
1413
+ - too many long flags`);
1414
+
1415
+ throw new Error(`${baseError}
1416
+ - unrecognised flag format`);
1145
1417
  }
1418
+ if (shortFlag === undefined && longFlag === undefined)
1419
+ throw new Error(
1420
+ `option creation failed due to no flags found in '${flags}'.`,
1421
+ );
1422
+
1146
1423
  return { shortFlag, longFlag };
1147
1424
  }
1148
1425
 
@@ -1275,7 +1552,7 @@ function requireCommand () {
1275
1552
 
1276
1553
  const { Argument, humanReadableArgName } = requireArgument();
1277
1554
  const { CommanderError } = requireError();
1278
- const { Help } = requireHelp();
1555
+ const { Help, stripColor } = requireHelp();
1279
1556
  const { Option, DualOptions } = requireOption();
1280
1557
  const { suggestSimilar } = requireSuggestSimilar();
1281
1558
 
@@ -1294,7 +1571,7 @@ function requireCommand () {
1294
1571
  this.options = [];
1295
1572
  this.parent = null;
1296
1573
  this._allowUnknownOption = false;
1297
- this._allowExcessArguments = true;
1574
+ this._allowExcessArguments = false;
1298
1575
  /** @type {Argument[]} */
1299
1576
  this.registeredArguments = [];
1300
1577
  this._args = this.registeredArguments; // deprecated old name
@@ -1324,16 +1601,22 @@ function requireCommand () {
1324
1601
  /** @type {(boolean | string)} */
1325
1602
  this._showHelpAfterError = false;
1326
1603
  this._showSuggestionAfterError = true;
1604
+ this._savedState = null; // used in save/restoreStateBeforeParse
1327
1605
 
1328
- // see .configureOutput() for docs
1606
+ // see configureOutput() for docs
1329
1607
  this._outputConfiguration = {
1330
1608
  writeOut: (str) => process.stdout.write(str),
1331
1609
  writeErr: (str) => process.stderr.write(str),
1610
+ outputError: (str, write) => write(str),
1332
1611
  getOutHelpWidth: () =>
1333
1612
  process.stdout.isTTY ? process.stdout.columns : undefined,
1334
1613
  getErrHelpWidth: () =>
1335
1614
  process.stderr.isTTY ? process.stderr.columns : undefined,
1336
- outputError: (str, write) => write(str),
1615
+ getOutHasColors: () =>
1616
+ useColor() ?? (process.stdout.isTTY && process.stdout.hasColors?.()),
1617
+ getErrHasColors: () =>
1618
+ useColor() ?? (process.stderr.isTTY && process.stderr.hasColors?.()),
1619
+ stripColor: (str) => stripColor(str),
1337
1620
  };
1338
1621
 
1339
1622
  this._hidden = false;
@@ -1343,6 +1626,12 @@ function requireCommand () {
1343
1626
  /** @type {Command} */
1344
1627
  this._helpCommand = undefined; // lazy initialised, inherited
1345
1628
  this._helpConfiguration = {};
1629
+ /** @type {string | undefined} */
1630
+ this._helpGroupHeading = undefined; // soft initialised when added to parent
1631
+ /** @type {string | undefined} */
1632
+ this._defaultCommandGroup = undefined;
1633
+ /** @type {string | undefined} */
1634
+ this._defaultOptionGroup = undefined;
1346
1635
  }
1347
1636
 
1348
1637
  /**
@@ -1482,14 +1771,18 @@ function requireCommand () {
1482
1771
  *
1483
1772
  * The configuration properties are all functions:
1484
1773
  *
1485
- * // functions to change where being written, stdout and stderr
1774
+ * // change how output being written, defaults to stdout and stderr
1486
1775
  * writeOut(str)
1487
1776
  * writeErr(str)
1488
- * // matching functions to specify width for wrapping help
1777
+ * // change how output being written for errors, defaults to writeErr
1778
+ * outputError(str, write) // used for displaying errors and not used for displaying help
1779
+ * // specify width for wrapping help
1489
1780
  * getOutHelpWidth()
1490
1781
  * getErrHelpWidth()
1491
- * // functions based on what is being written out
1492
- * outputError(str, write) // used for displaying errors, and not used for displaying help
1782
+ * // color support, currently only used with Help
1783
+ * getOutHasColors()
1784
+ * getErrHasColors()
1785
+ * stripColor() // used to remove ANSI escape codes if output does not have colors
1493
1786
  *
1494
1787
  * @param {object} [configuration] - configuration options
1495
1788
  * @return {(Command | object)} `this` command for chaining, or stored configuration
@@ -1498,7 +1791,11 @@ function requireCommand () {
1498
1791
  configureOutput(configuration) {
1499
1792
  if (configuration === undefined) return this._outputConfiguration;
1500
1793
 
1501
- Object.assign(this._outputConfiguration, configuration);
1794
+ this._outputConfiguration = Object.assign(
1795
+ {},
1796
+ this._outputConfiguration,
1797
+ configuration,
1798
+ );
1502
1799
  return this;
1503
1800
  }
1504
1801
 
@@ -1579,16 +1876,16 @@ function requireCommand () {
1579
1876
  *
1580
1877
  * @param {string} name
1581
1878
  * @param {string} [description]
1582
- * @param {(Function|*)} [fn] - custom argument processing function
1879
+ * @param {(Function|*)} [parseArg] - custom argument processing function or default value
1583
1880
  * @param {*} [defaultValue]
1584
1881
  * @return {Command} `this` command for chaining
1585
1882
  */
1586
- argument(name, description, fn, defaultValue) {
1883
+ argument(name, description, parseArg, defaultValue) {
1587
1884
  const argument = this.createArgument(name, description);
1588
- if (typeof fn === 'function') {
1589
- argument.default(defaultValue).argParser(fn);
1885
+ if (typeof parseArg === 'function') {
1886
+ argument.default(defaultValue).argParser(parseArg);
1590
1887
  } else {
1591
- argument.default(fn);
1888
+ argument.default(parseArg);
1592
1889
  }
1593
1890
  this.addArgument(argument);
1594
1891
  return this;
@@ -1659,11 +1956,15 @@ function requireCommand () {
1659
1956
  helpCommand(enableOrNameAndArgs, description) {
1660
1957
  if (typeof enableOrNameAndArgs === 'boolean') {
1661
1958
  this._addImplicitHelpCommand = enableOrNameAndArgs;
1959
+ if (enableOrNameAndArgs && this._defaultCommandGroup) {
1960
+ // make the command to store the group
1961
+ this._initCommandGroup(this._getHelpCommand());
1962
+ }
1662
1963
  return this;
1663
1964
  }
1664
1965
 
1665
- enableOrNameAndArgs = enableOrNameAndArgs ?? 'help [command]';
1666
- const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
1966
+ const nameAndArgs = enableOrNameAndArgs ?? 'help [command]';
1967
+ const [, helpName, helpArgs] = nameAndArgs.match(/([^ ]+) *(.*)/);
1667
1968
  const helpDescription = description ?? 'display help for command';
1668
1969
 
1669
1970
  const helpCommand = this.createCommand(helpName);
@@ -1673,6 +1974,8 @@ function requireCommand () {
1673
1974
 
1674
1975
  this._addImplicitHelpCommand = true;
1675
1976
  this._helpCommand = helpCommand;
1977
+ // init group unless lazy create
1978
+ if (enableOrNameAndArgs || description) this._initCommandGroup(helpCommand);
1676
1979
 
1677
1980
  return this;
1678
1981
  }
@@ -1694,6 +1997,7 @@ function requireCommand () {
1694
1997
 
1695
1998
  this._addImplicitHelpCommand = true;
1696
1999
  this._helpCommand = helpCommand;
2000
+ this._initCommandGroup(helpCommand);
1697
2001
  return this;
1698
2002
  }
1699
2003
 
@@ -1870,6 +2174,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1870
2174
  - already used by option '${matchingOption.flags}'`);
1871
2175
  }
1872
2176
 
2177
+ this._initOptionGroup(option);
1873
2178
  this.options.push(option);
1874
2179
  }
1875
2180
 
@@ -1897,6 +2202,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1897
2202
  );
1898
2203
  }
1899
2204
 
2205
+ this._initCommandGroup(command);
1900
2206
  this.commands.push(command);
1901
2207
  }
1902
2208
 
@@ -2013,7 +2319,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2013
2319
  * @example
2014
2320
  * program
2015
2321
  * .option('-p, --pepper', 'add pepper')
2016
- * .option('-p, --pizza-type <TYPE>', 'type of pizza') // required option-argument
2322
+ * .option('--pt, --pizza-type <TYPE>', 'type of pizza') // required option-argument
2017
2323
  * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
2018
2324
  * .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
2019
2325
  *
@@ -2327,6 +2633,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2327
2633
  */
2328
2634
 
2329
2635
  parse(argv, parseOptions) {
2636
+ this._prepareForParse();
2330
2637
  const userArgs = this._prepareUserArgs(argv, parseOptions);
2331
2638
  this._parseCommand([], userArgs);
2332
2639
 
@@ -2355,12 +2662,82 @@ Expecting one of '${allowedValues.join("', '")}'`);
2355
2662
  */
2356
2663
 
2357
2664
  async parseAsync(argv, parseOptions) {
2665
+ this._prepareForParse();
2358
2666
  const userArgs = this._prepareUserArgs(argv, parseOptions);
2359
2667
  await this._parseCommand([], userArgs);
2360
2668
 
2361
2669
  return this;
2362
2670
  }
2363
2671
 
2672
+ _prepareForParse() {
2673
+ if (this._savedState === null) {
2674
+ this.saveStateBeforeParse();
2675
+ } else {
2676
+ this.restoreStateBeforeParse();
2677
+ }
2678
+ }
2679
+
2680
+ /**
2681
+ * Called the first time parse is called to save state and allow a restore before subsequent calls to parse.
2682
+ * Not usually called directly, but available for subclasses to save their custom state.
2683
+ *
2684
+ * This is called in a lazy way. Only commands used in parsing chain will have state saved.
2685
+ */
2686
+ saveStateBeforeParse() {
2687
+ this._savedState = {
2688
+ // name is stable if supplied by author, but may be unspecified for root command and deduced during parsing
2689
+ _name: this._name,
2690
+ // option values before parse have default values (including false for negated options)
2691
+ // shallow clones
2692
+ _optionValues: { ...this._optionValues },
2693
+ _optionValueSources: { ...this._optionValueSources },
2694
+ };
2695
+ }
2696
+
2697
+ /**
2698
+ * Restore state before parse for calls after the first.
2699
+ * Not usually called directly, but available for subclasses to save their custom state.
2700
+ *
2701
+ * This is called in a lazy way. Only commands used in parsing chain will have state restored.
2702
+ */
2703
+ restoreStateBeforeParse() {
2704
+ if (this._storeOptionsAsProperties)
2705
+ throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
2706
+ - either make a new Command for each call to parse, or stop storing options as properties`);
2707
+
2708
+ // clear state from _prepareUserArgs
2709
+ this._name = this._savedState._name;
2710
+ this._scriptPath = null;
2711
+ this.rawArgs = [];
2712
+ // clear state from setOptionValueWithSource
2713
+ this._optionValues = { ...this._savedState._optionValues };
2714
+ this._optionValueSources = { ...this._savedState._optionValueSources };
2715
+ // clear state from _parseCommand
2716
+ this.args = [];
2717
+ // clear state from _processArguments
2718
+ this.processedArgs = [];
2719
+ }
2720
+
2721
+ /**
2722
+ * Throw if expected executable is missing. Add lots of help for author.
2723
+ *
2724
+ * @param {string} executableFile
2725
+ * @param {string} executableDir
2726
+ * @param {string} subcommandName
2727
+ */
2728
+ _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2729
+ if (fs.existsSync(executableFile)) return;
2730
+
2731
+ const executableDirMessage = executableDir
2732
+ ? `searched for local subcommand relative to directory '${executableDir}'`
2733
+ : 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
2734
+ const executableMissing = `'${executableFile}' does not exist
2735
+ - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
2736
+ - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
2737
+ - ${executableDirMessage}`;
2738
+ throw new Error(executableMissing);
2739
+ }
2740
+
2364
2741
  /**
2365
2742
  * Execute a sub-command executable.
2366
2743
  *
@@ -2401,7 +2778,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2401
2778
  let resolvedScriptPath; // resolve possible symlink for installed npm binary
2402
2779
  try {
2403
2780
  resolvedScriptPath = fs.realpathSync(this._scriptPath);
2404
- } catch (err) {
2781
+ } catch {
2405
2782
  resolvedScriptPath = this._scriptPath;
2406
2783
  }
2407
2784
  executableDir = path.resolve(
@@ -2444,6 +2821,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2444
2821
  proc = childProcess.spawn(executableFile, args, { stdio: 'inherit' });
2445
2822
  }
2446
2823
  } else {
2824
+ this._checkForMissingExecutable(
2825
+ executableFile,
2826
+ executableDir,
2827
+ subcommand._name,
2828
+ );
2447
2829
  args.unshift(executableFile);
2448
2830
  // add executable arguments to spawn
2449
2831
  args = incrementNodeInspectorPort(process.execArgv).concat(args);
@@ -2482,14 +2864,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2482
2864
  proc.on('error', (err) => {
2483
2865
  // @ts-ignore: because err.code is an unknown property
2484
2866
  if (err.code === 'ENOENT') {
2485
- const executableDirMessage = executableDir
2486
- ? `searched for local subcommand relative to directory '${executableDir}'`
2487
- : 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
2488
- const executableMissing = `'${executableFile}' does not exist
2489
- - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
2490
- - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
2491
- - ${executableDirMessage}`;
2492
- throw new Error(executableMissing);
2867
+ this._checkForMissingExecutable(
2868
+ executableFile,
2869
+ executableDir,
2870
+ subcommand._name,
2871
+ );
2493
2872
  // @ts-ignore: because err.code is an unknown property
2494
2873
  } else if (err.code === 'EACCES') {
2495
2874
  throw new Error(`'${executableFile}' not executable`);
@@ -2519,6 +2898,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2519
2898
  const subCommand = this._findCommand(commandName);
2520
2899
  if (!subCommand) this.help({ error: true });
2521
2900
 
2901
+ subCommand._prepareForParse();
2522
2902
  let promiseChain;
2523
2903
  promiseChain = this._chainOrCallSubCommandHook(
2524
2904
  promiseChain,
@@ -2896,6 +3276,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2896
3276
  * Parse options from `argv` removing known options,
2897
3277
  * and return argv split into operands and unknown arguments.
2898
3278
  *
3279
+ * Side effects: modifies command by storing options. Does not reset state if called again.
3280
+ *
2899
3281
  * Examples:
2900
3282
  *
2901
3283
  * argv => operands, unknown
@@ -2918,6 +3300,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2918
3300
  return arg.length > 1 && arg[0] === '-';
2919
3301
  }
2920
3302
 
3303
+ const negativeNumberArg = (arg) => {
3304
+ // return false if not a negative number
3305
+ if (!/^-\d*\.?\d+(e[+-]?\d+)?$/.test(arg)) return false;
3306
+ // negative number is ok unless digit used as an option in command hierarchy
3307
+ return !this._getCommandAndAncestors().some((cmd) =>
3308
+ cmd.options
3309
+ .map((opt) => opt.short)
3310
+ .some((short) => /^-\d$/.test(short)),
3311
+ );
3312
+ };
3313
+
2921
3314
  // parse options
2922
3315
  let activeVariadicOption = null;
2923
3316
  while (args.length) {
@@ -2930,7 +3323,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2930
3323
  break;
2931
3324
  }
2932
3325
 
2933
- if (activeVariadicOption && !maybeOption(arg)) {
3326
+ if (
3327
+ activeVariadicOption &&
3328
+ (!maybeOption(arg) || negativeNumberArg(arg))
3329
+ ) {
2934
3330
  this.emit(`option:${activeVariadicOption.name()}`, arg);
2935
3331
  continue;
2936
3332
  }
@@ -2947,7 +3343,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2947
3343
  } else if (option.optional) {
2948
3344
  let value = null;
2949
3345
  // historical behaviour is optional value is following arg unless an option
2950
- if (args.length > 0 && !maybeOption(args[0])) {
3346
+ if (
3347
+ args.length > 0 &&
3348
+ (!maybeOption(args[0]) || negativeNumberArg(args[0]))
3349
+ ) {
2951
3350
  value = args.shift();
2952
3351
  }
2953
3352
  this.emit(`option:${option.name()}`, value);
@@ -2993,7 +3392,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
2993
3392
  // Might be a command-argument, or subcommand option, or unknown option, or help command or option.
2994
3393
 
2995
3394
  // An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
2996
- if (maybeOption(arg)) {
3395
+ // A negative number in a leaf command is not an unknown option.
3396
+ if (
3397
+ dest === operands &&
3398
+ maybeOption(arg) &&
3399
+ !(this.commands.length === 0 && negativeNumberArg(arg))
3400
+ ) {
2997
3401
  dest = unknown;
2998
3402
  }
2999
3403
 
@@ -3475,6 +3879,75 @@ Expecting one of '${allowedValues.join("', '")}'`);
3475
3879
  return this;
3476
3880
  }
3477
3881
 
3882
+ /**
3883
+ * Set/get the help group heading for this subcommand in parent command's help.
3884
+ *
3885
+ * @param {string} [heading]
3886
+ * @return {Command | string}
3887
+ */
3888
+
3889
+ helpGroup(heading) {
3890
+ if (heading === undefined) return this._helpGroupHeading ?? '';
3891
+ this._helpGroupHeading = heading;
3892
+ return this;
3893
+ }
3894
+
3895
+ /**
3896
+ * Set/get the default help group heading for subcommands added to this command.
3897
+ * (This does not override a group set directly on the subcommand using .helpGroup().)
3898
+ *
3899
+ * @example
3900
+ * program.commandsGroup('Development Commands:);
3901
+ * program.command('watch')...
3902
+ * program.command('lint')...
3903
+ * ...
3904
+ *
3905
+ * @param {string} [heading]
3906
+ * @returns {Command | string}
3907
+ */
3908
+ commandsGroup(heading) {
3909
+ if (heading === undefined) return this._defaultCommandGroup ?? '';
3910
+ this._defaultCommandGroup = heading;
3911
+ return this;
3912
+ }
3913
+
3914
+ /**
3915
+ * Set/get the default help group heading for options added to this command.
3916
+ * (This does not override a group set directly on the option using .helpGroup().)
3917
+ *
3918
+ * @example
3919
+ * program
3920
+ * .optionsGroup('Development Options:')
3921
+ * .option('-d, --debug', 'output extra debugging')
3922
+ * .option('-p, --profile', 'output profiling information')
3923
+ *
3924
+ * @param {string} [heading]
3925
+ * @returns {Command | string}
3926
+ */
3927
+ optionsGroup(heading) {
3928
+ if (heading === undefined) return this._defaultOptionGroup ?? '';
3929
+ this._defaultOptionGroup = heading;
3930
+ return this;
3931
+ }
3932
+
3933
+ /**
3934
+ * @param {Option} option
3935
+ * @private
3936
+ */
3937
+ _initOptionGroup(option) {
3938
+ if (this._defaultOptionGroup && !option.helpGroupHeading)
3939
+ option.helpGroup(this._defaultOptionGroup);
3940
+ }
3941
+
3942
+ /**
3943
+ * @param {Command} cmd
3944
+ * @private
3945
+ */
3946
+ _initCommandGroup(cmd) {
3947
+ if (this._defaultCommandGroup && !cmd.helpGroup())
3948
+ cmd.helpGroup(this._defaultCommandGroup);
3949
+ }
3950
+
3478
3951
  /**
3479
3952
  * Set the name of the command from script filename, such as process.argv[1],
3480
3953
  * or require.main.filename, or __filename.
@@ -3521,31 +3994,49 @@ Expecting one of '${allowedValues.join("', '")}'`);
3521
3994
 
3522
3995
  helpInformation(contextOptions) {
3523
3996
  const helper = this.createHelp();
3524
- if (helper.helpWidth === undefined) {
3525
- helper.helpWidth =
3526
- contextOptions && contextOptions.error
3527
- ? this._outputConfiguration.getErrHelpWidth()
3528
- : this._outputConfiguration.getOutHelpWidth();
3529
- }
3530
- return helper.formatHelp(this, helper);
3997
+ const context = this._getOutputContext(contextOptions);
3998
+ helper.prepareContext({
3999
+ error: context.error,
4000
+ helpWidth: context.helpWidth,
4001
+ outputHasColors: context.hasColors,
4002
+ });
4003
+ const text = helper.formatHelp(this, helper);
4004
+ if (context.hasColors) return text;
4005
+ return this._outputConfiguration.stripColor(text);
3531
4006
  }
3532
4007
 
3533
4008
  /**
4009
+ * @typedef HelpContext
4010
+ * @type {object}
4011
+ * @property {boolean} error
4012
+ * @property {number} helpWidth
4013
+ * @property {boolean} hasColors
4014
+ * @property {function} write - includes stripColor if needed
4015
+ *
4016
+ * @returns {HelpContext}
3534
4017
  * @private
3535
4018
  */
3536
4019
 
3537
- _getHelpContext(contextOptions) {
4020
+ _getOutputContext(contextOptions) {
3538
4021
  contextOptions = contextOptions || {};
3539
- const context = { error: !!contextOptions.error };
3540
- let write;
3541
- if (context.error) {
3542
- write = (arg) => this._outputConfiguration.writeErr(arg);
4022
+ const error = !!contextOptions.error;
4023
+ let baseWrite;
4024
+ let hasColors;
4025
+ let helpWidth;
4026
+ if (error) {
4027
+ baseWrite = (str) => this._outputConfiguration.writeErr(str);
4028
+ hasColors = this._outputConfiguration.getErrHasColors();
4029
+ helpWidth = this._outputConfiguration.getErrHelpWidth();
3543
4030
  } else {
3544
- write = (arg) => this._outputConfiguration.writeOut(arg);
4031
+ baseWrite = (str) => this._outputConfiguration.writeOut(str);
4032
+ hasColors = this._outputConfiguration.getOutHasColors();
4033
+ helpWidth = this._outputConfiguration.getOutHelpWidth();
3545
4034
  }
3546
- context.write = contextOptions.write || write;
3547
- context.command = this;
3548
- return context;
4035
+ const write = (str) => {
4036
+ if (!hasColors) str = this._outputConfiguration.stripColor(str);
4037
+ return baseWrite(str);
4038
+ };
4039
+ return { error, write, hasColors, helpWidth };
3549
4040
  }
3550
4041
 
3551
4042
  /**
@@ -3562,14 +4053,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
3562
4053
  deprecatedCallback = contextOptions;
3563
4054
  contextOptions = undefined;
3564
4055
  }
3565
- const context = this._getHelpContext(contextOptions);
4056
+
4057
+ const outputContext = this._getOutputContext(contextOptions);
4058
+ /** @type {HelpTextEventContext} */
4059
+ const eventContext = {
4060
+ error: outputContext.error,
4061
+ write: outputContext.write,
4062
+ command: this,
4063
+ };
3566
4064
 
3567
4065
  this._getCommandAndAncestors()
3568
4066
  .reverse()
3569
- .forEach((command) => command.emit('beforeAllHelp', context));
3570
- this.emit('beforeHelp', context);
4067
+ .forEach((command) => command.emit('beforeAllHelp', eventContext));
4068
+ this.emit('beforeHelp', eventContext);
3571
4069
 
3572
- let helpInformation = this.helpInformation(context);
4070
+ let helpInformation = this.helpInformation({ error: outputContext.error });
3573
4071
  if (deprecatedCallback) {
3574
4072
  helpInformation = deprecatedCallback(helpInformation);
3575
4073
  if (
@@ -3579,14 +4077,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
3579
4077
  throw new Error('outputHelp callback must return a string or a Buffer');
3580
4078
  }
3581
4079
  }
3582
- context.write(helpInformation);
4080
+ outputContext.write(helpInformation);
3583
4081
 
3584
4082
  if (this._getHelpOption()?.long) {
3585
4083
  this.emit(this._getHelpOption().long); // deprecated
3586
4084
  }
3587
- this.emit('afterHelp', context);
4085
+ this.emit('afterHelp', eventContext);
3588
4086
  this._getCommandAndAncestors().forEach((command) =>
3589
- command.emit('afterAllHelp', context),
4087
+ command.emit('afterAllHelp', eventContext),
3590
4088
  );
3591
4089
  }
3592
4090
 
@@ -3604,10 +4102,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
3604
4102
  */
3605
4103
 
3606
4104
  helpOption(flags, description) {
3607
- // Support disabling built-in help option.
4105
+ // Support enabling/disabling built-in help option.
3608
4106
  if (typeof flags === 'boolean') {
3609
4107
  if (flags) {
3610
- this._helpOption = this._helpOption ?? undefined; // preserve existing option
4108
+ if (this._helpOption === null) this._helpOption = undefined; // reenable
4109
+ if (this._defaultOptionGroup) {
4110
+ // make the option to store the group
4111
+ this._initOptionGroup(this._getHelpOption());
4112
+ }
3611
4113
  } else {
3612
4114
  this._helpOption = null; // disable
3613
4115
  }
@@ -3615,9 +4117,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
3615
4117
  }
3616
4118
 
3617
4119
  // Customise flags and description.
3618
- flags = flags ?? '-h, --help';
3619
- description = description ?? 'display help for command';
3620
- this._helpOption = this.createOption(flags, description);
4120
+ this._helpOption = this.createOption(
4121
+ flags ?? '-h, --help',
4122
+ description ?? 'display help for command',
4123
+ );
4124
+ // init group unless lazy create
4125
+ if (flags || description) this._initOptionGroup(this._helpOption);
3621
4126
 
3622
4127
  return this;
3623
4128
  }
@@ -3646,6 +4151,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3646
4151
  */
3647
4152
  addHelpOption(option) {
3648
4153
  this._helpOption = option;
4154
+ this._initOptionGroup(option);
3649
4155
  return this;
3650
4156
  }
3651
4157
 
@@ -3659,7 +4165,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3659
4165
 
3660
4166
  help(contextOptions) {
3661
4167
  this.outputHelp(contextOptions);
3662
- let exitCode = process.exitCode || 0;
4168
+ let exitCode = Number(process.exitCode ?? 0); // process.exitCode does allow a string or an integer, but we prefer just a number
3663
4169
  if (
3664
4170
  exitCode === 0 &&
3665
4171
  contextOptions &&
@@ -3672,6 +4178,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
3672
4178
  this._exit(exitCode, 'commander.help', '(outputHelp)');
3673
4179
  }
3674
4180
 
4181
+ /**
4182
+ * // Do a little typing to coordinate emit and listener for the help text events.
4183
+ * @typedef HelpTextEventContext
4184
+ * @type {object}
4185
+ * @property {boolean} error
4186
+ * @property {Command} command
4187
+ * @property {function} write
4188
+ */
4189
+
3675
4190
  /**
3676
4191
  * Add additional text to be displayed with the built-in help.
3677
4192
  *
@@ -3682,14 +4197,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
3682
4197
  * @param {(string | Function)} text - string to add, or a function returning a string
3683
4198
  * @return {Command} `this` command for chaining
3684
4199
  */
4200
+
3685
4201
  addHelpText(position, text) {
3686
4202
  const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
3687
4203
  if (!allowedValues.includes(position)) {
3688
4204
  throw new Error(`Unexpected value for position to addHelpText.
3689
4205
  Expecting one of '${allowedValues.join("', '")}'`);
3690
4206
  }
4207
+
3691
4208
  const helpEvent = `${position}Help`;
3692
- this.on(helpEvent, (context) => {
4209
+ this.on(helpEvent, (/** @type {HelpTextEventContext} */ context) => {
3693
4210
  let helpStr;
3694
4211
  if (typeof text === 'function') {
3695
4212
  helpStr = text({ error: context.error, command: context.command });
@@ -3773,7 +4290,36 @@ Expecting one of '${allowedValues.join("', '")}'`);
3773
4290
  });
3774
4291
  }
3775
4292
 
4293
+ /**
4294
+ * @returns {boolean | undefined}
4295
+ * @package
4296
+ */
4297
+ function useColor() {
4298
+ // Test for common conventions.
4299
+ // NB: the observed behaviour is in combination with how author adds color! For example:
4300
+ // - we do not test NODE_DISABLE_COLORS, but util:styletext does
4301
+ // - we do test NO_COLOR, but Chalk does not
4302
+ //
4303
+ // References:
4304
+ // https://no-color.org
4305
+ // https://bixense.com/clicolors/
4306
+ // https://github.com/nodejs/node/blob/0a00217a5f67ef4a22384cfc80eb6dd9a917fdc1/lib/internal/tty.js#L109
4307
+ // https://github.com/chalk/supports-color/blob/c214314a14bcb174b12b3014b2b0a8de375029ae/index.js#L33
4308
+ // (https://force-color.org recent web page from 2023, does not match major javascript implementations)
4309
+
4310
+ if (
4311
+ process.env.NO_COLOR ||
4312
+ process.env.FORCE_COLOR === '0' ||
4313
+ process.env.FORCE_COLOR === 'false'
4314
+ )
4315
+ return false;
4316
+ if (process.env.FORCE_COLOR || process.env.CLICOLOR_FORCE !== undefined)
4317
+ return true;
4318
+ return undefined;
4319
+ }
4320
+
3776
4321
  command.Command = Command;
4322
+ command.useColor = useColor; // exporting for tests
3777
4323
  return command;
3778
4324
  }
3779
4325
 
@@ -4591,12 +5137,13 @@ class SystemZone extends Zone {
4591
5137
  }
4592
5138
  }
4593
5139
 
4594
- let dtfCache = {};
4595
- function makeDTF(zone) {
4596
- if (!dtfCache[zone]) {
4597
- dtfCache[zone] = new Intl.DateTimeFormat("en-US", {
5140
+ const dtfCache = new Map();
5141
+ function makeDTF(zoneName) {
5142
+ let dtf = dtfCache.get(zoneName);
5143
+ if (dtf === undefined) {
5144
+ dtf = new Intl.DateTimeFormat("en-US", {
4598
5145
  hour12: false,
4599
- timeZone: zone,
5146
+ timeZone: zoneName,
4600
5147
  year: "numeric",
4601
5148
  month: "2-digit",
4602
5149
  day: "2-digit",
@@ -4605,8 +5152,9 @@ function makeDTF(zone) {
4605
5152
  second: "2-digit",
4606
5153
  era: "short",
4607
5154
  });
5155
+ dtfCache.set(zoneName, dtf);
4608
5156
  }
4609
- return dtfCache[zone];
5157
+ return dtf;
4610
5158
  }
4611
5159
 
4612
5160
  const typeToPos = {
@@ -4642,7 +5190,7 @@ function partsOffset(dtf, date) {
4642
5190
  return filled;
4643
5191
  }
4644
5192
 
4645
- let ianaZoneCache = {};
5193
+ const ianaZoneCache = new Map();
4646
5194
  /**
4647
5195
  * A zone identified by an IANA identifier, like America/New_York
4648
5196
  * @implements {Zone}
@@ -4653,10 +5201,11 @@ class IANAZone extends Zone {
4653
5201
  * @return {IANAZone}
4654
5202
  */
4655
5203
  static create(name) {
4656
- if (!ianaZoneCache[name]) {
4657
- ianaZoneCache[name] = new IANAZone(name);
5204
+ let zone = ianaZoneCache.get(name);
5205
+ if (zone === undefined) {
5206
+ ianaZoneCache.set(name, (zone = new IANAZone(name)));
4658
5207
  }
4659
- return ianaZoneCache[name];
5208
+ return zone;
4660
5209
  }
4661
5210
 
4662
5211
  /**
@@ -4664,8 +5213,8 @@ class IANAZone extends Zone {
4664
5213
  * @return {void}
4665
5214
  */
4666
5215
  static resetCache() {
4667
- ianaZoneCache = {};
4668
- dtfCache = {};
5216
+ ianaZoneCache.clear();
5217
+ dtfCache.clear();
4669
5218
  }
4670
5219
 
4671
5220
  /**
@@ -4768,6 +5317,7 @@ class IANAZone extends Zone {
4768
5317
  * @return {number}
4769
5318
  */
4770
5319
  offset(ts) {
5320
+ if (!this.valid) return NaN;
4771
5321
  const date = new Date(ts);
4772
5322
 
4773
5323
  if (isNaN(date)) return NaN;
@@ -4833,36 +5383,36 @@ function getCachedLF(locString, opts = {}) {
4833
5383
  return dtf;
4834
5384
  }
4835
5385
 
4836
- let intlDTCache = {};
5386
+ const intlDTCache = new Map();
4837
5387
  function getCachedDTF(locString, opts = {}) {
4838
5388
  const key = JSON.stringify([locString, opts]);
4839
- let dtf = intlDTCache[key];
4840
- if (!dtf) {
5389
+ let dtf = intlDTCache.get(key);
5390
+ if (dtf === undefined) {
4841
5391
  dtf = new Intl.DateTimeFormat(locString, opts);
4842
- intlDTCache[key] = dtf;
5392
+ intlDTCache.set(key, dtf);
4843
5393
  }
4844
5394
  return dtf;
4845
5395
  }
4846
5396
 
4847
- let intlNumCache = {};
5397
+ const intlNumCache = new Map();
4848
5398
  function getCachedINF(locString, opts = {}) {
4849
5399
  const key = JSON.stringify([locString, opts]);
4850
- let inf = intlNumCache[key];
4851
- if (!inf) {
5400
+ let inf = intlNumCache.get(key);
5401
+ if (inf === undefined) {
4852
5402
  inf = new Intl.NumberFormat(locString, opts);
4853
- intlNumCache[key] = inf;
5403
+ intlNumCache.set(key, inf);
4854
5404
  }
4855
5405
  return inf;
4856
5406
  }
4857
5407
 
4858
- let intlRelCache = {};
5408
+ const intlRelCache = new Map();
4859
5409
  function getCachedRTF(locString, opts = {}) {
4860
5410
  const { base, ...cacheKeyOpts } = opts; // exclude `base` from the options
4861
5411
  const key = JSON.stringify([locString, cacheKeyOpts]);
4862
- let inf = intlRelCache[key];
4863
- if (!inf) {
5412
+ let inf = intlRelCache.get(key);
5413
+ if (inf === undefined) {
4864
5414
  inf = new Intl.RelativeTimeFormat(locString, opts);
4865
- intlRelCache[key] = inf;
5415
+ intlRelCache.set(key, inf);
4866
5416
  }
4867
5417
  return inf;
4868
5418
  }
@@ -4877,14 +5427,28 @@ function systemLocale() {
4877
5427
  }
4878
5428
  }
4879
5429
 
4880
- let weekInfoCache = {};
5430
+ const intlResolvedOptionsCache = new Map();
5431
+ function getCachedIntResolvedOptions(locString) {
5432
+ let opts = intlResolvedOptionsCache.get(locString);
5433
+ if (opts === undefined) {
5434
+ opts = new Intl.DateTimeFormat(locString).resolvedOptions();
5435
+ intlResolvedOptionsCache.set(locString, opts);
5436
+ }
5437
+ return opts;
5438
+ }
5439
+
5440
+ const weekInfoCache = new Map();
4881
5441
  function getCachedWeekInfo(locString) {
4882
- let data = weekInfoCache[locString];
5442
+ let data = weekInfoCache.get(locString);
4883
5443
  if (!data) {
4884
5444
  const locale = new Intl.Locale(locString);
4885
5445
  // browsers currently implement this as a property, but spec says it should be a getter function
4886
5446
  data = "getWeekInfo" in locale ? locale.getWeekInfo() : locale.weekInfo;
4887
- weekInfoCache[locString] = data;
5447
+ // minimalDays was removed from WeekInfo: https://github.com/tc39/proposal-intl-locale-info/issues/86
5448
+ if (!("minimalDays" in data)) {
5449
+ data = { ...fallbackWeekSettings, ...data };
5450
+ }
5451
+ weekInfoCache.set(locString, data);
4888
5452
  }
4889
5453
  return data;
4890
5454
  }
@@ -4983,7 +5547,7 @@ function supportsFastNumbers(loc) {
4983
5547
  loc.numberingSystem === "latn" ||
4984
5548
  !loc.locale ||
4985
5549
  loc.locale.startsWith("en") ||
4986
- new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === "latn"
5550
+ getCachedIntResolvedOptions(loc.locale).numberingSystem === "latn"
4987
5551
  );
4988
5552
  }
4989
5553
  }
@@ -5142,7 +5706,6 @@ const fallbackWeekSettings = {
5142
5706
  /**
5143
5707
  * @private
5144
5708
  */
5145
-
5146
5709
  class Locale {
5147
5710
  static fromOpts(opts) {
5148
5711
  return Locale.create(
@@ -5166,9 +5729,11 @@ class Locale {
5166
5729
 
5167
5730
  static resetCache() {
5168
5731
  sysLocaleCache = null;
5169
- intlDTCache = {};
5170
- intlNumCache = {};
5171
- intlRelCache = {};
5732
+ intlDTCache.clear();
5733
+ intlNumCache.clear();
5734
+ intlRelCache.clear();
5735
+ intlResolvedOptionsCache.clear();
5736
+ weekInfoCache.clear();
5172
5737
  }
5173
5738
 
5174
5739
  static fromObject({ locale, numberingSystem, outputCalendar, weekSettings } = {}) {
@@ -5233,10 +5798,18 @@ class Locale {
5233
5798
 
5234
5799
  months(length, format = false) {
5235
5800
  return listStuff(this, length, months, () => {
5801
+ // Workaround for "ja" locale: formatToParts does not label all parts of the month
5802
+ // as "month" and for this locale there is no difference between "format" and "non-format".
5803
+ // As such, just use format() instead of formatToParts() and take the whole string
5804
+ const monthSpecialCase = this.intl === "ja" || this.intl.startsWith("ja-");
5805
+ format &= !monthSpecialCase;
5236
5806
  const intl = format ? { month: length, day: "numeric" } : { month: length },
5237
5807
  formatStr = format ? "format" : "standalone";
5238
5808
  if (!this.monthsCache[formatStr][length]) {
5239
- this.monthsCache[formatStr][length] = mapMonths((dt) => this.extract(dt, intl, "month"));
5809
+ const mapper = !monthSpecialCase
5810
+ ? (dt) => this.extract(dt, intl, "month")
5811
+ : (dt) => this.dtFormatter(dt, intl).format();
5812
+ this.monthsCache[formatStr][length] = mapMonths(mapper);
5240
5813
  }
5241
5814
  return this.monthsCache[formatStr][length];
5242
5815
  });
@@ -5322,7 +5895,7 @@ class Locale {
5322
5895
  return (
5323
5896
  this.locale === "en" ||
5324
5897
  this.locale.toLowerCase() === "en-us" ||
5325
- new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us")
5898
+ getCachedIntResolvedOptions(this.intl).locale.startsWith("en-us")
5326
5899
  );
5327
5900
  }
5328
5901
 
@@ -5661,22 +6234,26 @@ function parseDigits(str) {
5661
6234
  }
5662
6235
 
5663
6236
  // cache of {numberingSystem: {append: regex}}
5664
- let digitRegexCache = {};
6237
+ const digitRegexCache = new Map();
5665
6238
  function resetDigitRegexCache() {
5666
- digitRegexCache = {};
6239
+ digitRegexCache.clear();
5667
6240
  }
5668
6241
 
5669
6242
  function digitRegex({ numberingSystem }, append = "") {
5670
6243
  const ns = numberingSystem || "latn";
5671
6244
 
5672
- if (!digitRegexCache[ns]) {
5673
- digitRegexCache[ns] = {};
6245
+ let appendCache = digitRegexCache.get(ns);
6246
+ if (appendCache === undefined) {
6247
+ appendCache = new Map();
6248
+ digitRegexCache.set(ns, appendCache);
5674
6249
  }
5675
- if (!digitRegexCache[ns][append]) {
5676
- digitRegexCache[ns][append] = new RegExp(`${numberingSystems[ns]}${append}`);
6250
+ let regex = appendCache.get(append);
6251
+ if (regex === undefined) {
6252
+ regex = new RegExp(`${numberingSystems[ns]}${append}`);
6253
+ appendCache.set(append, regex);
5677
6254
  }
5678
6255
 
5679
- return digitRegexCache[ns][append];
6256
+ return regex;
5680
6257
  }
5681
6258
 
5682
6259
  let now = () => Date.now(),
@@ -6218,10 +6795,24 @@ function parseMillis(fraction) {
6218
6795
  }
6219
6796
  }
6220
6797
 
6221
- function roundTo(number, digits, towardZero = false) {
6222
- const factor = 10 ** digits,
6223
- rounder = towardZero ? Math.trunc : Math.round;
6224
- return rounder(number * factor) / factor;
6798
+ function roundTo(number, digits, rounding = "round") {
6799
+ const factor = 10 ** digits;
6800
+ switch (rounding) {
6801
+ case "expand":
6802
+ return number > 0
6803
+ ? Math.ceil(number * factor) / factor
6804
+ : Math.floor(number * factor) / factor;
6805
+ case "trunc":
6806
+ return Math.trunc(number * factor) / factor;
6807
+ case "round":
6808
+ return Math.round(number * factor) / factor;
6809
+ case "floor":
6810
+ return Math.floor(number * factor) / factor;
6811
+ case "ceil":
6812
+ return Math.ceil(number * factor) / factor;
6813
+ default:
6814
+ throw new RangeError(`Value rounding ${rounding} is out of range`);
6815
+ }
6225
6816
  }
6226
6817
 
6227
6818
  // DATE BASICS
@@ -6329,7 +6920,7 @@ function signedOffset(offHourStr, offMinuteStr) {
6329
6920
 
6330
6921
  function asNumber(value) {
6331
6922
  const numericValue = Number(value);
6332
- if (typeof value === "boolean" || value === "" || Number.isNaN(numericValue))
6923
+ if (typeof value === "boolean" || value === "" || !Number.isFinite(numericValue))
6333
6924
  throw new InvalidArgumentError(`Invalid unit value ${value}`);
6334
6925
  return numericValue;
6335
6926
  }
@@ -6588,8 +7179,12 @@ class Formatter {
6588
7179
  for (let i = 0; i < fmt.length; i++) {
6589
7180
  const c = fmt.charAt(i);
6590
7181
  if (c === "'") {
6591
- if (currentFull.length > 0) {
6592
- splits.push({ literal: bracketed || /^\s+$/.test(currentFull), val: currentFull });
7182
+ // turn '' into a literal signal quote instead of just skipping the empty literal
7183
+ if (currentFull.length > 0 || bracketed) {
7184
+ splits.push({
7185
+ literal: bracketed || /^\s+$/.test(currentFull),
7186
+ val: currentFull === "" ? "'" : currentFull,
7187
+ });
6593
7188
  }
6594
7189
  current = null;
6595
7190
  currentFull = "";
@@ -6653,7 +7248,7 @@ class Formatter {
6653
7248
  return this.dtFormatter(dt, opts).resolvedOptions();
6654
7249
  }
6655
7250
 
6656
- num(n, p = 0) {
7251
+ num(n, p = 0, signDisplay = undefined) {
6657
7252
  // we get some perf out of doing this here, annoyingly
6658
7253
  if (this.opts.forceSimple) {
6659
7254
  return padStart(n, p);
@@ -6664,6 +7259,9 @@ class Formatter {
6664
7259
  if (p > 0) {
6665
7260
  opts.padTo = p;
6666
7261
  }
7262
+ if (signDisplay) {
7263
+ opts.signDisplay = signDisplay;
7264
+ }
6667
7265
 
6668
7266
  return this.loc.numberFormatter(opts).format(n);
6669
7267
  }
@@ -6899,32 +7497,44 @@ class Formatter {
6899
7497
  }
6900
7498
 
6901
7499
  formatDurationFromString(dur, fmt) {
7500
+ const invertLargest = this.opts.signMode === "negativeLargestOnly" ? -1 : 1;
6902
7501
  const tokenToField = (token) => {
6903
7502
  switch (token[0]) {
6904
7503
  case "S":
6905
- return "millisecond";
7504
+ return "milliseconds";
6906
7505
  case "s":
6907
- return "second";
7506
+ return "seconds";
6908
7507
  case "m":
6909
- return "minute";
7508
+ return "minutes";
6910
7509
  case "h":
6911
- return "hour";
7510
+ return "hours";
6912
7511
  case "d":
6913
- return "day";
7512
+ return "days";
6914
7513
  case "w":
6915
- return "week";
7514
+ return "weeks";
6916
7515
  case "M":
6917
- return "month";
7516
+ return "months";
6918
7517
  case "y":
6919
- return "year";
7518
+ return "years";
6920
7519
  default:
6921
7520
  return null;
6922
7521
  }
6923
7522
  },
6924
- tokenToString = (lildur) => (token) => {
7523
+ tokenToString = (lildur, info) => (token) => {
6925
7524
  const mapped = tokenToField(token);
6926
7525
  if (mapped) {
6927
- return this.num(lildur.get(mapped), token.length);
7526
+ const inversionFactor =
7527
+ info.isNegativeDuration && mapped !== info.largestUnit ? invertLargest : 1;
7528
+ let signDisplay;
7529
+ if (this.opts.signMode === "negativeLargestOnly" && mapped !== info.largestUnit) {
7530
+ signDisplay = "never";
7531
+ } else if (this.opts.signMode === "all") {
7532
+ signDisplay = "always";
7533
+ } else {
7534
+ // "auto" and "negative" are the same, but "auto" has better support
7535
+ signDisplay = "auto";
7536
+ }
7537
+ return this.num(lildur.get(mapped) * inversionFactor, token.length, signDisplay);
6928
7538
  } else {
6929
7539
  return token;
6930
7540
  }
@@ -6934,8 +7544,14 @@ class Formatter {
6934
7544
  (found, { literal, val }) => (literal ? found : found.concat(val)),
6935
7545
  []
6936
7546
  ),
6937
- collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter((t) => t));
6938
- return stringifyTokens(tokens, tokenToString(collapsed));
7547
+ collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter((t) => t)),
7548
+ durationInfo = {
7549
+ isNegativeDuration: collapsed < 0,
7550
+ // this relies on "collapsed" being based on "shiftTo", which builds up the object
7551
+ // in order
7552
+ largestUnit: Object.keys(collapsed.values)[0],
7553
+ };
7554
+ return stringifyTokens(tokens, tokenToString(collapsed, durationInfo));
6939
7555
  }
6940
7556
  }
6941
7557
 
@@ -6996,11 +7612,11 @@ function simpleParse(...keys) {
6996
7612
  }
6997
7613
 
6998
7614
  // ISO and SQL parsing
6999
- const offsetRegex = /(?:(Z)|([+-]\d\d)(?::?(\d\d))?)/;
7615
+ const offsetRegex = /(?:([Zz])|([+-]\d\d)(?::?(\d\d))?)/;
7000
7616
  const isoExtendedZone = `(?:${offsetRegex.source}?(?:\\[(${ianaRegex.source})\\])?)?`;
7001
7617
  const isoTimeBaseRegex = /(\d\d)(?::?(\d\d)(?::?(\d\d)(?:[.,](\d{1,30}))?)?)?/;
7002
7618
  const isoTimeRegex = RegExp(`${isoTimeBaseRegex.source}${isoExtendedZone}`);
7003
- const isoTimeExtensionRegex = RegExp(`(?:T${isoTimeRegex.source})?`);
7619
+ const isoTimeExtensionRegex = RegExp(`(?:[Tt]${isoTimeRegex.source})?`);
7004
7620
  const isoYmdRegex = /([+-]\d{6}|\d{4})(?:-?(\d\d)(?:-?(\d\d))?)?/;
7005
7621
  const isoWeekRegex = /(\d{4})-?W(\d\d)(?:-?(\d))?/;
7006
7622
  const isoOrdinalRegex = /(\d{4})-?(\d{3})/;
@@ -7715,9 +8331,13 @@ let Duration$1 = class Duration {
7715
8331
  * @param {string} fmt - the format string
7716
8332
  * @param {Object} opts - options
7717
8333
  * @param {boolean} [opts.floor=true] - floor numerical values
8334
+ * @param {'negative'|'all'|'negativeLargestOnly'} [opts.signMode=negative] - How to handle signs
7718
8335
  * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat("y d s") //=> "1 6 2"
7719
8336
  * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat("yy dd sss") //=> "01 06 002"
7720
8337
  * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toFormat("M S") //=> "12 518402000"
8338
+ * @example Duration.fromObject({ days: 6, seconds: 2 }).toFormat("d s", { signMode: "all" }) //=> "+6 +2"
8339
+ * @example Duration.fromObject({ days: -6, seconds: -2 }).toFormat("d s", { signMode: "all" }) //=> "-6 -2"
8340
+ * @example Duration.fromObject({ days: -6, seconds: -2 }).toFormat("d s", { signMode: "negativeLargestOnly" }) //=> "-6 2"
7721
8341
  * @return {string}
7722
8342
  */
7723
8343
  toFormat(fmt, opts = {}) {
@@ -7737,21 +8357,25 @@ let Duration$1 = class Duration {
7737
8357
  * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options
7738
8358
  * @param {Object} opts - Formatting options. Accepts the same keys as the options parameter of the native `Intl.NumberFormat` constructor, as well as `listStyle`.
7739
8359
  * @param {string} [opts.listStyle='narrow'] - How to format the merged list. Corresponds to the `style` property of the options parameter of the native `Intl.ListFormat` constructor.
8360
+ * @param {boolean} [opts.showZeros=true] - Show all units previously used by the duration even if they are zero
7740
8361
  * @example
7741
8362
  * ```js
7742
- * var dur = Duration.fromObject({ days: 1, hours: 5, minutes: 6 })
7743
- * dur.toHuman() //=> '1 day, 5 hours, 6 minutes'
7744
- * dur.toHuman({ listStyle: "long" }) //=> '1 day, 5 hours, and 6 minutes'
7745
- * dur.toHuman({ unitDisplay: "short" }) //=> '1 day, 5 hr, 6 min'
8363
+ * var dur = Duration.fromObject({ months: 1, weeks: 0, hours: 5, minutes: 6 })
8364
+ * dur.toHuman() //=> '1 month, 0 weeks, 5 hours, 6 minutes'
8365
+ * dur.toHuman({ listStyle: "long" }) //=> '1 month, 0 weeks, 5 hours, and 6 minutes'
8366
+ * dur.toHuman({ unitDisplay: "short" }) //=> '1 mth, 0 wks, 5 hr, 6 min'
8367
+ * dur.toHuman({ showZeros: false }) //=> '1 month, 5 hours, 6 minutes'
7746
8368
  * ```
7747
8369
  */
7748
8370
  toHuman(opts = {}) {
7749
8371
  if (!this.isValid) return INVALID$2;
7750
8372
 
8373
+ const showZeros = opts.showZeros !== false;
8374
+
7751
8375
  const l = orderedUnits$1
7752
8376
  .map((unit) => {
7753
8377
  const val = this.values[unit];
7754
- if (isUndefined(val)) {
8378
+ if (isUndefined(val) || (val === 0 && !showZeros)) {
7755
8379
  return null;
7756
8380
  }
7757
8381
  return this.loc
@@ -8111,6 +8735,17 @@ let Duration$1 = class Duration {
8111
8735
  return clone$1(this, { values: negated }, true);
8112
8736
  }
8113
8737
 
8738
+ /**
8739
+ * Removes all units with values equal to 0 from this Duration.
8740
+ * @example Duration.fromObject({ years: 2, days: 0, hours: 0, minutes: 0 }).removeZeros().toObject() //=> { years: 2 }
8741
+ * @return {Duration}
8742
+ */
8743
+ removeZeros() {
8744
+ if (!this.isValid) return this;
8745
+ const vals = removeZeroes(this.values);
8746
+ return clone$1(this, { values: vals }, true);
8747
+ }
8748
+
8114
8749
  /**
8115
8750
  * Get the years.
8116
8751
  * @type {number}
@@ -8421,7 +9056,8 @@ class Interval {
8421
9056
  }
8422
9057
 
8423
9058
  /**
8424
- * Returns the end of the Interval
9059
+ * Returns the end of the Interval. This is the first instant which is not part of the interval
9060
+ * (Interval is half-open).
8425
9061
  * @type {DateTime}
8426
9062
  */
8427
9063
  get end() {
@@ -8429,8 +9065,16 @@ class Interval {
8429
9065
  }
8430
9066
 
8431
9067
  /**
8432
- * Returns whether this Interval's end is at least its start, meaning that the Interval isn't 'backwards'.
8433
- * @type {boolean}
9068
+ * Returns the last DateTime included in the interval (since end is not part of the interval)
9069
+ * @type {DateTime}
9070
+ */
9071
+ get lastDateTime() {
9072
+ return this.isValid ? (this.e ? this.e.minus(1) : null) : null;
9073
+ }
9074
+
9075
+ /**
9076
+ * Returns whether this Interval's end is at least its start, meaning that the Interval isn't 'backwards'.
9077
+ * @type {boolean}
8434
9078
  */
8435
9079
  get isValid() {
8436
9080
  return this.invalidReason === null;
@@ -8692,8 +9336,11 @@ class Interval {
8692
9336
  }
8693
9337
 
8694
9338
  /**
8695
- * Merge an array of Intervals into a equivalent minimal set of Intervals.
9339
+ * Merge an array of Intervals into an equivalent minimal set of Intervals.
8696
9340
  * Combines overlapping and adjacent Intervals.
9341
+ * The resulting array will contain the Intervals in ascending order, that is, starting with the earliest Interval
9342
+ * and ending with the latest.
9343
+ *
8697
9344
  * @param {Array} intervals
8698
9345
  * @return {Array}
8699
9346
  */
@@ -9841,21 +10488,22 @@ function toTechFormat(dt, format, allowZ = true) {
9841
10488
  : null;
9842
10489
  }
9843
10490
 
9844
- function toISODate(o, extended) {
10491
+ function toISODate(o, extended, precision) {
9845
10492
  const longFormat = o.c.year > 9999 || o.c.year < 0;
9846
10493
  let c = "";
9847
10494
  if (longFormat && o.c.year >= 0) c += "+";
9848
10495
  c += padStart(o.c.year, longFormat ? 6 : 4);
9849
-
10496
+ if (precision === "year") return c;
9850
10497
  if (extended) {
9851
10498
  c += "-";
9852
10499
  c += padStart(o.c.month);
10500
+ if (precision === "month") return c;
9853
10501
  c += "-";
9854
- c += padStart(o.c.day);
9855
10502
  } else {
9856
10503
  c += padStart(o.c.month);
9857
- c += padStart(o.c.day);
10504
+ if (precision === "month") return c;
9858
10505
  }
10506
+ c += padStart(o.c.day);
9859
10507
  return c;
9860
10508
  }
9861
10509
 
@@ -9865,26 +10513,39 @@ function toISOTime(
9865
10513
  suppressSeconds,
9866
10514
  suppressMilliseconds,
9867
10515
  includeOffset,
9868
- extendedZone
10516
+ extendedZone,
10517
+ precision
9869
10518
  ) {
9870
- let c = padStart(o.c.hour);
9871
- if (extended) {
9872
- c += ":";
9873
- c += padStart(o.c.minute);
9874
- if (o.c.millisecond !== 0 || o.c.second !== 0 || !suppressSeconds) {
9875
- c += ":";
9876
- }
9877
- } else {
9878
- c += padStart(o.c.minute);
9879
- }
9880
-
9881
- if (o.c.millisecond !== 0 || o.c.second !== 0 || !suppressSeconds) {
9882
- c += padStart(o.c.second);
9883
-
9884
- if (o.c.millisecond !== 0 || !suppressMilliseconds) {
9885
- c += ".";
9886
- c += padStart(o.c.millisecond, 3);
9887
- }
10519
+ let showSeconds = !suppressSeconds || o.c.millisecond !== 0 || o.c.second !== 0,
10520
+ c = "";
10521
+ switch (precision) {
10522
+ case "day":
10523
+ case "month":
10524
+ case "year":
10525
+ break;
10526
+ default:
10527
+ c += padStart(o.c.hour);
10528
+ if (precision === "hour") break;
10529
+ if (extended) {
10530
+ c += ":";
10531
+ c += padStart(o.c.minute);
10532
+ if (precision === "minute") break;
10533
+ if (showSeconds) {
10534
+ c += ":";
10535
+ c += padStart(o.c.second);
10536
+ }
10537
+ } else {
10538
+ c += padStart(o.c.minute);
10539
+ if (precision === "minute") break;
10540
+ if (showSeconds) {
10541
+ c += padStart(o.c.second);
10542
+ }
10543
+ }
10544
+ if (precision === "second") break;
10545
+ if (showSeconds && (!suppressMilliseconds || o.c.millisecond !== 0)) {
10546
+ c += ".";
10547
+ c += padStart(o.c.millisecond, 3);
10548
+ }
9888
10549
  }
9889
10550
 
9890
10551
  if (includeOffset) {
@@ -10016,15 +10677,27 @@ function normalizeUnitWithLocalWeeks(unit) {
10016
10677
  // This is safe for quickDT (used by local() and utc()) because we don't fill in
10017
10678
  // higher-order units from tsNow (as we do in fromObject, this requires that
10018
10679
  // offset is calculated from tsNow).
10680
+ /**
10681
+ * @param {Zone} zone
10682
+ * @return {number}
10683
+ */
10019
10684
  function guessOffsetForZone(zone) {
10020
- if (!zoneOffsetGuessCache[zone]) {
10021
- if (zoneOffsetTs === undefined) {
10022
- zoneOffsetTs = Settings.now();
10023
- }
10685
+ if (zoneOffsetTs === undefined) {
10686
+ zoneOffsetTs = Settings.now();
10687
+ }
10024
10688
 
10025
- zoneOffsetGuessCache[zone] = zone.offset(zoneOffsetTs);
10689
+ // Do not cache anything but IANA zones, because it is not safe to do so.
10690
+ // Guessing an offset which is not present in the zone can cause wrong results from fixOffset
10691
+ if (zone.type !== "iana") {
10692
+ return zone.offset(zoneOffsetTs);
10693
+ }
10694
+ const zoneName = zone.name;
10695
+ let offsetGuess = zoneOffsetGuessCache.get(zoneName);
10696
+ if (offsetGuess === undefined) {
10697
+ offsetGuess = zone.offset(zoneOffsetTs);
10698
+ zoneOffsetGuessCache.set(zoneName, offsetGuess);
10026
10699
  }
10027
- return zoneOffsetGuessCache[zone];
10700
+ return offsetGuess;
10028
10701
  }
10029
10702
 
10030
10703
  // this is a dumbed down version of fromObject() that runs about 60% faster
@@ -10064,8 +10737,9 @@ function quickDT(obj, opts) {
10064
10737
 
10065
10738
  function diffRelative(start, end, opts) {
10066
10739
  const round = isUndefined(opts.round) ? true : opts.round,
10740
+ rounding = isUndefined(opts.rounding) ? "trunc" : opts.rounding,
10067
10741
  format = (c, unit) => {
10068
- c = roundTo(c, round || opts.calendary ? 0 : 2, true);
10742
+ c = roundTo(c, round || opts.calendary ? 0 : 2, opts.calendary ? "round" : rounding);
10069
10743
  const formatter = end.loc.clone(opts).relFormatter(opts);
10070
10744
  return formatter.format(c, unit);
10071
10745
  },
@@ -10114,7 +10788,7 @@ let zoneOffsetTs;
10114
10788
  * This optimizes quickDT via guessOffsetForZone to avoid repeated calls of
10115
10789
  * zone.offset().
10116
10790
  */
10117
- let zoneOffsetGuessCache = {};
10791
+ const zoneOffsetGuessCache = new Map();
10118
10792
 
10119
10793
  /**
10120
10794
  * A DateTime is an immutable data structure representing a specific date and time and accompanying methods. It contains class and instance methods for creating, parsing, interrogating, transforming, and formatting them.
@@ -10318,7 +10992,7 @@ class DateTime {
10318
10992
  throw new InvalidArgumentError(
10319
10993
  `fromMillis requires a numerical input, but received a ${typeof milliseconds} with value ${milliseconds}`
10320
10994
  );
10321
- } else if (milliseconds < -864e13 || milliseconds > MAX_DATE) {
10995
+ } else if (milliseconds < -MAX_DATE || milliseconds > MAX_DATE) {
10322
10996
  // this isn't perfect because we can still end up out of range because of additional shifting, but it's a start
10323
10997
  return DateTime.invalid("Timestamp out of range");
10324
10998
  } else {
@@ -10679,7 +11353,7 @@ class DateTime {
10679
11353
 
10680
11354
  static resetCache() {
10681
11355
  zoneOffsetTs = undefined;
10682
- zoneOffsetGuessCache = {};
11356
+ zoneOffsetGuessCache.clear();
10683
11357
  }
10684
11358
 
10685
11359
  // INFO
@@ -11444,11 +12118,14 @@ class DateTime {
11444
12118
  * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'
11445
12119
  * @param {boolean} [opts.extendedZone=false] - add the time zone format extension
11446
12120
  * @param {string} [opts.format='extended'] - choose between the basic and extended format
12121
+ * @param {string} [opts.precision='milliseconds'] - truncate output to desired presicion: 'years', 'months', 'days', 'hours', 'minutes', 'seconds' or 'milliseconds'. When precision and suppressSeconds or suppressMilliseconds are used together, precision sets the maximum unit shown in the output, however seconds or milliseconds will still be suppressed if they are 0.
11447
12122
  * @example DateTime.utc(1983, 5, 25).toISO() //=> '1982-05-25T00:00:00.000Z'
11448
12123
  * @example DateTime.now().toISO() //=> '2017-04-22T20:47:05.335-04:00'
11449
12124
  * @example DateTime.now().toISO({ includeOffset: false }) //=> '2017-04-22T20:47:05.335'
11450
12125
  * @example DateTime.now().toISO({ format: 'basic' }) //=> '20170422T204705.335-0400'
11451
- * @return {string}
12126
+ * @example DateTime.now().toISO({ precision: 'day' }) //=> '2017-04-22Z'
12127
+ * @example DateTime.now().toISO({ precision: 'minute' }) //=> '2017-04-22T20:47Z'
12128
+ * @return {string|null}
11452
12129
  */
11453
12130
  toISO({
11454
12131
  format = "extended",
@@ -11456,16 +12133,26 @@ class DateTime {
11456
12133
  suppressMilliseconds = false,
11457
12134
  includeOffset = true,
11458
12135
  extendedZone = false,
12136
+ precision = "milliseconds",
11459
12137
  } = {}) {
11460
12138
  if (!this.isValid) {
11461
12139
  return null;
11462
12140
  }
11463
12141
 
12142
+ precision = normalizeUnit(precision);
11464
12143
  const ext = format === "extended";
11465
12144
 
11466
- let c = toISODate(this, ext);
11467
- c += "T";
11468
- c += toISOTime(this, ext, suppressSeconds, suppressMilliseconds, includeOffset, extendedZone);
12145
+ let c = toISODate(this, ext, precision);
12146
+ if (orderedUnits.indexOf(precision) >= 3) c += "T";
12147
+ c += toISOTime(
12148
+ this,
12149
+ ext,
12150
+ suppressSeconds,
12151
+ suppressMilliseconds,
12152
+ includeOffset,
12153
+ extendedZone,
12154
+ precision
12155
+ );
11469
12156
  return c;
11470
12157
  }
11471
12158
 
@@ -11473,16 +12160,17 @@ class DateTime {
11473
12160
  * Returns an ISO 8601-compliant string representation of this DateTime's date component
11474
12161
  * @param {Object} opts - options
11475
12162
  * @param {string} [opts.format='extended'] - choose between the basic and extended format
12163
+ * @param {string} [opts.precision='day'] - truncate output to desired precision: 'years', 'months', or 'days'.
11476
12164
  * @example DateTime.utc(1982, 5, 25).toISODate() //=> '1982-05-25'
11477
12165
  * @example DateTime.utc(1982, 5, 25).toISODate({ format: 'basic' }) //=> '19820525'
11478
- * @return {string}
12166
+ * @example DateTime.utc(1982, 5, 25).toISODate({ precision: 'month' }) //=> '1982-05'
12167
+ * @return {string|null}
11479
12168
  */
11480
- toISODate({ format = "extended" } = {}) {
12169
+ toISODate({ format = "extended", precision = "day" } = {}) {
11481
12170
  if (!this.isValid) {
11482
12171
  return null;
11483
12172
  }
11484
-
11485
- return toISODate(this, format === "extended");
12173
+ return toISODate(this, format === "extended", normalizeUnit(precision));
11486
12174
  }
11487
12175
 
11488
12176
  /**
@@ -11503,10 +12191,12 @@ class DateTime {
11503
12191
  * @param {boolean} [opts.extendedZone=true] - add the time zone format extension
11504
12192
  * @param {boolean} [opts.includePrefix=false] - include the `T` prefix
11505
12193
  * @param {string} [opts.format='extended'] - choose between the basic and extended format
12194
+ * @param {string} [opts.precision='milliseconds'] - truncate output to desired presicion: 'hours', 'minutes', 'seconds' or 'milliseconds'. When precision and suppressSeconds or suppressMilliseconds are used together, precision sets the maximum unit shown in the output, however seconds or milliseconds will still be suppressed if they are 0.
11506
12195
  * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime() //=> '07:34:19.361Z'
11507
12196
  * @example DateTime.utc().set({ hour: 7, minute: 34, seconds: 0, milliseconds: 0 }).toISOTime({ suppressSeconds: true }) //=> '07:34Z'
11508
12197
  * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime({ format: 'basic' }) //=> '073419.361Z'
11509
12198
  * @example DateTime.utc().set({ hour: 7, minute: 34 }).toISOTime({ includePrefix: true }) //=> 'T07:34:19.361Z'
12199
+ * @example DateTime.utc().set({ hour: 7, minute: 34, second: 56 }).toISOTime({ precision: 'minute' }) //=> '07:34Z'
11510
12200
  * @return {string}
11511
12201
  */
11512
12202
  toISOTime({
@@ -11516,12 +12206,14 @@ class DateTime {
11516
12206
  includePrefix = false,
11517
12207
  extendedZone = false,
11518
12208
  format = "extended",
12209
+ precision = "milliseconds",
11519
12210
  } = {}) {
11520
12211
  if (!this.isValid) {
11521
12212
  return null;
11522
12213
  }
11523
12214
 
11524
- let c = includePrefix ? "T" : "";
12215
+ precision = normalizeUnit(precision);
12216
+ let c = includePrefix && orderedUnits.indexOf(precision) >= 3 ? "T" : "";
11525
12217
  return (
11526
12218
  c +
11527
12219
  toISOTime(
@@ -11530,7 +12222,8 @@ class DateTime {
11530
12222
  suppressSeconds,
11531
12223
  suppressMilliseconds,
11532
12224
  includeOffset,
11533
- extendedZone
12225
+ extendedZone,
12226
+ precision
11534
12227
  )
11535
12228
  );
11536
12229
  }
@@ -11560,7 +12253,7 @@ class DateTime {
11560
12253
  /**
11561
12254
  * Returns a string representation of this DateTime appropriate for use in SQL Date
11562
12255
  * @example DateTime.utc(2014, 7, 13).toSQLDate() //=> '2014-07-13'
11563
- * @return {string}
12256
+ * @return {string|null}
11564
12257
  */
11565
12258
  toSQLDate() {
11566
12259
  if (!this.isValid) {
@@ -11655,7 +12348,7 @@ class DateTime {
11655
12348
  }
11656
12349
 
11657
12350
  /**
11658
- * Returns the epoch seconds of this DateTime.
12351
+ * Returns the epoch seconds (including milliseconds in the fractional part) of this DateTime.
11659
12352
  * @return {number}
11660
12353
  */
11661
12354
  toSeconds() {
@@ -11762,7 +12455,7 @@ class DateTime {
11762
12455
  /**
11763
12456
  * Return an Interval spanning between this DateTime and another DateTime
11764
12457
  * @param {DateTime} otherDateTime - the other end point of the Interval
11765
- * @return {Interval}
12458
+ * @return {Interval|DateTime}
11766
12459
  */
11767
12460
  until(otherDateTime) {
11768
12461
  return this.isValid ? Interval.fromDateTimes(this, otherDateTime) : this;
@@ -11808,12 +12501,13 @@ class DateTime {
11808
12501
 
11809
12502
  /**
11810
12503
  * Returns a string representation of a this time relative to now, such as "in two days". Can only internationalize if your
11811
- * platform supports Intl.RelativeTimeFormat. Rounds down by default.
12504
+ * platform supports Intl.RelativeTimeFormat. Rounds towards zero by default.
11812
12505
  * @param {Object} options - options that affect the output
11813
12506
  * @param {DateTime} [options.base=DateTime.now()] - the DateTime to use as the basis to which this time is compared. Defaults to now.
11814
12507
  * @param {string} [options.style="long"] - the style of units, must be "long", "short", or "narrow"
11815
12508
  * @param {string|string[]} options.unit - use a specific unit or array of units; if omitted, or an array, the method will pick the best unit. Use an array or one of "years", "quarters", "months", "weeks", "days", "hours", "minutes", or "seconds"
11816
12509
  * @param {boolean} [options.round=true] - whether to round the numbers in the output.
12510
+ * @param {string} [options.rounding="trunc"] - rounding method to use when rounding the numbers in the output. Can be "trunc" (toward zero), "expand" (away from zero), "round", "floor", or "ceil".
11817
12511
  * @param {number} [options.padding=0] - padding in milliseconds. This allows you to round up the result if it fits inside the threshold. Don't use in combination with {round: false} because the decimal output will include the padding.
11818
12512
  * @param {string} options.locale - override the locale of this DateTime
11819
12513
  * @param {string} options.numberingSystem - override the numberingSystem of this DateTime. The Intl system may choose not to honor this
@@ -14201,7 +14895,7 @@ class BinaryReader {
14201
14895
  // ignore
14202
14896
  }
14203
14897
  break;
14204
- // @ts-expect-error TS7029: Fallthrough case in switch
14898
+ // @ts-ignore TS7029: Fallthrough case in switch -- ignore instead of expect-error for compiler settings without noFallthroughCasesInSwitch: true
14205
14899
  case WireType.Bit64:
14206
14900
  this.pos += 4;
14207
14901
  case WireType.Bit32:
@@ -14482,7 +15176,6 @@ function around(index, lng, lat, maxResults = Infinity, maxDistance = Infinity,
14482
15176
  left: 0, // left index in the kd-tree array
14483
15177
  right: index.ids.length - 1, // right index
14484
15178
  axis: 0, // 0 for longitude axis and 1 for latitude axis
14485
- dist: 0, // will hold the lower bound of children's distances to the query point
14486
15179
  minLng: -180, // bounding box of the node
14487
15180
  minLat: -90,
14488
15181
  maxLng: 180,
@@ -14950,7 +15643,7 @@ function sqDist(ax, ay, bx, by) {
14950
15643
  return dx * dx + dy * dy;
14951
15644
  }
14952
15645
 
14953
- const xt="ENTRIES",B="KEYS",G="VALUES",g="";class V{set;_type;_path;constructor(e,n){const o=e._tree,s=Array.from(o.keys());this.set=e,this._type=n,this._path=s.length>0?[{node:o,keys:s}]:[];}next(){const e=this.dive();return this.backtrack(),e}dive(){if(this._path.length===0)return {done:true,value:undefined};const{node:e,keys:n}=z(this._path);if(z(n)===g)return {done:false,value:this.result()};const o=e.get(z(n));return this._path.push({node:o,keys:Array.from(o.keys())}),this.dive()}backtrack(){if(this._path.length===0)return;const e=z(this._path).keys;e.pop(),!(e.length>0)&&(this._path.pop(),this.backtrack());}key(){return this.set._prefix+this._path.map(({keys:e})=>z(e)).filter(e=>e!==g).join("")}value(){return z(this._path).node.get(g)}result(){switch(this._type){case G:return this.value();case B:return this.key();default:return [this.key(),this.value()]}}[Symbol.iterator](){return this}}const z=t=>t[t.length-1],zt=(t,e,n)=>{const o=new Map;if(typeof e!="string")return o;const s=e.length+1,r=s+n,i=new Uint8Array(r*s).fill(n+1);for(let c=0;c<s;++c)i[c]=c;for(let c=1;c<r;++c)i[c*s]=c;return K(t,e,n,o,i,1,s,""),o},K=(t,e,n,o,s,r,i,c)=>{const u=r*i;t:for(const d of t.keys())if(d===g){const a=s[u-1];a<=n&&o.set(c,[t.get(d),a]);}else {let a=r;for(let h=0;h<d.length;++h,++a){const f=d[h],_=i*a,p=_-i;let l=s[_];const m=Math.max(0,a-n-1),y=Math.min(i-1,a+n);for(let w=m;w<y;++w){const C=f!==e[w],O=s[p+w]+ +C,b=s[p+w+1]+1,x=s[_+w]+1,S=s[_+w+1]=Math.min(O,b,x);S<l&&(l=S);}if(l>n)continue t}K(t.get(d),e,n,o,s,a,i,c+d);}};class I{_tree;_prefix;_size=undefined;constructor(e=new Map,n=""){this._tree=e,this._prefix=n;}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");const[n,o]=v(this._tree,e.slice(this._prefix.length));if(n===undefined){const[s,r]=L(o);for(const i of s.keys())if(i!==g&&i.startsWith(r)){const c=new Map;return c.set(i.slice(r.length),s.get(i)),new I(c,e)}}return new I(n,e)}clear(){this._size=undefined,this._tree.clear();}delete(e){return this._size=undefined,St(this._tree,e)}entries(){return new V(this,xt)}forEach(e){for(const[n,o]of this)e(n,o,this);}fuzzyGet(e,n){return zt(this._tree,e,n)}get(e){const n=T(this._tree,e);return n!==undefined?n.get(g):undefined}has(e){return T(this._tree,e)?.has(g)??false}keys(){return new V(this,B)}set(e,n){if(typeof e!="string")throw new Error("key must be a string");return this._size=undefined,M(this._tree,e).set(g,n),this}get size(){if(this._size)return this._size;this._size=0;const e=this.entries();for(;!e.next().done;)this._size+=1;return this._size}update(e,n){if(typeof e!="string")throw new Error("key must be a string");this._size=undefined;const o=M(this._tree,e);return o.set(g,n(o.get(g))),this}fetch(e,n){if(typeof e!="string")throw new Error("key must be a string");this._size=undefined;const o=M(this._tree,e);let s=o.get(g);return s===undefined&&o.set(g,s=n()),s}values(){return new V(this,G)}[Symbol.iterator](){return this.entries()}static from(e){const n=new I;for(const[o,s]of e)n.set(o,s);return n}static fromObject(e){return I.from(Object.entries(e))}}const v=(t,e,n=[])=>{if(e.length===0||t==null)return [t,n];for(const o of t.keys())if(o!==g&&e.startsWith(o))return n.push([t,o]),v(t.get(o),e.slice(o.length),n);return n.push([t,e]),v(undefined,"",n)},T=(t,e)=>{if(e.length===0||!t)return t;for(const n of t.keys())if(n!==g&&e.startsWith(n))return T(t.get(n),e.slice(n.length))},M=(t,e)=>{const n=e.length;t:for(let o=0;t&&o<n;){for(const r of t.keys())if(r!==g&&e[o]===r[0]){const i=Math.min(n-o,r.length);let c=1;for(;c<i&&e[o+c]===r[c];)++c;const u=t.get(r);if(c===r.length)t=u;else {const d=new Map;d.set(r.slice(c),u),t.set(e.slice(o,o+c),d),t.delete(r),t=d;}o+=c;continue t}const s=new Map;return t.set(e.slice(o),s),s}return t},St=(t,e)=>{const[n,o]=v(t,e);if(n!==undefined){if(n.delete(g),n.size===0)Q(o);else if(n.size===1){const[s,r]=n.entries().next().value;Y(o,s,r);}}},Q=t=>{if(t.length===0)return;const[e,n]=L(t);if(e.delete(n),e.size===0)Q(t.slice(0,-1));else if(e.size===1){const[o,s]=e.entries().next().value;o!==g&&Y(t.slice(0,-1),o,s);}},Y=(t,e,n)=>{if(t.length===0)return;const[o,s]=L(t);o.set(s+e,n),o.delete(s);},L=t=>t[t.length-1],Z=(t,e)=>t._idToShortId.has(e),vt=/[\n\r\p{Z}\p{P}]+/u,D="or",H="and",Ft="and_not",kt=(t,e)=>{t.includes(e)||t.push(e);},tt=(t,e)=>{for(const n of e)t.includes(n)||t.push(n);},et=({score:t},{score:e})=>e-t,nt=()=>new Map,E=(t,e)=>Object.prototype.hasOwnProperty.call(t,e)?t[e]:undefined,ot={[D]:(t,e)=>{for(const n of e.keys()){const o=t.get(n);if(o==null)t.set(n,e.get(n));else {const{score:s,terms:r,match:i}=e.get(n);o.score=o.score+s,o.match=Object.assign(o.match,i),tt(o.terms,r);}}return t},[H]:(t,e)=>{const n=new Map;for(const o of e.keys()){const s=t.get(o);if(s==null)continue;const{score:r,terms:i,match:c}=e.get(o);tt(s.terms,i),n.set(o,{score:s.score+r,terms:s.terms,match:Object.assign(s.match,c)});}return n},[Ft]:(t,e)=>{for(const n of e.keys())t.delete(n);return t}},Ct=(t,e,n,o,s,r)=>{const{k:i,b:c,d:u}=r;return Math.log(1+(n-e+.5)/(e+.5))*(u+t*(i+1)/(t+i*(1-c+c*o/s)))},Ot=t=>(e,n,o)=>({term:e,fuzzy:typeof t.fuzzy=="function"?t.fuzzy(e,n,o):t.fuzzy??false,prefix:typeof t.prefix=="function"?t.prefix(e,n,o):t.prefix===true,termBoost:typeof t.boostTerm=="function"?t.boostTerm(e,n,o):1}),st=(t,e,n,o)=>{for(const s of Object.keys(t._fieldIds))if(t._fieldIds[s]===n){t._options.logger("warn",`SlimSearch: document with ID ${t._documentIds.get(e)} has changed before removal: term "${o}" was not present in field "${s}". Removing a document after it has changed can corrupt the index!`,"version_conflict");return}},it=(t,e,n,o)=>{const s=t._index.fetch(o,nt);let r=s.get(e);if(r==null)r=new Map,r.set(n,1),s.set(e,r);else {const i=r.get(n);r.set(n,(i??0)+1);}},A=(t,e,n,o)=>{if(!t._index.has(o)){st(t,n,e,o);return}const s=t._index.fetch(o,nt),r=s.get(e),i=r?.get(n);!r||typeof i>"u"?st(t,n,e,o):i<=1?r.size<=1?s.delete(e):r.delete(n):r.set(n,i-1),t._index.get(o).size===0&&t._index.delete(o);},Vt=(t,e,n,o,s)=>{let r=t._fieldLength.get(e);r==null&&t._fieldLength.set(e,r=[]),r[n]=s;const i=(t._avgFieldLength[n]||0)*o+s;t._avgFieldLength[n]=i/(o+1);},Tt=(t,e)=>{const n=t._nextId;return t._idToShortId.set(e,n),t._documentIds.set(n,e),t._documentCount+=1,t._nextId+=1,n},Mt=(t,e,n)=>{const{storeFields:o,extractField:s}=t._options;if(o?.length===0)return;let r=t._storedFields.get(e);r===undefined&&t._storedFields.set(e,r={});for(const i of o){const c=s(n,i);c!=null&&(r[i]=c);}},j=(t,e)=>{const{extractField:n,tokenize:o,processTerm:s,fields:r,idField:i}=t._options,c=n(e,i);if(c==null)throw new Error(`SlimSearch: document does not have ID field "${i}"`);if(Z(t,c))throw new Error(`SlimSearch: duplicate ID ${c}`);const u=Tt(t,c);Mt(t,u,e);for(const d of r){const a=n(e,d);if(a==null)continue;const h=o(a.toString(),d),f=t._fieldIds[d],_=new Set(h).size;Vt(t,u,f,t._documentCount-1,_);for(const p of h){const l=s(p,d);if(Array.isArray(l))for(const m of l)it(t,f,u,m);else l&&it(t,f,u,l);}}},q=(t,e)=>{for(const n of e)j(t,n);},Dt={k:1.2,b:.7,d:.5},$={idField:"id",extractField:(t,e)=>t[e],tokenize:t=>t.split(vt),processTerm:t=>t.toLowerCase(),fields:undefined,searchOptions:undefined,storeFields:[],logger:(t,e)=>{console?.[t]?.(e);},autoVacuum:true},rt={combineWith:D,prefix:false,fuzzy:false,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:Dt},Et={combineWith:H,prefix:(t,e,n)=>e===n.length-1},N={batchSize:1e3,batchWait:10},W={minDirtFactor:.1,minDirtCount:20},P={...N,...W},R=Symbol("*"),jt=(t,e)=>{const n=new Map,o={...t._options.searchOptions,...e};for(const[s,r]of t._documentIds){const i=o.boostDocument?o.boostDocument(r,"",t._storedFields.get(s)):1;n.set(s,{score:i,terms:[],match:{}});}return n},ct=(t,e=D)=>{if(t.length===0)return new Map;const n=e.toLowerCase();if(!(n in ot))throw new Error(`Invalid combination operator: ${e}`);return t.reduce(ot[n])},J=(t,e,n,o,s,r,i,c,u,d=new Map)=>{if(r==null)return d;for(const a of Object.keys(i)){const h=i[a],f=t._fieldIds[a],_=r.get(f);if(_==null)continue;let p=_.size;const l=t._avgFieldLength[f];for(const m of _.keys()){if(!t._documentIds.has(m)){A(t,f,m,n),p-=1;continue}const y=c?c(t._documentIds.get(m),n,t._storedFields.get(m)):1;if(!y)continue;const w=_.get(m),C=t._fieldLength.get(m)[f],O=Ct(w,p,t._documentCount,C,l,u),b=o*s*h*y*O,x=d.get(m);if(x){x.score+=b,kt(x.terms,e);const S=E(x.match,n);S?S.push(a):x.match[n]=[a];}else d.set(m,{score:b,terms:[e],match:{[n]:[a]}});}}return d},qt=(t,e,n)=>{const o={...t._options.searchOptions,...n},s=(o.fields??t._options.fields).reduce((l,m)=>({...l,[m]:E(o.boost,m)||1}),{}),{boostDocument:r,weights:i,maxFuzzy:c,bm25:u}=o,{fuzzy:d,prefix:a}={...rt.weights,...i},h=t._index.get(e.term),f=J(t,e.term,e.term,1,e.termBoost,h,s,r,u);let _,p;if(e.prefix&&(_=t._index.atPrefix(e.term)),e.fuzzy){const l=e.fuzzy===true?.2:e.fuzzy,m=l<1?Math.min(c,Math.round(e.term.length*l)):l;m&&(p=t._index.fuzzyGet(e.term,m));}if(_)for(const[l,m]of _){const y=l.length-e.term.length;if(!y)continue;p?.delete(l);const w=a*l.length/(l.length+.3*y);J(t,e.term,l,w,e.termBoost,m,s,r,u,f);}if(p)for(const l of p.keys()){const[m,y]=p.get(l);if(!y)continue;const w=d*l.length/(l.length+y);J(t,e.term,l,w,e.termBoost,m,s,r,u,f);}return f},ut=(t,e,n={})=>{if(e===R)return jt(t,n);if(typeof e!="string"){const a={...n,...e,queries:undefined},h=e.queries.map(f=>ut(t,f,a));return ct(h,a.combineWith)}const{tokenize:o,processTerm:s,searchOptions:r}=t._options,i={tokenize:o,processTerm:s,...r,...n},{tokenize:c,processTerm:u}=i,d=c(e).flatMap(a=>u(a)).filter(a=>!!a).map(Ot(i)).map(a=>qt(t,a,i));return ct(d,i.combineWith)},dt=(t,e,n={})=>{const{searchOptions:o}=t._options,s={...o,...n},r=ut(t,e,n),i=[];for(const[c,{score:u,terms:d,match:a}]of r){const h=d.length||1,f={id:t._documentIds.get(c),score:u*h,terms:Object.keys(a),queryTerms:d,match:a};Object.assign(f,t._storedFields.get(c)),(s.filter==null||s.filter(f))&&i.push(f);}return e===R&&s.boostDocument==null||i.sort(et),i};class Nt{_options;_index;_documentCount;_documentIds;_idToShortId;_fieldIds;_fieldLength;_avgFieldLength;_nextId;_storedFields;_dirtCount;_currentVacuum;_enqueuedVacuum;_enqueuedVacuumConditions;constructor(e){if(!e?.fields)throw new Error('SlimSearch: option "fields" must be provided');const n=e.autoVacuum==null||e.autoVacuum===true?P:e.autoVacuum;this._options={...$,...e,autoVacuum:n,searchOptions:{...rt,...e.searchOptions},autoSuggestOptions:{...Et,...e.autoSuggestOptions}},this._index=new I,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=W,this.addFields(this._options.fields);}get isVacuuming(){return this._currentVacuum!=null}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}get documentCount(){return this._documentCount}get termCount(){return this._index.size}toJSON(){const e=[];for(const[n,o]of this._index){const s={};for(const[r,i]of o)s[r]=Object.fromEntries(i);e.push([n,s]);}return {documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:e,version:2}}addFields(e){for(let n=0;n<e.length;n++)this._fieldIds[e[n]]=n;}}const lt=t=>new Nt(t);
15646
+ const xt="ENTRIES",B="KEYS",G="VALUES",g="";class V{set;_type;_path;constructor(e,n){const o=e._tree,s=Array.from(o.keys());this.set=e,this._type=n,this._path=s.length>0?[{node:o,keys:s}]:[];}next(){const e=this.dive();return this.backtrack(),e}dive(){if(this._path.length===0)return {done:true,value:void 0};const{node:e,keys:n}=z(this._path);if(z(n)===g)return {done:false,value:this.result()};const o=e.get(z(n));return this._path.push({node:o,keys:Array.from(o.keys())}),this.dive()}backtrack(){if(this._path.length===0)return;const e=z(this._path).keys;e.pop(),!(e.length>0)&&(this._path.pop(),this.backtrack());}key(){return this.set._prefix+this._path.map(({keys:e})=>z(e)).filter(e=>e!==g).join("")}value(){return z(this._path).node.get(g)}result(){switch(this._type){case G:return this.value();case B:return this.key();default:return [this.key(),this.value()]}}[Symbol.iterator](){return this}}const z=t=>t[t.length-1],zt=(t,e,n)=>{const o=new Map;if(typeof e!="string")return o;const s=e.length+1,r=s+n,i=new Uint8Array(r*s).fill(n+1);for(let c=0;c<s;++c)i[c]=c;for(let c=1;c<r;++c)i[c*s]=c;return K(t,e,n,o,i,1,s,""),o},K=(t,e,n,o,s,r,i,c)=>{const u=r*i;t:for(const d of t.keys())if(d===g){const a=s[u-1];a<=n&&o.set(c,[t.get(d),a]);}else {let a=r;for(let h=0;h<d.length;++h,++a){const f=d[h],_=i*a,p=_-i;let l=s[_];const m=Math.max(0,a-n-1),y=Math.min(i-1,a+n);for(let w=m;w<y;++w){const C=f!==e[w],O=s[p+w]+ +C,b=s[p+w+1]+1,x=s[_+w]+1,S=s[_+w+1]=Math.min(O,b,x);S<l&&(l=S);}if(l>n)continue t}K(t.get(d),e,n,o,s,a,i,c+d);}};class I{_tree;_prefix;_size=void 0;constructor(e=new Map,n=""){this._tree=e,this._prefix=n;}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");const[n,o]=v(this._tree,e.slice(this._prefix.length));if(n===void 0){const[s,r]=L(o);for(const i of s.keys())if(i!==g&&i.startsWith(r)){const c=new Map;return c.set(i.slice(r.length),s.get(i)),new I(c,e)}}return new I(n,e)}clear(){this._size=void 0,this._tree.clear();}delete(e){return this._size=void 0,St(this._tree,e)}entries(){return new V(this,xt)}forEach(e){for(const[n,o]of this)e(n,o,this);}fuzzyGet(e,n){return zt(this._tree,e,n)}get(e){const n=T(this._tree,e);return n!==void 0?n.get(g):void 0}has(e){return T(this._tree,e)?.has(g)??false}keys(){return new V(this,B)}set(e,n){if(typeof e!="string")throw new Error("key must be a string");return this._size=void 0,M(this._tree,e).set(g,n),this}get size(){if(this._size)return this._size;this._size=0;const e=this.entries();for(;!e.next().done;)this._size+=1;return this._size}update(e,n){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const o=M(this._tree,e);return o.set(g,n(o.get(g))),this}fetch(e,n){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const o=M(this._tree,e);let s=o.get(g);return s===void 0&&o.set(g,s=n()),s}values(){return new V(this,G)}[Symbol.iterator](){return this.entries()}static from(e){const n=new I;for(const[o,s]of e)n.set(o,s);return n}static fromObject(e){return I.from(Object.entries(e))}}const v=(t,e,n=[])=>{if(e.length===0||t==null)return [t,n];for(const o of t.keys())if(o!==g&&e.startsWith(o))return n.push([t,o]),v(t.get(o),e.slice(o.length),n);return n.push([t,e]),v(void 0,"",n)},T=(t,e)=>{if(e.length===0||!t)return t;for(const n of t.keys())if(n!==g&&e.startsWith(n))return T(t.get(n),e.slice(n.length))},M=(t,e)=>{const n=e.length;t:for(let o=0;t&&o<n;){for(const r of t.keys())if(r!==g&&e[o]===r[0]){const i=Math.min(n-o,r.length);let c=1;for(;c<i&&e[o+c]===r[c];)++c;const u=t.get(r);if(c===r.length)t=u;else {const d=new Map;d.set(r.slice(c),u),t.set(e.slice(o,o+c),d),t.delete(r),t=d;}o+=c;continue t}const s=new Map;return t.set(e.slice(o),s),s}return t},St=(t,e)=>{const[n,o]=v(t,e);if(n!==void 0){if(n.delete(g),n.size===0)Q(o);else if(n.size===1){const[s,r]=n.entries().next().value;Y(o,s,r);}}},Q=t=>{if(t.length===0)return;const[e,n]=L(t);if(e.delete(n),e.size===0)Q(t.slice(0,-1));else if(e.size===1){const[o,s]=e.entries().next().value;o!==g&&Y(t.slice(0,-1),o,s);}},Y=(t,e,n)=>{if(t.length===0)return;const[o,s]=L(t);o.set(s+e,n),o.delete(s);},L=t=>t[t.length-1],Z=(t,e)=>t._idToShortId.has(e),vt=/[\n\r\p{Z}\p{P}]+/u,D="or",H="and",Ft="and_not",kt=(t,e)=>{t.includes(e)||t.push(e);},tt=(t,e)=>{for(const n of e)t.includes(n)||t.push(n);},et=({score:t},{score:e})=>e-t,nt=()=>new Map,E=(t,e)=>Object.prototype.hasOwnProperty.call(t,e)?t[e]:void 0,ot={[D]:(t,e)=>{for(const n of e.keys()){const o=t.get(n);if(o==null)t.set(n,e.get(n));else {const{score:s,terms:r,match:i}=e.get(n);o.score=o.score+s,o.match=Object.assign(o.match,i),tt(o.terms,r);}}return t},[H]:(t,e)=>{const n=new Map;for(const o of e.keys()){const s=t.get(o);if(s==null)continue;const{score:r,terms:i,match:c}=e.get(o);tt(s.terms,i),n.set(o,{score:s.score+r,terms:s.terms,match:Object.assign(s.match,c)});}return n},[Ft]:(t,e)=>{for(const n of e.keys())t.delete(n);return t}},Ct=(t,e,n,o,s,r)=>{const{k:i,b:c,d:u}=r;return Math.log(1+(n-e+.5)/(e+.5))*(u+t*(i+1)/(t+i*(1-c+c*o/s)))},Ot=t=>(e,n,o)=>({term:e,fuzzy:typeof t.fuzzy=="function"?t.fuzzy(e,n,o):t.fuzzy??false,prefix:typeof t.prefix=="function"?t.prefix(e,n,o):t.prefix===true,termBoost:typeof t.boostTerm=="function"?t.boostTerm(e,n,o):1}),st=(t,e,n,o)=>{for(const s of Object.keys(t._fieldIds))if(t._fieldIds[s]===n){t._options.logger("warn",`SlimSearch: document with ID ${t._documentIds.get(e)} has changed before removal: term "${o}" was not present in field "${s}". Removing a document after it has changed can corrupt the index!`,"version_conflict");return}},it=(t,e,n,o)=>{const s=t._index.fetch(o,nt);let r=s.get(e);if(r==null)r=new Map,r.set(n,1),s.set(e,r);else {const i=r.get(n);r.set(n,(i??0)+1);}},A=(t,e,n,o)=>{if(!t._index.has(o)){st(t,n,e,o);return}const s=t._index.fetch(o,nt),r=s.get(e),i=r?.get(n);!r||typeof i>"u"?st(t,n,e,o):i<=1?r.size<=1?s.delete(e):r.delete(n):r.set(n,i-1),t._index.get(o).size===0&&t._index.delete(o);},Vt=(t,e,n,o,s)=>{let r=t._fieldLength.get(e);r==null&&t._fieldLength.set(e,r=[]),r[n]=s;const i=(t._avgFieldLength[n]||0)*o+s;t._avgFieldLength[n]=i/(o+1);},Tt=(t,e)=>{const n=t._nextId;return t._idToShortId.set(e,n),t._documentIds.set(n,e),t._documentCount+=1,t._nextId+=1,n},Mt=(t,e,n)=>{const{storeFields:o,extractField:s}=t._options;if(o?.length===0)return;let r=t._storedFields.get(e);r===void 0&&t._storedFields.set(e,r={});for(const i of o){const c=s(n,i);c!=null&&(r[i]=c);}},j=(t,e)=>{const{extractField:n,tokenize:o,processTerm:s,fields:r,idField:i}=t._options,c=n(e,i);if(c==null)throw new Error(`SlimSearch: document does not have ID field "${i}"`);if(Z(t,c))throw new Error(`SlimSearch: duplicate ID ${c}`);const u=Tt(t,c);Mt(t,u,e);for(const d of r){const a=n(e,d);if(a==null)continue;const h=o(a.toString(),d),f=t._fieldIds[d],_=new Set(h).size;Vt(t,u,f,t._documentCount-1,_);for(const p of h){const l=s(p,d);if(Array.isArray(l))for(const m of l)it(t,f,u,m);else l&&it(t,f,u,l);}}},q=(t,e)=>{for(const n of e)j(t,n);},Dt={k:1.2,b:.7,d:.5},$={idField:"id",extractField:(t,e)=>t[e],tokenize:t=>t.split(vt),processTerm:t=>t.toLowerCase(),fields:void 0,searchOptions:void 0,storeFields:[],logger:(t,e)=>{console?.[t]?.(e);},autoVacuum:true},rt={combineWith:D,prefix:false,fuzzy:false,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:Dt},Et={combineWith:H,prefix:(t,e,n)=>e===n.length-1},N={batchSize:1e3,batchWait:10},W={minDirtFactor:.1,minDirtCount:20},P={...N,...W},R=Symbol("*"),jt=(t,e)=>{const n=new Map,o={...t._options.searchOptions,...e};for(const[s,r]of t._documentIds){const i=o.boostDocument?o.boostDocument(r,"",t._storedFields.get(s)):1;n.set(s,{score:i,terms:[],match:{}});}return n},ct=(t,e=D)=>{if(t.length===0)return new Map;const n=e.toLowerCase();if(!(n in ot))throw new Error(`Invalid combination operator: ${e}`);return t.reduce(ot[n])},J=(t,e,n,o,s,r,i,c,u,d=new Map)=>{if(r==null)return d;for(const a of Object.keys(i)){const h=i[a],f=t._fieldIds[a],_=r.get(f);if(_==null)continue;let p=_.size;const l=t._avgFieldLength[f];for(const m of _.keys()){if(!t._documentIds.has(m)){A(t,f,m,n),p-=1;continue}const y=c?c(t._documentIds.get(m),n,t._storedFields.get(m)):1;if(!y)continue;const w=_.get(m),C=t._fieldLength.get(m)[f],O=Ct(w,p,t._documentCount,C,l,u),b=o*s*h*y*O,x=d.get(m);if(x){x.score+=b,kt(x.terms,e);const S=E(x.match,n);S?S.push(a):x.match[n]=[a];}else d.set(m,{score:b,terms:[e],match:{[n]:[a]}});}}return d},qt=(t,e,n)=>{const o={...t._options.searchOptions,...n},s=(o.fields??t._options.fields).reduce((l,m)=>({...l,[m]:E(o.boost,m)||1}),{}),{boostDocument:r,weights:i,maxFuzzy:c,bm25:u}=o,{fuzzy:d,prefix:a}={...rt.weights,...i},h=t._index.get(e.term),f=J(t,e.term,e.term,1,e.termBoost,h,s,r,u);let _,p;if(e.prefix&&(_=t._index.atPrefix(e.term)),e.fuzzy){const l=e.fuzzy===true?.2:e.fuzzy,m=l<1?Math.min(c,Math.round(e.term.length*l)):l;m&&(p=t._index.fuzzyGet(e.term,m));}if(_)for(const[l,m]of _){const y=l.length-e.term.length;if(!y)continue;p?.delete(l);const w=a*l.length/(l.length+.3*y);J(t,e.term,l,w,e.termBoost,m,s,r,u,f);}if(p)for(const l of p.keys()){const[m,y]=p.get(l);if(!y)continue;const w=d*l.length/(l.length+y);J(t,e.term,l,w,e.termBoost,m,s,r,u,f);}return f},ut=(t,e,n={})=>{if(e===R)return jt(t,n);if(typeof e!="string"){const a={...n,...e,queries:void 0},h=e.queries.map(f=>ut(t,f,a));return ct(h,a.combineWith)}const{tokenize:o,processTerm:s,searchOptions:r}=t._options,i={tokenize:o,processTerm:s,...r,...n},{tokenize:c,processTerm:u}=i,d=c(e).flatMap(a=>u(a)).filter(a=>!!a).map(Ot(i)).map(a=>qt(t,a,i));return ct(d,i.combineWith)},dt=(t,e,n={})=>{const{searchOptions:o}=t._options,s={...o,...n},r=ut(t,e,n),i=[];for(const[c,{score:u,terms:d,match:a}]of r){const h=d.length||1,f={id:t._documentIds.get(c),score:u*h,terms:Object.keys(a),queryTerms:d,match:a};Object.assign(f,t._storedFields.get(c)),(s.filter==null||s.filter(f))&&i.push(f);}return e===R&&s.boostDocument==null||i.sort(et),i};class Nt{_options;_index;_documentCount;_documentIds;_idToShortId;_fieldIds;_fieldLength;_avgFieldLength;_nextId;_storedFields;_dirtCount;_currentVacuum;_enqueuedVacuum;_enqueuedVacuumConditions;constructor(e){if(!e?.fields)throw new Error('SlimSearch: option "fields" must be provided');const n=e.autoVacuum==null||e.autoVacuum===true?P:e.autoVacuum;this._options={...$,...e,autoVacuum:n,searchOptions:{...rt,...e.searchOptions},autoSuggestOptions:{...Et,...e.autoSuggestOptions}},this._index=new I,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=W,this.addFields(this._options.fields);}get isVacuuming(){return this._currentVacuum!=null}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}get documentCount(){return this._documentCount}get termCount(){return this._index.size}toJSON(){const e=[];for(const[n,o]of this._index){const s={};for(const[r,i]of o)s[r]=Object.fromEntries(i);e.push([n,s]);}return {documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:e,version:2}}addFields(e){for(let n=0;n<e.length;n++)this._fieldIds[e[n]]=n;}}const lt=t=>new Nt(t);
14954
15647
 
14955
15648
  /**
14956
15649
  * Generates a list of accent variants for a given term.
@@ -14992,7 +15685,7 @@ const generateAccentVariants = (term) => {
14992
15685
 
14993
15686
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
14994
15687
  // versions:
14995
- // protoc-gen-ts_proto v2.6.1
15688
+ // protoc-gen-ts_proto v2.7.7
14996
15689
  // protoc v4.23.4
14997
15690
  // source: src/stops/proto/stops.proto
14998
15691
  /* eslint-disable */
@@ -15089,7 +15782,7 @@ const Stop = {
15089
15782
  },
15090
15783
  decode(input, length) {
15091
15784
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
15092
- let end = length === undefined ? reader.len : reader.pos + length;
15785
+ const end = length === undefined ? reader.len : reader.pos + length;
15093
15786
  const message = createBaseStop();
15094
15787
  while (reader.pos < end) {
15095
15788
  const tag = reader.uint32();
@@ -15171,7 +15864,7 @@ const Stop = {
15171
15864
  sourceStopId: isSet$1(object.sourceStopId) ? globalThis.String(object.sourceStopId) : "",
15172
15865
  lat: isSet$1(object.lat) ? globalThis.Number(object.lat) : undefined,
15173
15866
  lon: isSet$1(object.lon) ? globalThis.Number(object.lon) : undefined,
15174
- children: globalThis.Array.isArray(object === null || object === undefined ? undefined : object.children) ? object.children.map((e) => globalThis.Number(e)) : [],
15867
+ children: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.children) ? object.children.map((e) => globalThis.Number(e)) : [],
15175
15868
  parent: isSet$1(object.parent) ? globalThis.Number(object.parent) : undefined,
15176
15869
  locationType: isSet$1(object.locationType) ? locationTypeFromJSON(object.locationType) : 0,
15177
15870
  platform: isSet$1(object.platform) ? globalThis.String(object.platform) : undefined,
@@ -15192,7 +15885,7 @@ const Stop = {
15192
15885
  if (message.lon !== undefined) {
15193
15886
  obj.lon = message.lon;
15194
15887
  }
15195
- if ((_a = message.children) === null || _a === undefined ? undefined : _a.length) {
15888
+ if ((_a = message.children) === null || _a === void 0 ? void 0 : _a.length) {
15196
15889
  obj.children = message.children.map((e) => Math.round(e));
15197
15890
  }
15198
15891
  if (message.parent !== undefined) {
@@ -15207,19 +15900,19 @@ const Stop = {
15207
15900
  return obj;
15208
15901
  },
15209
15902
  create(base) {
15210
- return Stop.fromPartial(base !== null && base !== undefined ? base : {});
15903
+ return Stop.fromPartial(base !== null && base !== void 0 ? base : {});
15211
15904
  },
15212
15905
  fromPartial(object) {
15213
15906
  var _a, _b, _c, _d, _e, _f, _g, _h;
15214
15907
  const message = createBaseStop();
15215
- message.name = (_a = object.name) !== null && _a !== undefined ? _a : "";
15216
- message.sourceStopId = (_b = object.sourceStopId) !== null && _b !== undefined ? _b : "";
15217
- message.lat = (_c = object.lat) !== null && _c !== undefined ? _c : undefined;
15218
- message.lon = (_d = object.lon) !== null && _d !== undefined ? _d : undefined;
15219
- message.children = ((_e = object.children) === null || _e === undefined ? undefined : _e.map((e) => e)) || [];
15220
- message.parent = (_f = object.parent) !== null && _f !== undefined ? _f : undefined;
15221
- message.locationType = (_g = object.locationType) !== null && _g !== undefined ? _g : 0;
15222
- message.platform = (_h = object.platform) !== null && _h !== undefined ? _h : undefined;
15908
+ message.name = (_a = object.name) !== null && _a !== void 0 ? _a : "";
15909
+ message.sourceStopId = (_b = object.sourceStopId) !== null && _b !== void 0 ? _b : "";
15910
+ message.lat = (_c = object.lat) !== null && _c !== void 0 ? _c : undefined;
15911
+ message.lon = (_d = object.lon) !== null && _d !== void 0 ? _d : undefined;
15912
+ message.children = ((_e = object.children) === null || _e === void 0 ? void 0 : _e.map((e) => e)) || [];
15913
+ message.parent = (_f = object.parent) !== null && _f !== void 0 ? _f : undefined;
15914
+ message.locationType = (_g = object.locationType) !== null && _g !== void 0 ? _g : 0;
15915
+ message.platform = (_h = object.platform) !== null && _h !== void 0 ? _h : undefined;
15223
15916
  return message;
15224
15917
  },
15225
15918
  };
@@ -15238,7 +15931,7 @@ const StopsMap = {
15238
15931
  },
15239
15932
  decode(input, length) {
15240
15933
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
15241
- let end = length === undefined ? reader.len : reader.pos + length;
15934
+ const end = length === undefined ? reader.len : reader.pos + length;
15242
15935
  const message = createBaseStopsMap();
15243
15936
  while (reader.pos < end) {
15244
15937
  const tag = reader.uint32();
@@ -15296,13 +15989,13 @@ const StopsMap = {
15296
15989
  return obj;
15297
15990
  },
15298
15991
  create(base) {
15299
- return StopsMap.fromPartial(base !== null && base !== undefined ? base : {});
15992
+ return StopsMap.fromPartial(base !== null && base !== void 0 ? base : {});
15300
15993
  },
15301
15994
  fromPartial(object) {
15302
15995
  var _a, _b;
15303
15996
  const message = createBaseStopsMap();
15304
- message.version = (_a = object.version) !== null && _a !== undefined ? _a : "";
15305
- message.stops = Object.entries((_b = object.stops) !== null && _b !== undefined ? _b : {}).reduce((acc, [key, value]) => {
15997
+ message.version = (_a = object.version) !== null && _a !== void 0 ? _a : "";
15998
+ message.stops = Object.entries((_b = object.stops) !== null && _b !== void 0 ? _b : {}).reduce((acc, [key, value]) => {
15306
15999
  if (value !== undefined) {
15307
16000
  acc[globalThis.Number(key)] = Stop.fromPartial(value);
15308
16001
  }
@@ -15326,7 +16019,7 @@ const StopsMap_StopsEntry = {
15326
16019
  },
15327
16020
  decode(input, length) {
15328
16021
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
15329
- let end = length === undefined ? reader.len : reader.pos + length;
16022
+ const end = length === undefined ? reader.len : reader.pos + length;
15330
16023
  const message = createBaseStopsMap_StopsEntry();
15331
16024
  while (reader.pos < end) {
15332
16025
  const tag = reader.uint32();
@@ -15370,12 +16063,12 @@ const StopsMap_StopsEntry = {
15370
16063
  return obj;
15371
16064
  },
15372
16065
  create(base) {
15373
- return StopsMap_StopsEntry.fromPartial(base !== null && base !== undefined ? base : {});
16066
+ return StopsMap_StopsEntry.fromPartial(base !== null && base !== void 0 ? base : {});
15374
16067
  },
15375
16068
  fromPartial(object) {
15376
16069
  var _a;
15377
16070
  const message = createBaseStopsMap_StopsEntry();
15378
- message.key = (_a = object.key) !== null && _a !== undefined ? _a : 0;
16071
+ message.key = (_a = object.key) !== null && _a !== void 0 ? _a : 0;
15379
16072
  message.value = (object.value !== undefined && object.value !== null) ? Stop.fromPartial(object.value) : undefined;
15380
16073
  return message;
15381
16074
  },
@@ -15486,7 +16179,7 @@ class StopsIndex {
15486
16179
  });
15487
16180
  const stopsSet = new Map();
15488
16181
  for (const [id, stop] of stopsMap.entries()) {
15489
- const effectiveStopId = (_a = stop.parent) !== null && _a !== undefined ? _a : id;
16182
+ const effectiveStopId = (_a = stop.parent) !== null && _a !== void 0 ? _a : id;
15490
16183
  if (!stopsSet.has(effectiveStopId)) {
15491
16184
  stopsSet.set(effectiveStopId, {
15492
16185
  id: effectiveStopId,
@@ -15517,8 +16210,8 @@ class StopsIndex {
15517
16210
  /**
15518
16211
  * Deserializes a binary representation of the stops.
15519
16212
  *
15520
- * @param {Uint8Array} data - The binary data to deserialize.
15521
- * @returns {StopsMap} - The deserialized StopFinder.
16213
+ * @param data - The binary data to deserialize.
16214
+ * @returns The deserialized StopFinder.
15522
16215
  */
15523
16216
  static fromData(data) {
15524
16217
  const reader = new BinaryReader(data);
@@ -15528,7 +16221,7 @@ class StopsIndex {
15528
16221
  /**
15529
16222
  * Serializes the stops into a binary protobuf.
15530
16223
  *
15531
- * @returns {Uint8Array} - The serialized binary data.
16224
+ * @returns The serialized binary data.
15532
16225
  */
15533
16226
  serialize() {
15534
16227
  const protoStopsMap = serializeStopsMap(this.stopsMap);
@@ -15607,7 +16300,7 @@ class StopsIndex {
15607
16300
  return [];
15608
16301
  }
15609
16302
  const equivalentStops = stop.parent
15610
- ? ((_b = (_a = this.stopsMap.get(stop.parent)) === null || _a === undefined ? undefined : _a.children) !== null && _b !== undefined ? _b : [])
16303
+ ? ((_b = (_a = this.stopsMap.get(stop.parent)) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : [])
15611
16304
  : stop.children;
15612
16305
  return Array.from(new Set([id, ...equivalentStops])).map((stopId) => this.stopsMap.get(stopId));
15613
16306
  }
@@ -15696,7 +16389,7 @@ class Duration {
15696
16389
 
15697
16390
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
15698
16391
  // versions:
15699
- // protoc-gen-ts_proto v2.6.1
16392
+ // protoc-gen-ts_proto v2.7.7
15700
16393
  // protoc v4.23.4
15701
16394
  // source: src/timetable/proto/timetable.proto
15702
16395
  /* eslint-disable */
@@ -15830,7 +16523,7 @@ function createBaseRoute() {
15830
16523
  serviceRouteId: "",
15831
16524
  };
15832
16525
  }
15833
- const Route$1 = {
16526
+ const Route$2 = {
15834
16527
  encode(message, writer = new BinaryWriter()) {
15835
16528
  if (message.stopTimes.length !== 0) {
15836
16529
  writer.uint32(10).bytes(message.stopTimes);
@@ -15848,7 +16541,7 @@ const Route$1 = {
15848
16541
  },
15849
16542
  decode(input, length) {
15850
16543
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
15851
- let end = length === undefined ? reader.len : reader.pos + length;
16544
+ const end = length === undefined ? reader.len : reader.pos + length;
15852
16545
  const message = createBaseRoute();
15853
16546
  while (reader.pos < end) {
15854
16547
  const tag = reader.uint32();
@@ -15916,15 +16609,15 @@ const Route$1 = {
15916
16609
  return obj;
15917
16610
  },
15918
16611
  create(base) {
15919
- return Route$1.fromPartial(base !== null && base !== undefined ? base : {});
16612
+ return Route$2.fromPartial(base !== null && base !== void 0 ? base : {});
15920
16613
  },
15921
16614
  fromPartial(object) {
15922
16615
  var _a, _b, _c, _d;
15923
16616
  const message = createBaseRoute();
15924
- message.stopTimes = (_a = object.stopTimes) !== null && _a !== undefined ? _a : new Uint8Array(0);
15925
- message.pickUpDropOffTypes = (_b = object.pickUpDropOffTypes) !== null && _b !== undefined ? _b : new Uint8Array(0);
15926
- message.stops = (_c = object.stops) !== null && _c !== undefined ? _c : new Uint8Array(0);
15927
- message.serviceRouteId = (_d = object.serviceRouteId) !== null && _d !== undefined ? _d : "";
16617
+ message.stopTimes = (_a = object.stopTimes) !== null && _a !== void 0 ? _a : new Uint8Array(0);
16618
+ message.pickUpDropOffTypes = (_b = object.pickUpDropOffTypes) !== null && _b !== void 0 ? _b : new Uint8Array(0);
16619
+ message.stops = (_c = object.stops) !== null && _c !== void 0 ? _c : new Uint8Array(0);
16620
+ message.serviceRouteId = (_d = object.serviceRouteId) !== null && _d !== void 0 ? _d : "";
15928
16621
  return message;
15929
16622
  },
15930
16623
  };
@@ -15940,7 +16633,7 @@ const RoutesAdjacency = {
15940
16633
  },
15941
16634
  decode(input, length) {
15942
16635
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
15943
- let end = length === undefined ? reader.len : reader.pos + length;
16636
+ const end = length === undefined ? reader.len : reader.pos + length;
15944
16637
  const message = createBaseRoutesAdjacency();
15945
16638
  while (reader.pos < end) {
15946
16639
  const tag = reader.uint32();
@@ -15967,7 +16660,7 @@ const RoutesAdjacency = {
15967
16660
  return {
15968
16661
  routes: isObject(object.routes)
15969
16662
  ? Object.entries(object.routes).reduce((acc, [key, value]) => {
15970
- acc[key] = Route$1.fromJSON(value);
16663
+ acc[key] = Route$2.fromJSON(value);
15971
16664
  return acc;
15972
16665
  }, {})
15973
16666
  : {},
@@ -15980,21 +16673,21 @@ const RoutesAdjacency = {
15980
16673
  if (entries.length > 0) {
15981
16674
  obj.routes = {};
15982
16675
  entries.forEach(([k, v]) => {
15983
- obj.routes[k] = Route$1.toJSON(v);
16676
+ obj.routes[k] = Route$2.toJSON(v);
15984
16677
  });
15985
16678
  }
15986
16679
  }
15987
16680
  return obj;
15988
16681
  },
15989
16682
  create(base) {
15990
- return RoutesAdjacency.fromPartial(base !== null && base !== undefined ? base : {});
16683
+ return RoutesAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
15991
16684
  },
15992
16685
  fromPartial(object) {
15993
16686
  var _a;
15994
16687
  const message = createBaseRoutesAdjacency();
15995
- message.routes = Object.entries((_a = object.routes) !== null && _a !== undefined ? _a : {}).reduce((acc, [key, value]) => {
16688
+ message.routes = Object.entries((_a = object.routes) !== null && _a !== void 0 ? _a : {}).reduce((acc, [key, value]) => {
15996
16689
  if (value !== undefined) {
15997
- acc[key] = Route$1.fromPartial(value);
16690
+ acc[key] = Route$2.fromPartial(value);
15998
16691
  }
15999
16692
  return acc;
16000
16693
  }, {});
@@ -16010,13 +16703,13 @@ const RoutesAdjacency_RoutesEntry = {
16010
16703
  writer.uint32(10).string(message.key);
16011
16704
  }
16012
16705
  if (message.value !== undefined) {
16013
- Route$1.encode(message.value, writer.uint32(18).fork()).join();
16706
+ Route$2.encode(message.value, writer.uint32(18).fork()).join();
16014
16707
  }
16015
16708
  return writer;
16016
16709
  },
16017
16710
  decode(input, length) {
16018
16711
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16019
- let end = length === undefined ? reader.len : reader.pos + length;
16712
+ const end = length === undefined ? reader.len : reader.pos + length;
16020
16713
  const message = createBaseRoutesAdjacency_RoutesEntry();
16021
16714
  while (reader.pos < end) {
16022
16715
  const tag = reader.uint32();
@@ -16032,7 +16725,7 @@ const RoutesAdjacency_RoutesEntry = {
16032
16725
  if (tag !== 18) {
16033
16726
  break;
16034
16727
  }
16035
- message.value = Route$1.decode(reader, reader.uint32());
16728
+ message.value = Route$2.decode(reader, reader.uint32());
16036
16729
  continue;
16037
16730
  }
16038
16731
  }
@@ -16046,7 +16739,7 @@ const RoutesAdjacency_RoutesEntry = {
16046
16739
  fromJSON(object) {
16047
16740
  return {
16048
16741
  key: isSet(object.key) ? globalThis.String(object.key) : "",
16049
- value: isSet(object.value) ? Route$1.fromJSON(object.value) : undefined,
16742
+ value: isSet(object.value) ? Route$2.fromJSON(object.value) : undefined,
16050
16743
  };
16051
16744
  },
16052
16745
  toJSON(message) {
@@ -16055,18 +16748,18 @@ const RoutesAdjacency_RoutesEntry = {
16055
16748
  obj.key = message.key;
16056
16749
  }
16057
16750
  if (message.value !== undefined) {
16058
- obj.value = Route$1.toJSON(message.value);
16751
+ obj.value = Route$2.toJSON(message.value);
16059
16752
  }
16060
16753
  return obj;
16061
16754
  },
16062
16755
  create(base) {
16063
- return RoutesAdjacency_RoutesEntry.fromPartial(base !== null && base !== undefined ? base : {});
16756
+ return RoutesAdjacency_RoutesEntry.fromPartial(base !== null && base !== void 0 ? base : {});
16064
16757
  },
16065
16758
  fromPartial(object) {
16066
16759
  var _a;
16067
16760
  const message = createBaseRoutesAdjacency_RoutesEntry();
16068
- message.key = (_a = object.key) !== null && _a !== undefined ? _a : "";
16069
- message.value = (object.value !== undefined && object.value !== null) ? Route$1.fromPartial(object.value) : undefined;
16761
+ message.key = (_a = object.key) !== null && _a !== void 0 ? _a : "";
16762
+ message.value = (object.value !== undefined && object.value !== null) ? Route$2.fromPartial(object.value) : undefined;
16070
16763
  return message;
16071
16764
  },
16072
16765
  };
@@ -16088,7 +16781,7 @@ const Transfer = {
16088
16781
  },
16089
16782
  decode(input, length) {
16090
16783
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16091
- let end = length === undefined ? reader.len : reader.pos + length;
16784
+ const end = length === undefined ? reader.len : reader.pos + length;
16092
16785
  const message = createBaseTransfer();
16093
16786
  while (reader.pos < end) {
16094
16787
  const tag = reader.uint32();
@@ -16143,14 +16836,14 @@ const Transfer = {
16143
16836
  return obj;
16144
16837
  },
16145
16838
  create(base) {
16146
- return Transfer.fromPartial(base !== null && base !== undefined ? base : {});
16839
+ return Transfer.fromPartial(base !== null && base !== void 0 ? base : {});
16147
16840
  },
16148
16841
  fromPartial(object) {
16149
16842
  var _a, _b, _c;
16150
16843
  const message = createBaseTransfer();
16151
- message.destination = (_a = object.destination) !== null && _a !== undefined ? _a : 0;
16152
- message.type = (_b = object.type) !== null && _b !== undefined ? _b : 0;
16153
- message.minTransferTime = (_c = object.minTransferTime) !== null && _c !== undefined ? _c : undefined;
16844
+ message.destination = (_a = object.destination) !== null && _a !== void 0 ? _a : 0;
16845
+ message.type = (_b = object.type) !== null && _b !== void 0 ? _b : 0;
16846
+ message.minTransferTime = (_c = object.minTransferTime) !== null && _c !== void 0 ? _c : undefined;
16154
16847
  return message;
16155
16848
  },
16156
16849
  };
@@ -16166,7 +16859,7 @@ const StopsAdjacency = {
16166
16859
  },
16167
16860
  decode(input, length) {
16168
16861
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16169
- let end = length === undefined ? reader.len : reader.pos + length;
16862
+ const end = length === undefined ? reader.len : reader.pos + length;
16170
16863
  const message = createBaseStopsAdjacency();
16171
16864
  while (reader.pos < end) {
16172
16865
  const tag = reader.uint32();
@@ -16213,12 +16906,12 @@ const StopsAdjacency = {
16213
16906
  return obj;
16214
16907
  },
16215
16908
  create(base) {
16216
- return StopsAdjacency.fromPartial(base !== null && base !== undefined ? base : {});
16909
+ return StopsAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
16217
16910
  },
16218
16911
  fromPartial(object) {
16219
16912
  var _a;
16220
16913
  const message = createBaseStopsAdjacency();
16221
- message.stops = Object.entries((_a = object.stops) !== null && _a !== undefined ? _a : {}).reduce((acc, [key, value]) => {
16914
+ message.stops = Object.entries((_a = object.stops) !== null && _a !== void 0 ? _a : {}).reduce((acc, [key, value]) => {
16222
16915
  if (value !== undefined) {
16223
16916
  acc[key] = StopsAdjacency_StopAdjacency.fromPartial(value);
16224
16917
  }
@@ -16242,7 +16935,7 @@ const StopsAdjacency_StopAdjacency = {
16242
16935
  },
16243
16936
  decode(input, length) {
16244
16937
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16245
- let end = length === undefined ? reader.len : reader.pos + length;
16938
+ const end = length === undefined ? reader.len : reader.pos + length;
16246
16939
  const message = createBaseStopsAdjacency_StopAdjacency();
16247
16940
  while (reader.pos < end) {
16248
16941
  const tag = reader.uint32();
@@ -16271,31 +16964,31 @@ const StopsAdjacency_StopAdjacency = {
16271
16964
  },
16272
16965
  fromJSON(object) {
16273
16966
  return {
16274
- transfers: globalThis.Array.isArray(object === null || object === undefined ? undefined : object.transfers)
16967
+ transfers: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.transfers)
16275
16968
  ? object.transfers.map((e) => Transfer.fromJSON(e))
16276
16969
  : [],
16277
- routes: globalThis.Array.isArray(object === null || object === undefined ? undefined : object.routes) ? object.routes.map((e) => globalThis.String(e)) : [],
16970
+ routes: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.routes) ? object.routes.map((e) => globalThis.String(e)) : [],
16278
16971
  };
16279
16972
  },
16280
16973
  toJSON(message) {
16281
16974
  var _a, _b;
16282
16975
  const obj = {};
16283
- if ((_a = message.transfers) === null || _a === undefined ? undefined : _a.length) {
16976
+ if ((_a = message.transfers) === null || _a === void 0 ? void 0 : _a.length) {
16284
16977
  obj.transfers = message.transfers.map((e) => Transfer.toJSON(e));
16285
16978
  }
16286
- if ((_b = message.routes) === null || _b === undefined ? undefined : _b.length) {
16979
+ if ((_b = message.routes) === null || _b === void 0 ? void 0 : _b.length) {
16287
16980
  obj.routes = message.routes;
16288
16981
  }
16289
16982
  return obj;
16290
16983
  },
16291
16984
  create(base) {
16292
- return StopsAdjacency_StopAdjacency.fromPartial(base !== null && base !== undefined ? base : {});
16985
+ return StopsAdjacency_StopAdjacency.fromPartial(base !== null && base !== void 0 ? base : {});
16293
16986
  },
16294
16987
  fromPartial(object) {
16295
16988
  var _a, _b;
16296
16989
  const message = createBaseStopsAdjacency_StopAdjacency();
16297
- message.transfers = ((_a = object.transfers) === null || _a === undefined ? undefined : _a.map((e) => Transfer.fromPartial(e))) || [];
16298
- message.routes = ((_b = object.routes) === null || _b === undefined ? undefined : _b.map((e) => e)) || [];
16990
+ message.transfers = ((_a = object.transfers) === null || _a === void 0 ? void 0 : _a.map((e) => Transfer.fromPartial(e))) || [];
16991
+ message.routes = ((_b = object.routes) === null || _b === void 0 ? void 0 : _b.map((e) => e)) || [];
16299
16992
  return message;
16300
16993
  },
16301
16994
  };
@@ -16314,7 +17007,7 @@ const StopsAdjacency_StopsEntry = {
16314
17007
  },
16315
17008
  decode(input, length) {
16316
17009
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16317
- let end = length === undefined ? reader.len : reader.pos + length;
17010
+ const end = length === undefined ? reader.len : reader.pos + length;
16318
17011
  const message = createBaseStopsAdjacency_StopsEntry();
16319
17012
  while (reader.pos < end) {
16320
17013
  const tag = reader.uint32();
@@ -16358,12 +17051,12 @@ const StopsAdjacency_StopsEntry = {
16358
17051
  return obj;
16359
17052
  },
16360
17053
  create(base) {
16361
- return StopsAdjacency_StopsEntry.fromPartial(base !== null && base !== undefined ? base : {});
17054
+ return StopsAdjacency_StopsEntry.fromPartial(base !== null && base !== void 0 ? base : {});
16362
17055
  },
16363
17056
  fromPartial(object) {
16364
17057
  var _a;
16365
17058
  const message = createBaseStopsAdjacency_StopsEntry();
16366
- message.key = (_a = object.key) !== null && _a !== undefined ? _a : "";
17059
+ message.key = (_a = object.key) !== null && _a !== void 0 ? _a : "";
16367
17060
  message.value = (object.value !== undefined && object.value !== null)
16368
17061
  ? StopsAdjacency_StopAdjacency.fromPartial(object.value)
16369
17062
  : undefined;
@@ -16385,7 +17078,7 @@ const ServiceRoute = {
16385
17078
  },
16386
17079
  decode(input, length) {
16387
17080
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16388
- let end = length === undefined ? reader.len : reader.pos + length;
17081
+ const end = length === undefined ? reader.len : reader.pos + length;
16389
17082
  const message = createBaseServiceRoute();
16390
17083
  while (reader.pos < end) {
16391
17084
  const tag = reader.uint32();
@@ -16429,13 +17122,13 @@ const ServiceRoute = {
16429
17122
  return obj;
16430
17123
  },
16431
17124
  create(base) {
16432
- return ServiceRoute.fromPartial(base !== null && base !== undefined ? base : {});
17125
+ return ServiceRoute.fromPartial(base !== null && base !== void 0 ? base : {});
16433
17126
  },
16434
17127
  fromPartial(object) {
16435
17128
  var _a, _b;
16436
17129
  const message = createBaseServiceRoute();
16437
- message.type = (_a = object.type) !== null && _a !== undefined ? _a : 0;
16438
- message.name = (_b = object.name) !== null && _b !== undefined ? _b : "";
17130
+ message.type = (_a = object.type) !== null && _a !== void 0 ? _a : 0;
17131
+ message.name = (_b = object.name) !== null && _b !== void 0 ? _b : "";
16439
17132
  return message;
16440
17133
  },
16441
17134
  };
@@ -16451,7 +17144,7 @@ const ServiceRoutesMap = {
16451
17144
  },
16452
17145
  decode(input, length) {
16453
17146
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16454
- let end = length === undefined ? reader.len : reader.pos + length;
17147
+ const end = length === undefined ? reader.len : reader.pos + length;
16455
17148
  const message = createBaseServiceRoutesMap();
16456
17149
  while (reader.pos < end) {
16457
17150
  const tag = reader.uint32();
@@ -16498,12 +17191,12 @@ const ServiceRoutesMap = {
16498
17191
  return obj;
16499
17192
  },
16500
17193
  create(base) {
16501
- return ServiceRoutesMap.fromPartial(base !== null && base !== undefined ? base : {});
17194
+ return ServiceRoutesMap.fromPartial(base !== null && base !== void 0 ? base : {});
16502
17195
  },
16503
17196
  fromPartial(object) {
16504
17197
  var _a;
16505
17198
  const message = createBaseServiceRoutesMap();
16506
- message.routes = Object.entries((_a = object.routes) !== null && _a !== undefined ? _a : {}).reduce((acc, [key, value]) => {
17199
+ message.routes = Object.entries((_a = object.routes) !== null && _a !== void 0 ? _a : {}).reduce((acc, [key, value]) => {
16507
17200
  if (value !== undefined) {
16508
17201
  acc[key] = ServiceRoute.fromPartial(value);
16509
17202
  }
@@ -16527,7 +17220,7 @@ const ServiceRoutesMap_RoutesEntry = {
16527
17220
  },
16528
17221
  decode(input, length) {
16529
17222
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16530
- let end = length === undefined ? reader.len : reader.pos + length;
17223
+ const end = length === undefined ? reader.len : reader.pos + length;
16531
17224
  const message = createBaseServiceRoutesMap_RoutesEntry();
16532
17225
  while (reader.pos < end) {
16533
17226
  const tag = reader.uint32();
@@ -16571,12 +17264,12 @@ const ServiceRoutesMap_RoutesEntry = {
16571
17264
  return obj;
16572
17265
  },
16573
17266
  create(base) {
16574
- return ServiceRoutesMap_RoutesEntry.fromPartial(base !== null && base !== undefined ? base : {});
17267
+ return ServiceRoutesMap_RoutesEntry.fromPartial(base !== null && base !== void 0 ? base : {});
16575
17268
  },
16576
17269
  fromPartial(object) {
16577
17270
  var _a;
16578
17271
  const message = createBaseServiceRoutesMap_RoutesEntry();
16579
- message.key = (_a = object.key) !== null && _a !== undefined ? _a : "";
17272
+ message.key = (_a = object.key) !== null && _a !== void 0 ? _a : "";
16580
17273
  message.value = (object.value !== undefined && object.value !== null)
16581
17274
  ? ServiceRoute.fromPartial(object.value)
16582
17275
  : undefined;
@@ -16604,7 +17297,7 @@ const Timetable$1 = {
16604
17297
  },
16605
17298
  decode(input, length) {
16606
17299
  const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
16607
- let end = length === undefined ? reader.len : reader.pos + length;
17300
+ const end = length === undefined ? reader.len : reader.pos + length;
16608
17301
  const message = createBaseTimetable();
16609
17302
  while (reader.pos < end) {
16610
17303
  const tag = reader.uint32();
@@ -16670,12 +17363,12 @@ const Timetable$1 = {
16670
17363
  return obj;
16671
17364
  },
16672
17365
  create(base) {
16673
- return Timetable$1.fromPartial(base !== null && base !== undefined ? base : {});
17366
+ return Timetable$1.fromPartial(base !== null && base !== void 0 ? base : {});
16674
17367
  },
16675
17368
  fromPartial(object) {
16676
17369
  var _a;
16677
17370
  const message = createBaseTimetable();
16678
- message.version = (_a = object.version) !== null && _a !== undefined ? _a : "";
17371
+ message.version = (_a = object.version) !== null && _a !== void 0 ? _a : "";
16679
17372
  message.stopsAdjacency = (object.stopsAdjacency !== undefined && object.stopsAdjacency !== null)
16680
17373
  ? StopsAdjacency.fromPartial(object.stopsAdjacency)
16681
17374
  : undefined;
@@ -16720,231 +17413,6 @@ function isSet(value) {
16720
17413
  return value !== null && value !== undefined;
16721
17414
  }
16722
17415
 
16723
- const isLittleEndian = (() => {
16724
- const buffer = new ArrayBuffer(4);
16725
- const view = new DataView(buffer);
16726
- view.setUint32(0, 0x12345678);
16727
- return new Uint8Array(buffer)[0] === 0x78;
16728
- })();
16729
- const STANDARD_ENDIANNESS = true; // true = little-endian
16730
- function uint32ArrayToBytes(array) {
16731
- if (isLittleEndian === STANDARD_ENDIANNESS) {
16732
- return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
16733
- }
16734
- // If endianness doesn't match, we need to swap byte order
16735
- const result = new Uint8Array(array.length * 4);
16736
- const view = new DataView(result.buffer);
16737
- for (let i = 0; i < array.length; i++) {
16738
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16739
- view.setUint32(i * 4, array[i], STANDARD_ENDIANNESS);
16740
- }
16741
- return result;
16742
- }
16743
- function bytesToUint32Array(bytes) {
16744
- if (bytes.byteLength % 4 !== 0) {
16745
- throw new Error('Byte array length must be a multiple of 4 to convert to Uint32Array');
16746
- }
16747
- // If system endianness matches our standard, we can create a view directly
16748
- if (isLittleEndian === STANDARD_ENDIANNESS) {
16749
- return new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
16750
- }
16751
- // If endianness doesn't match, we need to swap byte order
16752
- const result = new Uint32Array(bytes.byteLength / 4);
16753
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
16754
- for (let i = 0; i < result.length; i++) {
16755
- result[i] = view.getUint32(i * 4, STANDARD_ENDIANNESS);
16756
- }
16757
- return result;
16758
- }
16759
- function uint16ArrayToBytes(array) {
16760
- if (isLittleEndian === STANDARD_ENDIANNESS) {
16761
- return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
16762
- }
16763
- // If endianness doesn't match, we need to swap byte order
16764
- const result = new Uint8Array(array.length * 2);
16765
- const view = new DataView(result.buffer);
16766
- for (let i = 0; i < array.length; i++) {
16767
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16768
- view.setUint16(i * 2, array[i], STANDARD_ENDIANNESS);
16769
- }
16770
- return result;
16771
- }
16772
- function bytesToUint16Array(bytes) {
16773
- if (bytes.byteLength % 2 !== 0) {
16774
- throw new Error('Byte array length must be a multiple of 2 to convert to Uint16Array');
16775
- }
16776
- // If system endianness matches our standard, we can create a view directly
16777
- if (isLittleEndian === STANDARD_ENDIANNESS) {
16778
- return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
16779
- }
16780
- // If endianness doesn't match, we need to swap byte order
16781
- const result = new Uint16Array(bytes.byteLength / 2);
16782
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
16783
- for (let i = 0; i < result.length; i++) {
16784
- result[i] = view.getUint16(i * 2, STANDARD_ENDIANNESS);
16785
- }
16786
- return result;
16787
- }
16788
- const serializeStopsAdjacency = (stopsAdjacency) => {
16789
- const protoStopsAdjacency = {
16790
- stops: {},
16791
- };
16792
- stopsAdjacency.forEach((value, key) => {
16793
- protoStopsAdjacency.stops[key] = {
16794
- transfers: value.transfers.map((transfer) => (Object.assign({ destination: transfer.destination, type: serializeTransferType(transfer.type) }, (transfer.minTransferTime !== undefined && {
16795
- minTransferTime: transfer.minTransferTime.toSeconds(),
16796
- })))),
16797
- routes: value.routes,
16798
- };
16799
- });
16800
- return protoStopsAdjacency;
16801
- };
16802
- const serializeRoutesAdjacency = (routesAdjacency) => {
16803
- const protoRoutesAdjacency = {
16804
- routes: {},
16805
- };
16806
- routesAdjacency.forEach((value, key) => {
16807
- protoRoutesAdjacency.routes[key] = {
16808
- stopTimes: uint16ArrayToBytes(value.stopTimes),
16809
- pickUpDropOffTypes: value.pickUpDropOffTypes,
16810
- stops: uint32ArrayToBytes(value.stops),
16811
- serviceRouteId: value.serviceRouteId,
16812
- };
16813
- });
16814
- return protoRoutesAdjacency;
16815
- };
16816
- const serializeServiceRoutesMap = (serviceRoutesMap) => {
16817
- const protoServiceRoutesMap = {
16818
- routes: {},
16819
- };
16820
- serviceRoutesMap.forEach((value, key) => {
16821
- protoServiceRoutesMap.routes[key] = {
16822
- type: serializeRouteType(value.type),
16823
- name: value.name,
16824
- };
16825
- });
16826
- return protoServiceRoutesMap;
16827
- };
16828
- const deserializeStopsAdjacency = (protoStopsAdjacency) => {
16829
- const stopsAdjacency = new Map();
16830
- Object.entries(protoStopsAdjacency.stops).forEach(([keyStr, value]) => {
16831
- const key = parseInt(keyStr, 10);
16832
- stopsAdjacency.set(key, {
16833
- transfers: value.transfers.map((transfer) => (Object.assign({ destination: transfer.destination, type: parseTransferType(transfer.type) }, (transfer.minTransferTime !== undefined && {
16834
- minTransferTime: Duration.fromSeconds(transfer.minTransferTime),
16835
- })))),
16836
- routes: value.routes,
16837
- });
16838
- });
16839
- return stopsAdjacency;
16840
- };
16841
- const deserializeRoutesAdjacency = (protoRoutesAdjacency) => {
16842
- const routesAdjacency = new Map();
16843
- Object.entries(protoRoutesAdjacency.routes).forEach(([key, value]) => {
16844
- const stops = bytesToUint32Array(value.stops);
16845
- const indices = new Map();
16846
- for (let i = 0; i < stops.length; i++) {
16847
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16848
- indices.set(stops[i], i);
16849
- }
16850
- routesAdjacency.set(key, {
16851
- stopTimes: bytesToUint16Array(value.stopTimes),
16852
- pickUpDropOffTypes: value.pickUpDropOffTypes,
16853
- stops: stops,
16854
- stopIndices: indices,
16855
- serviceRouteId: value.serviceRouteId,
16856
- });
16857
- });
16858
- return routesAdjacency;
16859
- };
16860
- const deserializeServiceRoutesMap = (protoServiceRoutesMap) => {
16861
- const serviceRoutesMap = new Map();
16862
- Object.entries(protoServiceRoutesMap.routes).forEach(([key, value]) => {
16863
- serviceRoutesMap.set(key, {
16864
- type: parseRouteType(value.type),
16865
- name: value.name,
16866
- });
16867
- });
16868
- return serviceRoutesMap;
16869
- };
16870
- const parseTransferType = (type) => {
16871
- switch (type) {
16872
- case TransferType.RECOMMENDED_TRANSFER_POINT:
16873
- return 'RECOMMENDED';
16874
- case TransferType.TIMED_TRANSFER:
16875
- return 'GUARANTEED';
16876
- case TransferType.REQUIRES_MINIMAL_TIME:
16877
- return 'REQUIRES_MINIMAL_TIME';
16878
- case TransferType.IN_SEAT_TRANSFER:
16879
- return 'IN_SEAT';
16880
- case TransferType.UNRECOGNIZED:
16881
- throw new Error('Unrecognized protobuf transfer type.');
16882
- }
16883
- };
16884
- const serializeTransferType = (type) => {
16885
- switch (type) {
16886
- case 'RECOMMENDED':
16887
- return TransferType.RECOMMENDED_TRANSFER_POINT;
16888
- case 'GUARANTEED':
16889
- return TransferType.TIMED_TRANSFER;
16890
- case 'REQUIRES_MINIMAL_TIME':
16891
- return TransferType.REQUIRES_MINIMAL_TIME;
16892
- case 'IN_SEAT':
16893
- return TransferType.IN_SEAT_TRANSFER;
16894
- }
16895
- };
16896
- const parseRouteType = (type) => {
16897
- switch (type) {
16898
- case RouteType.TRAM:
16899
- return 'TRAM';
16900
- case RouteType.SUBWAY:
16901
- return 'SUBWAY';
16902
- case RouteType.RAIL:
16903
- return 'RAIL';
16904
- case RouteType.BUS:
16905
- return 'BUS';
16906
- case RouteType.FERRY:
16907
- return 'FERRY';
16908
- case RouteType.CABLE_TRAM:
16909
- return 'CABLE_TRAM';
16910
- case RouteType.AERIAL_LIFT:
16911
- return 'AERIAL_LIFT';
16912
- case RouteType.FUNICULAR:
16913
- return 'FUNICULAR';
16914
- case RouteType.TROLLEYBUS:
16915
- return 'TROLLEYBUS';
16916
- case RouteType.MONORAIL:
16917
- return 'MONORAIL';
16918
- case RouteType.UNRECOGNIZED:
16919
- default:
16920
- throw new Error('Unrecognized protobuf route type.');
16921
- }
16922
- };
16923
- const serializeRouteType = (type) => {
16924
- switch (type) {
16925
- case 'TRAM':
16926
- return RouteType.TRAM;
16927
- case 'SUBWAY':
16928
- return RouteType.SUBWAY;
16929
- case 'RAIL':
16930
- return RouteType.RAIL;
16931
- case 'BUS':
16932
- return RouteType.BUS;
16933
- case 'FERRY':
16934
- return RouteType.FERRY;
16935
- case 'CABLE_TRAM':
16936
- return RouteType.CABLE_TRAM;
16937
- case 'AERIAL_LIFT':
16938
- return RouteType.AERIAL_LIFT;
16939
- case 'FUNICULAR':
16940
- return RouteType.FUNICULAR;
16941
- case 'TROLLEYBUS':
16942
- return RouteType.TROLLEYBUS;
16943
- case 'MONORAIL':
16944
- return RouteType.MONORAIL;
16945
- }
16946
- };
16947
-
16948
17416
  /**
16949
17417
  * A class representing a time as minutes since midnight.
16950
17418
  */
@@ -17034,9 +17502,12 @@ class Time {
17034
17502
  const [hoursStr, minutesStr, secondsStr] = timeStr.split(':');
17035
17503
  if (hoursStr === undefined ||
17036
17504
  minutesStr === undefined ||
17505
+ hoursStr.trim() === '' ||
17506
+ minutesStr.trim() === '' ||
17037
17507
  isNaN(Number(hoursStr)) ||
17038
17508
  isNaN(Number(minutesStr)) ||
17039
- (secondsStr !== undefined && isNaN(Number(secondsStr)))) {
17509
+ (secondsStr !== undefined &&
17510
+ (secondsStr.trim() === '' || isNaN(Number(secondsStr))))) {
17040
17511
  throw new Error('Input string must be in the format "HH:MM:SS" or "HH:MM".');
17041
17512
  }
17042
17513
  const hours = parseInt(hoursStr, 10);
@@ -17050,8 +17521,11 @@ class Time {
17050
17521
  * @returns A string representing the time.
17051
17522
  */
17052
17523
  toString() {
17053
- const hours = Math.floor(this.minutesSinceMidnight / 60);
17524
+ let hours = Math.floor(this.minutesSinceMidnight / 60);
17054
17525
  const minutes = Math.floor(this.minutesSinceMidnight % 60);
17526
+ if (hours >= 24) {
17527
+ hours = hours % 24;
17528
+ }
17055
17529
  return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
17056
17530
  }
17057
17531
  /**
@@ -17106,9 +17580,7 @@ class Time {
17106
17580
  throw new Error('At least one Time instance is required.');
17107
17581
  }
17108
17582
  return times.reduce((maxTime, currentTime) => {
17109
- return currentTime.toMinutes() > maxTime.toMinutes()
17110
- ? currentTime
17111
- : maxTime;
17583
+ return currentTime.isAfter(maxTime) ? currentTime : maxTime;
17112
17584
  });
17113
17585
  }
17114
17586
  /**
@@ -17122,18 +17594,476 @@ class Time {
17122
17594
  throw new Error('At least one Time instance is required.');
17123
17595
  }
17124
17596
  return times.reduce((minTime, currentTime) => {
17125
- return currentTime.toMinutes() < minTime.toMinutes()
17126
- ? currentTime
17127
- : minTime;
17597
+ return currentTime.isBefore(minTime) ? currentTime : minTime;
17128
17598
  });
17129
17599
  }
17600
+ /**
17601
+ * Determines if the current Time instance is after another Time instance.
17602
+ *
17603
+ * @param otherTime - A Time instance to compare against.
17604
+ * @returns True if the current Time instance is after the other Time instance, otherwise false.
17605
+ */
17606
+ isAfter(otherTime) {
17607
+ return this.minutesSinceMidnight > otherTime.toMinutes();
17608
+ }
17609
+ /**
17610
+ * Determines if the current Time instance is before another Time instance.
17611
+ *
17612
+ * @param otherTime - A Time instance to compare against.
17613
+ * @returns True if the current Time instance is before the other Time instance, otherwise false.
17614
+ */
17615
+ isBefore(otherTime) {
17616
+ return this.minutesSinceMidnight < otherTime.toMinutes();
17617
+ }
17618
+ /**
17619
+ * Determines if the current Time instance is equal to another Time instance.
17620
+ *
17621
+ * @param otherTime - A Time instance to compare against.
17622
+ * @returns True if the current Time instance is equal to the other Time instance, otherwise false.
17623
+ */
17624
+ equals(otherTime) {
17625
+ return this.minutesSinceMidnight === otherTime.toMinutes();
17626
+ }
17130
17627
  }
17131
17628
 
17132
17629
  const REGULAR = 0;
17133
17630
  const NOT_AVAILABLE = 1;
17134
17631
  const MUST_PHONE_AGENCY = 2;
17135
17632
  const MUST_COORDINATE_WITH_DRIVER = 3;
17136
- const ALL_TRANSPORT_MODES = [
17633
+ const pickUpDropOffTypeMap = [
17634
+ 'REGULAR',
17635
+ 'NOT_AVAILABLE',
17636
+ 'MUST_PHONE_AGENCY',
17637
+ 'MUST_COORDINATE_WITH_DRIVER',
17638
+ ];
17639
+ /**
17640
+ * Converts a numerical representation of a pick-up/drop-off type
17641
+ * into its corresponding string representation.
17642
+ *
17643
+ * @param numericalType - The numerical value representing the pick-up/drop-off type.
17644
+ * @returns The corresponding PickUpDropOffType as a string.
17645
+ * @throws An error if the numerical type is invalid.
17646
+ */
17647
+ const toPickupDropOffType = (numericalType) => {
17648
+ const type = pickUpDropOffTypeMap[numericalType];
17649
+ if (!type) {
17650
+ throw new Error(`Invalid pickup/drop-off type ${numericalType}`);
17651
+ }
17652
+ return type;
17653
+ };
17654
+ /**
17655
+ * A route identifies all trips of a given service route sharing the same list of stops.
17656
+ */
17657
+ let Route$1 = class Route {
17658
+ constructor(stopTimes, pickUpDropOffTypes, stops, serviceRouteId) {
17659
+ this.stopTimes = stopTimes;
17660
+ this.pickUpDropOffTypes = pickUpDropOffTypes;
17661
+ this.stops = stops;
17662
+ this.serviceRouteId = serviceRouteId;
17663
+ this.nbStops = stops.length;
17664
+ this.nbTrips = this.stopTimes.length / (this.stops.length * 2);
17665
+ this.stopIndices = new Map();
17666
+ for (let i = 0; i < stops.length; i++) {
17667
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17668
+ this.stopIndices.set(stops[i], i);
17669
+ }
17670
+ }
17671
+ /**
17672
+ * Serializes the Route into binary arrays.
17673
+ *
17674
+ * @returns The serialized binary data.
17675
+ */
17676
+ serialize() {
17677
+ return {
17678
+ stopTimes: this.stopTimes,
17679
+ pickUpDropOffTypes: this.pickUpDropOffTypes,
17680
+ stops: this.stops,
17681
+ serviceRouteId: this.serviceRouteId,
17682
+ };
17683
+ }
17684
+ /**
17685
+ * Checks if stop A is before stop B in the route.
17686
+ *
17687
+ * @param stopA - The StopId of the first stop.
17688
+ * @param stopB - The StopId of the second stop.
17689
+ * @returns True if stop A is before stop B, false otherwise.
17690
+ */
17691
+ isBefore(stopA, stopB) {
17692
+ const stopAIndex = this.stopIndices.get(stopA);
17693
+ if (stopAIndex === undefined) {
17694
+ throw new Error(`Stop index ${stopAIndex} not found in route ${this.serviceRouteId}`);
17695
+ }
17696
+ const stopBIndex = this.stopIndices.get(stopB);
17697
+ if (stopBIndex === undefined) {
17698
+ throw new Error(`Stop index ${stopBIndex} not found in route ${this.serviceRouteId}`);
17699
+ }
17700
+ return stopAIndex < stopBIndex;
17701
+ }
17702
+ /**
17703
+ * Retrieves the number of stops in the route.
17704
+ *
17705
+ * @returns The total number of stops in the route.
17706
+ */
17707
+ getNbStops() {
17708
+ return this.nbStops;
17709
+ }
17710
+ /**
17711
+ * Finds the ServiceRouteId of the route. It corresponds the identifier
17712
+ * of the service shown to the end user as a route.
17713
+ *
17714
+ * @returns The ServiceRouteId of the route.
17715
+ */
17716
+ serviceRoute() {
17717
+ return this.serviceRouteId;
17718
+ }
17719
+ /**
17720
+ * Retrieves the arrival time at a specific stop for a given trip.
17721
+ *
17722
+ * @param stopId - The identifier of the stop.
17723
+ * @param tripIndex - The index of the trip.
17724
+ * @returns The arrival time at the specified stop and trip as a Time object.
17725
+ */
17726
+ arrivalAt(stopId, tripIndex) {
17727
+ const arrivalIndex = (tripIndex * this.stops.length + this.stopIndex(stopId)) * 2;
17728
+ const arrival = this.stopTimes[arrivalIndex];
17729
+ if (arrival === undefined) {
17730
+ throw new Error(`Arrival time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17731
+ }
17732
+ return Time.fromMinutes(arrival);
17733
+ }
17734
+ /**
17735
+ * Retrieves the departure time at a specific stop for a given trip.
17736
+ *
17737
+ * @param stopId - The identifier of the stop.
17738
+ * @param tripIndex - The index of the trip.
17739
+ * @returns The departure time at the specified stop and trip as a Time object.
17740
+ */
17741
+ departureFrom(stopId, tripIndex) {
17742
+ const departureIndex = (tripIndex * this.stops.length + this.stopIndex(stopId)) * 2 + 1;
17743
+ const departure = this.stopTimes[departureIndex];
17744
+ if (departure === undefined) {
17745
+ throw new Error(`Departure time not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17746
+ }
17747
+ return Time.fromMinutes(departure);
17748
+ }
17749
+ /**
17750
+ * Retrieves the pick-up type for a specific stop and trip.
17751
+ *
17752
+ * @param stopId - The identifier of the stop.
17753
+ * @param tripIndex - The index of the trip.
17754
+ * @returns The pick-up type at the specified stop and trip.
17755
+ */
17756
+ pickUpTypeFrom(stopId, tripIndex) {
17757
+ const pickUpIndex = (tripIndex * this.stops.length + this.stopIndex(stopId)) * 2;
17758
+ const pickUpValue = this.pickUpDropOffTypes[pickUpIndex];
17759
+ if (pickUpValue === undefined) {
17760
+ throw new Error(`Pick up type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17761
+ }
17762
+ return toPickupDropOffType(pickUpValue);
17763
+ }
17764
+ /**
17765
+ * Retrieves the drop-off type for a specific stop and trip.
17766
+ *
17767
+ * @param stopId - The identifier of the stop.
17768
+ * @param tripIndex - The index of the trip.
17769
+ * @returns The drop-off type at the specified stop and trip.
17770
+ */
17771
+ dropOffTypeAt(stopId, tripIndex) {
17772
+ const dropOffIndex = (tripIndex * this.stops.length + this.stopIndex(stopId)) * 2 + 1;
17773
+ const dropOffValue = this.pickUpDropOffTypes[dropOffIndex];
17774
+ if (dropOffValue === undefined) {
17775
+ throw new Error(`Drop off type not found for stop ${stopId} at trip index ${tripIndex} in route ${this.serviceRouteId}`);
17776
+ }
17777
+ return toPickupDropOffType(dropOffValue);
17778
+ }
17779
+ /**
17780
+ * Iterates over the stops in the route, starting from an optional specified stop.
17781
+ * If no start stop is provided, the iteration begins from the first stop in the route.
17782
+ *
17783
+ * @param [startStopId] - (Optional) The StopId of the stop to start the iteration from.
17784
+ * @returns An IterableIterator of StopIds, starting from the specified stop or the first stop.
17785
+ * @throws An error if the specified start stop is not found in the route.
17786
+ */
17787
+ stopsIterator(startStopId) {
17788
+ const startIndex = startStopId !== undefined ? this.stopIndices.get(startStopId) : 0;
17789
+ if (startIndex === undefined) {
17790
+ throw new Error(`Start stop ${startStopId} not found in route ${this.serviceRouteId}`);
17791
+ }
17792
+ function* generator(stops, startIndex) {
17793
+ for (let i = startIndex; i < stops.length; i++) {
17794
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17795
+ yield stops[i];
17796
+ }
17797
+ }
17798
+ return generator(this.stops, startIndex);
17799
+ }
17800
+ /**
17801
+ * Finds the earliest trip that can be taken from a specific stop on a given route,
17802
+ * optionally constrained by a latest trip index and a time before which the trip
17803
+ * should not depart.
17804
+ * *
17805
+ * @param stopId - The StopId of the stop where the trip should be found.
17806
+ * @param [after=Time.origin()] - The earliest time after which the trip should depart.
17807
+ * If not provided, searches all available trips.
17808
+ * @param [beforeTrip] - (Optional) The index of the trip before which the search should be constrained.
17809
+ * If not provided, searches all available trips.
17810
+ * @returns The index of the earliest trip meeting the criteria, or undefined if no such trip is found.
17811
+ */
17812
+ findEarliestTrip(stopId, after = Time.origin(), beforeTrip) {
17813
+ const maxTripIndex = beforeTrip !== undefined
17814
+ ? Math.min(beforeTrip - 1, this.nbTrips - 1)
17815
+ : this.nbTrips - 1;
17816
+ if (maxTripIndex < 0) {
17817
+ return undefined;
17818
+ }
17819
+ let earliestTripIndex;
17820
+ let lowTrip = 0;
17821
+ let highTrip = maxTripIndex;
17822
+ while (lowTrip <= highTrip) {
17823
+ const midTrip = Math.floor((lowTrip + highTrip) / 2);
17824
+ const departure = this.departureFrom(stopId, midTrip);
17825
+ const pickUpType = this.pickUpTypeFrom(stopId, midTrip);
17826
+ if ((departure.isAfter(after) || departure.equals(after)) &&
17827
+ pickUpType !== 'NOT_AVAILABLE') {
17828
+ earliestTripIndex = midTrip;
17829
+ highTrip = midTrip - 1;
17830
+ }
17831
+ else {
17832
+ lowTrip = midTrip + 1;
17833
+ }
17834
+ }
17835
+ return earliestTripIndex;
17836
+ }
17837
+ /**
17838
+ * Retrieves the index of a stop within the route.
17839
+ * @param stopId The StopId of the stop to locate in the route.
17840
+ * @returns The index of the stop in the route.
17841
+ */
17842
+ stopIndex(stopId) {
17843
+ const stopIndex = this.stopIndices.get(stopId);
17844
+ if (stopIndex === undefined) {
17845
+ throw new Error(`Stop index for ${stopId} not found in route ${this.serviceRouteId}`);
17846
+ }
17847
+ return stopIndex;
17848
+ }
17849
+ };
17850
+
17851
+ const isLittleEndian = (() => {
17852
+ const buffer = new ArrayBuffer(4);
17853
+ const view = new DataView(buffer);
17854
+ view.setUint32(0, 0x12345678);
17855
+ return new Uint8Array(buffer)[0] === 0x78;
17856
+ })();
17857
+ const STANDARD_ENDIANNESS = true; // true = little-endian
17858
+ const uint32ArrayToBytes = (array) => {
17859
+ if (isLittleEndian === STANDARD_ENDIANNESS) {
17860
+ return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
17861
+ }
17862
+ // If endianness doesn't match, we need to swap byte order
17863
+ const result = new Uint8Array(array.length * 4);
17864
+ const view = new DataView(result.buffer);
17865
+ for (let i = 0; i < array.length; i++) {
17866
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17867
+ view.setUint32(i * 4, array[i], STANDARD_ENDIANNESS);
17868
+ }
17869
+ return result;
17870
+ };
17871
+ const bytesToUint32Array = (bytes) => {
17872
+ if (bytes.byteLength % 4 !== 0) {
17873
+ throw new Error('Byte array length must be a multiple of 4 to convert to Uint32Array');
17874
+ }
17875
+ // If system endianness matches our standard, we can create a view directly
17876
+ if (isLittleEndian === STANDARD_ENDIANNESS) {
17877
+ return new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
17878
+ }
17879
+ // If endianness doesn't match, we need to swap byte order
17880
+ const result = new Uint32Array(bytes.byteLength / 4);
17881
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
17882
+ for (let i = 0; i < result.length; i++) {
17883
+ result[i] = view.getUint32(i * 4, STANDARD_ENDIANNESS);
17884
+ }
17885
+ return result;
17886
+ };
17887
+ const uint16ArrayToBytes = (array) => {
17888
+ if (isLittleEndian === STANDARD_ENDIANNESS) {
17889
+ return new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
17890
+ }
17891
+ // If endianness doesn't match, we need to swap byte order
17892
+ const result = new Uint8Array(array.length * 2);
17893
+ const view = new DataView(result.buffer);
17894
+ for (let i = 0; i < array.length; i++) {
17895
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17896
+ view.setUint16(i * 2, array[i], STANDARD_ENDIANNESS);
17897
+ }
17898
+ return result;
17899
+ };
17900
+ const bytesToUint16Array = (bytes) => {
17901
+ if (bytes.byteLength % 2 !== 0) {
17902
+ throw new Error('Byte array length must be a multiple of 2 to convert to Uint16Array');
17903
+ }
17904
+ // If system endianness matches our standard, we can create a view directly
17905
+ if (isLittleEndian === STANDARD_ENDIANNESS) {
17906
+ return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
17907
+ }
17908
+ // If endianness doesn't match, we need to swap byte order
17909
+ const result = new Uint16Array(bytes.byteLength / 2);
17910
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
17911
+ for (let i = 0; i < result.length; i++) {
17912
+ result[i] = view.getUint16(i * 2, STANDARD_ENDIANNESS);
17913
+ }
17914
+ return result;
17915
+ };
17916
+ const serializeStopsAdjacency = (stopsAdjacency) => {
17917
+ const protoStopsAdjacency = {
17918
+ stops: {},
17919
+ };
17920
+ stopsAdjacency.forEach((value, key) => {
17921
+ protoStopsAdjacency.stops[key] = {
17922
+ transfers: value.transfers.map((transfer) => (Object.assign({ destination: transfer.destination, type: serializeTransferType(transfer.type) }, (transfer.minTransferTime !== undefined && {
17923
+ minTransferTime: transfer.minTransferTime.toSeconds(),
17924
+ })))),
17925
+ routes: value.routes,
17926
+ };
17927
+ });
17928
+ return protoStopsAdjacency;
17929
+ };
17930
+ const serializeRoutesAdjacency = (routesAdjacency) => {
17931
+ const protoRoutesAdjacency = {
17932
+ routes: {},
17933
+ };
17934
+ routesAdjacency.forEach((route, key) => {
17935
+ const routeData = route.serialize();
17936
+ protoRoutesAdjacency.routes[key] = {
17937
+ stopTimes: uint16ArrayToBytes(routeData.stopTimes),
17938
+ pickUpDropOffTypes: routeData.pickUpDropOffTypes,
17939
+ stops: uint32ArrayToBytes(routeData.stops),
17940
+ serviceRouteId: routeData.serviceRouteId,
17941
+ };
17942
+ });
17943
+ return protoRoutesAdjacency;
17944
+ };
17945
+ const serializeServiceRoutesMap = (serviceRoutesMap) => {
17946
+ const protoServiceRoutesMap = {
17947
+ routes: {},
17948
+ };
17949
+ serviceRoutesMap.forEach((value, key) => {
17950
+ protoServiceRoutesMap.routes[key] = {
17951
+ type: serializeRouteType(value.type),
17952
+ name: value.name,
17953
+ };
17954
+ });
17955
+ return protoServiceRoutesMap;
17956
+ };
17957
+ const deserializeStopsAdjacency = (protoStopsAdjacency) => {
17958
+ const stopsAdjacency = new Map();
17959
+ Object.entries(protoStopsAdjacency.stops).forEach(([keyStr, value]) => {
17960
+ const key = parseInt(keyStr, 10);
17961
+ stopsAdjacency.set(key, {
17962
+ transfers: value.transfers.map((transfer) => (Object.assign({ destination: transfer.destination, type: parseTransferType(transfer.type) }, (transfer.minTransferTime !== undefined && {
17963
+ minTransferTime: Duration.fromSeconds(transfer.minTransferTime),
17964
+ })))),
17965
+ routes: value.routes,
17966
+ });
17967
+ });
17968
+ return stopsAdjacency;
17969
+ };
17970
+ const deserializeRoutesAdjacency = (protoRoutesAdjacency) => {
17971
+ const routesAdjacency = new Map();
17972
+ Object.entries(protoRoutesAdjacency.routes).forEach(([key, value]) => {
17973
+ const stops = bytesToUint32Array(value.stops);
17974
+ routesAdjacency.set(key, new Route$1(bytesToUint16Array(value.stopTimes), value.pickUpDropOffTypes, stops, value.serviceRouteId));
17975
+ });
17976
+ return routesAdjacency;
17977
+ };
17978
+ const deserializeServiceRoutesMap = (protoServiceRoutesMap) => {
17979
+ const serviceRoutesMap = new Map();
17980
+ Object.entries(protoServiceRoutesMap.routes).forEach(([key, value]) => {
17981
+ serviceRoutesMap.set(key, {
17982
+ type: parseRouteType(value.type),
17983
+ name: value.name,
17984
+ });
17985
+ });
17986
+ return serviceRoutesMap;
17987
+ };
17988
+ const parseTransferType = (type) => {
17989
+ switch (type) {
17990
+ case TransferType.RECOMMENDED_TRANSFER_POINT:
17991
+ return 'RECOMMENDED';
17992
+ case TransferType.TIMED_TRANSFER:
17993
+ return 'GUARANTEED';
17994
+ case TransferType.REQUIRES_MINIMAL_TIME:
17995
+ return 'REQUIRES_MINIMAL_TIME';
17996
+ case TransferType.IN_SEAT_TRANSFER:
17997
+ return 'IN_SEAT';
17998
+ case TransferType.UNRECOGNIZED:
17999
+ throw new Error('Unrecognized protobuf transfer type.');
18000
+ }
18001
+ };
18002
+ const serializeTransferType = (type) => {
18003
+ switch (type) {
18004
+ case 'RECOMMENDED':
18005
+ return TransferType.RECOMMENDED_TRANSFER_POINT;
18006
+ case 'GUARANTEED':
18007
+ return TransferType.TIMED_TRANSFER;
18008
+ case 'REQUIRES_MINIMAL_TIME':
18009
+ return TransferType.REQUIRES_MINIMAL_TIME;
18010
+ case 'IN_SEAT':
18011
+ return TransferType.IN_SEAT_TRANSFER;
18012
+ }
18013
+ };
18014
+ const parseRouteType = (type) => {
18015
+ switch (type) {
18016
+ case RouteType.TRAM:
18017
+ return 'TRAM';
18018
+ case RouteType.SUBWAY:
18019
+ return 'SUBWAY';
18020
+ case RouteType.RAIL:
18021
+ return 'RAIL';
18022
+ case RouteType.BUS:
18023
+ return 'BUS';
18024
+ case RouteType.FERRY:
18025
+ return 'FERRY';
18026
+ case RouteType.CABLE_TRAM:
18027
+ return 'CABLE_TRAM';
18028
+ case RouteType.AERIAL_LIFT:
18029
+ return 'AERIAL_LIFT';
18030
+ case RouteType.FUNICULAR:
18031
+ return 'FUNICULAR';
18032
+ case RouteType.TROLLEYBUS:
18033
+ return 'TROLLEYBUS';
18034
+ case RouteType.MONORAIL:
18035
+ return 'MONORAIL';
18036
+ case RouteType.UNRECOGNIZED:
18037
+ default:
18038
+ throw new Error('Unrecognized protobuf route type.');
18039
+ }
18040
+ };
18041
+ const serializeRouteType = (type) => {
18042
+ switch (type) {
18043
+ case 'TRAM':
18044
+ return RouteType.TRAM;
18045
+ case 'SUBWAY':
18046
+ return RouteType.SUBWAY;
18047
+ case 'RAIL':
18048
+ return RouteType.RAIL;
18049
+ case 'BUS':
18050
+ return RouteType.BUS;
18051
+ case 'FERRY':
18052
+ return RouteType.FERRY;
18053
+ case 'CABLE_TRAM':
18054
+ return RouteType.CABLE_TRAM;
18055
+ case 'AERIAL_LIFT':
18056
+ return RouteType.AERIAL_LIFT;
18057
+ case 'FUNICULAR':
18058
+ return RouteType.FUNICULAR;
18059
+ case 'TROLLEYBUS':
18060
+ return RouteType.TROLLEYBUS;
18061
+ case 'MONORAIL':
18062
+ return RouteType.MONORAIL;
18063
+ }
18064
+ };
18065
+
18066
+ const ALL_TRANSPORT_MODES = new Set([
17137
18067
  'TRAM',
17138
18068
  'SUBWAY',
17139
18069
  'RAIL',
@@ -17144,11 +18074,10 @@ const ALL_TRANSPORT_MODES = [
17144
18074
  'FUNICULAR',
17145
18075
  'TROLLEYBUS',
17146
18076
  'MONORAIL',
17147
- ];
18077
+ ]);
17148
18078
  const CURRENT_VERSION = '0.0.3';
17149
18079
  /**
17150
- * The internal transit timetable format
17151
- * reuses some GTFS concepts for the sake of simplicity for now.
18080
+ * The internal transit timetable format.
17152
18081
  */
17153
18082
  class Timetable {
17154
18083
  constructor(stopsAdjacency, routesAdjacency, routes) {
@@ -17157,9 +18086,9 @@ class Timetable {
17157
18086
  this.routes = routes;
17158
18087
  }
17159
18088
  /**
17160
- * Serializes the Timetable into a binary protobuf.
18089
+ * Serializes the Timetable into a binary array.
17161
18090
  *
17162
- * @returns {Uint8Array} - The serialized binary data.
18091
+ * @returns The serialized binary data.
17163
18092
  */
17164
18093
  serialize() {
17165
18094
  const protoTimetable = {
@@ -17175,8 +18104,8 @@ class Timetable {
17175
18104
  /**
17176
18105
  * Deserializes a binary protobuf into a Timetable object.
17177
18106
  *
17178
- * @param {Uint8Array} data - The binary data to deserialize.
17179
- * @returns {Timetable} - The deserialized Timetable object.
18107
+ * @param data - The binary data to deserialize.
18108
+ * @returns The deserialized Timetable object.
17180
18109
  */
17181
18110
  static fromData(data) {
17182
18111
  const reader = new BinaryReader(data);
@@ -17192,108 +18121,89 @@ class Timetable {
17192
18121
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
17193
18122
  deserializeServiceRoutesMap(protoTimetable.routes));
17194
18123
  }
17195
- getRoutesThroughStop(stopId) {
17196
- const stopAdjacency = this.stopsAdjacency.get(stopId);
17197
- if (!stopAdjacency) {
17198
- return [];
17199
- }
17200
- return stopAdjacency.routes;
17201
- }
18124
+ /**
18125
+ * Retrieves the route associated with the given route ID.
18126
+ *
18127
+ * @param routeId - The ID of the route to be retrieved.
18128
+ * @returns The route corresponding to the provided ID,
18129
+ * or undefined if no such route exists.
18130
+ */
17202
18131
  getRoute(routeId) {
17203
18132
  return this.routesAdjacency.get(routeId);
17204
18133
  }
18134
+ /**
18135
+ * Retrieves all transfer options available at the specified stop.
18136
+ *
18137
+ * @param stopId - The ID of the stop to get transfers for.
18138
+ * @returns An array of transfer options available at the stop.
18139
+ */
17205
18140
  getTransfers(stopId) {
17206
18141
  var _a, _b;
17207
- return (_b = (_a = this.stopsAdjacency.get(stopId)) === null || _a === undefined ? undefined : _a.transfers) !== null && _b !== undefined ? _b : [];
18142
+ return (_b = (_a = this.stopsAdjacency.get(stopId)) === null || _a === void 0 ? void 0 : _a.transfers) !== null && _b !== void 0 ? _b : [];
18143
+ }
18144
+ /**
18145
+ * Retrieves the service route associated with the given route.
18146
+ * A service route refers to a collection of trips that are displayed
18147
+ * to riders as a single service.
18148
+ *
18149
+ * @param route - The route for which the service route is to be retrieved.
18150
+ * @returns The service route corresponding to the provided route.
18151
+ */
18152
+ getServiceRoute(route) {
18153
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
18154
+ return this.routes.get(route.serviceRoute());
18155
+ }
18156
+ /**
18157
+ * Finds all routes passing through a stop.
18158
+ *
18159
+ * @param stopId - The ID of the stop to find routes for.
18160
+ * @returns An array of routes passing through the specified stop.
18161
+ */
18162
+ routesPassingThrough(stopId) {
18163
+ const stopData = this.stopsAdjacency.get(stopId);
18164
+ if (!stopData) {
18165
+ return [];
18166
+ }
18167
+ const routes = [];
18168
+ for (const routeId of stopData.routes) {
18169
+ const route = this.routesAdjacency.get(routeId);
18170
+ if (route) {
18171
+ routes.push(route);
18172
+ }
18173
+ }
18174
+ return routes;
17208
18175
  }
17209
18176
  /**
17210
18177
  * Finds routes that are reachable from a set of stop IDs.
17211
18178
  * Also identifies the first stop available to hop on each route among
17212
18179
  * the input stops.
18180
+ *
18181
+ * @param fromStops - The set of stop IDs to find reachable routes from.
18182
+ * @param transportModes - The set of transport modes to consider for reachable routes.
18183
+ * @returns A map of reachable routes to the first stop available to hop on each route.
17213
18184
  */
17214
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
17215
18185
  findReachableRoutes(fromStops, transportModes = ALL_TRANSPORT_MODES) {
17216
- var _a;
17217
18186
  const reachableRoutes = new Map();
17218
- for (const stop of fromStops) {
17219
- const validRoutes = (_a = this.stopsAdjacency
17220
- .get(stop)) === null || _a === undefined ? undefined : _a.routes.filter((routeId) => {
17221
- const serviceRoute = this.getServiceRouteFromRouteId(routeId);
17222
- if (!serviceRoute) {
17223
- return false;
17224
- }
17225
- return transportModes.includes(serviceRoute.type);
18187
+ for (const originStop of fromStops) {
18188
+ const validRoutes = this.routesPassingThrough(originStop).filter((route) => {
18189
+ const serviceRoute = this.getServiceRoute(route);
18190
+ return transportModes.has(serviceRoute.type);
17226
18191
  });
17227
- for (const routeId of validRoutes || []) {
17228
- const hopOnStop = reachableRoutes.get(routeId);
18192
+ for (const route of validRoutes) {
18193
+ const hopOnStop = reachableRoutes.get(route);
17229
18194
  if (hopOnStop) {
17230
- // Checks if the existing hop on stop is before the current stop
17231
- const routeStopIndices = this.routesAdjacency.get(routeId).stopIndices;
17232
- const stopIndex = routeStopIndices.get(stop);
17233
- const hopOnStopIndex = routeStopIndices.get(hopOnStop);
17234
- if (stopIndex < hopOnStopIndex) {
18195
+ if (route.isBefore(originStop, hopOnStop)) {
17235
18196
  // if the current stop is before the existing hop on stop, replace it
17236
- reachableRoutes.set(routeId, stop);
18197
+ reachableRoutes.set(route, originStop);
17237
18198
  }
17238
18199
  }
17239
18200
  else {
17240
- reachableRoutes.set(routeId, stop);
18201
+ reachableRoutes.set(route, originStop);
17241
18202
  }
17242
18203
  }
17243
18204
  }
17244
18205
  return reachableRoutes;
17245
18206
  }
17246
- getServiceRouteFromRouteId(routeId) {
17247
- const route = this.routesAdjacency.get(routeId);
17248
- if (!route) {
17249
- console.warn(`Route ${routeId} not found.`);
17250
- return undefined;
17251
- }
17252
- return this.routes.get(route.serviceRouteId);
17253
- }
17254
- getServiceRoute(serviceRouteId) {
17255
- return this.routes.get(serviceRouteId);
17256
- }
17257
- /**
17258
- * Finds the earliest trip that can be taken from a specific stop on a given route,
17259
- * optionally constrained by a latest trip index and a time before which the trip
17260
- * should not depart.
17261
- */
17262
- findEarliestTrip(route, stopId, beforeTrip, after = Time.origin()) {
17263
- const stopIndex = route.stopIndices.get(stopId);
17264
- const stopsNumber = route.stops.length;
17265
- if (beforeTrip === undefined) {
17266
- for (let tripIndex = 0; tripIndex < route.stopTimes.length / stopsNumber; tripIndex++) {
17267
- const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
17268
- const departure = route.stopTimes[stopTimeIndex * 2 + 1];
17269
- const pickUpType = route.pickUpDropOffTypes[stopTimeIndex * 2];
17270
- if (departure >= after.toMinutes() && pickUpType !== NOT_AVAILABLE) {
17271
- return tripIndex;
17272
- }
17273
- }
17274
- return undefined;
17275
- }
17276
- else {
17277
- let earliestTripIndex;
17278
- let earliestDeparture;
17279
- for (let tripIndex = beforeTrip; // ?? route.stopTimes.length / stopsNumber - 1;
17280
- tripIndex >= 0; tripIndex--) {
17281
- const stopTimeIndex = tripIndex * stopsNumber + stopIndex;
17282
- const departure = route.stopTimes[stopTimeIndex * 2 + 1];
17283
- const pickUpType = route.pickUpDropOffTypes[stopTimeIndex * 2];
17284
- if (departure < after.toMinutes()) {
17285
- break;
17286
- }
17287
- if (pickUpType !== NOT_AVAILABLE &&
17288
- (earliestDeparture === undefined ||
17289
- departure < earliestDeparture.toMinutes())) {
17290
- earliestTripIndex = tripIndex;
17291
- earliestDeparture = Time.fromMinutes(departure);
17292
- }
17293
- }
17294
- return earliestTripIndex;
17295
- }
17296
- }
17297
18207
  }
17298
18208
 
17299
18209
  const standardProfile = {
@@ -17610,7 +18520,7 @@ const normalize_options = function (opts) {
17610
18520
  );
17611
18521
  }
17612
18522
  // Normalize option `columns`
17613
- options.cast_first_line_to_header = null;
18523
+ options.cast_first_line_to_header = undefined;
17614
18524
  if (options.columns === true) {
17615
18525
  // Fields in the first line are converted as-is to columns
17616
18526
  options.cast_first_line_to_header = undefined;
@@ -18142,7 +19052,7 @@ const normalize_options = function (opts) {
18142
19052
  // Normalize option `to`
18143
19053
  if (options.to === undefined || options.to === null) {
18144
19054
  options.to = -1;
18145
- } else {
19055
+ } else if (options.to !== -1) {
18146
19056
  if (typeof options.to === "string" && /\d+/.test(options.to)) {
18147
19057
  options.to = parseInt(options.to);
18148
19058
  }
@@ -18161,7 +19071,7 @@ const normalize_options = function (opts) {
18161
19071
  // Normalize option `to_line`
18162
19072
  if (options.to_line === undefined || options.to_line === null) {
18163
19073
  options.to_line = -1;
18164
- } else {
19074
+ } else if (options.to_line !== -1) {
18165
19075
  if (typeof options.to_line === "string" && /\d+/.test(options.to_line)) {
18166
19076
  options.to_line = parseInt(options.to_line);
18167
19077
  }
@@ -18292,10 +19202,14 @@ const transform = function (original_options = {}) {
18292
19202
  this.state.bufBytesStart += bomLength;
18293
19203
  buf = buf.slice(bomLength);
18294
19204
  // Renormalize original options with the new encoding
18295
- this.options = normalize_options({
19205
+ const options = normalize_options({
18296
19206
  ...this.original_options,
18297
19207
  encoding: encoding,
18298
19208
  });
19209
+ // Properties are merged with the existing options instance
19210
+ for (const key in options) {
19211
+ this.options[key] = options[key];
19212
+ }
18299
19213
  // Options will re-evaluate the Buffer with the new encoding
18300
19214
  ({ comment, escape, quote } = this.options);
18301
19215
  break;
@@ -19038,10 +19952,14 @@ const transform = function (original_options = {}) {
19038
19952
  if (skip_records_with_error) {
19039
19953
  this.state.recordHasError = true;
19040
19954
  if (this.options.on_skip !== undefined) {
19041
- this.options.on_skip(
19042
- err,
19043
- raw ? this.state.rawBuffer.toString(encoding) : undefined,
19044
- );
19955
+ try {
19956
+ this.options.on_skip(
19957
+ err,
19958
+ raw ? this.state.rawBuffer.toString(encoding) : undefined,
19959
+ );
19960
+ } catch (err) {
19961
+ return err;
19962
+ }
19045
19963
  }
19046
19964
  // this.emit('skip', err, raw ? this.state.rawBuffer.toString(encoding) : undefined);
19047
19965
  return undefined;
@@ -19238,11 +20156,17 @@ const hashIds = (ids) => {
19238
20156
  * @param stream The CSV stream.
19239
20157
  * @returns A parser from the csv-parse library.
19240
20158
  */
19241
- const parseCsv = (stream) => {
20159
+ const parseCsv = (stream, numericColumns = []) => {
19242
20160
  return stream.pipe(parse({
19243
20161
  delimiter: ',',
19244
20162
  columns: true,
19245
- cast: true,
20163
+ cast: (value, context) => {
20164
+ if (typeof context.column === 'string' &&
20165
+ numericColumns.includes(context.column)) {
20166
+ return Number(value);
20167
+ }
20168
+ return value;
20169
+ },
19246
20170
  bom: true,
19247
20171
  ignore_last_delimiters: true,
19248
20172
  relax_column_count: true,
@@ -19256,11 +20180,11 @@ const parseCsv = (stream) => {
19256
20180
  * @param profile A configuration object defining the specificities of the GTFS feed.
19257
20181
  * @returns A map of all the valid routes.
19258
20182
  */
19259
- const parseRoutes = (routesStream_1, ...args_1) => __awaiter(undefined, [routesStream_1, ...args_1], undefined, function* (routesStream, profile = standardProfile) {
20183
+ const parseRoutes = (routesStream_1, ...args_1) => __awaiter(void 0, [routesStream_1, ...args_1], void 0, function* (routesStream, profile = standardProfile) {
19260
20184
  var _a, e_1, _b, _c;
19261
20185
  const routes = new Map();
19262
20186
  try {
19263
- for (var _d = true, _e = __asyncValues(parseCsv(routesStream)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
20187
+ for (var _d = true, _e = __asyncValues(parseCsv(routesStream, ['route_type'])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19264
20188
  _c = _f.value;
19265
20189
  _d = false;
19266
20190
  const rawLine = _c;
@@ -19271,7 +20195,7 @@ const parseRoutes = (routesStream_1, ...args_1) => __awaiter(undefined, [routesS
19271
20195
  continue;
19272
20196
  }
19273
20197
  routes.set(line.route_id, {
19274
- name: line.route_short_name + '',
20198
+ name: line.route_short_name,
19275
20199
  type: routeType,
19276
20200
  });
19277
20201
  }
@@ -19324,13 +20248,23 @@ const weekdays = {
19324
20248
  * @param date The active date.
19325
20249
  * @param calendarStream A readable stream for the GTFS calendar.txt file.
19326
20250
  */
19327
- const parseCalendar = (calendarStream, serviceIds, date) => __awaiter(undefined, undefined, undefined, function* () {
20251
+ const parseCalendar = (calendarStream, serviceIds, date) => __awaiter(void 0, void 0, void 0, function* () {
19328
20252
  var _a, e_1, _b, _c;
19329
20253
  const activeDate = toGtfsDate(date);
19330
20254
  const weekday = date.weekday;
19331
20255
  const weekdayIndex = weekdays[weekday];
19332
20256
  try {
19333
- for (var _d = true, _e = __asyncValues(parseCsv(calendarStream)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
20257
+ for (var _d = true, _e = __asyncValues(parseCsv(calendarStream, [
20258
+ 'monday',
20259
+ 'tuesday',
20260
+ 'wednesday',
20261
+ 'thursday',
20262
+ 'friday',
20263
+ 'saturday',
20264
+ 'sunday',
20265
+ 'start_date',
20266
+ 'end_date',
20267
+ ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19334
20268
  _c = _f.value;
19335
20269
  _d = false;
19336
20270
  const rawLine = _c;
@@ -19361,11 +20295,14 @@ const parseCalendar = (calendarStream, serviceIds, date) => __awaiter(undefined,
19361
20295
  * @param date The active date, in the format "YYYYMMDD".
19362
20296
  * @param calendarDatesStream A readable stream for the GTFS calendar_dates.txt file.
19363
20297
  */
19364
- const parseCalendarDates = (calendarDatesStream, serviceIds, date) => __awaiter(undefined, undefined, undefined, function* () {
20298
+ const parseCalendarDates = (calendarDatesStream, serviceIds, date) => __awaiter(void 0, void 0, void 0, function* () {
19365
20299
  var _a, e_2, _b, _c;
19366
20300
  const activeDate = toGtfsDate(date);
19367
20301
  try {
19368
- for (var _d = true, _e = __asyncValues(parseCsv(calendarDatesStream)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
20302
+ for (var _d = true, _e = __asyncValues(parseCsv(calendarDatesStream, [
20303
+ 'date',
20304
+ 'exception_type',
20305
+ ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19369
20306
  _c = _f.value;
19370
20307
  _d = false;
19371
20308
  const rawLine = _c;
@@ -19398,17 +20335,21 @@ const parseCalendarDates = (calendarDatesStream, serviceIds, date) => __awaiter(
19398
20335
  * @param stopsStream The readable stream containing the stops data.
19399
20336
  * @return A mapping of stop IDs to corresponding stop details.
19400
20337
  */
19401
- const parseStops = (stopsStream, platformParser) => __awaiter(undefined, undefined, undefined, function* () {
20338
+ const parseStops = (stopsStream, platformParser) => __awaiter(void 0, void 0, void 0, function* () {
19402
20339
  var _a, e_1, _b, _c;
19403
20340
  const parsedStops = new Map();
19404
20341
  let i = 0;
19405
20342
  try {
19406
- for (var _d = true, _e = __asyncValues(parseCsv(stopsStream)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
20343
+ for (var _d = true, _e = __asyncValues(parseCsv(stopsStream, [
20344
+ 'stop_lat',
20345
+ 'stop_lon',
20346
+ 'location_type',
20347
+ ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19407
20348
  _c = _f.value;
19408
20349
  _d = false;
19409
20350
  const rawLine = _c;
19410
20351
  const line = rawLine;
19411
- const stop = Object.assign({ id: i, sourceStopId: line.stop_id + '', name: line.stop_name, lat: line.stop_lat, lon: line.stop_lon, locationType: line.location_type
20352
+ const stop = Object.assign({ id: i, sourceStopId: line.stop_id, name: line.stop_name, lat: line.stop_lat, lon: line.stop_lon, locationType: line.location_type
19412
20353
  ? parseGtfsLocationType(line.location_type)
19413
20354
  : 'SIMPLE_STOP_OR_PLATFORM', children: [] }, (line.parent_station && { parentSourceId: line.parent_station }));
19414
20355
  if (platformParser) {
@@ -19422,7 +20363,7 @@ const parseStops = (stopsStream, platformParser) => __awaiter(undefined, undefin
19422
20363
  console.info(`Could not parse platform for stop ${line.stop_id}.`);
19423
20364
  }
19424
20365
  }
19425
- parsedStops.set(line.stop_id + '', stop);
20366
+ parsedStops.set(line.stop_id, stop);
19426
20367
  i = i + 1;
19427
20368
  }
19428
20369
  }
@@ -19499,11 +20440,14 @@ const parseGtfsLocationType = (gtfsLocationType) => {
19499
20440
  * @param stopsStream The readable stream containing the stops data.
19500
20441
  * @return A mapping of stop IDs to corresponding stop details.
19501
20442
  */
19502
- const parseTransfers = (transfersStream, stopsMap) => __awaiter(undefined, undefined, undefined, function* () {
20443
+ const parseTransfers = (transfersStream, stopsMap) => __awaiter(void 0, void 0, void 0, function* () {
19503
20444
  var _a, e_1, _b, _c;
19504
20445
  const transfers = new Map();
19505
20446
  try {
19506
- for (var _d = true, _e = __asyncValues(parseCsv(transfersStream)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
20447
+ for (var _d = true, _e = __asyncValues(parseCsv(transfersStream, [
20448
+ 'transfer_type',
20449
+ 'min_transfer_time',
20450
+ ])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19507
20451
  _c = _f.value;
19508
20452
  _d = false;
19509
20453
  const rawLine = _c;
@@ -19528,9 +20472,9 @@ const parseTransfers = (transfersStream, stopsMap) => __awaiter(undefined, undef
19528
20472
  console.info(`Missing minimum transfer time between ${transferEntry.from_stop_id} and ${transferEntry.to_stop_id}.`);
19529
20473
  }
19530
20474
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19531
- const fromStop = stopsMap.get(transferEntry.from_stop_id + '');
20475
+ const fromStop = stopsMap.get(transferEntry.from_stop_id);
19532
20476
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19533
- const toStop = stopsMap.get(transferEntry.to_stop_id + '');
20477
+ const toStop = stopsMap.get(transferEntry.to_stop_id);
19534
20478
  const transfer = Object.assign({ destination: toStop.id, type: parseGtfsTransferType(transferEntry.transfer_type) }, (transferEntry.min_transfer_time && {
19535
20479
  minTransferTime: Duration.fromSeconds(transferEntry.min_transfer_time),
19536
20480
  }));
@@ -19570,11 +20514,11 @@ const parseGtfsTransferType = (gtfsTransferType) => {
19570
20514
  * @param routeIds A mapping of route IDs to route details.
19571
20515
  * @returns A mapping of trip IDs to corresponding route IDs.
19572
20516
  */
19573
- const parseTrips = (tripsStream, serviceIds, routeIds) => __awaiter(undefined, undefined, undefined, function* () {
20517
+ const parseTrips = (tripsStream, serviceIds, routeIds) => __awaiter(void 0, void 0, void 0, function* () {
19574
20518
  var _a, e_1, _b, _c;
19575
20519
  const trips = new Map();
19576
20520
  try {
19577
- for (var _d = true, _e = __asyncValues(parseCsv(tripsStream)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
20521
+ for (var _d = true, _e = __asyncValues(parseCsv(tripsStream, ['stop_sequence'])), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
19578
20522
  _c = _f.value;
19579
20523
  _d = false;
19580
20524
  const rawLine = _c;
@@ -19604,11 +20548,14 @@ const buildStopsAdjacencyStructure = (validStops, routes, transfersMap) => {
19604
20548
  const stopsAdjacency = new Map();
19605
20549
  for (const routeId of routes.keys()) {
19606
20550
  const route = routes.get(routeId);
19607
- for (const stop of route.stops) {
20551
+ if (!route) {
20552
+ throw new Error(`Route ${routeId} not found`);
20553
+ }
20554
+ for (const stop of route.stopsIterator()) {
19608
20555
  if (!stopsAdjacency.get(stop) && validStops.has(stop)) {
19609
20556
  stopsAdjacency.set(stop, { routes: [], transfers: [] });
19610
20557
  }
19611
- (_a = stopsAdjacency.get(stop)) === null || _a === undefined ? undefined : _a.routes.push(routeId);
20558
+ (_a = stopsAdjacency.get(stop)) === null || _a === void 0 ? void 0 : _a.routes.push(routeId);
19612
20559
  }
19613
20560
  }
19614
20561
  for (const [stop, transfers] of transfersMap) {
@@ -19632,7 +20579,7 @@ const buildStopsAdjacencyStructure = (validStops, routes, transfersMap) => {
19632
20579
  * @param validStopIds A set of valid stop IDs.
19633
20580
  * @returns A mapping of route IDs to route details. The routes returned correspond to the set of trips from GTFS that share the same stop list.
19634
20581
  */
19635
- const parseStopTimes = (stopTimesStream, stopsMap, validTripIds, validStopIds) => __awaiter(undefined, undefined, undefined, function* () {
20582
+ const parseStopTimes = (stopTimesStream, stopsMap, validTripIds, validStopIds) => __awaiter(void 0, void 0, void 0, function* () {
19636
20583
  var _a, e_2, _b, _c;
19637
20584
  var _d, _e;
19638
20585
  /**
@@ -19670,7 +20617,6 @@ const parseStopTimes = (stopTimesStream, stopsMap, validTripIds, validStopIds) =
19670
20617
  route = {
19671
20618
  serviceRouteId: gtfsRouteId,
19672
20619
  stops: stopsArray,
19673
- stopIndices: new Map(stops.map((stop, i) => [stop, i])),
19674
20620
  stopTimes: stopTimesArray,
19675
20621
  pickUpDropOffTypes: pickUpDropOffTypesArray,
19676
20622
  };
@@ -19735,7 +20681,7 @@ const parseStopTimes = (stopTimesStream, stopsMap, validTripIds, validStopIds) =
19735
20681
  let dropOffTypes = [];
19736
20682
  let currentTripId = undefined;
19737
20683
  try {
19738
- for (var _f = true, _g = __asyncValues(parseCsv(stopTimesStream)), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
20684
+ for (var _f = true, _g = __asyncValues(parseCsv(stopTimesStream, ['stop_sequence'])), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
19739
20685
  _c = _h.value;
19740
20686
  _f = false;
19741
20687
  const rawLine = _c;
@@ -19756,7 +20702,7 @@ const parseStopTimes = (stopTimesStream, stopsMap, validTripIds, validStopIds) =
19756
20702
  }
19757
20703
  currentTripId = line.trip_id;
19758
20704
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19759
- stops.push(stopsMap.get(line.stop_id + '').id);
20705
+ stops.push(stopsMap.get(line.stop_id).id);
19760
20706
  const departure = (_d = line.departure_time) !== null && _d !== void 0 ? _d : line.arrival_time;
19761
20707
  const arrival = (_e = line.arrival_time) !== null && _e !== void 0 ? _e : line.departure_time;
19762
20708
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -19778,12 +20724,15 @@ const parseStopTimes = (stopTimesStream, stopsMap, validTripIds, validStopIds) =
19778
20724
  if (currentTripId) {
19779
20725
  addTrip(currentTripId);
19780
20726
  }
19781
- return routes;
20727
+ const routesAdjacency = new Map();
20728
+ for (const [routeId, routeData] of routes) {
20729
+ routesAdjacency.set(routeId, new Route$1(routeData.stopTimes, routeData.pickUpDropOffTypes, routeData.stops, routeData.serviceRouteId));
20730
+ }
20731
+ return routesAdjacency;
19782
20732
  });
19783
20733
  const parsePickupDropOffType = (gtfsType) => {
19784
20734
  switch (gtfsType) {
19785
20735
  default:
19786
- console.warn(`Unknown pickup/drop-off type ${gtfsType}`);
19787
20736
  return REGULAR;
19788
20737
  case 0:
19789
20738
  return REGULAR;
@@ -19818,7 +20767,7 @@ class GtfsParser {
19818
20767
  * @returns An object containing the timetable and stops map.
19819
20768
  */
19820
20769
  parse(date) {
19821
- return __awaiter(this, undefined, undefined, function* () {
20770
+ return __awaiter(this, void 0, void 0, function* () {
19822
20771
  const zip = new StreamZip.async({ file: this.path });
19823
20772
  const entries = yield zip.entries();
19824
20773
  const datetime = DateTime.fromJSDate(date);
@@ -19879,7 +20828,7 @@ class GtfsParser {
19879
20828
  * @returns An object containing the timetable and stops map.
19880
20829
  */
19881
20830
  parseStops() {
19882
- return __awaiter(this, undefined, undefined, function* () {
20831
+ return __awaiter(this, void 0, void 0, function* () {
19883
20832
  const zip = new StreamZip.async({ file: this.path });
19884
20833
  log.info(`Parsing ${STOPS_FILE}`);
19885
20834
  const stopsStream = yield zip.stream(STOPS_FILE);
@@ -19897,7 +20846,7 @@ class GtfsParser {
19897
20846
  * @returns The platform corresponding to this stop.
19898
20847
  */
19899
20848
  const platformParser = (stopEntry) => {
19900
- const stopId = String(stopEntry.stop_id);
20849
+ const stopId = stopEntry.stop_id;
19901
20850
  const stopParts = stopId.split(':');
19902
20851
  if (stopParts.length > 2) {
19903
20852
  return stopParts[2];
@@ -20018,7 +20967,7 @@ class Query {
20018
20967
  }
20019
20968
  Query.Builder = class {
20020
20969
  constructor() {
20021
- this.toValue = [];
20970
+ this.toValue = new Set();
20022
20971
  // lastDepartureTimeValue?: Date;
20023
20972
  // via: StopId[] = [];
20024
20973
  this.optionsValue = {
@@ -20027,26 +20976,47 @@ Query.Builder = class {
20027
20976
  transportModes: ALL_TRANSPORT_MODES,
20028
20977
  };
20029
20978
  }
20979
+ /**
20980
+ * Sets the starting stop.
20981
+ */
20030
20982
  from(from) {
20031
20983
  this.fromValue = from;
20032
20984
  return this;
20033
20985
  }
20986
+ /**
20987
+ * Sets the destination stops(s), routing will stop when all the provided stops are reached.
20988
+ */
20034
20989
  to(to) {
20035
- this.toValue = Array.isArray(to) ? to : [to];
20990
+ this.toValue = to instanceof Set ? to : new Set([to]);
20036
20991
  return this;
20037
20992
  }
20993
+ /**
20994
+ * Sets the departure time for the query.
20995
+ * Note that the router will favor routes that depart shortly after the provided departure time,
20996
+ * even if a later route might arrive at the same time.
20997
+ * Range queries will allow to specify a range of departure times in the future.
20998
+ */
20038
20999
  departureTime(departureTime) {
20039
21000
  this.departureTimeValue = departureTime;
20040
21001
  return this;
20041
21002
  }
21003
+ /**
21004
+ * Sets the maximum number of transfers allowed.
21005
+ */
20042
21006
  maxTransfers(maxTransfers) {
20043
21007
  this.optionsValue.maxTransfers = maxTransfers;
20044
21008
  return this;
20045
21009
  }
21010
+ /**
21011
+ * Sets the minimum transfer time to use when no transfer time is provided in the data.
21012
+ */
20046
21013
  minTransferTime(minTransferTime) {
20047
21014
  this.optionsValue.minTransferTime = minTransferTime;
20048
21015
  return this;
20049
21016
  }
21017
+ /**
21018
+ * Sets the transport modes to consider.
21019
+ */
20050
21020
  transportModes(transportModes) {
20051
21021
  this.optionsValue.transportModes = transportModes;
20052
21022
  return this;
@@ -20056,10 +21026,20 @@ Query.Builder = class {
20056
21026
  }
20057
21027
  };
20058
21028
 
21029
+ /**
21030
+ * Represents a resolved route consisting of multiple legs,
21031
+ * which can be either vehicle legs or transfer legs.
21032
+ */
20059
21033
  class Route {
20060
21034
  constructor(legs) {
20061
21035
  this.legs = legs;
20062
21036
  }
21037
+ /**
21038
+ * Calculates the departure time of the route.
21039
+ *
21040
+ * @returns The departure time of the route.
21041
+ * @throws If no vehicle leg is found in the route.
21042
+ */
20063
21043
  departureTime() {
20064
21044
  const cumulativeTransferTime = Duration.zero();
20065
21045
  for (let i = 0; i < this.legs.length; i++) {
@@ -20074,6 +21054,12 @@ class Route {
20074
21054
  }
20075
21055
  throw new Error('No vehicle leg found in route');
20076
21056
  }
21057
+ /**
21058
+ * Calculates the arrival time of the route.
21059
+ *
21060
+ * @returns The arrival time of the route.
21061
+ * @throws If no vehicle leg is found in the route.
21062
+ */
20077
21063
  arrivalTime() {
20078
21064
  let lastVehicleArrivalTime = Time.origin();
20079
21065
  const totalTransferTime = Duration.zero();
@@ -20096,25 +21082,71 @@ class Route {
20096
21082
  }
20097
21083
  return lastVehicleArrivalTime.plus(totalTransferTime);
20098
21084
  }
21085
+ /**
21086
+ * Calculates the total duration of the route.
21087
+ *
21088
+ * @returns The total duration of the route.
21089
+ */
20099
21090
  totalDuration() {
20100
21091
  if (this.legs.length === 0)
20101
21092
  return Duration.zero();
20102
21093
  return this.arrivalTime().diff(this.departureTime());
20103
21094
  }
20104
- print() {
21095
+ /**
21096
+ * Generates a human-readable string representation of the route.
21097
+ *
21098
+ * @returns A formatted string describing each leg of the route.
21099
+ */
21100
+ toString() {
20105
21101
  return this.legs
20106
21102
  .map((leg, index) => {
20107
- var _a, _b;
20108
- if ('route' in leg) {
20109
- return `Leg ${index + 1}: ${leg.from.name} to ${leg.to.name}
20110
- via route ${leg.route.type} ${leg.route.name},
20111
- departs at ${leg.departureTime.toString()}, arrives at ${leg.arrivalTime.toString()}`;
20112
- }
20113
- return `Leg ${index + 1}: Transfer from ${leg.from.name} to ${leg.to.name},
20114
- minimum transfer time: ${(_b = (_a = leg.minTransferTime) === null || _a === undefined ? undefined : _a.toString()) !== null && _b !== undefined ? _b : 'not specified'}`;
21103
+ var _a;
21104
+ const fromStop = `From: ${leg.from.name}${leg.from.platform ? ` (Pl. ${leg.from.platform})` : ''}`;
21105
+ const toStop = `To: ${leg.to.name}${leg.to.platform ? ` (Pl. ${leg.to.platform})` : ''}`;
21106
+ const transferDetails = 'minTransferTime' in leg
21107
+ ? `Minimum Transfer Time: ${(_a = leg.minTransferTime) === null || _a === void 0 ? void 0 : _a.toString()}`
21108
+ : '';
21109
+ const travelDetails = 'route' in leg && 'departureTime' in leg && 'arrivalTime' in leg
21110
+ ? `Route: ${leg.route.type} ${leg.route.name}, Departure: ${leg.departureTime.toString()}, Arrival: ${leg.arrivalTime.toString()}`
21111
+ : '';
21112
+ return [
21113
+ `Leg ${index + 1}:`,
21114
+ ` ${fromStop}`,
21115
+ ` ${toStop}`,
21116
+ transferDetails ? ` ${transferDetails}` : '',
21117
+ travelDetails ? ` ${travelDetails}` : '',
21118
+ ]
21119
+ .filter((line) => line.trim() !== '')
21120
+ .join('\n');
20115
21121
  })
20116
21122
  .join('\n');
20117
21123
  }
21124
+ /**
21125
+ * Generates a concise JSON representation of the route.
21126
+ * This is particularly useful for generating regression tests
21127
+ * to verify the correctness of route calculations.
21128
+ *
21129
+ * @returns A JSON representation of the route.
21130
+ */
21131
+ asJson() {
21132
+ const jsonLegs = this.legs.map((leg) => {
21133
+ if ('route' in leg) {
21134
+ return {
21135
+ from: leg.from.sourceStopId,
21136
+ to: leg.to.sourceStopId,
21137
+ departure: leg.departureTime.toString(),
21138
+ arrival: leg.arrivalTime.toString(),
21139
+ route: leg.route,
21140
+ };
21141
+ }
21142
+ else {
21143
+ return Object.assign({ from: leg.from.sourceStopId, to: leg.to.sourceStopId, type: leg.type }, (leg.minTransferTime !== undefined && {
21144
+ minTransferTime: leg.minTransferTime.toString(),
21145
+ }));
21146
+ }
21147
+ });
21148
+ return jsonLegs;
21149
+ }
20118
21150
  }
20119
21151
 
20120
21152
  class Result {
@@ -20133,7 +21165,11 @@ class Result {
20133
21165
  */
20134
21166
  bestRoute(to) {
20135
21167
  var _a, _b, _c;
20136
- const destinationList = Array.isArray(to) ? to : to ? [to] : this.query.to;
21168
+ const destinationList = to instanceof Set
21169
+ ? Array.from(to)
21170
+ : to
21171
+ ? [to]
21172
+ : Array.from(this.query.to);
20137
21173
  const destinations = destinationList.flatMap((destination) => this.stopsIndex.equivalentStops(destination));
20138
21174
  let fastestDestination = undefined;
20139
21175
  let fastestTime = undefined;
@@ -20141,7 +21177,7 @@ class Result {
20141
21177
  const arrivalTime = this.earliestArrivals.get(destination.id);
20142
21178
  if (arrivalTime !== undefined) {
20143
21179
  if (fastestTime === undefined ||
20144
- arrivalTime.time.toMinutes() < fastestTime.time.toMinutes()) {
21180
+ arrivalTime.arrival.isBefore(fastestTime.arrival)) {
20145
21181
  fastestDestination = destination.id;
20146
21182
  fastestTime = arrivalTime;
20147
21183
  }
@@ -20154,9 +21190,9 @@ class Result {
20154
21190
  let currentStop = fastestDestination;
20155
21191
  let round = fastestTime.legNumber;
20156
21192
  while (fastestTime.origin !== currentStop) {
20157
- const tripLeg = (_a = this.earliestArrivalsPerRound[round]) === null || _a === undefined ? undefined : _a.get(currentStop);
20158
- if (!(tripLeg === null || tripLeg === undefined ? undefined : tripLeg.leg)) {
20159
- throw new Error(`No leg found for a trip leg: start stop=${(_c = (_b = tripLeg === null || tripLeg === undefined ? undefined : tripLeg.leg) === null || _b === undefined ? undefined : _b.from.id) !== null && _c !== undefined ? _c : 'unknown'}, end stop=${currentStop}, round=${round}, origin=${fastestTime.origin}`);
21193
+ const tripLeg = (_a = this.earliestArrivalsPerRound[round]) === null || _a === void 0 ? void 0 : _a.get(currentStop);
21194
+ if (!(tripLeg === null || tripLeg === void 0 ? void 0 : tripLeg.leg)) {
21195
+ throw new Error(`No leg found for a trip leg: start stop=${(_c = (_b = tripLeg === null || tripLeg === void 0 ? void 0 : tripLeg.leg) === null || _b === void 0 ? void 0 : _b.from.id) !== null && _c !== void 0 ? _c : 'unknown'}, end stop=${currentStop}, round=${round}, origin=${fastestTime.origin}`);
20160
21196
  }
20161
21197
  route.unshift(tripLeg.leg);
20162
21198
  currentStop = tripLeg.leg.from.id;
@@ -20178,13 +21214,13 @@ class Result {
20178
21214
  let earliestArrival = undefined;
20179
21215
  const relevantArrivals = maxTransfers !== undefined
20180
21216
  ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
20181
- this.earliestArrivalsPerRound[maxTransfers - 1]
21217
+ this.earliestArrivalsPerRound[maxTransfers + 1]
20182
21218
  : this.earliestArrivals;
20183
21219
  for (const equivalentStop of equivalentStops) {
20184
21220
  const arrivalTime = relevantArrivals.get(equivalentStop.id);
20185
21221
  if (arrivalTime !== undefined) {
20186
21222
  if (earliestArrival === undefined ||
20187
- arrivalTime.time.toMinutes() < earliestArrival.time.toMinutes()) {
21223
+ arrivalTime.arrival.isBefore(earliestArrival.arrival)) {
20188
21224
  earliestArrival = arrivalTime;
20189
21225
  }
20190
21226
  }
@@ -20194,6 +21230,12 @@ class Result {
20194
21230
  }
20195
21231
 
20196
21232
  const UNREACHED = Time.infinity();
21233
+ /**
21234
+ * A public transportation network router utilizing the RAPTOR algorithm for
21235
+ * efficient journey planning and routing. For more information on the RAPTOR
21236
+ * algorithm, refer to its detailed explanation in the research paper:
21237
+ * https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/raptor_alenex.pdf
21238
+ */
20197
21239
  class Router {
20198
21240
  constructor(timetable, stopsIndex) {
20199
21241
  this.timetable = timetable;
@@ -20205,10 +21247,18 @@ class Router {
20205
21247
  * stops that can be reached through these transfers.
20206
21248
  */
20207
21249
  considerTransfers(query, markedStops, arrivalsAtCurrentRound, earliestArrivals, round) {
20208
- var _a, _b, _c, _d;
21250
+ var _a, _b;
20209
21251
  const { options } = query;
20210
21252
  const newlyMarkedStops = new Set();
20211
21253
  for (const stop of markedStops) {
21254
+ const currentArrival = arrivalsAtCurrentRound.get(stop);
21255
+ if (!currentArrival)
21256
+ continue;
21257
+ // Skip transfers if the last leg was also a transfer
21258
+ const previousLeg = currentArrival.leg;
21259
+ if (previousLeg && !('route' in previousLeg)) {
21260
+ continue;
21261
+ }
20212
21262
  for (const transfer of this.timetable.getTransfers(stop)) {
20213
21263
  let transferTime;
20214
21264
  if (transfer.minTransferTime) {
@@ -20220,14 +21270,12 @@ class Router {
20220
21270
  else {
20221
21271
  transferTime = options.minTransferTime;
20222
21272
  }
20223
- const arrivalAfterTransfer = arrivalsAtCurrentRound
20224
- .get(stop)
20225
- .time.plus(transferTime);
20226
- const originalArrival = (_b = (_a = arrivalsAtCurrentRound.get(transfer.destination)) === null || _a === undefined ? undefined : _a.time) !== null && _b !== undefined ? _b : UNREACHED;
20227
- if (arrivalAfterTransfer.toMinutes() < originalArrival.toMinutes()) {
20228
- const origin = (_d = (_c = arrivalsAtCurrentRound.get(stop)) === null || _c === undefined ? undefined : _c.origin) !== null && _d !== undefined ? _d : stop;
21273
+ const arrivalAfterTransfer = currentArrival.arrival.plus(transferTime);
21274
+ const originalArrival = (_b = (_a = arrivalsAtCurrentRound.get(transfer.destination)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
21275
+ if (arrivalAfterTransfer.isBefore(originalArrival)) {
21276
+ const origin = currentArrival.origin;
20229
21277
  arrivalsAtCurrentRound.set(transfer.destination, {
20230
- time: arrivalAfterTransfer,
21278
+ arrival: arrivalAfterTransfer,
20231
21279
  legNumber: round,
20232
21280
  origin: origin,
20233
21281
  leg: {
@@ -20238,7 +21286,7 @@ class Router {
20238
21286
  },
20239
21287
  });
20240
21288
  earliestArrivals.set(transfer.destination, {
20241
- time: arrivalAfterTransfer,
21289
+ arrival: arrivalAfterTransfer,
20242
21290
  legNumber: round,
20243
21291
  origin: origin,
20244
21292
  });
@@ -20250,6 +21298,22 @@ class Router {
20250
21298
  markedStops.add(newStop);
20251
21299
  }
20252
21300
  }
21301
+ /**
21302
+ * Finds the earliest arrival time at any stop from a given set of destinations.
21303
+ *
21304
+ * @param earliestArrivals A map of stops to their earliest reaching times.
21305
+ * @param destinations An array of destination stops to evaluate.
21306
+ * @returns The earliest arrival time among the provided destinations.
21307
+ */
21308
+ earliestArrivalAtAnyStop(earliestArrivals, destinations) {
21309
+ var _a, _b;
21310
+ let earliestArrivalAtAnyDestination = UNREACHED;
21311
+ for (const destination of destinations) {
21312
+ const arrival = (_b = (_a = earliestArrivals.get(destination.id)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
21313
+ earliestArrivalAtAnyDestination = Time.min(earliestArrivalAtAnyDestination, arrival);
21314
+ }
21315
+ return earliestArrivalAtAnyDestination;
21316
+ }
20253
21317
  /**
20254
21318
  * The main Raptor algorithm implementation.
20255
21319
  *
@@ -20257,12 +21321,12 @@ class Router {
20257
21321
  * @returns A result object containing data structures allowing to reconstruct routes and .
20258
21322
  */
20259
21323
  route(query) {
20260
- var _a, _b, _c, _d, _e, _f, _g;
21324
+ var _a, _b, _c, _d, _e;
20261
21325
  const { from, to, departureTime, options } = query;
20262
21326
  // Consider children or siblings of the "from" stop as potential origins
20263
21327
  const origins = this.stopsIndex.equivalentStops(from);
20264
21328
  // Consider children or siblings of the "to" stop(s) as potential destinations
20265
- const destinations = to.flatMap((destination) => this.stopsIndex.equivalentStops(destination));
21329
+ const destinations = Array.from(to).flatMap((destination) => this.stopsIndex.equivalentStops(destination));
20266
21330
  const earliestArrivals = new Map();
20267
21331
  const earliestArrivalsWithoutAnyLeg = new Map();
20268
21332
  const earliestArrivalsPerRound = [earliestArrivalsWithoutAnyLeg];
@@ -20271,12 +21335,12 @@ class Router {
20271
21335
  for (const originStop of origins) {
20272
21336
  markedStops.add(originStop.id);
20273
21337
  earliestArrivals.set(originStop.id, {
20274
- time: departureTime,
21338
+ arrival: departureTime,
20275
21339
  legNumber: 0,
20276
21340
  origin: originStop.id,
20277
21341
  });
20278
21342
  earliestArrivalsWithoutAnyLeg.set(originStop.id, {
20279
- time: departureTime,
21343
+ arrival: departureTime,
20280
21344
  legNumber: 0,
20281
21345
  origin: originStop.id,
20282
21346
  });
@@ -20292,38 +21356,19 @@ class Router {
20292
21356
  const reachableRoutes = this.timetable.findReachableRoutes(markedStops, options.transportModes);
20293
21357
  markedStops.clear();
20294
21358
  // for each route that can be reached with at least round - 1 trips
20295
- for (const [routeId, hopOnStop] of reachableRoutes.entries()) {
20296
- const route = this.timetable.getRoute(routeId);
21359
+ for (const [route, hopOnStop] of reachableRoutes.entries()) {
20297
21360
  let currentTrip = undefined;
20298
- const hopOnIndex = route.stopIndices.get(hopOnStop);
20299
- // for each stop in the route starting with the hop-on one
20300
- for (let i = hopOnIndex; i < route.stops.length; i++) {
20301
- const currentStop = route.stops[i];
20302
- const stopNumbers = route.stops.length;
21361
+ for (const currentStop of route.stopsIterator(hopOnStop)) {
20303
21362
  if (currentTrip !== undefined) {
20304
- const currentArrivalIndex = (currentTrip.trip * stopNumbers + i) * 2;
20305
- const currentArrivalTime = Time.fromMinutes(route.stopTimes[currentArrivalIndex]);
20306
- const currentDropOffType = route.pickUpDropOffTypes[i * 2 + 1];
20307
- const earliestArrivalAtCurrentStop = (_b = (_a = earliestArrivals.get(currentStop)) === null || _a === undefined ? undefined : _a.time) !== null && _b !== undefined ? _b : UNREACHED;
20308
- let arrivalToImprove = earliestArrivalAtCurrentStop;
20309
- if (destinations.length > 0) {
20310
- const earliestArrivalsAtDestinations = [];
20311
- // if multiple destinations are specified, the target pruning
20312
- // should compare to the earliest arrival at any of them
20313
- for (const destinationStop of destinations) {
20314
- const earliestArrivalAtDestination = (_d = (_c = earliestArrivals.get(destinationStop.id)) === null || _c === undefined ? undefined : _c.time) !== null && _d !== undefined ? _d : UNREACHED;
20315
- earliestArrivalsAtDestinations.push(earliestArrivalAtDestination);
20316
- }
20317
- const earliestArrivalAtDestination = Time.min(...earliestArrivalsAtDestinations);
20318
- arrivalToImprove = Time.min(earliestArrivalAtCurrentStop, earliestArrivalAtDestination);
20319
- }
20320
- if (currentDropOffType !== NOT_AVAILABLE &&
20321
- currentArrivalTime.toMinutes() < arrivalToImprove.toMinutes()) {
20322
- const bestHopOnStopIndex = route.stopIndices.get(currentTrip.bestHopOnStop);
20323
- const bestHopOnStopDepartureIndex = currentTrip.trip * stopNumbers * 2 + bestHopOnStopIndex * 2 + 1;
20324
- const bestHopOnDepartureTime = Time.fromMinutes(route.stopTimes[bestHopOnStopDepartureIndex]);
21363
+ const currentArrivalTime = route.arrivalAt(currentStop, currentTrip.tripIndex);
21364
+ const currentDropOffType = route.dropOffTypeAt(currentStop, currentTrip.tripIndex);
21365
+ const earliestArrivalAtCurrentStop = (_b = (_a = earliestArrivals.get(currentStop)) === null || _a === void 0 ? void 0 : _a.arrival) !== null && _b !== void 0 ? _b : UNREACHED;
21366
+ if (currentDropOffType !== 'NOT_AVAILABLE' &&
21367
+ currentArrivalTime.isBefore(earliestArrivalAtCurrentStop) &&
21368
+ currentArrivalTime.isBefore(this.earliestArrivalAtAnyStop(earliestArrivals, destinations))) {
21369
+ const bestHopOnDepartureTime = route.departureFrom(currentTrip.bestHopOnStop, currentTrip.tripIndex);
20325
21370
  arrivalsAtCurrentRound.set(currentStop, {
20326
- time: currentArrivalTime,
21371
+ arrival: currentArrivalTime,
20327
21372
  legNumber: round,
20328
21373
  origin: currentTrip.origin,
20329
21374
  leg: {
@@ -20331,11 +21376,11 @@ class Router {
20331
21376
  to: this.stopsIndex.findStopById(currentStop),
20332
21377
  departureTime: bestHopOnDepartureTime,
20333
21378
  arrivalTime: currentArrivalTime,
20334
- route: this.timetable.getServiceRoute(route.serviceRouteId),
21379
+ route: this.timetable.getServiceRoute(route),
20335
21380
  },
20336
21381
  });
20337
21382
  earliestArrivals.set(currentStop, {
20338
- time: currentArrivalTime,
21383
+ arrival: currentArrivalTime,
20339
21384
  legNumber: round,
20340
21385
  origin: currentTrip.origin,
20341
21386
  });
@@ -20344,18 +21389,18 @@ class Router {
20344
21389
  }
20345
21390
  // check if we can catch a previous trip at the current stop
20346
21391
  // if there was no current trip, find the first one reachable
20347
- const earliestArrivalOnPreviousRound = (_e = arrivalsAtPreviousRound.get(currentStop)) === null || _e === undefined ? undefined : _e.time;
21392
+ const earliestArrivalOnPreviousRound = (_c = arrivalsAtPreviousRound.get(currentStop)) === null || _c === void 0 ? void 0 : _c.arrival;
20348
21393
  if (earliestArrivalOnPreviousRound !== undefined &&
20349
21394
  (currentTrip === undefined ||
20350
- earliestArrivalOnPreviousRound.toMinutes() <=
20351
- route.stopTimes[(currentTrip.trip * stopNumbers + i) * 2])) {
20352
- const earliestTrip = this.timetable.findEarliestTrip(route, currentStop, currentTrip === null || currentTrip === undefined ? undefined : currentTrip.trip, earliestArrivalOnPreviousRound);
21395
+ earliestArrivalOnPreviousRound.isBefore(route.arrivalAt(currentStop, currentTrip.tripIndex)) ||
21396
+ earliestArrivalOnPreviousRound.equals(route.arrivalAt(currentStop, currentTrip.tripIndex)))) {
21397
+ const earliestTrip = route.findEarliestTrip(currentStop, earliestArrivalOnPreviousRound, currentTrip === null || currentTrip === void 0 ? void 0 : currentTrip.tripIndex);
20353
21398
  if (earliestTrip !== undefined) {
20354
21399
  currentTrip = {
20355
- trip: earliestTrip,
21400
+ tripIndex: earliestTrip,
20356
21401
  // we need to keep track of the best hop-on stop to reconstruct the route at the end
20357
21402
  bestHopOnStop: currentStop,
20358
- origin: (_g = (_f = arrivalsAtPreviousRound.get(currentStop)) === null || _f === undefined ? undefined : _f.origin) !== null && _g !== undefined ? _g : currentStop,
21403
+ origin: (_e = (_d = arrivalsAtPreviousRound.get(currentStop)) === null || _d === void 0 ? void 0 : _d.origin) !== null && _e !== void 0 ? _e : currentStop,
20359
21404
  };
20360
21405
  }
20361
21406
  }
@@ -20369,31 +21414,97 @@ class Router {
20369
21414
  }
20370
21415
  }
20371
21416
 
20372
- const prettyPrintRoute = (route) => {
20373
- route.legs.forEach((leg, index) => {
20374
- var _a;
20375
- const fromStop = `From: ${leg.from.name}${leg.from.platform ? ' (Pl. ' + leg.from.platform + ')' : ''}`;
20376
- const toStop = `To: ${leg.to.name}${leg.to.platform ? ' (Pl. ' + leg.to.platform + ')' : ''}`;
20377
- let transferDetails = '';
20378
- let travelDetails = '';
20379
- if ('minTransferTime' in leg) {
20380
- transferDetails = `Minimum Transfer Time: ${(_a = leg.minTransferTime) === null || _a === undefined ? undefined : _a.toString()}`;
20381
- }
20382
- if ('route' in leg && 'departureTime' in leg && 'arrivalTime' in leg) {
20383
- travelDetails = `Route: ${leg.route.type} ${leg.route.name}, Departure: ${leg.departureTime.toString()}, Arrival: ${leg.arrivalTime.toString()}`;
21417
+ /**
21418
+ *
21419
+ * @param filePath
21420
+ * @returns
21421
+ */
21422
+ const loadQueriesFromJson = (filePath) => {
21423
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
21424
+ const serializedQueries = JSON.parse(fileContent);
21425
+ return serializedQueries.map((serializedQuery) => {
21426
+ const queryBuilder = new Query.Builder()
21427
+ .from(serializedQuery.from)
21428
+ .to(new Set(serializedQuery.to))
21429
+ .departureTime(Time.fromString(serializedQuery.departureTime));
21430
+ if (serializedQuery.maxTransfers !== undefined) {
21431
+ queryBuilder.maxTransfers(serializedQuery.maxTransfers);
20384
21432
  }
20385
- console.log(`Leg ${index + 1}:`);
20386
- console.log(` ${fromStop}`);
20387
- console.log(` ${toStop}`);
20388
- if (transferDetails) {
20389
- console.log(` ${transferDetails}`);
21433
+ return queryBuilder.build();
21434
+ });
21435
+ };
21436
+ /**
21437
+ *
21438
+ * @param router
21439
+ * @param stopsIndex
21440
+ * @param tasks
21441
+ * @param iterations
21442
+ * @returns
21443
+ */
21444
+ const testRouterPerformance = (router, stopsIndex, tasks, iterations) => {
21445
+ const results = [];
21446
+ for (const task of tasks) {
21447
+ const fromStop = stopsIndex.findStopBySourceStopId(task.from);
21448
+ const toStops = Array.from(task.to).map((stopId) => stopsIndex.findStopBySourceStopId(stopId));
21449
+ if (!fromStop || toStops.some((toStop) => !toStop)) {
21450
+ throw new Error(`Invalid task: Start or end station not found for task ${JSON.stringify(task)}`);
20390
21451
  }
20391
- if (travelDetails) {
20392
- console.log(` ${travelDetails}`);
21452
+ let totalTime = 0;
21453
+ let totalMemory = 0;
21454
+ for (let i = 0; i < iterations; i++) {
21455
+ if (global.gc) {
21456
+ global.gc();
21457
+ }
21458
+ const startMemory = process.memoryUsage().heapUsed;
21459
+ const startTime = performance.now();
21460
+ router.route(task);
21461
+ const endTime = performance.now();
21462
+ const endMemory = process.memoryUsage().heapUsed;
21463
+ totalTime += endTime - startTime;
21464
+ if (endMemory >= startMemory) {
21465
+ totalMemory += endMemory - startMemory;
21466
+ }
20393
21467
  }
21468
+ results.push({
21469
+ task,
21470
+ meanTimeMs: totalTime / iterations,
21471
+ meanMemoryMb: totalMemory / iterations / (1024 * 1024),
21472
+ });
21473
+ }
21474
+ return results;
21475
+ };
21476
+ /**
21477
+ *
21478
+ * @param results
21479
+ * @returns
21480
+ */
21481
+ const prettyPrintPerformanceResults = (results) => {
21482
+ if (results.length === 0) {
21483
+ console.log('No performance results to display.');
21484
+ return;
21485
+ }
21486
+ const overallMeanTimeMs = results.reduce((sum, result) => sum + result.meanTimeMs, 0) /
21487
+ results.length;
21488
+ const overallMeanMemoryMb = results.reduce((sum, result) => sum + result.meanMemoryMb, 0) /
21489
+ results.length;
21490
+ console.log('Overall Performance Results:');
21491
+ console.log(` Mean Time (ms): ${overallMeanTimeMs.toFixed(2)}`);
21492
+ console.log(` Mean Memory (MB): ${overallMeanMemoryMb.toFixed(2)}`);
21493
+ console.log('');
21494
+ console.log('Individual Task Results:');
21495
+ results.forEach((result, index) => {
21496
+ console.log(`Task ${index + 1}:`);
21497
+ console.log(` Mean Time (ms): ${result.meanTimeMs.toFixed(2)}`);
21498
+ console.log(` Mean Memory (MB): ${result.meanMemoryMb.toFixed(2)}`);
20394
21499
  console.log('');
20395
21500
  });
20396
21501
  };
21502
+
21503
+ /**
21504
+ * Plots the graph of the result to a dot file.
21505
+ * @param result - The result object to plot.
21506
+ * @param filePath - The path where the dot file will be saved.
21507
+ */
20397
21508
  const plotGraphToDotFile = (result, filePath) => {
20398
21509
  const plotter = new Plotter(result);
20399
21510
  const dotContent = plotter.plotDotGraph();
@@ -20414,9 +21525,22 @@ const startRepl = (stopsPath, timetablePath) => {
20414
21525
  help: 'Find stops by name using .find <query>',
20415
21526
  action(query) {
20416
21527
  this.clearBufferedCommand();
20417
- const stops = stopsIndex.findStopsByName(query);
21528
+ let stops = [];
21529
+ const stopBySourceId = stopsIndex.findStopBySourceStopId(query);
21530
+ if (stopBySourceId !== undefined) {
21531
+ stops.push(stopBySourceId);
21532
+ }
21533
+ else if (!isNaN(Number(query))) {
21534
+ const stopById = stopsIndex.findStopById(Number(query));
21535
+ if (stopById !== undefined) {
21536
+ stops.push(stopById);
21537
+ }
21538
+ }
21539
+ else {
21540
+ stops = stopsIndex.findStopsByName(query);
21541
+ }
20418
21542
  stops.forEach((stop) => {
20419
- console.log(`${stop.name} (${stop.id})`);
21543
+ console.log(`${stop.name} (${stop.sourceStopId} - ${stop.id})`);
20420
21544
  });
20421
21545
  this.displayPrompt();
20422
21546
  },
@@ -20439,17 +21563,21 @@ const startRepl = (stopsPath, timetablePath) => {
20439
21563
  const fromIndex = parts.indexOf('from');
20440
21564
  const toIndex = parts.indexOf('to');
20441
21565
  const fromId = parts.slice(fromIndex + 1, toIndex).join(' ');
20442
- const toId = parts
20443
- .slice(toIndex + 1, withTransfersIndex === -1 ? parts.indexOf('at') : parts.indexOf('at'))
20444
- .join(' ');
21566
+ const toId = parts.slice(toIndex + 1, parts.indexOf('at')).join(' ');
20445
21567
  if (!fromId || !toId || !atTime) {
20446
21568
  console.log('Usage: .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]');
20447
21569
  this.displayPrompt();
20448
21570
  return;
20449
21571
  }
20450
21572
  const fromStop = stopsIndex.findStopBySourceStopId(fromId) ||
21573
+ (isNaN(Number(fromId))
21574
+ ? undefined
21575
+ : stopsIndex.findStopById(Number(fromId))) ||
20451
21576
  stopsIndex.findStopsByName(fromId)[0];
20452
21577
  const toStop = stopsIndex.findStopBySourceStopId(toId) ||
21578
+ (isNaN(Number(toId))
21579
+ ? undefined
21580
+ : stopsIndex.findStopById(Number(toId))) ||
20453
21581
  stopsIndex.findStopsByName(toId)[0];
20454
21582
  if (!fromStop) {
20455
21583
  console.log(`No stop found for 'from' ID or name: ${fromId}`);
@@ -20465,7 +21593,7 @@ const startRepl = (stopsPath, timetablePath) => {
20465
21593
  try {
20466
21594
  const query = new Query.Builder()
20467
21595
  .from(fromStop.sourceStopId)
20468
- .to([toStop.sourceStopId])
21596
+ .to(toStop.sourceStopId)
20469
21597
  .departureTime(departureTime)
20470
21598
  .maxTransfers(maxTransfers)
20471
21599
  .build();
@@ -20476,12 +21604,13 @@ const startRepl = (stopsPath, timetablePath) => {
20476
21604
  console.log(`Destination not reachable`);
20477
21605
  }
20478
21606
  else {
20479
- console.log(`Arriving to ${toStop.name} at ${arrivalTime.time.toString()} with ${arrivalTime.legNumber - 1} transfers from ${(_a = stopsIndex.findStopById(arrivalTime.origin)) === null || _a === void 0 ? void 0 : _a.name}.`);
21607
+ console.log(`Arriving to ${toStop.name} at ${arrivalTime.arrival.toString()} with ${arrivalTime.legNumber - 1} transfers from ${(_a = stopsIndex.findStopById(arrivalTime.origin)) === null || _a === void 0 ? void 0 : _a.name}.`);
20480
21608
  }
20481
21609
  const bestRoute = result.bestRoute(toStop.sourceStopId);
20482
21610
  if (bestRoute) {
20483
21611
  console.log(`Found route from ${fromStop.name} to ${toStop.name}:`);
20484
- prettyPrintRoute(bestRoute);
21612
+ console.log(bestRoute.toString());
21613
+ console.log(JSON.stringify(bestRoute.asJson(), null, 2));
20485
21614
  }
20486
21615
  else {
20487
21616
  console.log('No route found');
@@ -20543,7 +21672,7 @@ const startRepl = (stopsPath, timetablePath) => {
20543
21672
  try {
20544
21673
  const query = new Query.Builder()
20545
21674
  .from(fromStop.sourceStopId)
20546
- .to([toStop.sourceStopId])
21675
+ .to(toStop.sourceStopId)
20547
21676
  .departureTime(departureTime)
20548
21677
  .maxTransfers(maxTransfers)
20549
21678
  .build();
@@ -20576,7 +21705,7 @@ program
20576
21705
  .option('-s, --stopsOutputPath <path>', 'Path to output stops file', '/tmp/stops')
20577
21706
  .option('-p, --profileName <name>', 'Profile name for GTFS config', 'CH')
20578
21707
  .option('-v, --verbose', 'Verbose mode', false)
20579
- .action((gtfsPath, options) => __awaiter(undefined, undefined, undefined, function* () {
21708
+ .action((gtfsPath, options) => __awaiter(void 0, void 0, void 0, function* () {
20580
21709
  if (options.verbose) {
20581
21710
  log.setDefaultLevel(log.levels.INFO);
20582
21711
  }
@@ -20590,12 +21719,12 @@ program
20590
21719
  }));
20591
21720
  program
20592
21721
  .command('parse-stops')
20593
- .description('Parse a GTFS feed and output a timetable and stops file.')
21722
+ .description('Parse a GTFS feed and output a stops file.')
20594
21723
  .argument('<gtfsPath>', 'Path to GTFS data')
20595
21724
  .option('-s, --outputPath <path>', 'Path to output stops file', '/tmp/stops')
20596
21725
  .option('-p, --profileName <name>', 'Profile name for GTFS config', 'CH')
20597
21726
  .option('-v, --verbose', 'Verbose mode', false)
20598
- .action((gtfsPath, options) => __awaiter(undefined, undefined, undefined, function* () {
21727
+ .action((gtfsPath, options) => __awaiter(void 0, void 0, void 0, function* () {
20599
21728
  if (options.verbose) {
20600
21729
  log.setDefaultLevel(log.levels.INFO);
20601
21730
  }
@@ -20614,5 +21743,20 @@ program
20614
21743
  .action((options) => {
20615
21744
  startRepl(options.stopsPath, options.timetablePath);
20616
21745
  });
21746
+ program
21747
+ .command('perf')
21748
+ .description('Evaluate the performance of the router on a set of routes.')
21749
+ .argument('<routesPath>', 'Path to the JSON file containing the routes')
21750
+ .option('-s, --stopsPath <path>', 'Path to the stops file', '/tmp/stops')
21751
+ .option('-t, --timetablePath <path>', 'Path to the timetable file', '/tmp/timetable')
21752
+ .option('-i, --iterations <number>', 'Number of iterations for performance tests', '20')
21753
+ .action((routesPath, options) => {
21754
+ const stopsIndex = StopsIndex.fromData(fs.readFileSync(options.stopsPath));
21755
+ const timetable = Timetable.fromData(fs.readFileSync(options.timetablePath));
21756
+ const router = new Router(timetable, stopsIndex);
21757
+ const queries = loadQueriesFromJson(routesPath);
21758
+ const performanceResults = testRouterPerformance(router, stopsIndex, queries, parseInt(options.iterations, 10));
21759
+ prettyPrintPerformanceResults(performanceResults);
21760
+ });
20617
21761
  program.parse(process.argv);
20618
21762
  //# sourceMappingURL=cli.mjs.map