gtfs 4.18.7 → 4.19.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.
@@ -0,0 +1,2417 @@
1
+ import { S as tripUpdates, b as vehiclePositions, t as models_exports, v as serviceAlertInformedEntities, x as stopTimeUpdates, y as serviceAlerts } from "./models-9NvwLHlL.js";
2
+ import path from "node:path";
3
+ import { createReadStream, existsSync, lstatSync } from "node:fs";
4
+ import { cp, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
5
+ import { parse } from "csv-parse";
6
+ import stripBomStream from "strip-bom-stream";
7
+ import { temporaryDirectory } from "tempy";
8
+ import mapSeries from "promise-map-series";
9
+ import fs from "fs";
10
+ import Database from "better-sqlite3";
11
+ import { homedir } from "node:os";
12
+ import { cloneDeep, compact, filter, get, groupBy, last, noop, omit, omitBy, orderBy, pick, snakeCase, sortBy, without } from "lodash-es";
13
+ import sanitize from "sanitize-filename";
14
+ import StreamZip from "node-stream-zip";
15
+ import { clearLine, cursorTo } from "node:readline";
16
+ import * as colors from "yoctocolors";
17
+ import { feature, featureCollection } from "@turf/helpers";
18
+ import GtfsRealtimeBindings from "gtfs-realtime-bindings";
19
+ import sqlString from "sqlstring-sqlite";
20
+ import Long from "long";
21
+ import { stringify } from "csv-stringify";
22
+
23
+ //#region src/lib/log-utils.ts
24
+ /**
25
+ * Creates a logging function based on configuration settings
26
+ * @param {Config} config - Configuration object containing logging preferences
27
+ * @returns {LogFunction} Logging function that writes to stdout, or noop if verbose is false
28
+ * @example
29
+ * const logger = log({ verbose: true });
30
+ * logger('Processing...', true); // Overwrites current line
31
+ * logger('Done!'); // Writes on new line
32
+ */
33
+ function log(config) {
34
+ if (config.verbose === false) return noop;
35
+ if (config.logFunction) return config.logFunction;
36
+ return (text, overwrite = false) => {
37
+ if (overwrite && process.stdout.isTTY) {
38
+ clearLine(process.stdout, 0);
39
+ cursorTo(process.stdout, 0);
40
+ } else process.stdout.write("\n");
41
+ process.stdout.write(text);
42
+ };
43
+ }
44
+ /**
45
+ * Creates a warning logging function
46
+ * @param {Config} config - Configuration object containing logging preferences
47
+ * @returns {(text: string) => void} Function that logs formatted warning messages
48
+ * @example
49
+ * const warnLogger = logWarning(config);
50
+ * warnLogger('Resource not found'); // Outputs yellow warning message
51
+ */
52
+ function logWarning(config) {
53
+ if (config.logFunction) return config.logFunction;
54
+ return (text) => {
55
+ process.stdout.write(`\n${formatWarning(text)}\n`);
56
+ };
57
+ }
58
+ /**
59
+ * Creates an error logging function
60
+ * @param {Config} config - Configuration object containing logging preferences
61
+ * @returns {(text: string) => void} Function that logs formatted error messages
62
+ * @example
63
+ * const errorLogger = logError(config);
64
+ * errorLogger('Failed to connect'); // Outputs red error message
65
+ */
66
+ function logError(config) {
67
+ if (config.logFunction) return config.logFunction;
68
+ return (text) => {
69
+ process.stdout.write(`\n${formatError(text)}\n`);
70
+ };
71
+ }
72
+ /**
73
+ * Formats warning text with yellow color and underline
74
+ * @param {string} text - The warning message to format
75
+ * @returns {string} Formatted warning message in yellow with underlined "Warning" prefix
76
+ * @example
77
+ * const formattedWarning = formatWarning('Resource not found');
78
+ * console.log(formattedWarning); // Yellow "Warning: Resource not found"
79
+ */
80
+ function formatWarning(text) {
81
+ return colors.yellow(`${colors.underline("Warning")}: ${text}`);
82
+ }
83
+ /**
84
+ * Formats error text with red color and underline
85
+ * @param {Error | string} error - The error object or message to format
86
+ * @returns {string} Formatted error message in red with underlined "Error" prefix
87
+ * @example
88
+ * const formattedError = formatError(new Error('Connection failed'));
89
+ * console.log(formattedError); // Red "Error: Connection failed"
90
+ */
91
+ function formatError(error) {
92
+ const cleanMessage = (error instanceof Error ? error.message : error).replace(/^Error:\s*/i, "");
93
+ return colors.red(`${colors.underline("Error")}: ${cleanMessage}`);
94
+ }
95
+
96
+ //#endregion
97
+ //#region src/lib/file-utils.ts
98
+ const homeDirectory = homedir();
99
+ /**
100
+ * Attempts to parse and load configuration from various sources
101
+ * Priority: 1. CLI config path 2. CLI direct args 3. ./config.json
102
+ * @param {ConfigArgs} argv - Command line arguments
103
+ * @throws {Error} If configuration cannot be found or parsed
104
+ * @returns {Promise<Record<string, any>>} Parsed configuration object
105
+ * @example
106
+ * const config = await getConfig({ configPath: './my-config.json' });
107
+ */
108
+ async function getConfig(argv) {
109
+ let config;
110
+ let data;
111
+ try {
112
+ if (argv.configPath) {
113
+ data = await readFile(path.resolve(untildify(argv.configPath)), "utf8");
114
+ config = Object.assign(JSON.parse(data), argv);
115
+ } else if (argv.gtfsPath || argv.gtfsUrl || argv.sqlitePath) config = {
116
+ agencies: [...argv.gtfsPath ? [{ path: argv.gtfsPath }] : [], ...argv.gtfsUrl ? [{ url: argv.gtfsUrl }] : []],
117
+ ...omit(argv, ["path", "url"])
118
+ };
119
+ else if (existsSync(path.resolve("./config.json"))) {
120
+ data = await readFile(path.resolve("./config.json"), "utf8");
121
+ config = Object.assign(JSON.parse(data), argv);
122
+ log(config)("Using configuration from ./config.json");
123
+ } else throw new Error("Cannot find configuration file. Use config-sample.json as a starting point, pass --configPath option.");
124
+ return config;
125
+ } catch (error) {
126
+ if (error instanceof SyntaxError) throw new Error(`Cannot parse configuration file. Check to ensure that it is valid JSON. Error: ${error.message}`, { cause: error });
127
+ throw error;
128
+ }
129
+ }
130
+ /**
131
+ * Prepares a directory for saving files by clearing its contents
132
+ * @param {string} exportPath - Path to the directory to prepare
133
+ * @returns {Promise<void>}
134
+ * @example
135
+ * await prepDirectory('./output');
136
+ */
137
+ async function prepDirectory(exportPath) {
138
+ await rm(exportPath, {
139
+ recursive: true,
140
+ force: true
141
+ });
142
+ await mkdir(exportPath, { recursive: true });
143
+ }
144
+ /**
145
+ * Extracts contents of a zip file to specified directory
146
+ * @param {string} zipfilePath - Path to the zip file
147
+ * @param {string} exportPath - Directory to extract contents to
148
+ * @returns {Promise<void>}
149
+ * @throws {Error} If zip file cannot be opened or extracted
150
+ * @example
151
+ * await unzip('./data.zip', './extracted');
152
+ */
153
+ async function unzip(zipfilePath, exportPath) {
154
+ try {
155
+ const zip = new StreamZip.async({ file: zipfilePath });
156
+ await zip.extract(null, exportPath);
157
+ await zip.close();
158
+ } catch (error) {
159
+ throw new Error(`Failed to extract zip file: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error });
160
+ }
161
+ }
162
+ /**
163
+ * Generates a safe folder name from input string
164
+ * Converts to snake_case and removes unsafe characters
165
+ * @param {string} folderName - Input string to convert to folder name
166
+ * @returns {string} Sanitized folder name
167
+ * @example
168
+ * generateFolderName('My Folder!') // returns 'my_folder'
169
+ */
170
+ function generateFolderName(folderName) {
171
+ if (!folderName || typeof folderName !== "string") throw new Error("Folder name must be a non-empty string");
172
+ return snakeCase(sanitize(folderName));
173
+ }
174
+ /**
175
+ * Converts a tilde path to a full path
176
+ * @param pathWithTilde The path to convert
177
+ * @returns The full path
178
+ */
179
+ function untildify(pathWithTilde) {
180
+ return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
181
+ }
182
+
183
+ //#endregion
184
+ //#region src/lib/errors.ts
185
+ let GtfsErrorCategory = /* @__PURE__ */ function(GtfsErrorCategory) {
186
+ GtfsErrorCategory["CONFIG"] = "config";
187
+ GtfsErrorCategory["DOWNLOAD"] = "download";
188
+ GtfsErrorCategory["ZIP"] = "zip";
189
+ GtfsErrorCategory["VALIDATION"] = "validation";
190
+ GtfsErrorCategory["DATABASE"] = "database";
191
+ GtfsErrorCategory["PARSE"] = "parse";
192
+ GtfsErrorCategory["QUERY"] = "query";
193
+ GtfsErrorCategory["INTERNAL"] = "internal";
194
+ return GtfsErrorCategory;
195
+ }({});
196
+ /**
197
+ * Error codes are a public API contract and must remain stable across
198
+ * minor/patch releases.
199
+ */
200
+ let GtfsErrorCode = /* @__PURE__ */ function(GtfsErrorCode) {
201
+ GtfsErrorCode["GTFS_DOWNLOAD_HTTP"] = "GTFS_DOWNLOAD_HTTP";
202
+ GtfsErrorCode["GTFS_DOWNLOAD_FAILED"] = "GTFS_DOWNLOAD_FAILED";
203
+ GtfsErrorCode["GTFS_ZIP_INVALID"] = "GTFS_ZIP_INVALID";
204
+ GtfsErrorCode["GTFS_REQUIRED_FIELD_MISSING"] = "GTFS_REQUIRED_FIELD_MISSING";
205
+ GtfsErrorCode["GTFS_INVALID_DATE"] = "GTFS_INVALID_DATE";
206
+ GtfsErrorCode["GTFS_CONFIG_INVALID"] = "GTFS_CONFIG_INVALID";
207
+ GtfsErrorCode["DB_OPEN_FAILED"] = "DB_OPEN_FAILED";
208
+ GtfsErrorCode["GTFS_DB_OPERATION_FAILED"] = "GTFS_DB_OPERATION_FAILED";
209
+ GtfsErrorCode["GTFS_JSON_INVALID"] = "GTFS_JSON_INVALID";
210
+ GtfsErrorCode["GTFS_UNSUPPORTED_FILE_TYPE"] = "GTFS_UNSUPPORTED_FILE_TYPE";
211
+ GtfsErrorCode["GTFS_CSV_PARSE_FAILED"] = "GTFS_CSV_PARSE_FAILED";
212
+ GtfsErrorCode["GTFS_QUERY_INVALID"] = "GTFS_QUERY_INVALID";
213
+ return GtfsErrorCode;
214
+ }({});
215
+ let GtfsWarningCode = /* @__PURE__ */ function(GtfsWarningCode) {
216
+ GtfsWarningCode["GTFS_DUPLICATE_PRIMARY_KEY"] = "GTFS_DUPLICATE_PRIMARY_KEY";
217
+ return GtfsWarningCode;
218
+ }({});
219
+ var GtfsError = class extends Error {
220
+ code;
221
+ category;
222
+ isOperational;
223
+ statusCode;
224
+ details;
225
+ constructor(message, options) {
226
+ super(message, { cause: options.cause });
227
+ this.name = "GtfsError";
228
+ this.code = options.code;
229
+ this.category = options.category;
230
+ this.isOperational = options.isOperational ?? true;
231
+ this.statusCode = options.statusCode;
232
+ this.details = options.details;
233
+ }
234
+ };
235
+ function isGtfsError(error) {
236
+ if (!error || typeof error !== "object") return false;
237
+ const candidate = error;
238
+ return candidate.name === "GtfsError" && typeof candidate.message === "string" && typeof candidate.code === "string" && typeof candidate.category === "string" && typeof candidate.isOperational === "boolean";
239
+ }
240
+ function isGtfsValidationError(error) {
241
+ return isGtfsError(error) && error.category === "validation";
242
+ }
243
+ function toGtfsError(error, fallback) {
244
+ if (isGtfsError(error)) return error;
245
+ return new GtfsError(fallback.message, {
246
+ ...fallback,
247
+ cause: error
248
+ });
249
+ }
250
+ function createImportReport() {
251
+ return {
252
+ errors: [],
253
+ warnings: [],
254
+ errorCountsByCode: {},
255
+ warningCountsByCode: {}
256
+ };
257
+ }
258
+ function addImportError(report, error) {
259
+ report.errors.push(error);
260
+ report.errorCountsByCode[error.code] = (report.errorCountsByCode[error.code] ?? 0) + 1;
261
+ }
262
+ function addImportWarning(report, warning) {
263
+ report.warnings.push(warning);
264
+ report.warningCountsByCode[warning.code] = (report.warningCountsByCode[warning.code] ?? 0) + 1;
265
+ }
266
+ function formatGtfsError(error, options = { verbosity: "developer" }) {
267
+ if (!isGtfsError(error)) {
268
+ const message = error instanceof Error ? error.message : String(error);
269
+ return options.verbosity === "user" ? message : `UNKNOWN_ERROR: ${message}`;
270
+ }
271
+ if (options.verbosity === "user") return error.message;
272
+ return [
273
+ `${error.code}: ${error.message}`,
274
+ `category=${error.category}`,
275
+ error.statusCode !== void 0 ? `statusCode=${error.statusCode}` : null,
276
+ error.details ? `details=${JSON.stringify(error.details)}` : null
277
+ ].filter(Boolean).join(" | ");
278
+ }
279
+
280
+ //#endregion
281
+ //#region src/lib/db.ts
282
+ const dbs = {};
283
+ function setupDb(sqlitePath) {
284
+ const db = new Database(untildify(sqlitePath));
285
+ db.pragma("journal_mode = OFF");
286
+ db.pragma("synchronous = OFF");
287
+ db.pragma("temp_store = MEMORY");
288
+ dbs[sqlitePath] = db;
289
+ return db;
290
+ }
291
+ function openDb(config = null) {
292
+ if (config) {
293
+ const { sqlitePath = ":memory:", db } = config;
294
+ if (db) return db;
295
+ if (dbs[sqlitePath]) return dbs[sqlitePath];
296
+ return setupDb(sqlitePath);
297
+ }
298
+ if (Object.keys(dbs).length === 0) return setupDb(":memory:");
299
+ if (Object.keys(dbs).length === 1) return dbs[Object.keys(dbs)[0]];
300
+ if (Object.keys(dbs).length > 1) throw new GtfsError("Multiple databases open, please specify which one to use.", {
301
+ code: "GTFS_DB_OPERATION_FAILED",
302
+ category: "database",
303
+ details: { openDatabaseCount: Object.keys(dbs).length }
304
+ });
305
+ throw new GtfsError("Unable to find database connection.", {
306
+ code: "GTFS_DB_OPERATION_FAILED",
307
+ category: "database"
308
+ });
309
+ }
310
+ function closeDb(db = null) {
311
+ if (Object.keys(dbs).length === 0) throw new GtfsError("No database connection. Call `openDb(config)` before using any methods.", {
312
+ code: "GTFS_DB_OPERATION_FAILED",
313
+ category: "database"
314
+ });
315
+ if (!db) {
316
+ if (Object.keys(dbs).length > 1) throw new GtfsError("Multiple database connections. Pass the db you want to close as a parameter to `closeDb`.", {
317
+ code: "GTFS_DB_OPERATION_FAILED",
318
+ category: "database",
319
+ details: { openDatabaseCount: Object.keys(dbs).length }
320
+ });
321
+ db = dbs[Object.keys(dbs)[0]];
322
+ }
323
+ db.close();
324
+ delete dbs[db.name];
325
+ }
326
+ function deleteDb(db = null) {
327
+ if (Object.keys(dbs).length === 0) throw new GtfsError("No database connection. Call `openDb(config)` before using any methods.", {
328
+ code: "GTFS_DB_OPERATION_FAILED",
329
+ category: "database"
330
+ });
331
+ if (!db) {
332
+ if (Object.keys(dbs).length > 1) throw new GtfsError("Multiple database connections. Pass the db you want to delete as a parameter to `deleteDb`.", {
333
+ code: "GTFS_DB_OPERATION_FAILED",
334
+ category: "database",
335
+ details: { openDatabaseCount: Object.keys(dbs).length }
336
+ });
337
+ db = dbs[Object.keys(dbs)[0]];
338
+ }
339
+ db.close();
340
+ if (db.name !== ":memory:") fs.unlinkSync(db.name);
341
+ delete dbs[db.name];
342
+ }
343
+
344
+ //#endregion
345
+ //#region src/lib/geojson-utils.ts
346
+ /**
347
+ * Validates if a string is valid JSON
348
+ * @param {string} string - The string to validate as JSON
349
+ * @returns {boolean} True if string is valid JSON, false otherwise
350
+ * @example
351
+ * isValidJSON('{"key": "value"}') // returns true
352
+ * isValidJSON('invalid json') // returns false
353
+ */
354
+ function isValidJSON(string) {
355
+ try {
356
+ JSON.parse(string);
357
+ return true;
358
+ } catch {
359
+ return false;
360
+ }
361
+ }
362
+ /**
363
+ * Validates if an array of positions forms a valid LineString
364
+ * @param {Position[]} [lineString] - Array of coordinate pairs
365
+ * @returns {boolean} True if lineString is valid, false otherwise
366
+ */
367
+ function isValidLineString(lineString) {
368
+ if (!lineString || lineString.length <= 1) return false;
369
+ if (lineString.length === 2) {
370
+ const [[x1, y1], [x2, y2]] = lineString;
371
+ return !(x1 === x2 && y1 === y2);
372
+ }
373
+ return true;
374
+ }
375
+ /**
376
+ * Consolidates shape groups into unique line segments
377
+ * @param {Shape[][]} shapeGroups - Array of shape point groups
378
+ * @returns {Position[][]} Array of consolidated line strings
379
+ */
380
+ function consolidateShapes(shapeGroups) {
381
+ const keys = /* @__PURE__ */ new Set();
382
+ const segmentsArray = shapeGroups.map((shapes) => shapes.reduce((memo, point, idx) => {
383
+ if (idx > 0) {
384
+ const prevPoint = shapes[idx - 1];
385
+ memo.push([[prevPoint.shape_pt_lon, prevPoint.shape_pt_lat], [point.shape_pt_lon, point.shape_pt_lat]]);
386
+ }
387
+ return memo;
388
+ }, []));
389
+ const consolidatedLineStrings = [];
390
+ for (const segments of segmentsArray) {
391
+ consolidatedLineStrings.push([]);
392
+ for (const segment of segments) {
393
+ const key1 = segment.flat().join(",");
394
+ const key2 = segment.reverse().flat().join(",");
395
+ const currentLine = last(consolidatedLineStrings);
396
+ if (!currentLine || keys.has(key1) || keys.has(key2)) {
397
+ consolidatedLineStrings.push([]);
398
+ continue;
399
+ }
400
+ if (currentLine.length === 0) currentLine.push(segment[0]);
401
+ currentLine.push(segment[1]);
402
+ keys.add(key1);
403
+ keys.add(key2);
404
+ }
405
+ }
406
+ return filter(consolidatedLineStrings, isValidLineString);
407
+ }
408
+ /**
409
+ * Formats a color string to hex format
410
+ * @param {string | null | undefined} color - Color string to format
411
+ * @returns {string | undefined} Formatted hex color or undefined
412
+ * @example
413
+ * formatHexColor('FF0000') // returns '#FF0000'
414
+ */
415
+ function formatHexColor(color) {
416
+ if (!color) return void 0;
417
+ return `#${color}`;
418
+ }
419
+ /**
420
+ * Formats properties object by cleaning null values and formatting colors
421
+ * @param {Record<string, unknown>} properties - Properties object to format
422
+ * @returns {Record<string, unknown>} Formatted properties object
423
+ */
424
+ function formatProperties(properties) {
425
+ const formattedProperties = cloneDeep(omitBy(properties, (value) => value == null));
426
+ const formattedRouteColor = formatHexColor(properties.route_color);
427
+ const formattedRouteTextColor = formatHexColor(properties.route_text_color);
428
+ if (formattedRouteColor) formattedProperties.route_color = formattedRouteColor;
429
+ if (formattedRouteTextColor) formattedProperties.route_text_color = formattedRouteTextColor;
430
+ if (properties.routes && Array.isArray(properties.routes)) formattedProperties.routes = properties.routes.map((route) => formatProperties(route));
431
+ return formattedProperties;
432
+ }
433
+ /**
434
+ * Converts GTFS shapes to GeoJSON Feature
435
+ * @param {Shape[]} shapes - Array of GTFS shapes
436
+ * @param {Record<string, unknown>} [properties={}] - Properties to add to the feature
437
+ * @returns {Feature} GeoJSON Feature with MultiLineString geometry
438
+ */
439
+ function shapesToGeoJSONFeature(shapes, properties = {}) {
440
+ return feature({
441
+ type: "MultiLineString",
442
+ coordinates: consolidateShapes(Object.values(groupBy(shapes, "shape_id")).map((shapeGroup) => sortBy(shapeGroup, "shape_pt_sequence")))
443
+ }, formatProperties(properties));
444
+ }
445
+ /**
446
+ * Converts GTFS stops to GeoJSON FeatureCollection
447
+ * @param {Stop[]} stops - Array of GTFS stops
448
+ * @returns {FeatureCollection} GeoJSON FeatureCollection of Point features
449
+ */
450
+ function stopsToGeoJSONFeatureCollection(stops) {
451
+ return featureCollection(compact(stops.map((stop) => {
452
+ if (!stop.stop_lon || !stop.stop_lat) return;
453
+ return feature({
454
+ type: "Point",
455
+ coordinates: [stop.stop_lon, stop.stop_lat]
456
+ }, formatProperties(omit(stop, ["stop_lat", "stop_lon"])));
457
+ })));
458
+ }
459
+
460
+ //#endregion
461
+ //#region src/lib/utils.ts
462
+ /**
463
+ * Validates the configuration object for GTFS import
464
+ * @param config The configuration object to validate
465
+ * @throws Error if agencies are missing or if agency lacks both url and path
466
+ * @returns The validated config object
467
+ */
468
+ function validateConfigForImport(config) {
469
+ if (!config.agencies || config.agencies.length === 0) throw new GtfsError("No `agencies` specified in config", {
470
+ code: "GTFS_CONFIG_INVALID",
471
+ category: "config",
472
+ details: { field: "agencies" }
473
+ });
474
+ for (const [index, agency] of config.agencies.entries()) if (!agency.path && !agency.url) throw new GtfsError(`No Agency \`url\` or \`path\` specified in config for agency index ${index}.`, {
475
+ code: "GTFS_CONFIG_INVALID",
476
+ category: "config",
477
+ details: { agencyIndex: index }
478
+ });
479
+ return config;
480
+ }
481
+ /**
482
+ * Initializes configuration with default values
483
+ * @param initialConfig The user-provided configuration
484
+ * @returns Merged configuration with defaults
485
+ */
486
+ function setDefaultConfig(initialConfig) {
487
+ return {
488
+ sqlitePath: ":memory:",
489
+ ignoreDuplicates: false,
490
+ ignoreErrors: false,
491
+ gtfsRealtimeExpirationSeconds: 0,
492
+ verbose: true,
493
+ downloadTimeout: 3e4,
494
+ ...initialConfig
495
+ };
496
+ }
497
+ /**
498
+ * Converts a Long timestamp to ISO date string
499
+ * @param longDate Object containing high, low, and unsigned values
500
+ * @returns ISO formatted date string
501
+ */
502
+ function convertLongTimeToDate(longDate) {
503
+ const { high, low, unsigned } = longDate;
504
+ return (/* @__PURE__ */ new Date(Long.fromBits(low, high, unsigned).toNumber() * 1e3)).toISOString();
505
+ }
506
+ /**
507
+ * Converts time string in HH:mm:ss format to seconds since midnight
508
+ * @param time Time string in HH:mm:ss format
509
+ * @returns Number of seconds since midnight, or null if invalid format
510
+ */
511
+ function calculateSecondsFromMidnight(time) {
512
+ if (!time || typeof time !== "string") return null;
513
+ const [hours, minutes, seconds] = time.split(":").map(Number);
514
+ if ([
515
+ hours,
516
+ minutes,
517
+ seconds
518
+ ].some(isNaN) || minutes >= 60 || seconds >= 60) return null;
519
+ return hours * 3600 + minutes * 60 + seconds;
520
+ }
521
+ /**
522
+ * Ensures time components have leading zeros (e.g., "9:5:1" -> "09:05:01")
523
+ * @param time Time string in HH:mm:ss format
524
+ * @returns Formatted time string with leading zeros, or null if invalid format
525
+ */
526
+ function padLeadingZeros(time) {
527
+ const split = time.split(":").map((d) => String(Number(d)).padStart(2, "0"));
528
+ if (split.length !== 3) return null;
529
+ return split.join(":");
530
+ }
531
+ /**
532
+ * Formats SQL SELECT clause from array of field names or field mapping object
533
+ * @param fields Array of field names or object mapping source to alias
534
+ * @returns Formatted SELECT clause
535
+ */
536
+ function formatSelectClause(fields) {
537
+ if (Array.isArray(fields)) return `SELECT ${fields.length > 0 ? fields.map((fieldName) => sqlString.escapeId(fieldName)).join(", ") : "*"}`;
538
+ return `SELECT ${Object.entries(fields).map((key) => `${sqlString.escapeId(key[0])} AS ${sqlString.escapeId(key[1])}`).join(", ")}`;
539
+ }
540
+ /**
541
+ * Formats SQL JOIN clause from array of join configurations
542
+ * @param joinObject Array of join options
543
+ * @returns Formatted JOIN clause
544
+ */
545
+ function formatJoinClause(joinObject) {
546
+ return joinObject.map((data) => `${data.type ? data.type + " JOIN" : "INNER JOIN"} ${sqlString.escapeId(data.table)} ON ${data.on}`).join(" ");
547
+ }
548
+ /**
549
+ * Converts degrees to radians
550
+ * @param angle Angle in degrees
551
+ * @returns Angle in radians
552
+ */
553
+ function degree2radian(angle) {
554
+ return angle * Math.PI / 180;
555
+ }
556
+ /**
557
+ * Converts radians to degrees
558
+ * @param angle Angle in radians
559
+ * @returns Angle in degrees
560
+ */
561
+ function radian2degree(angle) {
562
+ return angle / Math.PI * 180;
563
+ }
564
+ const EARTH_RADIUS_METERS = 6371e3;
565
+ /**
566
+ * Creates SQL WHERE clause for geographic bounding box search
567
+ * @param latitudeDegree Center latitude in degrees
568
+ * @param longitudeDegree Center longitude in degrees
569
+ * @param boundingBoxSideMeters Size of bounding box in meters
570
+ * @returns SQL WHERE clause for bounding box search
571
+ */
572
+ function formatWhereClauseBoundingBox(latitudeDegree, longitudeDegree, boundingBoxSideMeters) {
573
+ const lat = Number(latitudeDegree);
574
+ const lon = Number(longitudeDegree);
575
+ if (isNaN(lat) || isNaN(lon) || lat < -90 || lat > 90 || lon < -180 || lon > 180) throw new GtfsError("Invalid latitude or longitude values", {
576
+ code: "GTFS_QUERY_INVALID",
577
+ category: "query",
578
+ details: {
579
+ latitudeDegree,
580
+ longitudeDegree,
581
+ boundingBoxSideMeters
582
+ }
583
+ });
584
+ const latitudeRadian = degree2radian(lat);
585
+ const radiusFromLatitude = Math.cos(latitudeRadian) * EARTH_RADIUS_METERS;
586
+ const halfSide = boundingBoxSideMeters / 2;
587
+ const deltaLatitude = radian2degree(halfSide / EARTH_RADIUS_METERS);
588
+ const deltaLongitude = radian2degree(halfSide / radiusFromLatitude);
589
+ return [`stop_lat BETWEEN ${lat - deltaLatitude} AND ${lat + deltaLatitude}`, `stop_lon BETWEEN ${lon - deltaLongitude} AND ${lon + deltaLongitude}`].join(" AND ");
590
+ }
591
+ /**
592
+ * Formats SQL WHERE clause for a single key-value pair
593
+ * @param key Column name
594
+ * @param value Single value, array of values, or null
595
+ * @returns Formatted WHERE clause condition
596
+ */
597
+ function formatWhereClause(key, value) {
598
+ if (Array.isArray(value)) {
599
+ let whereClause = `${sqlString.escapeId(key)} IN (${value.filter((v) => v !== null).map((v) => sqlString.escape(v)).join(", ")})`;
600
+ if (value.includes(null)) whereClause = `(${whereClause} OR ${sqlString.escapeId(key)} IS NULL)`;
601
+ return whereClause;
602
+ }
603
+ if (value === null) return `${sqlString.escapeId(key)} IS NULL`;
604
+ return `${sqlString.escapeId(key)} = ${sqlString.escape(value)}`;
605
+ }
606
+ /**
607
+ * Formats complete SQL WHERE clause from query object
608
+ * @param query Object containing column-value pairs
609
+ * @returns Formatted WHERE clause or empty string if no conditions
610
+ */
611
+ function formatWhereClauses(query) {
612
+ if (Object.keys(query).length === 0) return "";
613
+ return `WHERE ${Object.entries(query).map(([key, value]) => formatWhereClause(key, value)).join(" AND ")}`;
614
+ }
615
+ /**
616
+ * Formats SQL ORDER BY clause from array of sorting criteria
617
+ * @param orderBy Array of [column, direction] tuples
618
+ * @returns Formatted ORDER BY clause
619
+ */
620
+ function formatOrderByClause(orderBy) {
621
+ let orderByClause = "";
622
+ if (orderBy.length > 0) {
623
+ orderByClause += "ORDER BY ";
624
+ orderByClause += orderBy.map(([key, value]) => {
625
+ const direction = value === "DESC" ? "DESC" : "ASC";
626
+ return `${sqlString.escapeId(key)} ${direction}`;
627
+ }).join(", ");
628
+ }
629
+ return orderByClause;
630
+ }
631
+ /**
632
+ * Gets day of week name from YYYYMMDD date number
633
+ * @param date Date in YYYYMMDD format
634
+ * @returns Lowercase day name (sunday-saturday)
635
+ */
636
+ function getDayOfWeekFromDate(date) {
637
+ const DAYS_OF_WEEK = [
638
+ "sunday",
639
+ "monday",
640
+ "tuesday",
641
+ "wednesday",
642
+ "thursday",
643
+ "friday",
644
+ "saturday"
645
+ ];
646
+ if (!Number.isInteger(date) || date.toString().length !== 8) throw new GtfsError("Date must be in YYYYMMDD format", {
647
+ code: "GTFS_INVALID_DATE",
648
+ category: "validation",
649
+ details: { value: date }
650
+ });
651
+ const year = Math.floor(date / 1e4);
652
+ const month = Math.floor(date % 1e4 / 100);
653
+ const day = date % 100;
654
+ const dateObj = new Date(year, month - 1, day);
655
+ if (dateObj.toString() === "Invalid Date") throw new GtfsError("Invalid date", {
656
+ code: "GTFS_INVALID_DATE",
657
+ category: "validation",
658
+ details: { value: date }
659
+ });
660
+ return DAYS_OF_WEEK[dateObj.getDay()];
661
+ }
662
+ /**
663
+ * Formats a numeric value according to the decimal precision rules of the specified currency,
664
+ * without any currency symbols or separators.
665
+ * @param value The numeric value to format (e.g., 10.5)
666
+ * @param currency The ISO 4217 currency code (e.g., 'USD', 'JPY', 'EUR')
667
+ * @returns The formatted string with appropriate decimal places
668
+ * Examples:
669
+ * - formatCurrency(10.5, 'USD') => '10.50' // USD uses 2 decimal places
670
+ * - formatCurrency(10.5, 'JPY') => '10' // JPY uses 0 decimal places
671
+ * - formatCurrency(10.523, 'BHD') => '10.523' // BHD uses 3 decimal places
672
+ */
673
+ function formatCurrency(value, currency) {
674
+ const parts = new Intl.NumberFormat(void 0, {
675
+ style: "currency",
676
+ currency
677
+ }).formatToParts(value);
678
+ const integerPart = parts.find((part) => part.type === "integer")?.value ?? "0";
679
+ const fractionPart = parts.find((part) => part.type === "fraction")?.value ?? "";
680
+ return `${integerPart}${fractionPart !== "" ? `.${fractionPart}` : ""}`;
681
+ }
682
+ /**
683
+ * Gets the timestamp column name for a given column name
684
+ * @param columnName The column name
685
+ * @returns The timestamp column name
686
+ */
687
+ function getTimestampColumnName(columnName) {
688
+ return columnName.endsWith("time") ? `${columnName}stamp` : `${columnName}_timestamp`;
689
+ }
690
+ /**
691
+ * Applies a prefix to a value if the column should be prefixed and the value is not null
692
+ * @param value The value to prefix
693
+ * @param columnShouldBePrefixed Whether the column should be prefixed
694
+ * @param prefix The prefix to apply
695
+ * @returns The value with the prefix applied if the column should be prefixed and the value is not null
696
+ */
697
+ function applyPrefixToValue(value, columnShouldBePrefixed, prefix) {
698
+ if (!columnShouldBePrefixed || prefix === void 0 || value === null || value === void 0) return value;
699
+ return `${prefix}${value}`;
700
+ }
701
+ /**
702
+ * Pluralizes a word based on the count
703
+ * @param singularWord The singular word
704
+ * @param pluralWord The plural word
705
+ * @param count The count of the word
706
+ * @returns The pluralized word
707
+ */
708
+ function pluralize(singularWord, pluralWord, count) {
709
+ return count === 1 ? singularWord : pluralWord;
710
+ }
711
+
712
+ //#endregion
713
+ //#region src/lib/import-gtfs-realtime.ts
714
+ const BATCH_SIZE$1 = 1e3;
715
+ const MAX_RETRIES = 3;
716
+ const RETRY_DELAY = 1e3;
717
+ /**
718
+ * Prepares a field value for database insertion
719
+ */
720
+ function prepareRealtimeFieldValue(entity, column, task) {
721
+ if (column.name === "created_timestamp") return task.currentTimestamp;
722
+ if (column.name === "expiration_timestamp") return task.currentTimestamp + task.gtfsRealtimeExpirationSeconds;
723
+ const baseValue = column.source === void 0 ? column.default : get(entity, column.source, column.default);
724
+ const prefixedValue = applyPrefixToValue(baseValue?.__isLong__ ? convertLongTimeToDate(baseValue) : baseValue, column.prefix, task.prefix);
725
+ return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
726
+ }
727
+ /**
728
+ * Creates a prepared statement for a model
729
+ */
730
+ function createPreparedStatement(db, model) {
731
+ const columns = model.schema.map((column) => column.name);
732
+ const placeholders = model.schema.map(() => "?").join(", ");
733
+ return db.prepare(`REPLACE INTO ${model.filenameBase} (${columns.join(", ")}) VALUES (${placeholders})`);
734
+ }
735
+ /**
736
+ * Processes entities in batches
737
+ */
738
+ async function processBatch(items, batchSize, processor) {
739
+ let totalRecordCount = 0;
740
+ let totalErrorCount = 0;
741
+ for (let i = 0; i < items.length; i += batchSize) {
742
+ const batch = items.slice(i, i + batchSize);
743
+ try {
744
+ const result = await processor(batch);
745
+ totalRecordCount += result.recordCount;
746
+ totalErrorCount += result.errorCount;
747
+ } catch (error) {
748
+ const errorMessage = error instanceof Error ? error.message : String(error);
749
+ totalErrorCount += batch.length;
750
+ console.error(`Batch processing error: ${errorMessage}`);
751
+ }
752
+ }
753
+ return {
754
+ recordCount: totalRecordCount,
755
+ errorCount: totalErrorCount
756
+ };
757
+ }
758
+ /**
759
+ * Fetches GTFS Realtime data
760
+ */
761
+ async function fetchGtfsRealtimeData(type, task) {
762
+ const urlConfig = getUrlConfig(type, task);
763
+ if (!urlConfig) return null;
764
+ task.log(`Importing - GTFS-Realtime from ${urlConfig.url}`);
765
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) try {
766
+ const response = await fetch(urlConfig.url, {
767
+ method: "GET",
768
+ redirect: "follow",
769
+ headers: {
770
+ "User-Agent": "node-gtfs",
771
+ ...urlConfig.headers ?? {},
772
+ "Accept-Encoding": "gzip"
773
+ },
774
+ signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
775
+ });
776
+ if (!response.ok) throw new GtfsError(`HTTP ${response.status}: ${response.statusText}`, {
777
+ code: "GTFS_DOWNLOAD_HTTP",
778
+ category: "download",
779
+ statusCode: response.status,
780
+ details: {
781
+ url: urlConfig.url,
782
+ status: response.status,
783
+ statusText: response.statusText
784
+ }
785
+ });
786
+ const buffer = await response.arrayBuffer();
787
+ const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(new Uint8Array(buffer));
788
+ return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
789
+ enums: String,
790
+ longs: String,
791
+ bytes: String,
792
+ defaults: false,
793
+ arrays: true,
794
+ objects: true,
795
+ oneofs: true
796
+ });
797
+ } catch (error) {
798
+ const gtfsError = toGtfsError(error, {
799
+ message: error instanceof Error ? error.message : String(error),
800
+ code: "GTFS_DOWNLOAD_FAILED",
801
+ category: "download",
802
+ details: {
803
+ type,
804
+ url: urlConfig.url
805
+ }
806
+ });
807
+ if (attempt === MAX_RETRIES) {
808
+ if (task.ignoreErrors) {
809
+ task.logError(`Failed to fetch ${type} after ${MAX_RETRIES} attempts: ${gtfsError.message}`);
810
+ if (task.report) addImportError(task.report, gtfsError);
811
+ return null;
812
+ }
813
+ throw gtfsError;
814
+ }
815
+ task.logWarning(`Attempt ${attempt} failed for ${type}: ${gtfsError.message}`);
816
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY * attempt));
817
+ }
818
+ return null;
819
+ }
820
+ /**
821
+ * Gets URL configuration for a specific realtime type
822
+ */
823
+ function getUrlConfig(type, task) {
824
+ switch (type) {
825
+ case "alerts": return task.realtimeAlerts;
826
+ case "tripupdates": return task.realtimeTripUpdates;
827
+ case "vehiclepositions": return task.realtimeVehiclePositions;
828
+ default: return;
829
+ }
830
+ }
831
+ /**
832
+ * Creates a processor for service alerts
833
+ */
834
+ function createServiceAlertsProcessor(db, task) {
835
+ const alertStmt = createPreparedStatement(db, serviceAlerts);
836
+ const informedEntityStmt = createPreparedStatement(db, serviceAlertInformedEntities);
837
+ const deleteInformedEntitiesStmt = db.prepare(`DELETE FROM ${serviceAlertInformedEntities.filenameBase} WHERE alert_id = ?`);
838
+ return async (batch) => {
839
+ let recordCount = 0;
840
+ let errorCount = 0;
841
+ db.transaction(() => {
842
+ for (const entity of batch) try {
843
+ const alertId = applyPrefixToValue(entity.id, true, task.prefix);
844
+ deleteInformedEntitiesStmt.run(alertId);
845
+ const alertValues = serviceAlerts.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
846
+ alertStmt.run(alertValues);
847
+ recordCount++;
848
+ if (entity.alert?.informedEntity?.length) for (const informedEntity of entity.alert.informedEntity) {
849
+ informedEntity.parent = entity;
850
+ const entityValues = serviceAlertInformedEntities.schema.map((column) => prepareRealtimeFieldValue(informedEntity, column, task));
851
+ informedEntityStmt.run(entityValues);
852
+ recordCount++;
853
+ }
854
+ } catch (error) {
855
+ const errorMessage = error instanceof Error ? error.message : String(error);
856
+ errorCount++;
857
+ task.logWarning(`Alert processing error: ${errorMessage}`);
858
+ }
859
+ })();
860
+ return {
861
+ recordCount,
862
+ errorCount
863
+ };
864
+ };
865
+ }
866
+ /**
867
+ * Creates a processor for trip updates
868
+ */
869
+ function createTripUpdatesProcessor(db, task) {
870
+ const tripUpdateStmt = createPreparedStatement(db, tripUpdates);
871
+ const stopTimeStmt = createPreparedStatement(db, stopTimeUpdates);
872
+ const deleteStopTimesByTripStmt = db.prepare(`DELETE FROM ${stopTimeUpdates.filenameBase} WHERE trip_id = ? AND trip_start_time IS ?`);
873
+ return async (batch) => {
874
+ let recordCount = 0;
875
+ let errorCount = 0;
876
+ db.transaction(() => {
877
+ for (const entity of batch) try {
878
+ const tripUpdateValues = tripUpdates.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
879
+ tripUpdateStmt.run(tripUpdateValues);
880
+ recordCount++;
881
+ if (entity.tripUpdate?.stopTimeUpdate?.length) {
882
+ const tripId = applyPrefixToValue(entity.tripUpdate?.trip?.tripId ?? null, true, task.prefix);
883
+ const tripStartTime = entity.tripUpdate?.trip?.startTime ?? null;
884
+ if (tripId !== null) deleteStopTimesByTripStmt.run(tripId, tripStartTime);
885
+ for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
886
+ stopTimeUpdate.parent = entity;
887
+ const stopTimeValues = stopTimeUpdates.schema.map((column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task));
888
+ stopTimeStmt.run(stopTimeValues);
889
+ recordCount++;
890
+ }
891
+ }
892
+ } catch (error) {
893
+ const errorMessage = error instanceof Error ? error.message : String(error);
894
+ errorCount++;
895
+ task.logWarning(`Trip update processing error: ${errorMessage}`);
896
+ }
897
+ })();
898
+ return {
899
+ recordCount,
900
+ errorCount
901
+ };
902
+ };
903
+ }
904
+ /**
905
+ * Creates a processor for vehicle positions
906
+ */
907
+ function createVehiclePositionsProcessor(db, task) {
908
+ const vehiclePositionStmt = createPreparedStatement(db, vehiclePositions);
909
+ return async (batch) => {
910
+ let recordCount = 0;
911
+ let errorCount = 0;
912
+ db.transaction(() => {
913
+ for (const entity of batch) try {
914
+ const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
915
+ vehiclePositionStmt.run(fieldValues);
916
+ recordCount++;
917
+ } catch (error) {
918
+ const errorMessage = error instanceof Error ? error.message : String(error);
919
+ errorCount++;
920
+ task.logWarning(`Vehicle position processing error: ${errorMessage}`);
921
+ }
922
+ })();
923
+ return {
924
+ recordCount,
925
+ errorCount
926
+ };
927
+ };
928
+ }
929
+ /**
930
+ * Removes expired GTFS-Realtime data
931
+ */
932
+ function removeExpiredRealtimeData(config) {
933
+ const db = openDb(config);
934
+ log(config)(`Removing expired GTFS-Realtime data`);
935
+ db.transaction(() => {
936
+ for (const table of [
937
+ "vehicle_positions",
938
+ "trip_updates",
939
+ "stop_time_updates",
940
+ "service_alerts",
941
+ "service_alert_informed_entities"
942
+ ]) db.prepare(`DELETE FROM ${table} WHERE expiration_timestamp <= strftime('%s','now')`).run();
943
+ })();
944
+ log(config)(`Removed expired GTFS-Realtime data\r`, true);
945
+ }
946
+ /**
947
+ * Updates GTFS Realtime data
948
+ */
949
+ async function updateGtfsRealtimeData(task) {
950
+ if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) return;
951
+ const [alertsData, tripUpdatesData, vehiclePositionsData] = await Promise.all([
952
+ task.realtimeAlerts?.url ? fetchGtfsRealtimeData("alerts", task) : null,
953
+ task.realtimeTripUpdates?.url ? fetchGtfsRealtimeData("tripupdates", task) : null,
954
+ task.realtimeVehiclePositions?.url ? fetchGtfsRealtimeData("vehiclepositions", task) : null
955
+ ]);
956
+ const db = openDb({ sqlitePath: task.sqlitePath });
957
+ const recordCounts = {
958
+ alerts: 0,
959
+ tripupdates: 0,
960
+ vehiclepositions: 0
961
+ };
962
+ if (alertsData?.entity?.length) recordCounts.alerts = (await processBatch(alertsData.entity, BATCH_SIZE$1, createServiceAlertsProcessor(db, task))).recordCount;
963
+ if (tripUpdatesData?.entity?.length) recordCounts.tripupdates = (await processBatch(tripUpdatesData.entity, BATCH_SIZE$1, createTripUpdatesProcessor(db, task))).recordCount;
964
+ if (vehiclePositionsData?.entity?.length) recordCounts.vehiclepositions = (await processBatch(vehiclePositionsData.entity, BATCH_SIZE$1, createVehiclePositionsProcessor(db, task))).recordCount;
965
+ task.log(`GTFS-Realtime import complete: ${recordCounts.alerts} alerts, ${recordCounts.tripupdates} trip updates, ${recordCounts.vehiclepositions} vehicle positions`);
966
+ }
967
+ /**
968
+ * Main function to update GTFS Realtime data
969
+ */
970
+ async function updateGtfsRealtime(initialConfig) {
971
+ const config = setDefaultConfig(initialConfig);
972
+ validateConfigForImport(config);
973
+ try {
974
+ openDb(config);
975
+ const agencyCount = config.agencies.length;
976
+ log(config)(`Starting GTFS-Realtime refresh for ${pluralize("agency", "agencies", agencyCount)} using SQLite database at ${config.sqlitePath}`);
977
+ removeExpiredRealtimeData(config);
978
+ await mapSeries(config.agencies, async (agency) => {
979
+ let task;
980
+ try {
981
+ task = {
982
+ realtimeAlerts: agency.realtimeAlerts,
983
+ realtimeTripUpdates: agency.realtimeTripUpdates,
984
+ realtimeVehiclePositions: agency.realtimeVehiclePositions,
985
+ downloadTimeout: config.downloadTimeout,
986
+ gtfsRealtimeExpirationSeconds: config.gtfsRealtimeExpirationSeconds,
987
+ ignoreErrors: config.ignoreErrors,
988
+ sqlitePath: config.sqlitePath,
989
+ prefix: agency.prefix,
990
+ currentTimestamp: Math.floor(Date.now() / 1e3),
991
+ log: log(config),
992
+ logWarning: logWarning(config),
993
+ logError: logError(config)
994
+ };
995
+ await updateGtfsRealtimeData(task);
996
+ } catch (error) {
997
+ const gtfsError = toGtfsError(error, {
998
+ message: error instanceof Error ? error.message : String(error),
999
+ code: "GTFS_DB_OPERATION_FAILED",
1000
+ category: "database",
1001
+ details: { sqlitePath: task?.sqlitePath ?? config.sqlitePath }
1002
+ });
1003
+ if (config.ignoreErrors) {
1004
+ logError(config)(formatGtfsError(gtfsError));
1005
+ if (task?.report) addImportError(task.report, gtfsError);
1006
+ } else throw gtfsError;
1007
+ }
1008
+ });
1009
+ log(config)(`Completed GTFS-Realtime refresh for ${pluralize("agency", "agencies", agencyCount)}\n`);
1010
+ } catch (error) {
1011
+ if (error.code === "SQLITE_CANTOPEN") {
1012
+ const dbOpenError = new GtfsError(`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`, {
1013
+ code: "DB_OPEN_FAILED",
1014
+ category: "database",
1015
+ details: {
1016
+ sqlitePath: config.sqlitePath,
1017
+ dbCode: error.code
1018
+ },
1019
+ cause: error
1020
+ });
1021
+ logError(config)(dbOpenError.message);
1022
+ throw dbOpenError;
1023
+ }
1024
+ throw toGtfsError(error, {
1025
+ message: error instanceof Error ? error.message : String(error),
1026
+ code: "GTFS_DB_OPERATION_FAILED",
1027
+ category: "database"
1028
+ });
1029
+ }
1030
+ }
1031
+
1032
+ //#endregion
1033
+ //#region src/lib/import-gtfs.ts
1034
+ function reportTaskError(task, error) {
1035
+ if (task.report) addImportError(task.report, error);
1036
+ }
1037
+ const getTextFiles = async (folderPath) => {
1038
+ return (await readdir(folderPath)).filter((filename) => filename.slice(-3) === "txt");
1039
+ };
1040
+ const downloadGtfsFiles = async (task) => {
1041
+ if (!task.url) throw new GtfsError("No `url` specified in config", {
1042
+ code: "GTFS_CONFIG_INVALID",
1043
+ category: "config"
1044
+ });
1045
+ task.log(`Downloading GTFS from ${task.url}`);
1046
+ task.path = `${task.downloadDir}/gtfs.zip`;
1047
+ try {
1048
+ const response = await fetch(task.url, {
1049
+ method: "GET",
1050
+ redirect: "follow",
1051
+ headers: {
1052
+ "User-Agent": "node-gtfs",
1053
+ ...task.headers
1054
+ },
1055
+ signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
1056
+ });
1057
+ if (!response.ok) throw new GtfsError(`Unable to download GTFS from ${task.url}. Got status ${response.status}.`, {
1058
+ code: "GTFS_DOWNLOAD_HTTP",
1059
+ category: "download",
1060
+ statusCode: response.status,
1061
+ details: {
1062
+ url: task.url,
1063
+ status: response.status,
1064
+ statusText: response.statusText
1065
+ }
1066
+ });
1067
+ const buffer = await response.arrayBuffer();
1068
+ await writeFile(task.path, Buffer.from(buffer));
1069
+ task.log("Download successful");
1070
+ } catch (error) {
1071
+ throw toGtfsError(error, {
1072
+ message: `Unable to download GTFS from ${task.url}.`,
1073
+ code: "GTFS_DOWNLOAD_FAILED",
1074
+ category: "download",
1075
+ details: { url: task.url }
1076
+ });
1077
+ }
1078
+ };
1079
+ const extractGtfsFiles = async (task) => {
1080
+ if (!task.path) throw new GtfsError("No `path` specified in config", {
1081
+ code: "GTFS_CONFIG_INVALID",
1082
+ category: "config",
1083
+ details: { field: "path" }
1084
+ });
1085
+ const gtfsPath = untildify(task.path);
1086
+ task.log(`Importing static GTFS from ${task.path}\r`);
1087
+ if (path.extname(gtfsPath) === ".zip") try {
1088
+ await unzip(gtfsPath, task.downloadDir);
1089
+ if ((await getTextFiles(task.downloadDir)).length === 0) {
1090
+ const folders = (await readdir(task.downloadDir)).filter((filename) => !["__MACOSX"].includes(filename)).map((filename) => path.join(task.downloadDir, filename)).filter((source) => lstatSync(source).isDirectory());
1091
+ if (folders.length > 1) throw new GtfsError(`More than one subfolder found in zip file at \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`, {
1092
+ code: "GTFS_ZIP_INVALID",
1093
+ category: "zip",
1094
+ details: {
1095
+ path: task.path,
1096
+ folderCount: folders.length
1097
+ }
1098
+ });
1099
+ else if (folders.length === 0) throw new GtfsError(`No .txt files found in \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`, {
1100
+ code: "GTFS_ZIP_INVALID",
1101
+ category: "zip",
1102
+ details: { path: task.path }
1103
+ });
1104
+ const subfolderName = folders[0];
1105
+ const directoryTextFiles = await getTextFiles(subfolderName);
1106
+ if (directoryTextFiles.length === 0) throw new GtfsError(`No .txt files found in \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`, {
1107
+ code: "GTFS_ZIP_INVALID",
1108
+ category: "zip",
1109
+ details: {
1110
+ path: task.path,
1111
+ subfolderName
1112
+ }
1113
+ });
1114
+ await Promise.all(directoryTextFiles.map(async (fileName) => rename(path.join(subfolderName, fileName), path.join(task.downloadDir, fileName))));
1115
+ }
1116
+ } catch (error) {
1117
+ const wrappedError = toGtfsError(error, {
1118
+ message: `Unable to unzip file ${task.path}`,
1119
+ code: "GTFS_ZIP_INVALID",
1120
+ category: "zip",
1121
+ details: { path: task.path }
1122
+ });
1123
+ task.logError(formatGtfsError(wrappedError));
1124
+ throw wrappedError;
1125
+ }
1126
+ else try {
1127
+ await cp(gtfsPath, task.downloadDir, { recursive: true });
1128
+ } catch (error) {
1129
+ throw new GtfsError(`Unable to load files from path \`${gtfsPath}\` defined in configuration. Verify that path exists and contains GTFS files.`, {
1130
+ code: "GTFS_DOWNLOAD_FAILED",
1131
+ category: "download",
1132
+ details: { path: gtfsPath },
1133
+ cause: error
1134
+ });
1135
+ }
1136
+ };
1137
+ const createGtfsTables = (db) => {
1138
+ for (const model of Object.values(models_exports)) {
1139
+ if (!model.schema) continue;
1140
+ const sqlColumnCreateStatements = [];
1141
+ for (const column of model.schema) {
1142
+ const checks = [];
1143
+ if (column.min !== void 0 && column.max !== void 0) checks.push(`${column.name} >= ${column.min} AND ${column.name} <= ${column.max}`);
1144
+ else if (column.min !== void 0) checks.push(`${column.name} >= ${column.min}`);
1145
+ else if (column.max !== void 0) checks.push(`${column.name} <= ${column.max}`);
1146
+ if (column.type === "integer") checks.push(`(TYPEOF(${column.name}) = 'integer' OR ${column.name} IS NULL)`);
1147
+ else if (column.type === "real") checks.push(`(TYPEOF(${column.name}) = 'real' OR ${column.name} IS NULL)`);
1148
+ const required = column.required ? "NOT NULL" : "";
1149
+ const columnDefault = column.default ? "DEFAULT " + column.default : "";
1150
+ const columnCollation = column.nocase ? "COLLATE NOCASE" : "";
1151
+ const checkClause = checks.length > 0 ? `CHECK(${checks.join(" AND ")})` : "";
1152
+ sqlColumnCreateStatements.push(`${column.name} ${column.type} ${checkClause} ${required} ${columnDefault} ${columnCollation}`);
1153
+ if (column.type === "time") sqlColumnCreateStatements.push(`${getTimestampColumnName(column.name)} INTEGER GENERATED ALWAYS AS (
1154
+ CASE
1155
+ WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
1156
+ ELSE CAST(
1157
+ substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
1158
+ substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
1159
+ substr(${column.name}, -2) AS INTEGER
1160
+ )
1161
+ END
1162
+ ) STORED`);
1163
+ }
1164
+ const primaryColumns = model.schema.filter((column) => column.primary);
1165
+ if (primaryColumns.length > 0) sqlColumnCreateStatements.push(`PRIMARY KEY (${primaryColumns.map(({ name }) => name).join(", ")})`);
1166
+ db.prepare(`DROP TABLE IF EXISTS ${model.filenameBase};`).run();
1167
+ db.prepare(`CREATE TABLE ${model.filenameBase} (${sqlColumnCreateStatements.join(", ")});`).run();
1168
+ }
1169
+ };
1170
+ const createGtfsIndexes = (db) => {
1171
+ for (const model of Object.values(models_exports)) {
1172
+ if (!model.schema) continue;
1173
+ for (const column of model.schema) {
1174
+ if (column.index) db.prepare(`CREATE INDEX idx_${model.filenameBase}_${column.name} ON ${model.filenameBase} (${column.name});`).run();
1175
+ if (column.type === "time") {
1176
+ const timestampColumnName = getTimestampColumnName(column.name);
1177
+ db.prepare(`CREATE INDEX idx_${model.filenameBase}_${timestampColumnName} ON ${model.filenameBase} (${timestampColumnName});`).run();
1178
+ }
1179
+ }
1180
+ }
1181
+ };
1182
+ const formatGtfsLine = (line, model, totalLineCount) => {
1183
+ const lineNumber = totalLineCount + 1;
1184
+ const formattedLine = {};
1185
+ const filenameBase = model.filenameBase;
1186
+ const filenameExtension = model.filenameExtension;
1187
+ for (const { name, type, required } of model.schema) {
1188
+ let value = line[name];
1189
+ if (value === "" || value === void 0 || value === null) {
1190
+ formattedLine[name] = null;
1191
+ if (required) throw new GtfsError(`Missing required value in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`, {
1192
+ code: "GTFS_REQUIRED_FIELD_MISSING",
1193
+ category: "validation",
1194
+ details: {
1195
+ file: `${filenameBase}.${filenameExtension}`,
1196
+ line: lineNumber,
1197
+ column: name
1198
+ }
1199
+ });
1200
+ continue;
1201
+ }
1202
+ if (type === "date") {
1203
+ value = value?.toString().replace(/-/g, "");
1204
+ if (value.length !== 8) throw new GtfsError(`Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`, {
1205
+ code: "GTFS_INVALID_DATE",
1206
+ category: "validation",
1207
+ details: {
1208
+ file: `${filenameBase}.${filenameExtension}`,
1209
+ line: lineNumber,
1210
+ column: name,
1211
+ value
1212
+ }
1213
+ });
1214
+ } else if (type === "time") value = padLeadingZeros(value);
1215
+ if (type === "json") value = JSON.stringify(value);
1216
+ formattedLine[name] = value;
1217
+ }
1218
+ return formattedLine;
1219
+ };
1220
+ const BATCH_SIZE = 1e5;
1221
+ const importGtfsFiles = async (db, task) => {
1222
+ await mapSeries(Object.values(models_exports), (model) => new Promise((resolve, reject) => {
1223
+ let totalLineCount = 0;
1224
+ const filename = `${model.filenameBase}.${model.filenameExtension}`;
1225
+ if (task.exclude && task.exclude.includes(model.filenameBase)) {
1226
+ task.log(`Skipping - ${filename}\r`);
1227
+ resolve();
1228
+ return;
1229
+ }
1230
+ if (model.extension === "gtfs-realtime") {
1231
+ resolve();
1232
+ return;
1233
+ }
1234
+ const filepath = path.join(task.downloadDir, `${filename}`);
1235
+ if (!existsSync(filepath)) {
1236
+ if (!model.nonstandard) task.log(`Importing - ${filename} - No file found\r`);
1237
+ resolve();
1238
+ return;
1239
+ }
1240
+ task.log(`Importing - ${filename}\r`);
1241
+ const columns = model.schema;
1242
+ const prefixedColumns = new Set(columns.filter((column) => column.prefix).map((column) => column.name));
1243
+ const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
1244
+ const insert = db.prepare(prepareStatement);
1245
+ const insertLines = db.transaction((lines) => {
1246
+ for (const [rowNumber, line] of Object.entries(lines)) try {
1247
+ if (task.prefix === void 0) insert.run(line);
1248
+ else {
1249
+ const prefixedLine = Object.fromEntries(Object.entries(line).map(([columnName, value]) => [columnName, applyPrefixToValue(value, prefixedColumns.has(columnName), task.prefix)]));
1250
+ insert.run(prefixedLine);
1251
+ }
1252
+ } catch (error) {
1253
+ if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
1254
+ const primaryColumns = columns.filter((column) => column.primary);
1255
+ task.logWarning(`Duplicate values for primary key (${primaryColumns.map((column) => column.name).join(", ")}) found in ${filename}. Set the \`ignoreDuplicates\` option to true in config.json to ignore this error`);
1256
+ if (task.report) addImportWarning(task.report, {
1257
+ code: "GTFS_DUPLICATE_PRIMARY_KEY",
1258
+ message: `Duplicate values for primary key found in ${filename}.`,
1259
+ details: {
1260
+ file: filename,
1261
+ line: Number(rowNumber) + 1,
1262
+ columns: primaryColumns.map((column) => column.name)
1263
+ }
1264
+ });
1265
+ }
1266
+ task.logWarning(`Check ${filename} for invalid data on line ${rowNumber + 1}.`);
1267
+ throw toGtfsError(error, {
1268
+ message: error instanceof Error ? error.message : String(error),
1269
+ code: "GTFS_DB_OPERATION_FAILED",
1270
+ category: "database",
1271
+ details: {
1272
+ file: filename,
1273
+ line: Number(rowNumber) + 1,
1274
+ sqlitePath: task.sqlitePath,
1275
+ dbCode: error.code
1276
+ }
1277
+ });
1278
+ }
1279
+ });
1280
+ if (model.filenameExtension === "txt") {
1281
+ const parser = parse({
1282
+ columns: true,
1283
+ relax_quotes: true,
1284
+ trim: true,
1285
+ skip_empty_lines: true,
1286
+ ...task.csvOptions
1287
+ });
1288
+ let lines = [];
1289
+ parser.on("readable", () => {
1290
+ try {
1291
+ let record;
1292
+ while (record = parser.read()) {
1293
+ totalLineCount += 1;
1294
+ lines.push(formatGtfsLine(record, model, totalLineCount));
1295
+ if (lines.length >= BATCH_SIZE) {
1296
+ insertLines(lines);
1297
+ lines = [];
1298
+ task.log(`Importing - ${filename} - ${totalLineCount} lines imported\r`, true);
1299
+ }
1300
+ }
1301
+ } catch (error) {
1302
+ const gtfsError = toGtfsError(error, {
1303
+ message: error instanceof Error ? error.message : String(error),
1304
+ code: "GTFS_CSV_PARSE_FAILED",
1305
+ category: "parse",
1306
+ details: { file: filename }
1307
+ });
1308
+ if (task.ignoreErrors) {
1309
+ reportTaskError(task, gtfsError);
1310
+ task.logError(`Error processing ${filename}: ${gtfsError.message}`);
1311
+ resolve();
1312
+ } else reject(gtfsError);
1313
+ }
1314
+ });
1315
+ parser.on("end", () => {
1316
+ try {
1317
+ if (lines.length > 0) try {
1318
+ insertLines(lines);
1319
+ } catch (error) {
1320
+ const gtfsError = toGtfsError(error, {
1321
+ message: error instanceof Error ? error.message : String(error),
1322
+ code: "GTFS_DB_OPERATION_FAILED",
1323
+ category: "database",
1324
+ details: {
1325
+ file: filename,
1326
+ sqlitePath: task.sqlitePath
1327
+ }
1328
+ });
1329
+ if (task.ignoreErrors) {
1330
+ task.logError(`Error inserting data for ${filename}: ${gtfsError.message}`);
1331
+ reportTaskError(task, gtfsError);
1332
+ resolve();
1333
+ return;
1334
+ } else {
1335
+ reject(gtfsError);
1336
+ return;
1337
+ }
1338
+ }
1339
+ task.log(`Importing - ${filename} - ${totalLineCount} lines imported\r`, true);
1340
+ resolve();
1341
+ } catch (error) {
1342
+ const gtfsError = toGtfsError(error, {
1343
+ message: error instanceof Error ? error.message : String(error),
1344
+ code: "GTFS_DB_OPERATION_FAILED",
1345
+ category: "database",
1346
+ details: {
1347
+ file: filename,
1348
+ sqlitePath: task.sqlitePath
1349
+ }
1350
+ });
1351
+ if (task.ignoreErrors) {
1352
+ task.logError(`Error finalizing ${filename}: ${gtfsError.message}`);
1353
+ reportTaskError(task, gtfsError);
1354
+ resolve();
1355
+ } else reject(gtfsError);
1356
+ }
1357
+ });
1358
+ parser.on("error", (error) => {
1359
+ const gtfsError = toGtfsError(error, {
1360
+ message: error instanceof Error ? error.message : String(error),
1361
+ code: "GTFS_CSV_PARSE_FAILED",
1362
+ category: "parse",
1363
+ details: { file: filename }
1364
+ });
1365
+ if (task.ignoreErrors) {
1366
+ task.logError(`Parser error for ${filename}: ${gtfsError.message}`);
1367
+ reportTaskError(task, gtfsError);
1368
+ resolve();
1369
+ } else reject(gtfsError);
1370
+ });
1371
+ createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
1372
+ } else if (model.filenameExtension === "geojson") readFile(filepath, "utf8").then((data) => {
1373
+ if (isValidJSON(data) === false) if (task.ignoreErrors) {
1374
+ task.logError(`Invalid JSON in ${filename}`);
1375
+ reportTaskError(task, new GtfsError(`Invalid JSON in ${filename}`, {
1376
+ code: "GTFS_JSON_INVALID",
1377
+ category: "parse",
1378
+ details: { file: filename }
1379
+ }));
1380
+ resolve();
1381
+ return;
1382
+ } else {
1383
+ reject(new GtfsError(`Invalid JSON in ${filename}`, {
1384
+ code: "GTFS_JSON_INVALID",
1385
+ category: "parse",
1386
+ details: { file: filename }
1387
+ }));
1388
+ return;
1389
+ }
1390
+ totalLineCount += 1;
1391
+ const line = formatGtfsLine({ geojson: data }, model, totalLineCount);
1392
+ try {
1393
+ insertLines([line]);
1394
+ task.log(`Importing - ${filename} - ${totalLineCount} lines imported\r`, true);
1395
+ resolve();
1396
+ } catch (error) {
1397
+ const gtfsError = toGtfsError(error, {
1398
+ message: error instanceof Error ? error.message : String(error),
1399
+ code: "GTFS_DB_OPERATION_FAILED",
1400
+ category: "database",
1401
+ details: {
1402
+ file: filename,
1403
+ sqlitePath: task.sqlitePath
1404
+ }
1405
+ });
1406
+ if (task.ignoreErrors) {
1407
+ task.logError(`Error inserting data for ${filename}: ${gtfsError.message}`);
1408
+ reportTaskError(task, gtfsError);
1409
+ resolve();
1410
+ } else reject(gtfsError);
1411
+ }
1412
+ }).catch((error) => {
1413
+ const gtfsError = toGtfsError(error, {
1414
+ message: error instanceof Error ? error.message : String(error),
1415
+ code: "GTFS_CSV_PARSE_FAILED",
1416
+ category: "parse",
1417
+ details: { file: filename }
1418
+ });
1419
+ if (task.ignoreErrors) {
1420
+ task.logError(`Error reading ${filename}: ${gtfsError.message}`);
1421
+ reportTaskError(task, gtfsError);
1422
+ resolve();
1423
+ } else reject(gtfsError);
1424
+ });
1425
+ else if (task.ignoreErrors) {
1426
+ task.logError(`Unsupported file type: ${model.filenameExtension} for ${filename}`);
1427
+ resolve();
1428
+ } else reject(new GtfsError(`Unsupported file type: ${model.filenameExtension}`, {
1429
+ code: "GTFS_UNSUPPORTED_FILE_TYPE",
1430
+ category: "parse",
1431
+ details: {
1432
+ file: filename,
1433
+ extension: model.filenameExtension
1434
+ }
1435
+ }));
1436
+ }));
1437
+ task.log(`Static GTFS import complete`);
1438
+ };
1439
+ async function importGtfs(initialConfig) {
1440
+ const startTime = process.hrtime.bigint();
1441
+ const config = setDefaultConfig(initialConfig);
1442
+ validateConfigForImport(config);
1443
+ const report = config.includeImportReport ? createImportReport() : void 0;
1444
+ try {
1445
+ const db = openDb(config);
1446
+ const agencyCount = config.agencies.length;
1447
+ log(config)(`Starting GTFS import for ${pluralize("file", "files", agencyCount)} using SQLite database at ${config.sqlitePath}`);
1448
+ createGtfsTables(db);
1449
+ await mapSeries(config.agencies, async (agency) => {
1450
+ try {
1451
+ const tempPath = temporaryDirectory();
1452
+ const task = {
1453
+ exclude: agency.exclude,
1454
+ headers: agency.headers,
1455
+ realtimeAlerts: agency.realtimeAlerts,
1456
+ realtimeTripUpdates: agency.realtimeTripUpdates,
1457
+ realtimeVehiclePositions: agency.realtimeVehiclePositions,
1458
+ downloadDir: tempPath,
1459
+ downloadTimeout: config.downloadTimeout,
1460
+ gtfsRealtimeExpirationSeconds: config.gtfsRealtimeExpirationSeconds,
1461
+ csvOptions: config.csvOptions || {},
1462
+ ignoreDuplicates: config.ignoreDuplicates,
1463
+ ignoreErrors: config.ignoreErrors,
1464
+ sqlitePath: config.sqlitePath,
1465
+ prefix: agency.prefix,
1466
+ currentTimestamp: Math.floor(Date.now() / 1e3),
1467
+ log: log(config),
1468
+ logWarning: logWarning(config),
1469
+ logError: logError(config),
1470
+ report
1471
+ };
1472
+ if ("url" in agency) {
1473
+ Object.assign(task, { url: agency.url });
1474
+ await downloadGtfsFiles(task);
1475
+ } else Object.assign(task, { path: agency.path });
1476
+ await extractGtfsFiles(task);
1477
+ await importGtfsFiles(db, task);
1478
+ await updateGtfsRealtimeData(task);
1479
+ await rm(tempPath, { recursive: true });
1480
+ } catch (error) {
1481
+ const wrappedError = toGtfsError(error, {
1482
+ message: error instanceof Error ? error.message : String(error),
1483
+ code: "GTFS_CSV_PARSE_FAILED",
1484
+ category: "parse"
1485
+ });
1486
+ if (config.ignoreErrors) {
1487
+ logError(config)(formatGtfsError(wrappedError));
1488
+ if (report) addImportError(report, wrappedError);
1489
+ } else throw wrappedError;
1490
+ }
1491
+ });
1492
+ log(config)(`Creating DB indexes`);
1493
+ createGtfsIndexes(db);
1494
+ const endTime = process.hrtime.bigint();
1495
+ const elapsedSeconds = Number(endTime - startTime) / 1e9;
1496
+ log(config)(`Completed GTFS import in ${elapsedSeconds.toFixed(1)} seconds\n`);
1497
+ } catch (error) {
1498
+ if (error.code === "SQLITE_CANTOPEN") {
1499
+ const dbOpenError = new GtfsError(`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`, {
1500
+ code: "DB_OPEN_FAILED",
1501
+ category: "database",
1502
+ details: {
1503
+ sqlitePath: config.sqlitePath,
1504
+ dbCode: error.code
1505
+ },
1506
+ cause: error
1507
+ });
1508
+ logError(config)(dbOpenError.message);
1509
+ throw dbOpenError;
1510
+ }
1511
+ throw toGtfsError(error, {
1512
+ message: error instanceof Error ? error.message : String(error),
1513
+ code: "GTFS_CSV_PARSE_FAILED",
1514
+ category: "parse"
1515
+ });
1516
+ }
1517
+ if (report) return report;
1518
+ }
1519
+
1520
+ //#endregion
1521
+ //#region src/lib/export.ts
1522
+ const getAgencies$1 = (db, config) => {
1523
+ try {
1524
+ return db.prepare("SELECT agency_name FROM agency;").all();
1525
+ } catch {
1526
+ if (config.sqlitePath === ":memory:") throw new Error("No agencies found in SQLite. You are using an in-memory database - if running this from command line be sure to specify a value for `sqlitePath` in config.json other than \":memory:\".");
1527
+ throw new Error("No agencies found in SQLite. Be sure to first import data into SQLite using `gtfs-import` or `importGtfs(config);`");
1528
+ }
1529
+ };
1530
+ const exportGtfs = async (initialConfig) => {
1531
+ const config = setDefaultConfig(initialConfig);
1532
+ const db = openDb(config);
1533
+ const agencies = getAgencies$1(db, config);
1534
+ const agencyCount = agencies.length;
1535
+ if (agencyCount === 0) throw new Error("No agencies found in SQLite. Be sure to first import data into SQLite using `gtfs-import` or `importGtfs(config);`");
1536
+ else if (agencyCount > 1) logWarning(config)("More than one agency is defined in config.json. Export will merge all into one GTFS file.");
1537
+ log(config)(`Starting GTFS export for ${pluralize("agency", "agencies", agencyCount)} using SQLite database at ${config.sqlitePath}`);
1538
+ const folderName = generateFolderName(agencies[0].agency_name);
1539
+ const defaultExportPath = path.join(process.cwd(), "gtfs-export", folderName);
1540
+ const exportPath = untildify(config.exportPath || defaultExportPath);
1541
+ await prepDirectory(exportPath);
1542
+ if (compact(await mapSeries(Object.values(models_exports).filter((model) => model.extension !== "gtfs-realtime"), async (model) => {
1543
+ const filePath = path.join(exportPath, `${model.filenameBase}.${model.filenameExtension}`);
1544
+ const tableName = sqlString.escapeId(model.filenameBase);
1545
+ const lines = db.prepare(`SELECT * FROM ${tableName};`).all();
1546
+ if (!lines || lines.length === 0) {
1547
+ if (!model.nonstandard) log(config)(`Skipping (no data) - ${model.filenameBase}.${model.filenameExtension}\r`);
1548
+ return;
1549
+ }
1550
+ if (model.filenameExtension === "txt") {
1551
+ const excludeColumns = [];
1552
+ if (model.filenameBase === "routes") {
1553
+ const routesWithAgencyId = db.prepare("SELECT agency_id FROM routes WHERE agency_id IS NOT NULL;").all();
1554
+ if (!routesWithAgencyId || routesWithAgencyId.length === 0) excludeColumns.push("agency_id");
1555
+ } else if (model.filenameBase === "fare_attributes") for (const line of lines) line.price = formatCurrency(line.price, line.currency_type);
1556
+ else if (model.filenameBase === "fare_products") for (const line of lines) line.amount = formatCurrency(line.amount, line.currency);
1557
+ await writeFile(filePath, await stringify(lines, {
1558
+ columns: without(model.schema.map((column) => column.name), ...excludeColumns),
1559
+ header: true
1560
+ }));
1561
+ } else if (model.filenameExtension === "geojson") await writeFile(filePath, lines?.[0].geojson ?? "");
1562
+ else throw new Error(`Unexpected filename extension: ${model.filenameExtension}`);
1563
+ log(config)(`Exporting - ${model.filenameBase}.${model.filenameExtension}\r`);
1564
+ return `${model.filenameBase}.${model.filenameExtension}`;
1565
+ })).length === 0) {
1566
+ log(config)("No GTFS data exported. Be sure to first import data into SQLite.");
1567
+ return;
1568
+ }
1569
+ log(config)(`Completed GTFS export to ${exportPath}`);
1570
+ log(config)(`Completed GTFS export for ${pluralize("agency", "agencies", agencyCount)}\n`);
1571
+ };
1572
+
1573
+ //#endregion
1574
+ //#region src/lib/advancedQuery.ts
1575
+ function advancedQuery(table, advancedQueryOptions) {
1576
+ const queryOptions = {
1577
+ query: {},
1578
+ fields: [],
1579
+ orderBy: [],
1580
+ join: [],
1581
+ options: {},
1582
+ ...advancedQueryOptions
1583
+ };
1584
+ const db = queryOptions.options.db ?? openDb();
1585
+ const tableName = sqlString.escapeId(table);
1586
+ const selectClause = formatSelectClause(queryOptions.fields);
1587
+ const whereClause = formatWhereClauses(queryOptions.query);
1588
+ const joinClause = formatJoinClause(queryOptions.join);
1589
+ const orderByClause = formatOrderByClause(queryOptions.orderBy);
1590
+ return db.prepare(`${selectClause} FROM ${tableName} ${joinClause} ${whereClause} ${orderByClause};`).all();
1591
+ }
1592
+
1593
+ //#endregion
1594
+ //#region src/lib/gtfs/agencies.ts
1595
+ function getAgencies(query = {}, fields = [], orderBy = [], options = {}) {
1596
+ const db = options.db ?? openDb();
1597
+ const tableName = "agency";
1598
+ const selectClause = formatSelectClause(fields);
1599
+ const whereClause = formatWhereClauses(query);
1600
+ const orderByClause = formatOrderByClause(orderBy);
1601
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1602
+ }
1603
+
1604
+ //#endregion
1605
+ //#region src/lib/gtfs/areas.ts
1606
+ function getAreas(query = {}, fields = [], orderBy = [], options = {}) {
1607
+ const db = options.db ?? openDb();
1608
+ const tableName = "areas";
1609
+ const selectClause = formatSelectClause(fields);
1610
+ const whereClause = formatWhereClauses(query);
1611
+ const orderByClause = formatOrderByClause(orderBy);
1612
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1613
+ }
1614
+
1615
+ //#endregion
1616
+ //#region src/lib/gtfs/attributions.ts
1617
+ function getAttributions(query = {}, fields = [], orderBy = [], options = {}) {
1618
+ const db = options.db ?? openDb();
1619
+ const tableName = "attributions";
1620
+ const selectClause = formatSelectClause(fields);
1621
+ const whereClause = formatWhereClauses(query);
1622
+ const orderByClause = formatOrderByClause(orderBy);
1623
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1624
+ }
1625
+
1626
+ //#endregion
1627
+ //#region src/lib/gtfs/booking-rules.ts
1628
+ function getBookingRules(query = {}, fields = [], orderBy = [], options = {}) {
1629
+ const db = options.db ?? openDb();
1630
+ const tableName = "booking_rules";
1631
+ const selectClause = formatSelectClause(fields);
1632
+ const whereClause = formatWhereClauses(query);
1633
+ const orderByClause = formatOrderByClause(orderBy);
1634
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1635
+ }
1636
+
1637
+ //#endregion
1638
+ //#region src/lib/gtfs/calendar-dates.ts
1639
+ function getCalendarDates(query = {}, fields = [], orderBy = [], options = {}) {
1640
+ const db = options.db ?? openDb();
1641
+ const tableName = "calendar_dates";
1642
+ const selectClause = formatSelectClause(fields);
1643
+ const whereClause = formatWhereClauses(query);
1644
+ const orderByClause = formatOrderByClause(orderBy);
1645
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1646
+ }
1647
+
1648
+ //#endregion
1649
+ //#region src/lib/gtfs/calendars.ts
1650
+ function getCalendars(query = {}, fields = [], orderBy = [], options = {}) {
1651
+ const db = options.db ?? openDb();
1652
+ const tableName = "calendar";
1653
+ const selectClause = formatSelectClause(fields);
1654
+ const whereClause = formatWhereClauses(query);
1655
+ const orderByClause = formatOrderByClause(orderBy);
1656
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1657
+ }
1658
+ function getServiceIdsByDate(date, options = {}) {
1659
+ const db = options.db ?? openDb();
1660
+ if (!date) throw new GtfsError("`date` is a required query parameter", {
1661
+ code: "GTFS_QUERY_INVALID",
1662
+ category: "query",
1663
+ details: { field: "date" }
1664
+ });
1665
+ const dayOfWeek = getDayOfWeekFromDate(date);
1666
+ return db.prepare(`
1667
+ SELECT service_id FROM (
1668
+ SELECT service_id
1669
+ FROM calendar
1670
+ WHERE start_date <= ? AND end_date >= ? AND ${dayOfWeek} = 1
1671
+ UNION
1672
+ SELECT service_id
1673
+ FROM calendar_dates
1674
+ WHERE date = ? AND exception_type = 1
1675
+ )
1676
+ EXCEPT
1677
+ SELECT service_id
1678
+ FROM calendar_dates
1679
+ WHERE date = ? AND exception_type = 2
1680
+ `).all(date, date, date, date).map((record) => record.service_id);
1681
+ }
1682
+
1683
+ //#endregion
1684
+ //#region src/lib/gtfs/fare-attributes.ts
1685
+ function getFareAttributes(query = {}, fields = [], orderBy = [], options = {}) {
1686
+ const db = options.db ?? openDb();
1687
+ const tableName = "fare_attributes";
1688
+ const selectClause = formatSelectClause(fields);
1689
+ const whereClause = formatWhereClauses(query);
1690
+ const orderByClause = formatOrderByClause(orderBy);
1691
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1692
+ }
1693
+
1694
+ //#endregion
1695
+ //#region src/lib/gtfs/fare-leg-rules.ts
1696
+ function getFareLegRules(query = {}, fields = [], orderBy = [], options = {}) {
1697
+ const db = options.db ?? openDb();
1698
+ const tableName = "fare_leg_rules";
1699
+ const selectClause = formatSelectClause(fields);
1700
+ const whereClause = formatWhereClauses(query);
1701
+ const orderByClause = formatOrderByClause(orderBy);
1702
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1703
+ }
1704
+
1705
+ //#endregion
1706
+ //#region src/lib/gtfs/fare-media.ts
1707
+ function getFareMedia(query = {}, fields = [], orderBy = [], options = {}) {
1708
+ const db = options.db ?? openDb();
1709
+ const tableName = "fare_media";
1710
+ const selectClause = formatSelectClause(fields);
1711
+ const whereClause = formatWhereClauses(query);
1712
+ const orderByClause = formatOrderByClause(orderBy);
1713
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1714
+ }
1715
+
1716
+ //#endregion
1717
+ //#region src/lib/gtfs/fare-products.ts
1718
+ function getFareProducts(query = {}, fields = [], orderBy = [], options = {}) {
1719
+ const db = options.db ?? openDb();
1720
+ const tableName = "fare_products";
1721
+ const selectClause = formatSelectClause(fields);
1722
+ const whereClause = formatWhereClauses(query);
1723
+ const orderByClause = formatOrderByClause(orderBy);
1724
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1725
+ }
1726
+
1727
+ //#endregion
1728
+ //#region src/lib/gtfs/fare-rules.ts
1729
+ function getFareRules(query = {}, fields = [], orderBy = [], options = {}) {
1730
+ const db = options.db ?? openDb();
1731
+ const tableName = "fare_rules";
1732
+ const selectClause = formatSelectClause(fields);
1733
+ const whereClause = formatWhereClauses(query);
1734
+ const orderByClause = formatOrderByClause(orderBy);
1735
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1736
+ }
1737
+
1738
+ //#endregion
1739
+ //#region src/lib/gtfs/fare-transfer-rules.ts
1740
+ function getFareTransferRules(query = {}, fields = [], orderBy = [], options = {}) {
1741
+ const db = options.db ?? openDb();
1742
+ const tableName = "fare_transfer_rules";
1743
+ const selectClause = formatSelectClause(fields);
1744
+ const whereClause = formatWhereClauses(query);
1745
+ const orderByClause = formatOrderByClause(orderBy);
1746
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1747
+ }
1748
+
1749
+ //#endregion
1750
+ //#region src/lib/gtfs/feed-info.ts
1751
+ function getFeedInfo(query = {}, fields = [], orderBy = [], options = {}) {
1752
+ const db = options.db ?? openDb();
1753
+ const tableName = "feed_info";
1754
+ const selectClause = formatSelectClause(fields);
1755
+ const whereClause = formatWhereClauses(query);
1756
+ const orderByClause = formatOrderByClause(orderBy);
1757
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1758
+ }
1759
+
1760
+ //#endregion
1761
+ //#region src/lib/gtfs/frequencies.ts
1762
+ function getFrequencies(query = {}, fields = [], orderBy = [], options = {}) {
1763
+ const db = options.db ?? openDb();
1764
+ const tableName = "frequencies";
1765
+ const selectClause = formatSelectClause(fields);
1766
+ const whereClause = formatWhereClauses(query);
1767
+ const orderByClause = formatOrderByClause(orderBy);
1768
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1769
+ }
1770
+
1771
+ //#endregion
1772
+ //#region src/lib/gtfs/levels.ts
1773
+ function getLevels(query = {}, fields = [], orderBy = [], options = {}) {
1774
+ const db = options.db ?? openDb();
1775
+ const tableName = "levels";
1776
+ const selectClause = formatSelectClause(fields);
1777
+ const whereClause = formatWhereClauses(query);
1778
+ const orderByClause = formatOrderByClause(orderBy);
1779
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1780
+ }
1781
+
1782
+ //#endregion
1783
+ //#region src/lib/gtfs/location-groups.ts
1784
+ function getLocationGroups(query = {}, fields = [], orderBy = [], options = {}) {
1785
+ const db = options.db ?? openDb();
1786
+ const tableName = "location_groups";
1787
+ const selectClause = formatSelectClause(fields);
1788
+ const whereClause = formatWhereClauses(query);
1789
+ const orderByClause = formatOrderByClause(orderBy);
1790
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1791
+ }
1792
+
1793
+ //#endregion
1794
+ //#region src/lib/gtfs/location-group-stops.ts
1795
+ function getLocationGroupStops(query = {}, fields = [], orderBy = [], options = {}) {
1796
+ const db = options.db ?? openDb();
1797
+ const tableName = "location_group_stops";
1798
+ const selectClause = formatSelectClause(fields);
1799
+ const whereClause = formatWhereClauses(query);
1800
+ const orderByClause = formatOrderByClause(orderBy);
1801
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1802
+ }
1803
+
1804
+ //#endregion
1805
+ //#region src/lib/gtfs/locations.ts
1806
+ function getLocations(query = {}, fields = [], orderBy = [], options = {}) {
1807
+ const db = options.db ?? openDb();
1808
+ const tableName = "locations";
1809
+ const selectClause = formatSelectClause(fields);
1810
+ const whereClause = formatWhereClauses(query);
1811
+ const orderByClause = formatOrderByClause(orderBy);
1812
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1813
+ }
1814
+
1815
+ //#endregion
1816
+ //#region src/lib/gtfs/networks.ts
1817
+ function getNetworks(query = {}, fields = [], orderBy = [], options = {}) {
1818
+ const db = options.db ?? openDb();
1819
+ const tableName = "networks";
1820
+ const selectClause = formatSelectClause(fields);
1821
+ const whereClause = formatWhereClauses(query);
1822
+ const orderByClause = formatOrderByClause(orderBy);
1823
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1824
+ }
1825
+
1826
+ //#endregion
1827
+ //#region src/lib/gtfs/pathways.ts
1828
+ function getPathways(query = {}, fields = [], orderBy = [], options = {}) {
1829
+ const db = options.db ?? openDb();
1830
+ const tableName = "pathways";
1831
+ const selectClause = formatSelectClause(fields);
1832
+ const whereClause = formatWhereClauses(query);
1833
+ const orderByClause = formatOrderByClause(orderBy);
1834
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1835
+ }
1836
+
1837
+ //#endregion
1838
+ //#region src/lib/gtfs/rider-categories.ts
1839
+ function getRiderCategories(query = {}, fields = [], orderBy = [], options = {}) {
1840
+ const db = options.db ?? openDb();
1841
+ const tableName = "rider_categories";
1842
+ const selectClause = formatSelectClause(fields);
1843
+ const whereClause = formatWhereClauses(query);
1844
+ const orderByClause = formatOrderByClause(orderBy);
1845
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1846
+ }
1847
+
1848
+ //#endregion
1849
+ //#region src/lib/gtfs/route-networks.ts
1850
+ function getRouteNetworks(query = {}, fields = [], orderBy = [], options = {}) {
1851
+ const db = options.db ?? openDb();
1852
+ const tableName = "route_networks";
1853
+ const selectClause = formatSelectClause(fields);
1854
+ const whereClause = formatWhereClauses(query);
1855
+ const orderByClause = formatOrderByClause(orderBy);
1856
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1857
+ }
1858
+
1859
+ //#endregion
1860
+ //#region src/lib/gtfs/routes.ts
1861
+ function buildStoptimeSubquery$1(query) {
1862
+ return `SELECT DISTINCT trip_id FROM stop_times ${formatWhereClauses(query)}`;
1863
+ }
1864
+ function buildTripSubquery$2(query) {
1865
+ let whereClause = "";
1866
+ const tripQuery = omit(query, ["stop_id"]);
1867
+ const stoptimeQuery = pick(query, ["stop_id"]);
1868
+ const whereClauses = Object.entries(tripQuery).map(([key, value]) => formatWhereClause(key, value));
1869
+ if (Object.values(stoptimeQuery).length > 0) whereClauses.push(`trip_id IN (${buildStoptimeSubquery$1(stoptimeQuery)})`);
1870
+ if (whereClauses.length > 0) whereClause = `WHERE ${whereClauses.join(" AND ")}`;
1871
+ return `SELECT DISTINCT route_id FROM trips ${whereClause}`;
1872
+ }
1873
+ function getRoutes(query = {}, fields = [], orderBy = [], options = {}) {
1874
+ const db = options.db ?? openDb();
1875
+ const tableName = "routes";
1876
+ const selectClause = formatSelectClause(fields);
1877
+ let whereClause = "";
1878
+ const orderByClause = formatOrderByClause(orderBy);
1879
+ const routeQuery = omit(query, ["stop_id", "service_id"]);
1880
+ const tripQuery = pick(query, ["stop_id", "service_id"]);
1881
+ const whereClauses = Object.entries(routeQuery).map(([key, value]) => formatWhereClause(key, value));
1882
+ if (Object.values(tripQuery).length > 0) whereClauses.push(`route_id IN (${buildTripSubquery$2(tripQuery)})`);
1883
+ if (whereClauses.length > 0) whereClause = `WHERE ${whereClauses.join(" AND ")}`;
1884
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1885
+ }
1886
+
1887
+ //#endregion
1888
+ //#region src/lib/gtfs-plus/route-attributes.ts
1889
+ function getRouteAttributes(query = {}, fields = [], orderBy = [], options = {}) {
1890
+ const db = options.db ?? openDb();
1891
+ const tableName = "route_attributes";
1892
+ const selectClause = formatSelectClause(fields);
1893
+ const whereClause = formatWhereClauses(query);
1894
+ const orderByClause = formatOrderByClause(orderBy);
1895
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1896
+ }
1897
+
1898
+ //#endregion
1899
+ //#region src/lib/gtfs/shapes.ts
1900
+ function buildTripSubquery$1(query) {
1901
+ return `SELECT DISTINCT shape_id FROM trips ${formatWhereClauses(query)}`;
1902
+ }
1903
+ function getShapes(query = {}, fields = [], orderBy = [], options = {}) {
1904
+ const db = options.db ?? openDb();
1905
+ const tableName = "shapes";
1906
+ const selectClause = formatSelectClause(fields);
1907
+ let whereClause = "";
1908
+ const orderByClause = formatOrderByClause(orderBy);
1909
+ const shapeQuery = omit(query, [
1910
+ "route_id",
1911
+ "trip_id",
1912
+ "service_id",
1913
+ "direction_id"
1914
+ ]);
1915
+ const tripQuery = pick(query, [
1916
+ "route_id",
1917
+ "trip_id",
1918
+ "service_id",
1919
+ "direction_id"
1920
+ ]);
1921
+ const whereClauses = Object.entries(shapeQuery).map(([key, value]) => formatWhereClause(key, value));
1922
+ if (Object.values(tripQuery).length > 0) whereClauses.push(`shape_id IN (${buildTripSubquery$1(tripQuery)})`);
1923
+ if (whereClauses.length > 0) whereClause = `WHERE ${whereClauses.join(" AND ")}`;
1924
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1925
+ }
1926
+ function getShapesAsGeoJSON(query = {}, options = {}) {
1927
+ const agencies = getAgencies({}, [], [], options);
1928
+ return featureCollection(compact(getRoutes(pick(query, ["route_id"]), [], [], options).map((route) => {
1929
+ const shapes = getShapes({
1930
+ route_id: route.route_id,
1931
+ ...omit(query, "route_id")
1932
+ }, [], [], options);
1933
+ if (shapes.length === 0) return;
1934
+ const routeAttributes = getRouteAttributes({ route_id: route.route_id }, [], [], options);
1935
+ const agency = agencies.find((agency) => agency.agency_id === route.agency_id);
1936
+ return shapesToGeoJSONFeature(shapes, {
1937
+ agency_name: agency ? agency.agency_name : void 0,
1938
+ shape_id: query.shape_id,
1939
+ ...route,
1940
+ ...routeAttributes?.[0] || []
1941
+ });
1942
+ })));
1943
+ }
1944
+
1945
+ //#endregion
1946
+ //#region src/lib/gtfs/stop-areas.ts
1947
+ function getStopAreas(query = {}, fields = [], orderBy = [], options = {}) {
1948
+ const db = options.db ?? openDb();
1949
+ const tableName = "stop_areas";
1950
+ const selectClause = formatSelectClause(fields);
1951
+ const whereClause = formatWhereClauses(query);
1952
+ const orderByClause = formatOrderByClause(orderBy);
1953
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1954
+ }
1955
+
1956
+ //#endregion
1957
+ //#region src/lib/gtfs-plus/stop-attributes.ts
1958
+ function getStopAttributes(query = {}, fields = [], orderBy = [], options = {}) {
1959
+ const db = options.db ?? openDb();
1960
+ const tableName = "stop_attributes";
1961
+ const selectClause = formatSelectClause(fields);
1962
+ const whereClause = formatWhereClauses(query);
1963
+ const orderByClause = formatOrderByClause(orderBy);
1964
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
1965
+ }
1966
+
1967
+ //#endregion
1968
+ //#region src/lib/gtfs/stops.ts
1969
+ function buildTripSubquery(query) {
1970
+ return `SELECT trip_id FROM trips ${formatWhereClauses(query)}`;
1971
+ }
1972
+ function buildStoptimeSubquery(query) {
1973
+ return `SELECT DISTINCT stop_id FROM stop_times WHERE trip_id IN (${buildTripSubquery(query)})`;
1974
+ }
1975
+ function getStops(query = {}, fields = [], orderBy = [], options = {}) {
1976
+ const db = options.db ?? openDb();
1977
+ const tableName = "stops";
1978
+ const selectClause = formatSelectClause(fields);
1979
+ let whereClause = "";
1980
+ let orderByClause = formatOrderByClause(orderBy);
1981
+ const stopQueryOmitKeys = [
1982
+ "route_id",
1983
+ "trip_id",
1984
+ "service_id",
1985
+ "direction_id",
1986
+ "shape_id"
1987
+ ];
1988
+ if (options.bounding_box_side_m !== void 0) stopQueryOmitKeys.push("stop_lat", "stop_lon");
1989
+ const stopQuery = omit(query, stopQueryOmitKeys);
1990
+ const tripQuery = pick(query, [
1991
+ "route_id",
1992
+ "trip_id",
1993
+ "service_id",
1994
+ "direction_id",
1995
+ "shape_id"
1996
+ ]);
1997
+ const whereClauses = Object.entries(stopQuery).map(([key, value]) => formatWhereClause(key, value));
1998
+ if (options.bounding_box_side_m !== void 0 && query.stop_lat !== void 0 && query.stop_lon !== void 0) {
1999
+ whereClauses.push(formatWhereClauseBoundingBox(query.stop_lat, query.stop_lon, options.bounding_box_side_m));
2000
+ if (orderBy.length === 0) orderByClause = `ORDER BY (((stop_lat - ${query.stop_lat}) * (stop_lat - ${query.stop_lat})) + ((stop_lon - ${query.stop_lon}) * (stop_lon - ${query.stop_lon}))) ASC`;
2001
+ }
2002
+ if (Object.values(tripQuery).length > 0) whereClauses.push(`stop_id IN (${buildStoptimeSubquery(tripQuery)})`);
2003
+ if (whereClauses.length > 0) whereClause = `WHERE ${whereClauses.join(" AND ")}`;
2004
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2005
+ }
2006
+ function getStopsAsGeoJSON(query = {}, options = {}) {
2007
+ const db = options.db ?? openDb();
2008
+ const stops = getStops(query, [], [], options);
2009
+ const agencies = getAgencies({}, [], [], options);
2010
+ return stopsToGeoJSONFeatureCollection(stops.map((stop) => {
2011
+ const routes = db.prepare(`SELECT * FROM routes WHERE route_id IN (SELECT DISTINCT route_id FROM trips WHERE trip_id IN (SELECT DISTINCT trip_id FROM stop_times WHERE stop_id = ?))`).all(stop.stop_id);
2012
+ const stopAttributes = getStopAttributes({ stop_id: stop.stop_id });
2013
+ return {
2014
+ ...stop,
2015
+ ...stopAttributes?.[0] || [],
2016
+ routes: orderBy(routes, (route) => route?.route_short_name ? Number.parseInt(route.route_short_name, 10) : 0),
2017
+ agency_name: agencies[0]?.agency_name ?? null
2018
+ };
2019
+ }).filter((stop) => stop.routes.length > 0));
2020
+ }
2021
+
2022
+ //#endregion
2023
+ //#region src/lib/gtfs/stop-times.ts
2024
+ function getStoptimes(query = {}, fields = [], orderBy = [], options = {}) {
2025
+ const db = options.db ?? openDb();
2026
+ const tableName = "stop_times";
2027
+ const selectClause = formatSelectClause(fields);
2028
+ let whereClause = "";
2029
+ const orderByClause = formatOrderByClause(orderBy);
2030
+ const stoptimeQuery = omit(query, [
2031
+ "date",
2032
+ "start_time",
2033
+ "end_time"
2034
+ ]);
2035
+ const whereClauses = Object.entries(stoptimeQuery).map(([key, value]) => formatWhereClause(key, value));
2036
+ if (query.date) {
2037
+ if (typeof query.date !== "number") throw new GtfsError("`date` must be a number in yyyymmdd format", {
2038
+ code: "GTFS_QUERY_INVALID",
2039
+ category: "query",
2040
+ details: {
2041
+ field: "date",
2042
+ value: query.date
2043
+ }
2044
+ });
2045
+ const tripSubquery = `SELECT DISTINCT trip_id FROM trips WHERE service_id IN (${getServiceIdsByDate(query.date, options).map((id) => sqlString.escape(id)).join(",")})`;
2046
+ whereClauses.push(`trip_id IN (${tripSubquery})`);
2047
+ }
2048
+ if (query.start_time) {
2049
+ if (typeof query.start_time !== "string") throw new GtfsError("`start_time` must be a string in HH:mm:ss format", {
2050
+ code: "GTFS_QUERY_INVALID",
2051
+ category: "query",
2052
+ details: {
2053
+ field: "start_time",
2054
+ value: query.start_time
2055
+ }
2056
+ });
2057
+ whereClauses.push(`arrival_timestamp >= ${calculateSecondsFromMidnight(query.start_time)}`);
2058
+ }
2059
+ if (query.end_time) {
2060
+ if (typeof query.end_time !== "string") throw new GtfsError("`end_time` must be a string in HH:mm:ss format", {
2061
+ code: "GTFS_QUERY_INVALID",
2062
+ category: "query",
2063
+ details: {
2064
+ field: "end_time",
2065
+ value: query.end_time
2066
+ }
2067
+ });
2068
+ whereClauses.push(`departure_timestamp <= ${calculateSecondsFromMidnight(query.end_time)}`);
2069
+ }
2070
+ if (whereClauses.length > 0) whereClause = `WHERE ${whereClauses.join(" AND ")}`;
2071
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2072
+ }
2073
+
2074
+ //#endregion
2075
+ //#region src/lib/gtfs/timeframes.ts
2076
+ function getTimeframes(query = {}, fields = [], orderBy = [], options = {}) {
2077
+ const db = options.db ?? openDb();
2078
+ const tableName = "timeframes";
2079
+ const selectClause = formatSelectClause(fields);
2080
+ const whereClause = formatWhereClauses(query);
2081
+ const orderByClause = formatOrderByClause(orderBy);
2082
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2083
+ }
2084
+
2085
+ //#endregion
2086
+ //#region src/lib/gtfs/transfers.ts
2087
+ function getTransfers(query = {}, fields = [], orderBy = [], options = {}) {
2088
+ const db = options.db ?? openDb();
2089
+ const tableName = "transfers";
2090
+ const selectClause = formatSelectClause(fields);
2091
+ const whereClause = formatWhereClauses(query);
2092
+ const orderByClause = formatOrderByClause(orderBy);
2093
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2094
+ }
2095
+
2096
+ //#endregion
2097
+ //#region src/lib/gtfs/translations.ts
2098
+ function getTranslations(query = {}, fields = [], orderBy = [], options = {}) {
2099
+ const db = options.db ?? openDb();
2100
+ const tableName = "translations";
2101
+ const selectClause = formatSelectClause(fields);
2102
+ const whereClause = formatWhereClauses(query);
2103
+ const orderByClause = formatOrderByClause(orderBy);
2104
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2105
+ }
2106
+
2107
+ //#endregion
2108
+ //#region src/lib/gtfs/trips.ts
2109
+ function getTrips(query = {}, fields = [], orderBy = [], options = {}) {
2110
+ const db = options.db ?? openDb();
2111
+ const tableName = "trips";
2112
+ const selectClause = formatSelectClause(fields);
2113
+ let whereClause = "";
2114
+ const orderByClause = formatOrderByClause(orderBy);
2115
+ const tripQuery = omit(query, ["date"]);
2116
+ const whereClauses = Object.entries(tripQuery).map(([key, value]) => formatWhereClause(key, value));
2117
+ if (query.date) {
2118
+ if (typeof query.date !== "number") throw new GtfsError("`date` must be a number in yyyymmdd format", {
2119
+ code: "GTFS_QUERY_INVALID",
2120
+ category: "query",
2121
+ details: {
2122
+ field: "date",
2123
+ value: query.date
2124
+ }
2125
+ });
2126
+ const serviceIds = getServiceIdsByDate(query.date, options);
2127
+ whereClauses.push(`service_id IN (${serviceIds.map((id) => sqlString.escape(id)).join(",")})`);
2128
+ }
2129
+ if (whereClauses.length > 0) whereClause = `WHERE ${whereClauses.join(" AND ")}`;
2130
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2131
+ }
2132
+
2133
+ //#endregion
2134
+ //#region src/lib/gtfs-plus/calendar-attributes.ts
2135
+ function getCalendarAttributes(query = {}, fields = [], orderBy = [], options = {}) {
2136
+ const db = options.db ?? openDb();
2137
+ const tableName = "calendar_attributes";
2138
+ const selectClause = formatSelectClause(fields);
2139
+ const whereClause = formatWhereClauses(query);
2140
+ const orderByClause = formatOrderByClause(orderBy);
2141
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2142
+ }
2143
+
2144
+ //#endregion
2145
+ //#region src/lib/gtfs-plus/directions.ts
2146
+ function getDirections(query = {}, fields = [], orderBy = [], options = {}) {
2147
+ const db = options.db ?? openDb();
2148
+ const tableName = "directions";
2149
+ const selectClause = formatSelectClause(fields);
2150
+ const whereClause = formatWhereClauses(query);
2151
+ const orderByClause = formatOrderByClause(orderBy);
2152
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2153
+ }
2154
+
2155
+ //#endregion
2156
+ //#region src/lib/non-standard/timetables.ts
2157
+ function getTimetables(query = {}, fields = [], orderBy = [], options = {}) {
2158
+ const db = options.db ?? openDb();
2159
+ const tableName = "timetables";
2160
+ const selectClause = formatSelectClause(fields);
2161
+ const whereClause = formatWhereClauses(query);
2162
+ const orderByClause = formatOrderByClause(orderBy);
2163
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2164
+ }
2165
+
2166
+ //#endregion
2167
+ //#region src/lib/non-standard/timetable-stop-order.ts
2168
+ function getTimetableStopOrders(query = {}, fields = [], orderBy = [], options = {}) {
2169
+ const db = options.db ?? openDb();
2170
+ const tableName = "timetable_stop_order";
2171
+ const selectClause = formatSelectClause(fields);
2172
+ const whereClause = formatWhereClauses(query);
2173
+ const orderByClause = formatOrderByClause(orderBy);
2174
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2175
+ }
2176
+
2177
+ //#endregion
2178
+ //#region src/lib/non-standard/timetable-pages.ts
2179
+ function getTimetablePages(query = {}, fields = [], orderBy = [], options = {}) {
2180
+ const db = options.db ?? openDb();
2181
+ const tableName = "timetable_pages";
2182
+ const selectClause = formatSelectClause(fields);
2183
+ const whereClause = formatWhereClauses(query);
2184
+ const orderByClause = formatOrderByClause(orderBy);
2185
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2186
+ }
2187
+
2188
+ //#endregion
2189
+ //#region src/lib/non-standard/timetable-notes.ts
2190
+ function getTimetableNotes(query = {}, fields = [], orderBy = [], options = {}) {
2191
+ const db = options.db ?? openDb();
2192
+ const tableName = "timetable_notes";
2193
+ const selectClause = formatSelectClause(fields);
2194
+ const whereClause = formatWhereClauses(query);
2195
+ const orderByClause = formatOrderByClause(orderBy);
2196
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2197
+ }
2198
+
2199
+ //#endregion
2200
+ //#region src/lib/non-standard/timetable-notes-references.ts
2201
+ function getTimetableNotesReferences(query = {}, fields = [], orderBy = [], options = {}) {
2202
+ const db = options.db ?? openDb();
2203
+ const tableName = "timetable_notes_references";
2204
+ const selectClause = formatSelectClause(fields);
2205
+ const whereClause = formatWhereClauses(query);
2206
+ const orderByClause = formatOrderByClause(orderBy);
2207
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2208
+ }
2209
+
2210
+ //#endregion
2211
+ //#region src/lib/non-standard/trips-dated-vehicle-journey.ts
2212
+ function getTripsDatedVehicleJourneys(query = {}, fields = [], orderBy = [], options = {}) {
2213
+ const db = options.db ?? openDb();
2214
+ const tableName = "trips_dated_vehicle_journey";
2215
+ const selectClause = formatSelectClause(fields);
2216
+ const whereClause = formatWhereClauses(query);
2217
+ const orderByClause = formatOrderByClause(orderBy);
2218
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2219
+ }
2220
+
2221
+ //#endregion
2222
+ //#region src/lib/gtfs-ride/board-alights.ts
2223
+ function getBoardAlights(query = {}, fields = [], orderBy = [], options = {}) {
2224
+ const db = options.db ?? openDb();
2225
+ const tableName = "board_alight";
2226
+ const selectClause = formatSelectClause(fields);
2227
+ const whereClause = formatWhereClauses(query);
2228
+ const orderByClause = formatOrderByClause(orderBy);
2229
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2230
+ }
2231
+
2232
+ //#endregion
2233
+ //#region src/lib/gtfs-ride/ride-feed-info.ts
2234
+ function getRideFeedInfo(query = {}, fields = [], orderBy = [], options = {}) {
2235
+ const db = options.db ?? openDb();
2236
+ const tableName = "ride_feed_info";
2237
+ const selectClause = formatSelectClause(fields);
2238
+ const whereClause = formatWhereClauses(query);
2239
+ const orderByClause = formatOrderByClause(orderBy);
2240
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2241
+ }
2242
+
2243
+ //#endregion
2244
+ //#region src/lib/gtfs-ride/rider-trips.ts
2245
+ function getRiderTrips(query = {}, fields = [], orderBy = [], options = {}) {
2246
+ const db = options.db ?? openDb();
2247
+ const tableName = "rider_trip";
2248
+ const selectClause = formatSelectClause(fields);
2249
+ const whereClause = formatWhereClauses(query);
2250
+ const orderByClause = formatOrderByClause(orderBy);
2251
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2252
+ }
2253
+
2254
+ //#endregion
2255
+ //#region src/lib/gtfs-ride/ridership.ts
2256
+ function getRidership(query = {}, fields = [], orderBy = [], options = {}) {
2257
+ const db = options.db ?? openDb();
2258
+ const tableName = "ridership";
2259
+ const selectClause = formatSelectClause(fields);
2260
+ const whereClause = formatWhereClauses(query);
2261
+ const orderByClause = formatOrderByClause(orderBy);
2262
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2263
+ }
2264
+
2265
+ //#endregion
2266
+ //#region src/lib/gtfs-ride/trip-capacities.ts
2267
+ function getTripCapacities(query = {}, fields = [], orderBy = [], options = {}) {
2268
+ const db = options.db ?? openDb();
2269
+ const tableName = "trip_capacity";
2270
+ const selectClause = formatSelectClause(fields);
2271
+ const whereClause = formatWhereClauses(query);
2272
+ const orderByClause = formatOrderByClause(orderBy);
2273
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2274
+ }
2275
+
2276
+ //#endregion
2277
+ //#region src/lib/gtfs-realtime/stop-time-updates.ts
2278
+ function getStopTimeUpdates(query = {}, fields = [], orderBy = [], options = {}) {
2279
+ const db = options.db ?? openDb();
2280
+ const tableName = "stop_time_updates";
2281
+ const selectClause = formatSelectClause(fields);
2282
+ const whereClause = formatWhereClauses(query);
2283
+ const orderByClause = formatOrderByClause(orderBy);
2284
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2285
+ }
2286
+
2287
+ //#endregion
2288
+ //#region src/lib/gtfs-realtime/trip-updates.ts
2289
+ function getTripUpdates(query = {}, fields = [], orderBy = [], options = {}) {
2290
+ const db = options.db ?? openDb();
2291
+ const tableName = "trip_updates";
2292
+ const selectClause = formatSelectClause(fields);
2293
+ const whereClause = formatWhereClauses(query);
2294
+ const orderByClause = formatOrderByClause(orderBy);
2295
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2296
+ }
2297
+
2298
+ //#endregion
2299
+ //#region src/lib/gtfs-realtime/vehicle-positions.ts
2300
+ function getVehiclePositions(query = {}, fields = [], orderBy = [], options = {}) {
2301
+ const db = options.db ?? openDb();
2302
+ const tableName = "vehicle_positions";
2303
+ const selectClause = formatSelectClause(fields);
2304
+ const whereClause = formatWhereClauses(query);
2305
+ const orderByClause = formatOrderByClause(orderBy);
2306
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2307
+ }
2308
+
2309
+ //#endregion
2310
+ //#region src/lib/gtfs-realtime/service-alerts.ts
2311
+ const ENTITY_COLUMNS = new Set([
2312
+ "alert_id",
2313
+ "stop_id",
2314
+ "route_id",
2315
+ "route_type",
2316
+ "trip_id",
2317
+ "direction_id"
2318
+ ]);
2319
+ function getServiceAlerts(query = {}, fields = [], orderBy = [], options = {}) {
2320
+ const db = options.db ?? openDb();
2321
+ const tableName = "service_alerts";
2322
+ const joinTableName = "service_alert_informed_entities";
2323
+ const selectClause = formatSelectClause(fields);
2324
+ const orderByClause = formatOrderByClause(orderBy);
2325
+ const alertQuery = {};
2326
+ const entityQuery = {};
2327
+ for (const [key, value] of Object.entries(query)) if (ENTITY_COLUMNS.has(key)) entityQuery[key] = value;
2328
+ else alertQuery[key] = value;
2329
+ const whereClause = formatWhereClauses(alertQuery);
2330
+ const alerts = db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2331
+ const alertIds = alerts.map((alert) => alert.id);
2332
+ if (alertIds.length === 0) return [];
2333
+ const alertIdPlaceholders = alertIds.map(() => "?").join(", ");
2334
+ const entityFilterClause = formatWhereClauses(entityQuery);
2335
+ const entityWhereClause = entityFilterClause ? `${entityFilterClause} AND alert_id IN (${alertIdPlaceholders})` : `WHERE alert_id IN (${alertIdPlaceholders})`;
2336
+ const entities = db.prepare(`SELECT * FROM ${joinTableName} ${entityWhereClause};`).all(...alertIds);
2337
+ const entitiesByAlertId = /* @__PURE__ */ new Map();
2338
+ for (const entity of entities) {
2339
+ const group = entitiesByAlertId.get(entity.alert_id);
2340
+ if (group) group.push(entity);
2341
+ else entitiesByAlertId.set(entity.alert_id, [entity]);
2342
+ }
2343
+ return (Object.keys(entityQuery).length > 0 ? alerts.filter((alert) => entitiesByAlertId.has(alert.id)) : alerts).map((alert) => ({
2344
+ ...alert,
2345
+ informed_entities: entitiesByAlertId.get(alert.id) ?? []
2346
+ }));
2347
+ }
2348
+
2349
+ //#endregion
2350
+ //#region src/lib/gtfs-realtime/service-alert-informed-entities.ts
2351
+ function getServiceAlertInformedEntities(query = {}, fields = [], orderBy = [], options = {}) {
2352
+ const db = options.db ?? openDb();
2353
+ const tableName = "service_alert_informed_entities";
2354
+ const selectClause = formatSelectClause(fields);
2355
+ const whereClause = formatWhereClauses(query);
2356
+ const orderByClause = formatOrderByClause(orderBy);
2357
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2358
+ }
2359
+
2360
+ //#endregion
2361
+ //#region src/lib/ods/deadheads.ts
2362
+ function getDeadheads(query = {}, fields = [], orderBy = [], options = {}) {
2363
+ const db = options.db ?? openDb();
2364
+ const tableName = "deadheads";
2365
+ const selectClause = formatSelectClause(fields);
2366
+ const whereClause = formatWhereClauses(query);
2367
+ const orderByClause = formatOrderByClause(orderBy);
2368
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2369
+ }
2370
+
2371
+ //#endregion
2372
+ //#region src/lib/ods/deadhead-times.ts
2373
+ function getDeadheadTimes(query = {}, fields = [], orderBy = [], options = {}) {
2374
+ const db = options.db ?? openDb();
2375
+ const tableName = "deadhead_times";
2376
+ const selectClause = formatSelectClause(fields);
2377
+ const whereClause = formatWhereClauses(query);
2378
+ const orderByClause = formatOrderByClause(orderBy);
2379
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2380
+ }
2381
+
2382
+ //#endregion
2383
+ //#region src/lib/ods/ops-locations.ts
2384
+ function getOpsLocations(query = {}, fields = [], orderBy = [], options = {}) {
2385
+ const db = options.db ?? openDb();
2386
+ const tableName = "ops_locations";
2387
+ const selectClause = formatSelectClause(fields);
2388
+ const whereClause = formatWhereClauses(query);
2389
+ const orderByClause = formatOrderByClause(orderBy);
2390
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2391
+ }
2392
+
2393
+ //#endregion
2394
+ //#region src/lib/ods/run-events.ts
2395
+ function getRunEvents(query = {}, fields = [], orderBy = [], options = {}) {
2396
+ const db = options.db ?? openDb();
2397
+ const tableName = "run_events";
2398
+ const selectClause = formatSelectClause(fields);
2399
+ const whereClause = formatWhereClauses(query);
2400
+ const orderByClause = formatOrderByClause(orderBy);
2401
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2402
+ }
2403
+
2404
+ //#endregion
2405
+ //#region src/lib/ods/runs-pieces.ts
2406
+ function getRunsPieces(query = {}, fields = [], orderBy = [], options = {}) {
2407
+ const db = options.db ?? openDb();
2408
+ const tableName = "runs_pieces";
2409
+ const selectClause = formatSelectClause(fields);
2410
+ const whereClause = formatWhereClauses(query);
2411
+ const orderByClause = formatOrderByClause(orderBy);
2412
+ return db.prepare(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`).all();
2413
+ }
2414
+
2415
+ //#endregion
2416
+ export { getCalendars as $, getStopsAsGeoJSON as A, getNetworks as B, getCalendarAttributes as C, untildify as Ct, getTimeframes as D, getTransfers as E, getRouteAttributes as F, getFrequencies as G, getLocationGroupStops as H, getRoutes as I, getFareRules as J, getFeedInfo as K, getRouteNetworks as L, getStopAreas as M, getShapes as N, getStoptimes as O, getShapesAsGeoJSON as P, getFareAttributes as Q, getRiderCategories as R, getDirections as S, prepDirectory as St, getTranslations as T, formatError as Tt, getLocationGroups as U, getLocations as V, getLevels as W, getFareMedia as X, getFareProducts as Y, getFareLegRules as Z, getTimetableNotesReferences as _, formatGtfsError as _t, getDeadheads as a, getAgencies as at, getTimetableStopOrders as b, generateFolderName as bt, getVehiclePositions as c, importGtfs as ct, getTripCapacities as d, deleteDb as dt, getServiceIdsByDate as et, getRidership as f, openDb as ft, getTripsDatedVehicleJourneys as g, GtfsWarningCode as gt, getBoardAlights as h, GtfsErrorCode as ht, getDeadheadTimes as i, getAreas as it, getStopAttributes as j, getStops as k, getTripUpdates as l, updateGtfsRealtime as lt, getRideFeedInfo as m, GtfsErrorCategory as mt, getRunEvents as n, getBookingRules as nt, getServiceAlertInformedEntities as o, advancedQuery as ot, getRiderTrips as p, GtfsError as pt, getFareTransferRules as q, getOpsLocations as r, getAttributions as rt, getServiceAlerts as s, exportGtfs as st, getRunsPieces as t, getCalendarDates as tt, getStopTimeUpdates as u, closeDb as ut, getTimetableNotes as v, isGtfsError as vt, getTrips as w, unzip as wt, getTimetables as x, getConfig as xt, getTimetablePages as y, isGtfsValidationError as yt, getPathways as z };
2417
+ //# sourceMappingURL=src-DuXcyA_N.js.map