@opentripplanner/core-utils 15.0.0 → 16.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/esm/index.js +3 -0
  2. package/esm/index.js.map +1 -1
  3. package/esm/itinerary.js +104 -78
  4. package/esm/itinerary.js.map +1 -1
  5. package/esm/map.js +2 -2
  6. package/esm/map.js.map +1 -1
  7. package/esm/query-gen.js +9 -5
  8. package/esm/query-gen.js.map +1 -1
  9. package/esm/route.js +26 -20
  10. package/esm/route.js.map +1 -1
  11. package/esm/storage.js +4 -1
  12. package/esm/storage.js.map +1 -1
  13. package/esm/time.js +6 -5
  14. package/esm/time.js.map +1 -1
  15. package/esm/ui.js +4 -2
  16. package/esm/ui.js.map +1 -1
  17. package/lib/index.d.ts.map +1 -1
  18. package/lib/index.js +6 -0
  19. package/lib/index.js.map +1 -1
  20. package/lib/itinerary.d.ts +20 -11
  21. package/lib/itinerary.d.ts.map +1 -1
  22. package/lib/itinerary.js +96 -85
  23. package/lib/itinerary.js.map +1 -1
  24. package/lib/map.d.ts +2 -2
  25. package/lib/map.d.ts.map +1 -1
  26. package/lib/map.js +1 -1
  27. package/lib/map.js.map +1 -1
  28. package/lib/query-gen.d.ts +1 -19
  29. package/lib/query-gen.d.ts.map +1 -1
  30. package/lib/query-gen.js +9 -5
  31. package/lib/query-gen.js.map +1 -1
  32. package/lib/route.d.ts +10 -8
  33. package/lib/route.d.ts.map +1 -1
  34. package/lib/route.js +22 -16
  35. package/lib/route.js.map +1 -1
  36. package/lib/storage.d.ts +1 -1
  37. package/lib/storage.d.ts.map +1 -1
  38. package/lib/storage.js +4 -1
  39. package/lib/storage.js.map +1 -1
  40. package/lib/time.d.ts +3 -1
  41. package/lib/time.d.ts.map +1 -1
  42. package/lib/time.js +5 -4
  43. package/lib/time.js.map +1 -1
  44. package/lib/ui.d.ts.map +1 -1
  45. package/lib/ui.js +4 -2
  46. package/lib/ui.js.map +1 -1
  47. package/package.json +9 -7
  48. package/src/__tests__/itinerary.ts +64 -6
  49. package/src/index.ts +3 -0
  50. package/src/itinerary.ts +145 -97
  51. package/src/map.ts +5 -3
  52. package/src/query-gen.ts +15 -9
  53. package/src/route.ts +65 -38
  54. package/src/storage.ts +8 -2
  55. package/src/time.ts +7 -6
  56. package/src/ui.ts +8 -6
  57. package/tsconfig.json +1 -0
  58. package/tsconfig.tsbuildinfo +1 -1
package/src/route.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { Leg, Route, TransitOperator } from "@opentripplanner/types";
1
+ import {
2
+ Leg,
3
+ Route,
4
+ TransitMode,
5
+ TransitOperator
6
+ } from "@opentripplanner/types";
2
7
  import chroma from "chroma-js";
