@cliwant/mcp-sam-gov 1.2.0 → 1.4.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 (91) hide show
  1. package/README.ja.md +22 -9
  2. package/README.ko.md +22 -9
  3. package/README.md +70 -12
  4. package/dist/bea.d.ts +105 -0
  5. package/dist/bea.d.ts.map +1 -0
  6. package/dist/bea.js +303 -0
  7. package/dist/bea.js.map +1 -0
  8. package/dist/census-economic.d.ts +1 -1
  9. package/dist/census-economic.d.ts.map +1 -1
  10. package/dist/census-economic.js +12 -6
  11. package/dist/census-economic.js.map +1 -1
  12. package/dist/cms-facility.d.ts +112 -0
  13. package/dist/cms-facility.d.ts.map +1 -0
  14. package/dist/cms-facility.js +311 -0
  15. package/dist/cms-facility.js.map +1 -0
  16. package/dist/cms-hospital.d.ts +105 -0
  17. package/dist/cms-hospital.d.ts.map +1 -0
  18. package/dist/cms-hospital.js +290 -0
  19. package/dist/cms-hospital.js.map +1 -0
  20. package/dist/cms-supplier.d.ts +133 -0
  21. package/dist/cms-supplier.d.ts.map +1 -0
  22. package/dist/cms-supplier.js +414 -0
  23. package/dist/cms-supplier.js.map +1 -0
  24. package/dist/cms-utilization.d.ts +113 -0
  25. package/dist/cms-utilization.d.ts.map +1 -0
  26. package/dist/cms-utilization.js +328 -0
  27. package/dist/cms-utilization.js.map +1 -0
  28. package/dist/courtlistener.d.ts +115 -0
  29. package/dist/courtlistener.d.ts.map +1 -0
  30. package/dist/courtlistener.js +398 -0
  31. package/dist/courtlistener.js.map +1 -0
  32. package/dist/cpsc.d.ts +81 -0
  33. package/dist/cpsc.d.ts.map +1 -0
  34. package/dist/cpsc.js +283 -0
  35. package/dist/cpsc.js.map +1 -0
  36. package/dist/dol.d.ts +118 -0
  37. package/dist/dol.d.ts.map +1 -0
  38. package/dist/dol.js +421 -0
  39. package/dist/dol.js.map +1 -0
  40. package/dist/epa-envirofacts.d.ts +97 -0
  41. package/dist/epa-envirofacts.d.ts.map +1 -0
  42. package/dist/epa-envirofacts.js +292 -0
  43. package/dist/epa-envirofacts.js.map +1 -0
  44. package/dist/fred.d.ts +1 -1
  45. package/dist/fred.js +1 -1
  46. package/dist/keys.d.ts +11 -8
  47. package/dist/keys.d.ts.map +1 -1
  48. package/dist/keys.js +55 -8
  49. package/dist/keys.js.map +1 -1
  50. package/dist/lda.d.ts +105 -0
  51. package/dist/lda.d.ts.map +1 -0
  52. package/dist/lda.js +317 -0
  53. package/dist/lda.js.map +1 -0
  54. package/dist/nhtsa.d.ts +91 -0
  55. package/dist/nhtsa.d.ts.map +1 -0
  56. package/dist/nhtsa.js +263 -0
  57. package/dist/nhtsa.js.map +1 -0
  58. package/dist/nonprofit.d.ts +116 -0
  59. package/dist/nonprofit.d.ts.map +1 -0
  60. package/dist/nonprofit.js +342 -0
  61. package/dist/nonprofit.js.map +1 -0
  62. package/dist/openfda-device.d.ts +85 -0
  63. package/dist/openfda-device.d.ts.map +1 -0
  64. package/dist/openfda-device.js +277 -0
  65. package/dist/openfda-device.js.map +1 -0
  66. package/dist/openfda.d.ts +133 -0
  67. package/dist/openfda.d.ts.map +1 -0
  68. package/dist/openfda.js +402 -0
  69. package/dist/openfda.js.map +1 -0
  70. package/dist/server.d.ts.map +1 -1
  71. package/dist/server.js +872 -6
  72. package/dist/server.js.map +1 -1
  73. package/package.json +2 -1
  74. package/src/bea.ts +372 -0
  75. package/src/census-economic.ts +12 -6
  76. package/src/cms-facility.ts +379 -0
  77. package/src/cms-hospital.ts +344 -0
  78. package/src/cms-supplier.ts +527 -0
  79. package/src/cms-utilization.ts +389 -0
  80. package/src/courtlistener.ts +465 -0
  81. package/src/cpsc.ts +333 -0
  82. package/src/dol.ts +515 -0
  83. package/src/epa-envirofacts.ts +342 -0
  84. package/src/fred.ts +1 -1
  85. package/src/keys.ts +60 -8
  86. package/src/lda.ts +385 -0
  87. package/src/nhtsa.ts +352 -0
  88. package/src/nonprofit.ts +460 -0
  89. package/src/openfda-device.ts +356 -0
  90. package/src/openfda.ts +495 -0
  91. package/src/server.ts +995 -6
