refactor enrichStations, only load on first request

This commit is contained in:
Traines 2025-02-13 22:16:22 +00:00
parent 6ff406ea79
commit 71d1a4f1a9
6 changed files with 66 additions and 56 deletions

View file

@ -45,15 +45,23 @@ const loadEnrichedStationData = (profile) => new Promise((resolve, reject) => {
}); });
}); });
const applyEnrichedStationData = async (ctx, shouldLoadEnrichedStationData) => {
const {profile, common} = ctx;
if (shouldLoadEnrichedStationData && !common.locations) {
const locations = await loadEnrichedStationData(profile);
common.locations = locations;
}
};
const createClient = (profile, userAgent, opt = {}) => { const createClient = (profile, userAgent, opt = {}) => {
profile = Object.assign({}, defaultProfile, profile); profile = Object.assign({}, defaultProfile, profile);
validateProfile(profile); validateProfile(profile);
const common = {}; const common = {};
if (opt.enrichStations !== false) { let shouldLoadEnrichedStationData = false;
loadEnrichedStationData(profile) if (typeof opt.enrichStations === 'function') {
.then(locations => { profile.enrichStation = opt.enrichStations;
common.locations = locations; } else if (opt.enrichStations !== false) {
}); shouldLoadEnrichedStationData = true;
} }
if ('string' !== typeof userAgent) { if ('string' !== typeof userAgent) {
@ -64,6 +72,7 @@ const createClient = (profile, userAgent, opt = {}) => {
} }
const _stationBoard = async (station, type, resultsField, parse, opt = {}) => { const _stationBoard = async (station, type, resultsField, parse, opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
if (isObj(station) && station.id) { if (isObj(station) && station.id) {
station = station.id; station = station.id;
} else if ('string' !== typeof station) { } else if ('string' !== typeof station) {
@ -129,6 +138,7 @@ const createClient = (profile, userAgent, opt = {}) => {
}; };
const journeys = async (from, to, opt = {}) => { const journeys = async (from, to, opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
if ('earlierThan' in opt && 'laterThan' in opt) { if ('earlierThan' in opt && 'laterThan' in opt) {
throw new TypeError('opt.earlierThan and opt.laterThan are mutually exclusive.'); throw new TypeError('opt.earlierThan and opt.laterThan are mutually exclusive.');
} }
@ -211,6 +221,8 @@ const createClient = (profile, userAgent, opt = {}) => {
}; };
const refreshJourney = async (refreshToken, opt = {}) => { const refreshJourney = async (refreshToken, opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
if ('string' !== typeof refreshToken || !refreshToken) { if ('string' !== typeof refreshToken || !refreshToken) {
throw new TypeError('refreshToken must be a non-empty string.'); throw new TypeError('refreshToken must be a non-empty string.');
} }
@ -237,6 +249,8 @@ const createClient = (profile, userAgent, opt = {}) => {
}; };
const locations = async (query, opt = {}) => { const locations = async (query, opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
if (!isNonEmptyString(query)) { if (!isNonEmptyString(query)) {
throw new TypeError('query must be a non-empty string.'); throw new TypeError('query must be a non-empty string.');
} }
@ -263,6 +277,8 @@ const createClient = (profile, userAgent, opt = {}) => {
}; };
const stop = async (stop, opt = {}) => { const stop = async (stop, opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
if (isObj(stop) && stop.id) { if (isObj(stop) && stop.id) {
stop = stop.id; stop = stop.id;
} else if ('string' !== typeof stop) { } else if ('string' !== typeof stop) {
@ -284,6 +300,8 @@ const createClient = (profile, userAgent, opt = {}) => {
}; };
const nearby = async (location, opt = {}) => { const nearby = async (location, opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
validateLocation(location, 'location'); validateLocation(location, 'location');
opt = Object.assign({ opt = Object.assign({
@ -314,6 +332,8 @@ const createClient = (profile, userAgent, opt = {}) => {
}; };
const trip = async (id, opt = {}) => { const trip = async (id, opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
if (!isNonEmptyString(id)) { if (!isNonEmptyString(id)) {
throw new TypeError('id must be a non-empty string.'); throw new TypeError('id must be a non-empty string.');
} }
@ -341,6 +361,8 @@ const createClient = (profile, userAgent, opt = {}) => {
// todo [breaking]: rename to trips()? // todo [breaking]: rename to trips()?
const tripsByName = async (_lineNameOrFahrtNr = '*', _opt = {}) => { const tripsByName = async (_lineNameOrFahrtNr = '*', _opt = {}) => {
await applyEnrichedStationData({profile, common}, shouldLoadEnrichedStationData);
throw new Error('not implemented'); throw new Error('not implemented');
}; };
@ -367,4 +389,5 @@ const createClient = (profile, userAgent, opt = {}) => {
export { export {
createClient, createClient,
loadEnrichedStationData,
}; };

View file

@ -16,7 +16,7 @@ import {parseTrip} from '../parse/trip.js';
import {parseJourneyLeg} from '../parse/journey-leg.js'; import {parseJourneyLeg} from '../parse/journey-leg.js';
import {parseJourney} from '../parse/journey.js'; import {parseJourney} from '../parse/journey.js';
import {parseLine} from '../parse/line.js'; import {parseLine} from '../parse/line.js';
import {parseLocation} from '../parse/location.js'; import {parseLocation, enrichStation} from '../parse/location.js';
import {parsePolyline} from '../parse/polyline.js'; import {parsePolyline} from '../parse/polyline.js';
import {parseOperator} from '../parse/operator.js'; import {parseOperator} from '../parse/operator.js';
import {parseRemarks, parseCancelled} from '../parse/remarks.js'; import {parseRemarks, parseCancelled} from '../parse/remarks.js';
@ -82,6 +82,7 @@ const defaultProfile = {
parseLine, parseLine,
parseStationName: id, parseStationName: id,
parseLocation, parseLocation,
enrichStation,
parsePolyline, parsePolyline,
parseOperator, parseOperator,
parseRemarks, parseRemarks,

View file

@ -14,7 +14,7 @@ const parseLocation = (ctx, l) => {
} }
const lid = parse(l.id || l.locationId, {delimiter: '@'}); const lid = parse(l.id || l.locationId, {delimiter: '@'});
const res = { let res = {
type: 'location', type: 'location',
id: (l.extId || l.evaNr || lid.L || l.evaNumber || l.evaNo || l.bahnhofsId || '').replace(leadingZeros, '') || null, id: (l.extId || l.evaNr || lid.L || l.evaNumber || l.evaNo || l.bahnhofsId || '').replace(leadingZeros, '') || null,
}; };
@ -46,13 +46,7 @@ const parseLocation = (ctx, l) => {
stop.products = profile.parseProducts(ctx, l.products); stop.products = profile.parseProducts(ctx, l.products);
} }
if (common && common.locations && common.locations[stop.id]) { stop = profile.enrichStation(ctx, stop);
delete stop.type;
stop = {
...common.locations[stop.id],
...stop,
};
}
// TODO isMeta // TODO isMeta
// TODO entrances, lines // TODO entrances, lines
@ -70,6 +64,8 @@ const parseLocation = (ctx, l) => {
} }
res.name = name; res.name = name;
res = enrichStation(ctx, res);
if (l.type === ADDRESS || lid.A == '2') { if (l.type === ADDRESS || lid.A == '2') {
res.address = name; res.address = name;
} }
@ -80,6 +76,22 @@ const parseLocation = (ctx, l) => {
return res; return res;
}; };
const enrichStation = (ctx, stop, locations) => {
const {common} = ctx;
const locs = locations || common?.locations;
const rich = locs && (locs[stop.id] || locs[stop.name]);
if (rich) {
delete stop.type;
delete stop.id;
stop = {
...rich,
...stop,
};
}
return stop;
};
export { export {
parseLocation, parseLocation,
enrichStation,
}; };

View file

@ -10,7 +10,7 @@ import {profile as rawProfile} from '../p/dbweb/index.js';
const res = require('./fixtures/dbweb-departures.json'); const res = require('./fixtures/dbweb-departures.json');
import {dbwebDepartures as expected} from './fixtures/dbweb-departures.js'; import {dbwebDepartures as expected} from './fixtures/dbweb-departures.js';
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: true}); const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
const {profile} = client; const {profile} = client;
const opt = { const opt = {

View file

@ -395,19 +395,10 @@ tap.test('trip details', async (t) => {
}); });
tap.test('departures at Berlin Schwedter Str.', async (t) => { tap.test('departures at Berlin Schwedter Str.', async (t) => {
const res = await new Promise((resolve) => {
let interval = setInterval(async () => { // repeat evaluating `departures()` until stations are enriched
const res = await client.departures(blnSchwedterStr, { const res = await client.departures(blnSchwedterStr, {
duration: 5, when, duration: 5, when,
}); });
if (res.departures[0].stop.name !== undefined) { // ctx.common.locations have loaded
clearInterval(interval);
return resolve(res);
}
}, 4000);
});
await testDepartures({ await testDepartures({
test: t, test: t,
res, res,
@ -418,8 +409,6 @@ tap.test('departures at Berlin Schwedter Str.', async (t) => {
}); });
tap.test('departures with station object', async (t) => { tap.test('departures with station object', async (t) => {
const res = await new Promise((resolve) => {
let interval = setInterval(async () => { // repeat evaluating `departures()` until stations are enriched
const res = await client.departures({ const res = await client.departures({
type: 'station', type: 'station',
id: jungfernheide, id: jungfernheide,
@ -431,31 +420,15 @@ tap.test('departures with station object', async (t) => {
}, },
}, {when}); }, {when});
if (res.departures[0].stop.name !== undefined) { // ctx.common.locations have loaded
clearInterval(interval);
return resolve(res);
}
}, 4000);
});
validate(t, res, 'departuresResponse', 'res'); validate(t, res, 'departuresResponse', 'res');
t.end(); t.end();
}); });
tap.test('arrivals at Berlin Schwedter Str.', async (t) => { tap.test('arrivals at Berlin Schwedter Str.', async (t) => {
const res = await new Promise((resolve) => {
let interval = setInterval(async () => { // repeat evaluating `arrivals()` until stations are enriched
const res = await client.arrivals(blnSchwedterStr, { const res = await client.arrivals(blnSchwedterStr, {
duration: 5, when, duration: 5, when,
}); });
if (res.arrivals[0].stop.name !== undefined) { // ctx.common.locations have loaded
clearInterval(interval);
return resolve(res);
}
}, 4000);
});
await testArrivals({ await testArrivals({
test: t, test: t,
res, res,

View file

@ -4,6 +4,7 @@ import {parseProducts} from '../../parse/products.js';
const profile = { const profile = {
parseLocation: parse, parseLocation: parse,
enrichStation: (ctx, stop) => stop,
parseStationName: (_, name) => name.toLowerCase(), parseStationName: (_, name) => name.toLowerCase(),
parseProducts, parseProducts,
products: [{ products: [{