3
8
  /**
4
9
  * Returns the transit operator (if an exact match is found) from the transit
@@ -16,7 +21,7 @@ export function getTransitOperatorFromFeedIdAndAgencyId(
16
21
  feedId: string,
17
22
  agencyId: string | number,
18
23
  transitOperators: TransitOperator[]
19
- ): TransitOperator {
24
+ ): TransitOperator | null {
20
25
  return (
21
26
  transitOperators.find(
22
27
  transitOperator =>
@@ -37,7 +42,7 @@ export function getTransitOperatorFromFeedIdAndAgencyId(
37
42
  export function getTransitOperatorFromLeg(
38
43
  leg: Leg,
39
44
  transitOperators: TransitOperator[]
40
- ): TransitOperator {
45
+ ): TransitOperator | null {
41
46
  if (!leg.routeId || !leg.agencyId) return null;
42
47
  const feedId = leg.routeId.split(":")[0];
43
48
  return getTransitOperatorFromFeedIdAndAgencyId(
@@ -60,7 +65,7 @@ export function getTransitOperatorFromLeg(
60
65
  export function getTransitOperatorFromOtpRoute(
61
66
  route: Route,
62
67
  transitOperators: TransitOperator[]
63
- ): TransitOperator {
68
+ ): TransitOperator | null {
64
69
  if (!route.id) return null;
65
70
  const feedId = route.id.split(":")[0];
66
71
  let agencyId: string | number;
@@ -108,7 +113,7 @@ const END_OF_LIST_COMPARATOR_VALUE = 999999999999;
108
113
  function getTransitOperatorComparatorValue(
109
114
  route: Route,
110
115
  transitOperators: TransitOperator[]
111
- ): number | string {
116
+ ): number | string | undefined {
112
117
  // if the transitOperators is undefined or has zero length, use the route's
113
118
  // agency name as the comparator value
114
119
  if (!transitOperators || transitOperators.length === 0) {
@@ -146,7 +151,7 @@ export function makeTransitOperatorComparator(
146
151
  return (a: Route, b: Route) => {
147
152
  const aVal = getTransitOperatorComparatorValue(a, transitOperators);
148
153
  const bVal = getTransitOperatorComparatorValue(b, transitOperators);
149
- if (typeof aVal === "string") {
154
+ if (typeof aVal === "string" && typeof bVal === "string") {
150
155
  // happens when transitOperators is undefined. Both aVal are guaranteed to
151
156
  // be strings. Make a string comparison.
152
157
  if (aVal < bVal) return -1;
@@ -163,21 +168,19 @@ export function makeTransitOperatorComparator(
163
168
  * Gets the desired sort values according to an optional getter function. If the
164
169
  * getter function is not defined, the original sort values are returned.
165
170
  */
166
- function getSortValues(
167
- getterFn: (item: unknown) => unknown,
168
- a: unknown,
169
- b: unknown
170
- ) {
171
- let aVal: unknown;
172
- let bVal: unknown;
171
+ function getSortValues<T>(a: T, b: T): { aVal: T; bVal: T };
172
+ function getSortValues<T, TValue>(
173
+ a: T,
174
+ b: T,
175
+ getterFn: (item: T) => TValue
176
+ ): { aVal: TValue; bVal: TValue };
177
+ function getSortValues<T, TValue>(a: T, b: T, getterFn?: (item: T) => TValue) {
173
178
  if (typeof getterFn === "function") {
174
- aVal = getterFn(a);
175
- bVal = getterFn(b);
176
- } else {
177
- aVal = a;
178
- bVal = b;
179
+ return { aVal: getterFn(a), bVal: getterFn(b) };
179
180
  }
180
- return { aVal, bVal };
181
+
182
+ // When no getter is provided, TValue is inferred as T via overload.
183
+ return { aVal: (a as unknown) as TValue, bVal: (b as unknown) as TValue };
181
184
  }
182
185
 
183
186
  // Lookup for the sort values associated with various OTP modes.
@@ -193,12 +196,19 @@ const modeComparatorValue = {
193
196
  CABLE_CAR: 6,
194
197
  FUNICULAR: 7,
195
198
  BUS: 8
196
- };
199
+ // eslint-disable-next-line prettier/prettier
200
+ } as const satisfies Partial<Record<TransitMode, number>>;
201
+
202
+ type SupportedTransitMode = keyof typeof modeComparatorValue;
203
+
204
+ function isSupportedTransitMode(mode: TransitMode): mode is SupportedTransitMode {
205
+ return mode in modeComparatorValue;
206
+ }
197
207
 
198
208
  // Lookup that maps route types to the OTP mode sort values.
199
209
  // Note: JSDoc format not used to avoid bug in documentationjs.
