gtfs 4.7.2 → 4.8.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/@types/index.d.ts CHANGED
@@ -63,6 +63,11 @@ export interface DbConfig {
63
63
  * A path to a SQLite database. Defaults to using an in-memory database.
64
64
  */
65
65
  sqlitePath?: string;
66
+
67
+ /**
68
+ * A better-sqlite3 database object. If provided, sqlitePath will be ignored.
69
+ */
70
+ db?: Database.Database;
66
71
  }
67
72
 
68
73
  export interface ExportConfig extends DbConfig, VerboseConfig {
@@ -114,6 +119,16 @@ export interface ImportConfig extends DbConfig, VerboseConfig {
114
119
  * Options passed to csv-parse for parsing GTFS CSV files.
115
120
  */
116
121
  csvOptions?: CsvParse.Options;
122
+
123
+ /**
124
+ * A timeout in milliseconds for downloading GTFS files. Defaults to no timeout.
125
+ */
126
+ downloadTimeout?: number;
127
+
128
+ /**
129
+ * Whether or not to ignore errors during import. Defaults to false.
130
+ */
131
+ ignoreErrors?: boolean;
117
132
  }
118
133
 
119
134
  export interface QueryOptions {
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.8.0] - 2024-03-10
9
+
10
+ ### Added
11
+
12
+ - `ignoreErrors` config option
13
+ - `downloadTimeout` config option
14
+ - `db` config option
15
+
8
16
  ## [4.7.2] - 2024-03-05
9
17
 
10
18
  ### Updated
package/README.md CHANGED
@@ -161,14 +161,17 @@ Copy `config-sample.json` to `config.json` and then add your projects configurat
161
161
 
162
162
  cp config-sample.json config.json
163
163
 
164
- | option | type | description |
165
- | --------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
166
- | [`agencies`](#agencies) | array | An array of GTFS files to be imported, and which files to exclude. |
167
- | [`csvOptions`](#csvOptions) | object | Options passed to `csv-parse` for parsing GTFS CSV files. Optional. |
168
- | [`exportPath`](#exportPath) | string | A path to a directory to put exported GTFS files. Optional, defaults to `gtfs-export/<agency_name>`. |
169
- | [`ignoreDuplicates`](#ignoreduplicates) | boolean | Whether or not to ignore unique constraints on ids when importing GTFS, such as `trip_id`, `calendar_id`. Optional, defaults to false. |
170
- | [`sqlitePath`](#sqlitePath) | string | A path to an SQLite database. Optional, defaults to using an in-memory database. |
171
- | [`verbose`](#verbose) | boolean | Whether or not to print output to the console. Optional, defaults to true. |
164
+ | option | type | description |
165
+ | --------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
166
+ | [`agencies`](#agencies) | array | An array of GTFS files to be imported, and which files to exclude. |
167
+ | [`csvOptions`](#csvOptions) | object | Options passed to `csv-parse` for parsing GTFS CSV files. Optional. |
168
+ | [`db`](#db) | database instance | An existing database instance to use instead of relying on node-gtfs to connect. Optional. |
169
+ | [`downloadTimeout`](#downloadtimeout) | integer | The number of milliseconds to wait before throwing an error when downloading GTFS. Optional. |
170
+ | [`exportPath`](#exportPath) | string | A path to a directory to put exported GTFS files. Optional, defaults to `gtfs-export/<agency_name>`. |
171
+ | [`ignoreDuplicates`](#ignoreduplicates) | boolean | Whether or not to ignore unique constraints on ids when importing GTFS, such as `trip_id`, `calendar_id`. Optional, defaults to false. |
172
+ | [`ignoreErrors`](#ignoreerrors) | boolean | Whether or not to ignore errors during the import process. If true, when importing multiple agencies, failed agencies will be skipped. Optional, defaults to false. |
173
+ | [`sqlitePath`](#sqlitePath) | string | A path to an SQLite database. Optional, defaults to using an in-memory database. |
174
+ | [`verbose`](#verbose) | boolean | Whether or not to print output to the console. Optional, defaults to true. |
172
175
 
173
176
  ### agencies
174
177
 
@@ -320,6 +323,60 @@ For instance, if you wanted to skip importing invalid lines in the GTFS file:
320
323
 
321
324
  See [full list of options](https://csv.js.org/parse/options/).
322
325
 
326
+ ### db
327
+
328
+ {Database Instance} When passing configuration to `importGtfs` in javascript, you can pass a `db` parameter with an existing database instance. This is not possible using a json configuration file Optional.
329
+
330
+ ```js
331
+ // Using better-sqlite3 to open database
332
+ import { importGtfs } from 'gtfs';
333
+ import Database from 'better-sqlite3';
334
+
335
+ const db = new Database('/path/to/database');
336
+
337
+ importGtfs({
338
+ agencies: [
339
+ {
340
+ path: '/path/to/the/unzipped/gtfs/',
341
+ },
342
+ ],
343
+ db: db,
344
+ });
345
+ ```
346
+
347
+ ```js
348
+ // Using `openDb` from node-gtfs to open database
349
+ import { importGtfs, openDb } from 'gtfs';
350
+
351
+ const db = openDb({
352
+ sqlitePath: '/path/to/database',
353
+ });
354
+
355
+ importGtfs({
356
+ agencies: [
357
+ {
358
+ path: '/path/to/the/unzipped/gtfs/',
359
+ },
360
+ ],
361
+ db: db,
362
+ });
363
+ ```
364
+
365
+ ### downloadTimeout
366
+
367
+ {Integer} A number of milliseconds to wait when downloading GTFS before throwing an error. Optional.
368
+
369
+ ```json
370
+ {
371
+ "agencies": [
372
+ {
373
+ "path": "/path/to/the/unzipped/gtfs/"
374
+ }
375
+ ],
376
+ "downloadTimeout": 5000
377
+ }
378
+ ```
379
+
323
380
  ### exportPath
324
381
 
325
382
  {String} A path to a directory to put exported GTFS files. If the directory does not exist, it will be created. Used when running `gtfs-export` script or `exportGtfs()`. Optional, defaults to `gtfs-export/<agency_name>` where `<agency_name>` is a sanitized, [snake-cased](https://en.wikipedia.org/wiki/Snake_case) version of the first `agency_name` in `agency.txt`.
@@ -350,6 +407,21 @@ See [full list of options](https://csv.js.org/parse/options/).
350
407
  }
351
408
  ```
352
409
 
410
+ ### ignoreErrors
411
+
412
+ {Boolean} When importing GTFS from multiple agencies, if you don't want node-GTFS to throw an error and instead skip failed GTFS importants proceed to the next agency. Defaults to `false`.
413
+
414
+ ```json
415
+ {
416
+ "agencies": [
417
+ {
418
+ "path": "/path/to/the/unzipped/gtfs/"
419
+ }
420
+ ],
421
+ "ignoreErrors": true
422
+ }
423
+ ```
424
+
353
425
  ### sqlitePath
354
426
 
355
427
  {String} A path to an SQLite database. Optional, defaults to using an in-memory database with a value of `:memory:`.
@@ -12,7 +12,9 @@
12
12
  "csvOptions": {
13
13
  "skip_lines_with_error": true
14
14
  },
15
+ "downloadTimeout": 5000,
15
16
  "ignoreDuplicates": false,
17
+ "ignoreErrors": false,
16
18
  "sqlitePath": "/tmp/gtfs",
17
19
  "exportPath": "~/path/to/export/gtfs"
18
20
  }
package/lib/db.js CHANGED
@@ -17,7 +17,10 @@ function setupDb(sqlitePath) {
17
17
  export function openDb(config) {
18
18
  // If config is passed, use that to open or return db
19
19
  if (config) {
20
- const { sqlitePath } = setDefaultConfig(config);
20
+ const { sqlitePath, db } = setDefaultConfig(config);
21
+ if (db) {
22
+ return db;
23
+ }
21
24
 
22
25
  if (dbs[sqlitePath]) {
23
26
  return dbs[sqlitePath];
@@ -34,7 +37,7 @@ export function openDb(config) {
34
37
 
35
38
  if (Object.keys(dbs).length > 1) {
36
39
  throw new Error(
37
- 'Multiple databases open, please specify which one to use.'
40
+ 'Multiple databases open, please specify which one to use.',
38
41
  );
39
42
  }
40
43
 
@@ -44,14 +47,14 @@ export function openDb(config) {
44
47
  export function closeDb(db) {
45
48
  if (Object.keys(dbs).length === 0) {
46
49
  throw new Error(
47
- 'No database connection. Call `openDb(config)` before using any methods.'
50
+ 'No database connection. Call `openDb(config)` before using any methods.',
48
51
  );
49
52
  }
50
53
 
51
54
  if (!db) {
52
55
  if (Object.keys(dbs).length > 1) {
53
56
  throw new Error(
54
- 'Multiple database connections. Pass the db you want to close as a parameter to `closeDb`.'
57
+ 'Multiple database connections. Pass the db you want to close as a parameter to `closeDb`.',
55
58
  );
56
59
  }
57
60
 
package/lib/import.js CHANGED
@@ -36,6 +36,9 @@ const downloadFiles = async (task) => {
36
36
  const response = await fetch(task.agency_url, {
37
37
  method: 'GET',
38
38
  headers: task.headers || {},
39
+ signal: task.downloadTimeout
40
+ ? AbortSignal.timeout(task.downloadTimeout)
41
+ : undefined,
39
42
  });
40
43
 
41
44
  if (response.status !== 200) {
@@ -594,8 +597,12 @@ const importFiles = (task) =>
594
597
  });
595
598
 
596
599
  parser.on('end', () => {
597
- // Insert all remaining lines
598
- importLines(task, lines, model, totalLineCount);
600
+ try {
601
+ // Insert all remaining lines
602
+ importLines(task, lines, model, totalLineCount);
603
+ } catch (error) {
604
+ reject(error);
605
+ }
599
606
  resolve();
600
607
  });
601
608
 
@@ -635,6 +642,7 @@ export async function importGtfs(initialConfig) {
635
642
  realtime_headers: agency.realtimeHeaders || false,
636
643
  realtime_urls: agency.realtimeUrls || false,
637
644
  downloadDir: path,
645
+ downloadTimeout: config.downloadTimeout,
638
646
  path: agency.path,
639
647
  csvOptions: config.csvOptions || {},
640
648
  ignoreDuplicates: config.ignoreDuplicates,
@@ -645,18 +653,26 @@ export async function importGtfs(initialConfig) {
645
653
  error: logError,
646
654
  };
647
655
 
648
- if (task.agency_url) {
649
- await downloadFiles(task);
650
- }
656
+ try {
657
+ if (task.agency_url) {
658
+ await downloadFiles(task);
659
+ }
651
660
 
652
- await readFiles(task);
653
- await importFiles(task);
661
+ await readFiles(task);
662
+ await importFiles(task);
654
663
 
655
- if (task.realtime_urls) {
656
- await updateRealtimeData(task);
657
- }
664
+ if (task.realtime_urls) {
665
+ await updateRealtimeData(task);
666
+ }
658
667
 
659
- cleanup();
668
+ cleanup();
669
+ } catch (error) {
670
+ if (config.ignoreErrors) {
671
+ logError(error.message);
672
+ } else {
673
+ throw error;
674
+ }
675
+ }
660
676
  });
661
677
 
662
678
  log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtfs",
3
- "version": "4.7.2",
3
+ "version": "4.8.0",
4
4
  "description": "Import GTFS transit data into SQLite and query routes, stops, times, fares and more",
5
5
  "keywords": [
6
6
  "transit",
@@ -60,7 +60,8 @@
60
60
  "David Abell",
61
61
  "Matthias Feist <matze@matf.de>",
62
62
  "Oliv4945",
63
- "Kyle Ramey"
63
+ "Kyle Ramey",
64
+ "Anton Bracke"
64
65
  ],
65
66
  "type": "module",
66
67
  "main": "index.js",
@@ -30,7 +30,7 @@ const agenciesFixturesLocal = [
30
30
  {
31
31
  path: path.join(
32
32
  path.dirname(fileURLToPath(import.meta.url)),
33
- '../fixture/caltrain_20160406.zip'
33
+ '../fixture/caltrain_20160406.zip',
34
34
  ),
35
35
  },
36
36
  ];
@@ -58,6 +58,18 @@ describe('importGtfs():', function () {
58
58
  routes.length.should.equal(4);
59
59
  });
60
60
 
61
+ it('should be able to download and import from HTTP with a downloadTimeout', async () => {
62
+ try {
63
+ await importGtfs({
64
+ ...config,
65
+ agencies: agenciesFixturesRemote,
66
+ downloadTimeout: 1,
67
+ });
68
+ } catch (error) {
69
+ error.name.should.equal('AbortError');
70
+ }
71
+ });
72
+
61
73
  it('should be able to download and import from local filesystem', async () => {
62
74
  await importGtfs({
63
75
  ...config,
@@ -105,7 +117,7 @@ describe('importGtfs():', function () {
105
117
  const countData = {};
106
118
  const temporaryDir = path.join(
107
119
  path.dirname(fileURLToPath(import.meta.url)),
108
- '../fixture/tmp/'
120
+ '../fixture/tmp/',
109
121
  );
110
122
 
111
123
  before(async () => {
@@ -135,7 +147,7 @@ describe('importGtfs():', function () {
135
147
  }
136
148
 
137
149
  countData[model.filenameBase] = data.length;
138
- }
150
+ },
139
151
  );
140
152
 
141
153
  return createReadStream(filePath)
@@ -144,7 +156,7 @@ describe('importGtfs():', function () {
144
156
  countData[model.collection] = 0;
145
157
  throw new Error(error);
146
158
  });
147
- })
159
+ }),
148
160
  );
149
161
 
150
162
  await importGtfs({
@@ -7,14 +7,25 @@ import config from '../test-config.js';
7
7
  import { openDb, closeDb, importGtfs, getShapes } from '../../index.js';
8
8
 
9
9
  const db2Config = {
10
- ...config,
11
10
  agencies: [
12
11
  {
13
12
  ...config.agencies[0],
14
13
  exclude: ['shapes'],
15
14
  },
16
15
  ],
17
- sqlitePath: './tmpdb',
16
+ verbose: false,
17
+ sqlitePath: './tmpdb2',
18
+ };
19
+
20
+ const db3Config = {
21
+ agencies: [
22
+ {
23
+ ...config.agencies[0],
24
+ exclude: ['shapes'],
25
+ },
26
+ ],
27
+ verbose: false,
28
+ sqlitePath: './tmpdb3',
18
29
  };
19
30
 
20
31
  describe('openDb():', () => {
@@ -31,6 +42,11 @@ describe('openDb():', () => {
31
42
  const db2 = openDb(db2Config);
32
43
  closeDb(db2);
33
44
  fs.unlinkSync(db2Config.sqlitePath);
45
+
46
+ // Close db3 and then delete it
47
+ const db3 = openDb(db3Config);
48
+ closeDb(db3);
49
+ fs.unlinkSync(db3Config.sqlitePath);
34
50
  });
35
51
 
36
52
  it('should allow raw db queries: calendar_dates', () => {
@@ -40,7 +56,7 @@ describe('openDb():', () => {
40
56
  .prepare(
41
57
  `SELECT * FROM calendar_dates WHERE exception_type = 1 AND service_id NOT IN (${serviceIds
42
58
  .map((serviceId) => `'${serviceId}'`)
43
- .join(', ')})`
59
+ .join(', ')})`,
44
60
  )
45
61
  .all();
46
62
 
@@ -55,7 +71,7 @@ describe('openDb():', () => {
55
71
  const db = openDb();
56
72
  const results = db
57
73
  .prepare(
58
- 'SELECT * from trips where trips.trip_id IN (SELECT start_stop_times.trip_id FROM stop_times as start_stop_times WHERE stop_id = ? AND start_stop_times.stop_sequence < (SELECT end_stop_times.stop_sequence FROM stop_times as end_stop_times WHERE end_stop_times.stop_sequence > start_stop_times.stop_sequence AND end_stop_times.trip_id = start_stop_times.trip_id AND end_stop_times.stop_id = ? ))'
74
+ 'SELECT * from trips where trips.trip_id IN (SELECT start_stop_times.trip_id FROM stop_times as start_stop_times WHERE stop_id = ? AND start_stop_times.stop_sequence < (SELECT end_stop_times.stop_sequence FROM stop_times as end_stop_times WHERE end_stop_times.stop_sequence > start_stop_times.stop_sequence AND end_stop_times.trip_id = start_stop_times.trip_id AND end_stop_times.stop_id = ? ))',
59
75
  )
60
76
  .all(startStopId, endStopId);
61
77
  should.exists(results);
@@ -69,7 +85,7 @@ describe('openDb():', () => {
69
85
  const db1 = openDb(config);
70
86
 
71
87
  db1.name.should.equal(':memory:');
72
- db2.name.should.equal('./tmpdb');
88
+ db2.name.should.equal('./tmpdb2');
73
89
 
74
90
  // Query db1 for shapes
75
91
  const shapeId = 'cal_sf_tam';
@@ -79,7 +95,7 @@ describe('openDb():', () => {
79
95
  },
80
96
  [],
81
97
  [],
82
- { db: db1 }
98
+ { db: db1 },
83
99
  );
84
100
 
85
101
  const expectedResult = {
@@ -101,10 +117,33 @@ describe('openDb():', () => {
101
117
  },
102
118
  [],
103
119
  [],
104
- { db: db2 }
120
+ { db: db2 },
105
121
  );
106
122
 
107
123
  should.exist(results2);
108
124
  results2.length.should.equal(0);
109
125
  });
126
+
127
+ it('should allow `db` configuration option', async () => {
128
+ const db3 = openDb(db3Config);
129
+
130
+ await importGtfs({
131
+ ...db3Config,
132
+ db: db3,
133
+ });
134
+
135
+ // Query db3 for shapes, none should exist
136
+ const shapeId = 'cal_sf_tam';
137
+ const results = getShapes(
138
+ {
139
+ shape_id: shapeId,
140
+ },
141
+ [],
142
+ [],
143
+ { db: db3 },
144
+ );
145
+
146
+ should.exist(results);
147
+ results.length.should.equal(0);
148
+ });
110
149
  });