tfl-ts
Version:
🚇 Fully-typed TypeScript client for Transport for London (TfL) API • Zero dependencies • Auto-generated types • Real-time arrivals • Journey planning • Universal compatibility
476 lines (475 loc) • 20.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Lines = exports.severityByMode = exports.severityDescriptions = exports.modeMetadata = exports.LINE_INFO = exports.LINE_NAMES = exports.Line = void 0;
const batchRequest_1 = require("./utils/batchRequest");
const stripTypes_1 = require("./utils/stripTypes");
// Import raw data from generated meta files
const Line_1 = require("./generated/meta/Line");
Object.defineProperty(exports, "Lines", { enumerable: true, get: function () { return Line_1.Lines; } });
const Meta_1 = require("./generated/meta/Meta");
// Create LINE_NAMES mapping
const LINE_NAMES = Line_1.Lines.reduce((acc, line) => {
acc[line.id] = line.name;
return acc;
}, {});
exports.LINE_NAMES = LINE_NAMES;
// Create LINE_INFO mapping
const LINE_INFO = Line_1.Lines.reduce((acc, line) => {
acc[line.id] = line;
return acc;
}, {});
exports.LINE_INFO = LINE_INFO;
// Extract unique service types from all lines
const allServiceTypes = new Set();
Line_1.Lines.forEach(line => {
line.serviceTypes?.forEach(st => {
if (st.name)
allServiceTypes.add(st.name);
});
});
const serviceTypesArray = Array.from(allServiceTypes);
// Extract unique mode names from all lines
const allModeNames = new Set();
Line_1.Lines.forEach(line => {
allModeNames.add(line.modeName);
});
const modeNamesArray = Array.from(allModeNames);
// Create mode metadata from the generated Modes data
const modeMetadata = Meta_1.Modes.reduce((acc, mode) => {
acc[mode.modeName] = {
isTflService: mode.isTflService,
isFarePaying: mode.isFarePaying,
isScheduledService: mode.isScheduledService
};
return acc;
}, {});
exports.modeMetadata = modeMetadata;
// Build severity by mode mapping from generated data
const buildSeverityByMode = () => {
const severityMap = {};
Meta_1.Severity.forEach(severity => {
if (!severityMap[severity.modeName]) {
severityMap[severity.modeName] = [];
}
severityMap[severity.modeName].push({
level: severity.severityLevel,
description: severity.description
});
});
// Sort by severity level (descending)
Object.keys(severityMap).forEach(mode => {
severityMap[mode].sort((a, b) => b.level - a.level);
});
return severityMap;
};
// Build severity descriptions from generated data
const buildSeverityDescriptions = () => {
const descriptions = new Set();
Meta_1.Severity.forEach(severity => {
descriptions.add(severity.description);
});
return Array.from(descriptions).sort();
};
// Build severity by mode mapping
const severityByMode = buildSeverityByMode();
exports.severityByMode = severityByMode;
const severityDescriptions = buildSeverityDescriptions();
exports.severityDescriptions = severityDescriptions;
/**
* Line class for interacting with Tfl Line API endpoints
* @example
* // Get all tube lines
* const tubeLines = await client.line.get({ modes: ['tube'] });
*
* // Get status for specific lines
* const status = await client.line.getStatus({ lineIds: ['central', 'victoria'] });
*
* // Search for lines
* const results = await client.line.search({ query: "victoria" });
*
* // Get static line information (no HTTP request)
* const lineName = client.line.LINE_NAMES['central']; // "Central"
* const lineInfo = client.line.LINE_INFO['central']; // Full line information
*
* // Validate user input before making API calls
* const validateLineIds = (ids: string[]) => {
* const validIds = ids.filter(id => id in client.line.LINE_NAMES);
* if (validIds.length !== ids.length) {
* const invalidIds = ids.filter(id => !(id in client.line.LINE_NAMES));
* throw new Error(`Invalid line IDs: ${invalidIds.join(', ')}`);
* }
* return validIds;
* };
*/
class Line {
constructor(api) {
this.api = api;
/** Map of line IDs to their display names (static, no HTTP request needed) */
this.LINE_NAMES = LINE_NAMES;
/** Map of line IDs to their full information (static, no HTTP request needed) */
this.LINE_INFO = LINE_INFO;
/** Map of mode names to their metadata (static, no HTTP request needed) */
this.MODE_METADATA = modeMetadata;
/** Available severity descriptions (static, no HTTP request needed) */
this.SEVERITY_DESCRIPTIONS = severityDescriptions;
/** Available service types (static, no HTTP request needed) */
this.SERVICE_TYPES = Meta_1.ServiceTypes;
/** Available disruption categories (static, no HTTP request needed) */
this.DISRUPTION_CATEGORIES = Meta_1.DisruptionCategories;
/** Mode-specific severity types (static, no HTTP request needed) */
this.SEVERITY_BY_MODE = severityByMode;
/** Available mode names (static, no HTTP request needed) */
this.MODE_NAMES = Meta_1.Modes.map(m => m.modeName);
/** Available place types (static, no HTTP request needed) */
this.PLACE_TYPES = Meta_1.PlaceTypes;
/** Available search providers (static, no HTTP request needed) */
this.SEARCH_PROVIDERS = Meta_1.SearchProviders;
/** Available sort options (static, no HTTP request needed) */
this.SORT_OPTIONS = Meta_1.Sorts;
/** Available stop types (static, no HTTP request needed) */
this.STOP_TYPES = Meta_1.StopTypes;
/** Available categories with their keys (static, no HTTP request needed) */
this.CATEGORIES = Meta_1.Categories;
/** All severity levels and descriptions (static, no HTTP request needed) */
this.ALL_SEVERITY = Meta_1.Severity;
this.batchRequest = new batchRequest_1.BatchRequest(api);
}
/**
* Get line information
* @param options - Query options for filtering lines
* @returns Promise resolving to an array of line information
* @example
* // Get all lines
* const allLines = await client.line.get();
*
* // Get tube lines
* const tubeLines = await client.line.get({ modes: ['tube'] });
*
* // Get specific lines
* const specificLines = await client.line.get({
* lineIds: ['central', 'victoria', 'jubilee']
* });
*
* // Validate user input before making API calls
* const userInput = ['central', 'invalid-line'];
* const validIds = userInput.filter(id => id in client.line.LINE_NAMES);
* if (validIds.length !== userInput.length) {
* throw new Error(`Invalid line IDs: ${userInput.filter(id => !(id in client.line.LINE_NAMES)).join(', ')}`);
* }
*/
async get(options) {
const { lineIds, modes, keepTflTypes } = options || {};
if (lineIds?.length) {
const response = await this.batchRequest.processBatch(lineIds, async (chunk) => this.api.line.lineGet(chunk).then(response => response.data));
return (0, stripTypes_1.stripTypeFields)(response, keepTflTypes);
}
if (modes?.length) {
const response = await this.batchRequest.processBatch(modes, async (chunk) => this.api.line.lineGetByMode(chunk).then(response => response.data));
return (0, stripTypes_1.stripTypeFields)(response, keepTflTypes);
}
const response = await this.api.line.lineGet([]).then(response => response.data);
return (0, stripTypes_1.stripTypeFields)(response, keepTflTypes);
}
/**
* Get detailed route information for Tfl lines
*
* This method returns comprehensive route information including:
* - Route sections with start and end stations
* - Service types (Regular/Night)
* - Direction (inbound/outbound)
* - Valid date ranges
*
* @param options - Query options for filtering routes
* @returns Promise resolving to an array of line route information
* @example
* // Most common: Get routes for specific lines
* const specificRoutes = await client.line.getRoute({
* lineIds: ['central', 'victoria'],
* serviceTypes: ['Regular']
* });
*
* // Common: Get routes for all lines of a specific mode
* const tubeRoutes = await client.line.getRoute({
* modes: ['tube'],
* serviceTypes: ['Regular', 'Night']
* });
*
* // Less common: Get all routes (use with caution - returns large dataset)
* const allRoutes = await client.line.getRoute();
*/
async getRoute(options = {}) {
const { lineIds, modes, keepTflTypes } = options;
if (lineIds?.length) {
return this.api.line.lineLineRoutesByIds({ ids: lineIds, serviceTypes: options.serviceTypes })
.then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
if (modes?.length) {
return this.api.line.lineRouteByMode({ modes, serviceTypes: options.serviceTypes })
.then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
return this.api.line.lineRoute({ serviceTypes: options.serviceTypes })
.then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
/**
* Get route sequence for a specific line and direction
*
* This method returns the complete sequence of stops for a line in a specific direction,
* including detailed information about each stop and the route sections.
*
* @param options - Query options for route sequence
* @returns Promise resolving to route sequence information
* @example
* // Get Central line inbound route sequence
* const sequence = await client.line.getRouteSequence({
* id: 'central',
* direction: 'inbound',
* serviceTypes: ['Regular']
* });
*
* // Get Victoria line outbound route sequence
* const sequence = await client.line.getRouteSequence({
* id: 'victoria',
* direction: 'outbound',
* excludeCrowding: true
* });
*/
async getRouteSequence(options) {
const { id, direction, serviceTypes, excludeCrowding, keepTflTypes } = options;
return this.api.line.lineRouteSequence({
id,
direction,
serviceTypes: serviceTypes,
excludeCrowding
}).then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
/**
* Get line status information
* @param options - Query options for status filtering
* @returns Promise resolving to an array of line status information
* @example
* // Get status for specific lines
* const status = await client.line.getStatus({
* lineIds: ['central', 'victoria'],
* detail: true
* });
*
* // Get status for all lines with specific severity
* const severeDelays = await client.line.getStatus({ severity: 3 });
*
* // Get status for tube lines only
* const tubeStatus = await client.line.getStatus({ modes: ['tube'] });
*/
async getStatus(options = {}) {
const { lineIds, modes, severity, dateRange, detail, severityLevel, keepTflTypes } = options;
// Handle severity-based status (new endpoint)
if (severity !== undefined && !lineIds?.length && !modes?.length) {
return this.api.line.lineStatusBySeverity(severity)
.then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
if (dateRange && lineIds?.length) {
return this.batchRequest.processBatch(lineIds, async (chunk) => this.api.line.lineStatus({
ids: chunk,
startDate: dateRange.startDate,
endDate: dateRange.endDate,
detail
}).then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes)));
}
if (lineIds?.length) {
return this.batchRequest.processBatch(lineIds, async (chunk) => this.api.line.lineStatusByIds({
ids: chunk,
detail
}).then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes)));
}
// Handle mode specific status
if (modes?.length) {
return this.api.line.lineStatusByMode({
modes: modes,
detail,
severityLevel
}).then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
// Default: get all modes first, then get status for all modes
const allModes = await this.api.line.lineMetaModes().then((response) => response.data);
const modeNames = allModes.map((mode) => mode.modeName).filter((name) => name !== undefined);
return this.api.line.lineStatusByMode({
modes: modeNames,
detail
}).then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
/**
* Get line disruption information
* @param options - Query options for disruption filtering
* @returns Promise resolving to an array of disruption information
* @example
* // Get disruptions for specific lines
* const disruptions = await client.line.getDisruption({
* lineIds: ['central', 'victoria']
* });
*
* // Get disruptions for all tube lines
* const tubeDisruptions = await client.line.getDisruption({
* modes: ['tube']
* });
*/
async getDisruption(options = {}) {
const { lineIds, modes, keepTflTypes } = options;
if (lineIds?.length) {
return this.api.line.lineDisruption(lineIds)
.then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
if (modes?.length) {
return this.api.line.lineDisruptionByMode(modes)
.then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
return this.api.line.lineMetaDisruptionCategories()
.then(response => (0, stripTypes_1.stripTypeFields)(response.data.map(category => ({
category: category,
type: '',
description: '',
})), keepTflTypes));
}
/**
* Get stop points (stations) for a specific line
*
* This method returns all stations that serve a given line, including
* their stop point IDs, names, and additional information.
*
* @param options - Query options for stop points
* @returns Promise resolving to an array of stop point information
* @example
* // Get all stations for Central line
* const stations = await client.line.getStopPoints({ id: 'central' });
*
* // Get TfL-operated national rail stations only
* const tflStations = await client.line.getStopPoints({
* id: 'elizabeth',
* tflOperatedNationalRailStationsOnly: true
* });
*/
async getStopPoints(options) {
const { id, tflOperatedNationalRailStationsOnly, keepTflTypes } = options;
return this.api.line.lineStopPoints({
id,
tflOperatedNationalRailStationsOnly
}).then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
/**
* Get timetable for a specific line and station
*
* This method returns timetable information for a specific station on a line,
* optionally including destination-specific timetables.
*
* @param options - Query options for timetable
* @returns Promise resolving to timetable information
* @example
* // Get timetable from Oxford Circus
* const timetable = await client.line.getTimetable({
* id: 'central',
* fromStopPointId: '940GZZLUOXC'
* });
*
* // Get timetable from Oxford Circus to Victoria
* const timetable = await client.line.getTimetable({
* id: 'central',
* fromStopPointId: '940GZZLUOXC',
* toStopPointId: '940GZZLUVIC'
* });
*/
async getTimetable(options) {
const { id, fromStopPointId, toStopPointId, keepTflTypes } = options;
if (toStopPointId) {
return this.api.line.lineTimetableTo(fromStopPointId, id, toStopPointId)
.then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
return this.api.line.lineTimetable(fromStopPointId, id)
.then((response) => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
/**
* Get arrival predictions for specific lines at a stop
*
* This method returns real-time arrival predictions for specified lines
* at a given stop, with optional direction and destination filtering.
*
* @param options - Query options for arrivals
* @returns Promise resolving to an array of arrival predictions
* @example
* // Get arrivals for Central line at Oxford Circus
* const arrivals = await client.line.getArrivals({
* lineIds: ['central'],
* stopPointId: '940GZZLUOXC'
* });
*
* // Get inbound arrivals for Victoria line at Victoria
* const arrivals = await client.line.getArrivals({
* lineIds: ['victoria'],
* stopPointId: '940GZZLUVIC',
* direction: 'inbound'
* });
*/
async getArrivals(options) {
const { lineIds, stopPointId, direction, destinationStationId, keepTflTypes } = options;
return this.api.line.lineArrivals({
ids: lineIds,
stopPointId,
direction,
destinationStationId
}).then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
/**
* Search lines and routes
* @param options - Query options for search
* @returns Promise resolving to search results
* @example
* // Search for lines containing "victoria"
* const results = await client.line.search({
* query: "victoria",
* modes: ['tube']
* });
*
* // Search for night service routes
* const results = await client.line.search({
* query: "central",
* serviceTypes: ['Night']
* });
*/
async search(options) {
const { query, modes, serviceTypes, keepTflTypes } = options;
return this.api.line.lineSearch({
query,
modes: modes,
serviceTypes: serviceTypes
}).then(response => (0, stripTypes_1.stripTypeFields)(response.data, keepTflTypes));
}
/**
* Get line metadata (makes HTTP request to TfL API)
*
* This method fetches live metadata from the TfL API. For static metadata
* that doesn't change frequently, consider using the static properties
* instead to save HTTP round trips.
*
* @param options - Options for metadata request
* @returns Promise resolving to line metadata
* @example
* // Get live metadata from TfL API
* const meta = await client.line.getMeta();
*
* // Use static metadata instead (no HTTP request)
* const serviceTypes = client.line.SERVICE_TYPES; // ['Regular', 'Night']
* const disruptionCategories = client.line.DISRUPTION_CATEGORIES;
*/
async getMeta(options = {}) {
const [modes, severities, disruptions, serviceTypes] = await Promise.all([
this.api.line.lineMetaModes().then(response => response.data),
this.api.line.lineMetaSeverity().then(response => response.data),
this.api.line.lineMetaDisruptionCategories().then(response => response.data),
this.api.line.lineMetaServiceTypes().then(response => response.data)
]);
return {
modes: (0, stripTypes_1.stripTypeFields)(modes, options.keepTflTypes),
severities: (0, stripTypes_1.stripTypeFields)(severities, options.keepTflTypes),
disruptionCategories: (0, stripTypes_1.stripTypeFields)(disruptions, options.keepTflTypes),
serviceTypes: (0, stripTypes_1.stripTypeFields)(serviceTypes, options.keepTflTypes)
};
}
}
exports.Line = Line;