@mailwoman/tiger 9.1.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/out/sdk/download.d.ts +0 -7
  2. package/out/sdk/download.d.ts.map +1 -1
  3. package/out/sdk/download.js +0 -18
  4. package/out/sdk/download.js.map +1 -1
  5. package/out/sdk/fetch.d.ts.map +1 -1
  6. package/out/sdk/fetch.js +11 -8
  7. package/out/sdk/fetch.js.map +1 -1
  8. package/out/sdk/redistricting.d.ts.map +1 -1
  9. package/out/sdk/redistricting.js +1 -5
  10. package/out/sdk/redistricting.js.map +1 -1
  11. package/out/sdk/state/TIGERTabulatedBlockEntity.d.ts.map +1 -1
  12. package/out/sdk/state/TIGERTabulatedBlockEntity.js +1 -1
  13. package/out/sdk/state/TIGERTabulatedBlockEntity.js.map +1 -1
  14. package/out/sdk/state/TIGERTractEntity.d.ts +1 -1
  15. package/out/sdk/state/TIGERTractEntity.d.ts.map +1 -1
  16. package/out/sdk/state/block-operations.d.ts +1 -1
  17. package/out/sdk/state/block-operations.d.ts.map +1 -1
  18. package/out/sdk/state/block-operations.js.map +1 -1
  19. package/out/tools/index.d.ts +1 -5
  20. package/out/tools/index.d.ts.map +1 -1
  21. package/out/tools/index.js +1 -5
  22. package/out/tools/index.js.map +1 -1
  23. package/out/tools/race-dots-map.d.ts.map +1 -1
  24. package/out/tools/race-dots-map.js +2 -1
  25. package/out/tools/race-dots-map.js.map +1 -1
  26. package/out/tools/race-dots.js +2 -2
  27. package/out/tools/race-dots.js.map +1 -1
  28. package/out/tools/serve-range.d.ts.map +1 -1
  29. package/out/tools/serve-range.js +2 -1
  30. package/out/tools/serve-range.js.map +1 -1
  31. package/out/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +19 -7
  33. package/sdk/download.ts +0 -22
  34. package/sdk/fetch.ts +11 -7
  35. package/sdk/redistricting.ts +1 -5
  36. package/sdk/state/TIGERTabulatedBlockEntity.ts +2 -1
  37. package/sdk/state/TIGERTractEntity.ts +1 -1
  38. package/sdk/state/block-operations.ts +2 -1
  39. package/tools/index.ts +1 -5
  40. package/tools/race-dots-map.ts +3 -1
  41. package/tools/race-dots.ts +2 -2
  42. package/tools/serve-range.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailwoman/tiger",
3
- "version": "9.1.0",
3
+ "version": "9.2.0",
4
4
  "description": "US Census TIGER/Line data processing and analysis.",
5
5
  "keywords": [
6
6
  "census",
@@ -21,7 +21,7 @@
21
21
  "repository": {
22
22
  "type": "git",
23
23
  "url": "https://github.com/sister-software/mailwoman.git",
24
- "directory": "tiger"
24
+ "directory": "packages/tiger"
25
25
  },
26
26
  "files": [
27
27
  "out/",
@@ -33,9 +33,16 @@
33
33
  "!*.test.tsx",
34
34
  "!**/*.test.ts",
35
35
  "!**/*.test.tsx",
36
- "!__snapshots__/"
36
+ "!__snapshots__/",
37
+ "!test/**"
37
38
  ],
38
39
  "type": "module",
40
+ "imports": {
41
+ "#index": {
42
+ "types": "./out/index.d.ts",
43
+ "default": "./out/index.js"
44
+ }
45
+ },
39
46
  "exports": {
40
47
  "./package.json": "./package.json",
41
48
  "./sdk": {
@@ -67,18 +74,23 @@
67
74
  "types": "./out/index.d.ts",
68
75
  "default": "./out/index.js"
69
76
  }
77
+ },
78
+ "imports": {
79
+ "#index": {
80
+ "types": "./out/index.d.ts",
81
+ "default": "./out/index.js"
82
+ }
70
83
  }
71
84
  },
72
85
  "scripts": {
73
86
  "test": "node ./out/sdk/TIGERService.test.js"
74
87
  },
75
88
  "dependencies": {
76
- "@mailwoman/core": "9.1.0",
77
- "@mailwoman/spatial": "9.1.0",
89
+ "@mailwoman/core": "9.2.0",
90
+ "@mailwoman/spatial": "9.2.0",
78
91
  "@turf/boolean-contains": "^7.3.5",
79
92
  "kysely": "^0.29.4",
80
- "shapefile-parser": "^1.0.3",
81
- "spliterator": "^6.1.0",
93
+ "spliterator": "^6.2.0",
82
94
  "type-fest": "^5.8.0"
83
95
  },
