gtfs 4.15.6 → 4.15.8

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.
@@ -18,60 +18,13 @@ import { omit, snakeCase } from "lodash-es";
18
18
  import sanitize from "sanitize-filename";
19
19
  import untildify from "untildify";
20
20
  import StreamZip from "node-stream-zip";
21
- async function getConfig(argv2) {
22
- let config;
23
- let data;
24
- try {
25
- if (argv2.configPath) {
26
- const configPath = path.resolve(untildify(argv2.configPath));
27
- data = await readFile(configPath, "utf8");
28
- config = Object.assign(JSON.parse(data), argv2);
29
- } else if (argv2.gtfsPath || argv2.gtfsUrl || argv2.sqlitePath) {
30
- const agencies = [
31
- ...argv2.gtfsPath ? [{ path: argv2.gtfsPath }] : [],
32
- ...argv2.gtfsUrl ? [{ url: argv2.gtfsUrl }] : []
33
- ];
34
- config = {
35
- agencies,
36
- ...omit(argv2, ["path", "url"])
37
- };
38
- } else if (existsSync(path.resolve("./config.json"))) {
39
- data = await readFile(path.resolve("./config.json"), "utf8");
40
- config = Object.assign(JSON.parse(data), argv2);
41
- console.log("Using configuration from ./config.json");
42
- } else {
43
- throw new Error(
44
- "Cannot find configuration file. Use config-sample.json as a starting point, pass --configPath option."
45
- );
46
- }
47
- return config;
48
- } catch (error) {
49
- if (error instanceof SyntaxError) {
50
- throw new Error(
51
- `Cannot parse configuration file. Check to ensure that it is valid JSON. Error: ${error.message}`
52
- );
53
- }
54
- throw error;
55
- }
56
- }
57
- async function unzip(zipfilePath, exportPath) {
58
- try {
59
- const zip = new StreamZip.async({ file: zipfilePath });
60
- await zip.extract(null, exportPath);
61
- await zip.close();
62
- } catch (error) {
63
- throw new Error(
64
- `Failed to extract zip file: ${error instanceof Error ? error.message : "Unknown error"}`
65
- );
66
- }
67
- }
68
21
 
69
22
  // src/lib/log-utils.ts
70
23
  import { clearLine, cursorTo } from "node:readline";
71
24
  import { noop } from "lodash-es";
72
25
  import * as colors from "yoctocolors";
