gtfs 4.17.6 → 4.18.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.
@@ -8,10 +8,10 @@ import PrettyError from "pretty-error";
8
8
  // src/lib/file-utils.ts
9
9
  import path from "path";
10
10
  import { existsSync } from "fs";
11
+ import { homedir } from "os";
11
12
  import { mkdir, readFile, rm } from "fs/promises";
12
13
  import { omit, snakeCase } from "lodash-es";
13
14
  import sanitize from "sanitize-filename";
14
- import untildify from "untildify";
15
15
  import StreamZip from "node-stream-zip";
16
16
 
17
17
  // src/lib/log-utils.ts
@@ -65,6 +65,7 @@ function formatError(error) {
65
65
  }
66
66
 
67
67
  // src/lib/file-utils.ts
68
+ var homeDirectory = homedir();
68
69
  async function getConfig(argv2) {
69
70
  let config;
70
71
  let data;
@@ -101,17 +102,17 @@ async function getConfig(argv2) {
101
102
  throw error;
102
103
  }
103
104
  }
105
+ function untildify(pathWithTilde) {
106
+ return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
107
+ }
104
108
 
105
109
  // src/lib/import-gtfs.ts
106
110
  import path2 from "path";
107
111
  import { createReadStream, existsSync as existsSync2, lstatSync } from "fs";
108
112
  import { cp, readdir, rename, readFile as readFile2, rm as rm2, writeFile } from "fs/promises";
109
113
  import { parse } from "csv-parse";
110
- import pluralize2 from "pluralize";
111
114
  import stripBomStream from "strip-bom-stream";
112
115
  import { temporaryDirectory } from "tempy";
113
- import Timer from "timer-machine";
114
- import untildify3 from "untildify";
115
116
  import mapSeries2 from "promise-map-series";
116
117
 
117
118
  // src/models/gtfs-realtime/trip-updates.ts
