gtfs 4.17.7 → 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
@@ -4147,7 +4147,7 @@ function isValidJSON(string) {
4147
4147
  try {
4148
4148
  JSON.parse(string);
4149
4149
  return true;
4150
- } catch (error) {
4150
+ } catch {
4151
4151
  return false;
4152
4152
  }
4153
4153
  }
@@ -4212,7 +4212,7 @@ function formatProperties(properties) {
4212
4212
  if (formattedRouteTextColor) {
4213
4213
  formattedProperties.route_text_color = formattedRouteTextColor;
4214
4214
  }
4215
- if (properties.routes) {
4215
+ if (properties.routes && Array.isArray(properties.routes)) {
4216
4216
  formattedProperties.routes = properties.routes.map(
4217
4217
  (route) => formatProperties(route)
4218
4218
  );
@@ -4277,7 +4277,8 @@ function setDefaultConfig(initialConfig) {
4277
4277
  ignoreDuplicates: false,
4278
4278
  ignoreErrors: false,
4279
4279
  gtfsRealtimeExpirationSeconds: 0,
4280
- verbose: true
4280
+ verbose: true,
4281
+ downloadTimeout: 3e4
4281
4282
  };
4282
4283
  return {
4283
4284
  ...defaults,
@@ -4425,56 +4426,9 @@ function pluralize(singularWord, pluralWord, count) {
4425
4426
  }
4426
4427
 
4427
4428
  // src/lib/import-gtfs-realtime.ts
4428
- async function fetchGtfsRealtimeData(urlConfig, task) {
4429
- task.log(`Downloading GTFS-Realtime from ${urlConfig.url}`);
4430
- const response = await fetch(urlConfig.url, {
4431
- method: "GET",
4432
- headers: {
4433
- ...urlConfig.headers ?? {},
4434
- "Accept-Encoding": "gzip"
4435
- },
4436
- signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
4437
- });
4438
- if (response.status !== 200) {
4439
- task.logWarning(
4440
- `Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
4441
- );
4442
- return null;
4443
- }
4444
- const buffer = await response.arrayBuffer();
4445
- const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
4446
- new Uint8Array(buffer)
4447
- );
4448
- return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
4449
- enums: String,
4450
- longs: String,
4451
- bytes: String,
4452
- defaults: false,
4453
- arrays: true,
4454
- objects: true,
4455
- oneofs: true
4456
- });
4457
- }
4458
- function removeExpiredRealtimeData(config) {
4459
- const db = openDb(config);
4460
- log(config)(`Removing expired GTFS-Realtime data`);
4461
- db.prepare(
4462
- `DELETE FROM vehicle_positions WHERE expiration_timestamp <= strftime('%s','now')`
4463
- ).run();
4464
- db.prepare(
4465
- `DELETE FROM trip_updates WHERE expiration_timestamp <= strftime('%s','now')`
4466
- ).run();
4467
- db.prepare(
4468
- `DELETE FROM stop_time_updates WHERE expiration_timestamp <= strftime('%s','now')`
4469
- ).run();
4470
- db.prepare(
4471
- `DELETE FROM service_alerts WHERE expiration_timestamp <= strftime('%s','now')`
4472
- ).run();
4473
- db.prepare(
4474
- `DELETE FROM service_alert_informed_entities WHERE expiration_timestamp <= strftime('%s','now')`
4475
- ).run();
4476
- log(config)(`Removed expired GTFS-Realtime data\r`, true);
4477
- }
4429
+ var BATCH_SIZE = 1e3;
4430
+ var MAX_RETRIES = 3;
4431
+ var RETRY_DELAY = 1e3;
4478
4432
  function prepareRealtimeFieldValue(entity, column, task) {
4479
4433
  if (column.name === "created_timestamp") {
4480
4434
  return task.currentTimestamp;
@@ -4491,163 +4445,265 @@ function prepareRealtimeFieldValue(entity, column, task) {
4491
4445
  );
4492
4446
  return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
4493
4447
  }
4494
- async function processRealtimeAlerts(db, gtfsRealtimeData, task) {
4495
- const alertStmt = db.prepare(
4496
- `REPLACE INTO ${serviceAlerts.filenameBase} (${serviceAlerts.schema.map((column) => column.name).join(
4497
- ", "
4498
- )}) VALUES (${serviceAlerts.schema.map(() => "?").join(", ")})`
4499
- );
4500
- const informedEntityStmt = db.prepare(
4501
- `REPLACE INTO ${serviceAlertInformedEntities.filenameBase} (${serviceAlertInformedEntities.schema.map((column) => column.name).join(
4502
- ", "
4503
- )}) 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})`
4504
4453
  );
4505
- let totalLineCount = 0;
4506
- db.transaction(() => {
4507
- for (const entity of gtfsRealtimeData.entity) {
4508
- const fieldValues = serviceAlerts.schema.map(
4509
- (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)
4510
4494
  );
4511
- try {
4512
- alertStmt.run(fieldValues);
4513
- if (entity.alert.informedEntity?.length) {
4514
- const informedEntities = entity.alert.informedEntity.map(
4515
- (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) {
4516
4553
  informedEntity.parent = entity;
4517
- return serviceAlertInformedEntities.schema.map(
4554
+ const entityValues = serviceAlertInformedEntities.schema.map(
4518
4555
  (column) => prepareRealtimeFieldValue(informedEntity, column, task)
4519
4556
  );
4557
+ informedEntityStmt.run(entityValues);
4558
+ recordCount++;
4520
4559
  }
4521
- );
4522
- for (const values of informedEntities) {
4523
- informedEntityStmt.run(values);
4524
4560
  }
4561
+ } catch (error) {
4562
+ const errorMessage = error instanceof Error ? error.message : String(error);
4563
+ errorCount++;
4564
+ task.logWarning(`Alert processing error: ${errorMessage}`);
4525
4565
  }
4526
- totalLineCount++;
4527
- } catch (error) {
4528
- task.logWarning(`Import error: ${error.message}`);
4529
4566
  }
4530
- }
4531
- task.log(
4532
- `Importing - GTFS-Realtime service alerts - ${totalLineCount} entries imported\r`,
4533
- true
4534
- );
4535
- })();
4567
+ })();
4568
+ return { recordCount, errorCount };
4569
+ };
4536
4570
  }
4537
- async function processRealtimeTripUpdates(db, gtfsRealtimeData, task) {
4538
- let totalLineCount = 0;
4539
- const tripUpdateStmt = db.prepare(
4540
- `REPLACE INTO ${tripUpdates.filenameBase} (${tripUpdates.schema.map((column) => column.name).join(
4541
- ", "
4542
- )}) VALUES (${tripUpdates.schema.map(() => "?").join(", ")})`
4571
+ function createTripUpdatesProcessor(db, task) {
4572
+ const tripUpdateStmt = createPreparedStatement(
4573
+ db,
4574
+ tripUpdates
4543
4575
  );
4544
- const stopTimeStmt = db.prepare(
4545
- `REPLACE INTO ${stopTimeUpdates.filenameBase} (${stopTimeUpdates.schema.map((column) => column.name).join(
4546
- ", "
4547
- )}) VALUES (${stopTimeUpdates.schema.map(() => "?").join(", ")})`
4576
+ const stopTimeStmt = createPreparedStatement(
4577
+ db,
4578
+ stopTimeUpdates
4548
4579
  );
4549
- db.transaction(() => {
4550
- for (const entity of gtfsRealtimeData.entity) {
4551
- try {
4552
- const fieldValues = tripUpdates.schema.map(
4553
- (column) => prepareRealtimeFieldValue(entity, column, task)
4554
- );
4555
- tripUpdateStmt.run(fieldValues);
4556
- for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
4557
- stopTimeUpdate.parent = entity;
4558
- const values = stopTimeUpdates.schema.map(
4559
- (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
4560
- );
4561
- 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}`);
4562
4603
  }
4563
- totalLineCount++;
4564
- } catch (error) {
4565
- task.logWarning(`Import error: ${error.message}`);
4566
4604
  }
4567
- }
4568
- task.log(
4569
- `Importing - GTFS-Realtime trip updates - ${totalLineCount} entries imported\r`,
4570
- true
4571
- );
4572
- })();
4605
+ })();
4606
+ return { recordCount, errorCount };
4607
+ };
4573
4608
  }
