gtfs 3.8.0 → 4.0.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.
Files changed (90) hide show
  1. package/@types/index.d.ts +130 -137
  2. package/@types/tests.ts +18 -13
  3. package/CHANGELOG.md +18 -0
  4. package/README.md +233 -212
  5. package/config-sample-rtupdates.json +6 -18
  6. package/lib/advancedQuery.js +8 -17
  7. package/lib/db.js +35 -59
  8. package/lib/export.js +16 -10
  9. package/lib/gtfs/agencies.js +8 -6
  10. package/lib/gtfs/areas.js +8 -11
  11. package/lib/gtfs/attributions.js +8 -6
  12. package/lib/gtfs/calendar-dates.js +8 -6
  13. package/lib/gtfs/calendars.js +8 -6
  14. package/lib/gtfs/fare-attributes.js +8 -6
  15. package/lib/gtfs/fare-leg-rules.js +8 -6
  16. package/lib/gtfs/fare-products.js +8 -6
  17. package/lib/gtfs/fare-rules.js +8 -6
  18. package/lib/gtfs/fare-transfer-rules.js +8 -6
  19. package/lib/gtfs/feed-info.js +8 -6
  20. package/lib/gtfs/frequencies.js +8 -6
  21. package/lib/gtfs/levels.js +8 -11
  22. package/lib/gtfs/pathways.js +8 -6
  23. package/lib/gtfs/routes.js +8 -11
  24. package/lib/gtfs/shapes.js +34 -39
  25. package/lib/gtfs/stop-areas.js +8 -6
  26. package/lib/gtfs/stop-times.js +8 -6
  27. package/lib/gtfs/stops.js +27 -33
  28. package/lib/gtfs/transfers.js +8 -6
  29. package/lib/gtfs/translations.js +8 -6
  30. package/lib/gtfs/trips.js +8 -11
  31. package/lib/gtfs-realtime/service-alerts.js +9 -7
  32. package/lib/gtfs-realtime/stop-times-updates.js +9 -7
  33. package/lib/gtfs-realtime/trip-updates.js +9 -7
  34. package/lib/gtfs-realtime/vehicle-positions.js +9 -7
  35. package/lib/gtfs-ride/board-alights.js +8 -6
  36. package/lib/gtfs-ride/ride-feed-infos.js +8 -6
  37. package/lib/gtfs-ride/rider-trips.js +8 -6
  38. package/lib/gtfs-ride/riderships.js +8 -6
  39. package/lib/gtfs-ride/trip-capacities.js +8 -6
  40. package/lib/gtfs.js +3 -9
  41. package/lib/import.js +213 -273
  42. package/lib/non-standard/directions.js +8 -6
  43. package/lib/non-standard/stop-attributes.js +8 -6
  44. package/lib/non-standard/timetable-notes-references.js +8 -6
  45. package/lib/non-standard/timetable-notes.js +8 -6
  46. package/lib/non-standard/timetable-pages.js +8 -6
  47. package/lib/non-standard/timetable-stop-order.js +8 -6
  48. package/lib/non-standard/timetables.js +8 -6
  49. package/lib/non-standard/trips-dated-vehicle-journey.js +9 -7
  50. package/package.json +4 -4
  51. package/test/mocha/advanced-query.js +9 -15
  52. package/test/mocha/export-gtfs.js +5 -6
  53. package/test/mocha/fare-transfer-rules.js +32 -0
  54. package/test/mocha/get-agencies.js +13 -19
  55. package/test/mocha/get-areas.js +27 -0
  56. package/test/mocha/get-attributions.js +7 -13
  57. package/test/mocha/get-board-alights.js +7 -13
  58. package/test/mocha/get-calendar-dates.js +11 -17
  59. package/test/mocha/get-calendars.js +11 -17
  60. package/test/mocha/get-directions.js +7 -13
  61. package/test/mocha/get-fare-attributes.js +9 -15
  62. package/test/mocha/get-fare-leg-rules.js +27 -0
  63. package/test/mocha/get-fare-products.js +27 -0
  64. package/test/mocha/get-fare-rules.js +9 -15
  65. package/test/mocha/get-feed-info.js +7 -13
  66. package/test/mocha/get-frequencies.js +7 -13
  67. package/test/mocha/get-levels.js +7 -7
  68. package/test/mocha/get-pathways.js +7 -13
  69. package/test/mocha/get-ride-feed-infos.js +7 -13
  70. package/test/mocha/get-rider-trips.js +7 -13
  71. package/test/mocha/get-riderships.js +7 -13
  72. package/test/mocha/get-routes.js +13 -13
  73. package/test/mocha/get-shapes-as-geojson.js +14 -15
  74. package/test/mocha/get-shapes.js +25 -25
  75. package/test/mocha/get-stop-attributes.js +7 -13
  76. package/test/mocha/get-stops-as-geojson.js +13 -19
  77. package/test/mocha/get-stops.js +19 -19
  78. package/test/mocha/get-stoptimes.js +11 -17
  79. package/test/mocha/get-timetable-pages.js +7 -13
  80. package/test/mocha/get-timetable-stop-orders.js +6 -7
  81. package/test/mocha/get-timetables.js +7 -13
  82. package/test/mocha/get-transfers.js +7 -13
  83. package/test/mocha/get-translations.js +7 -13
  84. package/test/mocha/get-trip-capacities.js +7 -13
  85. package/test/mocha/get-trips.js +9 -9
  86. package/test/mocha/import-gtfs.js +11 -11
  87. package/test/mocha/{get-db.js → open-db.js} +37 -29
  88. package/test/mocha/raw-query.js +34 -0
  89. package/test/mocha/exec-raw-query.js +0 -39
  90. package/test/mocha/run-raw-query.js +0 -54
