gtfs 4.17.7 → 4.18.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.
@@ -248,6 +248,12 @@ var agency = {
248
248
  name: "agency_email",
249
249
  type: "text",
250
250
  nocase: true
251
+ },
252
+ {
253
+ name: "cemv_support",
254
+ type: "integer",
255
+ min: 0,
256
+ max: 2
251
257
  }
252
258
  ]
253
259
  };
@@ -1178,6 +1184,12 @@ var routes = {
1178
1184
  name: "network_id",
1179
1185
  type: "text",
1180
1186
  prefix: true
1187
+ },
1188
+ {
1189
+ name: "cemv_support",
1190
+ type: "integer",
1191
+ min: 0,
1192
+ max: 2
1181
1193
  }
1182
1194
  ]
1183
1195
  };
@@ -1432,6 +1444,12 @@ var stops = {
1432
1444
  {
1433
1445
  name: "platform_code",
1434
1446
  type: "text"
1447
+ },
1448
+ {
1449
+ name: "stop_access",
1450
+ type: "integer",
1451
+ min: 0,
1452
+ max: 1
1435
1453
  }
1436
1454
  ]
1437
1455
  };
@@ -4158,7 +4176,7 @@ function isValidJSON(string) {
4158
4176
  try {
4159
4177
  JSON.parse(string);
4160
4178
  return true;
4161
- } catch (error) {
4179
+ } catch {
4162
4180
  return false;
4163
4181
  }
4164
4182
  }
@@ -4190,7 +4208,8 @@ function setDefaultConfig(initialConfig) {
4190
4208
  ignoreDuplicates: false,
4191
4209
  ignoreErrors: false,
4192
4210
  gtfsRealtimeExpirationSeconds: 0,
4193
- verbose: true
4211
+ verbose: true,
4212
+ downloadTimeout: 3e4
4194
4213
  };