200
210
  // https://github.com/documentationjs/documentation/issues/372
201
- const routeTypeComparatorValue = {
211
+ const routeTypeComparatorValue: Record<number, number> = {
202
212
  0: modeComparatorValue.TRAM, // - Tram, Streetcar, Light rail.
203
213
  1: modeComparatorValue.SUBWAY, // - Subway, Metro.
204
214
  2: modeComparatorValue.RAIL, // - Rail. Used for intercity or long-distance travel.
@@ -222,10 +232,13 @@ function getRouteTypeComparatorValue(route: Route): number {
222
232
  // string-based modes, but the long route response returns the
223
233
  // integer route type. This attempts to account for both of those cases.
224
234
  if (!route) throw new Error(`Route is undefined. ${route}`);
225
- if (typeof modeComparatorValue[route.mode] !== "undefined") {
235
+ if (route.mode && isSupportedTransitMode(route.mode)) {
226
236
  return modeComparatorValue[route.mode];
227
237
  }
228
- if (typeof routeTypeComparatorValue[route.type] !== "undefined") {
238
+ if (
239
+ route.type &&
240
+ typeof routeTypeComparatorValue[route.type] !== "undefined"
241
+ ) {
229
242
  return routeTypeComparatorValue[route.type];
230
243
  }
231
244
  // Default the comparator value to a large number (placing the route at the
@@ -311,11 +324,18 @@ const isNullOrNaN = (val: any): boolean => {
311
324
  * @param {function} [objGetterFn] An optional function to obtain the
312
325
  * comparison value from the comparator function arguments
313
326
  */
314
- export function makeNumericValueComparator(
315
- objGetterFn?: (item: Route) => number
327
+ export function makeNumericValueComparator(): (a: number, b: number) => number;
328
+ export function makeNumericValueComparator<T>(
329
+ objGetterFn: (item: T) => number | null | undefined
330
+ ): (a: T, b: T) => number;
331
+ export function makeNumericValueComparator<T>(
332
+ objGetterFn?: (item: T) => number | null | undefined
316
333
  ) {
317
- return (a: number, b: number): number | null => {
318
- const { aVal, bVal } = getSortValues(objGetterFn, a, b);
334
+ return (a: T, b: T): number => {
335
+ const { aVal, bVal } =
336
+ typeof objGetterFn === "function"
337
+ ? getSortValues(a, b, objGetterFn)
338
+ : getSortValues(a, b);
319
339
 
320
340
  // if both values aren't valid numbers, use the next sort criteria
321
341
  if (isNullOrNaN(aVal) && isNullOrNaN(bVal)) {
@@ -328,8 +348,7 @@ export function makeNumericValueComparator(
328
348
  if (isNullOrNaN(bVal)) return -1;
329
349
 
330
350
  // a and b are valid numbers, return the sort value
331
- // @ts-expect-error We know from the checks above that both aVal and bVal are valid numbers.
332
- return aVal - bVal;
351
+ return (aVal as number) - (bVal as number);
333
352
  };
334
353
  }
335
354
 
@@ -343,11 +362,19 @@ export function makeNumericValueComparator(
343
362
  * @param {function} [objGetterFn] An optional function to obtain the
344
363
  * comparison value from the comparator function arguments
345
364
  */
346
- export function makeStringValueComparator(
347
- objGetterFn?: (item: Route) => string
365
+ export function makeStringValueComparator(): (a: string, b: string) => number;
366
+ export function makeStringValueComparator<T>(
367
+ objGetterFn: (item: T) => string | null | undefined
368
+ ): (a: T, b: T) => number;
369
+ export function makeStringValueComparator<T>(
370
+ objGetterFn?: (item: T) => string | null | undefined
348
371
  ) {
349
- return (a: string, b: string): number => {
350
- const { aVal, bVal } = getSortValues(objGetterFn, a, b);
372
+ return (a: T, b: T): number => {
373
+ const { aVal, bVal } =
374
+ typeof objGetterFn === "function"
375
+ ? getSortValues(a, b, objGetterFn)
376
+ : getSortValues(a, b);
377
+
351
378
  // both a and b are uncomparable strings, return equivalent value
352
379
  if (!aVal && !bVal) return 0;
353
380
  // a is not a comparable string, b gets priority
@@ -370,7 +397,7 @@ export function makeStringValueComparator(
370
397
  * Also see https://github.com/opentripplanner/otp-react-redux/issues/122
371
398
  * This was updated in OTP2 TO be empty by default. https://docs.opentripplanner.org/en/v2.3.0/OTP2-MigrationGuide/#:~:text=the%20Alerts-,Changes%20to%20the%20Index%20API,-Error%20handling%20is
372
399
  */
373
- export function getRouteSortOrderValue(route: Route): number {
400
+ export function getRouteSortOrderValue(route: Route): number | null {
374
401
  const isOTP1 = !!route.agencyId;
375
402
  const { sortOrder } = route;
376
403
 
@@ -388,10 +415,10 @@ export function getRouteSortOrderValue(route: Route): number {
388
415
  * returned. If all comparison functions return equivalence, then the values
389
416
  * are assumed to be equivalent.
390
417
  */
391
- export function makeMultiCriteriaSort(
392
- ...criteria: ((a: unknown, b: unknown) => number)[]
418
+ export function makeMultiCriteriaSort<T = unknown>(
419
+ ...criteria: ((a: T, b: T) => number)[]
393
420
  ) {
394
- return (a: number, b: number): number => {
421
+ return (a: T, b: T): number => {
395
422
  for (let i = 0; i < criteria.length; i++) {
396
423
  const curCriteriaComparatorValue = criteria[i](a, b);
397
424
  // if the comparison objects are not equivalent, return the value obtained
@@ -437,7 +464,7 @@ export function makeMultiCriteriaSort(
437
464
  */
438
465
  export function makeRouteComparator(
439
466
  transitOperators: TransitOperator[]
440
- ): (a: number, b: number) => number {
467
+ ): (a: Route, b: Route) => number {
441
468
  return makeMultiCriteriaSort(
442
469
  makeTransitOperatorComparator(transitOperators),
443
470
  makeNumericValueComparator(obj => getRouteSortOrderValue(obj)),
package/src/storage.ts CHANGED
@@ -16,10 +16,16 @@ export function storeItem(key: string, object: unknown): void {
16
16
  * Retrieve a javascript object at the specified key. If not found, defaults to
17
17
  * null or, the optionally provided notFoundValue.
18
18
  */
19
- export function getItem(key: string, notFoundValue: unknown = null): unknown {
20
- let itemAsString: string;
19
+ export function getItem<T>(
20
+ key: string,
21
+ notFoundValue: T | null = null
22
+ ): T | null {
23
+ let itemAsString: string | null = null;
21
24
  try {
22
25
  itemAsString = window.localStorage.getItem(`${STORAGE_PREFIX}.${key}`);
26
+ if (!itemAsString) {
27
+ return notFoundValue;
28
+ }
23
29
  const json = JSON.parse(itemAsString);
24
30
  if (json) return json;
25
31
  return notFoundValue;
package/src/time.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Config } from "@opentripplanner/types";
2
2
  import { startOfDay, add, format } from "date-fns";
3
- import { utcToZonedTime } from "date-fns-tz";
3
+ import { toZonedTime } from "date-fns-tz";
4
4
 
5
5
  // Date/time formats (per date-fns) when sending/receiving date from OTP
6
6
  // regardless of whatever the user has configured as the display format.
@@ -55,7 +55,7 @@ export function getLongDateFormat(config: Config): string {
55
55
  * Offsets a time according to the provided time options
56
56
  * and returns the result.
57
57
  */
58
- export function offsetTime(ms, options) {
58
+ export function offsetTime(ms: number, options?: { offset: number }): number {
59
59
  return ms + (options?.offset || 0);
60
60
  }
61
61
 
@@ -79,8 +79,9 @@ export function formatSecondsAfterMidnight(
79
79
  * GMT+0 if the Intl API is unavailable.
80
80
  */
81
81
  export function getUserTimezone(fallbackTimezone = "Etc/Greenwich"): string {
82
- if (process.env.NODE_ENV === "test") return process.env.TZ;
83
- return Intl?.DateTimeFormat().resolvedOptions().timeZone || fallbackTimezone;
82
+ if (process.env.NODE_ENV === "test")
83
+ return process.env.TZ ?? fallbackTimezone;
84
+ return Intl?.DateTimeFormat().resolvedOptions().timeZone ?? fallbackTimezone;
84
85
  }
85
86
 
86
87
  /**
@@ -88,7 +89,7 @@ export function getUserTimezone(fallbackTimezone = "Etc/Greenwich"): string {
88
89
  * The conversion to the user's timezone is needed for testing purposes.
89
90
  */
90
91
  export function getCurrentTime(timezone = getUserTimezone()): string {
91
- return format(utcToZonedTime(Date.now(), timezone), OTP_API_TIME_FORMAT);
92
+ return format(toZonedTime(Date.now(), timezone), OTP_API_TIME_FORMAT);
92
93
  }
93
94
 
94
95
  /**
@@ -96,5 +97,5 @@ export function getCurrentTime(timezone = getUserTimezone()): string {
96
97
  * The conversion to the user's timezone is needed for testing purposes.
97
98
  */
98
99
  export function getCurrentDate(timezone = getUserTimezone()): string {
99
- return format(utcToZonedTime(Date.now(), timezone), OTP_API_DATE_FORMAT);
100
+ return format(toZonedTime(Date.now(), timezone), OTP_API_DATE_FORMAT);
100
101
  }
package/src/ui.ts CHANGED
@@ -11,15 +11,17 @@ export function isMobile(): boolean {
11
11
  * https://github.com/conveyal/trimet-mod-otp/issues/92.
12
12
  */
13
13
  export function enableScrollForSelector(selector: string): void {
14
- const overlay = document.querySelector(selector);
15
- let clientY = null; // remember Y position on touch start
14
+ const overlay = document.querySelector<HTMLElement>(selector);
15
+ let clientY: number | null = null; // remember Y position on touch start
16
16
 
17
17
  function isOverlayTotallyScrolled(): boolean {
18
18
  // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions
19
+ if (!overlay) return false;
19
20
  return overlay.scrollHeight - overlay.scrollTop <= overlay.clientHeight;
20
21
  }
21
22
 
22
23
  function disableRubberBand(event: TouchEvent) {
24
+ if (!overlay || clientY === null) return;
23
25
  const clientYDelta = event.targetTouches[0].clientY - clientY;
24
26
 
25
27
  if (overlay.scrollTop === 0 && clientYDelta > 0) {
@@ -33,9 +35,9 @@ export function enableScrollForSelector(selector: string): void {
33
35
  }
34
36
  }
35
37
 
36
- overlay.addEventListener(
38
+ overlay?.addEventListener(
37
39
  "touchstart",
38
- function(event: TouchEvent) {
40
+ (event: TouchEvent) => {
39
41
  if (event.targetTouches.length === 1) {
40
42
  // detect single touch
41
43
  clientY = event.targetTouches[0].clientY;
@@ -44,9 +46,9 @@ export function enableScrollForSelector(selector: string): void {
44
46
  false
45
47
  );
46
48
 
47
- overlay.addEventListener(
49
+ overlay?.addEventListener(
48
50
  "touchmove",
49
- function(event: TouchEvent) {
51
+ (event: TouchEvent) => {
50
52
  if (event.targetTouches.length === 1) {
51
53
  // detect single touch
52
54
  disableRubberBand(event);
package/tsconfig.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
+ "strict": true,
4
5
  "composite": true,
5
6
  "target": "es2019",
6
7
  "outDir": "./lib",