84
96
  "devDependencies": {
package/sdk/download.ts CHANGED
@@ -14,7 +14,6 @@
14
14
  * never holds it: a web stream piped to a write stream.
15
15
  */
16
16
 
17
- import { spawn } from "node:child_process"
18
17
  import { createWriteStream, existsSync } from "node:fs"
19
18
  import { rename } from "node:fs/promises"
20
19
  import { Readable } from "node:stream"
@@ -22,27 +21,6 @@ import { pipeline } from "node:stream/promises"
22
21
 
23
22
  import { verifyZipIntegrity } from "@mailwoman/core/fs/zip"
24
23
 
25
- /**
26
- * Run a command and resolve its stdout, rejecting with the captured stderr on a non-zero exit.
27
- *
28
- * Used for the `unzip -tq` integrity probe below — a shelled-out check rather than a library one because the archives
29
- * are large and `unzip -t` streams them without materializing the contents.
30
- */
31
- export function runCapture(cmd: string, args: string[]): Promise<string> {
32
- return new Promise((resolve, reject) => {
33
- const child = spawn(cmd, args)
34
- let out = ""
35
- let err = ""
36
- child.stdout.on("data", (d) => (out += d))
37
- child.stderr.on("data", (d) => (err += d))
38
- child.on("error", reject)
39
-
40
- child.on("close", (code) =>
41
- code === 0 ? resolve(out) : reject(new Error(`${cmd} exited ${code}: ${err.slice(0, 500)}`))
42
- )
43
- })
44
- }
45
-
46
24
  /**
47
25
  * Download `url` to `dest` unless a VALID copy is already there. Returns `true` when the cache was reused, `false` when
48
26
  * a download happened.
package/sdk/fetch.ts CHANGED
@@ -25,13 +25,11 @@
25
25
  */
26
26
 
27
27
  import { spawn } from "node:child_process"
28
- import { createWriteStream, existsSync } from "node:fs"
29
- import { mkdir, rename } from "node:fs/promises"
28
+ import { mkdir } from "node:fs/promises"
30
29
  import { dirname, join } from "node:path"
31
30
  import { DatabaseSync } from "node:sqlite"
32
- import { Readable } from "node:stream"
33
- import { pipeline } from "node:stream/promises"
34
31
 
32
+ import { APIClient, pluckResponseData } from "@mailwoman/core/api"
35
33
  import { extractZipEntries } from "@mailwoman/core/fs/zip"
36
34
  import { DatabaseClient } from "@mailwoman/core/kysley/client"
37
35
  import { tryParsingJSON } from "@mailwoman/core/objects"
@@ -176,10 +174,11 @@ function buildRow(level: TIGERFetchLevel, p: Record<string, unknown>, geometry:
176
174
  * Scrape the ADDRFEAT directory listing for a state's county FIPS codes.
177
175
  */
178
176
  async function discoverCounties(state: string, vintage: number): Promise<string[]> {
179
- const res = await fetch(`${CENSUS_HOST}/geo/tiger/TIGER${vintage}/ADDRFEAT/`, { redirect: "follow" })
177
+ // The LISTING only — a small HTML index. The per-county archives below stay on raw `fetch`, streaming to disk.
178
+ const html = await new APIClient({ displayName: "tiger-listing", retry: true })
179
+ .fetch<string>({ url: `${CENSUS_HOST}/geo/tiger/TIGER${vintage}/ADDRFEAT/`, responseType: "text" })
180
+ .then(pluckResponseData)
180
181
 
181
- if (!res.ok) throw new Error(`HTTP ${res.status} listing ADDRFEAT for vintage ${vintage}`)
182
- const html = await res.text()
183
182
  const re = new RegExp(`tl_${vintage}_${state}(\\d{3})_addrfeat\\.zip`, "g")
184
183
  const counties = new Set<string>()
185
184
 
@@ -222,6 +221,11 @@ export async function* fetchTIGER(options: FetchTIGEROptions): AsyncGenerator<Fe
222
221
  const kdb = new DatabaseClient<TIGERDatabase>({ database: db })
223
222
  await initializeTIGERSchema(kdb)
224
223
 
224
+ /**
225
+ * `level` picks the table AND the row shape, but nothing ties them at the type level: {@link buildRow} returns the
226
+ * union and Kysely needs the arm's concrete row. The narrowings below are sound only because `buildRow` switches on
227
+ * the SAME `level` — keep the two switches in step, or a row shape reaches the wrong table.
228
+ */
225
229
  const insertBatch = async (rows: Row[]): Promise<void> => {
226
230
  if (level === "tabblock20") {
227
231
  await kdb
@@ -16,13 +16,9 @@
16
16
  * https://www2.census.gov/programs-surveys/decennial/2020/data/01-Redistricting_File--PL_94-171/
17
17
  */
18
18
 
19
- import { spawn } from "node:child_process"
20
- import { createWriteStream, existsSync } from "node:fs"
21
- import { mkdir, rename } from "node:fs/promises"
19
+ import { mkdir } from "node:fs/promises"
22
20
  import { dirname, join } from "node:path"
23
21
  import { DatabaseSync } from "node:sqlite"
24
- import { Readable } from "node:stream"
25
- import { pipeline } from "node:stream/promises"
26
22
 
27
23
  import { extractZipEntries } from "@mailwoman/core/fs/zip"
28
24
  import { DatabaseClient } from "@mailwoman/core/kysley/client"
@@ -5,7 +5,8 @@
5
5
  */
6
6
 
7
7
  import { smartSnakeCase } from "@mailwoman/core"
8
- import { TIGERProperty } from "@mailwoman/tiger"
8
+
9
+ import { TIGERProperty } from "#index"
9
10
 
10
11
  /**
11
12
  * Default column selections for TIGER tabulated block entities.
@@ -4,7 +4,7 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- import type { AdminLevel1Code, FIPSCountyCode, FIPSTractCode } from "@mailwoman/tiger"
7
+ import type { AdminLevel1Code, FIPSCountyCode, FIPSTractCode } from "#index"
8
8
 
9
9
  /**
10
10
  * @internal
@@ -6,6 +6,7 @@
6
6
 
7
7
  import type { GeometryLiteral, MultiPolygonLiteral } from "@mailwoman/spatial"
8
8
  import { wellKnownGeometryToGeoJSON } from "@mailwoman/spatial/sdk"
9
+
9
10
  import type {
10
11
  FIPSStateCode,
11
12
  FIPSBlockGeoID,
@@ -13,7 +14,7 @@ import type {
13
14
  TIGERBlockFeature,
14
15
  TIGERBlockFeatureCollection,
15
16
  TIGERTabulatedBlockProperties,
16
- } from "@mailwoman/tiger"
17
+ } from "#index"
17
18
 
18
19
  export interface StateBlockIntersectionCriteria {
19
20
  stateCode: FIPSStateCode
package/tools/index.ts CHANGED
@@ -3,11 +3,7 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * TIGER operator tools — the `run()`-style modules behind `mailwoman tiger race-dots` /
7
- * `race-dots-map` (see the 2026-07-09 scripts→Pastel spec). No argv, no `process.exit`: commands
8
- * own parsing, rendering, and exit codes. Heavy deps (`@turf/boolean-contains`,
9
- * `@protomaps/basemaps`) are lazy-imported inside their entry fns. NOT `sdk/` — that submodule
10
- * means data acquisition.
6
+ * TIGER operator tool exports.
11
7
  */
12
8
 
13
9
  export * from "./race-dots.ts"
@@ -19,6 +19,8 @@
19
19
 
20
20
  import { writeFileSync } from "node:fs"
21
21
 
22
+ import { tempRootPath } from "@mailwoman/core/utils"
23
+
22
24
  /**
23
25
  * Options for {@linkcode raceDotsMap}.
24
26
  */
@@ -104,7 +106,7 @@ export async function raceDotsMap(
104
106
  report?: (line: string) => void
105
107
  ): Promise<RaceDotsMapResult> {
106
108
  const PMTILES_URL = options.pmtilesURL || "http://localhost:8899/race-dots-oc.pmtiles"
107
- const OUT = options.out || "/tmp/race-dots-oc.html"
109
+ const OUT = options.out || tempRootPath("race-dots-oc.html")
108
110
  const PER = options.per ?? 5
109
111
  const PER_PHRASE = PER === 1 ? "one dot is one person" : `one dot ≈ ${PER} people`
110
112
  const TITLE = options.title || `Race in Orange County, CA — ${PER_PHRASE} (2020 Census)`
@@ -25,7 +25,7 @@ import { createWriteStream } from "node:fs"
25
25
  import { DatabaseSync } from "node:sqlite"
26
26
 
27
27
  import { tryParsingJSON } from "@mailwoman/core/objects"
28
- import { dataRootPath } from "@mailwoman/core/utils"
28
+ import { dataRootPath, tempRootPath } from "@mailwoman/core/utils"
29
29
 
30
30
  /**
31
31
  * Attempts to place a dot inside its polygon by rejection sampling before giving up on it.
@@ -112,7 +112,7 @@ export async function raceDots(
112
112
  report?: (line: string) => void
113
113
  ): Promise<RaceDotsResult> {
114
114
  const DB = options.db || dataRootPath("tiger", "tiger-oc.db")
115
- const OUT = options.out || "/tmp/race-dots.ndjson"
115
+ const OUT = options.out || tempRootPath("race-dots.ndjson")
116
116
  const PER = options.per ?? 10 // people represented by one dot
117
117
  const LAYER = options.layer || "dots"
118
118
 
@@ -15,6 +15,8 @@ import { createReadStream, statSync, type Stats } from "node:fs"
15
15
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"
16
16
  import { extname, join, normalize } from "node:path"
17
17
 
18
+ import { mailwomanTempRoot } from "@mailwoman/core/utils"
19
+
18
20
  /**
19
21
  * Options for {@linkcode serveWithRangeSupport}.
20
22
  */
@@ -55,7 +57,7 @@ export function serveWithRangeSupport(
55
57
  options: ServeRangeOptions = {},
56
58
  report?: (line: string) => void
57
59
  ): Promise<RangeServer> {
58
- const dir = options.dir || "/tmp"
60
+ const dir = options.dir || mailwomanTempRoot()
59
61
  const port = options.port ?? 8899
60
62
 
61
63
  const server = createServer((req: IncomingMessage, res: ServerResponse) => {