UNPKG

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
"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;