From 1483805f13151dab648188512b3f126f03fad9e5 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Sun, 29 Mar 2026 23:19:34 +0100 Subject: [PATCH] fix: handle empty lines array in TFL source (#106) Empty lines array caused fetchLineStatuses to build /Line//Status URL, resulting in a 404 from the TFL API. Now defaults to all lines when the array is empty. Also switches fetchStations to Promise.allSettled so individual line failures don't break the entire station fetch. Co-authored-by: Ona --- packages/aelis-source-tfl/src/tfl-api.ts | 24 +++++++++++++++------ packages/aelis-source-tfl/src/tfl-source.ts | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/aelis-source-tfl/src/tfl-api.ts b/packages/aelis-source-tfl/src/tfl-api.ts index e2f7e98..7b726aa 100644 --- a/packages/aelis-source-tfl/src/tfl-api.ts +++ b/packages/aelis-source-tfl/src/tfl-api.ts @@ -69,7 +69,7 @@ export class TflApi { } async fetchLineStatuses(lines?: TflLineId[]): Promise { - const lineIds = lines ?? ALL_LINE_IDS + const lineIds = lines?.length ? lines : ALL_LINE_IDS const data = await this.fetch(`/Line/${lineIds.join(",")}/Status`) const parsed = lineResponseArray(data) @@ -101,8 +101,8 @@ export class TflApi { return this.stationsCache } - // Fetch stations for all lines in parallel - const responses = await Promise.all( + // Fetch stations for all lines in parallel, tolerating individual failures + const results = await Promise.allSettled( ALL_LINE_IDS.map(async (id) => { const data = await this.fetch(`/Line/${id}/StopPoints`) const parsed = lineStopPointsArray(data) @@ -116,7 +116,12 @@ export class TflApi { // Merge stations, combining lines for shared stations const stationMap = new Map() - for (const { lineId: currentLineId, stops } of responses) { + for (const result of results) { + if (result.status === "rejected") { + continue + } + + const { lineId: currentLineId, stops } = result.value for (const stop of stops) { const existing = stationMap.get(stop.naptanId) if (existing) { @@ -135,8 +140,15 @@ export class TflApi { } } - this.stationsCache = Array.from(stationMap.values()) - return this.stationsCache + // Only cache if all requests succeeded — partial results shouldn't persist + const allSucceeded = results.every((r) => r.status === "fulfilled") + const stations = Array.from(stationMap.values()) + + if (allSucceeded) { + this.stationsCache = stations + } + + return stations } } diff --git a/packages/aelis-source-tfl/src/tfl-source.ts b/packages/aelis-source-tfl/src/tfl-source.ts index a72667b..4d4637f 100644 --- a/packages/aelis-source-tfl/src/tfl-source.ts +++ b/packages/aelis-source-tfl/src/tfl-source.ts @@ -84,7 +84,7 @@ export class TflSource implements FeedSource { throw new Error("Either client or apiKey must be provided") } this.client = options.client ?? new TflApi(options.apiKey!) - this.lines = options.lines ?? [...TflSource.DEFAULT_LINES_OF_INTEREST] + this.lines = options.lines?.length ? options.lines : [...TflSource.DEFAULT_LINES_OF_INTEREST] } async listActions(): Promise> {