gtfs 4.14.4 → 4.15.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.
@@ -135,18 +135,16 @@ function formatError(error) {
135
135
  return colors.red(errorMessage);
136
136
  }
137
137
 
138
- // src/lib/import.ts
138
+ // src/lib/import-gtfs.ts
139
139
  import path2 from "node:path";
140
140
  import { createReadStream, existsSync as existsSync2, lstatSync } from "node:fs";
141
141
  import { cp, readdir, rename, readFile as readFile2, rm as rm2, writeFile } from "node:fs/promises";
142
142
  import { parse } from "csv-parse";
143
- import pluralize from "pluralize";
143
+ import pluralize2 from "pluralize";
144
144
  import stripBomStream from "strip-bom-stream";
145
145
  import { temporaryDirectory } from "tempy";
146
146
  import untildify3 from "untildify";
147
- import mapSeries from "promise-map-series";
148
- import GtfsRealtimeBindings from "gtfs-realtime-bindings";
149
- import sqlString2 from "sqlstring-sqlite";
147
+ import mapSeries2 from "promise-map-series";
150
148
 
151
149
  // src/models/models.ts
152
150
  var models_exports = {};
@@ -3298,6 +3296,12 @@ function isValidJSON(string) {
3298
3296
  return true;
3299
3297
  }
3300
3298
 
3299
+ // src/lib/import-gtfs-realtime.ts
3300
+ import pluralize from "pluralize";
3301
+ import GtfsRealtimeBindings from "gtfs-realtime-bindings";
3302
+ import sqlString2 from "sqlstring-sqlite";
3303
+ import mapSeries from "promise-map-series";
3304
+
3301
3305
  // src/lib/utils.ts
3302
3306
  import sqlString from "sqlstring-sqlite";
3303
3307
  import Long from "long";
@@ -3345,60 +3349,10 @@ function padLeadingZeros(time) {
3345
3349
  return split.join(":");
3346
3350
  }
3347
3351
 
