import { accountDb, repoDb } from "./scoped-db"; import type { ScopedValue } from "./scoped-db"; import { devMailMode } from "./notify/dev-mail"; export const DEV_SCOPED_SQL_PATH = "/__dev/scoped-sql"; const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]); type Body = { scope: "repo" | "account"; id: string; sql: string; params?: ScopedValue[] }; function isBody(value: unknown): value is Body { if (typeof value !== "object" || value === null) return false; const body = value as Record; return ( (body.scope === "repo" || body.scope === "account") && typeof body.id === "string" && typeof body.sql === "string" && (body.params === undefined || Array.isArray(body.params)) ); } /** * E2E harness access to a RepoState / AccountState database, the scoped counterpart of `wrangler d1 execute` * (those tables are no longer in D1). Same gate as the dev mail sink: 404 unless EMAIL_DEV_SINK=kv, PUBLIC_URL * is loopback, and the request targets a loopback host. POST `{ scope, id, sql, params }`, returns the rows. */ export async function handleDevScopedSql(request: Request, url: URL): Promise { if (url.pathname !== DEV_SCOPED_SQL_PATH) return null; const host = url.hostname.toLowerCase(); const loopback = LOCAL_HOSTS.has(host) || host.endsWith(".localhost"); if (devMailMode() !== "kv" || !loopback || request.method !== "POST") { return new Response("Not found\n", { status: 404 }); } const body: unknown = await request.json().catch(() => null); if (!isBody(body)) return new Response("Bad request\n", { status: 400 }); const db = body.scope === "repo" ? repoDb(body.id) : accountDb(body.id); return Response.json(await db.all(body.sql, ...(body.params ?? []))); }