4195
4214
  return {
4196
4215
  ...defaults,
@@ -4224,36 +4243,9 @@ function pluralize(singularWord, pluralWord, count) {
4224
4243
  }
4225
4244
 
4226
4245
  // src/lib/import-gtfs-realtime.ts
4227
- async function fetchGtfsRealtimeData(urlConfig, task) {
4228
- task.log(`Downloading GTFS-Realtime from ${urlConfig.url}`);
4229
- const response = await fetch(urlConfig.url, {
4230
- method: "GET",
4231
- headers: {
4232
- ...urlConfig.headers ?? {},
4233
- "Accept-Encoding": "gzip"
4234
- },
4235
- signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
4236
- });
4237
- if (response.status !== 200) {
4238
- task.logWarning(
4239
- `Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
4240
- );
4241
- return null;
4242
- }
4243
- const buffer = await response.arrayBuffer();
4244
- const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
4245
- new Uint8Array(buffer)
4246
- );
4247
- return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
4248
- enums: String,
4249
- longs: String,
4250
- bytes: String,
4251
- defaults: false,
4252
- arrays: true,
4253
- objects: true,
4254
- oneofs: true
4255
- });
4256
- }
4246
+ var BATCH_SIZE = 1e3;
4247
+ var MAX_RETRIES = 3;
4248
+ var RETRY_DELAY = 1e3;
4257
4249
  function prepareRealtimeFieldValue(entity, column, task) {
4258
4250
  if (column.name === "created_timestamp") {
4259
4251
  return task.currentTimestamp;
@@ -4270,163 +4262,246 @@ function prepareRealtimeFieldValue(entity, column, task) {
4270
4262
  );
4271
4263
  return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
4272
4264
  }
4273
- async function processRealtimeAlerts(db, gtfsRealtimeData, task) {
4274
- const alertStmt = db.prepare(
4275
- `REPLACE INTO ${serviceAlerts.filenameBase} (${serviceAlerts.schema.map((column) => column.name).join(
4276
- ", "
4277
- )}) VALUES (${serviceAlerts.schema.map(() => "?").join(", ")})`
4265
+ function createPreparedStatement(db, model) {
4266
+ const columns = model.schema.map((column) => column.name);
4267
+ const placeholders = model.schema.map(() => "?").join(", ");
4268
+ return db.prepare(
4269
+ `REPLACE INTO ${model.filenameBase} (${columns.join(", ")}) VALUES (${placeholders})`
4278
4270
  );
4279
- const informedEntityStmt = db.prepare(
4280
- `REPLACE INTO ${serviceAlertInformedEntities.filenameBase} (${serviceAlertInformedEntities.schema.map((column) => column.name).join(
4281
- ", "
4282
- )}) VALUES (${serviceAlertInformedEntities.schema.map(() => "?").join(", ")})`
4283
- );
4284
- let totalLineCount = 0;
4285
- db.transaction(() => {
4286
- for (const entity of gtfsRealtimeData.entity) {
4287
- const fieldValues = serviceAlerts.schema.map(
4288
- (column) => prepareRealtimeFieldValue(entity, column, task)
4271
+ }
4272
+ async function processBatch(items, batchSize, processor) {
4273
+ let totalRecordCount = 0;
4274
+ let totalErrorCount = 0;
4275
+ for (let i = 0; i < items.length; i += batchSize) {
4276
+ const batch = items.slice(i, i + batchSize);
4277
+ try {
4278
+ const result = await processor(batch);
4279
+ totalRecordCount += result.recordCount;
4280
+ totalErrorCount += result.errorCount;
4281
+ } catch (error) {
4282
+ const errorMessage = error instanceof Error ? error.message : String(error);
4283
+ totalErrorCount += batch.length;
4284
+ console.error(`Batch processing error: ${errorMessage}`);
4285
+ }
4286
+ }
4287
+ return { recordCount: totalRecordCount, errorCount: totalErrorCount };
4288
+ }
4289
+ async function fetchGtfsRealtimeData(type, task) {
4290
+ const urlConfig = getUrlConfig(type, task);
4291
+ if (!urlConfig) {
4292
+ return null;
4293
+ }
4294
+ task.log(`Importing - GTFS-Realtime from ${urlConfig.url}`);
4295
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4296
+ try {
4297
+ const response = await fetch(urlConfig.url, {
4298
+ method: "GET",
4299
+ headers: {
4300
+ ...urlConfig.headers ?? {},
4301
+ "Accept-Encoding": "gzip"
4302
+ },
4303
+ signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
4304
+ });
4305
+ if (response.status !== 200) {
4306
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4307
+ }
4308
+ const buffer = await response.arrayBuffer();
4309
+ const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
4310
+ new Uint8Array(buffer)
4289
4311
  );
4290
- try {
4291
- alertStmt.run(fieldValues);
4292
- if (entity.alert.informedEntity?.length) {
4293
- const informedEntities = entity.alert.informedEntity.map(
4294
- (informedEntity) => {
4312
+ const feedMessage = GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
4313
+ enums: String,
4314
+ longs: String,
4315
+ bytes: String,
4316
+ defaults: false,
4317
+ arrays: true,
4318
+ objects: true,
4319
+ oneofs: true
4320
+ });
4321
+ return feedMessage;
4322
+ } catch (error) {
4323
+ const errorMessage = error instanceof Error ? error.message : String(error);
4324
+ if (attempt === MAX_RETRIES) {
4325
+ if (task.ignoreErrors) {
4326
+ task.logError(
4327
+ `Failed to fetch ${type} after ${MAX_RETRIES} attempts: ${errorMessage}`
4328
+ );
4329
+ return null;
4330
+ }
4331
+ throw error;
4332
+ }
4333
+ task.logWarning(`Attempt ${attempt} failed for ${type}: ${errorMessage}`);
4334
+ await new Promise(
4335
+ (resolve) => setTimeout(resolve, RETRY_DELAY * attempt)
4336
+ );
4337
+ }
4338
+ }
4339
+ return null;
4340
+ }
4341
+ function getUrlConfig(type, task) {
4342
+ switch (type) {
4343
+ case "alerts":
4344
+ return task.realtimeAlerts;
4345
+ case "tripupdates":
4346
+ return task.realtimeTripUpdates;
4347
+ case "vehiclepositions":
4348
+ return task.realtimeVehiclePositions;
4349
+ default:
4350
+ return void 0;
4351
+ }
4352
+ }
4353
+ function createServiceAlertsProcessor(db, task) {
4354
+ const alertStmt = createPreparedStatement(db, serviceAlerts);
4355
+ const informedEntityStmt = createPreparedStatement(
4356
+ db,
4357
+ serviceAlertInformedEntities
4358
+ );
4359
+ return async (batch) => {
4360
+ let recordCount = 0;
4361
+ let errorCount = 0;
4362
+ db.transaction(() => {
4363
+ for (const entity of batch) {
4364
+ try {
4365
+ const alertValues = serviceAlerts.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4366
+ alertStmt.run(alertValues);
4367
+ recordCount++;
4368
+ if (entity.alert?.informedEntity?.length) {
4369
+ for (const informedEntity of entity.alert.informedEntity) {
4295
4370
  informedEntity.parent = entity;
4296
- return serviceAlertInformedEntities.schema.map(
4371
+ const entityValues = serviceAlertInformedEntities.schema.map(
4297
4372
  (column) => prepareRealtimeFieldValue(informedEntity, column, task)
4298
4373
  );
4374
+ informedEntityStmt.run(entityValues);
4375
+ recordCount++;
4299
4376
  }
4300
- );
4301
- for (const values of informedEntities) {
4302
- informedEntityStmt.run(values);
4303
4377
  }
4378
+ } catch (error) {
4379
+ const errorMessage = error instanceof Error ? error.message : String(error);
4380
+ errorCount++;
4381
+ task.logWarning(`Alert processing error: ${errorMessage}`);
4304
4382
  }
4305
- totalLineCount++;
4306
- } catch (error) {
4307
- task.logWarning(`Import error: ${error.message}`);
4308
4383
  }
4309
- }
4310
- task.log(
4311
- `Importing - GTFS-Realtime service alerts - ${totalLineCount} entries imported\r`,
4312
- true
4313
- );
4314
- })();
4384
+ })();
4385
+ return { recordCount, errorCount };
4386
+ };
4315
4387
  }
4316
- async function processRealtimeTripUpdates(db, gtfsRealtimeData, task) {
4317
- let totalLineCount = 0;
4318
- const tripUpdateStmt = db.prepare(
4319
- `REPLACE INTO ${tripUpdates.filenameBase} (${tripUpdates.schema.map((column) => column.name).join(
4320
- ", "
4321
- )}) VALUES (${tripUpdates.schema.map(() => "?").join(", ")})`
4388
+ function createTripUpdatesProcessor(db, task) {
4389
+ const tripUpdateStmt = createPreparedStatement(
4390
+ db,
4391
+ tripUpdates
4322
4392
  );
4323
- const stopTimeStmt = db.prepare(
4324
- `REPLACE INTO ${stopTimeUpdates.filenameBase} (${stopTimeUpdates.schema.map((column) => column.name).join(
4325
- ", "
4326
- )}) VALUES (${stopTimeUpdates.schema.map(() => "?").join(", ")})`
4393
+ const stopTimeStmt = createPreparedStatement(
4394
+ db,
4395
+ stopTimeUpdates
4327
4396
  );
4328
- db.transaction(() => {
4329
- for (const entity of gtfsRealtimeData.entity) {
4330
- try {
4331
- const fieldValues = tripUpdates.schema.map(
4332
- (column) => prepareRealtimeFieldValue(entity, column, task)
4333
- );
4334
- tripUpdateStmt.run(fieldValues);
4335
- for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
4336
- stopTimeUpdate.parent = entity;
4337
- const values = stopTimeUpdates.schema.map(
4338
- (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
4339
- );
4340
- stopTimeStmt.run(values);
4397
+ return async (batch) => {
4398
+ let recordCount = 0;
4399
+ let errorCount = 0;
4400
+ db.transaction(() => {
4401
+ for (const entity of batch) {
4402
+ try {
4403
+ const tripUpdateValues = tripUpdates.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4404
+ tripUpdateStmt.run(tripUpdateValues);
4405
+ recordCount++;
4406
+ if (entity.tripUpdate?.stopTimeUpdate?.length) {
4407
+ for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
4408
+ stopTimeUpdate.parent = entity;
4409
+ const stopTimeValues = stopTimeUpdates.schema.map(
4410
+ (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
4411
+ );
4412
+ stopTimeStmt.run(stopTimeValues);
4413
+ recordCount++;
4414
+ }
4415
+ }
4416
+ } catch (error) {
4417
+ const errorMessage = error instanceof Error ? error.message : String(error);
4418
+ errorCount++;
4419
+ task.logWarning(`Trip update processing error: ${errorMessage}`);
4341
4420
  }
4342
- totalLineCount++;
4343
- } catch (error) {
4344
- task.logWarning(`Import error: ${error.message}`);
4345
4421
  }
4346
- }
4347
- task.log(
4348
- `Importing - GTFS-Realtime trip updates - ${totalLineCount} entries imported\r`,
4349
- true
4350
- );
4351
- })();
4422
+ })();
4423
+ return { recordCount, errorCount };
4424
+ };
4352
4425
  }
4353
- async function processRealtimeVehiclePositions(db, gtfsRealtimeData, task) {
4354
- let totalLineCount = 0;
4355
- const vehiclePositionStmt = db.prepare(
4356
- `REPLACE INTO ${vehiclePositions.filenameBase} (${vehiclePositions.schema.map((column) => column.name).join(
4357
- ", "
4358
- )}) VALUES (${vehiclePositions.schema.map(() => "?").join(", ")})`
4426
+ function createVehiclePositionsProcessor(db, task) {
4427
+ const vehiclePositionStmt = createPreparedStatement(
4428
+ db,
4429
+ vehiclePositions
4359
4430
  );
4360
- db.transaction(() => {
4361
- for (const entity of gtfsRealtimeData.entity) {
4362
- try {
4363
- const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4364
- vehiclePositionStmt.run(fieldValues);
4365
- totalLineCount++;
4366
- } catch (error) {
4367
- task.logWarning(`Import error: ${error.message}`);
4431
+ return async (batch) => {
4432
+ let recordCount = 0;
4433
+ let errorCount = 0;
4434
+ db.transaction(() => {
4435
+ for (const entity of batch) {
4436
+ try {
4437
+ const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
4438
+ vehiclePositionStmt.run(fieldValues);
4439
+ recordCount++;
4440
+ } catch (error) {
4441
+ const errorMessage = error instanceof Error ? error.message : String(error);
4442
+ errorCount++;
4443
+ task.logWarning(`Vehicle position processing error: ${errorMessage}`);
4444
+ }
4368
4445
  }
4369
- }
4370
- task.log(
4371
- `Importing - GTFS-Realtime vehicle positions - ${totalLineCount} entries imported\r`,
4372
- true
4373
- );
4374
- })();
4446
+ })();
4447
+ return { recordCount, errorCount };
4448
+ };
4375
4449
  }
4376
4450
  async function updateGtfsRealtimeData(task) {
4377
- if (task.realtimeAlerts === void 0 && task.realtimeTripUpdates === void 0 && task.realtimeVehiclePositions === void 0) {
4451
+ if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) {
4378
4452
  return;
4379
4453
  }
4454
+ const [alertsData, tripUpdatesData, vehiclePositionsData] = await Promise.all(
4455
+ [
4456
+ task.realtimeAlerts?.url ? fetchGtfsRealtimeData("alerts", task) : null,
4457
+ task.realtimeTripUpdates?.url ? fetchGtfsRealtimeData("tripupdates", task) : null,
4458
+ task.realtimeVehiclePositions?.url ? fetchGtfsRealtimeData("vehiclepositions", task) : null
4459
+ ]
4460
+ );
4380
4461
  const db = openDb({ sqlitePath: task.sqlitePath });
4381
- if (task.realtimeAlerts?.url) {
4382
- try {
4383
- const alertsData = await fetchGtfsRealtimeData(task.realtimeAlerts, task);
4384
- if (alertsData?.entity) {
4385
- await processRealtimeAlerts(db, alertsData, task);
4386
- }
4387
- } catch (error) {
4388
- if (task.ignoreErrors) {
4389
- task.logError(error.message);
4390
- } else {
4391
- throw error;
4392
- }
4393
- }
4462
+ const recordCounts = {
4463
+ alerts: 0,
4464
+ tripupdates: 0,
4465
+ vehiclepositions: 0
4466
+ };
4467
+ const processingPromises = [];
4468
+ if (alertsData?.entity?.length) {
4469
+ processingPromises.push(
4470
+ processBatch(
4471
+ alertsData.entity,
4472
+ BATCH_SIZE,
4473
+ createServiceAlertsProcessor(db, task)
4474
+ ).then((result) => {
4475
+ recordCounts.alerts = result.recordCount;
4476
+ })
4477
+ );
4394
4478
  }
4395
- if (task.realtimeTripUpdates?.url) {
4396
- try {
4397
- const tripUpdatesData = await fetchGtfsRealtimeData(
4398
- task.realtimeTripUpdates,
4399
- task
4400
- );
4401
- if (tripUpdatesData?.entity) {
4402
- await processRealtimeTripUpdates(db, tripUpdatesData, task);
4403
- }
4404
- } catch (error) {
4405
- if (task.ignoreErrors) {
4406
- task.logError(error.message);
4407
- } else {
4408
- throw error;
4409
- }
4410
- }
4479
+ if (tripUpdatesData?.entity?.length) {
4480
+ processingPromises.push(
4481
+ processBatch(
4482
+ tripUpdatesData.entity,
4483
+ BATCH_SIZE,
4484
+ createTripUpdatesProcessor(db, task)
4485
+ ).then((result) => {
4486
+ recordCounts.tripupdates = result.recordCount;
4487
+ })
4488
+ );
4411
4489
  }
4412
- if (task.realtimeVehiclePositions?.url) {
4413
- try {
4414
- const vehiclePositionsData = await fetchGtfsRealtimeData(
4415
- task.realtimeVehiclePositions,
4416
- task
4417
- );
4418
- if (vehiclePositionsData?.entity) {
4419
- await processRealtimeVehiclePositions(db, vehiclePositionsData, task);
4420
- }
4421
- } catch (error) {
4422
- if (task.ignoreErrors) {
4423
- task.logError(error.message);
4424
- } else {
4425
- throw error;
4426
- }
4427
- }
4490
+ if (vehiclePositionsData?.entity?.length) {
4491
+ processingPromises.push(
4492
+ processBatch(
4493
+ vehiclePositionsData.entity,
4494
+ BATCH_SIZE,
4495
+ createVehiclePositionsProcessor(db, task)
4496
+ ).then((result) => {
4497
+ recordCounts.vehiclepositions = result.recordCount;
4498
+ })
4499
+ );
4428
4500
  }
4429
- task.log(`GTFS-Realtime data import complete`);
4501
+ await Promise.all(processingPromises);
4502
+ task.log(
4503
+ `GTFS-Realtime import complete: ${recordCounts.alerts} alerts, ${recordCounts.tripupdates} trip updates, ${recordCounts.vehiclepositions} vehicle positions`
4504
+ );
4430
4505
  }
4431
4506
 
4432
4507
  // src/lib/import-gtfs.ts
@@ -4459,7 +4534,7 @@ var extractGtfsFiles = async (task) => {
4459
4534
  throw new Error("No `path` specified in config");
4460
4535
  }
4461
4536
  const gtfsPath = untildify(task.path);
4462
- task.log(`Importing GTFS from ${task.path}\r`);
4537
+ task.log(`Importing static GTFS from ${task.path}\r`);
4463
4538
  if (path2.extname(gtfsPath) === ".zip") {
4464
4539
  try {
4465
4540
  await unzip(gtfsPath, task.downloadDir);
@@ -4542,11 +4617,11 @@ var createGtfsTables = (db) => {
4542
4617
  if (column.type === "time") {
4543
4618
  sqlColumnCreateStatements.push(
4544
4619
  `${getTimestampColumnName(column.name)} INTEGER GENERATED ALWAYS AS (
4545
- CASE
4546
- WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
4620
+ CASE
4621
+ WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
4547
4622
  ELSE CAST(
4548
- substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
4549
- substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
4623
+ substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
4624
+ substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
4550
4625
  substr(${column.name}, -2) AS INTEGER
4551
4626
  )
4552
4627
  END
@@ -4603,7 +4678,7 @@ var formatGtfsLine = (line, model, totalLineCount) => {
4603
4678
  continue;
4604
4679
  }
4605
4680
  if (type === "date") {
4606
- value = value.replace(/-/g, "");
4681
+ value = value?.toString().replace(/-/g, "");
4607
4682
  if (value.length !== 8) {
4608
4683
  throw new Error(
4609
4684
  `Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
@@ -4619,203 +4694,212 @@ var formatGtfsLine = (line, model, totalLineCount) => {
4619
4694
  }
4620
4695
  return formattedLine;
4621
4696
  };
4622
- var BATCH_SIZE = 1e5;
4623
- var importGtfsFiles = (db, task) => mapSeries2(
4624
- Object.values(models_exports),
4625
- (model) => new Promise((resolve, reject) => {
4626
- let totalLineCount = 0;
4627
- const filename = `${model.filenameBase}.${model.filenameExtension}`;
4628
- if (task.exclude && task.exclude.includes(model.filenameBase)) {
4629
- task.log(`Skipping - ${filename}\r`);
4630
- resolve();
4631
- return;
4632
- }
4633
- if (model.extension === "gtfs-realtime") {
4634
- resolve();
4635
- return;
4636
- }
4637
- const filepath = path2.join(task.downloadDir, `${filename}`);
4638
- if (!existsSync2(filepath)) {
4639
- if (!model.nonstandard) {
4640
- task.log(`Importing - ${filename} - No file found\r`);
4697
+ var BATCH_SIZE2 = 1e5;
4698
+ var importGtfsFiles = async (db, task) => {
4699
+ await mapSeries2(
4700
+ Object.values(models_exports),
4701
+ (model) => new Promise((resolve, reject) => {
4702
+ let totalLineCount = 0;
4703
+ const filename = `${model.filenameBase}.${model.filenameExtension}`;
4704
+ if (task.exclude && task.exclude.includes(model.filenameBase)) {
4705
+ task.log(`Skipping - ${filename}\r`);
4706
+ resolve();
4707
+ return;
4641
4708
  }
4642
- resolve();
4643
- return;
4644
- }
4645
- task.log(`Importing - ${filename}\r`);
4646
- const columns = model.schema;
4647
- const prefixedColumns = new Set(
4648
- columns.filter((column) => column.prefix).map((column) => column.name)
4649
- );
4650
- const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
4651
- const insert = db.prepare(prepareStatement);
4652
- const insertLines = db.transaction((lines) => {
4653
- for (const [rowNumber, line] of Object.entries(lines)) {
4654
- try {
4655
- if (task.prefix === void 0) {
4656
- insert.run(line);
4657
- } else {
4658
- const prefixedLine = Object.fromEntries(
4659
- Object.entries(
4660
- line
4661
- ).map(([columnName, value]) => [
4662
- columnName,
4663
- applyPrefixToValue(
4664
- value,
4665
- prefixedColumns.has(columnName),
4666
- task.prefix
4667
- )
4668
- ])
4669
- );
4670
- insert.run(prefixedLine);
4671
- }
4672
- } catch (error) {
4673
- if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
4674
- const primaryColumns = columns.filter(
4675
- (column) => column.primary
4676
- );
4677
- task.logWarning(
4678
- `Duplicate values for primary key (${primaryColumns.map((column) => column.name).join(", ")}) found in ${filename}. Set the \`ignoreDuplicates\` option to true in config.json to ignore this error`
4679
- );
4680
- }
4681
- task.logWarning(
4682
- `Check ${filename} for invalid data on line ${rowNumber + 1}.`
4683
- );
4684
- throw error;
4709
+ if (model.extension === "gtfs-realtime") {
4710
+ resolve();
4711
+ return;
4712
+ }
4713
+ const filepath = path2.join(task.downloadDir, `${filename}`);
4714
+ if (!existsSync2(filepath)) {
4715
+ if (!model.nonstandard) {
4716
+ task.log(`Importing - ${filename} - No file found\r`);
4685
4717
  }
4718
+ resolve();
4719
+ return;
4686
4720
  }
4687
- });
4688
- if (model.filenameExtension === "txt") {
4689
- const parser = parse({
4690
- columns: true,
4691
- relax_quotes: true,
4692
- trim: true,
4693
- skip_empty_lines: true,
4694
- ...task.csvOptions
4695
- });
4696
- let lines = [];
4697
- parser.on("readable", () => {
4698
- try {
4699
- let record;
4700
- while (record = parser.read()) {
4701
- totalLineCount += 1;
4702
- lines.push(formatGtfsLine(record, model, totalLineCount));
4703
- if (lines.length >= BATCH_SIZE) {
4704
- insertLines(lines);
4705
- lines = [];
4706
- task.log(
4707
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4708
- true
4721
+ task.log(`Importing - ${filename}\r`);
4722
+ const columns = model.schema;
4723
+ const prefixedColumns = new Set(
4724
+ columns.filter((column) => column.prefix).map((column) => column.name)
4725
+ );
4726
+ const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
4727
+ const insert = db.prepare(prepareStatement);
4728
+ const insertLines = db.transaction((lines) => {
4729
+ for (const [rowNumber, line] of Object.entries(lines)) {
4730
+ try {
4731
+ if (task.prefix === void 0) {
4732
+ insert.run(line);
4733
+ } else {
4734
+ const prefixedLine = Object.fromEntries(
4735
+ Object.entries(
4736
+ line
4737
+ ).map(([columnName, value]) => [
4738
+ columnName,
4739
+ applyPrefixToValue(
4740
+ value,
4741
+ prefixedColumns.has(columnName),
4742
+ task.prefix
4743
+ )
4744
+ ])
4709
4745
  );
4746
+ insert.run(prefixedLine);
4710
4747
  }
4711
- }
4712
- } catch (error) {
4713
- if (task.ignoreErrors) {
4714
- task.logError(`Error processing ${filename}: ${error.message}`);
4715
- resolve();
4716
- } else {
4717
- reject(error);
4748
+ } catch (error) {
4749
+ if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
4750
+ const primaryColumns = columns.filter(
4751
+ (column) => column.primary
4752
+ );
4753
+ task.logWarning(
4754
+ `Duplicate values for primary key (${primaryColumns.map((column) => column.name).join(", ")}) found in ${filename}. Set the \`ignoreDuplicates\` option to true in config.json to ignore this error`
4755
+ );
4756
+ }
4757
+ task.logWarning(
4758
+ `Check ${filename} for invalid data on line ${rowNumber + 1}.`
4759
+ );
4760
+ throw error;
4718
4761
  }
4719
4762
  }
4720
4763
  });
4721
- parser.on("end", () => {
4722
- try {
4723
- if (lines.length > 0) {
4724
- try {
4725
- insertLines(lines);
4726
- } catch (error) {
4727
- if (task.ignoreErrors) {
4728
- task.logError(
4729
- `Error inserting data for ${filename}: ${error.message}`
4764
+ if (model.filenameExtension === "txt") {
4765
+ const parser = parse({
4766
+ columns: true,
4767
+ relax_quotes: true,
4768
+ trim: true,
4769
+ skip_empty_lines: true,
4770
+ ...task.csvOptions
4771
+ });
4772
+ let lines = [];
4773
+ parser.on("readable", () => {
4774
+ try {
4775
+ let record;
4776
+ while (record = parser.read()) {
4777
+ totalLineCount += 1;
4778
+ lines.push(formatGtfsLine(record, model, totalLineCount));
4779
+ if (lines.length >= BATCH_SIZE2) {
4780
+ insertLines(lines);
4781
+ lines = [];
4782
+ task.log(
4783
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4784
+ true
4730
4785
  );
4731
- resolve();
4732
- return;
4733
- } else {
4734
- reject(error);
4735
- return;
4736
4786
  }
4737
4787
  }
4788
+ } catch (error) {
4789
+ if (task.ignoreErrors) {
4790
+ const errorMessage = error instanceof Error ? error.message : String(error);
4791
+ task.logError(`Error processing ${filename}: ${errorMessage}`);
4792
+ resolve();
4793
+ } else {
4794
+ reject(error);
4795
+ }
4738
4796
  }
4739
- task.log(
4740
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4741
- true
4742
- );
4743
- resolve();
4744
- } catch (error) {
4745
- if (task.ignoreErrors) {
4746
- task.logError(`Error finalizing ${filename}: ${error.message}`);
4797
+ });
4798
+ parser.on("end", () => {
4799
+ try {
4800
+ if (lines.length > 0) {
4801
+ try {
4802
+ insertLines(lines);
4803
+ } catch (error) {
4804
+ if (task.ignoreErrors) {
4805
+ const errorMessage = error instanceof Error ? error.message : String(error);
4806
+ task.logError(
4807
+ `Error inserting data for ${filename}: ${errorMessage}`
4808
+ );
4809
+ resolve();
4810
+ return;
4811
+ } else {
4812
+ reject(error);
4813
+ return;
4814
+ }
4815
+ }
4816
+ }
4817
+ task.log(
4818
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4819
+ true
4820
+ );
4747
4821
  resolve();
4748
- } else {
4749
- reject(error);
4822
+ } catch (error) {
4823
+ if (task.ignoreErrors) {
4824
+ const errorMessage = error instanceof Error ? error.message : String(error);
4825
+ task.logError(`Error finalizing ${filename}: ${errorMessage}`);
4826
+ resolve();
4827
+ } else {
4828
+ reject(error);
4829
+ }
4750
4830
  }
4751
- }
4752
- });
4753
- parser.on("error", (error) => {
4754
- if (task.ignoreErrors) {
4755
- task.logError(`Parser error for ${filename}: ${error.message}`);
4756
- resolve();
4757
- } else {
4758
- reject(error);
4759
- }
4760
- });
4761
- createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
4762
- } else if (model.filenameExtension === "geojson") {
4763
- readFile2(filepath, "utf8").then((data) => {
4764
- if (isValidJSON(data) === false) {
4831
+ });
4832
+ parser.on("error", (error) => {
4765
4833
  if (task.ignoreErrors) {
4766
- task.logError(`Invalid JSON in ${filename}`);
4834
+ const errorMessage = error instanceof Error ? error.message : String(error);
4835
+ task.logError(`Parser error for ${filename}: ${errorMessage}`);
4767
4836
  resolve();
4768
- return;
4769
4837
  } else {
4770
- reject(new Error(`Invalid JSON in ${filename}`));
4771
- return;
4838
+ reject(error);
4772
4839
  }
4773
- }
4774
- totalLineCount += 1;
4775
- const line = formatGtfsLine(
4776
- { geojson: data },
4777
- model,
4778
- totalLineCount
4779
- );
4780
- try {
4781
- insertLines([line]);
4782
- task.log(
4783
- `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4784
- true
4840
+ });
4841
+ createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
4842
+ } else if (model.filenameExtension === "geojson") {
4843
+ readFile2(filepath, "utf8").then((data) => {
4844
+ if (isValidJSON(data) === false) {
4845
+ if (task.ignoreErrors) {
4846
+ task.logError(`Invalid JSON in ${filename}`);
4847
+ resolve();
4848
+ return;
4849
+ } else {
4850
+ reject(new Error(`Invalid JSON in ${filename}`));
4851
+ return;
4852
+ }
4853
+ }
4854
+ totalLineCount += 1;
4855
+ const line = formatGtfsLine(
4856
+ { geojson: data },
4857
+ model,
4858
+ totalLineCount
4785
4859
  );
4786
- resolve();
4787
- } catch (error) {
4788
- if (task.ignoreErrors) {
4789
- task.logError(
4790
- `Error inserting data for ${filename}: ${error.message}`
4860
+ try {
4861
+ insertLines([line]);
4862
+ task.log(
4863
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
4864
+ true
4791
4865
  );
4792
4866
  resolve();
4867
+ } catch (error) {
4868
+ if (task.ignoreErrors) {
4869
+ const errorMessage = error instanceof Error ? error.message : String(error);
4870
+ task.logError(
4871
+ `Error inserting data for ${filename}: ${errorMessage}`
4872
+ );
4873
+ resolve();
4874
+ } else {
4875
+ reject(error);
4876
+ }
4877
+ }
4878
+ }).catch((error) => {
4879
+ if (task.ignoreErrors) {
4880
+ const errorMessage = error instanceof Error ? error.message : String(error);
4881
+ task.logError(`Error reading ${filename}: ${errorMessage}`);
4882
+ resolve();
4793
4883
  } else {
4794
4884
  reject(error);
4795
4885
  }
4796
- }
4797
- }).catch((error) => {
4886
+ });
4887
+ } else {
4798
4888
  if (task.ignoreErrors) {
4799
- task.logError(`Error reading ${filename}: ${error.message}`);
4889
+ task.logError(
4890
+ `Unsupported file type: ${model.filenameExtension} for ${filename}`
4891
+ );
4800
4892
  resolve();
4801
4893
  } else {
4802
- reject(error);
4894
+ reject(
4895
+ new Error(`Unsupported file type: ${model.filenameExtension}`)
4896
+ );
4803
4897
  }
4804
- });
4805
- } else {
4806
- if (task.ignoreErrors) {
4807
- task.logError(
4808
- `Unsupported file type: ${model.filenameExtension} for ${filename}`
4809
- );
4810
- resolve();
4811
- } else {
4812
- reject(
4813
- new Error(`Unsupported file type: ${model.filenameExtension}`)
4814
- );
4815
4898
  }
4816
- }
4817
- })
4818
- );
4899
+ })
4900
+ );
4901
+ task.log(`Static GTFS import complete`);
4902
+ };
4819
4903
  async function importGtfs(initialConfig) {
4820
4904
  const startTime = process.hrtime.bigint();
4821
4905
  const config = setDefaultConfig(initialConfig);
@@ -4832,7 +4916,6 @@ async function importGtfs(initialConfig) {
4832
4916
  const tempPath = temporaryDirectory();
4833
4917
  const task = {
4834
4918
  exclude: agency2.exclude,
4835
- url: agency2.url,
4836
4919
  headers: agency2.headers,
4837
4920
  realtimeAlerts: agency2.realtimeAlerts,
4838
4921
  realtimeTripUpdates: agency2.realtimeTripUpdates,
@@ -4840,7 +4923,6 @@ async function importGtfs(initialConfig) {
4840
4923
  downloadDir: tempPath,
4841
4924
  downloadTimeout: config.downloadTimeout,
4842
4925
  gtfsRealtimeExpirationSeconds: config.gtfsRealtimeExpirationSeconds,
4843
- path: agency2.path,
4844
4926
  csvOptions: config.csvOptions || {},
4845
4927
  ignoreDuplicates: config.ignoreDuplicates,
4846
4928
  ignoreErrors: config.ignoreErrors,
@@ -4851,8 +4933,13 @@ async function importGtfs(initialConfig) {
4851
4933
  logWarning: logWarning(config),
4852
4934
  logError: logError(config)
4853
4935
  };
4854
- if (task.url) {
4936
+ if ("url" in agency2) {
4937
+ Object.assign(task, { url: agency2.url });
4855
4938
  await downloadGtfsFiles(task);
4939
+ } else {
4940
+ Object.assign(task, {
4941
+ path: agency2.path
4942
+ });
4856
4943
  }
4857
4944
  await extractGtfsFiles(task);
4858
4945
  await importGtfsFiles(db, task);
@@ -4860,7 +4947,8 @@ async function importGtfs(initialConfig) {
4860
4947
  await rm2(tempPath, { recursive: true });
4861
4948
  } catch (error) {
4862
4949
  if (config.ignoreErrors) {
4863
- logError(config)(error.message);
4950
+ const errorMessage = error instanceof Error ? error.message : String(error);
4951
+ logError(config)(errorMessage);
4864
4952
  } else {
4865
4953
  throw error;
4866
4954
  }
@@ -4875,7 +4963,7 @@ async function importGtfs(initialConfig) {
4875
4963
  `
4876
4964
  );
4877
4965
  } catch (error) {
4878
- if (error?.code === "SQLITE_CANTOPEN") {
4966
+ if (error.code === "SQLITE_CANTOPEN") {
4879
4967
  logError(config)(
4880
4968
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
4881
4969
  );