package/dist/cpsc.js ADDED
@@ -0,0 +1,283 @@
1
+ /**
2
+ * cpsc.ts — CPSC CONSUMER-PRODUCT RECALLS (www.saferproducts.gov) — the consumer
3
+ * goods / import product-safety vetting lane (ADR-0058). ONE keyless tool:
4
+ * • cpsc_recalls — /RestWebServices/Recall?format=json + date/product/manufacturer/
5
+ * recallNumber filters.
6
+ * The third leg of the cross-agency product-safety family alongside NHTSA (vehicles)
7
+ * and openFDA (medical/food): a manufacturer / product / hazard recall history for
8
+ * B2G supplier and import vetting.
9
+ *
10
+ * ★ KEYLESS — there is NO API key at all (no parameter, no header). This module
11
+ * touches NO key seam (no KEY_REGISTRY / keys.ts / API_KEYS.md). It REUSES the
12
+ * shared `getJson` (redirect:"error") / `driftError` fetch envelope, the `str`
13
+ * coercion (null-never-empty-string), and `withMeta`/`buildMeta` — and mirrors
14
+ * nhtsa.ts / datagov-catalog.ts's fixed-host SSRF idiom + schema_drift
15
+ * catch-ladder verbatim.
16
+ *
17
+ * ★ THE HONESTY PILLARS (P1-P5, live-verified 2026-07-15):
18
+ * P1: the /Recall response is a BARE JSON ARRAY with NO count field and NO server
19
+ * pagination — it returns the COMPLETE set matching the filter. So
20
+ * totalAvailable = results.length and complete:true, WITH a disclosing note
21
+ * that CPSC reports no total-count field / no pagination. A total is NEVER
22
+ * fabricated (there is no upstream total to trust; the honest total is the
23
+ * length of the complete set).
24
+ * P2: an empty array `[]` ⇒ an HONEST EMPTY (returned:0, complete:true) — a filter
25
+ * that matches nothing is an honest no-match, NOT an error. A 4xx ⇒
26
+ * invalid_input; a 5xx/timeout ⇒ THROW (never a fake empty); a 200 non-JSON
27
+ * body OR a non-array body ⇒ schema_drift (never a fabricated empty).
28
+ * P3: dates stay STRINGS (via `str`); NumberOfUnits is free text ("About 6,500")
29
+ * kept as a STRING; nested arrays (Products/Manufacturers/Retailers/Hazards/
30
+ * Remedies/Injuries/ManufacturerCountries) are flattened to string arrays by
31
+ * extracting each object's `.Name` (★ManufacturerCountries uses `.Country`,
32
+ * NOT `.Name`), SKIPPING an empty `{}` object (never a fabricated entry); a
33
+ * genuinely-absent nested array ⇒ []; null-never-empty-string throughout.
34
+ * P4: the top-level body MUST be an array — a non-array (object/string/null) ⇒
35
+ * driftError (a broken response contract, never a fabricated empty).
36
+ * DEFAULT-WINDOW: with NO filter given, the unfiltered result is huge, so the tool
37
+ * defaults RecallDateStart to ~90 days ago and DISCLOSES the default in a note
38
+ * — it NEVER silently fetches the whole dataset.
39
+ * SSRF: fixed host `www.saferproducts.gov` (compile-time literal) + post-construction
40
+ * hostname/protocol assertion + redirect:"error"; every filter rides a
41
+ * module-built URLSearchParams (no raw passthrough); dates are ^\d{4}-\d{2}-\d{2}$;
42
+ * recallNumber is charclass-validated (letters/digits/hyphen only), so a
43
+ * `../` or `%` can never reach the fixed path.
44
+ */
45
+ import { ToolErrorCarrier } from "./errors.js";
46
+ import { getJson, driftError } from "./datasource.js";
47
+ import { str } from "./coerce.js";
48
+ import { withMeta } from "./meta.js";
49
+ // ─── Fixed endpoint (SSRF core — compile-time CONSTANTS) ──────────
50
+ export const CPSC_HOST = "www.saferproducts.gov";
51
+ const CPSC_RECALL_PATH = "/RestWebServices/Recall";
52
+ // HOST+path-only label (→ ToolError.upstreamEndpoint). Keyless ⇒ no token can ever
53
+ // appear here regardless, but the label stays host+path for consistency.
54
+ const CPSC_RECALL_LABEL = "cpsc:/RestWebServices/Recall";
55
+ // ─── Input validation grammar (SSRF + injection guard) ────────────
56
+ // dates: strict YYYY-MM-DD. recallNumber: letters/digits/hyphen only — rejects
57
+ // `../`, `%`, `/`, `.`, spaces, quotes, so a value can never break out of the
58
+ // URLSearchParams-encoded query onto the fixed host/path.
59
+ export const CPSC_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
60
+ export const CPSC_RECALL_NUMBER_RE = /^[A-Za-z0-9-]+$/;
61
+ // The default recent-window span (days) applied when NO filter is given, so an
62
+ // unbounded whole-dataset fetch never happens silently.
63
+ const CPSC_DEFAULT_WINDOW_DAYS = 90;
64
+ const KEYLESS_NOTE = "CPSC SaferProducts is a keyless public API (www.saferproducts.gov) — no API key is required or accepted.";
65
+ // The P1 load-bearing honesty caveat carried on EVERY response.
66
+ const CPSC_NO_PAGINATION_NOTE = "CPSC returns the complete matching set (no server pagination or total-count field) — totalAvailable is the number of returned recalls (the size of the complete set), not an upstream-reported total.";
67
+ // ─── Nested-array flatteners (P3) ─────────────────────────────────
68
+ /**
69
+ * Flatten a CPSC nested array (Products/Manufacturers/Retailers/Hazards/Remedies/
70
+ * Injuries/ManufacturerCountries) to a string[] by extracting `field` from each
71
+ * object via `str` (null-never-empty-string). An empty `{}` object (or one whose
72
+ * `field` is absent/blank) is SKIPPED — never a fabricated entry. A non-array
73
+ * (absent nested array) ⇒ [].
74
+ */
75
+ function extractField(x, field) {
76
+ if (!Array.isArray(x))
77
+ return [];
78
+ const out = [];
79
+ for (const el of x) {
80
+ if (el === null || typeof el !== "object")
81
+ continue;
82
+ const v = str(el[field]);
83
+ if (v !== null)
84
+ out.push(v);
85
+ }
86
+ return out;
87
+ }
88
+ /**
89
+ * The recall-level numberOfUnits (P3): CPSC carries NumberOfUnits per PRODUCT as
90
+ * FREE TEXT ("About 6,500"), so this returns the FIRST product's non-blank
91
+ * NumberOfUnits as a STRING (never numerically coerced), or null when none is
92
+ * present (null-never-empty-string).
93
+ */
94
+ function firstNumberOfUnits(products) {
95
+ if (!Array.isArray(products))
96
+ return null;
97
+ for (const p of products) {
98
+ if (p === null || typeof p !== "object")
99
+ continue;
100
+ const v = str(p.NumberOfUnits);
101
+ if (v !== null)
102
+ return v;
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * Map ONE /Recall row → the curated recall shape. Scalars via `str`
108
+ * (null-never-empty-string; dates stay strings). Nested arrays flattened via
109
+ * `extractField` on `.Name` — EXCEPT ManufacturerCountries, which carries `.Country`.
110
+ */
111
+ function mapRecall(row) {
112
+ const r = (row ?? {});
113
+ return {
114
+ recallNumber: str(r.RecallNumber),
115
+ recallDate: str(r.RecallDate),
116
+ title: str(r.Title),
117
+ description: str(r.Description),
118
+ url: str(r.URL),
119
+ products: extractField(r.Products, "Name"),
120
+ numberOfUnits: firstNumberOfUnits(r.Products),
121
+ manufacturers: extractField(r.Manufacturers, "Name"),
122
+ retailers: extractField(r.Retailers, "Name"),
123
+ hazards: extractField(r.Hazards, "Name"),
124
+ remedies: extractField(r.Remedies, "Name"),
125
+ injuries: extractField(r.Injuries, "Name"),
126
+ // ★ ManufacturerCountries objects carry `Country`, not `Name` (live-verified).
127
+ manufacturerCountries: extractField(r.ManufacturerCountries, "Country"),
128
+ };
129
+ }
130
+ /**
131
+ * Validate the optional inputs PRE-fetch (0 network call). A DIRECT handler call
132
+ * bypasses the server Zod, so re-guard the SSRF-relevant grammars here: dates are
133
+ * ^\d{4}-\d{2}-\d{2}$; recallNumber is letters/digits/hyphen only. productName /
134
+ * manufacturer are free text (they ride URLSearchParams-encoded, so injection is
135
+ * neutralized by encoding — no charclass needed, but they are validated as strings).
136
+ */
137
+ function validateArgs(args) {
138
+ const dateChecks = [
139
+ ["dateStart", args.dateStart],
140
+ ["dateEnd", args.dateEnd],
141
+ ];
142
+ for (const [name, value] of dateChecks) {
143
+ if (value !== undefined && (typeof value !== "string" || !CPSC_DATE_RE.test(value))) {
144
+ throw new ToolErrorCarrier({
145
+ kind: "invalid_input",
146
+ retryable: false,
147
+ message: `Invalid ${name} ${JSON.stringify(value)} — expected a YYYY-MM-DD date (^\\d{4}-\\d{2}-\\d{2}$), e.g. "2025-01-01".`,
148
+ upstreamEndpoint: CPSC_RECALL_LABEL,
149
+ });
150
+ }
151
+ }
152
+ if (args.recallNumber !== undefined &&
153
+ (typeof args.recallNumber !== "string" || !CPSC_RECALL_NUMBER_RE.test(args.recallNumber))) {
154
+ throw new ToolErrorCarrier({
155
+ kind: "invalid_input",
156
+ retryable: false,
157
+ message: `Invalid recallNumber ${JSON.stringify(args.recallNumber)} — expected letters/digits/hyphen only (^[A-Za-z0-9-]+$), e.g. "25088".`,
158
+ upstreamEndpoint: CPSC_RECALL_LABEL,
159
+ });
160
+ }
161
+ for (const [name, value] of [
162
+ ["productName", args.productName],
163
+ ["manufacturer", args.manufacturer],
164
+ ]) {
165
+ if (value !== undefined && typeof value !== "string") {
166
+ throw new ToolErrorCarrier({
167
+ kind: "invalid_input",
168
+ retryable: false,
169
+ message: `Invalid ${name} — expected a string.`,
170
+ upstreamEndpoint: CPSC_RECALL_LABEL,
171
+ });
172
+ }
173
+ }
174
+ }
175
+ // ─── SSRF-guarded fetch (fixed host + hostname assertion + redirect) ──
176
+ /**
177
+ * GET the CPSC /Recall JSON on the FIXED host. Builds
178
+ * `https://www.saferproducts.gov/RestWebServices/Recall?${params}`, asserts the
179
+ * CONSTRUCTED URL's hostname === the fixed host over https (belt-and-suspenders),
180
+ * and sets `redirect:"error"` (fail closed on any off-host 3xx). Keyless — no
181
+ * header/token.
182
+ */
183
+ async function getCpsc(params) {
184
+ const url = `https://${CPSC_HOST}${CPSC_RECALL_PATH}?${params.toString()}`;
185
+ const built = new URL(url);
186
+ if (built.hostname !== CPSC_HOST || built.protocol !== "https:") {
187
+ throw new ToolErrorCarrier({
188
+ kind: "invalid_input",
189
+ retryable: false,
190
+ message: `Constructed CPSC URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the fixed host ${JSON.stringify(CPSC_HOST)} over https — refusing to fetch (SSRF safety).`,
191
+ upstreamEndpoint: CPSC_RECALL_LABEL,
192
+ });
193
+ }
194
+ return getJson(url, { label: CPSC_RECALL_LABEL, redirect: "error" });
195
+ }
196
+ /** Compute an ISO YYYY-MM-DD `days` days before now (the default-window start). */
197
+ function daysAgoIso(days) {
198
+ return new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10);
199
+ }
200
+ // ─── Tool: cpsc_recalls ───────────────────────────────────────────
201
+ /**
202
+ * Fetch CPSC consumer-product RECALLS → curated recall rows + honest `_meta`.
203
+ * KEYLESS. All filters are optional; with NO filter given, RecallDateStart defaults
204
+ * to ~90 days ago (disclosed in a note) so the whole dataset is never silently
205
+ * fetched. The response is a bare array with no total-count field / no pagination
206
+ * ⇒ totalAvailable = the number of returned recalls, complete:true. An empty array
207
+ * ⇒ an honest empty; a 4xx ⇒ invalid_input; a 5xx/timeout ⇒ THROW; a 200 non-JSON
208
+ * OR a non-array body ⇒ schema_drift.
209
+ */
210
+ export async function recalls(args) {
211
+ validateArgs(args);
212
+ // ── Build the query from VALIDATED typed args, key-by-key (SSRF: no raw
213
+ // passthrough). format=json is ALWAYS appended. ──
214
+ const params = new URLSearchParams();
215
+ params.set("format", "json");
216
+ const filtersApplied = [];
217
+ if (args.dateStart !== undefined) {
218
+ params.set("RecallDateStart", args.dateStart);
219
+ filtersApplied.push("dateStart");
220
+ }
221
+ if (args.dateEnd !== undefined) {
222
+ params.set("RecallDateEnd", args.dateEnd);
223
+ filtersApplied.push("dateEnd");
224
+ }
225
+ if (args.productName !== undefined) {
226
+ params.set("ProductName", args.productName);
227
+ filtersApplied.push("productName");
228
+ }
229
+ if (args.manufacturer !== undefined) {
230
+ params.set("Manufacturer", args.manufacturer);
231
+ filtersApplied.push("manufacturer");
232
+ }
233
+ if (args.recallNumber !== undefined) {
234
+ params.set("RecallNumber", args.recallNumber);
235
+ filtersApplied.push("recallNumber");
236
+ }
237
+ // ── DEFAULT-WINDOW: with NO filter, an unbounded fetch would return the WHOLE
238
+ // dataset. Bound it to a ~90-day recent window (RecallDateStart) and DISCLOSE
239
+ // the default — never silently fetch everything. ──
240
+ const notes = [KEYLESS_NOTE, CPSC_NO_PAGINATION_NOTE];
241
+ let defaultWindowApplied = false;
242
+ if (filtersApplied.length === 0) {
243
+ const defaultStart = daysAgoIso(CPSC_DEFAULT_WINDOW_DAYS);
244
+ params.set("RecallDateStart", defaultStart);
245
+ defaultWindowApplied = true;
246
+ notes.push(`No filter was provided — to avoid silently fetching the ENTIRE recall dataset, results are bounded to a default recent window: RecallDateStart=${defaultStart} (~${CPSC_DEFAULT_WINDOW_DAYS} days ago). Pass dateStart/dateEnd, productName, manufacturer, or recallNumber for a scoped query.`);
247
+ }
248
+ // ── The typed catch-ladder (nhtsa.ts / datagov-catalog.ts shape, VERBATIM):
249
+ // a ToolErrorCarrier (host-assert / 4xx-5xx taxonomy) rethrows FIRST
250
+ // (preserving its kind); a 200 non-JSON `.json()` SyntaxError reclassifies to
251
+ // schema_drift; a bare error rethrows LAST. ──
252
+ let body;
253
+ try {
254
+ body = await getCpsc(params);
255
+ }
256
+ catch (e) {
257
+ if (e instanceof ToolErrorCarrier)
258
+ throw e;
259
+ if (e instanceof SyntaxError)
260
+ throw driftError(CPSC_RECALL_LABEL, "CPSC /RestWebServices/Recall returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).");
261
+ throw e;
262
+ }
263
+ // ── P4: the top-level body MUST be an array (a non-array object/string/null is
264
+ // drift, never a fabricated empty). ──
265
+ if (!Array.isArray(body)) {
266
+ throw driftError(CPSC_RECALL_LABEL, "CPSC /RestWebServices/Recall shape drift — the response must be a bare JSON array.");
267
+ }
268
+ const recalls = body.map(mapRecall);
269
+ const returned = recalls.length;
270
+ return withMeta({ recalls }, {
271
+ source: `${CPSC_HOST} /RestWebServices/Recall (CPSC consumer-product recalls; keyless)`,
272
+ keylessMode: true,
273
+ returned,
274
+ // P1 — no upstream total-count field / no pagination: the complete set IS the
275
+ // returned rows, so totalAvailable = returned and complete:true (derived).
276
+ totalAvailable: returned,
277
+ filtersApplied: defaultWindowApplied ? ["dateStart(default)"] : filtersApplied,
278
+ filtersDropped: [],
279
+ fieldsUnavailable: [],
280
+ notes,
281
+ });
282
+ }
283
+ //# sourceMappingURL=cpsc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cpsc.js","sourceRoot":"","sources":["../src/cpsc.ts"],"names":[],"mappings":"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"}
package/dist/dol.d.ts ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * dol.ts — US Department of Labor Data API v4 (apiprod.dol.gov) — the LABOR
3
+ * ENFORCEMENT lane (ADR-0053). WHD wage/hour violations, OSHA inspections, ILAB
4
+ * child/forced-labor reports, MSHA mine safety … — the compliance/enforcement
5
+ * signal no contract/spending/market source carries.
6
+ *
7
+ * TWO tools with a DELIBERATE key split (the honest reflection of the live API):
8
+ * • `dol_list_datasets` (KEYLESS) — GET /v4/datasets. The dataset CATALOG (and
9
+ * /v4/agencies) is keyless; this tool needs NO key. It returns the machine
10
+ * inventory (name / tablename / api_url / agency / …) you page + filter to find
11
+ * an endpoint, then feed its `apiUrl` into dol_get_dataset.
12
+ * • `dol_get_dataset` (KEY-REQUIRED, DOL_API_KEY) — GET
13
+ * /v4/get/{agency}/{endpoint}/json?… . The DATA endpoint has NO keyless tier, so
14
+ * with NO `DOL_API_KEY` this tool THROWS an invalid_input config error BEFORE any
15
+ * fetch (0 network call; the message names DOL_API_KEY + dataportal.dol.gov/registration).
16
+ * So DOL is the 4th REQUIRED key — but ONLY for the data tool; the catalog tool
17
+ * (and every other tool on the server) stays keyless.
18
+ *
19
+ * ★LIVE-VERIFIED WIRE FACTS (2026-07-15):
20
+ * - Host `apiprod.dol.gov` (AWS API Gateway) + base `/v4`.
21
+ * - /v4/datasets is KEYLESS 200 → `{ datasets:[…], meta:{ current_page, next_page,
22
+ * prev_page, total_pages, total_count } }`. `limit` is the PER-PAGE size (limit=1000
23
+ * returns the whole 42-row catalog in one page, total_pages:1); server-side agency
24
+ * filtering is NOT honored (verified: ?agency=ILAB still returned the full 42) — so
25
+ * agency/query filtering is CLIENT-SIDE over the fetched catalog.
26
+ * - The DATA route is the QUERY-STYLE form `/v4/get/{agency}/{endpoint}/{format}?…`
27
+ * (NOT the path-style `/…/limit/N/offset/O/format/json`): the query-style URL
28
+ * reached the DOL app and returned a proper `401 {"…key…missing…"}` on a bad key,
29
+ * whereas the path-style URL only ever hit the AWS gateway's generic
30
+ * `403 {"message":"Missing Authentication Token"}` (an unmatched route). The DOL
31
+ * API User Guide (dataportal.dol.gov/pdf/dol-api-user-guide.pdf) confirms the
32
+ * `/get/<agency>/<api_url>/<format>?limit=&offset=&filter_object=&…` template and
33
+ * that `<endpoint>` is the dataset's **api_url** (NOT its tablename).
34
+ *
35
+ * ★UNVERIFIED (key-gated — coded DEFENSIVELY, honestly disclosed): the DATA response
36
+ * body shape could not be observed live (every /v4/get call is 401 without a real
37
+ * key, and the User Guide shows no body example). So `dol_get_dataset` accepts EITHER
38
+ * a bare row array `[…]` OR `{ data:[…] }` OR `{ results:[…] }`; a top-level count
39
+ * (`total_count`/`total`/`count`, or `meta.total_count`) is used for totalAvailable
40
+ * ONLY if actually present, else `totalAvailable = null` (an HONEST unknown — never
41
+ * `returned` passed off as the total). Records are surfaced VERBATIM (each dataset
42
+ * has its own enforcement schema; blind coercion would distort compliance data — a
43
+ * JSON `0` stays `0`, a JSON `null` stays `null`, field names are preserved as-is).
44
+ *
45
+ * ★HONESTY (ADR-0053 P1–P4 + KEY + SSRF):
46
+ * [KEY] dol_get_dataset with NO DOL_API_KEY ⇒ invalid_input THROW pre-fetch (0
47
+ * fetch); the message names DOL_API_KEY + dataportal.dol.gov/registration. The key rides the
48
+ * `X-API-KEY` HEADER ONLY — NEVER the URL / label / _meta / notes / a log (the
49
+ * K-test). dol_list_datasets is keyless (no key read, no header).
50
+ * [P1] catalog: totalAvailable = meta.total_count (the API's real catalog total)
51
+ * for an unfiltered scan; when a CLIENT-SIDE filter is applied it is the
52
+ * filtered-set size (exact — the whole catalog is fetched in one page). data:
53
+ * totalAvailable = a real count field when present, else null; offset/limit
54
+ * pagination in both.
55
+ * [P2] data: 401/403 (missing/invalid key) ⇒ invalid_input reclassified with the
56
+ * DOL_API_KEY guidance (the AWS "Missing Authentication Token" is a key problem
57
+ * here) — never empty. 400 ⇒ invalid_input. empty rows ⇒ honest empty
58
+ * (returned:0). 429 ⇒ rate_limited THROW (Retry-After honored). 5xx/timeout ⇒
59
+ * upstream_unavailable THROW. 200 non-JSON ⇒ schema_drift.
60
+ * [P3] records verbatim (null-never-0 comes for free — JSON preserves 0/null and
61
+ * every original field name; the tool never introduces a fabricated 0).
62
+ * [P4] the expected array (catalog `datasets`; data's row array) absent/non-array
63
+ * ⇒ driftError. A ToolErrorCarrier (401/5xx/…) is a P2 outcome, rethrown
64
+ * BEFORE the drift check.
65
+ * [SSRF] fixed host `apiprod.dol.gov` + a post-construction hostname/https assert +
66
+ * `redirect:"error"`; agency/endpoint charclass `^[A-Za-z0-9_]+$` (they ride
67
+ * in the PATH); limit/offset integers; filterValue/fields ride URLSearchParams;
68
+ * the key rides the X-API-KEY header only.
69
+ */
70
+ import { num } from "./coerce.js";
71
+ import { type MetaBundle } from "./meta.js";
72
+ export { num };
73
+ export declare const DOL_HOST = "apiprod.dol.gov";
74
+ /** Read DOL_API_KEY from env; trim; return the value or undefined (unset/blank). */
75
+ export declare function dolApiKey(): string | undefined;
76
+ export type DolListDatasetsArgs = {
77
+ agency?: string;
78
+ query?: string;
79
+ limit?: number;
80
+ offset?: number;
81
+ };
82
+ export type DolDataset = {
83
+ name: string | null;
84
+ tablename: string | null;
85
+ apiUrl: string | null;
86
+ agency: string | null;
87
+ agencyAbbr: string | null;
88
+ description: string | null;
89
+ frequency: string | null;
90
+ datasetType: number | null;
91
+ category: string | null;
92
+ };
93
+ /**
94
+ * List the DOL Data API v4 dataset catalog (KEYLESS). Fetches the WHOLE catalog in
95
+ * one page, applies optional CLIENT-SIDE `agency` (abbr/name) + `query` (substring over
96
+ * name/description/tags) filters, then offset/limit-slices the filtered set. Honest
97
+ * `_meta`: totalAvailable = the catalog's real total (unfiltered) or the filtered-set
98
+ * size (exact — the whole catalog is in hand); offset pagination over the filtered set.
99
+ */
100
+ export declare function listDatasets(args: DolListDatasetsArgs): Promise<MetaBundle>;
101
+ export type DolGetDatasetArgs = {
102
+ agency?: string;
103
+ table?: string;
104
+ limit?: number;
105
+ offset?: number;
106
+ filterField?: string;
107
+ filterValue?: string;
108
+ fields?: string[];
109
+ };
110
+ /**
111
+ * Fetch records from one DOL dataset (KEY-REQUIRED). `agency` (abbr) + `table` (the
112
+ * dataset's api_url endpoint) ride the request PATH; limit/offset/filter ride the query;
113
+ * the DOL_API_KEY rides the X-API-KEY header ONLY. Records are surfaced VERBATIM. Honest
114
+ * `_meta`: totalAvailable is a real count field when present, else null (offset
115
+ * pagination). Unset key ⇒ invalid_input THROW pre-fetch (0 fetch).
116
+ */
117
+ export declare function getDataset(args: DolGetDatasetArgs): Promise<MetaBundle>;
118
+ //# sourceMappingURL=dol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dol.d.ts","sourceRoot":"","sources":["../src/dol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAIH,OAAO,EAAE,GAAG,EAAO,MAAM,aAAa,CAAC;AACvC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAIzE,OAAO,EAAE,GAAG,EAAE,CAAC;AAGf,eAAO,MAAM,QAAQ,oBAAoB,CAAC;AAkC1C,oFAAoF;AACpF,wBAAgB,SAAS,IAAI,MAAM,GAAG,SAAS,CAI9C;AAyBD,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAoBF;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiGjF;AAGD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAyK7E"}