4574
- async function processRealtimeVehiclePositions(db, gtfsRealtimeData, task) {
4575
- let totalLineCount = 0;
4576
- const vehiclePositionStmt = db.prepare(
4577
- `REPLACE INTO ${vehiclePositions.filenameBase} (${vehiclePositions.schema.map((column) => column.name).join(
4578
- ", "
4579
- )}) VALUES (${vehiclePositions.schema.map(() => "?").join(", ")})`
4609
+ function createVehiclePositionsProcessor(db, task) {
4610
+ const vehiclePositionStmt = createPreparedStatement(
4611
+ db,
4612
+ vehiclePositions
4580
4613
  );
4581
- db.transaction(() => {
4582
- for (const entity of gtfsRealtimeData.entity) {
4583
- try {
4584
- const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4585
- vehiclePositionStmt.run(fieldValues);
4586
- totalLineCount++;
4587
- } catch (error) {
4588
- 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
+ }
4589
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();
4590
4648
  }
4591
- task.log(
4592
- `Importing - GTFS-Realtime vehicle positions - ${totalLineCount} entries imported\r`,
4593
- true
4594
- );
4595
4649
  })();
4650
+ log(config)(`Removed expired GTFS-Realtime data\r`, true);
4596
4651
  }
4597
4652
  async function updateGtfsRealtimeData(task) {
4598
- if (task.realtimeAlerts === void 0 && task.realtimeTripUpdates === void 0 && task.realtimeVehiclePositions === void 0) {
4653
+ if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) {
4599
4654
  return;
4600
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
+ );
4601
4663
  const db = openDb({ sqlitePath: task.sqlitePath });
4602
- if (task.realtimeAlerts?.url) {
4603
- try {
4604
- const alertsData = await fetchGtfsRealtimeData(task.realtimeAlerts, task);
4605
- if (alertsData?.entity) {
4606
- await processRealtimeAlerts(db, alertsData, task);
4607
- }
4608
- } catch (error) {
4609
- if (task.ignoreErrors) {
4610
- task.logError(error.message);
4611
- } else {
4612
- throw error;
4613
- }
4614
- }
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
+ );
4615
4680
  }
4616
- if (task.realtimeTripUpdates?.url) {
4617
- try {
4618
- const tripUpdatesData = await fetchGtfsRealtimeData(
4619
- task.realtimeTripUpdates,
4620
- task
4621
- );
4622
- if (tripUpdatesData?.entity) {
4623
- await processRealtimeTripUpdates(db, tripUpdatesData, task);
4624
- }
4625
- } catch (error) {
4626
- if (task.ignoreErrors) {
4627
- task.logError(error.message);
4628
- } else {
4629
- throw error;
4630
- }
4631
- }
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
+ );
4632
4691
  }
4633
- if (task.realtimeVehiclePositions?.url) {
4634
- try {
4635
- const vehiclePositionsData = await fetchGtfsRealtimeData(
4636
- task.realtimeVehiclePositions,
4637
- task
4638
- );
4639
- if (vehiclePositionsData?.entity) {
4640
- await processRealtimeVehiclePositions(db, vehiclePositionsData, task);
4641
- }
4642
- } catch (error) {
4643
- if (task.ignoreErrors) {
4644
- task.logError(error.message);
4645
- } else {
4646
- throw error;
4647
- }
4648
- }
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
+ );
4649
4702
  }