3348
- // src/lib/import.ts
3349
- var downloadFiles = async (task) => {
3350
- if (!task.url) {
3351
- throw new Error("No `url` specified in config");
3352
- }
3353
- task.log(`Downloading GTFS from ${task.url}`);
3354
- task.path = `${task.downloadDir}/gtfs.zip`;
3355
- const response = await fetch(task.url, {
3356
- method: "GET",
3357
- headers: task.headers || {},
3358
- signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
3359
- });
3360
- if (response.status !== 200) {
3361
- throw new Error(
3362
- `Unable to download GTFS from ${task.url}. Got status ${response.status}.`
3363
- );
3364
- }
3365
- const buffer = await response.arrayBuffer();
3366
- await writeFile(task.path, Buffer.from(buffer));
3367
- task.log("Download successful");
3368
- };
3369
- var downloadGtfsRealtimeData = async (urlAndHeaders, task) => {
3370
- task.log(`Downloading GTFS-Realtime from ${urlAndHeaders.url}`);
3371
- const response = await fetch(urlAndHeaders.url, {
3372
- method: "GET",
3373
- headers: {
3374
- ...urlAndHeaders.headers ?? {},
3375
- "Accept-Encoding": "gzip"
3376
- },
3377
- signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
3378
- });
3379
- if (response.status !== 200) {
3380
- task.logWarning(
3381
- `Unable to download GTFS-Realtime from ${urlAndHeaders.url}. Got status ${response.status}.`
3382
- );
3383
- return null;
3384
- }
3385
- const buffer = await response.arrayBuffer();
3386
- const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
3387
- new Uint8Array(buffer)
3388
- );
3389
- return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
3390
- enums: String,
3391
- longs: String,
3392
- bytes: String,
3393
- defaults: true,
3394
- arrays: true,
3395
- objects: true,
3396
- oneofs: true
3397
- });
3398
- };
3399
- function getDescendantProp(obj, defaultValue, source) {
3400
- if (source === void 0) return defaultValue;
3401
- const arr = source.split(".");
3352
+ // src/lib/import-gtfs-realtime.ts
3353
+ function getNestedProperty(obj, defaultValue, path3) {
3354
+ if (path3 === void 0) return defaultValue;
3355
+ const arr = path3.split(".");
3402
3356
  while (arr.length) {
3403
3357
  const nextKey = arr.shift();
3404
3358
  if (nextKey === void 0) {
@@ -3427,7 +3381,37 @@ function getDescendantProp(obj, defaultValue, source) {
3427
3381
  if (obj.__isLong__) return convertLongTimeToDate(obj);
3428
3382
  return obj;
3429
3383
  }
3430
- var prepareRealtimeValue = (entity, column, task) => {
3384
+ async function fetchGtfsRealtimeData(urlConfig, task) {
3385
+ task.log(`Downloading GTFS-Realtime from ${urlConfig.url}`);
3386
+ const response = await fetch(urlConfig.url, {
3387
+ method: "GET",
3388
+ headers: {
3389
+ ...urlConfig.headers ?? {},
3390
+ "Accept-Encoding": "gzip"
3391
+ },
3392
+ signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
3393
+ });
3394
+ if (response.status !== 200) {
3395
+ task.logWarning(
3396
+ `Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
3397
+ );
3398
+ return null;
3399
+ }
3400
+ const buffer = await response.arrayBuffer();
3401
+ const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
3402
+ new Uint8Array(buffer)
3403
+ );
3404
+ return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
3405
+ enums: String,
3406
+ longs: String,
3407
+ bytes: String,
3408
+ defaults: true,
3409
+ arrays: true,
3410
+ objects: true,
3411
+ oneofs: true
3412
+ });
3413
+ }
3414
+ function prepareRealtimeFieldValue(entity, column, task) {
3431
3415
  if (column.name === "created_timestamp") {
3432
3416
  return task.currentTimestamp;
3433
3417
  }
@@ -3435,54 +3419,102 @@ var prepareRealtimeValue = (entity, column, task) => {
3435
3419
  return task.currentTimestamp + task.gtfsRealtimeExpirationSeconds;
3436
3420
  }
3437
3421
  return sqlString2.escape(
3438
- getDescendantProp(entity, column.default, column.source)
3422
+ getNestedProperty(entity, column.default, column.source)
3439
3423
  );
3440
- };
3441
- var updateRealtimeData = async (task) => {
3424
+ }
3425
+ async function processRealtimeAlerts(db, gtfsRealtimeData, task) {
3426
+ task.log(`Download successful`);
3427
+ let totalLineCount = 0;
3428
+ for (const entity of gtfsRealtimeData.entity) {
3429
+ const fieldValues = serviceAlerts.schema.map(
3430
+ (column) => prepareRealtimeFieldValue(entity, column, task)
3431
+ );
3432
+ try {
3433
+ db.prepare(
3434
+ `REPLACE INTO ${serviceAlerts.filenameBase} (${serviceAlerts.schema.map((column) => column.name).join(", ")}) VALUES (${fieldValues.join(", ")})`
3435
+ ).run();
3436
+ } catch (error) {
3437
+ task.logWarning("Import error: " + error.message);
3438
+ }
3439
+ const alertTargetArray = [];
3440
+ for (const informedEntity of entity.alert.informedEntity) {
3441
+ informedEntity.parent = entity;
3442
+ const subValues = serviceAlertTargets.schema.map(
3443
+ (column) => prepareRealtimeFieldValue(informedEntity, column, task)
3444
+ );
3445
+ alertTargetArray.push(`(${subValues.join(", ")})`);
3446
+ totalLineCount++;
3447
+ }
3448
+ try {
3449
+ db.prepare(
3450
+ `REPLACE INTO ${serviceAlertTargets.filenameBase} (${serviceAlertTargets.schema.map((column) => column.name).join(", ")}) VALUES ${alertTargetArray.join(", ")}`
3451
+ ).run();
3452
+ } catch (error) {
3453
+ task.logWarning("Import error: " + error.message);
3454
+ }
3455
+ task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
3456
+ }
3457
+ }
3458
+ async function processRealtimeTripUpdates(db, gtfsRealtimeData, task) {
3459
+ task.log(`Download successful`);
3460
+ let totalLineCount = 0;
3461
+ for (const entity of gtfsRealtimeData.entity) {
3462
+ const fieldValues = tripUpdates.schema.map(
3463
+ (column) => prepareRealtimeFieldValue(entity, column, task)
3464
+ );
3465
+ try {
3466
+ db.prepare(
3467
+ `REPLACE INTO ${tripUpdates.filenameBase} (${tripUpdates.schema.map((column) => column.name).join(", ")}) VALUES (${fieldValues.join(", ")})`
3468
+ ).run();
3469
+ } catch (error) {
3470
+ task.logWarning("Import error: " + error.message);
3471
+ }
3472
+ const stopTimeUpdateArray = [];
3473
+ for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
3474
+ stopTimeUpdate.parent = entity;
3475
+ const subValues = stopTimeUpdates.schema.map(
3476
+ (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
3477
+ );
3478
+ stopTimeUpdateArray.push(`(${subValues.join(", ")})`);
3479
+ totalLineCount++;
3480
+ }
3481
+ try {
3482
+ db.prepare(
3483
+ `REPLACE INTO ${stopTimeUpdates.filenameBase} (${stopTimeUpdates.schema.map((column) => column.name).join(", ")}) VALUES ${stopTimeUpdateArray.join(", ")}`
3484
+ ).run();
3485
+ } catch (error) {
3486
+ task.logWarning("Import error: " + error.message);
3487
+ }
3488
+ task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
3489
+ }
3490
+ }
3491
+ async function processRealtimeVehiclePositions(db, gtfsRealtimeData, task) {
3492
+ task.log(`Download successful`);
3493
+ let totalLineCount = 0;
3494
+ for (const entity of gtfsRealtimeData.entity) {
3495
+ const fieldValues = vehiclePositions.schema.map(
3496
+ (column) => prepareRealtimeFieldValue(entity, column, task)
3497
+ );
3498
+ try {
3499
+ db.prepare(
3500
+ `REPLACE INTO ${vehiclePositions.filenameBase} (${vehiclePositions.schema.map((column) => column.name).join(", ")}) VALUES (${fieldValues.join(", ")})`
3501
+ ).run();
3502
+ } catch (error) {
3503
+ task.logWarning("Import error: " + error.message);
3504
+ }
3505
+ task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
3506
+ }
3507
+ }
3508
+ async function updateGtfsRealtimeData(task) {
3442
3509
  if (task.realtimeAlerts === void 0 && task.realtimeTripUpdates === void 0 && task.realtimeVehiclePositions === void 0) {
3443
3510
  return;
3444
3511
  }
3445
- const db = openDb({
3446
- sqlitePath: task.sqlitePath
3447
- });
3512
+ const db = openDb({ sqlitePath: task.sqlitePath });
3448
3513
  if (task.realtimeAlerts?.url) {
3449
3514
  try {
3450
- const gtfsRealtimeData = await downloadGtfsRealtimeData(
3451
- task.realtimeAlerts,
3452
- task
3453
- );
3454
- if (gtfsRealtimeData?.entity) {
3455
- task.log(`Download successful`);
3456
- let totalLineCount = 0;
3457
- for (const entity of gtfsRealtimeData.entity) {
3458
- const fieldValues = serviceAlerts.schema.map(
3459
- (column) => prepareRealtimeValue(entity, column, task)
3460
- );
3461
- try {
3462
- db.prepare(
3463
- `REPLACE INTO ${serviceAlerts.filenameBase} (${serviceAlerts.schema.map((column) => column.name).join(", ")}) VALUES (${fieldValues.join(", ")})`
3464
- ).run();
3465
- } catch (error) {
3466
- task.logWarning("Import error: " + error.message);
3467
- }
3468
- const alertTargetArray = [];
3469
- for (const informedEntity of entity.alert.informedEntity) {
3470
- informedEntity.parent = entity;
3471
- const subValues = serviceAlertTargets.schema.map(
3472
- (column) => prepareRealtimeValue(informedEntity, column, task)
3473
- );
3474
- alertTargetArray.push(`(${subValues.join(", ")})`);
3475
- totalLineCount++;
3476
- }
3477
- try {
3478
- db.prepare(
3479
- `REPLACE INTO ${serviceAlertTargets.filenameBase} (${serviceAlertTargets.schema.map((column) => column.name).join(", ")}) VALUES ${alertTargetArray.join(", ")}`
3480
- ).run();
3481
- } catch (error) {
3482
- task.logWarning("Import error: " + error.message);
3483
- }
3484
- task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
3485
- }
3515
+ const alertsData = await fetchGtfsRealtimeData(task.realtimeAlerts, task);
3516
+ if (alertsData?.entity) {
3517
+ await processRealtimeAlerts(db, alertsData, task);
3486
3518
  }
3487
3519
  } catch (error) {
3488
3520
  if (task.ignoreErrors) {
@@ -3494,42 +3526,12 @@ var updateRealtimeData = async (task) => {
3494
3526
  }
3495
3527
  if (task.realtimeTripUpdates?.url) {
3496
3528
  try {
3497
- const gtfsRealtimeData = await downloadGtfsRealtimeData(
3529
+ const tripUpdatesData = await fetchGtfsRealtimeData(
3498
3530
  task.realtimeTripUpdates,
3499
3531
  task
3500
3532
  );
3501
- if (gtfsRealtimeData?.entity) {
3502
- task.log(`Download successful`);
3503
- let totalLineCount = 0;
3504
- for (const entity of gtfsRealtimeData.entity) {
3505
- const fieldValues = tripUpdates.schema.map(
3506
- (column) => prepareRealtimeValue(entity, column, task)
3507
- );
3508
- try {
3509
- db.prepare(
3510
- `REPLACE INTO ${tripUpdates.filenameBase} (${tripUpdates.schema.map((column) => column.name).join(", ")}) VALUES (${fieldValues.join(", ")})`
3511
- ).run();
3512
- } catch (error) {
3513
- task.logWarning("Import error: " + error.message);
3514
- }
3515
- const stopTimeUpdateArray = [];
3516
- for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
3517
- stopTimeUpdate.parent = entity;
3518
- const subValues = stopTimeUpdates.schema.map(
3519
- (column) => prepareRealtimeValue(stopTimeUpdate, column, task)
3520
- );
3521
- stopTimeUpdateArray.push(`(${subValues.join(", ")})`);
3522
- totalLineCount++;
3523
- }
3524
- try {
3525
- db.prepare(
3526
- `REPLACE INTO ${stopTimeUpdates.filenameBase} (${stopTimeUpdates.schema.map((column) => column.name).join(", ")}) VALUES ${stopTimeUpdateArray.join(", ")}`
3527
- ).run();
3528
- } catch (error) {
3529
- task.logWarning("Import error: " + error.message);
3530
- }
3531
- task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
3532
- }
3533
+ if (tripUpdatesData?.entity) {
3534
+ await processRealtimeTripUpdates(db, tripUpdatesData, task);
3533
3535
  }
3534
3536
  } catch (error) {
3535
3537
  if (task.ignoreErrors) {
@@ -3541,26 +3543,12 @@ var updateRealtimeData = async (task) => {
3541
3543
  }
3542
3544
  if (task.realtimeVehiclePositions?.url) {
3543
3545
  try {
3544
- const gtfsRealtimeData = await downloadGtfsRealtimeData(
3546
+ const vehiclePositionsData = await fetchGtfsRealtimeData(
3545
3547
  task.realtimeVehiclePositions,
3546
3548
  task
3547
3549
  );
3548
- if (gtfsRealtimeData?.entity) {
3549
- task.log(`Download successful`);
3550
- let totalLineCount = 0;
3551
- for (const entity of gtfsRealtimeData.entity) {
3552
- const fieldValues = vehiclePositions.schema.map(
3553
- (column) => prepareRealtimeValue(entity, column, task)
3554
- );
3555
- try {
3556
- db.prepare(
3557
- `REPLACE INTO ${vehiclePositions.filenameBase} (${vehiclePositions.schema.map((column) => column.name).join(", ")}) VALUES (${fieldValues.join(", ")})`
3558
- ).run();
3559
- } catch (error) {
3560
- task.logWarning("Import error: " + error.message);
3561
- }
3562
- task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
3563
- }
3550
+ if (vehiclePositionsData?.entity) {
3551
+ await processRealtimeVehiclePositions(db, vehiclePositionsData, task);
3564
3552
  }
3565
3553
  } catch (error) {
3566
3554
  if (task.ignoreErrors) {
@@ -3571,12 +3559,59 @@ var updateRealtimeData = async (task) => {
3571
3559
  }
3572
3560
  }
3573
3561
  task.log(`GTFS-Realtime data import complete`);
3562
+ }
3563
+
3564
+ // src/lib/import-gtfs.ts
3565
+ var dateCache = {};
3566
+ var calculateAndCacheDate = (value) => {
3567
+ const cached = dateCache[value];
3568
+ if (cached != null) {
3569
+ return cached;
3570
+ }
3571
+ const seconds = calculateSecondsFromMidnight(value);
3572
+ const date = padLeadingZeros(value);
3573
+ const computed = [seconds, date];
3574
+ dateCache[value] = computed;
3575
+ return computed;
3574
3576
  };
3575
3577
  var getTextFiles = async (folderPath) => {
3576
3578
  const files = await readdir(folderPath);
3577
3579
  return files.filter((filename) => filename.slice(-3) === "txt");
3578
3580
  };
3579
- var readFiles = async (task) => {
3581
+ var TIME_COLUMN_NAMES = [
3582
+ "start_time",
3583
+ "end_time",
3584
+ "arrival_time",
3585
+ "departure_time",
3586
+ "prior_notice_last_time",
3587
+ "prior_notice_start_time",
3588
+ "start_pickup_drop_off_window"
3589
+ ];
3590
+ var TIME_COLUMN_PAIRS = TIME_COLUMN_NAMES.map((name) => [
3591
+ name,
3592
+ name.endsWith("time") ? `${name}stamp` : `${name}_timestamp`
3593
+ ]);
3594
+ var downloadGtfsFiles = async (task) => {
3595
+ if (!task.url) {
3596
+ throw new Error("No `url` specified in config");
3597
+ }
3598
+ task.log(`Downloading GTFS from ${task.url}`);
3599
+ task.path = `${task.downloadDir}/gtfs.zip`;
3600
+ const response = await fetch(task.url, {
3601
+ method: "GET",
3602
+ headers: task.headers || {},
3603
+ signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
3604
+ });
3605
+ if (response.status !== 200) {
3606
+ throw new Error(
3607
+ `Unable to download GTFS from ${task.url}. Got status ${response.status}.`
3608
+ );
3609
+ }
3610
+ const buffer = await response.arrayBuffer();
3611
+ await writeFile(task.path, Buffer.from(buffer));
3612
+ task.log("Download successful");
3613
+ };
3614
+ var extractGtfsFiles = async (task) => {
3580
3615
  if (!task.path) {
3581
3616
  throw new Error("No `path` specified in config");
3582
3617
  }
@@ -3628,7 +3663,7 @@ var readFiles = async (task) => {
3628
3663
  }
3629
3664
  }
3630
3665
  };
3631
- var createTables = (db) => {
3666
+ var createGtfsTables = (db) => {
3632
3667
  for (const model of Object.values(models_exports)) {
3633
3668
  if (!model.schema) {
3634
3669
  return;
@@ -3657,6 +3692,13 @@ var createTables = (db) => {
3657
3692
  db.prepare(
3658
3693
  `CREATE TABLE ${model.filenameBase} (${columns.join(", ")});`
3659
3694
  ).run();
3695
+ }
3696
+ };
3697
+ var createGtfsIndexes = (db) => {
3698
+ for (const model of Object.values(models_exports)) {
3699
+ if (!model.schema) {
3700
+ return;
3701
+ }
3660
3702
  for (const column of model.schema.filter((column2) => column2.index)) {
3661
3703
  db.prepare(
3662
3704
  `CREATE INDEX idx_${model.filenameBase}_${column.name} ON ${model.filenameBase} (${column.name});`
@@ -3664,7 +3706,7 @@ var createTables = (db) => {
3664
3706
  }
3665
3707
  }
3666
3708
  };
3667
- var formatLine = (line, model, totalLineCount) => {
3709
+ var formatGtfsLine = (line, model, totalLineCount) => {
3668
3710
  const lineNumber = totalLineCount + 1;
3669
3711
  const formattedLine = {};
3670
3712
  for (const columnSchema of model.schema) {
@@ -3705,85 +3747,23 @@ var formatLine = (line, model, totalLineCount) => {
3705
3747
  );
3706
3748
  }
3707
3749
  }
3708
- const timeColumnNames = [
3709
- "start_time",
3710
- "end_time",
3711
- "arrival_time",
3712
- "departure_time",
3713
- "prior_notice_last_time",
3714
- "prior_notice_start_time",
3715
- "start_pickup_drop_off_window"
3716
- ];
3717
- for (const timeColumnName of timeColumnNames) {
3718
- if (formattedLine[timeColumnName]) {
3719
- const timestampColumnName = timeColumnName.endsWith("time") ? `${timeColumnName}stamp` : `${timeColumnName}_timestamp`;
3720
- formattedLine[timestampColumnName] = calculateSecondsFromMidnight(
3721
- formattedLine[timeColumnName]
3722
- );
3723
- formattedLine[timeColumnName] = padLeadingZeros(
3724
- formattedLine[timeColumnName]
3725
- );
3750
+ for (const [timeColumnName, timestampColumnName] of TIME_COLUMN_PAIRS) {
3751
+ const value = formattedLine[timeColumnName];
3752
+ if (value) {
3753
+ const [seconds, date] = calculateAndCacheDate(value);
3754
+ formattedLine[timestampColumnName] = seconds;
3755
+ formattedLine[timeColumnName] = date;
3726
3756
  }
3727
3757
  }
3728
3758
  return formattedLine;
3729
3759
  };
3730
- var importLines = (task, lines, model, totalLineCount) => {
3731
- const db = openDb({
3732
- sqlitePath: task.sqlitePath
3733
- });
3734
- if (lines.length === 0) {
3735
- return;
3736
- }
3737
- const linesToImportCount = lines.length;
3738
- const columns = model.schema.filter((column) => column.name !== "id");
3739
- const placeholders = [];
3740
- const values = [];
3741
- while (lines.length > 0) {
3742
- const line = lines.pop();
3743
- if (line === void 0) {
3744
- continue;
3745
- }
3746
- placeholders.push(`(${columns.map(() => "?").join(", ")})`);
3747
- values.push(
3748
- ...columns.map((column) => {
3749
- if (task.prefix !== void 0 && column.prefix === true) {
3750
- return `${task.prefix}${line[column.name]}`;
3751
- }
3752
- return line[column.name];
3753
- })
3754
- );
3755
- }
3756
- try {
3757
- db.prepare(
3758
- `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map((column) => column.name).join(", ")}) VALUES ${placeholders.join(",")}`
3759
- ).run(...values);
3760
- } catch (error) {
3761
- if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
3762
- const primaryColumns = model.schema.filter((column) => column.primary);
3763
- task.logWarning(
3764
- `Duplicate values for primary key (${primaryColumns.map((column) => column.name).join(", ")}) found in ${model.filenameBase}.${model.filenameExtension}. Set the \`ignoreDuplicates\` option to true in config.json to ignore this error`
3765
- );
3766
- }
3767
- task.logWarning(
3768
- `Check ${model.filenameBase}.${model.filenameExtension} for invalid data between lines ${totalLineCount - linesToImportCount} and ${totalLineCount}.`
3769
- );
3770
- throw error;
3771
- }
3772
- task.log(
3773
- `Importing - ${model.filenameBase}.${model.filenameExtension} - ${totalLineCount} lines imported\r`,
3774
- true
3775
- );
3776
- };
3777
- var importFiles = (task) => mapSeries(
3760
+ var importGtfsFiles = (db, task) => mapSeries2(
3778
3761
  Object.values(models_exports),
3779
3762
  (model) => new Promise((resolve, reject) => {
3780
- const lines = [];
3781
3763
  let totalLineCount = 0;
3782
- const maxInsertVariables = 32e3;
3764
+ const filename = `${model.filenameBase}.${model.filenameExtension}`;
3783
3765
  if (task.exclude && task.exclude.includes(model.filenameBase)) {
3784
- task.log(
3785
- `Skipping - ${model.filenameBase}.${model.filenameExtension}\r`
3786
- );
3766
+ task.log(`Skipping - ${filename}\r`);
3787
3767
  resolve();
3788
3768
  return;
3789
3769
  }
@@ -3791,22 +3771,51 @@ var importFiles = (task) => mapSeries(
3791
3771
  resolve();
3792
3772
  return;
3793
3773
  }
3794
- const filepath = path2.join(
3795
- task.downloadDir,
3796
- `${model.filenameBase}.${model.filenameExtension}`
3797
- );
3774
+ const filepath = path2.join(task.downloadDir, `${filename}`);
3798
3775
  if (!existsSync2(filepath)) {
3799
3776
  if (!model.nonstandard) {
3800
- task.log(
3801
- `Importing - ${model.filenameBase}.${model.filenameExtension} - No file found\r`
3802
- );
3777
+ task.log(`Importing - ${filename} - No file found\r`);
3803
3778
  }
3804
3779
  resolve();
3805
3780
  return;
3806
3781
  }
3807
- task.log(
3808
- `Importing - ${model.filenameBase}.${model.filenameExtension}\r`
3809
- );
3782
+ task.log(`Importing - ${filename}\r`);
3783
+ const columns = model.schema.filter((column) => column.name !== "id");
3784
+ const placeholder = columns.map(({ name }) => `@${name}`).join(", ");
3785
+ const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map((column) => column.name).join(", ")}) VALUES (${placeholder})`;
3786
+ const insert = db.prepare(prepareStatement);
3787
+ const insertLines = db.transaction((lines) => {
3788
+ for (const [rowNumber, line] of Object.entries(lines)) {
3789
+ try {
3790
+ if (task.prefix === void 0) {
3791
+ insert.run(line);
3792
+ } else {
3793
+ const prefixedLine = Object.fromEntries(
3794
+ Object.entries(
3795
+ line
3796
+ ).map(([columnName, value]) => [
3797
+ columnName,
3798
+ columns.find((col) => col.name === columnName)?.prefix ? `${task.prefix}${value}` : value
3799
+ ])
3800
+ );
3801
+ insert.run(prefixedLine);
3802
+ }
3803
+ } catch (error) {
3804
+ if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
3805
+ const primaryColumns = model.schema.filter(
3806
+ (column) => column.primary
3807
+ );
3808
+ task.logWarning(
3809
+ `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`
3810
+ );
3811
+ }
3812
+ task.logWarning(
3813
+ `Check ${filename} for invalid data on row ${rowNumber}.`
3814
+ );
3815
+ throw error;
3816
+ }
3817
+ }
3818
+ });
3810
3819
  if (model.filenameExtension === "txt") {
3811
3820
  const parser = parse({
3812
3821
  columns: true,
@@ -3815,23 +3824,21 @@ var importFiles = (task) => mapSeries(
3815
3824
  skip_empty_lines: true,
3816
3825
  ...task.csvOptions
3817
3826
  });
3827
+ let lines = [];
3818
3828
  parser.on("readable", () => {
3819
3829
  let record;
3820
3830
  while (record = parser.read()) {
3821
- try {
3822
- totalLineCount += 1;
3823
- lines.push(formatLine(record, model, totalLineCount));
3824
- if (lines.length >= maxInsertVariables / model.schema.length) {
3825
- importLines(task, lines, model, totalLineCount);
3826
- }
3827
- } catch (error) {
3828
- reject(error);
3829
- }
3831
+ totalLineCount += 1;
3832
+ lines.push(formatGtfsLine(record, model, totalLineCount));
3830
3833
  }
3831
3834
  });
3832
3835
  parser.on("end", () => {
3833
3836
  try {
3834
- importLines(task, lines, model, totalLineCount);
3837
+ insertLines(lines);
3838
+ task.log(
3839
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
3840
+ true
3841
+ );
3835
3842
  } catch (error) {
3836
3843
  reject(error);
3837
3844
  }
@@ -3842,14 +3849,19 @@ var importFiles = (task) => mapSeries(
3842
3849
  } else if (model.filenameExtension === "geojson") {
3843
3850
  readFile2(filepath, "utf8").then((data) => {
3844
3851
  if (isValidJSON(data) === false) {
3845
- reject(
3846
- new Error(
3847
- `Invalid JSON in ${model.filenameBase}.${model.filenameExtension}`
3848
- )
3849
- );
3852
+ reject(new Error(`Invalid JSON in ${filename}`));
3850
3853
  }
3851
- const line = formatLine({ geojson: data }, model, totalLineCount);
3852
- importLines(task, [line], model, totalLineCount);
3854
+ totalLineCount += 1;
3855
+ const line = formatGtfsLine(
3856
+ { geojson: data },
3857
+ model,
3858
+ totalLineCount
3859
+ );
3860
+ insertLines([line]);
3861
+ task.log(
3862
+ `Importing - ${filename} - ${totalLineCount} lines imported\r`,
3863
+ true
3864
+ );
3853
3865
  resolve();
3854
3866
  }).catch(reject);
3855
3867
  } else {
@@ -3869,14 +3881,10 @@ async function importGtfs(initialConfig) {
3869
3881
  const db = openDb(config);
3870
3882
  const agencyCount = config.agencies.length;
3871
3883
  log2(
3872
- `Starting GTFS import for ${pluralize(
3873
- "file",
3874
- agencyCount,
3875
- true
3876
- )} using SQLite database at ${config.sqlitePath}`
3884
+ `Starting GTFS import for ${pluralize2("file", agencyCount, true)} using SQLite database at ${config.sqlitePath}`
3877
3885
  );
3878
- createTables(db);
3879
- await mapSeries(config.agencies, async (agency2) => {
3886
+ createGtfsTables(db);
3887
+ await mapSeries2(config.agencies, async (agency2) => {
3880
3888
  try {
3881
3889
  const tempPath = temporaryDirectory();
3882
3890
  const task = {
@@ -3901,40 +3909,48 @@ async function importGtfs(initialConfig) {
3901
3909
  logError: logError2
3902
3910
  };
3903
3911
  if (task.url) {
3904
- await downloadFiles(task);
3912
+ await downloadGtfsFiles(task);
3905
3913
  }
3906
- await readFiles(task);
3907
- await importFiles(task);
3908
- await updateRealtimeData(task);
3914
+ await extractGtfsFiles(task);
3915
+ await importGtfsFiles(db, task);
3916
+ await updateGtfsRealtimeData(task);
3909
3917
  await rm2(tempPath, { recursive: true });
3910
3918
  } catch (error) {
3911
- if (config.ignoreErrors) {
3912
- logError2(error.message);
3913
- } else {
3914
- throw error;
3915
- }
3919
+ handleImportError(error, config, logError2);
3916
3920
  }
3917
3921
  });
3922
+ log2(`Creating DB indexes`);
3923
+ createGtfsIndexes(db);
3918
3924
  log2(
3919
- `Completed GTFS import for ${pluralize("agency", agencyCount, true)}
3925
+ `Completed GTFS import for ${pluralize2("agency", agencyCount, true)}
3920
3926
  `
3921
3927
  );
3922
3928
  } catch (error) {
3923
- if (error?.code === "SQLITE_CANTOPEN") {
3924
- logError2(
3925
- `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
3926
- );
3927
- }
3929
+ handleDatabaseError(error, config, logError2);
3930
+ }
3931
+ }
3932
+ function handleImportError(error, config, logError2) {
3933
+ if (config.ignoreErrors) {
3934
+ logError2(error.message);
3935
+ } else {
3928
3936
  throw error;
3929
3937
  }
3930
3938
  }
3939
+ function handleDatabaseError(error, config, logError2) {
3940
+ if (error?.code === "SQLITE_CANTOPEN") {
3941
+ logError2(
3942
+ `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
3943
+ );
3944
+ }
3945
+ throw error;
3946
+ }
3931
3947
 
3932
3948
  // src/lib/export.ts
3933
3949
  import { without, compact as compact2 } from "lodash-es";
3934
- import pluralize2 from "pluralize";
3950
+ import pluralize3 from "pluralize";
3935
3951
  import { stringify } from "csv-stringify";
3936
3952
  import sqlString3 from "sqlstring-sqlite";
3937
- import mapSeries2 from "promise-map-series";
3953
+ import mapSeries3 from "promise-map-series";
3938
3954
  import untildify4 from "untildify";
3939
3955
 
3940
3956
  // src/lib/advancedQuery.ts