import { env } from "cloudflare:workers"; import { Hono } from "hono"; import { LandingPage } from "./components/LandingPage"; import { AccountMetricCoordinator } from "./durable-objects/AccountMetricCoordinator"; import { MetricCoordinator } from "./durable-objects/MetricCoordinator"; import { MetricExporter } from "./durable-objects/MetricExporter"; import { type AppConfig, parseConfig } from "./lib/config"; import { checkHealth, healthResponse } from "./lib/health"; import { configFromEnv, createLogger } from "./lib/logger"; import { ConfigKeySchema, getConfig, getConfigKey, getEnvDefaults, resetAllConfig, resetConfigKey, setConfigKey, } from "./lib/runtime-config"; export { MetricCoordinator, AccountMetricCoordinator, MetricExporter }; type Variables = { config: AppConfig }; const app = new Hono<{ Bindings: Env; Variables: Variables }>(); // Parse config middleware app.use("*", async (c, next) => { c.set("config", parseConfig(c.env)); await next(); }); // Disable guards app.use("*", async (c, next) => { const path = c.req.path; if (c.var.config.disableUi && path === "/") { return c.text("Not Found", 404); } if (c.var.config.disableConfigApi && path.startsWith("/config")) { return c.text("Not Found", 404); } await next(); }); // Dynamic metrics path middleware (runs before routing) app.get(env.METRICS_PATH, async (c) => { const logger = createLogger("worker", configFromEnv(c.env)).withContext({ request_id: crypto.randomUUID(), }); logger.info("Metrics request received"); try { const coordinator = await MetricCoordinator.get(c.env); const output = await coordinator.export(); logger.info("Metrics exported successfully"); return c.text(output, 200, { "Content-Type": "text/plain; charset=utf-8", }); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error("Failed to collect metrics", { error: message }); return c.text(`Error collecting metrics: ${message}`, 500); } }); // Routes app.get("/", (c) => c.html()); app.get("/health", async (c) => { const health = await checkHealth(c.env); return healthResponse(health); }); // Config API routes app.get("/config", async (c) => { const config = await getConfig(c.env); return c.json(config); }); app.get("/config/defaults", (c) => { const defaults = getEnvDefaults(c.env); return c.json(defaults); }); app.get("/config/:key", async (c) => { const keyResult = ConfigKeySchema.safeParse(c.req.param("key")); if (!keyResult.success) { return c.json({ error: "Invalid config key" }, 400); } const value = await getConfigKey(c.env, keyResult.data); return c.json({ key: keyResult.data, value }); }); app.put("/config/:key", async (c) => { const keyResult = ConfigKeySchema.safeParse(c.req.param("key")); if (!keyResult.success) { return c.json({ error: "Invalid config key" }, 400); } const body = await c.req.json<{ value: unknown }>().catch(() => null); if (!body || !("value" in body)) { return c.json({ error: "Request body must contain 'value'" }, 400); } const result = await setConfigKey(c.env, keyResult.data, body.value); if (!result.success) { return c.json( { error: "Invalid value", details: result.error.issues }, 400, ); } return c.json(result.config); }); app.delete("/config/:key", async (c) => { const keyResult = ConfigKeySchema.safeParse(c.req.param("key")); if (!keyResult.success) { return c.json({ error: "Invalid config key" }, 400); } const config = await resetConfigKey(c.env, keyResult.data); return c.json(config); }); app.delete("/config", async (c) => { const config = await resetAllConfig(c.env); return c.json(config); }); app.notFound((c) => c.text("Not Found", 404)); export default app;