workers/app.ts
53 lines and 6 functions in DrDustinEdwards/carrel, at commit d5d5e82 on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| <anonymous:20> | 20 to 20 | 1 | 0% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
| fetch | 25 to 43 | 3 | 0% | WatchCRAP 12 | No data: mutation testing has not run on this repo yet |
| <anonymous:35> | 35 to 41 | 1 | 0% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
| scheduled | 45 to 57 | 2 | 0% | WatchCRAP 6 | No data: mutation testing has not run on this repo yet |
| <anonymous:49> | 49 to 49 | 1 | 0% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
| <anonymous:56> | 56 to 56 | 1 | 0% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
Imports
It imports app/lib/backup-checks.ts, app/lib/backup.server.ts, app/lib/context.ts, app/lib/google/refresh.server.ts, app/lib/health.server.ts, app/lib/inbox-report.server.ts, app/lib/mcp/door.ts, app/lib/mcp/server.ts, app/lib/refresh.server.ts, app/lib/sites.server.ts, app/lib/social/queue.server.ts, workers/csp-report.ts, workers/gate.ts, workers/security-headers.ts. No module of this repo imports it.
Source
import { createRequestHandler, RouterContextProvider } from "react-router";
import { cloudflareContext, nonceContext, viewerContext } from "~/lib/context";
import { BACKUP_CRON } from "~/lib/backup-checks";
import { runBackup } from "~/lib/backup.server";
import { refreshGoogle } from "~/lib/google/refresh.server";
import { runHealth } from "~/lib/health.server";
import { reportInbox } from "~/lib/inbox-report.server";
import { aiDoor, isMcpHost, mcpOrigin } from "~/lib/mcp/door";
import { carrelOrigin } from "~/lib/mcp/server";
import { refreshAllSites } from "~/lib/refresh.server";
import { siteOrigins } from "~/lib/sites.server";
import { processSocial } from "~/lib/social/queue.server";
import { cspReport, isCspReport } from "./csp-report";
import { gate } from "./gate";
import { newNonce, withDoorSecurityHeaders, withSecurityHeaders } from "./security-headers";
const requestHandler = createRequestHandler(
() => import("virtual:react-router/server-build"),
import.meta.env.MODE,
);
export default {
async fetch(request, env, ctx) {
// Two doors, told apart by the hostname alone. The AI door (carrel-mcp.dustinedwards.info) speaks
// OAuth and MCP and nothing else, plus the CSP report sink; the pages are never served there.
// Everything else is the browser door, behind Access. Every response on either carries the
// security headers (workers/security-headers.ts), refusals and redirects included.
if (isMcpHost(request, env)) {
const response = isCspReport(request) ? await cspReport(request, carrelOrigin(env)) : await aiDoor(request, env, ctx);
return withDoorSecurityHeaders(response, mcpOrigin(env));
}
const nonce = newNonce();
const response = await gate(request, env, async (req, viewer) => {
const context = new RouterContextProvider();
context.set(cloudflareContext, { env, ctx });
context.set(viewerContext, viewer);
context.set(nonceContext, nonce);
return requestHandler(req, context);
});
return withSecurityHeaders(response, nonce, { imageOrigins: siteOrigins(env), reportOrigin: mcpOrigin(env) });
},
scheduled(controller, env, ctx) {
// The nightly cron runs the dump alone. A failure is logged and left for the health check, whose
// backup age keeps growing, to report; it is never swallowed.
if (controller.cron === BACKUP_CRON) {
ctx.waitUntil(runBackup(env).catch((error) => console.error(JSON.stringify({ backup: "failed", error: String(error) }))));
return;
}
ctx.waitUntil(runHealth(env));
ctx.waitUntil(refreshAllSites(env));
ctx.waitUntil(refreshGoogle(env));
ctx.waitUntil(reportInbox(env));
ctx.waitUntil(processSocial(env).catch((error) => console.error(JSON.stringify({ social: "tick-failed", error: String(error) }))));
},
} satisfies ExportedHandler<Env>;