diff --git a/apps/cloud/src/edge/docs.ts b/apps/cloud/src/edge/docs.ts index fc59f5e3c..fabf3773e 100644 --- a/apps/cloud/src/edge/docs.ts +++ b/apps/cloud/src/edge/docs.ts @@ -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); }, ); diff --git a/apps/cloud/src/edge/passthrough.test.ts b/apps/cloud/src/edge/passthrough.test.ts new file mode 100644 index 000000000..2f5bbd35a --- /dev/null +++ b/apps/cloud/src/edge/passthrough.test.ts @@ -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(/^\.\.\//); + } + }); +}); diff --git a/apps/cloud/src/edge/passthrough.ts b/apps/cloud/src/edge/passthrough.ts new file mode 100644 index 000000000..12b6229d4 --- /dev/null +++ b/apps/cloud/src/edge/passthrough.ts @@ -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 => + 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 | null => { + if (isDocsPath(pathname)) return docsProxyResponse(request, pathname); + if (isPosthogPath(pathname)) return fetch(buildPosthogUpstream(request, pathname)); + return null; +}; diff --git a/apps/cloud/src/edge/posthog.ts b/apps/cloud/src/edge/posthog.ts index 6badd574f..a9a035b4d 100644 --- a/apps/cloud/src/edge/posthog.ts +++ b/apps/cloud/src/edge/posthog.ts @@ -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)); }, ); diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 99333d0ea..0304ab024 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -13,6 +13,7 @@ import handler from "@tanstack/react-start/server-entry"; import { isAppOwnedPath } from "./app-paths"; import { marketingProxyRequest } from "./edge/marketing"; +import { passthroughResponse } from "./edge/passthrough"; import { makeCloudMcpAgentHandler } from "./mcp/agent-handler"; import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount"; import { parseTraceparent } from "./mcp/traceparent"; @@ -102,70 +103,12 @@ export { McpExecutionOwnerDirectoryDO } from "@executor-js/cloudflare/mcp/execut // until the in-flight export resolves. // --------------------------------------------------------------------------- -const rawFetchHandler = handler.fetch as ( +const fetchHandler = handler.fetch as ( request: Request, env: Env, ctx: ExecutionContext, ) => Response | Promise; -// TEMPORARY DIAGNOSTIC (Aug 2026 latency hunt). -// -// The standing explanation is that Start's lazy `loadEntries` import of the -// 2.56MB server graph costs seconds on the first Start-handled request per -// isolate. That does not fit the numbers: module evaluation is CPU work, but -// these requests report 45-72ms of CPU against 4s of wall time. -// -// So distinguish the two directly. `wasWarm` is false only for the FIRST -// Start-handled request in this isolate — the one that pays the module load. -// If cold requests are slow and warm ones are fast, the graph load is real. -// If both are slow, the cost is per-request work inside Start/Effect and the -// module-load story is wrong. -let startHandledInThisIsolate = false; - -const fetchHandler = async ( - request: Request, - env: Env, - ctx: ExecutionContext, -): Promise => { - const wasWarm = startHandledInThisIsolate; - startHandledInThisIsolate = true; - // Sync the clock on BOTH sides. Without the trailing `scheduler.wait(0)`, - // a handler that performs no I/O leaves `Date.now()` pinned and reports - // 0ms for work that actually took seconds — which is exactly what the - // first run of this probe showed (handlerMs 0 against 6002ms wall). - await scheduler.wait(0); - - // "First Start request in this isolate" does TWO things: it evaluates the - // 2.56MB module graph, and it builds the app's Effect layers (DB, WorkOS) - // for the first time. Those have different fixes, so time them apart. - // Importing the same virtual ids `loadEntries` uses means its cache finds - // the module already evaluated, so `handlerMs` below excludes the load. - const moduleStartedAt = Date.now(); - // oxlint-disable-next-line executor/no-try-catch-or-throw -- temporary diagnostic: a probe failure must not affect the request - try { - await Promise.all([import("#tanstack-router-entry"), import("#tanstack-start-entry")]); - } catch { - // ignored — the timing is the signal - } - await scheduler.wait(0); - const moduleMs = Date.now() - moduleStartedAt; - - const startedAt = Date.now(); - const response = await rawFetchHandler(request, env, ctx); - await scheduler.wait(0); - const handlerMs = Date.now() - startedAt; - console.log( - JSON.stringify({ - probe: "start-graph", - path: new URL(request.url).pathname, - wasWarm, - moduleMs, - handlerMs, - }), - ); - return response; -}; - const tracer = trace.getTracer("executor-cloud-worker"); const traceparentValueFor = (spanContext: SpanContext): string => @@ -237,90 +180,6 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ const cloudflareHandler: ExportedHandler = { fetch: async (request, env, ctx) => { - // TEMPORARY DIAGNOSTIC (Aug 2026 latency hunt). - // - // workerd freezes `Date.now()` and only advances it when I/O completes, so - // every `Date.now()` delta taken ACROSS the first I/O of a request silently - // includes all wall-clock since the request started — queueing, isolate - // start, module evaluation. That is why per-phase timings kept reporting - // 0ms for everything up to the first await and then a multi-second number - // for whichever await happened to be first: the instrument was measuring - // the clock jump, not the operation. - // - // `scheduler.wait(0)` is an I/O boundary, so awaiting it here forces the - // clock forward before any real work. The delta it absorbs IS the - // pre-work time (queue + start + module eval); every measurement after it - // is honest. - const entryPinned = Date.now(); - await scheduler.wait(0); - const preWorkMs = Date.now() - entryPinned; - const probeUrl = new URL(request.url); - - // With the clock synced above, these two deltas are real durations. They - // answer whether the multi-second cost is specific to the Cache API or - // hits every outbound subrequest from this Worker. - const cacheStartedAt = Date.now(); - const probeCaches = caches as CacheStorage & { readonly default?: Cache }; - await probeCaches.default?.match("https://executor.sh/__probe_never_cached"); - const cacheProbeMs = Date.now() - cacheStartedAt; - - const timerStartedAt = Date.now(); - await scheduler.wait(1); - const timerProbeMs = Date.now() - timerStartedAt; - - // The cache and timer probes are LOCAL. Neither leaves the isolate, and - // both come back in single-digit ms while the same requests take 4-6s. - // This one is a real outbound network subrequest to a small, fast, - // unrelated endpoint — the only class of I/O not yet measured, and the - // one the docs proxy (0.098s direct, 3-6s through the Worker) implicates. - const fetchStartedAt = Date.now(); - // oxlint-disable-next-line executor/no-try-catch-or-throw -- temporary diagnostic: a probe failure must not affect the request - try { - await fetch("https://cloudflare.com/cdn-cgi/trace", { method: "GET" }); - } catch { - // ignored — the timing is the signal, not the result - } - const fetchProbeMs = Date.now() - fetchStartedAt; - - // `fetch()` above resolves when HEADERS arrive — it never reads a body, - // which is why it returns in ~1ms while the request it lives in takes - // seconds. Both genuinely slow operations read a body: the docs proxy - // reads a 179KB page, and the JWKS store does `hit.json()`. Split header - // time from body time on the exact URL the docs proxy uses. - // - // Gated to one cheap path so normal traffic never pays for the probe. - let bodyHeadersMs = -1; - let bodyReadMs = -1; - let bodyBytes = -1; - if (probeUrl.pathname === "/robots.txt") { - // oxlint-disable-next-line executor/no-try-catch-or-throw -- temporary diagnostic: a probe failure must not affect the request - try { - const h0 = Date.now(); - const probeRes = await fetch("https://executor.mintlify.dev/docs/concepts/policies"); - bodyHeadersMs = Date.now() - h0; - const b0 = Date.now(); - const text = await probeRes.text(); - bodyReadMs = Date.now() - b0; - bodyBytes = text.length; - } catch { - // ignored — the timing is the signal - } - } - - console.log( - JSON.stringify({ - probe: "clock-sync", - path: probeUrl.pathname, - preWorkMs, - cacheProbeMs, - timerProbeMs, - fetchProbeMs, - bodyHeadersMs, - bodyReadMs, - bodyBytes, - }), - ); - // Public pages must not enter TanStack Start: its first-request dynamic // import loads the entire React + Effect server graph and can take seconds // on a cold isolate. Classify and service-bind marketing at the Worker @@ -329,6 +188,16 @@ const cloudflareHandler: ExportedHandler = { const marketing: Fetcher | undefined = env.MARKETING; if (marketingRequest && marketing) return marketing.fetch(marketingRequest); + // Same reasoning, same seam: `/docs` and the PostHog proxy forward to an + // external origin and never touch the router, React, or the Effect app. + // Left in Start's middleware they still paid its lazy `loadEntries` import + // first — measured at p50 3.1s on a cold isolate, against p50 33ms for the + // request's own work, on a Worker where 1,666 dispatches spread across + // 1,608 isolates (so nearly every request is cold). Forward before Start. + const passthroughPath = new URL(request.url).pathname; + const passthrough = passthroughResponse(request, passthroughPath); + if (passthrough) return passthrough; + // Browser OTLP ingress — before the server span opens: exporter traffic // must never trace itself (the browser already excludes /v1/traces from // its own tracing for the same reason). diff --git a/apps/cloud/src/start-virtual-entries.d.ts b/apps/cloud/src/start-virtual-entries.d.ts deleted file mode 100644 index c76881feb..000000000 --- a/apps/cloud/src/start-virtual-entries.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// TanStack Start's internal virtual server-entry modules (registered by the -// Start vite plugin; the same ids `start-server-core`'s `loadEntries` -// imports). server.ts imports them to time module evaluation separately from -// first-request app initialization — only the evaluation side effect matters, -// so the value shape is left untyped. Kept in a standalone declaration file: -// shorthand ambient modules only register from a non-module file -// (env-augment.d.ts is a module). -declare module "#tanstack-router-entry"; -declare module "#tanstack-start-entry";