package/lib/import.js CHANGED
@@ -13,7 +13,7 @@ import gtfsrt from 'gtfs-realtime-bindings';
13
13
  import sqlString from 'sqlstring-sqlite';
14
14
 
15
15
  import models from '../models/models.js';
16
- import { openDb, setupDb } from './db.js';
16
+ import { openDb } from './db.js';
17
17
  import { unzip } from './file-utils.js';
18
18
  import {
19
19
  log as _log,
@@ -82,72 +82,33 @@ function getDescendantProp(obj, desc, defaultvalue) {
82
82
  return obj;
83
83
  }
84
84
 
85
- const markRealtimeDataStale = async (db, log) => {
86
- const vehiclePositionModel = models.find(
87
- (x) => x.filenameBase === 'vehicle_positions'
88
- );
89
- const tripUpdatesModel = models.find(
90
- (x) => x.filenameBase === 'trip_updates'
91
- );
92
- const stopTimesUpdatesModel = models.find(
93
- (x) => x.filenameBase === 'stop_times_updates'
94
- );
95
- const serviceAlertsModel = models.find(
96
- (x) => x.filenameBase === 'service_alerts'
97
- );
98
- const serviceAlertTargetsModel = models.find(
99
- (x) => x.filenameBase === 'service_alert_targets'
100
- );
85
+ const markRealtimeDataStale = (config, log) => {
86
+ const db = openDb(config);
101
87
 
102
- // Mark all data as stale
103
88
  log(`Marking GTFS-Realtime data as stale..`);
104
- await db.run(`UPDATE ${vehiclePositionModel.filenameBase} SET isUpdated=0`);
105
- await db.run(`UPDATE ${tripUpdatesModel.filenameBase} SET isUpdated=0`);
106
- await db.run(`UPDATE ${stopTimesUpdatesModel.filenameBase} SET isUpdated=0`);
107
- await db.run(`UPDATE ${serviceAlertsModel.filenameBase} SET isUpdated=0`);
108
- await db.run(
109
- `UPDATE ${serviceAlertTargetsModel.filenameBase} SET isUpdated=0`
110
- );
89
+ db.prepare(`UPDATE vehicle_positions SET isUpdated=0`).run();
90
+ db.prepare(`UPDATE trip_updates SET isUpdated=0`).run();
91
+ db.prepare(`UPDATE stop_times_updates SET isUpdated=0`).run();
92
+ db.prepare(`UPDATE service_alerts SET isUpdated=0`).run();
93
+ db.prepare(`UPDATE service_alert_targets SET isUpdated=0`).run();
111
94
  log(`Marked GTFS-Realtime data as stale\r`, true);
112
95
  };
113
96
 
114
- const cleanStaleRealtimeData = async (db, log) => {
115
- const vehiclePositionModel = models.find(
116
- (x) => x.filenameBase === 'vehicle_positions'
117
- );
118
- const tripUpdatesModel = models.find(
119
- (x) => x.filenameBase === 'trip_updates'
120
- );
121
- const stopTimesUpdatesModel = models.find(
122
- (x) => x.filenameBase === 'stop_times_updates'
123
- );
124
- const serviceAlertsModel = models.find(
125
- (x) => x.filenameBase === 'service_alerts'
126
- );
127
- const serviceAlertTargetsModel = models.find(
128
- (x) => x.filenameBase === 'service_alert_targets'
129
- );
97
+ const cleanStaleRealtimeData = (config, log) => {
98
+ const db = openDb(config);
130
99
 
131
100
  log(`Cleaning stale GTFS-RT data..`);
132
- await db.run(
133
- `DELETE FROM ${vehiclePositionModel.filenameBase} WHERE isUpdated=0`
134
- );
135
- await db.run(
136
- `DELETE FROM ${tripUpdatesModel.filenameBase} WHERE isUpdated=0`
137
- );
138
- await db.run(
139
- `DELETE FROM ${stopTimesUpdatesModel.filenameBase} WHERE isUpdated=0`
140
- );
141
- await db.run(
142
- `DELETE FROM ${serviceAlertsModel.filenameBase} WHERE isUpdated=0`
143
- );
144
- await db.run(
145
- `DELETE FROM ${serviceAlertTargetsModel.filenameBase} WHERE isUpdated=0`
146
- );
101
+ db.prepare(`DELETE FROM vehicle_positions WHERE isUpdated=0`).run();
102
+ db.prepare(`DELETE FROM trip_updates WHERE isUpdated=0`).run();
103
+ db.prepare(`DELETE FROM stop_times_updates WHERE isUpdated=0`).run();
104
+ db.prepare(`DELETE FROM service_alerts WHERE isUpdated=0`).run();
105
+ db.prepare(`DELETE FROM service_alert_targets WHERE isUpdated=0`).run();
147
106
  log(`Cleaned stale GTFS-Realtime data\r`, true);
148
107
  };
149
108
 
150
109
  const updateRealtimeData = async (task) => {
110
+ const db = openDb(task);
111
+
151
112
  const model = {
152
113
  vehicle_positions: models.find(
153
114
  (x) => x.filenameBase === 'vehicle_positions'
@@ -220,16 +181,15 @@ const updateRealtimeData = async (task) => {
220
181
  )
221
182
  );
222
183
 
223
- // eslint-disable-next-line no-await-in-loop
224
- await task.db
225
- .run(
184
+ try {
185
+ db.prepare(
226
186
  `REPLACE INTO ${model[gtfsRealtimeType].filenameBase} (${
227
187
  fields[gtfsRealtimeType]
228
188
  }) VALUES (${fieldValues.join(', ')})`
229
- )
230
- .catch((error) => {
231
- task.warn('Import error: ' + error.message);
232
- });
189
+ ).run();
190
+ } catch (error) {
191
+ task.warn('Import error: ' + error.message);
192
+ }
233
193
 
234
194
  // Special processing for tripUpdates
235
195
  if (entity.tripUpdate) {
@@ -245,16 +205,15 @@ const updateRealtimeData = async (task) => {
245
205
  totalLineCount++;
246
206
  }
247
207
 
248
- // eslint-disable-next-line no-await-in-loop
249
- await task.db
250
- .run(
208
+ try {
209
+ db.prepare(
251
210
  `REPLACE INTO ${model.stop_times_updates.filenameBase} (${
252
211
  fields.stop_times_updates
253
212
  }) VALUES ${stopUpdateArray.join(', ')}`
254
- )
255
- .catch((error) => {
256
- task.warn('Import error: ' + error.message);
257
- });
213
+ ).run();
214
+ } catch (error) {
215
+ task.warn('Import error: ' + error.message);
216
+ }
258
217
  }
259
218
 
260
219
  // Special processing for serviceAlerts
@@ -271,16 +230,15 @@ const updateRealtimeData = async (task) => {
271
230
  totalLineCount++;
272
231
  }
273
232
 
274
- // eslint-disable-next-line no-await-in-loop
275
- await task.db
276
- .run(
233
+ try {
234
+ db.prepare(
277
235
  `REPLACE INTO ${model.service_alert_targets.filenameBase} (${
278
236
  fields.service_alert_targets
279
237
  }) VALUES ${alertTargetArray.join(', ')}`
280
- )
281
- .catch((error) => {
282
- task.warn('Import error: ' + error.message);
283
- });
238
+ ).run();
239
+ } catch (error) {
240
+ task.warn('Import error: ' + error.message);
241
+ }
284
242
  }
285
243
 
286
244
  task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
@@ -355,159 +313,147 @@ const readFiles = async (task) => {
355
313
  }
356
314
  };
357
315
 
358
- const deleteTables = (db) =>
359
- Promise.all(
360
- models.map((model) => db.run(`DROP TABLE IF EXISTS ${model.filenameBase};`))
361
- );
316
+ const createTables = (db) => {
317
+ for (const model of models) {
318
+ if (!model.schema) {
319
+ return;
320
+ }
362
321
 
363
- const createTables = (db) =>
364
- Promise.all(
365
- models.map(async (model) => {
366
- if (!model.schema) {
367
- return;
322
+ const columns = model.schema.map((column) => {
323
+ let check = '';
324
+ if (column.min !== undefined && column.max) {
325
+ check = `CHECK( ${column.name} >= ${column.min} AND ${column.name} <= ${column.max} )`;
326
+ } else if (column.min) {
327
+ check = `CHECK( ${column.name} >= ${column.min} )`;
328
+ } else if (column.max) {
329
+ check = `CHECK( ${column.name} <= ${column.max} )`;
368
330
  }
369
331
 
370
- const columns = model.schema.map((column) => {
371
- let check = '';
372
- if (column.min !== undefined && column.max) {
373
- check = `CHECK( ${column.name} >= ${column.min} AND ${column.name} <= ${column.max} )`;
374
- } else if (column.min) {
375
- check = `CHECK( ${column.name} >= ${column.min} )`;
376
- } else if (column.max) {
377
- check = `CHECK( ${column.name} <= ${column.max} )`;
378
- }
332
+ const primary = column.primary ? 'PRIMARY KEY' : '';
333
+ const required = column.required ? 'NOT NULL' : '';
334
+ const columnDefault = column.default ? 'DEFAULT ' + column.default : '';
335
+ const columnCollation = column.nocase ? 'COLLATE NOCASE' : '';
336
+ return `${column.name} ${column.type} ${check} ${primary} ${required} ${columnDefault} ${columnCollation}`;
337
+ });
379
338
 
380
- const primary = column.primary ? 'PRIMARY KEY' : '';
381
- const required = column.required ? 'NOT NULL' : '';
382
- const columnDefault = column.default ? 'DEFAULT ' + column.default : '';
383
- const columnCollation = column.nocase ? 'COLLATE NOCASE' : '';
384
- return `${column.name} ${column.type} ${check} ${primary} ${required} ${columnDefault} ${columnCollation}`;
385
- });
339
+ db.prepare(`DROP TABLE IF EXISTS ${model.filenameBase};`).run();
386
340
 
387
- await db.run(
388
- `CREATE TABLE ${model.filenameBase} (${columns.join(', ')});`
389
- );
341
+ db.prepare(
342
+ `CREATE TABLE ${model.filenameBase} (${columns.join(', ')});`
343
+ ).run();
390
344
 
391
- await Promise.all(
392
- model.schema.map(async (column) => {
393
- if (column.index) {
394
- const unique = column.index === 'unique' ? 'UNIQUE' : '';
395
- await db.run(
396
- `CREATE ${unique} INDEX idx_${model.filenameBase}_${column.name} ON ${model.filenameBase} (${column.name});`
397
- );
398
- }
399
- })
400
- );
401
- })
402
- );
345
+ for (const column of model.schema.filter((column) => column.index)) {
346
+ const unique = column.index === 'unique' ? 'UNIQUE' : '';
347
+ db.prepare(
348
+ `CREATE ${unique} INDEX idx_${model.filenameBase}_${column.name} ON ${model.filenameBase} (${column.name});`
349
+ ).run();
350
+ }
351
+ }
352
+ };
403
353
 
404
354
  const formatLine = (line, model, totalLineCount) => {
405
355
  const lineNumber = totalLineCount + 1;
406
- for (const fieldName of Object.keys(line)) {
407
- const columnSchema = model.schema.find(
408
- (schema) => schema.name === fieldName
409
- );
410
356
 
411
- // Remove columns not part of model
412
- if (!columnSchema) {
413
- delete line[fieldName];
414
- continue;
415
- }
357
+ const formattedLine = {};
416
358
 
417
- // Remove null values
418
- if (line[fieldName] === null || line[fieldName] === '') {
419
- delete line[fieldName];
420
- }
359
+ for (const columnSchema of model.schema) {
360
+ const lineValue = line[columnSchema.name];
421
361
 
422
- // Convert fields that should be integer
423
362
  if (columnSchema.type === 'integer') {
424
- const value = Number.parseInt(line[fieldName], 10);
425
-
426
- if (Number.isNaN(value)) {
427
- delete line[fieldName];
428
- } else {
429
- line[fieldName] = value;
430
- }
363
+ // Convert fields that should be integer
364
+ formattedLine[columnSchema.name] = Number.parseInt(lineValue, 10);
365
+ } else if (columnSchema.type === 'real') {
366
+ // Convert fields that should be float
367
+ formattedLine[columnSchema.name] = Number.parseFloat(lineValue);
368
+ } else {
369
+ formattedLine[columnSchema.name] = lineValue;
431
370
  }
432
371
 
433
- // Convert fields that should be float
434
- if (columnSchema.type === 'real') {
435
- const value = Number.parseFloat(line[fieldName]);
436
-
437
- if (Number.isNaN(value)) {
438
- delete line[fieldName];
439
- } else {
440
- line[fieldName] = value;
441
- }
372
+ if (
373
+ formattedLine[columnSchema.name] === '' ||
374
+ formattedLine[columnSchema.name] === undefined ||
375
+ formattedLine[columnSchema.name] === null ||
376
+ Number.isNaN(formattedLine[columnSchema.name])
377
+ ) {
378
+ // Add null values
379
+ formattedLine[columnSchema.name] = null;
442
380
  }
443
381
 
444
382
  // Validate required
445
383
  if (
446
384
  columnSchema.required === true &&
447
- (line[fieldName] === undefined || line[fieldName] === '')
385
+ formattedLine[columnSchema.name] === null
448
386
  ) {
449
387
  throw new Error(
450
- `Missing required value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}.`
388
+ `Missing required value in ${model.filenameBase}.txt for ${columnSchema.name} on line ${lineNumber}.`
451
389
  );
452
390
  }
453
391
 
454
392
  // Validate minimum
455
- if (columnSchema.min !== undefined && line[fieldName] < columnSchema.min) {
393
+ if (
394
+ columnSchema.min !== undefined &&
395
+ formattedLine[columnSchema.name] < columnSchema.min
396
+ ) {
456
397
  throw new Error(
457
- `Invalid value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}: below minimum value of ${columnSchema.min}.`
398
+ `Invalid value in ${model.filenameBase}.txt for ${columnSchema.name} on line ${lineNumber}: below minimum value of ${columnSchema.min}.`
458
399
  );
459
400
  }
460
401
 
461
402
  // Validate maximum
462
- if (columnSchema.max !== undefined && line[fieldName] > columnSchema.max) {
403
+ if (
404
+ columnSchema.max !== undefined &&
405
+ formattedLine[columnSchema.name] > columnSchema.max
406
+ ) {
463
407
  throw new Error(
464
- `Invalid value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}: above maximum value of ${columnSchema.max}.`
408
+ `Invalid value in ${model.filenameBase}.txt for ${columnSchema.name} on line ${lineNumber}: above maximum value of ${columnSchema.max}.`
465
409
  );
466
410
  }
467
411
  }
468
412
 
469
413
  // Convert to midnight timestamp
470
- const timestampFormat = [
414
+ const timestampColumnNames = [
471
415
  'start_time',
472
416
  'end_time',
473
417
  'arrival_time',
474
418
  'departure_time',
475
419
  ];
476
420
 
477
- for (const fieldName of timestampFormat) {
478
- if (line[fieldName]) {
479
- line[`${fieldName}stamp`] = calculateSecondsFromMidnight(line[fieldName]);
421
+ for (const timestampColumnName of timestampColumnNames) {
422
+ if (formattedLine[timestampColumnName]) {
423
+ formattedLine[`${timestampColumnName}stamp`] =
424
+ calculateSecondsFromMidnight(formattedLine[timestampColumnName]);
480
425
  }
481
426
  }
482
427
 
483
- return line;
428
+ return formattedLine;
484
429
  };
485
430
 
486
- const importLines = async (task, lines, model, totalLineCount) => {
431
+ const importLines = (task, lines, model, totalLineCount) => {
432
+ const db = openDb(task);
433
+
487
434
  if (lines.length === 0) {
488
435
  return;
489
436
  }
490
437
 
491
438
  const linesToImportCount = lines.length;
492
- const fieldNames = model.schema.map((column) => column.name);
439
+ const fieldNames = model.schema
440
+ .filter((column) => column.name !== 'id')
441
+ .map((column) => column.name);
493
442
  const placeholders = [];
494
443
  const values = [];
495
444
 
496
445
  while (lines.length > 0) {
497
446
  const line = lines.pop();
498
447
  placeholders.push(`(${fieldNames.map(() => '?').join(', ')})`);
499
- for (const fieldName of fieldNames) {
500
- values.push(line[fieldName]);
501
- }
448
+ values.push(...fieldNames.map((fieldName) => line[fieldName]));
502
449
  }
503
450
 
504
451
  try {
505
- await task.db.run(
452
+ db.prepare(
506
453
  `INSERT ${task.ignoreDuplicates ? 'OR IGNORE' : ''} INTO ${
507
454
  model.filenameBase
508
- }(${fieldNames.join(', ')}) VALUES${placeholders.join(',')}`,
509
- values
510
- );
455
+ } (${fieldNames.join(', ')}) VALUES ${placeholders.join(',')}`
456
+ ).run(...values);
511
457
  } catch (error) {
512
458
  task.warn(
513
459
  `Check ${model.filenameBase}.txt for invalid data between lines ${
@@ -536,7 +482,7 @@ const importFiles = (task) =>
536
482
  return;
537
483
  }
538
484
 
539
- // If the model is a database/gtfs-realtime model then just silently exit as we dont really care here
485
+ // If the model is a database/gtfs-realtime model then silently exit
540
486
  if (model.extension === 'gtfs-realtime') {
541
487
  resolve();
542
488
  return;
@@ -569,18 +515,16 @@ const importFiles = (task) =>
569
515
  ...task.csvOptions,
570
516
  });
571
517
 
572
- parser.on('readable', async () => {
518
+ parser.on('readable', () => {
573
519
  let record;
574
520
 
575
521
  while ((record = parser.read())) {
576
522
  try {
577
523
  totalLineCount += 1;
578
524
  lines.push(formatLine(record, model, totalLineCount));
579
-
580
525
  // If we have a bunch of lines ready to insert, then do it
581
526
  if (lines.length >= maxInsertVariables / model.schema.length) {
582
- /* eslint-disable-next-line no-await-in-loop */
583
- await importLines(task, lines, model, totalLineCount);
527
+ importLines(task, lines, model, totalLineCount);
584
528
  }
585
529
  } catch (error) {
586
530
  reject(error);
@@ -588,9 +532,9 @@ const importFiles = (task) =>
588
532
  }
589
533
  });
590
534
 
591
- parser.on('end', async () => {
535
+ parser.on('end', () => {
592
536
  // Insert all remaining lines
593
- await importLines(task, lines, model, totalLineCount).catch(reject);
537
+ importLines(task, lines, model, totalLineCount);
594
538
  resolve();
595
539
  });
596
540
 
@@ -606,70 +550,65 @@ export async function importGtfs(initialConfig) {
606
550
  const log = _log(config);
607
551
  const logError = _logError(config);
608
552
  const logWarning = _logWarning(config);
609
- const db = await openDb(config).catch((error) => {
610
- if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
611
- logError(
612
- `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
613
- );
614
- }
553
+ try {
554
+ const db = openDb(config);
555
+
556
+ const agencyCount = config.agencies.length;
557
+ log(
558
+ `Starting GTFS import for ${pluralize(
559
+ 'file',
560
+ agencyCount,
561
+ true
562
+ )} using SQLite database at ${config.sqlitePath}`
563
+ );
615
564
 
616
- throw error;
617
- });
565
+ createTables(db);
566
+
567
+ await mapSeries(config.agencies, async (agency) => {
568
+ const { path, cleanup } = await dir({ unsafeCleanup: true });
569
+
570
+ const task = {
571
+ exclude: agency.exclude,
572
+ agency_url: agency.url,
573
+ headers: agency.headers || false,
574
+ realtime_headers: agency.realtimeHeaders || false,
575
+ realtime_urls: agency.realtimeUrls || false,
576
+ downloadDir: path,
577
+ path: agency.path,
578
+ csvOptions: config.csvOptions || {},
579
+ ignoreDuplicates: config.ignoreDuplicates,
580
+ sqlitePath: config.sqlitePath,
581
+ log,
582
+ warn: logWarning,
583
+ error: logError,
584
+ };
585
+
586
+ if (task.agency_url) {
587
+ await downloadFiles(task);
588
+ }
618
589
 
619
- const agencyCount = config.agencies.length;
620
- log(
621
- `Starting GTFS import for ${pluralize(
622
- 'file',
623
- agencyCount,
624
- true
625
- )} using SQLite database at ${config.sqlitePath}`
626
- );
590
+ await readFiles(task);
591
+ await importFiles(task);
627
592
 
628
- await deleteTables(db);
629
- await createTables(db);
630
-
631
- await setupDb(db);
632
-
633
- await mapSeries(config.agencies, async (agency) => {
634
- const { path, cleanup } = await dir({ unsafeCleanup: true });
635
-
636
- const task = {
637
- exclude: agency.exclude,
638
- agency_url: agency.url,
639
- headers: agency.headers || false,
640
- realtime_headers: agency.realtimeHeaders || false,
641
- realtime_urls: agency.realtimeUrls || false,
642
- downloadDir: path,
643
- path: agency.path,
644
- csvOptions: config.csvOptions || {},
645
- ignoreDuplicates: config.ignoreDuplicates,
646
- db,
647
- log(message, overwrite) {
648
- log(message, overwrite);
649
- },
650
- warn(message) {
651
- logWarning(message);
652
- },
653
- error(message) {
654
- logError(message);
655
- },
656
- };
657
-
658
- if (task.agency_url) {
659
- await downloadFiles(task);
660
- }
593
+ if (task.realtime_urls) {
594
+ await updateRealtimeData(task);
595
+ }
661
596
 
662
- await readFiles(task);
663
- await importFiles(task);
597
+ cleanup();
598
+ });
664
599
 
665
- if (task.realtime_urls) {
666
- await updateRealtimeData(task);
600
+ log(
601
+ `Completed GTFS import for ${pluralize('agency', agencyCount, true)}\n`
602
+ );
603
+ } catch (error) {
604
+ if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
605
+ logError(
606
+ `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
607
+ );
667
608
  }
668
609
 
669
- cleanup();
670
- });
671
-
672
- log(`Completed GTFS import for ${pluralize('agency', agencyCount, true)}\n`);
610
+ throw error;
611
+ }
673
612
  }
674
613
 
675
614
  export async function updateGtfsRealtime(initialConfig) {
@@ -678,7 +617,49 @@ export async function updateGtfsRealtime(initialConfig) {
678
617
  const log = _log(config);
679
618
  const logError = _logError(config);
680
619
  const logWarning = _logWarning(config);
681
- const db = await openDb(config).catch((error) => {
620
+
621
+ try {
622
+ openDb(config);
623
+
624
+ const agencyCount = config.agencies.length;
625
+ log(
626
+ `Starting GTFS-Realtime refresh for ${pluralize(
627
+ 'agencies',
628
+ agencyCount,
629
+ true
630
+ )} using SQLite database at ${config.sqlitePath}`
631
+ );
632
+
633
+ markRealtimeDataStale(config, log);
634
+
635
+ await Promise.all(
636
+ config.agencies.map(async (agency) => {
637
+ if (!agency.realtimeUrls) {
638
+ return;
639
+ }
640
+
641
+ const task = {
642
+ realtime_headers: agency.realtimeHeaders || false,
643
+ realtime_urls: agency.realtimeUrls || false,
644
+ sqlitePath: config.sqlitePath,
645
+ log,
646
+ warn: logWarning,
647
+ error: logError,
648
+ };
649
+
650
+ await updateRealtimeData(task);
651
+ })
652
+ );
653
+
654
+ cleanStaleRealtimeData(config, log);
655
+ log(
656
+ `Completed GTFS-Realtime refresh for ${pluralize(
657
+ 'agencies',
658
+ agencyCount,
659
+ true
660
+ )}\n`
661
+ );
662
+ } catch (error) {
682
663
  if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
683
664
  logError(
684
665
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
@@ -686,46 +667,5 @@ export async function updateGtfsRealtime(initialConfig) {
686
667
  }
687
668
 
688
669
  throw error;
689
- });
690
-
691
- const agencyCount = config.agencies.length;
692
- log(
693
- `Starting GTFS-Realtime refresh for ${pluralize(
694
- 'agencies',
695
- agencyCount,
696
- true
697
- )} using SQLite database at ${config.sqlitePath}`
698
- );
699
-
700
- await markRealtimeDataStale(db, log);
701
-
702
- await mapSeries(config.agencies, async (agency) => {
703
- const task = {
704
- realtime_headers: agency.realtimeHeaders || false,
705
- realtime_urls: agency.realtimeUrls || false,
706
- db,
707
- log(message, overwrite) {
708
- log(message, overwrite);
709
- },
710
- warn(message) {
711
- logWarning(message);
712
- },
713
- error(message) {
714
- logError(message);
715
- },
716
- };
717
-
718
- if (task.realtime_urls) {
719
- await updateRealtimeData(task);
720
- }
721
- });
722
-
723
- await cleanStaleRealtimeData(db, log);
724
- log(
725
- `Completed GTFS-Realtime refresh for ${pluralize(
726
- 'agencies',
727
- agencyCount,
728
- true
729
- )}\n`
730
- );
670
+ }
731
671
  }