gtfs 3.1.1 → 3.2.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 (111) hide show
  1. package/.eslintrc.json +14 -19
  2. package/.husky/pre-commit +4 -0
  3. package/@types/index.d.ts +269 -0
  4. package/@types/tests.ts +26 -0
  5. package/@types/tsconfig.json +17 -0
  6. package/CHANGELOG.md +182 -1
  7. package/README.md +168 -149
  8. package/bin/gtfs-export.js +4 -5
  9. package/bin/gtfs-import.js +6 -7
  10. package/config-sample-full.json +2 -7
  11. package/docs/images/node-gtfs-logo.svg +18 -0
  12. package/lib/db.js +63 -12
  13. package/lib/export.js +27 -13
  14. package/lib/file-utils.js +26 -9
  15. package/lib/geojson-utils.js +51 -38
  16. package/lib/gtfs/agencies.js +15 -4
  17. package/lib/gtfs/attributions.js +15 -4
  18. package/lib/gtfs/calendar-dates.js +15 -4
  19. package/lib/gtfs/calendars.js +15 -4
  20. package/lib/gtfs/fare-attributes.js +15 -4
  21. package/lib/gtfs/fare-rules.js +15 -4
  22. package/lib/gtfs/feed-info.js +15 -4
  23. package/lib/gtfs/frequencies.js +15 -4
  24. package/lib/gtfs/levels.js +15 -4
  25. package/lib/gtfs/pathways.js +15 -4
  26. package/lib/gtfs/routes.js +22 -6
  27. package/lib/gtfs/shapes.js +59 -23
  28. package/lib/gtfs/stop-times.js +15 -4
  29. package/lib/gtfs/stops.js +57 -23
  30. package/lib/gtfs/transfers.js +15 -4
  31. package/lib/gtfs/translations.js +15 -4
  32. package/lib/gtfs/trips.js +15 -4
  33. package/lib/gtfs-ride/board-alights.js +15 -4
  34. package/lib/gtfs-ride/ride-feed-infos.js +15 -4
  35. package/lib/gtfs-ride/rider-trips.js +15 -4
  36. package/lib/gtfs-ride/riderships.js +15 -4
  37. package/lib/gtfs-ride/trip-capacities.js +15 -4
  38. package/lib/import.js +203 -128
  39. package/lib/log-utils.js +8 -4
  40. package/lib/non-standard/directions.js +15 -4
  41. package/lib/non-standard/stop-attributes.js +15 -4
  42. package/lib/non-standard/timetable-notes-references.js +15 -4
  43. package/lib/non-standard/timetable-notes.js +15 -4
  44. package/lib/non-standard/timetable-pages.js +15 -4
  45. package/lib/non-standard/timetable-stop-order.js +15 -4
  46. package/lib/non-standard/timetables.js +15 -4
  47. package/lib/utils.js +26 -12
  48. package/models/gtfs/agency.js +11 -11
  49. package/models/gtfs/attributions.js +14 -14
  50. package/models/gtfs/calendar-dates.js +7 -7
  51. package/models/gtfs/calendar.js +12 -12
  52. package/models/gtfs/fare-attributes.js +9 -9
  53. package/models/gtfs/fare-rules.js +8 -8
  54. package/models/gtfs/feed-info.js +12 -12
  55. package/models/gtfs/frequencies.js +10 -10
  56. package/models/gtfs/levels.js +5 -5
  57. package/models/gtfs/pathways.js +14 -14
  58. package/models/gtfs/routes.js +14 -14
  59. package/models/gtfs/shapes.js +8 -8
  60. package/models/gtfs/stop-times.js +17 -17
  61. package/models/gtfs/stops.js +17 -17
  62. package/models/gtfs/transfers.js +7 -7
  63. package/models/gtfs/translations.js +10 -10
  64. package/models/gtfs/trips.js +12 -12
  65. package/models/gtfs-ride/board-alight.js +24 -24
  66. package/models/gtfs-ride/ride-feed-info.js +8 -8
  67. package/models/gtfs-ride/rider-trip.js +21 -21
  68. package/models/gtfs-ride/ridership.js +23 -23
  69. package/models/gtfs-ride/trip-capacity.js +10 -10
  70. package/models/models.js +1 -1
  71. package/models/non-standard/directions.js +6 -6
  72. package/models/non-standard/stop-attributes.js +5 -5
  73. package/models/non-standard/timetable-notes-references.js +9 -9
  74. package/models/non-standard/timetable-notes.js +5 -5
  75. package/models/non-standard/timetable-pages.js +5 -5
  76. package/models/non-standard/timetable-stop-order.js +6 -6
  77. package/models/non-standard/timetables.js +27 -27
  78. package/package.json +35 -13
  79. package/test/mocha/export-gtfs.js +74 -44
  80. package/test/mocha/get-agencies.js +20 -14
  81. package/test/mocha/get-attributions.js +10 -4
  82. package/test/mocha/get-board-alights.js +10 -4
  83. package/test/mocha/get-calendar-dates.js +31 -24
  84. package/test/mocha/get-calendars.js +17 -11
  85. package/test/mocha/get-db.js +71 -5
  86. package/test/mocha/get-directions.js +10 -4
  87. package/test/mocha/get-fare-attributes.js +12 -6
  88. package/test/mocha/get-fare-rules.js +17 -13
  89. package/test/mocha/get-feed-info.js +10 -4
  90. package/test/mocha/get-frequencies.js +10 -4
  91. package/test/mocha/get-levels.js +4 -4
  92. package/test/mocha/get-pathways.js +10 -4
  93. package/test/mocha/get-ride-feed-infos.js +9 -3
  94. package/test/mocha/get-rider-trips.js +10 -4
  95. package/test/mocha/get-riderships.js +10 -4
  96. package/test/mocha/get-routes.js +12 -16
  97. package/test/mocha/get-shapes-as-geojson.js +12 -6
  98. package/test/mocha/get-shapes.js +31 -39
  99. package/test/mocha/get-stop-attributes.js +10 -4
  100. package/test/mocha/get-stops-as-geojson.js +11 -5
  101. package/test/mocha/get-stops.js +62 -51
  102. package/test/mocha/get-stoptimes.js +18 -10
  103. package/test/mocha/get-timetable-pages.js +10 -4
  104. package/test/mocha/get-timetable-stop-orders.js +10 -4
  105. package/test/mocha/get-timetables.js +10 -4
  106. package/test/mocha/get-transfers.js +10 -4
  107. package/test/mocha/get-translations.js +10 -4
  108. package/test/mocha/get-trip-capacities.js +10 -4
  109. package/test/mocha/get-trips.js +6 -6
  110. package/test/mocha/import-gtfs.js +63 -46
  111. package/test/test-config.js +9 -4
