Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 9 additions & 69 deletions apps/cloud/src/edge/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,23 @@
// base path, so the pathname is forwarded UNCHANGED — only the host/proto swap
// to the upstream origin (unlike the PostHog proxy, which strips its prefix).
//
// Like the PostHog/Sentry tunnels (and unlike the marketing proxy, which needs
// the prod-only `env.MARKETING` service binding), this is a plain external
// `fetch`, so it runs on every host — `/docs` previews against live Mintlify in
// local dev too. `/docs` is distinct from the app-owned `/api/docs` (Swagger),
// so this never shadows an Effect-served route.
// The matching, upstream construction, and client span live in `./passthrough`,
// which server.ts dispatches BEFORE Start loads: forwarding a docs page must
// not pay for the whole server graph. This middleware stays registered so hosts
// that reach Start by another entry (local dev) keep identical behavior; in the
// deployed Worker it is unreachable. `/docs` is distinct from the app-owned
// `/api/docs` (Swagger), so this never shadows an Effect-served route.
// ---------------------------------------------------------------------------

import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
import { createMiddleware } from "@tanstack/react-start";

const DOCS_UPSTREAM_HOST = "executor.mintlify.dev";
import { docsProxyResponse, isDocsPath } from "./passthrough";

// The proxy fetch gets its own client span: `/docs` requests otherwise render
// as a single opaque server span, and during the Aug 2026 regression there
// was no way to tell upstream (Mintlify/Vercel) latency from worker-side
// dispatch cost. The noop tracer applies when no provider is installed
// (local dev without AXIOM_TOKEN), so this is free there.
const tracer = trace.getTracer("executor-cloud-docs-proxy");

export const isDocsPath = (pathname: string) =>
pathname === "/docs" || pathname.startsWith("/docs/");

// Build the upstream request for an already-classified `/docs` path. Caller
// guarantees `isDocsPath(pathname)` — we only swap the origin and fix up the
// forwarding headers, preserving method, body, path, and query.
export const buildDocsUpstream = (request: Request): Request => {
const url = new URL(request.url);
const forwardedHost = url.host;

url.hostname = DOCS_UPSTREAM_HOST;
url.protocol = "https:";
url.port = "";

const upstream = new Request(url, request);
// Mintlify keys canonical links off the public host; tell it the real one.
upstream.headers.set("X-Forwarded-Host", forwardedHost);
upstream.headers.set("X-Forwarded-Proto", "https");
// Never leak the executor.sh session cookie to the docs origin.
upstream.headers.delete("cookie");
return upstream;
};
export { buildDocsUpstream, isDocsPath } from "./passthrough";