73
26
  function log(config) {
74
- if (!config.verbose) {
27
+ if (config.verbose === false) {
75
28
  return noop;
76
29
  }
77
30
  if (config.logFunction) {
@@ -116,6 +69,55 @@ function formatError(error) {
116
69
  return colors.red(`${colors.underline("Error")}: ${cleanMessage}`);
117
70
  }
118
71
 
72
+ // src/lib/file-utils.ts
73
+ async function getConfig(argv2) {
74
+ let config;
75
+ let data;
76
+ try {
77
+ if (argv2.configPath) {
78
+ const configPath = path.resolve(untildify(argv2.configPath));
79
+ data = await readFile(configPath, "utf8");
80
+ config = Object.assign(JSON.parse(data), argv2);
81
+ } else if (argv2.gtfsPath || argv2.gtfsUrl || argv2.sqlitePath) {
82
+ const agencies = [
83
+ ...argv2.gtfsPath ? [{ path: argv2.gtfsPath }] : [],
84
+ ...argv2.gtfsUrl ? [{ url: argv2.gtfsUrl }] : []
85
+ ];
86
+ config = {
87
+ agencies,
88
+ ...omit(argv2, ["path", "url"])
89
+ };
90
+ } else if (existsSync(path.resolve("./config.json"))) {
91
+ data = await readFile(path.resolve("./config.json"), "utf8");
92
+ config = Object.assign(JSON.parse(data), argv2);
93
+ log(config)("Using configuration from ./config.json");
94
+ } else {
95
+ throw new Error(
96
+ "Cannot find configuration file. Use config-sample.json as a starting point, pass --configPath option."
97
+ );
98
+ }
99
+ return config;
100
+ } catch (error) {
101
+ if (error instanceof SyntaxError) {
102
+ throw new Error(
103
+ `Cannot parse configuration file. Check to ensure that it is valid JSON. Error: ${error.message}`
104
+ );
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+ async function unzip(zipfilePath, exportPath) {
110
+ try {
111
+ const zip = new StreamZip.async({ file: zipfilePath });
112
+ await zip.extract(null, exportPath);
113
+ await zip.close();
114
+ } catch (error) {
115
+ throw new Error(
116
+ `Failed to extract zip file: ${error instanceof Error ? error.message : "Unknown error"}`
117
+ );
118
+ }
119
+ }
120
+
119
121
  // src/lib/import-gtfs.ts
120
122
  import path2 from "node:path";
121
123
  import { createReadStream, existsSync as existsSync2, lstatSync } from "node:fs";
@@ -264,7 +266,8 @@ var attributions = {
264
266
  {
265
267
  name: "attribution_id",
266
268
  type: "text",
267
- prefix: true
269
+ prefix: true,
270
+ primary: true
268
271
  },
269
272
  {
270
273
  name: "agency_id",
@@ -697,26 +700,31 @@ var fareRules = {
697
700
  name: "fare_id",
698
701
  type: "text",
699
702
  required: true,
703
+ primary: true,
700
704
  prefix: true
701
705
  },
702
706
  {
703
707
  name: "route_id",
704
708
  type: "text",
709
+ primary: true,
705
710
  prefix: true
706
711
  },
707
712
  {
708
713
  name: "origin_id",
709
714
  type: "text",
715
+ primary: true,
710
716
  prefix: true
711
717
  },
712
718
  {
713
719
  name: "destination_id",
714
720
  type: "text",
721
+ primary: true,
715
722
  prefix: true
716
723
  },
717
724
  {
718
725
  name: "contains_id",
719
726
  type: "text",
727
+ primary: true,
720
728
  prefix: true
721
729
  }
722
730
  ]
@@ -923,14 +931,16 @@ var locationGroupStops = {
923
931
  type: "text",
924
932
  prefix: true,
925
933
  index: true,
926
- required: true
934
+ required: true,
935
+ primary: true
927
936
  },
928
937
  {
929
938
  name: "stop_id",
930
939
  type: "text",
931
940
  required: true,
932
941
  prefix: true,
933
- index: true
942
+ index: true,
943
+ primary: true
934
944
  }
935
945
  ]
936
946
  };
@@ -1191,12 +1201,14 @@ var stopAreas = {
1191
1201
  name: "area_id",
1192
1202
  type: "text",
1193
1203
  required: true,
1204
+ primary: true,
1194
1205
  prefix: true
1195
1206
  },
1196
1207
  {
1197
1208
  name: "stop_id",
1198
1209
  type: "text",
1199
1210
  required: true,
1211
+ primary: true,
1200
1212
  prefix: true
1201
1213
  }
1202
1214
  ]
@@ -1420,16 +1432,19 @@ var timeframes = {
1420
1432
  },
1421
1433
  {
1422
1434
  name: "start_time",
1423
- type: "text"
1435
+ type: "text",
1436
+ primary: true
1424
1437
  },
1425
1438
  {
1426
1439
  name: "end_time",
1427
- type: "text"
1440
+ type: "text",
1441
+ primary: true
1428
1442
  },
1429
1443
  {
1430
1444
  name: "service_id",
1431
1445
  type: "text",
1432
1446
  required: true,
1447
+ primary: true,
1433
1448
  index: true,
1434
1449
  prefix: true
1435
1450
  }
@@ -1616,21 +1631,19 @@ var timetables = {
1616
1631
  filenameExtension: "txt",
1617
1632
  nonstandard: true,
1618
1633
  schema: [
1619
- {
1620
- name: "id",
1621
- type: "integer",
1622
- primary: true,
1623
- prefix: true
1624
- },
1625
1634
  {
1626
1635
  name: "timetable_id",
1627
1636
  type: "text",
1628
- prefix: true
1637
+ prefix: true,
1638
+ required: true,
1639
+ primary: true
1629
1640
  },
1630
1641
  {
1631
1642
  name: "route_id",
1632
1643
  type: "text",
1633
- prefix: true
1644
+ prefix: true,
1645
+ required: true,
1646
+ primary: true
1634
1647
  },
1635
1648
  {
1636
1649
  name: "direction_id",
@@ -1764,6 +1777,7 @@ var timetablePages = {
1764
1777
  name: "timetable_page_id",
1765
1778
  type: "text",
1766
1779
  primary: true,
1780
+ required: true,
1767
1781
  prefix: true
1768
1782
  },
1769
1783
  {
@@ -1783,28 +1797,28 @@ var timetableStopOrder = {
1783
1797
  filenameExtension: "txt",
1784
1798
  nonstandard: true,
1785
1799
  schema: [
1786
- {
1787
- name: "id",
1788
- type: "integer",
1789
- primary: true,
1790
- prefix: true
1791
- },
1792
1800
  {
1793
1801
  name: "timetable_id",
1794
1802
  type: "text",
1795
1803
  index: true,
1796
- prefix: true
1804
+ prefix: true,
1805
+ required: true,
1806
+ primary: true
1797
1807
  },
1798
1808
  {
1799
1809
  name: "stop_id",
1800
1810
  type: "text",
1801
- prefix: true
1811
+ prefix: true,
1812
+ required: true,
1813
+ primary: true
1802
1814
  },
1803
1815
  {
1804
1816
  name: "stop_sequence",
1805
1817
  type: "integer",
1806
1818
  min: 0,
1807
- index: true
1819
+ index: true,
1820
+ required: true,
1821
+ primary: true
1808
1822
  }
1809
1823
  ]
1810
1824
  };
@@ -1819,7 +1833,8 @@ var timetableNotes = {
1819
1833
  name: "note_id",
1820
1834
  type: "text",
1821
1835
  primary: true,
1822
- prefix: true
1836
+ prefix: true,
1837
+ required: true
1823
1838
  },
1824
1839
  {
1825
1840
  name: "symbol",
@@ -1828,7 +1843,8 @@ var timetableNotes = {
1828
1843
  {
1829
1844
  name: "note",
1830
1845
  type: "text",
1831
- nocase: true
1846
+ nocase: true,
1847
+ required: true
1832
1848
  }
1833
1849
  ]
1834
1850
  };
@@ -1842,37 +1858,39 @@ var timetableNotesReferences = {
1842
1858
  {
1843
1859
  name: "note_id",
1844
1860
  type: "text",
1845
- prefix: true
1861
+ prefix: true,
1862
+ required: true,
1863
+ primary: true
1846
1864
  },
1847
1865
  {
1848
1866
  name: "timetable_id",
1849
1867
  type: "text",
1850
- index: true,
1851
- prefix: true
1868
+ prefix: true,
1869
+ primary: true
1852
1870
  },
1853
1871
  {
1854
1872
  name: "route_id",
1855
1873
  type: "text",
1856
- index: true,
1857
- prefix: true
1874
+ prefix: true,
1875
+ primary: true
1858
1876
  },
1859
1877
  {
1860
1878
  name: "trip_id",
1861
1879
  type: "text",
1862
- index: true,
1863
- prefix: true
1880
+ prefix: true,
1881
+ primary: true
1864
1882
  },
1865
1883
  {
1866
1884
  name: "stop_id",
1867
1885
  type: "text",
1868
- index: true,
1869
- prefix: true
1886
+ prefix: true,
1887
+ primary: true
1870
1888
  },
1871
1889
  {
1872
1890
  name: "stop_sequence",
1873
1891
  type: "integer",
1874
1892
  min: 0,
1875
- index: true
1893
+ primary: true
1876
1894
  },
1877
1895
  {
1878
1896
  name: "show_on_stoptime",
@@ -2506,7 +2524,7 @@ var tripUpdates = {
2506
2524
  extension: "gtfs-realtime",
2507
2525
  schema: [
2508
2526
  {
2509
- name: "update_id",
2527
+ name: "id",
2510
2528
  type: "text",
2511
2529
  required: true,
2512
2530
  primary: true,
@@ -2670,7 +2688,7 @@ var vehiclePositions = {
2670
2688
  extension: "gtfs-realtime",
2671
2689
  schema: [
2672
2690
  {
2673
- name: "update_id",
2691
+ name: "id",
2674
2692
  type: "text",
2675
2693
  required: true,
2676
2694
  primary: true,
@@ -2909,17 +2927,12 @@ var deadheadTimes = {
2909
2927
  nonstandard: true,
2910
2928
  extension: "ods",
2911
2929
  schema: [
2912
- {
2913
- name: "id",
2914
- type: "integer",
2915
- primary: true,
2916
- prefix: true
2917
- },
2918
2930
  {
2919
2931
  name: "deadhead_id",
2920
2932
  type: "text",
2921
2933
  required: true,
2922
2934
  index: true,
2935
+ primary: true,
2923
2936
  prefix: true
2924
2937
  },
2925
2938
  {
@@ -2956,6 +2969,7 @@ var deadheadTimes = {
2956
2969
  name: "location_sequence",
2957
2970
  type: "integer",
2958
2971
  required: true,
2972
+ primary: true,
2959
2973
  min: 0,
2960
2974
  index: true
2961
2975
  },
@@ -3305,7 +3319,8 @@ function setDefaultConfig(initialConfig) {
3305
3319
  sqlitePath: ":memory:",
3306
3320
  ignoreDuplicates: false,
3307
3321
  ignoreErrors: false,
3308
- gtfsRealtimeExpirationSeconds: 0
3322
+ gtfsRealtimeExpirationSeconds: 0,
3323
+ verbose: true
3309
3324
  };
3310
3325
  return {
3311
3326
  ...defaults,
@@ -3654,18 +3669,31 @@ var createGtfsTables = (db) => {
3654
3669
  return;
3655
3670
  }
3656
3671
  const columns = model.schema.map((column) => {
3657
- let check = "";
3672
+ const checks = [];
3658
3673
  if (column.min !== void 0 && column.max) {
3659
- check = `CHECK( ${column.name} >= ${column.min} AND ${column.name} <= ${column.max} )`;
3674
+ checks.push(
3675
+ `${column.name} >= ${column.min} AND ${column.name} <= ${column.max}`
3676
+ );
3660
3677
  } else if (column.min) {
3661
- check = `CHECK( ${column.name} >= ${column.min} )`;
3678
+ checks.push(`${column.name} >= ${column.min}`);
3662
3679
  } else if (column.max) {
3663
- check = `CHECK( ${column.name} <= ${column.max} )`;
3680
+ checks.push(`${column.name} <= ${column.max}`);
3681
+ }
3682
+ if (column.type === "integer") {
3683
+ checks.push(
3684
+ `(TYPEOF(${column.name}) = 'integer' OR ${column.name} IS NULL)`
3685
+ );
3686
+ }
3687
+ if (column.type === "real") {
3688
+ checks.push(
3689
+ `(TYPEOF(${column.name}) = 'real' OR ${column.name} IS NULL)`
3690
+ );
3664
3691
  }
3665
3692
  const required = column.required ? "NOT NULL" : "";
3666
3693
  const columnDefault = column.default ? "DEFAULT " + column.default : "";
3667
3694
  const columnCollation = column.nocase ? "COLLATE NOCASE" : "";
3668
- return `${column.name} ${column.type} ${check} ${required} ${columnDefault} ${columnCollation}`;
3695
+ const checkClause = checks.length > 0 ? `CHECK(${checks.join(" AND ")})` : "";
3696
+ return `${column.name} ${column.type} ${checkClause} ${required} ${columnDefault} ${columnCollation}`;
3669
3697
  });
3670
3698
  const primaryColumns = model.schema.filter((column) => column.primary);
3671
3699
  if (primaryColumns.length > 0) {
@@ -3708,38 +3736,15 @@ var formatGtfsLine = (line, model, totalLineCount) => {
3708
3736
  }
3709
3737
  continue;
3710
3738
  }
3711
- switch (type) {
3712
- case "date":
3713
- value = value.replace(/-/g, "");
3714
- if (value.length !== 8) {
3715
- throw new Error(
3716
- `Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
3717
- );
3718
- }
3719
- value = parseInt(value, 10);
3720
- break;
3721
- case "integer":
3722
- value = parseInt(value, 10);
3723
- break;
3724
- case "real":
3725
- value = parseFloat(value);
3726
- break;
3727
- }
3728
- if (Number.isNaN(value)) {
3729
- formattedLine[name] = null;
3730
- continue;
3739
+ if (type === "date") {
3740
+ value = value.replace(/-/g, "");
3741
+ if (value.length !== 8) {
3742
+ throw new Error(
3743
+ `Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
3744
+ );
3745
+ }
3731
3746
  }
3732
3747
  formattedLine[name] = value;
3733
- if (min !== void 0 && value < min) {
3734
- throw new Error(
3735
- `Invalid value in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}: below minimum value of ${min}.`
3736
- );
3737
- }
3738
- if (max !== void 0 && value > max) {
3739
- throw new Error(
3740
- `Invalid value in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}: above maximum value of ${max}.`
3741
- );
3742
- }
3743
3748
  }
3744
3749
  for (const [timeColumnName, timestampColumnName] of TIME_COLUMN_PAIRS) {
3745
3750
  const value = formattedLine[timeColumnName];
@@ -3775,12 +3780,11 @@ var importGtfsFiles = (db, task) => mapSeries2(
3775
3780
  return;
3776
3781
  }
3777
3782
  task.log(`Importing - ${filename}\r`);
3778
- const columns = model.schema.filter((column) => column.name !== "id");
3779
- const placeholder = columns.map(({ name }) => `@${name}`).join(", ");
3783
+ const placeholder = model.schema.map(({ name }) => `@${name}`).join(", ");
3780
3784
  const prefixedColumns = new Set(
3781
- columns.filter((col) => col.prefix).map((col) => col.name)
3785
+ model.schema.filter((column) => column.prefix).map((column) => column.name)
3782
3786
  );
3783
- const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map((column) => column.name).join(", ")}) VALUES (${placeholder})`;
3787
+ const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${model.schema.map((column) => column.name).join(", ")}) VALUES (${placeholder})`;
3784
3788
  const insert = db.prepare(prepareStatement);
3785
3789
  const insertLines = db.transaction((lines) => {
3786
3790
  for (const [rowNumber, line] of Object.entries(lines)) {
@@ -3808,7 +3812,7 @@ var importGtfsFiles = (db, task) => mapSeries2(
3808
3812
  );
3809
3813
  }
3810
3814
  task.logWarning(
3811
- `Check ${filename} for invalid data on row ${rowNumber}.`
3815
+ `Check ${filename} for invalid data on line ${rowNumber + 1}.`
3812
3816
  );
3813
3817
  throw error;
3814
3818
  }