History

cloud/local/examples: thread per-user ScopeStack through HTTP + MCP

- cloud: resolves userId from the authenticated session and builds a [userOrgScope, orgScope] stack via createScopedExecutor(userId, orgId, orgName). userId threaded through execution-stack, mcp-session, and MCP dispatch init. - cloud env refactor: deletes the lazy Proxy apps/cloud/src/env.ts in favor of cloudflare:workers env imported at call sites; adds env-augment.d.ts to type secrets not present in generated wrangler types. - cloud/server.ts: env-guarded OTEL wiring (skips instrument() when AXIOM_TOKEN absent); captures rawFetch before instrument() mutates it so dev requests still land on the Hono app. - cloud/services/db.ts: prefers DATABASE_URL over the hyperdrive proxy so local dev works without the tunnel. - vite.config.ts: devCrashGuard plugin that prints uncaught Worker errors with a stable exit. - hosts/mcp: optional debug flag surfaces verbose session logging, wired up from cloud mcp-session. - local/examples/marketing: migrated to scopes: [scope].

Rhys Sullivan committed 6 months ago

Commit d4e311e1 parent 427699c25 files · +320 −174Browse files

Showing the changes to apps/cloud/src/server.ts (1 of 25 files). Show all

Unified · Split

apps/cloud/src/server.ts+33−16
@@ -3,7 +3,6 @@ import handler from "@tanstack/react-start/server-entry"; · 2 unmodified lines
3import { instrument, type TraceConfig } from "@microlabs/otel-cf-workers";3import { instrument, type TraceConfig } from "@microlabs/otel-cf-workers";
4 4
5import { McpSessionDO as McpSessionDOBase } from "./mcp-session";5import { McpSessionDO as McpSessionDOBase } from "./mcp-session";
6import { server } from "./env";
7 6
8// ---------------------------------------------------------------------------7// ---------------------------------------------------------------------------
9// OTEL config for the main fetch handler — `otel-cf-workers` owns the global8// OTEL config for the main fetch handler — `otel-cf-workers` owns the global
@@ -15,40 +14,36 @@ import { server } from "./env"; · 5 unmodified lines
15// DOMException "Illegal invocation".14// DOMException "Illegal invocation".
16// ---------------------------------------------------------------------------15// ---------------------------------------------------------------------------
17 16
18const parseSampleRatio = (value: string): number => {17const parseSampleRatio = (value: string | undefined): number => {
19 const n = Number(value);18 const n = Number(value);
20 if (!Number.isFinite(n)) return 1;19 if (!Number.isFinite(n)) return 1;
21 return Math.min(1, Math.max(0, n));20 return Math.min(1, Math.max(0, n));
22};21};
23 22
24const otelConfig: TraceConfig = {23const otelConfig = (env: Env): TraceConfig => ({
25 service: { name: "executor-cloud", version: "1.0.0" },24 service: { name: "executor-cloud", version: "1.0.0" },
26 exporter: {25 exporter: {
27 url: server.AXIOM_TRACES_URL,26 url: env.AXIOM_TRACES_URL ?? "https://api.axiom.co/v1/traces",
28 headers: {27 headers: {
29 Authorization: `Bearer ${server.AXIOM_TOKEN}`,28 Authorization: `Bearer ${env.AXIOM_TOKEN ?? ""}`,
30 "X-Axiom-Dataset": server.AXIOM_DATASET,29 "X-Axiom-Dataset": env.AXIOM_DATASET ?? "executor-cloud",
31 },30 },
32 },31 },
33 sampling: {32 sampling: {
34 headSampler: {33 headSampler: {
35 // Keep remote parent decisions and make local sampling policy explicit.34 // Keep remote parent decisions and make local sampling policy explicit.
36 acceptRemote: true,35 acceptRemote: true,
37 ratio: parseSampleRatio(server.AXIOM_TRACES_SAMPLE_RATIO),36 ratio: parseSampleRatio(env.AXIOM_TRACES_SAMPLE_RATIO),
38 },37 },
39 },38 },
40};39});
41 40
42// otel-cf-workers owns the global TracerProvider. Sentry's OTEL compat shim41// otel-cf-workers owns the global TracerProvider. Sentry's OTEL compat shim
43// registers a ProxyTracerProvider of its own, which prevents otel-cf-workers42// registers a ProxyTracerProvider of its own, which prevents otel-cf-workers
44// from finding its WorkerTracer and breaks the whole request path with43// from finding its WorkerTracer and breaks the whole request path with
45// "global tracer is not of type WorkerTracer".44// "global tracer is not of type WorkerTracer".
46//45const sentryOptions = (env: Env) => ({
47// The `_env` parameter is unused — `server` from `./env` already gives us46 dsn: env.SENTRY_DSN,
48// typed access to every secret. It's only in the signature so Sentry's
49// generics infer the DO's `Env` type correctly.
50const sentryOptions = (_env: Env) => ({
51 dsn: server.SENTRY_DSN,
52 tracesSampleRate: 0,47 tracesSampleRate: 0,
53 enableLogs: true,48 enableLogs: true,
54 sendDefaultPii: true,49 sendDefaultPii: true,
@@ -76,6 +71,28 @@ export const McpSessionDO = Sentry.instrumentDurableObjectWithSentry( · 21 unmodified lines
76// Worker fetch handler71// Worker fetch handler
77// ---------------------------------------------------------------------------72// ---------------------------------------------------------------------------
78 73
79const instrumentedHandler = instrument({ fetch: handler.fetch }, otelConfig);74// Skip OTLP wiring when no Axiom token is configured (dev without secrets).
75// Otherwise the exporter ships every span with `Bearer ` (empty), which
76// returns 401 on every batch and eventually drops the keep-alive socket —
77// the Node http agent's unhandled `'error'` then crashes the process with
78// ECONNRESET. It also registers otel-cf-workers' `WorkerTracer` as the
79// global tracer; spans started outside its config ALS then die with
80// "Config is undefined". Matches the gate in `DoTelemetryLive`.
81// `instrument()` mutates the handler it's given (replaces `.fetch` with the
82// proxied version), so capture the raw fetch first and then build the
83// instrumented handler from a separate object.
84const rawFetch = handler.fetch;
85const instrumentedHandler = instrument({ fetch: rawFetch }, otelConfig);
86
87const dispatchHandler = {
88 fetch: (request: Request, env: Env, ctx: unknown) => {
89 const fn = env.AXIOM_TOKEN ? instrumentedHandler.fetch! : rawFetch;
90 return (fn as (req: Request, env: Env, ctx: unknown) => Response | Promise<Response>)(
91 request,
92 env,
93 ctx,
94 );
95 },
96};
80 97
81export default Sentry.withSentry(sentryOptions, instrumentedHandler);98export default Sentry.withSentry(sentryOptions, dispatchHandler);