4650
- 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
+ );
4651
4707
  }
4652
4708
  async function updateGtfsRealtime(initialConfig) {
4653
4709
  const config = setDefaultConfig(initialConfig);
@@ -4681,8 +4737,9 @@ async function updateGtfsRealtime(initialConfig) {
4681
4737
  };
4682
4738
  await updateGtfsRealtimeData(task);
4683
4739
  } catch (error) {
4740
+ const errorMessage = error instanceof Error ? error.message : String(error);
4684
4741
  if (config.ignoreErrors) {
4685
- logError(config)(error.message);
4742
+ logError(config)(errorMessage);
4686
4743
  } else {
4687
4744
  throw error;
4688
4745
  }
@@ -4697,7 +4754,7 @@ async function updateGtfsRealtime(initialConfig) {
4697
4754
  `
4698
4755
  );
4699
4756
  } catch (error) {
4700
- if (error?.code === "SQLITE_CANTOPEN") {
4757
+ if (error.code === "SQLITE_CANTOPEN") {
4701
4758
  logError(config)(
4702
4759
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
4703
4760
  );
@@ -4736,7 +4793,7 @@ var extractGtfsFiles = async (task) => {
4736
4793
  throw new Error("No `path` specified in config");
4737
4794
  }
4738
4795
  const gtfsPath = untildify(task.path);
4739
- task.log(`Importing GTFS from ${task.path}\r`);
4796
+ task.log(`Importing static GTFS from ${task.path}\r`);
4740
4797
  if (path2.extname(gtfsPath) === ".zip") {
4741
4798
  try {
4742
4799
  await unzip(gtfsPath, task.downloadDir);
@@ -4819,11 +4876,11 @@ var createGtfsTables = (db) => {
4819
4876
  if (column.type === "time") {
4820
4877
  sqlColumnCreateStatements.push(
4821
4878
  `${getTimestampColumnName(column.name)} INTEGER GENERATED ALWAYS AS (
4822
- CASE
4823
- WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
4879
+ CASE
4880
+ WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
4824
4881
  ELSE CAST(
4825
- substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
4826
- 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 +
4827
4884
  substr(${column.name}, -2) AS INTEGER
4828
4885
  )
4829
4886
  END
@@ -4880,7 +4937,7 @@ var formatGtfsLine = (line, model, totalLineCount) => {
4880
4937
  continue;
4881
4938
  }
4882
4939
  if (type === "date") {
4883
- value = value.replace(/-/g, "");
4940
+ value = value?.toString().replace(/-/g, "");
4884
4941
  if (value.length !== 8) {
4885
4942
  throw new Error(
4886
4943
  `Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
@@ -4896,203 +4953,212 @@ var formatGtfsLine = (line, model, totalLineCount) => {
4896
4953
  }
4897
4954
  return formattedLine;
4898
4955
  };
4899
- var BATCH_SIZE = 1e5;
4900
- var importGtfsFiles = (db, task) => mapSeries2(
4901
- Object.values(models_exports),
4902
- (model) => new Promise((resolve, reject) => {
4903
- let totalLineCount = 0;
4904
- const filename = `${model.filenameBase}.${model.filenameExtension}`;
4905
- if (task.exclude && task.exclude.includes(model.filenameBase)) {
4906
- task.log(`Skipping - ${filename}\r`);
4907
- resolve();
4908
- return;
4909
- }
4910
- if (model.extension === "gtfs-realtime") {
4911
- resolve();
4912
- return;
4913
- }
4914
- const filepath = path2.join(task.downloadDir, `${filename}`);
4915
- if (!existsSync2(filepath)) {
4916
- if (!model.nonstandard) {
4917
- 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;
4918
4967
  }
4919
- resolve();
4920
- return;
4921
- }
4922
- task.log(`Importing - ${filename}\r`);
4923
- const columns = model.schema;
4924
- const prefixedColumns = new Set(
4925
- columns.filter((column) => column.prefix).map((column) => column.name)
4926
- );
4927
- const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
4928
- const insert = db.prepare(prepareStatement);
4929
- const insertLines = db.transaction((lines) => {
4930
- for (const [rowNumber, line] of Object.entries(lines)) {
4931
- try {
4932
- if (task.prefix === void 0) {
4933
- insert.run(line);
4934
- } else {
4935
- const prefixedLine = Object.fromEntries(
4936
- Object.entries(
4937
- line
4938
- ).map(([columnName, value]) => [
4939
- columnName,
4940
- applyPrefixToValue(
4941
- value,
4942
- prefixedColumns.has(columnName),
4943
- task.prefix
4944
- )
4945
- ])
4946
- );
4947
- insert.run(prefixedLine);
4948
- }
4949
- } catch (error) {
4950
- if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
4951
- const primaryColumns = columns.filter(
4952
- (column) => column.primary
4953
- );
4954
- task.logWarning(
4955
- `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`
4956
- );
4957
- }
4958
- task.logWarning(
4959
- `Check ${filename} for invalid data on line ${rowNumber + 1}.`
4960
- );
4961
- 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`);
4962
4976
  }
4977
+ resolve();
4978
+ return;
4963
4979
  }
4964
- });
4965
- if (model.filenameExtension === "txt") {
4966
- const parser = parse({
4967
- columns: true,
4968
- relax_quotes: true,
4969
- trim: true,
4970
- skip_empty_lines: true,
4971
- ...task.csvOptions
4972
- });
4973
- let lines = [];
4974
- parser.on("readable", () => {
4975
- try {
4976
- let record;
4977
- while (record = parser.read()) {
4978
- totalLineCount += 1;
4979
- lines.push(formatGtfsLine(record, model, totalLineCount));
4980
- if (lines.length >= BATCH_SIZE) {
4981
- insertLines(lines);
4982
- lines = [];
4983
- task.log(
4984
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4985
- 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
+ ])
4986
5004
  );
5005
+ insert.run(prefixedLine);
4987
5006
  }
4988
- }
4989
- } catch (error) {
4990
- if (task.ignoreErrors) {
4991
- task.logError(`Error processing ${filename}: ${error.message}`);
4992
- resolve();
4993
- } else {
4994
- reject(error);
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;
4995
5020
  }
4996
5021
  }
4997
5022
  });
