gtfs 4.17.6 → 4.18.0

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.
package/dist/index.js CHANGED
@@ -9,11 +9,8 @@ import path2 from "path";
9
9
  import { createReadStream, existsSync as existsSync2, lstatSync } from "fs";
10
10
  import { cp, readdir, rename, readFile as readFile2, rm as rm2, writeFile } from "fs/promises";
11
11
  import { parse } from "csv-parse";
12
- import pluralize2 from "pluralize";
13
12
  import stripBomStream from "strip-bom-stream";
14
13
  import { temporaryDirectory } from "tempy";
15
- import Timer from "timer-machine";
16
- import untildify3 from "untildify";
17
14
  import mapSeries2 from "promise-map-series";
18
15
 
19
16
  // src/models/models.ts
@@ -3976,84 +3973,14 @@ var vehicles = {
3976
3973
  // src/lib/db.ts
3977
3974
  import fs from "fs";
3978
3975
  import Database from "better-sqlite3";
3979
- import untildify from "untildify";
3980
- var dbs = {};
3981
- function setupDb(sqlitePath) {
3982
- const db = new Database(untildify(sqlitePath));
3983
- db.pragma("journal_mode = OFF");
3984
- db.pragma("synchronous = OFF");
3985
- db.pragma("temp_store = MEMORY");
3986
- dbs[sqlitePath] = db;
3987
- return db;
3988
- }
3989
- function openDb(config = null) {
3990
- if (config) {
3991
- const { sqlitePath = ":memory:", db } = config;
3992
- if (db) {
3993
- return db;
3994
- }
3995
- if (dbs[sqlitePath]) {
3996
- return dbs[sqlitePath];
3997
- }
3998
- return setupDb(sqlitePath);
3999
- }
4000
- if (Object.keys(dbs).length === 0) {
4001
- return setupDb(":memory:");
4002
- }
4003
- if (Object.keys(dbs).length === 1) {
4004
- const filename = Object.keys(dbs)[0];
4005
- return dbs[filename];
4006
- }
4007
- if (Object.keys(dbs).length > 1) {
4008
- throw new Error(
4009
- "Multiple databases open, please specify which one to use."
4010
- );
4011
- }
4012
- throw new Error("Unable to find database connection.");
4013
- }
4014
- function closeDb(db = null) {
4015
- if (Object.keys(dbs).length === 0) {
4016
- throw new Error(
4017
- "No database connection. Call `openDb(config)` before using any methods."
4018
- );
4019
- }
4020
- if (!db) {
4021
- if (Object.keys(dbs).length > 1) {
4022
- throw new Error(
4023
- "Multiple database connections. Pass the db you want to close as a parameter to `closeDb`."
4024
- );
4025
- }
4026
- db = dbs[Object.keys(dbs)[0]];
4027
- }
4028
- db.close();
4029
- delete dbs[db.name];
4030
- }
4031
- function deleteDb(db = null) {
4032
- if (Object.keys(dbs).length === 0) {
4033
- throw new Error(
4034
- "No database connection. Call `openDb(config)` before using any methods."
4035
- );
4036
- }
4037
- if (!db) {
4038
- if (Object.keys(dbs).length > 1) {
4039
- throw new Error(
4040
- "Multiple database connections. Pass the db you want to delete as a parameter to `deleteDb`."
4041
- );
4042
- }
4043
- db = dbs[Object.keys(dbs)[0]];
4044
- }
4045
- db.close();
4046
- fs.unlinkSync(db.name);
4047
- delete dbs[db.name];
4048
- }
4049
3976
 
4050
3977
  // src/lib/file-utils.ts
4051
3978
  import path from "path";
4052
3979
  import { existsSync } from "fs";
3980
+ import { homedir } from "os";
4053
3981
  import { mkdir, readFile, rm } from "fs/promises";
4054
3982
  import { omit, snakeCase } from "lodash-es";
4055
3983
  import sanitize from "sanitize-filename";
4056
- import untildify2 from "untildify";
4057
3984
  import StreamZip from "node-stream-zip";
4058
3985
 
4059
3986
  // src/lib/log-utils.ts
@@ -4107,6 +4034,7 @@ function formatError(error) {
4107
4034
  }
4108
4035
 
4109
4036
  // src/lib/file-utils.ts
4037
+ var homeDirectory = homedir();
4110
4038
  async function prepDirectory(exportPath) {
4111
4039
  await rm(exportPath, { recursive: true, force: true });
4112
4040
  await mkdir(exportPath, { recursive: true });
@@ -4128,6 +4056,80 @@ function generateFolderName(folderName) {
4128
4056
  }
4129
4057
  return snakeCase(sanitize(folderName));
4130
4058
  }
4059
+ function untildify(pathWithTilde) {
4060
+ return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
4061
+ }
4062
+
4063
+ // src/lib/db.ts
4064
+ var dbs = {};
4065
+ function setupDb(sqlitePath) {
4066
+ const db = new Database(untildify(sqlitePath));
4067
+ db.pragma("journal_mode = OFF");
4068
+ db.pragma("synchronous = OFF");
4069
+ db.pragma("temp_store = MEMORY");
4070
+ dbs[sqlitePath] = db;
4071
+ return db;
4072
+ }
4073
+ function openDb(config = null) {
4074
+ if (config) {
4075
+ const { sqlitePath = ":memory:", db } = config;
4076
+ if (db) {
4077
+ return db;
4078
+ }
4079
+ if (dbs[sqlitePath]) {
4080
+ return dbs[sqlitePath];
4081
+ }
4082
+ return setupDb(sqlitePath);
4083
+ }
4084
+ if (Object.keys(dbs).length === 0) {
4085
+ return setupDb(":memory:");
4086
+ }
4087
+ if (Object.keys(dbs).length === 1) {
4088
+ const filename = Object.keys(dbs)[0];
4089
+ return dbs[filename];
4090
+ }
4091
+ if (Object.keys(dbs).length > 1) {
4092
+ throw new Error(
4093
+ "Multiple databases open, please specify which one to use."
4094
+ );
4095
+ }
4096
+ throw new Error("Unable to find database connection.");
4097
+ }
4098
+ function closeDb(db = null) {
4099
+ if (Object.keys(dbs).length === 0) {
4100
+ throw new Error(
4101
+ "No database connection. Call `openDb(config)` before using any methods."
4102
+ );
4103
+ }
4104
+ if (!db) {
4105
+ if (Object.keys(dbs).length > 1) {
4106
+ throw new Error(
4107
+ "Multiple database connections. Pass the db you want to close as a parameter to `closeDb`."
4108
+ );
4109
+ }
4110
+ db = dbs[Object.keys(dbs)[0]];
4111
+ }
4112
+ db.close();
4113
+ delete dbs[db.name];
4114
+ }
4115
+ function deleteDb(db = null) {
4116
+ if (Object.keys(dbs).length === 0) {
4117
+ throw new Error(
4118
+ "No database connection. Call `openDb(config)` before using any methods."
4119
+ );
4120
+ }
4121
+ if (!db) {
4122
+ if (Object.keys(dbs).length > 1) {
4123
+ throw new Error(
4124
+ "Multiple database connections. Pass the db you want to delete as a parameter to `deleteDb`."
4125
+ );
4126
+ }
4127
+ db = dbs[Object.keys(dbs)[0]];
4128
+ }
4129
+ db.close();
4130
+ fs.unlinkSync(db.name);
4131
+ delete dbs[db.name];
4132
+ }
4131
4133
 
4132
4134
  // src/lib/geojson-utils.ts
4133
4135
  import {
@@ -4145,7 +4147,7 @@ function isValidJSON(string) {
4145
4147
  try {
4146
4148
  JSON.parse(string);
4147
4149
  return true;
4148
- } catch (error) {
4150
+ } catch {
4149
4151
  return false;
4150
4152
  }
4151
4153
  }
@@ -4210,7 +4212,7 @@ function formatProperties(properties) {
4210
4212
  if (formattedRouteTextColor) {
4211
4213
  formattedProperties.route_text_color = formattedRouteTextColor;
4212
4214
  }
4213
- if (properties.routes) {
4215
+ if (properties.routes && Array.isArray(properties.routes)) {
4214
4216
  formattedProperties.routes = properties.routes.map(
4215
4217
  (route) => formatProperties(route)
4216
4218
  );
@@ -4249,7 +4251,6 @@ function stopsToGeoJSONFeatureCollection(stops2) {
4249
4251
  }
4250
4252
 
4251
4253
  // src/lib/import-gtfs-realtime.ts
4252
- import pluralize from "pluralize";
4253
4254
  import GtfsRealtimeBindings from "gtfs-realtime-bindings";
4254
4255
  import mapSeries from "promise-map-series";
4255
4256
  import { get } from "lodash-es";
@@ -4276,7 +4277,8 @@ function setDefaultConfig(initialConfig) {
4276
4277
  ignoreDuplicates: false,
4277
4278
  ignoreErrors: false,
4278
4279
  gtfsRealtimeExpirationSeconds: 0,
4279
- verbose: true
4280
+ verbose: true,
4281
+ downloadTimeout: 3e4
4280
4282
  };
4281
4283
  return {
4282
4284
  ...defaults,
@@ -4419,58 +4421,14 @@ function applyPrefixToValue(value, columnShouldBePrefixed, prefix) {
4419
4421
  }
4420
4422
  return `${prefix}${value}`;
4421
4423
  }
4424
+ function pluralize(singularWord, pluralWord, count) {
4425
+ return count === 1 ? singularWord : pluralWord;
4426
+ }
4422
4427
 
4423
4428
  // src/lib/import-gtfs-realtime.ts
4424
- async function fetchGtfsRealtimeData(urlConfig, task) {
4425
- task.log(`Downloading GTFS-Realtime from ${urlConfig.url}`);
4426
- const response = await fetch(urlConfig.url, {
4427
- method: "GET",
4428
- headers: {
4429
- ...urlConfig.headers ?? {},
4430
- "Accept-Encoding": "gzip"
4431
- },
4432
- signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
4433
- });
4434
- if (response.status !== 200) {
4435
- task.logWarning(
4436
- `Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
4437
- );
4438
- return null;
4439
- }
4440
- const buffer = await response.arrayBuffer();
4441
- const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
4442
- new Uint8Array(buffer)
4443
- );
4444
- return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
4445
- enums: String,
4446
- longs: String,
4447
- bytes: String,
4448
- defaults: false,
4449
- arrays: true,
4450
- objects: true,
4451
- oneofs: true
4452
- });
4453
- }
4454
- function removeExpiredRealtimeData(config) {
4455
- const db = openDb(config);
4456
- log(config)(`Removing expired GTFS-Realtime data`);
4457
- db.prepare(
4458
- `DELETE FROM vehicle_positions WHERE expiration_timestamp <= strftime('%s','now')`
4459
- ).run();
4460
- db.prepare(
4461
- `DELETE FROM trip_updates WHERE expiration_timestamp <= strftime('%s','now')`
4462
- ).run();
4463
- db.prepare(
4464
- `DELETE FROM stop_time_updates WHERE expiration_timestamp <= strftime('%s','now')`
4465
- ).run();
4466
- db.prepare(
4467
- `DELETE FROM service_alerts WHERE expiration_timestamp <= strftime('%s','now')`
4468
- ).run();
4469
- db.prepare(
4470
- `DELETE FROM service_alert_informed_entities WHERE expiration_timestamp <= strftime('%s','now')`
4471
- ).run();
4472
- log(config)(`Removed expired GTFS-Realtime data\r`, true);
4473
- }
4429
+ var BATCH_SIZE = 1e3;
4430
+ var MAX_RETRIES = 3;
4431
+ var RETRY_DELAY = 1e3;
4474
4432
  function prepareRealtimeFieldValue(entity, column, task) {
4475
4433
  if (column.name === "created_timestamp") {
4476
4434
  return task.currentTimestamp;
@@ -4487,163 +4445,265 @@ function prepareRealtimeFieldValue(entity, column, task) {
4487
4445
  );
4488
4446
  return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
4489
4447
  }
4490
- async function processRealtimeAlerts(db, gtfsRealtimeData, task) {
4491
- const alertStmt = db.prepare(
4492
- `REPLACE INTO ${serviceAlerts.filenameBase} (${serviceAlerts.schema.map((column) => column.name).join(
4493
- ", "
4494
- )}) VALUES (${serviceAlerts.schema.map(() => "?").join(", ")})`
4495
- );
4496
- const informedEntityStmt = db.prepare(
4497
- `REPLACE INTO ${serviceAlertInformedEntities.filenameBase} (${serviceAlertInformedEntities.schema.map((column) => column.name).join(
4498
- ", "
4499
- )}) VALUES (${serviceAlertInformedEntities.schema.map(() => "?").join(", ")})`
4448
+ function createPreparedStatement(db, model) {
4449
+ const columns = model.schema.map((column) => column.name);
4450
+ const placeholders = model.schema.map(() => "?").join(", ");
4451
+ return db.prepare(
4452
+ `REPLACE INTO ${model.filenameBase} (${columns.join(", ")}) VALUES (${placeholders})`
4500
4453
  );
4501
- let totalLineCount = 0;
4502
- db.transaction(() => {
4503
- for (const entity of gtfsRealtimeData.entity) {
4504
- const fieldValues = serviceAlerts.schema.map(
4505
- (column) => prepareRealtimeFieldValue(entity, column, task)
4454
+ }
4455
+ async function processBatch(items, batchSize, processor) {
4456
+ let totalRecordCount = 0;
4457
+ let totalErrorCount = 0;
4458
+ for (let i = 0; i < items.length; i += batchSize) {
4459
+ const batch = items.slice(i, i + batchSize);
4460
+ try {
4461
+ const result = await processor(batch);
4462
+ totalRecordCount += result.recordCount;
4463
+ totalErrorCount += result.errorCount;
4464
+ } catch (error) {
4465
+ const errorMessage = error instanceof Error ? error.message : String(error);
4466
+ totalErrorCount += batch.length;
4467
+ console.error(`Batch processing error: ${errorMessage}`);
4468
+ }
4469
+ }
4470
+ return { recordCount: totalRecordCount, errorCount: totalErrorCount };
4471
+ }
4472
+ async function fetchGtfsRealtimeData(type, task) {
4473
+ const urlConfig = getUrlConfig(type, task);
4474
+ if (!urlConfig) {
4475
+ return null;
4476
+ }
4477
+ task.log(`Importing - GTFS-Realtime from ${urlConfig.url}`);
4478
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4479
+ try {
4480
+ const response = await fetch(urlConfig.url, {
4481
+ method: "GET",
4482
+ headers: {
4483
+ ...urlConfig.headers ?? {},
4484
+ "Accept-Encoding": "gzip"
4485
+ },
4486
+ signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
4487
+ });
4488
+ if (response.status !== 200) {
4489
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4490
+ }
4491
+ const buffer = await response.arrayBuffer();
4492
+ const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
4493
+ new Uint8Array(buffer)
4506
4494
  );
4507
- try {
4508
- alertStmt.run(fieldValues);
4509
- if (entity.alert.informedEntity?.length) {
4510
- const informedEntities = entity.alert.informedEntity.map(
4511
- (informedEntity) => {
4495
+ const feedMessage = GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
4496
+ enums: String,
4497
+ longs: String,
4498
+ bytes: String,
4499
+ defaults: false,
4500
+ arrays: true,
4501
+ objects: true,
4502
+ oneofs: true
4503
+ });
4504
+ return feedMessage;
4505
+ } catch (error) {
4506
+ const errorMessage = error instanceof Error ? error.message : String(error);
4507
+ if (attempt === MAX_RETRIES) {
4508
+ if (task.ignoreErrors) {
4509
+ task.logError(
4510
+ `Failed to fetch ${type} after ${MAX_RETRIES} attempts: ${errorMessage}`
4511
+ );
4512
+ return null;
4513
+ }
4514
+ throw error;
4515
+ }
4516
+ task.logWarning(`Attempt ${attempt} failed for ${type}: ${errorMessage}`);
4517
+ await new Promise(
4518
+ (resolve) => setTimeout(resolve, RETRY_DELAY * attempt)
4519
+ );
4520
+ }
4521
+ }
4522
+ return null;
4523
+ }
4524
+ function getUrlConfig(type, task) {
4525
+ switch (type) {
4526
+ case "alerts":
4527
+ return task.realtimeAlerts;
4528
+ case "tripupdates":
4529
+ return task.realtimeTripUpdates;
4530
+ case "vehiclepositions":
4531
+ return task.realtimeVehiclePositions;
4532
+ default:
4533
+ return void 0;
4534
+ }
4535
+ }
4536
+ function createServiceAlertsProcessor(db, task) {
4537
+ const alertStmt = createPreparedStatement(db, serviceAlerts);
4538
+ const informedEntityStmt = createPreparedStatement(
4539
+ db,
4540
+ serviceAlertInformedEntities
4541
+ );
4542
+ return async (batch) => {
4543
+ let recordCount = 0;
4544
+ let errorCount = 0;
4545
+ db.transaction(() => {
4546
+ for (const entity of batch) {
4547
+ try {
4548
+ const alertValues = serviceAlerts.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4549
+ alertStmt.run(alertValues);
4550
+ recordCount++;
4551
+ if (entity.alert?.informedEntity?.length) {
4552
+ for (const informedEntity of entity.alert.informedEntity) {
4512
4553
  informedEntity.parent = entity;
4513
- return serviceAlertInformedEntities.schema.map(
4554
+ const entityValues = serviceAlertInformedEntities.schema.map(
4514
4555
  (column) => prepareRealtimeFieldValue(informedEntity, column, task)
4515
4556
  );
4557
+ informedEntityStmt.run(entityValues);
4558
+ recordCount++;
4516
4559
  }
4517
- );
4518
- for (const values of informedEntities) {
4519
- informedEntityStmt.run(values);
4520
4560
  }
4561
+ } catch (error) {
4562
+ const errorMessage = error instanceof Error ? error.message : String(error);
4563
+ errorCount++;
4564
+ task.logWarning(`Alert processing error: ${errorMessage}`);
4521
4565
  }
4522
- totalLineCount++;
4523
- } catch (error) {
4524
- task.logWarning(`Import error: ${error.message}`);
4525
4566
  }
4526
- }
4527
- task.log(
4528
- `Importing - GTFS-Realtime service alerts - ${totalLineCount} entries imported\r`,
4529
- true
4530
- );
4531
- })();
4567
+ })();
4568
+ return { recordCount, errorCount };
4569
+ };
4532
4570
  }
4533
- async function processRealtimeTripUpdates(db, gtfsRealtimeData, task) {
4534
- let totalLineCount = 0;
4535
- const tripUpdateStmt = db.prepare(
4536
- `REPLACE INTO ${tripUpdates.filenameBase} (${tripUpdates.schema.map((column) => column.name).join(
4537
- ", "
4538
- )}) VALUES (${tripUpdates.schema.map(() => "?").join(", ")})`
4571
+ function createTripUpdatesProcessor(db, task) {
4572
+ const tripUpdateStmt = createPreparedStatement(
4573
+ db,
4574
+ tripUpdates
4539
4575
  );
4540
- const stopTimeStmt = db.prepare(
4541
- `REPLACE INTO ${stopTimeUpdates.filenameBase} (${stopTimeUpdates.schema.map((column) => column.name).join(
4542
- ", "
4543
- )}) VALUES (${stopTimeUpdates.schema.map(() => "?").join(", ")})`
4576
+ const stopTimeStmt = createPreparedStatement(
4577
+ db,
4578
+ stopTimeUpdates
4544
4579
  );
4545
- db.transaction(() => {
4546
- for (const entity of gtfsRealtimeData.entity) {
4547
- try {
4548
- const fieldValues = tripUpdates.schema.map(
4549
- (column) => prepareRealtimeFieldValue(entity, column, task)
4550
- );
4551
- tripUpdateStmt.run(fieldValues);
4552
- for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
4553
- stopTimeUpdate.parent = entity;
4554
- const values = stopTimeUpdates.schema.map(
4555
- (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
4556
- );
4557
- stopTimeStmt.run(values);
4580
+ return async (batch) => {
4581
+ let recordCount = 0;
4582
+ let errorCount = 0;
4583
+ db.transaction(() => {
4584
+ for (const entity of batch) {
4585
+ try {
4586
+ const tripUpdateValues = tripUpdates.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4587
+ tripUpdateStmt.run(tripUpdateValues);
4588
+ recordCount++;
4589
+ if (entity.tripUpdate?.stopTimeUpdate?.length) {
4590
+ for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
4591
+ stopTimeUpdate.parent = entity;
4592
+ const stopTimeValues = stopTimeUpdates.schema.map(
4593
+ (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
4594
+ );
4595
+ stopTimeStmt.run(stopTimeValues);
4596
+ recordCount++;
4597
+ }
4598
+ }
4599
+ } catch (error) {
4600
+ const errorMessage = error instanceof Error ? error.message : String(error);
4601
+ errorCount++;
4602
+ task.logWarning(`Trip update processing error: ${errorMessage}`);
4558
4603
  }
4559
- totalLineCount++;
4560
- } catch (error) {
4561
- task.logWarning(`Import error: ${error.message}`);
4562
4604
  }
4563
- }
4564
- task.log(
4565
- `Importing - GTFS-Realtime trip updates - ${totalLineCount} entries imported\r`,
4566
- true
4567
- );
4568
- })();
4605
+ })();
4606
+ return { recordCount, errorCount };
4607
+ };
4569
4608
  }
4570
- async function processRealtimeVehiclePositions(db, gtfsRealtimeData, task) {
4571
- let totalLineCount = 0;
4572
- const vehiclePositionStmt = db.prepare(
4573
- `REPLACE INTO ${vehiclePositions.filenameBase} (${vehiclePositions.schema.map((column) => column.name).join(
4574
- ", "
4575
- )}) VALUES (${vehiclePositions.schema.map(() => "?").join(", ")})`
4609
+ function createVehiclePositionsProcessor(db, task) {
4610
+ const vehiclePositionStmt = createPreparedStatement(
4611
+ db,
4612
+ vehiclePositions
4576
4613
  );
4577
- db.transaction(() => {
4578
- for (const entity of gtfsRealtimeData.entity) {
4579
- try {
4580
- const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4581
- vehiclePositionStmt.run(fieldValues);
4582
- totalLineCount++;
4583
- } catch (error) {
4584
- task.logWarning(`Import error: ${error.message}`);
4614
+ return async (batch) => {
4615
+ let recordCount = 0;
4616
+ let errorCount = 0;
4617
+ db.transaction(() => {
4618
+ for (const entity of batch) {
4619
+ try {
4620
+ const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4621
+ vehiclePositionStmt.run(fieldValues);
4622
+ recordCount++;
4623
+ } catch (error) {
4624
+ const errorMessage = error instanceof Error ? error.message : String(error);
4625
+ errorCount++;
4626
+ task.logWarning(`Vehicle position processing error: ${errorMessage}`);
4627
+ }
4585
4628
  }
4629
+ })();
4630
+ return { recordCount, errorCount };
4631
+ };
4632
+ }
4633
+ function removeExpiredRealtimeData(config) {
4634
+ const db = openDb(config);
4635
+ log(config)(`Removing expired GTFS-Realtime data`);
4636
+ db.transaction(() => {
4637
+ const tables = [
4638
+ "vehicle_positions",
4639
+ "trip_updates",
4640
+ "stop_time_updates",
4641
+ "service_alerts",
4642
+ "service_alert_informed_entities"
4643
+ ];
4644
+ for (const table of tables) {
4645
+ db.prepare(
4646
+ `DELETE FROM ${table} WHERE expiration_timestamp <= strftime('%s','now')`
4647
+ ).run();
4586
4648
  }
4587
- task.log(
4588
- `Importing - GTFS-Realtime vehicle positions - ${totalLineCount} entries imported\r`,
4589
- true
4590
- );
4591
4649
  })();
4650
+ log(config)(`Removed expired GTFS-Realtime data\r`, true);
4592
4651
  }
4593
4652
  async function updateGtfsRealtimeData(task) {
4594
- if (task.realtimeAlerts === void 0 && task.realtimeTripUpdates === void 0 && task.realtimeVehiclePositions === void 0) {
4653
+ if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) {
4595
4654
  return;
4596
4655
  }
4656
+ const [alertsData, tripUpdatesData, vehiclePositionsData] = await Promise.all(
4657
+ [
4658
+ task.realtimeAlerts?.url ? fetchGtfsRealtimeData("alerts", task) : null,
4659
+ task.realtimeTripUpdates?.url ? fetchGtfsRealtimeData("tripupdates", task) : null,
4660
+ task.realtimeVehiclePositions?.url ? fetchGtfsRealtimeData("vehiclepositions", task) : null
4661
+ ]
4662
+ );
4597
4663
  const db = openDb({ sqlitePath: task.sqlitePath });
4598
- if (task.realtimeAlerts?.url) {
4599
- try {
4600
- const alertsData = await fetchGtfsRealtimeData(task.realtimeAlerts, task);
4601
- if (alertsData?.entity) {
4602
- await processRealtimeAlerts(db, alertsData, task);
4603
- }
4604
- } catch (error) {
4605
- if (task.ignoreErrors) {
4606
- task.logError(error.message);
4607
- } else {
4608
- throw error;
4609
- }
4610
- }
4664
+ const recordCounts = {
4665
+ alerts: 0,
4666
+ tripupdates: 0,
4667
+ vehiclepositions: 0
4668
+ };
4669
+ const processingPromises = [];
4670
+ if (alertsData?.entity?.length) {
4671
+ processingPromises.push(
4672
+ processBatch(
4673
+ alertsData.entity,
4674
+ BATCH_SIZE,
4675
+ createServiceAlertsProcessor(db, task)
4676
+ ).then((result) => {
4677
+ recordCounts.alerts = result.recordCount;
4678
+ })
4679
+ );
4611
4680
  }
4612
- if (task.realtimeTripUpdates?.url) {
4613
- try {
4614
- const tripUpdatesData = await fetchGtfsRealtimeData(
4615
- task.realtimeTripUpdates,
4616
- task
4617
- );
4618
- if (tripUpdatesData?.entity) {
4619
- await processRealtimeTripUpdates(db, tripUpdatesData, task);
4620
- }
4621
- } catch (error) {
4622
- if (task.ignoreErrors) {
4623
- task.logError(error.message);
4624
- } else {
4625
- throw error;
4626
- }
4627
- }
4681
+ if (tripUpdatesData?.entity?.length) {
4682
+ processingPromises.push(
4683
+ processBatch(
4684
+ tripUpdatesData.entity,
4685
+ BATCH_SIZE,
4686
+ createTripUpdatesProcessor(db, task)
4687
+ ).then((result) => {
4688
+ recordCounts.tripupdates = result.recordCount;
4689
+ })
4690
+ );
4628
4691
  }
4629
- if (task.realtimeVehiclePositions?.url) {
4630
- try {
4631
- const vehiclePositionsData = await fetchGtfsRealtimeData(
4632
- task.realtimeVehiclePositions,
4633
- task
4634
- );
4635
- if (vehiclePositionsData?.entity) {
4636
- await processRealtimeVehiclePositions(db, vehiclePositionsData, task);
4637
- }
4638
- } catch (error) {
4639
- if (task.ignoreErrors) {
4640
- task.logError(error.message);
4641
- } else {
4642
- throw error;
4643
- }
4644
- }
4692
+ if (vehiclePositionsData?.entity?.length) {
4693
+ processingPromises.push(
4694
+ processBatch(
4695
+ vehiclePositionsData.entity,
4696
+ BATCH_SIZE,
4697
+ createVehiclePositionsProcessor(db, task)
4698
+ ).then((result) => {
4699
+ recordCounts.vehiclepositions = result.recordCount;
4700
+ })
4701
+ );
4645
4702
  }
4646
- task.log(`GTFS-Realtime data import complete`);
4703
+ await Promise.all(processingPromises);
4704
+ task.log(
4705
+ `GTFS-Realtime import complete: ${recordCounts.alerts} alerts, ${recordCounts.tripupdates} trip updates, ${recordCounts.vehiclepositions} vehicle positions`
4706
+ );
4647
4707
  }
4648
4708
  async function updateGtfsRealtime(initialConfig) {
4649
4709
  const config = setDefaultConfig(initialConfig);
@@ -4653,9 +4713,9 @@ async function updateGtfsRealtime(initialConfig) {
4653
4713
  const agencyCount = config.agencies.length;
4654
4714
  log(config)(
4655
4715
  `Starting GTFS-Realtime refresh for ${pluralize(
4716
+ "agency",
4656
4717
  "agencies",
4657
- agencyCount,
4658
- true
4718
+ agencyCount
4659
4719
  )} using SQLite database at ${config.sqlitePath}`
4660
4720
  );
4661
4721
  removeExpiredRealtimeData(config);
@@ -4677,8 +4737,9 @@ async function updateGtfsRealtime(initialConfig) {
4677
4737
  };
4678
4738
  await updateGtfsRealtimeData(task);
4679
4739
  } catch (error) {
4740
+ const errorMessage = error instanceof Error ? error.message : String(error);
4680
4741
  if (config.ignoreErrors) {
4681
- logError(config)(error.message);
4742
+ logError(config)(errorMessage);
4682
4743
  } else {
4683
4744
  throw error;
4684
4745
  }
@@ -4686,14 +4747,14 @@ async function updateGtfsRealtime(initialConfig) {
4686
4747
  });
4687
4748
  log(config)(
4688
4749
  `Completed GTFS-Realtime refresh for ${pluralize(
4750
+ "agency",
4689
4751
  "agencies",
4690
- agencyCount,
4691
- true
4752
+ agencyCount
4692
4753
  )}
4693
4754
  `
4694
4755
  );
4695
4756
  } catch (error) {
4696
- if (error?.code === "SQLITE_CANTOPEN") {
4757
+ if (error.code === "SQLITE_CANTOPEN") {
4697
4758
  logError(config)(
4698
4759
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
4699
4760
  );
@@ -4731,8 +4792,8 @@ var extractGtfsFiles = async (task) => {
4731
4792
  if (!task.path) {
4732
4793
  throw new Error("No `path` specified in config");
4733
4794
  }
4734
- const gtfsPath = untildify3(task.path);
4735
- task.log(`Importing GTFS from ${task.path}\r`);
4795
+ const gtfsPath = untildify(task.path);
4796
+ task.log(`Importing static GTFS from ${task.path}\r`);
4736
4797
  if (path2.extname(gtfsPath) === ".zip") {
4737
4798
  try {
4738
4799
  await unzip(gtfsPath, task.downloadDir);
@@ -4815,11 +4876,11 @@ var createGtfsTables = (db) => {
4815
4876
  if (column.type === "time") {
4816
4877
  sqlColumnCreateStatements.push(
4817
4878
  `${getTimestampColumnName(column.name)} INTEGER GENERATED ALWAYS AS (
4818
- CASE
4819
- WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
4879
+ CASE
4880
+ WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
4820
4881
  ELSE CAST(
4821
- substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
4822
- substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
4882
+ substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
4883
+ substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
4823
4884
  substr(${column.name}, -2) AS INTEGER
4824
4885
  )
4825
4886
  END
@@ -4876,7 +4937,7 @@ var formatGtfsLine = (line, model, totalLineCount) => {
4876
4937
  continue;
4877
4938
  }
4878
4939
  if (type === "date") {
4879
- value = value.replace(/-/g, "");
4940
+ value = value?.toString().replace(/-/g, "");
4880
4941
  if (value.length !== 8) {
4881
4942
  throw new Error(
4882
4943
  `Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
@@ -4892,155 +4953,221 @@ var formatGtfsLine = (line, model, totalLineCount) => {
4892
4953
  }
4893
4954
  return formattedLine;
4894
4955
  };
4895
- var BATCH_SIZE = 1e5;
4896
- var importGtfsFiles = (db, task) => mapSeries2(
4897
- Object.values(models_exports),
4898
- (model) => new Promise((resolve, reject) => {
4899
- let totalLineCount = 0;
4900
- const filename = `${model.filenameBase}.${model.filenameExtension}`;
4901
- if (task.exclude && task.exclude.includes(model.filenameBase)) {
4902
- task.log(`Skipping - ${filename}\r`);
4903
- resolve();
4904
- return;
4905
- }
4906
- if (model.extension === "gtfs-realtime") {
4907
- resolve();
4908
- return;
4909
- }
4910
- const filepath = path2.join(task.downloadDir, `${filename}`);
4911
- if (!existsSync2(filepath)) {
4912
- if (!model.nonstandard) {
4913
- task.log(`Importing - ${filename} - No file found\r`);
4956
+ var BATCH_SIZE2 = 1e5;
4957
+ var importGtfsFiles = async (db, task) => {
4958
+ await mapSeries2(
4959
+ Object.values(models_exports),
4960
+ (model) => new Promise((resolve, reject) => {
4961
+ let totalLineCount = 0;
4962
+ const filename = `${model.filenameBase}.${model.filenameExtension}`;
4963
+ if (task.exclude && task.exclude.includes(model.filenameBase)) {
4964
+ task.log(`Skipping - ${filename}\r`);
4965
+ resolve();
4966
+ return;
4914
4967
  }
4915
- resolve();
4916
- return;
4917
- }
4918
- task.log(`Importing - ${filename}\r`);
4919
- const columns = model.schema;
4920
- const prefixedColumns = new Set(
4921
- columns.filter((column) => column.prefix).map((column) => column.name)
4922
- );
4923
- const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
4924
- const insert = db.prepare(prepareStatement);
4925
- const insertLines = db.transaction((lines) => {
4926
- for (const [rowNumber, line] of Object.entries(lines)) {
4927
- try {
4928
- if (task.prefix === void 0) {
4929
- insert.run(line);
4930
- } else {
4931
- const prefixedLine = Object.fromEntries(
4932
- Object.entries(
4933
- line
4934
- ).map(([columnName, value]) => [
4935
- columnName,
4936
- applyPrefixToValue(
4937
- value,
4938
- prefixedColumns.has(columnName),
4939
- task.prefix
4940
- )
4941
- ])
4942
- );
4943
- insert.run(prefixedLine);
4944
- }
4945
- } catch (error) {
4946
- if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
4947
- const primaryColumns = columns.filter(
4948
- (column) => column.primary
4949
- );
4950
- task.logWarning(
4951
- `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`
4952
- );
4953
- }
4954
- task.logWarning(
4955
- `Check ${filename} for invalid data on line ${rowNumber + 1}.`
4956
- );
4957
- throw error;
4968
+ if (model.extension === "gtfs-realtime") {
4969
+ resolve();
4970
+ return;
4971
+ }
4972
+ const filepath = path2.join(task.downloadDir, `${filename}`);
4973
+ if (!existsSync2(filepath)) {
4974
+ if (!model.nonstandard) {
4975
+ task.log(`Importing - ${filename} - No file found\r`);
4958
4976
  }
4977
+ resolve();
4978
+ return;
4959
4979
  }
4960
- });
4961
- if (model.filenameExtension === "txt") {
4962
- const parser = parse({
4963
- columns: true,
4964
- relax_quotes: true,
4965
- trim: true,
4966
- skip_empty_lines: true,
4967
- ...task.csvOptions
4968
- });
4969
- let lines = [];
4970
- parser.on("readable", () => {
4971
- try {
4972
- let record;
4973
- while (record = parser.read()) {
4974
- totalLineCount += 1;
4975
- lines.push(formatGtfsLine(record, model, totalLineCount));
4976
- if (lines.length >= BATCH_SIZE) {
4977
- insertLines(lines);
4978
- lines = [];
4979
- task.log(
4980
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4981
- true
4980
+ task.log(`Importing - ${filename}\r`);
4981
+ const columns = model.schema;
4982
+ const prefixedColumns = new Set(
4983
+ columns.filter((column) => column.prefix).map((column) => column.name)
4984
+ );
4985
+ const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
4986
+ const insert = db.prepare(prepareStatement);
4987
+ const insertLines = db.transaction((lines) => {
4988
+ for (const [rowNumber, line] of Object.entries(lines)) {
4989
+ try {
4990
+ if (task.prefix === void 0) {
4991
+ insert.run(line);
4992
+ } else {
4993
+ const prefixedLine = Object.fromEntries(
4994
+ Object.entries(
4995
+ line
4996
+ ).map(([columnName, value]) => [
4997
+ columnName,
4998
+ applyPrefixToValue(
4999
+ value,
5000
+ prefixedColumns.has(columnName),
5001
+ task.prefix
5002
+ )
5003
+ ])
4982
5004
  );
5005
+ insert.run(prefixedLine);
4983
5006
  }
5007
+ } catch (error) {
5008
+ if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
5009
+ const primaryColumns = columns.filter(
5010
+ (column) => column.primary
5011
+ );
5012
+ task.logWarning(
5013
+ `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`
5014
+ );
5015
+ }
5016
+ task.logWarning(
5017
+ `Check ${filename} for invalid data on line ${rowNumber + 1}.`
5018
+ );
5019
+ throw error;
4984
5020
  }
4985
- } catch (error) {
4986
- reject(error);
4987
5021
  }
4988
5022
  });
4989
- parser.on("end", () => {
4990
- try {
4991
- if (lines.length > 0) {
4992
- try {
4993
- insertLines(lines);
4994
- } catch (error) {
5023
+ if (model.filenameExtension === "txt") {
5024
+ const parser = parse({
5025
+ columns: true,
5026
+ relax_quotes: true,
5027
+ trim: true,
5028
+ skip_empty_lines: true,
5029
+ ...task.csvOptions
5030
+ });
5031
+ let lines = [];
5032
+ parser.on("readable", () => {
5033
+ try {
5034
+ let record;
5035
+ while (record = parser.read()) {
5036
+ totalLineCount += 1;
5037
+ lines.push(formatGtfsLine(record, model, totalLineCount));
5038
+ if (lines.length >= BATCH_SIZE2) {
5039
+ insertLines(lines);
5040
+ lines = [];
5041
+ task.log(
5042
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5043
+ true
5044
+ );
5045
+ }
5046
+ }
5047
+ } catch (error) {
5048
+ if (task.ignoreErrors) {
5049
+ const errorMessage = error instanceof Error ? error.message : String(error);
5050
+ task.logError(`Error processing ${filename}: ${errorMessage}`);
5051
+ resolve();
5052
+ } else {
5053
+ reject(error);
5054
+ }
5055
+ }
5056
+ });
5057
+ parser.on("end", () => {
5058
+ try {
5059
+ if (lines.length > 0) {
5060
+ try {
5061
+ insertLines(lines);
5062
+ } catch (error) {
5063
+ if (task.ignoreErrors) {
5064
+ const errorMessage = error instanceof Error ? error.message : String(error);
5065
+ task.logError(
5066
+ `Error inserting data for ${filename}: ${errorMessage}`
5067
+ );
5068
+ resolve();
5069
+ return;
5070
+ } else {
5071
+ reject(error);
5072
+ return;
5073
+ }
5074
+ }
5075
+ }
5076
+ task.log(
5077
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5078
+ true
5079
+ );
5080
+ resolve();
5081
+ } catch (error) {
5082
+ if (task.ignoreErrors) {
5083
+ const errorMessage = error instanceof Error ? error.message : String(error);
5084
+ task.logError(`Error finalizing ${filename}: ${errorMessage}`);
5085
+ resolve();
5086
+ } else {
4995
5087
  reject(error);
4996
5088
  }
4997
5089
  }
4998
- task.log(
4999
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5000
- true
5090
+ });
5091
+ parser.on("error", (error) => {
5092
+ if (task.ignoreErrors) {
5093
+ const errorMessage = error instanceof Error ? error.message : String(error);
5094
+ task.logError(`Parser error for ${filename}: ${errorMessage}`);
5095
+ resolve();
5096
+ } else {
5097
+ reject(error);
5098
+ }
5099
+ });
5100
+ createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
5101
+ } else if (model.filenameExtension === "geojson") {
5102
+ readFile2(filepath, "utf8").then((data) => {
5103
+ if (isValidJSON(data) === false) {
5104
+ if (task.ignoreErrors) {
5105
+ task.logError(`Invalid JSON in ${filename}`);
5106
+ resolve();
5107
+ return;
5108
+ } else {
5109
+ reject(new Error(`Invalid JSON in ${filename}`));
5110
+ return;
5111
+ }
5112
+ }
5113
+ totalLineCount += 1;
5114
+ const line = formatGtfsLine(
5115
+ { geojson: data },
5116
+ model,
5117
+ totalLineCount
5118
+ );
5119
+ try {
5120
+ insertLines([line]);
5121
+ task.log(
5122
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5123
+ true
5124
+ );
5125
+ resolve();
5126
+ } catch (error) {
5127
+ if (task.ignoreErrors) {
5128
+ const errorMessage = error instanceof Error ? error.message : String(error);
5129
+ task.logError(
5130
+ `Error inserting data for ${filename}: ${errorMessage}`
5131
+ );
5132
+ resolve();
5133
+ } else {
5134
+ reject(error);
5135
+ }
5136
+ }
5137
+ }).catch((error) => {
5138
+ if (task.ignoreErrors) {
5139
+ const errorMessage = error instanceof Error ? error.message : String(error);
5140
+ task.logError(`Error reading ${filename}: ${errorMessage}`);
5141
+ resolve();
5142
+ } else {
5143
+ reject(error);
5144
+ }
5145
+ });
5146
+ } else {
5147
+ if (task.ignoreErrors) {
5148
+ task.logError(
5149
+ `Unsupported file type: ${model.filenameExtension} for ${filename}`
5001
5150
  );
5002
5151
  resolve();
5003
- } catch (error) {
5004
- reject(error);
5005
- }
5006
- });
5007
- parser.on("error", reject);
5008
- createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
5009
- } else if (model.filenameExtension === "geojson") {
5010
- readFile2(filepath, "utf8").then((data) => {
5011
- if (isValidJSON(data) === false) {
5012
- reject(new Error(`Invalid JSON in ${filename}`));
5152
+ } else {
5153
+ reject(
5154
+ new Error(`Unsupported file type: ${model.filenameExtension}`)
5155
+ );
5013
5156
  }
5014
- totalLineCount += 1;
5015
- const line = formatGtfsLine(
5016
- { geojson: data },
5017
- model,
5018
- totalLineCount
5019
- );
5020
- insertLines([line]);
5021
- task.log(
5022
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5023
- true
5024
- );
5025
- resolve();
5026
- }).catch(reject);
5027
- } else {
5028
- reject(
5029
- new Error(`Unsupported file type: ${model.filenameExtension}`)
5030
- );
5031
- }
5032
- })
5033
- );
5157
+ }
5158
+ })
5159
+ );
5160
+ task.log(`Static GTFS import complete`);
5161
+ };
5034
5162
  async function importGtfs(initialConfig) {
5035
- const timer = new Timer();
5036
- timer.start();
5163
+ const startTime = process.hrtime.bigint();
5037
5164
  const config = setDefaultConfig(initialConfig);
5038
5165
  validateConfigForImport(config);
5039
5166
  try {
5040
5167
  const db = openDb(config);
5041
5168
  const agencyCount = config.agencies.length;
5042
5169
  log(config)(
5043
- `Starting GTFS import for ${pluralize2("file", agencyCount, true)} using SQLite database at ${config.sqlitePath}`
5170
+ `Starting GTFS import for ${pluralize("file", "files", agencyCount)} using SQLite database at ${config.sqlitePath}`
5044
5171
  );
5045
5172
  createGtfsTables(db);
5046
5173
  await mapSeries2(config.agencies, async (agency2) => {
@@ -5048,7 +5175,6 @@ async function importGtfs(initialConfig) {
5048
5175
  const tempPath = temporaryDirectory();
5049
5176
  const task = {
5050
5177
  exclude: agency2.exclude,
5051
- url: agency2.url,
5052
5178
  headers: agency2.headers,
5053
5179
  realtimeAlerts: agency2.realtimeAlerts,
5054
5180
  realtimeTripUpdates: agency2.realtimeTripUpdates,
@@ -5056,7 +5182,6 @@ async function importGtfs(initialConfig) {
5056
5182
  downloadDir: tempPath,
5057
5183
  downloadTimeout: config.downloadTimeout,
5058
5184
  gtfsRealtimeExpirationSeconds: config.gtfsRealtimeExpirationSeconds,
5059
- path: agency2.path,
5060
5185
  csvOptions: config.csvOptions || {},
5061
5186
  ignoreDuplicates: config.ignoreDuplicates,
5062
5187
  ignoreErrors: config.ignoreErrors,
@@ -5067,8 +5192,13 @@ async function importGtfs(initialConfig) {
5067
5192
  logWarning: logWarning(config),
5068
5193
  logError: logError(config)
5069
5194
  };
5070
- if (task.url) {
5195
+ if ("url" in agency2) {
5196
+ Object.assign(task, { url: agency2.url });
5071
5197
  await downloadGtfsFiles(task);
5198
+ } else {
5199
+ Object.assign(task, {
5200
+ path: agency2.path
5201
+ });
5072
5202
  }
5073
5203
  await extractGtfsFiles(task);
5074
5204
  await importGtfsFiles(db, task);
@@ -5076,7 +5206,8 @@ async function importGtfs(initialConfig) {
5076
5206
  await rm2(tempPath, { recursive: true });
5077
5207
  } catch (error) {
5078
5208
  if (config.ignoreErrors) {
5079
- logError(config)(error.message);
5209
+ const errorMessage = error instanceof Error ? error.message : String(error);
5210
+ logError(config)(errorMessage);
5080
5211
  } else {
5081
5212
  throw error;
5082
5213
  }
@@ -5084,14 +5215,14 @@ async function importGtfs(initialConfig) {
5084
5215
  });
5085
5216
  log(config)(`Creating DB indexes`);
5086
5217
  createGtfsIndexes(db);
5087
- const seconds = Math.round(timer.time() / 1e3);
5088
- timer.stop();
5218
+ const endTime = process.hrtime.bigint();
5219
+ const elapsedSeconds = Number(endTime - startTime) / 1e9;
5089
5220
  log(config)(
5090
- `Completed GTFS import for ${pluralize2("agency", agencyCount, true)} in ${seconds} seconds
5221
+ `Completed GTFS import in ${elapsedSeconds.toFixed(1)} seconds
5091
5222
  `
5092
5223
  );
5093
5224
  } catch (error) {
5094
- if (error?.code === "SQLITE_CANTOPEN") {
5225
+ if (error.code === "SQLITE_CANTOPEN") {
5095
5226
  logError(config)(
5096
5227
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
5097
5228
  );
@@ -5104,15 +5235,13 @@ async function importGtfs(initialConfig) {
5104
5235
  import path3 from "path";
5105
5236
  import { writeFile as writeFile2 } from "fs/promises";
5106
5237
  import { without, compact as compact2 } from "lodash-es";
5107
- import pluralize3 from "pluralize";
5108
5238
  import { stringify } from "csv-stringify";
5109
5239
  import sqlString2 from "sqlstring-sqlite";
5110
5240
  import mapSeries3 from "promise-map-series";
5111
- import untildify4 from "untildify";
5112
5241
  var getAgencies = (db, config) => {
5113
5242
  try {
5114
5243
  return db.prepare("SELECT agency_name FROM agency;").all();
5115
- } catch (error) {
5244
+ } catch {
5116
5245
  if (config.sqlitePath === ":memory:") {
5117
5246
  throw new Error(
5118
5247
  '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:".'
@@ -5138,15 +5267,15 @@ var exportGtfs = async (initialConfig) => {
5138
5267
  );
5139
5268
  }
5140
5269
  log(config)(
5141
- `Starting GTFS export for ${pluralize3(
5270
+ `Starting GTFS export for ${pluralize(
5142
5271
  "agency",
5143
- agencyCount,
5144
- true
5272
+ "agencies",
5273
+ agencyCount
5145
5274
  )} using SQLite database at ${config.sqlitePath}`
5146
5275
  );
5147
5276
  const folderName = generateFolderName(agencies[0].agency_name);
5148
5277
  const defaultExportPath = path3.join(process.cwd(), "gtfs-export", folderName);
5149
- const exportPath = untildify4(config.exportPath || defaultExportPath);
5278
+ const exportPath = untildify(config.exportPath || defaultExportPath);
5150
5279
  await prepDirectory(exportPath);
5151
5280
  const modelsToExport = Object.values(models_exports).filter(
5152
5281
  (model) => model.extension !== "gtfs-realtime"
@@ -5179,11 +5308,17 @@ var exportGtfs = async (initialConfig) => {
5179
5308
  }
5180
5309
  } else if (model.filenameBase === "fare_attributes") {
5181
5310
  for (const line of lines) {
5182
- line.price = formatCurrency(line.price, line.currency_type);
5311
+ line.price = formatCurrency(
5312
+ line.price,
5313
+ line.currency_type
5314
+ );
5183
5315
  }
5184
5316
  } else if (model.filenameBase === "fare_products") {
5185
5317
  for (const line of lines) {
5186
- line.amount = formatCurrency(line.amount, line.currency);
5318
+ line.amount = formatCurrency(
5319
+ line.amount,
5320
+ line.currency
5321
+ );
5187
5322
  }
5188
5323
  }
5189
5324
  const columns = without(
@@ -5214,7 +5349,7 @@ var exportGtfs = async (initialConfig) => {
5214
5349
  }
5215
5350
  log(config)(`Completed GTFS export to ${exportPath}`);
5216
5351
  log(config)(
5217
- `Completed GTFS export for ${pluralize3("agency", agencyCount, true)}
5352
+ `Completed GTFS export for ${pluralize("agency", "agencies", agencyCount)}
5218
5353
  `
5219
5354
  );
5220
5355
  };
@@ -5558,10 +5693,7 @@ function getRoutes(query = {}, fields = [], orderBy2 = [], options = {}) {
5558
5693
  let whereClause = "";
5559
5694
  const orderByClause = formatOrderByClause(orderBy2);
5560
5695
  const routeQuery = omit3(query, ["stop_id", "service_id"]);
5561
- const tripQuery = pick(query, [
5562
- "stop_id",
5563
- "service_id"
5564
- ]);
5696
+ const tripQuery = pick(query, ["stop_id", "service_id"]);
5565
5697
  const whereClauses = Object.entries(routeQuery).map(
5566
5698
  ([key, value]) => formatWhereClause(key, value)
5567
5699
  );
@@ -5609,7 +5741,12 @@ function getShapes(query = {}, fields = [], orderBy2 = [], options = {}) {
5609
5741
  "service_id",
5610
5742
  "direction_id"
5611
5743
  ]);
5612
- const tripQuery = pick2(query, ["route_id", "trip_id", "service_id", "direction_id"]);
5744
+ const tripQuery = pick2(query, [
5745
+ "route_id",
5746
+ "trip_id",
5747
+ "service_id",
5748
+ "direction_id"
5749
+ ]);
5613
5750
  const whereClauses = Object.entries(shapeQuery).map(
5614
5751
  ([key, value]) => formatWhereClause(key, value)
5615
5752
  );
@@ -5716,7 +5853,7 @@ function getStops(query = {}, fields = [], orderBy2 = [], options = {}) {
5716
5853
  if (options.bounding_box_side_m !== void 0) {
5717
5854
  stopQueryOmitKeys.push("stop_lat", "stop_lon");
5718
5855
  }
5719
- let stopQuery = omit5(query, stopQueryOmitKeys);
5856
+ const stopQuery = omit5(query, stopQueryOmitKeys);
5720
5857
  const tripQuery = pick3(query, [
5721
5858
  "route_id",
5722
5859
  "trip_id",
@@ -5781,7 +5918,7 @@ function getStoptimes(query = {}, fields = [], orderBy2 = [], options = {}) {
5781
5918
  let whereClause = "";
5782
5919
  const orderByClause = formatOrderByClause(orderBy2);
5783
5920
  const stoptimeQueryOmitKeys = ["date", "start_time", "end_time"];
5784
- let stoptimeQuery = omit6(query, stoptimeQueryOmitKeys);
5921
+ const stoptimeQuery = omit6(query, stoptimeQueryOmitKeys);
5785
5922
  const whereClauses = Object.entries(stoptimeQuery).map(
5786
5923
  ([key, value]) => formatWhereClause(key, value)
5787
5924
  );
@@ -5789,7 +5926,7 @@ function getStoptimes(query = {}, fields = [], orderBy2 = [], options = {}) {
5789
5926
  if (typeof query.date !== "number") {
5790
5927
  throw new Error("`date` must be a number in yyyymmdd format");
5791
5928
  }
5792
- const serviceIds = getServiceIdsByDate(query.date);
5929
+ const serviceIds = getServiceIdsByDate(query.date, options);
5793
5930
  const tripSubquery = `SELECT DISTINCT trip_id FROM trips WHERE service_id IN (${serviceIds.map((id) => sqlString4.escape(id)).join(",")})`;
5794
5931
  whereClauses.push(`trip_id IN (${tripSubquery})`);
5795
5932
  }
@@ -5863,7 +6000,7 @@ function getTrips(query = {}, fields = [], orderBy2 = [], options = {}) {
5863
6000
  let whereClause = "";
5864
6001
  const orderByClause = formatOrderByClause(orderBy2);
5865
6002
  const tripQueryOmitKeys = ["date"];
5866
- let tripQuery = omit7(query, tripQueryOmitKeys);
6003
+ const tripQuery = omit7(query, tripQueryOmitKeys);
5867
6004
  const whereClauses = Object.entries(tripQuery).map(
5868
6005
  ([key, value]) => formatWhereClause(key, value)
5869
6006
  );
@@ -5871,7 +6008,7 @@ function getTrips(query = {}, fields = [], orderBy2 = [], options = {}) {
5871
6008
  if (typeof query.date !== "number") {
5872
6009
  throw new Error("`date` must be a number in yyyymmdd format");
5873
6010
  }
5874
- const serviceIds = getServiceIdsByDate(query.date);
6011
+ const serviceIds = getServiceIdsByDate(query.date, options);
5875
6012
  whereClauses.push(
5876
6013
  `service_id IN (${serviceIds.map((id) => sqlString5.escape(id)).join(",")})`
5877
6014
  );
@@ -6153,6 +6290,7 @@ export {
6153
6290
  closeDb,
6154
6291
  deleteDb,
6155
6292
  exportGtfs,
6293
+ generateFolderName,
6156
6294
  getAgencies2 as getAgencies,
6157
6295
  getAreas,
6158
6296
  getAttributions,
@@ -6213,6 +6351,9 @@ export {
6213
6351
  getVehiclePositions,
6214
6352
  importGtfs,
6215
6353
  openDb,
6354
+ prepDirectory,
6355
+ untildify,
6356
+ unzip,
6216
6357
  updateGtfsRealtime
6217
6358
  };
6218
6359
  //# sourceMappingURL=index.js.map