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