@@ -582,10 +583,9 @@ var serviceAlertInformedEntities = {
582
583
 
583
584
  // src/lib/db.ts
584
585
  import Database from "better-sqlite3";
585
- import untildify2 from "untildify";
586
586
  var dbs = {};
587
587
  function setupDb(sqlitePath) {
588
- const db = new Database(untildify2(sqlitePath));
588
+ const db = new Database(untildify(sqlitePath));
589
589
  db.pragma("journal_mode = OFF");
590
590
  db.pragma("synchronous = OFF");
591
591
  db.pragma("temp_store = MEMORY");
@@ -632,7 +632,6 @@ import {
632
632
  import { feature, featureCollection } from "@turf/helpers";
633
633
 
634
634
  // src/lib/import-gtfs-realtime.ts
635
- import pluralize from "pluralize";
636
635
  import GtfsRealtimeBindings from "gtfs-realtime-bindings";
637
636
  import mapSeries from "promise-map-series";
638
637
  import { get } from "lodash-es";
@@ -659,7 +658,8 @@ function setDefaultConfig(initialConfig) {
659
658
  ignoreDuplicates: false,
660
659
  ignoreErrors: false,
661
660
  gtfsRealtimeExpirationSeconds: 0,
662
- verbose: true
661
+ verbose: true,
662
+ downloadTimeout: 3e4
663
663
  };
664
664
  return {
665
665
  ...defaults,
@@ -678,58 +678,14 @@ function applyPrefixToValue(value, columnShouldBePrefixed, prefix) {
678
678
  }
679
679
  return `${prefix}${value}`;
680
680
  }
681
+ function pluralize(singularWord, pluralWord, count) {
682
+ return count === 1 ? singularWord : pluralWord;
683
+ }
681
684
 
682
685
  // src/lib/import-gtfs-realtime.ts
683
- async function fetchGtfsRealtimeData(urlConfig, task) {
684
- task.log(`Downloading GTFS-Realtime from ${urlConfig.url}`);
685
- const response = await fetch(urlConfig.url, {
686
- method: "GET",
687
- headers: {
688
- ...urlConfig.headers ?? {},
689
- "Accept-Encoding": "gzip"
690
- },
691
- signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
692
- });
693
- if (response.status !== 200) {
694
- task.logWarning(
695
- `Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
696
- );
697
- return null;
698
- }
699
- const buffer = await response.arrayBuffer();
700
- const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
701
- new Uint8Array(buffer)
702
- );
703
- return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
704
- enums: String,
705
- longs: String,
706
- bytes: String,
707
- defaults: false,
708
- arrays: true,
709
- objects: true,
710
- oneofs: true
711
- });
712
- }
713
- function removeExpiredRealtimeData(config) {
714
- const db = openDb(config);
715
- log(config)(`Removing expired GTFS-Realtime data`);
716
- db.prepare(
717
- `DELETE FROM vehicle_positions WHERE expiration_timestamp <= strftime('%s','now')`
718
- ).run();
719
- db.prepare(
720
- `DELETE FROM trip_updates WHERE expiration_timestamp <= strftime('%s','now')`
721
- ).run();
722
- db.prepare(
723
- `DELETE FROM stop_time_updates WHERE expiration_timestamp <= strftime('%s','now')`
724
- ).run();
725
- db.prepare(
726
- `DELETE FROM service_alerts WHERE expiration_timestamp <= strftime('%s','now')`
727
- ).run();
728
- db.prepare(
729
- `DELETE FROM service_alert_informed_entities WHERE expiration_timestamp <= strftime('%s','now')`
730
- ).run();
731
- log(config)(`Removed expired GTFS-Realtime data\r`, true);
732
- }
686
+ var BATCH_SIZE = 1e3;
687
+ var MAX_RETRIES = 3;
688
+ var RETRY_DELAY = 1e3;
733
689
  function prepareRealtimeFieldValue(entity, column, task) {
734
690
  if (column.name === "created_timestamp") {
735
691
  return task.currentTimestamp;
@@ -746,163 +702,265 @@ function prepareRealtimeFieldValue(entity, column, task) {
746
702
  );
747
703
  return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
748
704
  }
749
- async function processRealtimeAlerts(db, gtfsRealtimeData, task) {
750
- const alertStmt = db.prepare(
751
- `REPLACE INTO ${serviceAlerts.filenameBase} (${serviceAlerts.schema.map((column) => column.name).join(
752
- ", "
753
- )}) VALUES (${serviceAlerts.schema.map(() => "?").join(", ")})`
754
- );
755
- const informedEntityStmt = db.prepare(
756
- `REPLACE INTO ${serviceAlertInformedEntities.filenameBase} (${serviceAlertInformedEntities.schema.map((column) => column.name).join(
757
- ", "
758
- )}) VALUES (${serviceAlertInformedEntities.schema.map(() => "?").join(", ")})`
705
+ function createPreparedStatement(db, model) {
706
+ const columns = model.schema.map((column) => column.name);
707
+ const placeholders = model.schema.map(() => "?").join(", ");
708
+ return db.prepare(
709
+ `REPLACE INTO ${model.filenameBase} (${columns.join(", ")}) VALUES (${placeholders})`
759
710
  );
760
- let totalLineCount = 0;
761
- db.transaction(() => {
762
- for (const entity of gtfsRealtimeData.entity) {
763
- const fieldValues = serviceAlerts.schema.map(
764
- (column) => prepareRealtimeFieldValue(entity, column, task)
711
+ }
712
+ async function processBatch(items, batchSize, processor) {
713
+ let totalRecordCount = 0;
714
+ let totalErrorCount = 0;
715
+ for (let i = 0; i < items.length; i += batchSize) {
716
+ const batch = items.slice(i, i + batchSize);
717
+ try {
718
+ const result = await processor(batch);
719
+ totalRecordCount += result.recordCount;
720
+ totalErrorCount += result.errorCount;
721
+ } catch (error) {
722
+ const errorMessage = error instanceof Error ? error.message : String(error);
723
+ totalErrorCount += batch.length;
724
+ console.error(`Batch processing error: ${errorMessage}`);
725
+ }
726
+ }
727
+ return { recordCount: totalRecordCount, errorCount: totalErrorCount };
728
+ }
729
+ async function fetchGtfsRealtimeData(type, task) {
730
+ const urlConfig = getUrlConfig(type, task);
731
+ if (!urlConfig) {
732
+ return null;
733
+ }
734
+ task.log(`Importing - GTFS-Realtime from ${urlConfig.url}`);
735
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
736
+ try {
737
+ const response = await fetch(urlConfig.url, {
738
+ method: "GET",
739
+ headers: {
740
+ ...urlConfig.headers ?? {},
741
+ "Accept-Encoding": "gzip"
742
+ },
743
+ signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
744
+ });
745
+ if (response.status !== 200) {
746
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
747
+ }
748
+ const buffer = await response.arrayBuffer();
749
+ const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
750
+ new Uint8Array(buffer)
765
751
  );
766
- try {
767
- alertStmt.run(fieldValues);
768
- if (entity.alert.informedEntity?.length) {
769
- const informedEntities = entity.alert.informedEntity.map(
770
- (informedEntity) => {
752
+ const feedMessage = GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
753
+ enums: String,
754
+ longs: String,
755
+ bytes: String,
756
+ defaults: false,
757
+ arrays: true,
758
+ objects: true,
759
+ oneofs: true
760
+ });
761
+ return feedMessage;
762
+ } catch (error) {
763
+ const errorMessage = error instanceof Error ? error.message : String(error);
764
+ if (attempt === MAX_RETRIES) {
765
+ if (task.ignoreErrors) {
766
+ task.logError(
767
+ `Failed to fetch ${type} after ${MAX_RETRIES} attempts: ${errorMessage}`
768
+ );
769
+ return null;
770
+ }
771
+ throw error;
772
+ }
773
+ task.logWarning(`Attempt ${attempt} failed for ${type}: ${errorMessage}`);
774
+ await new Promise(
775
+ (resolve) => setTimeout(resolve, RETRY_DELAY * attempt)
776
+ );
777
+ }
778
+ }
779
+ return null;
780
+ }
781
+ function getUrlConfig(type, task) {
782
+ switch (type) {
783
+ case "alerts":
784
+ return task.realtimeAlerts;
785
+ case "tripupdates":
786
+ return task.realtimeTripUpdates;
787
+ case "vehiclepositions":
788
+ return task.realtimeVehiclePositions;
789
+ default:
790
+ return void 0;
791
+ }
792
+ }
793
+ function createServiceAlertsProcessor(db, task) {
794
+ const alertStmt = createPreparedStatement(db, serviceAlerts);
795
+ const informedEntityStmt = createPreparedStatement(
796
+ db,
797
+ serviceAlertInformedEntities
798
+ );
799
+ return async (batch) => {
800
+ let recordCount = 0;
801
+ let errorCount = 0;
802
+ db.transaction(() => {
803
+ for (const entity of batch) {
804
+ try {
805
+ const alertValues = serviceAlerts.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
806
+ alertStmt.run(alertValues);
807
+ recordCount++;
808
+ if (entity.alert?.informedEntity?.length) {
809
+ for (const informedEntity of entity.alert.informedEntity) {
771
810
  informedEntity.parent = entity;
772
- return serviceAlertInformedEntities.schema.map(
811
+ const entityValues = serviceAlertInformedEntities.schema.map(
773
812
  (column) => prepareRealtimeFieldValue(informedEntity, column, task)
774
813
  );
814
+ informedEntityStmt.run(entityValues);
815
+ recordCount++;
775
816
  }
776
- );
777
- for (const values of informedEntities) {
778
- informedEntityStmt.run(values);
779
817
  }
818
+ } catch (error) {
819
+ const errorMessage = error instanceof Error ? error.message : String(error);
820
+ errorCount++;
821
+ task.logWarning(`Alert processing error: ${errorMessage}`);
780
822
  }
781
- totalLineCount++;
782
- } catch (error) {
783
- task.logWarning(`Import error: ${error.message}`);
784
823
  }
785
- }
786
- task.log(
787
- `Importing - GTFS-Realtime service alerts - ${totalLineCount} entries imported\r`,
788
- true
789
- );
790
- })();
824
+ })();
825
+ return { recordCount, errorCount };
826
+ };
791
827
  }
792
- async function processRealtimeTripUpdates(db, gtfsRealtimeData, task) {
793
- let totalLineCount = 0;
794
- const tripUpdateStmt = db.prepare(
795
- `REPLACE INTO ${tripUpdates.filenameBase} (${tripUpdates.schema.map((column) => column.name).join(
796
- ", "
797
- )}) VALUES (${tripUpdates.schema.map(() => "?").join(", ")})`
828
+ function createTripUpdatesProcessor(db, task) {
829
+ const tripUpdateStmt = createPreparedStatement(
830
+ db,
831
+ tripUpdates
798
832
  );
799
- const stopTimeStmt = db.prepare(
800
- `REPLACE INTO ${stopTimeUpdates.filenameBase} (${stopTimeUpdates.schema.map((column) => column.name).join(
801
- ", "
802
- )}) VALUES (${stopTimeUpdates.schema.map(() => "?").join(", ")})`
833
+ const stopTimeStmt = createPreparedStatement(
834
+ db,
835
+ stopTimeUpdates
803
836
  );
804
- db.transaction(() => {
805
- for (const entity of gtfsRealtimeData.entity) {
806
- try {
807
- const fieldValues = tripUpdates.schema.map(
808
- (column) => prepareRealtimeFieldValue(entity, column, task)
809
- );
810
- tripUpdateStmt.run(fieldValues);
811
- for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
812
- stopTimeUpdate.parent = entity;
813
- const values = stopTimeUpdates.schema.map(
814
- (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
815
- );
816
- stopTimeStmt.run(values);
837
+ return async (batch) => {
838
+ let recordCount = 0;
839
+ let errorCount = 0;
840
+ db.transaction(() => {
841
+ for (const entity of batch) {
842
+ try {
843
+ const tripUpdateValues = tripUpdates.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
844
+ tripUpdateStmt.run(tripUpdateValues);
845
+ recordCount++;
846
+ if (entity.tripUpdate?.stopTimeUpdate?.length) {
847
+ for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
848
+ stopTimeUpdate.parent = entity;
849
+ const stopTimeValues = stopTimeUpdates.schema.map(
850
+ (column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
851
+ );
852
+ stopTimeStmt.run(stopTimeValues);
853
+ recordCount++;
854
+ }
855
+ }
856
+ } catch (error) {
857
+ const errorMessage = error instanceof Error ? error.message : String(error);
858
+ errorCount++;
859
+ task.logWarning(`Trip update processing error: ${errorMessage}`);
817
860
  }
818
- totalLineCount++;
819
- } catch (error) {
820
- task.logWarning(`Import error: ${error.message}`);
821
861
  }
822
- }
823
- task.log(
824
- `Importing - GTFS-Realtime trip updates - ${totalLineCount} entries imported\r`,
825
- true
826
- );
827
- })();
862
+ })();
863
+ return { recordCount, errorCount };
864
+ };
828
865
  }
829
- async function processRealtimeVehiclePositions(db, gtfsRealtimeData, task) {
830
- let totalLineCount = 0;
831
- const vehiclePositionStmt = db.prepare(
832
- `REPLACE INTO ${vehiclePositions.filenameBase} (${vehiclePositions.schema.map((column) => column.name).join(
833
- ", "
834
- )}) VALUES (${vehiclePositions.schema.map(() => "?").join(", ")})`
866
+ function createVehiclePositionsProcessor(db, task) {
867
+ const vehiclePositionStmt = createPreparedStatement(
868
+ db,
869
+ vehiclePositions
835
870
  );
836
- db.transaction(() => {
837
- for (const entity of gtfsRealtimeData.entity) {
838
- try {
839
- const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
840
- vehiclePositionStmt.run(fieldValues);
841
- totalLineCount++;
842
- } catch (error) {
843
- task.logWarning(`Import error: ${error.message}`);
871
+ return async (batch) => {
872
+ let recordCount = 0;
873
+ let errorCount = 0;
874
+ db.transaction(() => {
875
+ for (const entity of batch) {
876
+ try {
877
+ const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
878
+ vehiclePositionStmt.run(fieldValues);
879
+ recordCount++;
880
+ } catch (error) {
881
+ const errorMessage = error instanceof Error ? error.message : String(error);
882
+ errorCount++;
883
+ task.logWarning(`Vehicle position processing error: ${errorMessage}`);
884
+ }
844
885
  }
886
+ })();
887
+ return { recordCount, errorCount };
888
+ };
889
+ }
890
+ function removeExpiredRealtimeData(config) {
891
+ const db = openDb(config);
892
+ log(config)(`Removing expired GTFS-Realtime data`);
893
+ db.transaction(() => {
894
+ const tables = [
895
+ "vehicle_positions",
896
+ "trip_updates",
897
+ "stop_time_updates",
898
+ "service_alerts",
899
+ "service_alert_informed_entities"
900
+ ];
901
+ for (const table of tables) {
902
+ db.prepare(
903
+ `DELETE FROM ${table} WHERE expiration_timestamp <= strftime('%s','now')`
904
+ ).run();
845
905
  }
846
- task.log(
847
- `Importing - GTFS-Realtime vehicle positions - ${totalLineCount} entries imported\r`,
848
- true
849
- );
850
906
  })();
907
+ log(config)(`Removed expired GTFS-Realtime data\r`, true);
851
908
  }
852
909
  async function updateGtfsRealtimeData(task) {
853
- if (task.realtimeAlerts === void 0 && task.realtimeTripUpdates === void 0 && task.realtimeVehiclePositions === void 0) {
910
+ if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) {
854
911
  return;
855
912
  }
913
+ const [alertsData, tripUpdatesData, vehiclePositionsData] = await Promise.all(
914
+ [
915
+ task.realtimeAlerts?.url ? fetchGtfsRealtimeData("alerts", task) : null,
916
+ task.realtimeTripUpdates?.url ? fetchGtfsRealtimeData("tripupdates", task) : null,
917
+ task.realtimeVehiclePositions?.url ? fetchGtfsRealtimeData("vehiclepositions", task) : null
918
+ ]
919
+ );
856
920
  const db = openDb({ sqlitePath: task.sqlitePath });
857
- if (task.realtimeAlerts?.url) {
858
- try {
859
- const alertsData = await fetchGtfsRealtimeData(task.realtimeAlerts, task);
860
- if (alertsData?.entity) {
861
- await processRealtimeAlerts(db, alertsData, task);
862
- }
863
- } catch (error) {
864
- if (task.ignoreErrors) {
865
- task.logError(error.message);
866
- } else {
867
- throw error;
868
- }
869
- }
921
+ const recordCounts = {
922
+ alerts: 0,
923
+ tripupdates: 0,
924
+ vehiclepositions: 0
925
+ };
926
+ const processingPromises = [];
927
+ if (alertsData?.entity?.length) {
928
+ processingPromises.push(
929
+ processBatch(
930
+ alertsData.entity,
931
+ BATCH_SIZE,
932
+ createServiceAlertsProcessor(db, task)
933
+ ).then((result) => {
934
+ recordCounts.alerts = result.recordCount;
935
+ })
936
+ );
870
937
  }
871
- if (task.realtimeTripUpdates?.url) {
872
- try {
873
- const tripUpdatesData = await fetchGtfsRealtimeData(
874
- task.realtimeTripUpdates,
875
- task
876
- );
877
- if (tripUpdatesData?.entity) {
878
- await processRealtimeTripUpdates(db, tripUpdatesData, task);
879
- }
880
- } catch (error) {
881
- if (task.ignoreErrors) {
882
- task.logError(error.message);
883
- } else {
884
- throw error;
885
- }
886
- }
938
+ if (tripUpdatesData?.entity?.length) {
939
+ processingPromises.push(
940
+ processBatch(
941
+ tripUpdatesData.entity,
942
+ BATCH_SIZE,
943
+ createTripUpdatesProcessor(db, task)
944
+ ).then((result) => {
945
+ recordCounts.tripupdates = result.recordCount;
946
+ })
947
+ );
887
948
  }
888
- if (task.realtimeVehiclePositions?.url) {
889
- try {
890
- const vehiclePositionsData = await fetchGtfsRealtimeData(
891
- task.realtimeVehiclePositions,
892
- task
893
- );
894
- if (vehiclePositionsData?.entity) {
895
- await processRealtimeVehiclePositions(db, vehiclePositionsData, task);
896
- }
897
- } catch (error) {
898
- if (task.ignoreErrors) {
899
- task.logError(error.message);
900
- } else {
901
- throw error;
902
- }
903
- }
949
+ if (vehiclePositionsData?.entity?.length) {
950
+ processingPromises.push(
951
+ processBatch(
952
+ vehiclePositionsData.entity,
953
+ BATCH_SIZE,
954
+ createVehiclePositionsProcessor(db, task)
955
+ ).then((result) => {
956
+ recordCounts.vehiclepositions = result.recordCount;
957
+ })
958
+ );
904
959
  }
905
- task.log(`GTFS-Realtime data import complete`);
960
+ await Promise.all(processingPromises);
961
+ task.log(
962
+ `GTFS-Realtime import complete: ${recordCounts.alerts} alerts, ${recordCounts.tripupdates} trip updates, ${recordCounts.vehiclepositions} vehicle positions`
963
+ );
906
964
  }
907
965
  async function updateGtfsRealtime(initialConfig) {
908
966
  const config = setDefaultConfig(initialConfig);
@@ -912,9 +970,9 @@ async function updateGtfsRealtime(initialConfig) {
912
970
  const agencyCount = config.agencies.length;
913
971
  log(config)(
914
972
  `Starting GTFS-Realtime refresh for ${pluralize(
973
+ "agency",
915
974
  "agencies",
916
- agencyCount,
917
- true
975
+ agencyCount
918
976
  )} using SQLite database at ${config.sqlitePath}`
919
977
  );
920
978
  removeExpiredRealtimeData(config);
@@ -936,8 +994,9 @@ async function updateGtfsRealtime(initialConfig) {
936
994
  };
937
995
  await updateGtfsRealtimeData(task);
938
996
  } catch (error) {
997
+ const errorMessage = error instanceof Error ? error.message : String(error);
939
998
  if (config.ignoreErrors) {
940
- logError(config)(error.message);
999
+ logError(config)(errorMessage);
941
1000
  } else {
942
1001
  throw error;
943
1002
  }
@@ -945,14 +1004,14 @@ async function updateGtfsRealtime(initialConfig) {
945
1004
  });
946
1005
  log(config)(
947
1006
  `Completed GTFS-Realtime refresh for ${pluralize(
1007
+ "agency",
948
1008
  "agencies",
949
- agencyCount,
950
- true
1009
+ agencyCount
951
1010
  )}
952
1011
  `
953
1012
  );
954
1013
  } catch (error) {
955
- if (error?.code === "SQLITE_CANTOPEN") {
1014
+ if (error.code === "SQLITE_CANTOPEN") {
956
1015
  logError(config)(
957
1016
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
958
1017
  );
@@ -965,11 +1024,9 @@ async function updateGtfsRealtime(initialConfig) {
965
1024
  import path3 from "path";
966
1025
  import { writeFile as writeFile2 } from "fs/promises";
967
1026
  import { without, compact as compact2 } from "lodash-es";
968
- import pluralize3 from "pluralize";
969
1027
  import { stringify } from "csv-stringify";
970
1028
  import sqlString2 from "sqlstring-sqlite";
971
1029
  import mapSeries3 from "promise-map-series";
972
- import untildify4 from "untildify";
973
1030
 
974
1031
  // src/lib/advancedQuery.ts
975
1032
  import sqlString3 from "sqlstring-sqlite";