@cliwant/mcp-sam-gov 1.9.0 → 1.10.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.
@@ -0,0 +1,68 @@
1
+ /**
2
+ * arcgis-feature.ts — generic ArcGIS REST FeatureServer/MapServer layer query
3
+ * over a CURATED service allowlist (loop — SLED bid campaign, 2026-07-19).
4
+ *
5
+ * WHAT IT ADDS: an enormous amount of US government data (esp. SLED procurement /
6
+ * GIS / infrastructure) is published as ArcGIS REST feature layers. This is the
7
+ * QUERY companion to `arcgis_hub_discover_datasets` (which DISCOVERS Hub datasets):
8
+ * it queries the ROWS of a curated set of high-value ArcGIS layers. First payload:
9
+ * the DC OCP "PASS" procurement layers (solicitations / contracts / purchase
10
+ * orders / payments) — DC's live open-solicitation feed with 46 fields, keyless.
11
+ *
12
+ * The module REUSES `getJson` (redirect:"error") / `driftError` / `num`·`str` /
13
+ * `withMeta`·`buildMeta`, mirroring socrata.ts (curated allowlist) + datagov-
14
+ * catalog.ts. KEYLESS.
15
+ *
16
+ * ★ SSRF: the caller supplies a `service` ENUM key (never a free host/URL); each
17
+ * allowlist entry is a FIXED, live-verified ArcGIS layer base URL. The query
18
+ * URL is built on that fixed base, and a post-construction assertion requires
19
+ * the built hostname === the allowlist entry's host (over https) BEFORE the
20
+ * fetch; `redirect:"error"`. `where`/`outFields`/`orderByFields` ride in
21
+ * URLSearchParams (encoded) — they filter/project the read-only layer and
22
+ * CANNOT alter the host (a malformed `where` ⇒ upstream 400 ⇒ invalid_input,
23
+ * surfaced, never silent). Adding a service later = an allowlist SOURCE edit +
24
+ * a live count/`/query` verification — NEVER a free runtime param.
25
+ *
26
+ * ★ HONESTY PILLARS (captured live 2026-07-19):
27
+ * P1: totalAvailable = the layer's EXACT match count (a `returnCountOnly=true`
28
+ * companion query), NEVER the page length. Best-effort: a count failure ⇒
29
+ * totalAvailable:null + a note (the rows are still returned) — never a fake
30
+ * total. `exceededTransferLimit` also forces hasMore.
31
+ * P2: getJson THROWS on 429/5xx/timeout; an ArcGIS `{error:{code,message}}`
32
+ * body (HTTP 200) is classified (400 ⇒ invalid_input, else upstream) and
33
+ * THROWN — never a fake empty. A genuine no-match (features:[], count:0) ⇒
34
+ * honest empty.
35
+ * P3: attributes pass through VERBATIM (they are the layer's own record). NOTE
36
+ * (disclosed every response): ArcGIS date fields are epoch MILLISECONDS and a
37
+ * negative/sentinel value (e.g. -2209093200000 ≈ year 1900) is a placeholder,
38
+ * not a real date — surfaced verbatim, never coerced.
39
+ * P4: `body.features` absent/non-array (and no error) ⇒ driftError; a 200
40
+ * non-JSON body ⇒ schema_drift via the catch-ladder.
41
+ */
42
+ import { num } from "./coerce.js";
43
+ import { type MetaBundle } from "./meta.js";
44
+ export { num };
45
+ export type ArcgisService = {
46
+ key: string;
47
+ base: string;
48
+ label: string;
49
+ note: string;
50
+ };
51
+ export declare const ARCGIS_SERVICES: readonly ArcgisService[];
52
+ export type ArcgisFeatureQueryArgs = {
53
+ service: string;
54
+ where?: string;
55
+ outFields?: string;
56
+ orderByFields?: string;
57
+ limit?: number;
58
+ offset?: number;
59
+ };
60
+ /**
61
+ * Query rows from a curated ArcGIS REST feature layer. `service` is an allowlist
62
+ * enum; `where` (SQL-ish filter, default 1=1), `outFields` (default *),
63
+ * `orderByFields`, `limit`/`offset`. Returns curated record attributes + honest
64
+ * `_meta` (totalAvailable = the layer's exact match count via a returnCountOnly
65
+ * companion; epoch-ms date disclosure).
66
+ */
67
+ export declare function featureQuery(args: ArcgisFeatureQueryArgs): Promise<MetaBundle>;
68
+ //# sourceMappingURL=arcgis-feature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arcgis-feature.d.ts","sourceRoot":"","sources":["../src/arcgis-feature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAEzE,OAAO,EAAE,GAAG,EAAE,CAAC;AAMf,MAAM,MAAM,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACvF,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EA2B1C,CAAC;AAmDX,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmFpF"}
@@ -0,0 +1,206 @@
1
+ /**
2
+ * arcgis-feature.ts — generic ArcGIS REST FeatureServer/MapServer layer query
3
+ * over a CURATED service allowlist (loop — SLED bid campaign, 2026-07-19).
4
+ *
5
+ * WHAT IT ADDS: an enormous amount of US government data (esp. SLED procurement /
6
+ * GIS / infrastructure) is published as ArcGIS REST feature layers. This is the
7
+ * QUERY companion to `arcgis_hub_discover_datasets` (which DISCOVERS Hub datasets):
8
+ * it queries the ROWS of a curated set of high-value ArcGIS layers. First payload:
9
+ * the DC OCP "PASS" procurement layers (solicitations / contracts / purchase
10
+ * orders / payments) — DC's live open-solicitation feed with 46 fields, keyless.
11
+ *
12
+ * The module REUSES `getJson` (redirect:"error") / `driftError` / `num`·`str` /
13
+ * `withMeta`·`buildMeta`, mirroring socrata.ts (curated allowlist) + datagov-
14
+ * catalog.ts. KEYLESS.
15
+ *
16
+ * ★ SSRF: the caller supplies a `service` ENUM key (never a free host/URL); each
17
+ * allowlist entry is a FIXED, live-verified ArcGIS layer base URL. The query
18
+ * URL is built on that fixed base, and a post-construction assertion requires
19
+ * the built hostname === the allowlist entry's host (over https) BEFORE the
20
+ * fetch; `redirect:"error"`. `where`/`outFields`/`orderByFields` ride in
21
+ * URLSearchParams (encoded) — they filter/project the read-only layer and
22
+ * CANNOT alter the host (a malformed `where` ⇒ upstream 400 ⇒ invalid_input,
23
+ * surfaced, never silent). Adding a service later = an allowlist SOURCE edit +
24
+ * a live count/`/query` verification — NEVER a free runtime param.
25
+ *
26
+ * ★ HONESTY PILLARS (captured live 2026-07-19):
27
+ * P1: totalAvailable = the layer's EXACT match count (a `returnCountOnly=true`
28
+ * companion query), NEVER the page length. Best-effort: a count failure ⇒
29
+ * totalAvailable:null + a note (the rows are still returned) — never a fake
30
+ * total. `exceededTransferLimit` also forces hasMore.
31
+ * P2: getJson THROWS on 429/5xx/timeout; an ArcGIS `{error:{code,message}}`
32
+ * body (HTTP 200) is classified (400 ⇒ invalid_input, else upstream) and
33
+ * THROWN — never a fake empty. A genuine no-match (features:[], count:0) ⇒
34
+ * honest empty.
35
+ * P3: attributes pass through VERBATIM (they are the layer's own record). NOTE
36
+ * (disclosed every response): ArcGIS date fields are epoch MILLISECONDS and a
37
+ * negative/sentinel value (e.g. -2209093200000 ≈ year 1900) is a placeholder,
38
+ * not a real date — surfaced verbatim, never coerced.
39
+ * P4: `body.features` absent/non-array (and no error) ⇒ driftError; a 200
40
+ * non-JSON body ⇒ schema_drift via the catch-ladder.
41
+ */
42
+ import { ToolErrorCarrier } from "./errors.js";
43
+ import { getJson, driftError } from "./datasource.js";
44
+ import { num } from "./coerce.js";
45
+ import { withMeta } from "./meta.js";
46
+ export { num };
47
+ export const ARCGIS_SERVICES = [
48
+ { key: "dc_pass_solicitations", base: "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Government_Operations/MapServer/19", label: "DC OCP PASS — Solicitations", note: "DC Office of Contracting & Procurement live solicitations (SOLICITATIONNUMBER, SOLICITATIONTITLE, DUE_DATE, OPENDATE, CLOSEDATE, NIGPCODE, CONTRACTINGOFFICER, AWARD_TO…). ~25k." },
49
+ { key: "dc_pass_contracts", base: "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Government_Operations/MapServer/37", label: "DC OCP PASS — Contracts", note: "DC awarded contracts. ~50k." },
50
+ { key: "dc_pass_purchase_orders", base: "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Government_Operations/MapServer/16", label: "DC OCP PASS — Purchase Orders", note: "DC purchase orders. ~275k." },
51
+ { key: "dc_pass_payments", base: "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Government_Operations/MapServer/17", label: "DC OCP PASS — Payments", note: "DC vendor payments. ~1.55M." },
52
+ // ── Additional US local-gov procurement layers (loop, 2026-07-19) — found via
53
+ // arcgis_hub_discover_datasets, each on a reachable Esri-hosted services*.
54
+ // arcgis.com endpoint (NOT WAF-gated) + live-verified returnCountOnly. ──
55
+ { key: "asheville_purchase_orders", base: "https://services.arcgis.com/aJ16ENn1AaqdFlqx/ArcGIS/rest/services/Financials/FeatureServer/9", label: "Asheville NC — Purchase Order Line Items", note: "City of Asheville NC purchase-order line items. ~62.8k." },
56
+ { key: "asheville_po_summary", base: "https://services.arcgis.com/aJ16ENn1AaqdFlqx/ArcGIS/rest/services/Financials/FeatureServer/8", label: "Asheville NC — Purchase Order Summary", note: "City of Asheville NC purchase-order summary. ~18.7k." },
57
+ { key: "bellevue_vendor_payments", base: "https://services1.arcgis.com/EYzEZbDhXZjURPbP/arcgis/rest/services/YTD_Vendor_Payments/FeatureServer/0", label: "Bellevue WA — YTD Vendor Payments", note: "City of Bellevue WA year-to-date vendor payments. ~15.8k." },
58
+ { key: "bellevue_awarded_contracts", base: "https://services1.arcgis.com/EYzEZbDhXZjURPbP/arcgis/rest/services/Annual_Awarded_Contracts/FeatureServer/0", label: "Bellevue WA — Annual Awarded Contracts", note: "City of Bellevue WA annual awarded contracts. ~2.0k." },
59
+ { key: "miamidade_purchase_orders_2017", base: "https://services.arcgis.com/8Pc9XBTAsYuxx9Ny/arcgis/rest/services/miamidade_procurement_data_2017/FeatureServer/0", label: "Miami-Dade County FL — Purchase Orders (ADPICS) 2017", note: "Miami-Dade County FL purchase orders — a 2017 snapshot (historical, disclosed). ~94.8k." },
60
+ { key: "miamidade_purchase_orders_2025", base: "https://services.arcgis.com/8Pc9XBTAsYuxx9Ny/arcgis/rest/services/miamidade_procurement_data_informs_2025/FeatureServer/0", label: "Miami-Dade County FL — Purchase Orders (INFORMS) 2025", note: "Miami-Dade County FL purchase orders — CURRENT (2025 INFORMS). ~25k." },
61
+ { key: "suffolk_county_ny_contracts_2018", base: "https://services.arcgis.com/JsDD4qdG5r2a7hR5/arcgis/rest/services/County_Contracts_2018/FeatureServer/0", label: "Suffolk County NY — County Contracts 2018", note: "Suffolk County NY contracts — a 2018 snapshot (historical, disclosed). ~1.4k." },
62
+ { key: "matsu_borough_ak_checkbook", base: "https://services.arcgis.com/fX5IGselyy1TirdY/arcgis/rest/services/Logos_Checkbook_Data/FeatureServer/0", label: "Matanuska-Susitna Borough AK — Checkbook", note: "Matanuska-Susitna Borough AK checkbook (vendor spend). ~1.2k." },
63
+ { key: "lasvegas_checkbook", base: "https://services1.arcgis.com/F1v0ufATbBQScMtY/arcgis/rest/services/Processed_Checkbook_Table_View/FeatureServer/0", label: "City of Las Vegas NV — Open Checkbook", note: "City of Las Vegas NV checkbook (DEPARTMENT/VENDOR/TRANSACTION_AMOUNT/TRANSACTION_DATE). ~373k." },
64
+ { key: "baltimore_checkbook", base: "https://services1.arcgis.com/UWYHeuuJISiGmgXx/arcgis/rest/services/OpenCheckbookFY2022_Through_Present/FeatureServer/0", label: "Baltimore City MD — Open Checkbook (FY2022–present)", note: "Baltimore City MD checkbook, FY2022 through present (Supplier_Name/Payment_Amount/Supplier_Contract_Number/Agency). ~367k." },
65
+ { key: "naperville_vendor_payments", base: "https://services1.arcgis.com/rXJ6QApc2sOtl1Pd/arcgis/rest/services/Expenditures_2018_to_Current_Year/FeatureServer/0", label: "City of Naperville IL — Vendor Payments (2018–current)", note: "City of Naperville IL vendor payments, 2018 to current year (Vendor_Name/Payment_Amount/Purch_Order_No/Check_Date). ~127k." },
66
+ { key: "worcester_ma_checkbook_fy25", base: "https://services1.arcgis.com/j8dqo2DJE7mVUBU1/arcgis/rest/services/Fiscal_Year_2025_Open_Checkbook/FeatureServer/0", label: "City of Worcester MA — Open Checkbook FY2025", note: "City of Worcester MA open checkbook, FY2025 (Payee/Pmt_Amount/Departments/Spend_Categories/Pmt_Date). ~38k." },
67
+ { key: "lasvegas_purchasing_contracts", base: "https://services1.arcgis.com/F1v0ufATbBQScMtY/arcgis/rest/services/Purchasing_Contracts__view/FeatureServer/0", label: "City of Las Vegas NV — Purchasing Contracts", note: "City of Las Vegas NV purchasing contract register (Project_Number/Project_Name/Supplier_Name/Award_Date/Termination_Date). ~3.4k, all with a supplier. Complements lasvegas_checkbook (payments)." },
68
+ { key: "txdot_construction_projects", base: "https://services.arcgis.com/KTcxiTD9dsQw4r7Z/arcgis/rest/services/TxDOT_Projects_Info_All/FeatureServer/0", label: "Texas DOT — Projects (with awarded construction company)", note: "Texas Department of Transportation highway projects (CNSTR_CMPNY_NM awarded construction company/CNSTR_NTPD_DT notice-to-proceed/TYPE_OF_WORK/HWY_NBR). ~85k rows, ~14.1k with an awarded contractor (filter CNSTR_CMPNY_NM IS NOT NULL for awards)." },
69
+ { key: "akdot_construction_awards", base: "https://services.arcgis.com/r4A0V7UzH9fcLVvv/arcgis/rest/services/AWARDS_DASHBOARD_AWARDS_TABLE_POINTS/FeatureServer/0", label: "Alaska DOT&PF — Construction Bid Awards", note: "Alaska Department of Transportation & Public Facilities construction bid awards (ContractID/Contractor/Bids_Received/Engineer_Est/Award_Amount/Advertised_Date/Bid_Opening_Date/Reg_Award_Date/DOT_Region). ~1.1k." },
70
+ { key: "akdot_aashtoware_proposals", base: "https://services.arcgis.com/r4A0V7UzH9fcLVvv/arcgis/rest/services/AASHTOWARE_Proposals_Points/FeatureServer/0", label: "Alaska DOT&PF — AASHTOWARE Proposals/Lettings", note: "Alaska DOT&PF AASHTOWARE proposals & lettings with awarded vendor (ProposalId/LettingId/StateProjectNumber/RefVendor_LongName/RefVendor_DBECertStatus/AwardedAmount/Contract_ID/FundType). ~1k, ~1,035 with an AwardedAmount>0. Complements akdot_construction_awards." },
71
+ { key: "iowadot_public_bid_awards", base: "https://services.arcgis.com/8lRhdTsQyJpO52F1/arcgis/rest/services/Project_Scheduling_Public_Bid_Point_View/FeatureServer/0", label: "Iowa DOT — Public Bid / Project Scheduling", note: "Iowa Department of Transportation public bid & project scheduling (PROJECT_NUMBER/WORK_DESC/LETTING_FISCAL_YEAR/PROGRAM_ESTIMATE/CONTRACT_AWARDED/AWARDED/FINAL_CONTRACT/CONTRACTOR/CONTRACT_ID/STATUS). ~360." },
72
+ { key: "okdot_cirb_contract_status", base: "https://services6.arcgis.com/RBtoEUQ2lmN0K3GY/arcgis/rest/services/CIRB_Project_Status/FeatureServer/0", label: "Oklahoma DOT — CIRB Contract Status", note: "Oklahoma Department of Transportation County Improvements for Roads & Bridges (CIRB) contract status (T_PROJECT/JP_DESCRIPTION/A_CONTRACT_AMOUNT/A_AMOUNT_EARNED/A_AMOUNT_PAID/D_LET_DATE/D_CONTRACT_AWARD_DATE/D_WORK_ORDER_DATE). ~780." },
73
+ { key: "topeka_checkbook_aggregate", base: "https://services1.arcgis.com/EvtgI3PZ9PyCGJZS/arcgis/rest/services/Checkbook_Aggregate/FeatureServer/0", label: "City of Topeka KS — Open Checkbook (aggregate FY2015–2023)", note: "City of Topeka KS open checkbook, all years aggregated FY2015–2023 (fiscal_year/fiscal_period/department/program/fund/vendor_name/description/dollars). ~332k rows, ~323.6k with dollars>0." },
74
+ ];
75
+ const SERVICE_BY_KEY = new Map(ARCGIS_SERVICES.map((s) => [s.key, s]));
76
+ const DATE_NOTE = "ArcGIS date fields are epoch MILLISECONDS (e.g. DUE_DATE, OPENDATE, CLOSEDATE); a negative/sentinel value (≈ year 1900) is a placeholder, not a real date — surfaced verbatim, parse client-side.";
77
+ // ─── SSRF-guarded fetch (fixed allowlist base + hostname assertion) ──
78
+ async function getArcgisQuery(svc, params) {
79
+ const url = `${svc.base}/query?${params.toString()}`;
80
+ const allowedHost = new URL(svc.base).hostname;
81
+ const built = new URL(url);
82
+ if (built.hostname !== allowedHost || built.protocol !== "https:") {
83
+ throw new ToolErrorCarrier({
84
+ kind: "invalid_input",
85
+ message: `Constructed ArcGIS URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the allowlisted service host ${JSON.stringify(allowedHost)} over https — refusing to fetch (SSRF safety).`,
86
+ retryable: false,
87
+ upstreamEndpoint: `arcgis:${svc.key}`,
88
+ });
89
+ }
90
+ return getJson(url, { label: `arcgis:${svc.key}`, redirect: "error" });
91
+ }
92
+ // ArcGIS returns HTTP 200 + `{error:{code,message}}` for BOTH a bad query AND a
93
+ // transient server hiccup, and the messages OVERLAP (live-verified 2026-07-19):
94
+ // - "Failed to execute query." ⇒ a bad column/field/where (the caller's query).
95
+ // - "Unable to complete operation." ⇒ AMBIGUOUS — a bad SQL syntax OR a transient
96
+ // server blip (observed intermittently on a valid query).
97
+ // So classify a clearly-input message as invalid_input (non-retryable), but treat
98
+ // the ambiguous/generic one as upstream_unavailable (RETRYABLE) — misclassifying a
99
+ // server blip as invalid_input would falsely blame the caller for an outage.
100
+ const ARCGIS_INPUT_ERROR_RE = /failed to execute query|invalid|parameter|field|syntax|parse|not supported|does not exist|cannot find|out of range/i;
101
+ /** An ArcGIS `{error:{code,message}}` body (HTTP 200) ⇒ throw the right kind. */
102
+ function throwIfArcgisError(svc, body) {
103
+ const e = body?.error;
104
+ if (e && typeof e === "object") {
105
+ const code = num(e.code);
106
+ const msg = String(e.message ?? "ArcGIS query error");
107
+ const inputish = code === 400 && ARCGIS_INPUT_ERROR_RE.test(msg);
108
+ throw new ToolErrorCarrier({
109
+ kind: inputish ? "invalid_input" : "upstream_unavailable",
110
+ message: `ArcGIS ${svc.key} error${code !== null ? ` (${code})` : ""}: ${msg}`.slice(0, 300),
111
+ retryable: !inputish,
112
+ upstreamEndpoint: `arcgis:${svc.key}`,
113
+ });
114
+ }
115
+ }
116
+ /**
117
+ * Query rows from a curated ArcGIS REST feature layer. `service` is an allowlist
118
+ * enum; `where` (SQL-ish filter, default 1=1), `outFields` (default *),
119
+ * `orderByFields`, `limit`/`offset`. Returns curated record attributes + honest
120
+ * `_meta` (totalAvailable = the layer's exact match count via a returnCountOnly
121
+ * companion; epoch-ms date disclosure).
122
+ */
123
+ export async function featureQuery(args) {
124
+ const svc = SERVICE_BY_KEY.get(args.service);
125
+ if (!svc) {
126
+ throw new ToolErrorCarrier({
127
+ kind: "invalid_input",
128
+ message: `Unknown ArcGIS service ${JSON.stringify(args.service)}. Allowed: ${ARCGIS_SERVICES.map((s) => s.key).join(", ")}.`,
129
+ retryable: false,
130
+ });
131
+ }
132
+ const limit = args.limit ?? 50;
133
+ const offset = args.offset ?? 0;
134
+ const where = args.where && args.where.trim() ? args.where.trim() : "1=1";
135
+ const outFields = args.outFields && args.outFields.trim() ? args.outFields.trim() : "*";
136
+ const filtersApplied = ["service"];
137
+ if (args.where && args.where.trim())
138
+ filtersApplied.push("where");
139
+ // ── Primary rows query. ──
140
+ const rowParams = new URLSearchParams();
141
+ rowParams.set("where", where);
142
+ rowParams.set("outFields", outFields);
143
+ rowParams.set("returnGeometry", "false");
144
+ rowParams.set("resultOffset", String(offset));
145
+ rowParams.set("resultRecordCount", String(limit));
146
+ if (args.orderByFields && args.orderByFields.trim())
147
+ rowParams.set("orderByFields", args.orderByFields.trim());
148
+ rowParams.set("f", "json");
149
+ let body;
150
+ try {
151
+ body = await getArcgisQuery(svc, rowParams);
152
+ }
153
+ catch (e) {
154
+ if (e instanceof ToolErrorCarrier)
155
+ throw e;
156
+ if (e instanceof SyntaxError)
157
+ throw driftError(`arcgis:${svc.key}`, "ArcGIS returned a non-JSON body at HTTP 200 — schema drift.");
158
+ throw e;
159
+ }
160
+ throwIfArcgisError(svc, body); // P2: a 200 {error} body ⇒ throw (never a fake empty)
161
+ const b = (body ?? {});
162
+ // P4: features MUST be an array (a missing/non-array, with no error, is drift).
163
+ if (!Array.isArray(b.features)) {
164
+ throw driftError(`arcgis:${svc.key}`, "ArcGIS shape drift — response.features must be an array.");
165
+ }
166
+ const records = b.features.map((f) => f?.attributes ?? {});
167
+ const returned = records.length;
168
+ // ── P1: totalAvailable = the layer's exact match count (returnCountOnly
169
+ // companion). BEST-EFFORT: any count failure ⇒ null + note (rows kept). ──
170
+ let totalAvailable = null;
171
+ let countNote = null;
172
+ try {
173
+ const countParams = new URLSearchParams();
174
+ countParams.set("where", where);
175
+ countParams.set("returnCountOnly", "true");
176
+ countParams.set("f", "json");
177
+ const cbody = await getArcgisQuery(svc, countParams);
178
+ throwIfArcgisError(svc, cbody);
179
+ const c = num(cbody?.count);
180
+ if (c !== null)
181
+ totalAvailable = c;
182
+ else
183
+ countNote = "The returnCountOnly companion returned an unexpected shape — totalAvailable is unknown (null); the rows are still complete.";
184
+ }
185
+ catch {
186
+ countNote = "The returnCountOnly companion failed (transient) — totalAvailable is unknown (null); the rows are still returned.";
187
+ }
188
+ const exceeded = b.exceededTransferLimit === true;
189
+ const hasMore = totalAvailable !== null ? offset + returned < totalAvailable : (exceeded || returned >= limit);
190
+ const nextOffset = hasMore ? offset + returned : null;
191
+ const notes = [`Source: ${svc.label} (ArcGIS REST feature layer, keyless). ${svc.note}`, DATE_NOTE];
192
+ if (countNote)
193
+ notes.push(countNote);
194
+ return withMeta({ service: svc.key, records }, {
195
+ source: `${new URL(svc.base).hostname} via ArcGIS REST (keyless)`,
196
+ keylessMode: true,
197
+ returned,
198
+ totalAvailable,
199
+ filtersApplied,
200
+ filtersDropped: [],
201
+ fieldsUnavailable: [],
202
+ pagination: { offset, limit, hasMore, nextOffset },
203
+ notes,
204
+ });
205
+ }
206
+ //# sourceMappingURL=arcgis-feature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arcgis-feature.js","sourceRoot":"","sources":["../src/arcgis-feature.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * arcgis-hub.ts — ArcGIS Hub dataset DISCOVERY (`hub.arcgis.com/api/v3/datasets`),
3
+ * a NEW keyless-first SLED-discovery platform (loop cycle 9).
4
+ *
5
+ * WHAT IT ADDS: a large fraction of US state/local/regional/tribal (SLED) open
6
+ * data — especially GIS / infrastructure / permits / boundaries / procurement —
7
+ * is published on ArcGIS Hub, NOT on Socrata or CKAN. This tool DISCOVERS those
8
+ * datasets (keyword search over the whole Hub) so a B2G researcher can find what
9
+ * fragmented local data exists. It is the ArcGIS sibling of
10
+ * `socrata_discover_datasets` / `datagov_search_datasets`.
11
+ *
12
+ * The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
13
+ * (redirect:"error") / `driftError` / `num`·`str` (coerce.ts, null-never-0 /
14
+ * null-never-empty-string) / `withMeta`·`buildMeta`, mirroring datagov-catalog.ts.
15
+ * KEYLESS: no key seam (no token is ever read/sent).
16
+ *
17
+ * ★ SSRF: the host is a compile-time literal (`ARCGIS_HUB_HOST`). Every input
18
+ * (`q` / `filter[openData]` / `page[size]` / `page[start]`) rides in a
19
+ * MODULE-BUILT `URLSearchParams` assembled key-by-key from validated typed args
20
+ * — NO raw-query / raw-host passthrough (this is a FIXED-host catalog search, so
21
+ * there is no per-jurisdiction host vector like socrata_query has). A
22
+ * post-construction hostname/protocol assertion + `redirect:"error"` lock it.
23
+ *
24
+ * ★ PROVENANCE (the crux — different trust posture from our other sources): ArcGIS
25
+ * Hub is a GLOBAL, OPEN publishing platform. Anyone — a US city, a foreign
26
+ * government, an NGO, a company — can publish, so results are NOT all US and NOT
27
+ * all governmental. This tool is therefore explicitly a DISCOVERY aid, NOT a
28
+ * curated official-source allowlist (unlike `socrata_query`, whose hosts are a
29
+ * vetted allowlist). Each result surfaces `owner`/`orgName`/`source`/`region`
30
+ * VERBATIM so the consumer can VET the publisher, and every response carries the
31
+ * global-platform disclosure. `openDataOnly` (default true) biases toward items
32
+ * the publisher designated as open data. DISCOVERY ONLY — to read rows, follow
33
+ * the dataset on its own ArcGIS endpoint (a guarded row-query tool is a planned
34
+ * addition; ArcGIS feature services live on arbitrary hosts, which needs its own
35
+ * SSRF design).
36
+ *
37
+ * ★ HONESTY PILLARS (captured live 2026-07-19):
38
+ * P1 (real total): the v3 response carries `meta.total` (== `meta.stats.totalCount`)
39
+ * — the EXACT match count across the Hub. `totalAvailable = num(meta.total)`
40
+ * (NEVER data.length); a page is `truncated` when `offset + returned < total`.
41
+ * Pagination is a 1-based record offset (`page[start] = offset + 1`).
42
+ * P2: getJson→fetchWithRetry THROWS on 429 (rate_limited) / 5xx
43
+ * (upstream_unavailable) / timeout — NEVER a fake empty. A genuine no-match
44
+ * (`data:[]`, total 0) ⇒ honest empty (datasets:[], returned:0, total:0,
45
+ * complete:true).
46
+ * P3: every scalar via `str` (null-never-empty) / booleans preserved / `num`
47
+ * for counts (null-never-0). A missing owner/source/region is null, never "".
48
+ * P4: `body.data` absent/non-array ⇒ driftError; a 200 non-JSON body ⇒
49
+ * schema_drift via the catch-ladder (ToolErrorCarrier rethrow FIRST so a
50
+ * 429/5xx keeps its taxonomy → SyntaxError→driftError → bare rethrow).
51
+ */
52
+ import { num } from "./coerce.js";
53
+ import { type MetaBundle } from "./meta.js";
54
+ export { num };
55
+ export declare const ARCGIS_HUB_HOST = "hub.arcgis.com";
56
+ export type HubDataset = {
57
+ id: string | null;
58
+ name: string | null;
59
+ description: string | null;
60
+ owner: string | null;
61
+ orgName: string | null;
62
+ source: string | null;
63
+ region: string | null;
64
+ type: string | null;
65
+ sector: string | null;
66
+ keywords: string[];
67
+ downloadable: boolean | null;
68
+ hasApi: boolean | null;
69
+ created: string | null;
70
+ modified: string | null;
71
+ landingPage: string | null;
72
+ itemId: string | null;
73
+ };
74
+ export type ArcgisHubDiscoverArgs = {
75
+ query: string;
76
+ openDataOnly?: boolean;
77
+ limit?: number;
78
+ offset?: number;
79
+ };
80
+ /**
81
+ * Search ArcGIS Hub datasets by keyword. Filters: `query` (→q, REQUIRED, ≥2
82
+ * non-whitespace chars), `openDataOnly` (default true → filter[openData]=true,
83
+ * biases toward publisher-designated open data), `limit` (→page[size]), `offset`
84
+ * (→page[start]=offset+1, 1-based). The query is MODULE-BUILT from validated typed
85
+ * args (NO raw passthrough). Returns curated dataset rows + honest `_meta`:
86
+ * totalAvailable = the EXACT Hub match count (meta.total, P1), the global-platform
87
+ * provenance disclosure, and per-row publisher fields for vetting.
88
+ */
89
+ export declare function discoverDatasets(args: ArcgisHubDiscoverArgs): Promise<MetaBundle>;
90
+ //# sourceMappingURL=arcgis-hub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arcgis-hub.d.ts","sourceRoot":"","sources":["../src/arcgis-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;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,eAAe,mBAAmB,CAAC;AAahD,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAkEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,UAAU,CAAC,CA6FrB"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * arcgis-hub.ts — ArcGIS Hub dataset DISCOVERY (`hub.arcgis.com/api/v3/datasets`),
3
+ * a NEW keyless-first SLED-discovery platform (loop cycle 9).
4
+ *
5
+ * WHAT IT ADDS: a large fraction of US state/local/regional/tribal (SLED) open
6
+ * data — especially GIS / infrastructure / permits / boundaries / procurement —
7
+ * is published on ArcGIS Hub, NOT on Socrata or CKAN. This tool DISCOVERS those
8
+ * datasets (keyword search over the whole Hub) so a B2G researcher can find what
9
+ * fragmented local data exists. It is the ArcGIS sibling of
10
+ * `socrata_discover_datasets` / `datagov_search_datasets`.
11
+ *
12
+ * The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
13
+ * (redirect:"error") / `driftError` / `num`·`str` (coerce.ts, null-never-0 /
14
+ * null-never-empty-string) / `withMeta`·`buildMeta`, mirroring datagov-catalog.ts.
15
+ * KEYLESS: no key seam (no token is ever read/sent).
16
+ *
17
+ * ★ SSRF: the host is a compile-time literal (`ARCGIS_HUB_HOST`). Every input
18
+ * (`q` / `filter[openData]` / `page[size]` / `page[start]`) rides in a
19
+ * MODULE-BUILT `URLSearchParams` assembled key-by-key from validated typed args
20
+ * — NO raw-query / raw-host passthrough (this is a FIXED-host catalog search, so
21
+ * there is no per-jurisdiction host vector like socrata_query has). A
22
+ * post-construction hostname/protocol assertion + `redirect:"error"` lock it.
23
+ *
24
+ * ★ PROVENANCE (the crux — different trust posture from our other sources): ArcGIS
25
+ * Hub is a GLOBAL, OPEN publishing platform. Anyone — a US city, a foreign
26
+ * government, an NGO, a company — can publish, so results are NOT all US and NOT
27
+ * all governmental. This tool is therefore explicitly a DISCOVERY aid, NOT a
28
+ * curated official-source allowlist (unlike `socrata_query`, whose hosts are a
29
+ * vetted allowlist). Each result surfaces `owner`/`orgName`/`source`/`region`
30
+ * VERBATIM so the consumer can VET the publisher, and every response carries the
31
+ * global-platform disclosure. `openDataOnly` (default true) biases toward items
32
+ * the publisher designated as open data. DISCOVERY ONLY — to read rows, follow
33
+ * the dataset on its own ArcGIS endpoint (a guarded row-query tool is a planned
34
+ * addition; ArcGIS feature services live on arbitrary hosts, which needs its own
35
+ * SSRF design).
36
+ *
37
+ * ★ HONESTY PILLARS (captured live 2026-07-19):
38
+ * P1 (real total): the v3 response carries `meta.total` (== `meta.stats.totalCount`)
39
+ * — the EXACT match count across the Hub. `totalAvailable = num(meta.total)`
40
+ * (NEVER data.length); a page is `truncated` when `offset + returned < total`.
41
+ * Pagination is a 1-based record offset (`page[start] = offset + 1`).
42
+ * P2: getJson→fetchWithRetry THROWS on 429 (rate_limited) / 5xx
43
+ * (upstream_unavailable) / timeout — NEVER a fake empty. A genuine no-match
44
+ * (`data:[]`, total 0) ⇒ honest empty (datasets:[], returned:0, total:0,
45
+ * complete:true).
46
+ * P3: every scalar via `str` (null-never-empty) / booleans preserved / `num`
47
+ * for counts (null-never-0). A missing owner/source/region is null, never "".
48
+ * P4: `body.data` absent/non-array ⇒ driftError; a 200 non-JSON body ⇒
49
+ * schema_drift via the catch-ladder (ToolErrorCarrier rethrow FIRST so a
50
+ * 429/5xx keeps its taxonomy → SyntaxError→driftError → bare rethrow).
51
+ */
52
+ import { ToolErrorCarrier } from "./errors.js";
53
+ import { getJson, driftError } from "./datasource.js";
54
+ import { num, str } from "./coerce.js";
55
+ import { withMeta } from "./meta.js";
56
+ // Re-export the shared honesty coercion (single audited copy in ./coerce.js) so
57
+ // the fault suite's num-parity guard resolves `num` from this module too.
58
+ export { num };
59
+ // ─── Fixed endpoint (SSRF core — compile-time CONSTANTS) ──────────
60
+ export const ARCGIS_HUB_HOST = "hub.arcgis.com";
61
+ const ARCGIS_HUB_PATH = "/api/v3/datasets";
62
+ // HOST+path label — surfaces in ToolError.upstreamEndpoint. Keyless: no token can
63
+ // ever appear here (none is read).
64
+ const ARCGIS_HUB_LABEL = "arcgis-hub:/api/v3/datasets";
65
+ const ARCGIS_HUB_SOURCE = "hub.arcgis.com via ArcGIS Hub (keyless; GLOBAL open platform — vet each publisher)";
66
+ // The load-bearing provenance caveat carried on EVERY response.
67
+ const ARCGIS_HUB_PROVENANCE_NOTE = "ArcGIS Hub is a GLOBAL, OPEN publishing platform — results include non-US and non-governmental publishers. This is a DISCOVERY aid, NOT a curated official-source allowlist (unlike socrata_query): VET each result's owner/orgName/source/region before relying on it. DISCOVERY ONLY (metadata + links) — to read rows, follow the dataset on its own ArcGIS endpoint.";
68
+ /** A string[] from a mixed value (drops null/empty via str), else [] when absent/non-array. */
69
+ function strArray(x) {
70
+ if (!Array.isArray(x))
71
+ return [];
72
+ return x.map((v) => str(v)).filter((v) => v !== null);
73
+ }
74
+ /** null-preserving boolean coercion — a real true/false survives; anything else → null (never a fabricated false). */
75
+ function boolOrNull(x) {
76
+ return typeof x === "boolean" ? x : null;
77
+ }
78
+ /**
79
+ * Map ONE `data[]` row → the curated dataset shape. The Hub's JSON:API row is
80
+ * `{ id, type, attributes:{…} }`. Every scalar via `str` (null-never-empty);
81
+ * booleans null-preserving; keywords via strArray. The publisher-identifying
82
+ * fields (owner/orgName/source/region) are surfaced VERBATIM for vetting.
83
+ */
84
+ function mapDataset(row) {
85
+ const r = (row ?? {});
86
+ const a = r.attributes ?? {};
87
+ return {
88
+ id: str(r.id),
89
+ name: str(a.name),
90
+ description: str(a.searchDescription ?? a.description ?? a.snippet),
91
+ owner: str(a.owner),
92
+ orgName: str(a.orgName ?? a.organization),
93
+ source: str(a.source),
94
+ region: str(a.region),
95
+ type: str(a.type),
96
+ sector: str(a.sector),
97
+ keywords: strArray(a.tags),
98
+ downloadable: boolOrNull(a.downloadable),
99
+ hasApi: boolOrNull(a.hasApi),
100
+ created: str(a.created),
101
+ modified: str(a.modified),
102
+ landingPage: str(a.landingPage ?? a.url),
103
+ itemId: str(a.itemId),
104
+ };
105
+ }
106
+ // ─── SSRF-guarded fetch (fixed host + hostname assertion + redirect) ──
107
+ /**
108
+ * GET one ArcGIS Hub v3 JSON resource. All caller params ride in `params`
109
+ * (URLSearchParams, encoded). Builds `https://${ARCGIS_HUB_HOST}${ARCGIS_HUB_PATH}?…`
110
+ * on the FIXED host, asserts the CONSTRUCTED URL's hostname === the host over
111
+ * https (belt-and-suspenders), sets `redirect:"error"` (an off-host 3xx must NOT
112
+ * be followed). KEYLESS — no headers/token.
113
+ */
114
+ async function getHub(params) {
115
+ const qs = params.toString();
116
+ const url = `https://${ARCGIS_HUB_HOST}${ARCGIS_HUB_PATH}${qs ? `?${qs}` : ""}`;
117
+ const built = new URL(url);
118
+ if (built.hostname !== ARCGIS_HUB_HOST || built.protocol !== "https:") {
119
+ throw new ToolErrorCarrier({
120
+ kind: "invalid_input",
121
+ message: `Constructed ArcGIS Hub URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the fixed host ${JSON.stringify(ARCGIS_HUB_HOST)} over https — refusing to fetch (SSRF safety).`,
122
+ retryable: false,
123
+ upstreamEndpoint: ARCGIS_HUB_LABEL,
124
+ });
125
+ }
126
+ return getJson(url, { label: ARCGIS_HUB_LABEL, redirect: "error" });
127
+ }
128
+ /**
129
+ * Search ArcGIS Hub datasets by keyword. Filters: `query` (→q, REQUIRED, ≥2
130
+ * non-whitespace chars), `openDataOnly` (default true → filter[openData]=true,
131
+ * biases toward publisher-designated open data), `limit` (→page[size]), `offset`
132
+ * (→page[start]=offset+1, 1-based). The query is MODULE-BUILT from validated typed
133
+ * args (NO raw passthrough). Returns curated dataset rows + honest `_meta`:
134
+ * totalAvailable = the EXACT Hub match count (meta.total, P1), the global-platform
135
+ * provenance disclosure, and per-row publisher fields for vetting.
136
+ */
137
+ export async function discoverDatasets(args) {
138
+ const limit = args.limit ?? 20;
139
+ const offset = args.offset ?? 0;
140
+ const openDataOnly = args.openDataOnly ?? true;
141
+ // Belt-and-suspenders (behind the server's Zod): a <2-char / whitespace-only q
142
+ // would scan the whole global Hub — reject pre-fetch (0 network call).
143
+ const q = args.query ?? "";
144
+ if (q.trim().length < 2) {
145
+ throw new ToolErrorCarrier({
146
+ kind: "invalid_input",
147
+ message: `ArcGIS Hub 'query' must be at least 2 non-whitespace characters (a broad scan of the entire global Hub is refused).`,
148
+ retryable: false,
149
+ upstreamEndpoint: ARCGIS_HUB_LABEL,
150
+ });
151
+ }
152
+ // Build the query from VALIDATED typed args, key-by-key (SSRF: no raw passthrough).
153
+ const params = new URLSearchParams();
154
+ const filtersApplied = ["query"];
155
+ params.set("q", q);
156
+ params.set("page[size]", String(limit));
157
+ params.set("page[start]", String(offset + 1)); // Hub page[start] is 1-based.
158
+ if (openDataOnly) {
159
+ params.set("filter[openData]", "true");
160
+ filtersApplied.push("openDataOnly");
161
+ }
162
+ // The typed catch-ladder (datagov-catalog shape, VERBATIM): preserve the
163
+ // 429/404/5xx/400/timeout ToolErrorCarrier taxonomy FIRST; reclassify a 200
164
+ // non-JSON SyntaxError to schema_drift SECOND; bare-rethrow LAST.
165
+ let body;
166
+ try {
167
+ body = await getHub(params);
168
+ }
169
+ catch (e) {
170
+ if (e instanceof ToolErrorCarrier)
171
+ throw e;
172
+ if (e instanceof SyntaxError)
173
+ throw driftError(ARCGIS_HUB_LABEL, "ArcGIS Hub returned a non-JSON body at HTTP 200 — schema drift.");
174
+ throw e;
175
+ }
176
+ const b = (body ?? {});
177
+ // P4: `data` MUST be an array (a missing/string/null data is drift, never a
178
+ // fabricated empty — a TypeError must never mask drift as upstream_unavailable).
179
+ if (!Array.isArray(b.data)) {
180
+ throw driftError(ARCGIS_HUB_LABEL, "ArcGIS Hub shape drift — /api/v3/datasets response.data must be an array.");
181
+ }
182
+ const datasets = b.data.map(mapDataset);
183
+ const returned = datasets.length;
184
+ // P1: totalAvailable = the EXACT Hub match count (meta.total), NEVER data.length.
185
+ // meta.total (== meta.stats.totalCount) is the primary total; if absent → null
186
+ // (best-effort, never a fabricated count). num() is null-never-0.
187
+ const meta = (b.meta ?? {});
188
+ const totalAvailable = num(meta.total ?? meta.stats?.totalCount);
189
+ const hasMore = totalAvailable !== null ? offset + returned < totalAvailable : returned >= limit;
190
+ const nextOffset = hasMore ? offset + returned : null;
191
+ const notes = [ARCGIS_HUB_PROVENANCE_NOTE];
192
+ notes.push(openDataOnly
193
+ ? "openDataOnly=true: results are filtered to items the publisher designated as open data (filter[openData]=true). Set openDataOnly=false to broaden to all shared items."
194
+ : "openDataOnly=false: ALL shared Hub items are searched (not only designated open data) — vetting the publisher matters even more.");
195
+ if (totalAvailable === null) {
196
+ notes.push("totalAvailable is unknown (the Hub omitted meta.total on this response); completeness is inferred from page fullness (returned < limit ⇒ last page).");
197
+ }
198
+ return withMeta({ query: q, openDataOnly, datasets }, {
199
+ source: ARCGIS_HUB_SOURCE,
200
+ keylessMode: true,
201
+ returned,
202
+ totalAvailable,
203
+ filtersApplied,
204
+ filtersDropped: [],
205
+ fieldsUnavailable: [],
206
+ pagination: { offset, limit, hasMore, nextOffset },
207
+ notes,
208
+ });
209
+ }
210
+ //# sourceMappingURL=arcgis-hub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arcgis-hub.js","sourceRoot":"","sources":["../src/arcgis-hub.ts"],"names":[],"mappings":"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"}