// Static-Space shim: answer the playground's own /api/* and /sample routes // in the browser by calling the Socrata APIs directly (they send // Access-Control-Allow-Origin: *). This keeps index.html identical to the // FastAPI-served version — only the transport changes. (function () { const DOMAIN = "data.cityofnewyork.us"; const ID_RE = /^[a-z0-9]{4}-[a-z0-9]{4}$/; const MAX_LIMIT = 1000; const SAMPLES = [ { title: "311 complaints by borough, 2026", tool: "query_dataset", args: { dataset_id: "erm2-nwe9", select: "borough, count(*) as n", where: "created_date > '2026-01-01T00:00:00'", group: "borough", order: "n DESC", }, }, { title: "Search the catalog for squirrels", tool: "search_datasets", args: { query: "squirrel census" } }, { title: "Top complaint types this year", tool: "profile_column", args: { dataset_id: "erm2-nwe9", column: "complaint_type", top: 10 }, }, { title: "Schema of the 2015 Street Tree Census", tool: "get_schema", args: { dataset_id: "uvpi-gqnh" } }, ]; const ok = (body) => new Response(JSON.stringify(body), { status: 200, headers: { "Content-Type": "application/json" } }); const fail = (status, detail) => new Response(JSON.stringify({ detail }), { status, headers: { "Content-Type": "application/json" } }); function checkId(id) { if (!ID_RE.test((id || "").trim().toLowerCase())) throw { status: 422, detail: `invalid dataset id '${id}': expected the Socrata 4x4 form, e.g. 'erm2-nwe9'` }; return id.trim().toLowerCase(); } const clamp = (v, dflt) => Math.max(1, Math.min(parseInt(v || dflt, 10) || dflt, MAX_LIMIT)); async function upstream(url) { const res = await fetch(url); if (!res.ok) { let detail = ""; try { detail = (await res.json()).message || ""; } catch (e) { /* not json */ } if (res.status === 404) throw { status: 502, detail: "not found — check the dataset id and domain" }; if (res.status === 400) throw { status: 502, detail: `the API rejected the query (${detail || "bad request"}) — check your SoQL syntax` }; throw { status: 502, detail: `HTTP ${res.status} from Socrata: ${detail}` }; } return res.json(); } async function route(path, params) { if (path === "/sample") { const i = parseInt(params.get("index") || "0", 10) || 0; return SAMPLES[((i % SAMPLES.length) + SAMPLES.length) % SAMPLES.length]; } if (path === "/api/search") { const data = await upstream( "https://api.us.socrata.com/api/catalog/v1?domains=" + DOMAIN + "&only=dataset&q=" + encodeURIComponent(params.get("q") || "") + "&limit=" + clamp(params.get("limit"), 10) ); return (data.results || []).map((r) => ({ id: r.resource.id, name: r.resource.name, description: (r.resource.description || "").slice(0, 500), updated: r.resource.data_updated_at, domain: DOMAIN, })); } if (path.startsWith("/api/schema/")) { const id = checkId(path.split("/").pop()); const data = await upstream(`https://${DOMAIN}/api/views/${id}.json`); return { id, domain: DOMAIN, name: data.name, description: (data.description || "").slice(0, 1000), columns: (data.columns || []) .filter((c) => !String(c.fieldName || "").startsWith(":")) .map((c) => ({ field_name: c.fieldName, type: c.dataTypeName, description: (c.description || "").slice(0, 300) })), }; } if (path === "/api/query" || path === "/api/profile") { const id = checkId(params.get("dataset_id")); const q = new URLSearchParams(); if (path === "/api/profile") { const col = params.get("column") || ""; q.set("$select", `${col}, count(*) as count`); q.set("$group", col); q.set("$order", "count DESC"); q.set("$limit", clamp(params.get("top"), 20)); } else { for (const [key, dollar] of [["select", "$select"], ["where", "$where"], ["group", "$group"], ["order", "$order"]]) if (params.get(key)) q.set(dollar, params.get(key)); q.set("$limit", clamp(params.get("limit"), 50)); if (+params.get("offset")) q.set("$offset", +params.get("offset")); } return upstream(`https://${DOMAIN}/resource/${id}.json?` + q.toString()); } throw { status: 404, detail: "unknown route " + path }; } const realFetch = window.fetch.bind(window); window.fetch = async function (input, init) { const url = typeof input === "string" ? input : input.url; if (url.startsWith("/api/") || url.startsWith("/sample")) { const u = new URL(url, location.origin); try { return ok(await route(u.pathname, u.searchParams)); } catch (e) { return fail(e.status || 502, e.detail || String(e)); } } return realFetch(input, init); }; })();