export const docsProxyMiddleware = createMiddleware({ type: "request" }).server(
({ pathname, request, next }) => {
if (!isDocsPath(pathname)) return next();
return tracer.startActiveSpan(
`http.client ${request.method}`,
{
kind: SpanKind.CLIENT,
attributes: {
"server.address": DOCS_UPSTREAM_HOST,
"url.path": pathname,
"http.request.method": request.method,
},
},
async (span) => {
// oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; observe upstream response/error for span status, then pass both through unchanged
try {
const response = await fetch(buildDocsUpstream(request));
span.setAttribute("http.response.status_code", response.status);
if (response.status >= 500) {
span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${response.status}` });
}
return response;
} catch (err) {
// oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- adapter boundary: fetch rejects untyped; normalized only for the OTel span record, the original error is rethrown below
const cause = err instanceof Error ? err : String(err);
span.recordException(cause);
// oxlint-disable-next-line executor/no-unknown-error-message -- adapter boundary: same normalization as the recordException line above
const message = typeof cause === "string" ? cause : cause.message;
span.setStatus({ code: SpanStatusCode.ERROR, message });
// oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; preserve the original rejection for the platform handler
throw err;
} finally {
span.end();
}
},
);
return docsProxyResponse(request, pathname);
},
);
85 changes: 85 additions & 0 deletions apps/cloud/src/edge/passthrough.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "@effect/vitest";

import {
buildDocsUpstream,
buildPosthogUpstream,
isDocsPath,
isPosthogPath,
passthroughResponse,
POSTHOG_PROXY_PATH,
} from "./passthrough";

describe("passthrough matching", () => {
it("claims /docs and everything under it, but not /api/docs", () => {
expect(isDocsPath("/docs")).toBe(true);
expect(isDocsPath("/docs/concepts/policies")).toBe(true);
// The app-owned Swagger route must keep reaching the Effect app.
expect(isDocsPath("/api/docs")).toBe(false);
expect(isDocsPath("/docsearch")).toBe(false);
});

it("claims the PostHog proxy path and its subtree only", () => {
expect(isPosthogPath(POSTHOG_PROXY_PATH)).toBe(true);
expect(isPosthogPath(`${POSTHOG_PROXY_PATH}/i/v0/e/`)).toBe(true);
expect(isPosthogPath(`${POSTHOG_PROXY_PATH}extra`)).toBe(false);
expect(isPosthogPath("/api/connections")).toBe(false);
});

it("returns null for app paths so they fall through to normal dispatch", () => {
expect(passthroughResponse(new Request("https://executor.sh/"), "/")).toBeNull();
expect(
passthroughResponse(new Request("https://executor.sh/api/connections"), "/api/connections"),
).toBeNull();
expect(passthroughResponse(new Request("https://executor.sh/mcp"), "/mcp")).toBeNull();
});
});

describe("upstream construction", () => {
it("forwards the docs path unchanged and strips the session cookie", () => {
const upstream = buildDocsUpstream(
new Request("https://executor.sh/docs/concepts/policies?x=1", {
headers: { cookie: "wos-session=secret" },
}),
);
const url = new URL(upstream.url);
expect(url.hostname).toBe("executor.mintlify.dev");
expect(url.pathname).toBe("/docs/concepts/policies");
expect(url.search).toBe("?x=1");
expect(upstream.headers.get("X-Forwarded-Host")).toBe("executor.sh");
expect(upstream.headers.get("cookie")).toBeNull();
});

it("strips the proxy prefix for PostHog and splits ingest from assets", () => {
const ingest = buildPosthogUpstream(
new Request(`https://executor.sh${POSTHOG_PROXY_PATH}/i/v0/e/`),
`${POSTHOG_PROXY_PATH}/i/v0/e/`,
);
expect(new URL(ingest.url).hostname).toBe("us.i.posthog.com");
expect(new URL(ingest.url).pathname).toBe("/i/v0/e/");

const assets = buildPosthogUpstream(
new Request(`https://executor.sh${POSTHOG_PROXY_PATH}/static/array.js`),
`${POSTHOG_PROXY_PATH}/static/array.js`,
);
expect(new URL(assets.url).hostname).toBe("us-assets.i.posthog.com");
expect(new URL(assets.url).pathname).toBe("/static/array.js");
});
});

describe("Start-graph independence", () => {
// The entire point of this module is that server.ts can answer a proxy
// request WITHOUT importing TanStack Start. An import of the app or of
// `@tanstack/react-start` here silently reintroduces the ~3.1s cold-isolate
// `loadEntries` cost this module exists to avoid, and nothing else would
// catch it — the behavior stays correct, only slow.
it("imports neither TanStack Start nor an app module", () => {
const source = readFileSync(new URL("./passthrough.ts", import.meta.url), "utf8");
const imports = [...source.matchAll(/^\s*import[^"']*["']([^"']+)["']/gm)].map((m) => m[1]);
expect(imports.length).toBeGreaterThan(0);
for (const specifier of imports) {
expect(specifier).not.toMatch(/@tanstack/);
expect(specifier).not.toMatch(/^\.\.\//);
}
});
});
135 changes: 135 additions & 0 deletions apps/cloud/src/edge/passthrough.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// ---------------------------------------------------------------------------
// Pure passthrough proxies — dispatched from the Worker entry, before Start.
// ---------------------------------------------------------------------------
//
// `/docs` and the PostHog proxy forward to an external origin and never touch
// the router, React, or the Effect app. They lived in Start's request
// middleware, which meant each one still paid Start's lazy `loadEntries`
// import of the whole server graph before it could forward a request.
//
// Measured on production (2026-08-18), splitting the two costs apart on a cold
// isolate: the graph import is p50 **3.1s** while the request's own work is
// p50 **33ms**. And essentially every request is cold — `worker.dispatch` ran
// 1,666 requests across 1,608 isolates (1.04 req/isolate), because `/mcp`
// dispatches before `fetchHandler` and so never warms the graph. A `/docs`
// page took 3-6s through the Worker against 0.098s straight from the upstream.
//
// So these move to the Worker entry, exactly as marketing did: classify and
// forward before anything imports Start. This module therefore must NOT import
// `@tanstack/react-start` or any app module — that import is the cost it
// exists to avoid.
//
// The middleware wrappers in `./docs` and `./posthog` stay registered. In the
// deployed Worker they become unreachable (server.ts answers first), but they
// keep the behavior identical on any host that reaches Start by another entry
// (local dev), and they source their matching from here so the two can't drift.
// ---------------------------------------------------------------------------

import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";

const DOCS_UPSTREAM_HOST = "executor.mintlify.dev";
const POSTHOG_INGEST_HOST = "us.i.posthog.com";
const POSTHOG_ASSETS_HOST = "us-assets.i.posthog.com";

export const POSTHOG_PROXY_PATH = `/api/${(
import.meta.env.VITE_PUBLIC_ANALYTICS_PATH ?? "a"
).replace(/^\/+|\/+$/g, "")}`;

// The proxy fetch gets its own client span: `/docs` requests otherwise render
// as a single opaque server span, and during the Aug 2026 regression there
// was no way to tell upstream (Mintlify/Vercel) latency from worker-side
// dispatch cost. The noop tracer applies when no provider is installed
// (local dev without AXIOM_TOKEN), so this is free there.
const tracer = trace.getTracer("executor-cloud-docs-proxy");

export const isDocsPath = (pathname: string): boolean =>
pathname === "/docs" || pathname.startsWith("/docs/");

export const isPosthogPath = (pathname: string): boolean =>
pathname === POSTHOG_PROXY_PATH || pathname.startsWith(`${POSTHOG_PROXY_PATH}/`);

/**
* Build the upstream request for an already-classified `/docs` path. Caller
* guarantees `isDocsPath(pathname)` — we only swap the origin and fix up the
* forwarding headers, preserving method, body, path, and query.
*/
export const buildDocsUpstream = (request: Request): Request => {
const url = new URL(request.url);
const forwardedHost = url.host;

url.hostname = DOCS_UPSTREAM_HOST;
url.protocol = "https:";
url.port = "";

const upstream = new Request(url, request);
// Mintlify keys canonical links off the public host; tell it the real one.
upstream.headers.set("X-Forwarded-Host", forwardedHost);
upstream.headers.set("X-Forwarded-Proto", "https");
// Never leak the executor.sh session cookie to the docs origin.
upstream.headers.delete("cookie");
return upstream;
};

/** Build the upstream request for an already-classified PostHog proxy path. */
export const buildPosthogUpstream = (request: Request, pathname: string): Request => {
const url = new URL(request.url);
url.hostname = pathname.startsWith(`${POSTHOG_PROXY_PATH}/static/`)
? POSTHOG_ASSETS_HOST
: POSTHOG_INGEST_HOST;
url.protocol = "https:";
url.port = "";
url.pathname = pathname.slice(POSTHOG_PROXY_PATH.length) || "/";

const upstream = new Request(url, request);
upstream.headers.delete("cookie");
return upstream;
};

export const docsProxyResponse = (request: Request, pathname: string): Promise<Response> =>
tracer.startActiveSpan(
`http.client ${request.method}`,
{
kind: SpanKind.CLIENT,
attributes: {
"server.address": DOCS_UPSTREAM_HOST,
"url.path": pathname,
"http.request.method": request.method,
},
},
async (span) => {
// oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; observe upstream response/error for span status, then pass both through unchanged
try {
const response = await fetch(buildDocsUpstream(request));
span.setAttribute("http.response.status_code", response.status);
if (response.status >= 500) {
span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${response.status}` });
}
return response;
} catch (err) {
// oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- adapter boundary: fetch rejects untyped; normalized only for the OTel span record, the original error is rethrown below
const cause = err instanceof Error ? err : String(err);
span.recordException(cause);
// oxlint-disable-next-line executor/no-unknown-error-message -- adapter boundary: same normalization as the recordException line above
const message = typeof cause === "string" ? cause : cause.message;
span.setStatus({ code: SpanStatusCode.ERROR, message });
// oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; preserve the original rejection for the platform handler
throw err;
} finally {
span.end();
}
},
);

/**
* Answer a pure passthrough request without loading the Start server graph.
* Returns `null` when the request belongs to the app, so the caller falls
* through to normal dispatch.
*/
export const passthroughResponse = (
request: Request,
pathname: string,
): Promise<Response> | null => {
if (isDocsPath(pathname)) return docsProxyResponse(request, pathname);
if (isPosthogPath(pathname)) return fetch(buildPosthogUpstream(request, pathname));
return null;
};
29 changes: 8 additions & 21 deletions apps/cloud/src/edge/posthog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,20 @@
// first-party path and we forward to PostHog's ingest + asset hosts. Keeps
// events flowing past adblockers that match *.posthog.com. See
// https://posthog.com/docs/advanced/proxy/cloudflare
//
// The matching and forwarding live in `./passthrough`, which server.ts
// dispatches BEFORE Start loads (a proxy must not pay for the server graph).
// This middleware stays registered so hosts that reach Start by another entry
// keep identical behavior; in the deployed Worker it is unreachable.
// ---------------------------------------------------------------------------

import { createMiddleware } from "@tanstack/react-start";

const POSTHOG_INGEST_HOST = "us.i.posthog.com";
const POSTHOG_ASSETS_HOST = "us-assets.i.posthog.com";
const POSTHOG_PROXY_PATH = `/api/${(import.meta.env.VITE_PUBLIC_ANALYTICS_PATH ?? "a").replace(
/^\/+|\/+$/g,
"",
)}`;
import { buildPosthogUpstream, isPosthogPath } from "./passthrough";

export const posthogProxyMiddleware = createMiddleware({ type: "request" }).server(
({ pathname, request, next }) => {
if (pathname !== POSTHOG_PROXY_PATH && !pathname.startsWith(`${POSTHOG_PROXY_PATH}/`)) {
return next();
}

const url = new URL(request.url);
url.hostname = pathname.startsWith(`${POSTHOG_PROXY_PATH}/static/`)
? POSTHOG_ASSETS_HOST
: POSTHOG_INGEST_HOST;
url.protocol = "https:";
url.port = "";
url.pathname = pathname.slice(POSTHOG_PROXY_PATH.length) || "/";

const upstream = new Request(url, request);
upstream.headers.delete("cookie");
return fetch(upstream);
if (!isPosthogPath(pathname)) return next();
return fetch(buildPosthogUpstream(request, pathname));
},
);
Loading
Loading