4998
- parser.on("end", () => {
4999
- try {
5000
- if (lines.length > 0) {
5001
- try {
5002
- insertLines(lines);
5003
- } catch (error) {
5004
- if (task.ignoreErrors) {
5005
- task.logError(
5006
- `Error inserting data for ${filename}: ${error.message}`
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
5007
5044
  );
5008
- resolve();
5009
- return;
5010
- } else {
5011
- reject(error);
5012
- return;
5013
5045
  }
5014
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
+ }
5015
5055
  }
5016
- task.log(
5017
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5018
- true
5019
- );
5020
- resolve();
5021
- } catch (error) {
5022
- if (task.ignoreErrors) {
5023
- task.logError(`Error finalizing ${filename}: ${error.message}`);
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
+ );
5024
5080
  resolve();
5025
- } else {
5026
- reject(error);
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 {
5087
+ reject(error);
5088
+ }
5027
5089
  }
5028
- }
5029
- });
5030
- parser.on("error", (error) => {
5031
- if (task.ignoreErrors) {
5032
- task.logError(`Parser error for ${filename}: ${error.message}`);
5033
- resolve();
5034
- } else {
5035
- reject(error);
5036
- }
5037
- });
5038
- createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
5039
- } else if (model.filenameExtension === "geojson") {
5040
- readFile2(filepath, "utf8").then((data) => {
5041
- if (isValidJSON(data) === false) {
5090
+ });
5091
+ parser.on("error", (error) => {
5042
5092
  if (task.ignoreErrors) {
5043
- task.logError(`Invalid JSON in ${filename}`);
5093
+ const errorMessage = error instanceof Error ? error.message : String(error);
5094
+ task.logError(`Parser error for ${filename}: ${errorMessage}`);
5044
5095
  resolve();
5045
- return;
5046
5096
  } else {
5047
- reject(new Error(`Invalid JSON in ${filename}`));
5048
- return;
5097
+ reject(error);
5049
5098
  }
5050
- }
5051
- totalLineCount += 1;
5052
- const line = formatGtfsLine(
5053
- { geojson: data },
5054
- model,
5055
- totalLineCount
5056
- );
5057
- try {
5058
- insertLines([line]);
5059
- task.log(
5060
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5061
- true
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
5062
5118
  );
5063
- resolve();
5064
- } catch (error) {
5065
- if (task.ignoreErrors) {
5066
- task.logError(
5067
- `Error inserting data for ${filename}: ${error.message}`
5119
+ try {
5120
+ insertLines([line]);
5121
+ task.log(
5122
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
5123
+ true
5068
5124
  );
5069
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();
5070
5142
  } else {
5071
5143
  reject(error);
5072
5144
  }
5073
- }
5074
- }).catch((error) => {
5145
+ });
5146
+ } else {
5075
5147
  if (task.ignoreErrors) {
5076
- task.logError(`Error reading ${filename}: ${error.message}`);
5148
+ task.logError(
5149
+ `Unsupported file type: ${model.filenameExtension} for ${filename}`
5150
+ );
5077
5151
  resolve();
5078
5152
  } else {
5079
- reject(error);
5153
+ reject(
5154
+ new Error(`Unsupported file type: ${model.filenameExtension}`)
5155
+ );
5080
5156
  }
5081
- });
5082
- } else {
5083
- if (task.ignoreErrors) {
5084
- task.logError(
5085
- `Unsupported file type: ${model.filenameExtension} for ${filename}`
5086
- );
5087
- resolve();
5088
- } else {
5089
- reject(
5090
- new Error(`Unsupported file type: ${model.filenameExtension}`)
5091
- );
5092
5157
  }
5093
- }
5094
- })
5095
- );
5158
+ })
5159
+ );
5160
+ task.log(`Static GTFS import complete`);
5161
+ };
5096
5162
  async function importGtfs(initialConfig) {
5097
5163
  const startTime = process.hrtime.bigint();
5098
5164
  const config = setDefaultConfig(initialConfig);
@@ -5109,7 +5175,6 @@ async function importGtfs(initialConfig) {
5109
5175
  const tempPath = temporaryDirectory();
5110
5176
  const task = {
5111
5177
  exclude: agency2.exclude,
5112
- url: agency2.url,
5113
5178
  headers: agency2.headers,
5114
5179
  realtimeAlerts: agency2.realtimeAlerts,
5115
5180
  realtimeTripUpdates: agency2.realtimeTripUpdates,
@@ -5117,7 +5182,6 @@ async function importGtfs(initialConfig) {
5117
5182
  downloadDir: tempPath,
5118
5183
  downloadTimeout: config.downloadTimeout,
5119
5184
  gtfsRealtimeExpirationSeconds: config.gtfsRealtimeExpirationSeconds,
5120
- path: agency2.path,
5121
5185
  csvOptions: config.csvOptions || {},
5122
5186
  ignoreDuplicates: config.ignoreDuplicates,
5123
5187
  ignoreErrors: config.ignoreErrors,
@@ -5128,8 +5192,13 @@ async function importGtfs(initialConfig) {
5128
5192
  logWarning: logWarning(config),
5129
5193
  logError: logError(config)
5130
5194
  };
5131
- if (task.url) {
5195
+ if ("url" in agency2) {
5196
+ Object.assign(task, { url: agency2.url });
5132
5197
  await downloadGtfsFiles(task);
5198
+ } else {
5199
+ Object.assign(task, {
5200
+ path: agency2.path
5201
+ });
5133
5202
  }
5134
5203
  await extractGtfsFiles(task);
5135
5204
  await importGtfsFiles(db, task);
@@ -5137,7 +5206,8 @@ async function importGtfs(initialConfig) {
5137
5206
  await rm2(tempPath, { recursive: true });
5138
5207
  } catch (error) {
5139
5208
  if (config.ignoreErrors) {
5140
- logError(config)(error.message);
5209
+ const errorMessage = error instanceof Error ? error.message : String(error);
5210
+ logError(config)(errorMessage);
5141
5211
  } else {
5142
5212
  throw error;
5143
5213
  }
@@ -5152,7 +5222,7 @@ async function importGtfs(initialConfig) {
5152
5222
  `
5153
5223
  );
5154
5224
  } catch (error) {
5155
- if (error?.code === "SQLITE_CANTOPEN") {
5225
+ if (error.code === "SQLITE_CANTOPEN") {
5156
5226
  logError(config)(
5157
5227
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
5158
5228
  );
@@ -5171,7 +5241,7 @@ import mapSeries3 from "promise-map-series";
5171
5241
  var getAgencies = (db, config) => {
5172
5242
  try {
5173
5243
  return db.prepare("SELECT agency_name FROM agency;").all();
5174
- } catch (error) {
5244
+ } catch {
5175
5245
  if (config.sqlitePath === ":memory:") {
5176
5246
  throw new Error(
5177
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:".'
@@ -5238,11 +5308,17 @@ var exportGtfs = async (initialConfig) => {
5238
5308
  }
5239
5309
  } else if (model.filenameBase === "fare_attributes") {
5240
5310
  for (const line of lines) {
5241
- line.price = formatCurrency(line.price, line.currency_type);
5311
+ line.price = formatCurrency(
5312
+ line.price,
5313
+ line.currency_type
5314
+ );
5242
5315
  }
5243
5316
  } else if (model.filenameBase === "fare_products") {
5244
5317
  for (const line of lines) {
5245
- line.amount = formatCurrency(line.amount, line.currency);
5318
+ line.amount = formatCurrency(
5319
+ line.amount,
5320
+ line.currency
5321
+ );
5246
5322
  }
5247
5323
  }
5248
5324
  const columns = without(
@@ -5617,10 +5693,7 @@ function getRoutes(query = {}, fields = [], orderBy2 = [], options = {}) {
5617
5693
  let whereClause = "";
5618
5694
  const orderByClause = formatOrderByClause(orderBy2);
5619
5695
  const routeQuery = omit3(query, ["stop_id", "service_id"]);
5620
- const tripQuery = pick(query, [
5621
- "stop_id",
5622
- "service_id"
5623
- ]);
5696
+ const tripQuery = pick(query, ["stop_id", "service_id"]);
5624
5697
  const whereClauses = Object.entries(routeQuery).map(
5625
5698
  ([key, value]) => formatWhereClause(key, value)
5626
5699
  );
@@ -5668,7 +5741,12 @@ function getShapes(query = {}, fields = [], orderBy2 = [], options = {}) {
5668
5741
  "service_id",
5669
5742
  "direction_id"
5670
5743
  ]);
5671
- 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
+ ]);
5672
5750
  const whereClauses = Object.entries(shapeQuery).map(
5673
5751
  ([key, value]) => formatWhereClause(key, value)
5674
5752
  );
@@ -5775,7 +5853,7 @@ function getStops(query = {}, fields = [], orderBy2 = [], options = {}) {
5775
5853
  if (options.bounding_box_side_m !== void 0) {
5776
5854
  stopQueryOmitKeys.push("stop_lat", "stop_lon");
5777
5855
  }
5778
- let stopQuery = omit5(query, stopQueryOmitKeys);
5856
+ const stopQuery = omit5(query, stopQueryOmitKeys);
5779
5857
  const tripQuery = pick3(query, [
5780
5858
  "route_id",
5781
5859
  "trip_id",
@@ -5840,7 +5918,7 @@ function getStoptimes(query = {}, fields = [], orderBy2 = [], options = {}) {
5840
5918
  let whereClause = "";
5841
5919
  const orderByClause = formatOrderByClause(orderBy2);
5842
5920
  const stoptimeQueryOmitKeys = ["date", "start_time", "end_time"];
5843
- let stoptimeQuery = omit6(query, stoptimeQueryOmitKeys);
5921
+ const stoptimeQuery = omit6(query, stoptimeQueryOmitKeys);
5844
5922
  const whereClauses = Object.entries(stoptimeQuery).map(
5845
5923
  ([key, value]) => formatWhereClause(key, value)
5846
5924
  );
@@ -5848,7 +5926,7 @@ function getStoptimes(query = {}, fields = [], orderBy2 = [], options = {}) {
5848
5926
  if (typeof query.date !== "number") {
5849
5927
  throw new Error("`date` must be a number in yyyymmdd format");
5850
5928
  }
5851
- const serviceIds = getServiceIdsByDate(query.date);
5929
+ const serviceIds = getServiceIdsByDate(query.date, options);
5852
5930
  const tripSubquery = `SELECT DISTINCT trip_id FROM trips WHERE service_id IN (${serviceIds.map((id) => sqlString4.escape(id)).join(",")})`;
5853
5931
  whereClauses.push(`trip_id IN (${tripSubquery})`);
5854
5932
  }
@@ -5922,7 +6000,7 @@ function getTrips(query = {}, fields = [], orderBy2 = [], options = {}) {
5922
6000
  let whereClause = "";
5923
6001
  const orderByClause = formatOrderByClause(orderBy2);
5924
6002
  const tripQueryOmitKeys = ["date"];
5925
- let tripQuery = omit7(query, tripQueryOmitKeys);
6003
+ const tripQuery = omit7(query, tripQueryOmitKeys);
5926
6004
  const whereClauses = Object.entries(tripQuery).map(
5927
6005
  ([key, value]) => formatWhereClause(key, value)
5928
6006
  );
@@ -5930,7 +6008,7 @@ function getTrips(query = {}, fields = [], orderBy2 = [], options = {}) {
5930
6008
  if (typeof query.date !== "number") {
5931
6009
  throw new Error("`date` must be a number in yyyymmdd format");
5932
6010
  }
5933
- const serviceIds = getServiceIdsByDate(query.date);
6011
+ const serviceIds = getServiceIdsByDate(query.date, options);
5934
6012
  whereClauses.push(
5935
6013
  `service_id IN (${serviceIds.map((id) => sqlString5.escape(id)).join(",")})`
5936
6014
  );
@@ -6212,6 +6290,7 @@ export {
6212
6290
  closeDb,
6213
6291
  deleteDb,
6214
6292
  exportGtfs,
6293
+ generateFolderName,
6215
6294
  getAgencies2 as getAgencies,
6216
6295
  getAreas,
6217
6296
  getAttributions,
@@ -6272,6 +6351,9 @@ export {
6272
6351
  getVehiclePositions,
6273
6352
  importGtfs,
6274
6353
  openDb,
6354
+ prepDirectory,
6355
+ untildify,
6356
+ unzip,
6275
6357
  updateGtfsRealtime
6276
6358
  };
6277
6359
  //# sourceMappingURL=index.js.map