@@ -2,18 +2,29 @@ import sqlString from 'sqlstring-sqlite';
2
2
 
3
3
  import { getDb } from '../db.js';
4
4
 
5
- import { formatOrderByClause, formatSelectClause, formatWhereClauses } from '../utils.js';
5
+ import {
6
+ formatOrderByClause,
7
+ formatSelectClause,
8
+ formatWhereClauses,
9
+ } from '../utils.js';
6
10
  import boardAlights from '../../models/gtfs-ride/board-alight.js';
7
11
 
8
12
  /*
9
13
  * Returns an array of all board-alights that match the query parameters.
10
14
  */
11
- export async function getBoardAlights(query = {}, fields = [], orderBy = []) {
12
- const db = await getDb();
15
+ export async function getBoardAlights(
16
+ query = {},
17
+ fields = [],
18
+ orderBy = [],
19
+ options = {}
20
+ ) {
21
+ const db = options.db ?? (await getDb());
13
22
  const tableName = sqlString.escapeId(boardAlights.filenameBase);
14
23
  const selectClause = formatSelectClause(fields);
15
24
  const whereClause = formatWhereClauses(query);
16
25
  const orderByClause = formatOrderByClause(orderBy);
17
26
 
18
- return db.all(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`);
27
+ return db.all(
28
+ `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
29
+ );
19
30
  }
@@ -2,18 +2,29 @@ import sqlString from 'sqlstring-sqlite';
2
2
 
3
3
  import { getDb } from '../db.js';
4
4
 
5
- import { formatOrderByClause, formatSelectClause, formatWhereClauses } from '../utils.js';
5
+ import {
6
+ formatOrderByClause,
7
+ formatSelectClause,
8
+ formatWhereClauses,
9
+ } from '../utils.js';
6
10
  import rideFeedInfo from '../../models/gtfs-ride/ride-feed-info.js';
7
11
 
8
12
  /*
9
13
  * Returns an array of all ride-feed-info that match the query parameters.
10
14
  */
11
- export async function getRideFeedInfos(query = {}, fields = [], orderBy = []) {
12
- const db = await getDb();
15
+ export async function getRideFeedInfos(
16
+ query = {},
17
+ fields = [],
18
+ orderBy = [],
19
+ options = {}
20
+ ) {
21
+ const db = options.db ?? (await getDb());
13
22
  const tableName = sqlString.escapeId(rideFeedInfo.filenameBase);
14
23
  const selectClause = formatSelectClause(fields);
15
24
  const whereClause = formatWhereClauses(query);
16
25
  const orderByClause = formatOrderByClause(orderBy);
17
26
 
18
- return db.all(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`);
27
+ return db.all(
28
+ `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
29
+ );
19
30
  }
@@ -2,18 +2,29 @@ import sqlString from 'sqlstring-sqlite';
2
2
 
3
3
  import { getDb } from '../db.js';
4
4
 
5
- import { formatOrderByClause, formatSelectClause, formatWhereClauses } from '../utils.js';
5
+ import {
6
+ formatOrderByClause,
7
+ formatSelectClause,
8
+ formatWhereClauses,
9
+ } from '../utils.js';
6
10
  import riderTrip from '../../models/gtfs-ride/rider-trip.js';
7
11
 
8
12
  /*
9
13
  * Returns an array of all rider trips that match the query parameters.
10
14
  */
11
- export async function getRiderTrips(query = {}, fields = [], orderBy = []) {
12
- const db = await getDb();
15
+ export async function getRiderTrips(
16
+ query = {},
17
+ fields = [],
18
+ orderBy = [],
19
+ options = {}
20
+ ) {
21
+ const db = options.db ?? (await getDb());
13
22
  const tableName = sqlString.escapeId(riderTrip.filenameBase);
14
23
  const selectClause = formatSelectClause(fields);
15
24
  const whereClause = formatWhereClauses(query);
16
25
  const orderByClause = formatOrderByClause(orderBy);
17
26
 
18
- return db.all(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`);
27
+ return db.all(
28
+ `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
29
+ );
19
30
  }
@@ -2,18 +2,29 @@ import sqlString from 'sqlstring-sqlite';
2
2
 
3
3
  import { getDb } from '../db.js';
4
4
 
5
- import { formatOrderByClause, formatSelectClause, formatWhereClauses } from '../utils.js';
5
+ import {
6
+ formatOrderByClause,
7
+ formatSelectClause,
8
+ formatWhereClauses,
9
+ } from '../utils.js';
6
10
  import riderships from '../../models/gtfs-ride/ridership.js';
7
11
 
8
12
  /*
9
13
  * Returns an array of all riderships that match the query parameters.
10
14
  */
11
- export async function getRiderships(query = {}, fields = [], orderBy = []) {
12
- const db = await getDb();
15
+ export async function getRiderships(
16
+ query = {},
17
+ fields = [],
18
+ orderBy = [],
19
+ options = {}
20
+ ) {
21
+ const db = options.db ?? (await getDb());
13
22
  const tableName = sqlString.escapeId(riderships.filenameBase);
14
23
  const selectClause = formatSelectClause(fields);
15
24
  const whereClause = formatWhereClauses(query);
16
25
  const orderByClause = formatOrderByClause(orderBy);
17
26
 
18
- return db.all(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`);
27
+ return db.all(
28
+ `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
29
+ );
19
30
  }
@@ -2,18 +2,29 @@ import sqlString from 'sqlstring-sqlite';
2
2
 
3
3
  import { getDb } from '../db.js';
4
4
 
5
- import { formatOrderByClause, formatSelectClause, formatWhereClauses } from '../utils.js';
5
+ import {
6
+ formatOrderByClause,
7
+ formatSelectClause,
8
+ formatWhereClauses,
9
+ } from '../utils.js';
6
10
  import tripCapacity from '../../models/gtfs-ride/trip-capacity.js';
7
11
 
8
12
  /*
9
13
  * Returns an array of all trip-capacities that match the query parameters.
10
14
  */
11
- export async function getTripCapacities(query = {}, fields = [], orderBy = []) {
12
- const db = await getDb();
15
+ export async function getTripCapacities(
16
+ query = {},
17
+ fields = [],
18
+ orderBy = [],
19
+ options = {}
20
+ ) {
21
+ const db = options.db ?? (await getDb());
13
22
  const tableName = sqlString.escapeId(tripCapacity.filenameBase);
14
23
  const selectClause = formatSelectClause(fields);
15
24
  const whereClause = formatWhereClauses(query);
16
25
  const orderByClause = formatOrderByClause(orderBy);
17
26
 
18
- return db.all(`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`);
27
+ return db.all(
28
+ `${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
29
+ );
19
30
  }
package/lib/import.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import { createReadStream, existsSync, lstatSync } from 'node:fs';
3
3
  import { readdir, rename, writeFile } from 'node:fs/promises';
4
- import copydir from 'copy-dir';
4
+ import copy from 'recursive-copy';
5
5
  import fetch from 'node-fetch';
6
- import parse from 'csv-parse';
6
+ import { parse } from 'csv-parse';
7
7
  import pluralize from 'pluralize';
8
8
  import stripBomStream from 'strip-bom-stream';
9
9
  import { dir } from 'tmp-promise';
@@ -13,32 +13,43 @@ import mapSeries from 'promise-map-series';
13
13
  import models from '../models/models.js';
14
14
  import { openDb, setupDb } from './db.js';
15
15
  import { unzip } from './file-utils.js';
16
- import { log as _log, logError as _logError, logWarning as _logWarning } from './log-utils.js';
17
- import { calculateSecondsFromMidnight, setDefaultConfig, validateConfigForImport } from './utils.js';
18
-
19
- const downloadFiles = async task => {
16
+ import {
17
+ log as _log,
18
+ logError as _logError,
19
+ logWarning as _logWarning,
20
+ } from './log-utils.js';
21
+ import {
22
+ calculateSecondsFromMidnight,
23
+ setDefaultConfig,
24
+ validateConfigForImport,
25
+ } from './utils.js';
26
+
27
+ const downloadFiles = async (task) => {
20
28
  task.log(`Downloading GTFS from ${task.agency_url}`);
21
29
 
22
30
  task.path = `${task.downloadDir}/gtfs.zip`;
23
31
 
24
- const response = await fetch(task.agency_url, { method: 'GET', headers: task.agency_headers || {} });
32
+ const response = await fetch(task.agency_url, {
33
+ method: 'GET',
34
+ headers: task.agency_headers || {},
35
+ });
25
36
 
26
37
  if (response.status !== 200) {
27
38
  throw new Error('Couldn’t download files');
28
39
  }
29
40
 
30
- const buffer = await response.buffer();
41
+ const buffer = await response.arrayBuffer();
31
42
 
32
- await writeFile(task.path, buffer);
43
+ await writeFile(task.path, Buffer.from(buffer));
33
44
  task.log('Download successful');
34
45
  };
35
46
 
36
- const getTextFiles = async folderPath => {
47
+ const getTextFiles = async (folderPath) => {
37
48
  const files = await readdir(folderPath);
38
- return files.filter(filename => filename.slice(-3) === 'txt');
49
+ return files.filter((filename) => filename.slice(-3) === 'txt');
39
50
  };
40
51
 
41
- const readFiles = async task => {
52
+ const readFiles = async (task) => {
42
53
  const gtfsPath = untildify(task.path);
43
54
  task.log(`Importing GTFS from ${task.path}\r`);
44
55
  if (path.extname(gtfsPath) === '.zip') {
@@ -49,22 +60,37 @@ const readFiles = async task => {
49
60
  // If no .txt files in this directory, check for subdirectories and copy them here
50
61
  if (textFiles.length === 0) {
51
62
  const files = await readdir(task.downloadDir);
52
- const folders = files.map(filename => path.join(task.downloadDir, filename)).filter(source => lstatSync(source).isDirectory());
63
+ const folders = files
64
+ .map((filename) => path.join(task.downloadDir, filename))
65
+ .filter((source) => lstatSync(source).isDirectory());
53
66
 
54
67
  if (folders.length > 1) {
55
- throw new Error(`More than one subfolder found in zip file at \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`);
68
+ throw new Error(
69
+ `More than one subfolder found in zip file at \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`
70
+ );
56
71
  } else if (folders.length === 0) {
57
- throw new Error(`No .txt files found in \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`);
72
+ throw new Error(
73
+ `No .txt files found in \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`
74
+ );
58
75
  }
59
76
 
60
77
  const subfolderName = folders[0];
61
78
  const directoryTextFiles = await getTextFiles(subfolderName);
62
79
 
63
80
  if (directoryTextFiles.length === 0) {
64
- throw new Error(`No .txt files found in \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`);
81
+ throw new Error(
82
+ `No .txt files found in \`${task.path}\`. Ensure that .txt files are in the top level of the zip file, or in a single subdirectory.`
83
+ );
65
84
  }
66
85
 
67
- await Promise.all(directoryTextFiles.map(async fileName => rename(path.join(subfolderName, fileName), path.join(task.downloadDir, fileName))));
86
+ await Promise.all(
87
+ directoryTextFiles.map(async (fileName) =>
88
+ rename(
89
+ path.join(subfolderName, fileName),
90
+ path.join(task.downloadDir, fileName)
91
+ )
92
+ )
93
+ );
68
94
  }
69
95
  } catch (error) {
70
96
  task.error(error);
@@ -74,52 +100,66 @@ const readFiles = async task => {
74
100
  } else {
75
101
  // Local file is unzipped, just copy it from there.
76
102
  try {
77
- copydir.sync(gtfsPath, task.downloadDir);
103
+ await copy(gtfsPath, task.downloadDir);
78
104
  } catch {
79
- throw new Error(`Unable to load files from path \`${gtfsPath}\` defined in configuration. Verify that path exists and contains GTFS files.`);
105
+ throw new Error(
106
+ `Unable to load files from path \`${gtfsPath}\` defined in configuration. Verify that path exists and contains GTFS files.`
107
+ );
80
108
  }
81
109
  }
82
110
  };
83
111
 
84
- const deleteTables = db => Promise.all(models.map(
85
- model => db.run(`DROP TABLE IF EXISTS ${model.filenameBase};`)
86
- ));
87
-
88
- const createTables = db => Promise.all(models.map(async model => {
89
- if (!model.schema) {
90
- return;
91
- }
92
-
93
- const columns = model.schema.map(column => {
94
- let check = '';
95
- if (column.min !== undefined && column.max) {
96
- check = `CHECK( ${column.name} >= ${column.min} AND ${column.name} <= ${column.max} )`;
97
- } else if (column.min) {
98
- check = `CHECK( ${column.name} >= ${column.min} )`;
99
- } else if (column.max) {
100
- check = `CHECK( ${column.name} <= ${column.max} )`;
101
- }
112
+ const deleteTables = (db) =>
113
+ Promise.all(
114
+ models.map((model) => db.run(`DROP TABLE IF EXISTS ${model.filenameBase};`))
115
+ );
102
116
 
103
- const primary = column.primary ? 'PRIMARY KEY' : '';
104
- const required = column.required ? 'NOT NULL' : '';
105
- const columnDefault = column.default ? 'DEFAULT ' + column.default : '';
106
- return `${column.name} ${column.type} ${check} ${primary} ${required} ${columnDefault}`;
107
- });
117
+ const createTables = (db) =>
118
+ Promise.all(
119
+ models.map(async (model) => {
120
+ if (!model.schema) {
121
+ return;
122
+ }
108
123
 
109
- await db.run(`CREATE TABLE ${model.filenameBase} (${columns.join(', ')});`);
124
+ const columns = model.schema.map((column) => {
125
+ let check = '';
126
+ if (column.min !== undefined && column.max) {
127
+ check = `CHECK( ${column.name} >= ${column.min} AND ${column.name} <= ${column.max} )`;
128
+ } else if (column.min) {
129
+ check = `CHECK( ${column.name} >= ${column.min} )`;
130
+ } else if (column.max) {
131
+ check = `CHECK( ${column.name} <= ${column.max} )`;
132
+ }
110
133
 
111
- await Promise.all(model.schema.map(async column => {
112
- if (column.index) {
113
- const unique = column.index === 'unique' ? 'UNIQUE' : '';
114
- await db.run(`CREATE ${unique} INDEX idx_${model.filenameBase}_${column.name} ON ${model.filenameBase} (${column.name});`);
115
- }
116
- }));
117
- }));
134
+ const primary = column.primary ? 'PRIMARY KEY' : '';
135
+ const required = column.required ? 'NOT NULL' : '';
136
+ const columnDefault = column.default ? 'DEFAULT ' + column.default : '';
137
+ return `${column.name} ${column.type} ${check} ${primary} ${required} ${columnDefault}`;
138
+ });
139
+
140
+ await db.run(
141
+ `CREATE TABLE ${model.filenameBase} (${columns.join(', ')});`
142
+ );
143
+
144
+ await Promise.all(
145
+ model.schema.map(async (column) => {
146
+ if (column.index) {
147
+ const unique = column.index === 'unique' ? 'UNIQUE' : '';
148
+ await db.run(
149
+ `CREATE ${unique} INDEX idx_${model.filenameBase}_${column.name} ON ${model.filenameBase} (${column.name});`
150
+ );
151
+ }
152
+ })
153
+ );
154
+ })
155
+ );
118
156
 
119
157
  const formatLine = (line, model, totalLineCount) => {
120
158
  const lineNumber = totalLineCount + 1;
121
159
  for (const fieldName of Object.keys(line)) {
122
- const columnSchema = model.schema.find(schema => schema.name === fieldName);
160
+ const columnSchema = model.schema.find(
161
+ (schema) => schema.name === fieldName
162
+ );
123
163
 
124
164
  // Remove columns not part of model
125
165
  if (!columnSchema) {
@@ -155,18 +195,27 @@ const formatLine = (line, model, totalLineCount) => {
155
195
  }
156
196
 
157
197
  // Validate required
158
- if (columnSchema.required === true && (line[fieldName] === undefined || line[fieldName] === '')) {
159
- throw new Error(`Missing required value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}.`);
198
+ if (
199
+ columnSchema.required === true &&
200
+ (line[fieldName] === undefined || line[fieldName] === '')
201
+ ) {
202
+ throw new Error(
203
+ `Missing required value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}.`
204
+ );
160
205
  }
161
206
 
162
207
  // Validate minimum
163
208
  if (columnSchema.min !== undefined && line[fieldName] < columnSchema.min) {
164
- throw new Error(`Invalid value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}: below minimum value of ${columnSchema.min}.`);
209
+ throw new Error(
210
+ `Invalid value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}: below minimum value of ${columnSchema.min}.`
211
+ );
165
212
  }
166
213
 
167
214
  // Validate maximum
168
215
  if (columnSchema.max !== undefined && line[fieldName] > columnSchema.max) {
169
- throw new Error(`Invalid value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}: above maximum value of ${columnSchema.max}.`);
216
+ throw new Error(
217
+ `Invalid value in ${model.filenameBase}.txt for ${fieldName} on line ${lineNumber}: above maximum value of ${columnSchema.max}.`
218
+ );
170
219
  }
171
220
  }
172
221
 
@@ -175,7 +224,7 @@ const formatLine = (line, model, totalLineCount) => {
175
224
  'start_time',
176
225
  'end_time',
177
226
  'arrival_time',
178
- 'departure_time'
227
+ 'departure_time',
179
228
  ];
180
229
 
181
230
  for (const fieldName of timestampFormat) {
@@ -193,7 +242,7 @@ const importLines = async (task, lines, model, totalLineCount) => {
193
242
  }
194
243
 
195
244
  const linesToImportCount = lines.length;
196
- const fieldNames = model.schema.map(column => column.name);
245
+ const fieldNames = model.schema.map((column) => column.name);
197
246
  const placeholders = [];
198
247
  const values = [];
199
248
 
@@ -206,103 +255,129 @@ const importLines = async (task, lines, model, totalLineCount) => {
206
255
  }
207
256
 
208
257
  try {
209
- await task.db.run(`INSERT INTO ${model.filenameBase}(${fieldNames.join(', ')}) VALUES${placeholders.join(',')}`, values);
258
+ await task.db.run(
259
+ `INSERT INTO ${model.filenameBase}(${fieldNames.join(
260
+ ', '
261
+ )}) VALUES${placeholders.join(',')}`,
262
+ values
263
+ );
210
264
  } catch (error) {
211
- task.warn(`Check ${model.filenameBase}.txt for invalid data between lines ${totalLineCount - linesToImportCount} and ${totalLineCount}.`);
265
+ task.warn(
266
+ `Check ${model.filenameBase}.txt for invalid data between lines ${
267
+ totalLineCount - linesToImportCount
268
+ } and ${totalLineCount}.`
269
+ );
212
270
  throw error;
213
271
  }
214
272
 
215
- task.log(`Importing - ${model.filenameBase}.txt - ${totalLineCount} lines imported\r`, true);
273
+ task.log(
274
+ `Importing - ${model.filenameBase}.txt - ${totalLineCount} lines imported\r`,
275
+ true
276
+ );
216
277
  };
217
278
 
218
- const importFiles = task => mapSeries(models, model => new Promise((resolve, reject) => {
219
- // Loop through each GTFS file
220
- // Filter out excluded files from config
221
- if (task.exclude && task.exclude.includes(model.filenameBase)) {
222
- task.log(`Skipping - ${model.filenameBase}.txt\r`);
223
- resolve();
224
- return;
225
- }
226
-
227
- const filepath = path.join(task.downloadDir, `${model.filenameBase}.txt`);
228
-
229
- if (!existsSync(filepath)) {
230
- if (!model.nonstandard) {
231
- task.log(`Importing - ${model.filenameBase}.txt - No file found\r`);
232
- }
233
-
234
- resolve();
235
- return;
236
- }
237
-
238
- task.log(`Importing - ${model.filenameBase}.txt\r`);
239
-
240
- const lines = [];
241
- let totalLineCount = 0;
242
- const maxInsertVariables = 800;
243
- const parser = parse({
244
- columns: true,
245
- relax: true,
246
- trim: true,
247
- skip_empty_lines: true,
248
- ...task.csvOptions
249
- });
250
-
251
- parser.on('readable', async () => {
252
- let record;
253
- /* eslint-disable-next-line no-cond-assign */
254
- while (record = parser.read()) {
255
- try {
256
- totalLineCount += 1;
257
- lines.push(formatLine(record, model, totalLineCount));
258
-
259
- // If we have a bunch of lines ready to insert, then do it
260
- if (lines.length >= maxInsertVariables / model.schema.length) {
261
- /* eslint-disable-next-line no-await-in-loop */
262
- await importLines(task, lines, model, totalLineCount);
279
+ const importFiles = (task) =>
280
+ mapSeries(
281
+ models,
282
+ (model) =>
283
+ new Promise((resolve, reject) => {
284
+ // Loop through each GTFS file
285
+ // Filter out excluded files from config
286
+ if (task.exclude && task.exclude.includes(model.filenameBase)) {
287
+ task.log(`Skipping - ${model.filenameBase}.txt\r`);
288
+ resolve();
289
+ return;
263
290
  }
264
- } catch (error) {
265
- reject(error);
266
- }
267
- }
268
- });
269
291
 
270
- parser.on('end', async () => {
271
- // Insert all remaining lines
272
- await importLines(task, lines, model, totalLineCount).catch(reject);
273
- resolve();
274
- });
292
+ const filepath = path.join(
293
+ task.downloadDir,
294
+ `${model.filenameBase}.txt`
295
+ );
275
296
 
276
- parser.on('error', reject);
297
+ if (!existsSync(filepath)) {
298
+ if (!model.nonstandard) {
299
+ task.log(`Importing - ${model.filenameBase}.txt - No file found\r`);
300
+ }
277
301
 
278
- createReadStream(filepath)
279
- .pipe(stripBomStream())
280
- .pipe(parser);
281
- }));
302
+ resolve();
303
+ return;
304
+ }
282
305
 
283
- const importGtfs = async initialConfig => {
306
+ task.log(`Importing - ${model.filenameBase}.txt\r`);
307
+
308
+ const lines = [];
309
+ let totalLineCount = 0;
310
+ const maxInsertVariables = 800;
311
+ const parser = parse({
312
+ columns: true,
313
+ relax: true,
314
+ trim: true,
315
+ skip_empty_lines: true,
316
+ ...task.csvOptions,
317
+ });
318
+
319
+ parser.on('readable', async () => {
320
+ let record;
321
+
322
+ while ((record = parser.read())) {
323
+ try {
324
+ totalLineCount += 1;
325
+ lines.push(formatLine(record, model, totalLineCount));
326
+
327
+ // If we have a bunch of lines ready to insert, then do it
328
+ if (lines.length >= maxInsertVariables / model.schema.length) {
329
+ /* eslint-disable-next-line no-await-in-loop */
330
+ await importLines(task, lines, model, totalLineCount);
331
+ }
332
+ } catch (error) {
333
+ reject(error);
334
+ }
335
+ }
336
+ });
337
+
338
+ parser.on('end', async () => {
339
+ // Insert all remaining lines
340
+ await importLines(task, lines, model, totalLineCount).catch(reject);
341
+ resolve();
342
+ });
343
+
344
+ parser.on('error', reject);
345
+
346
+ createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
347
+ })
348
+ );
349
+
350
+ const importGtfs = async (initialConfig) => {
284
351
  const config = setDefaultConfig(initialConfig);
285
352
  validateConfigForImport(config);
286
353
  const log = _log(config);
287
354
  const logError = _logError(config);
288
355
  const logWarning = _logWarning(config);
289
- const db = await openDb(config).catch(error => {
356
+ const db = await openDb(config).catch((error) => {
290
357
  if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
291
- logError(`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`);
358
+ logError(
359
+ `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
360
+ );
292
361
  }
293
362
 
294
363
  throw error;
295
364
  });
296
365
 
297
366
  const agencyCount = config.agencies.length;
298
- log(`Starting GTFS import for ${pluralize('file', agencyCount, true)} using SQLite database at ${config.sqlitePath}`);
367
+ log(
368
+ `Starting GTFS import for ${pluralize(
369
+ 'file',
370
+ agencyCount,
371
+ true
372
+ )} using SQLite database at ${config.sqlitePath}`
373
+ );
299
374
 
300
375
  await deleteTables(db);
301
376
  await createTables(db);
302
377
 
303
378
  await setupDb(db);
304
379
 
305
- await mapSeries(config.agencies, async agency => {
380
+ await mapSeries(config.agencies, async (agency) => {
306
381
  const { path, cleanup } = await dir({ unsafeCleanup: true });
307
382
 
308
383
  const task = {
@@ -316,12 +391,12 @@ const importGtfs = async initialConfig => {
316
391
  log: (message, overwrite) => {
317
392
  log(message, overwrite);
318
393
  },
319
- warn: message => {
394
+ warn: (message) => {
320
395
  logWarning(message);
321
396
  },
322
- error: message => {
397
+ error: (message) => {
323
398
  logError(message);
324
- }
399
+ },
325
400
  };
326
401
 
327
402
  if (task.agency_url) {
package/lib/log-utils.js CHANGED
@@ -38,7 +38,7 @@ export function logWarning(config) {
38
38
  return config.logFunction;
39
39
  }
40
40
 
41
- return text => {
41
+ return (text) => {
42
42
  process.stdout.write(`\n${formatWarning(text)}\n`);
43
43
  };
44
44
  }
@@ -51,7 +51,7 @@ export function logError(config) {
51
51
  return config.logFunction;
52
52
  }
53
53
 
54
- return text => {
54
+ return (text) => {
55
55
  process.stdout.write(`\n${formatError(text)}\n`);
56
56
  };
57
57
  }
@@ -60,7 +60,9 @@ export function logError(config) {
60
60
  * Format console warning text
61
61
  */
62
62
  export function formatWarning(text) {
63
- return `${chalk.yellow.underline('Warning')}${chalk.yellow(':')} ${chalk.yellow(text)}`;
63
+ return `${chalk.yellow.underline('Warning')}${chalk.yellow(
64
+ ':'
65
+ )} ${chalk.yellow(text)}`;
64
66
  }
65
67
 
66
68
  /*
@@ -68,5 +70,7 @@ export function formatWarning(text) {
68
70
  */
69
71
  export function formatError(error) {
70
72
  const message = error instanceof Error ? error.message : error;
71
- return `${chalk.red.underline('Error')}${chalk.red(':')} ${chalk.red(message.replace('Error: ', ''))}`;
73
+ return `${chalk.red.underline('Error')}${chalk.red(':')} ${chalk.red(
74
+ message.replace('Error: ', '')
75
+ )}`;
72
76
  }