140 lines · 7.5 KB · TypeScript
RawDownload
import { Layer } from "effect";import { HttpServer } from "effect/unstable/http"; import { DbProvider, ExecutorApp } from "@executor-js/api/server"; import { cloudPlugins } from "./plugins";import { CoreSharedServices } from "./auth/workos";import { makeCloudExtensionRoutes } from "./extensions/routes";import { RequestScopedServicesLive } from "./api/layers";import { CloudMeteringEngineDecorator } from "./engine/execution-stack-metered";import { workosAccountMiddleware } from "./account/account-api";import { ApiKeyService } from "./auth/api-keys";import { cloudIdentityFailureStrategy, workosIdentityLayer } from "./auth/workos-auth-provider";import { DbService } from "./db/db";import { cloudMcpAuth } from "./mcp";import { ErrorCaptureLive } from "./observability";import { AutumnService } from "./extensions/billing/service";import {  CLOUD_MOUNT_PREFIX,  CloudCodeExecutorProvider,  CloudDbProvider,  CloudHostConfig,  CloudPluginsProvider,} from "./engine/execution-stack";import { WorkerTelemetryLive } from "./observability/telemetry"; // ===========================================================================// The Executor CLOUD app, as ONE `ExecutorApp.make` call.//// The whole scenario in 60 seconds: WorkOS identity (api-key Bearer OR sealed-// session cookie, api-key wins) over a per-request Hyperdrive→Postgres socket,// the Cloudflare dynamic-worker code substrate, MCP served by a Durable-Object// session store (the DO class is exported straight from server.ts), console+Sentry error// capture — and Autumn BILLING entering ONLY as extensions: the engine// metering decorator, the account seat-gate, the `/api/billing/*` proxy route,// and the createOrganization free-limit gate. `diff` against// `apps/host-selfhost/src/app.ts` is the entire product difference.//// `ExecutorApp.make` owns the assembly (the execution-stack middleware wrapping// the protected API, the MCP envelope, the account API on the /api-prefixed// router, the extension routes, provideMerge(boot)). This file slots cloud's// Pass-6 provider Layers into the named seams.//// Request scoping (Cloudflare Workers' I/O isolation): the postgres.js socket// MUST be rebuilt per request. `requestScoped` is folded by `make` into the// execution-stack middleware; the account + session extension routes fold their// own `requestScopedMiddleware`. `boot` holds only long-lived context (WorkOS// client, telemetry, billing service shell, the resolved identity provider).// =========================================================================== // The WorkOS control plane: the raw SDK client (`CoreSharedServices`) is the// base; the api-key service builds on it, so each WorkOS-dependent service shares// the one boot `WorkOSClient`. Surfaces both tags (the api-key service is read by// the account provider + MCP seam, AND by the per-request identity layer below).// Lives in `boot`, so `workosIdentityLayer`'s residual `WorkOSClient |// ApiKeyService` (the long-lived control plane it reads) resolves from there.// Tests point the REAL client at a WorkOS emulator via WORKOS_API_URL — no// layer substitution.const apiKeyService = ApiKeyService.WorkOS.pipe(Layer.provide(CoreSharedServices));const controlPlane = Layer.mergeAll(CoreSharedServices, apiKeyService); // `CloudDbProvider` only reads the per-request `DbService` at runtime; we widen// its residual type to also carry the boot `AutumnService` the metering// decorator reads, so `make` infers `RDb = DbService | AutumnService` (both// satisfied by `boot`, `DbService` per request via `requestScoped`).const cloudDb: Layer.Layer<DbProvider, never, DbService | AutumnService> = CloudDbProvider; const { appLayer, toWebHandler } = ExecutorApp.make({  plugins: cloudPlugins,  providers: {    // Identity: the NEUTRAL `IdentityProvider`. WorkOS api-key Bearer BEATS    // sealed-session cookie (precedence inside `workosIdentityLayer`). Maps    // rejected credentials to the shared `Unauthorized | NoOrganization |    // Unavailable`; cloud's failure strategy renders the exact `{ error, code }`    // JSON bytes at 401/403/503. The facade builds `authenticate` from the    // `IdentityProvider` tag and provides THIS layer per request over    // `requestScoped`, so the identity resolution lives in the request fiber's    // socket scope. Its residual `UserStoreService` resolves from `requestScoped`    // (the per-request socket); `WorkOSClient | ApiKeyService` from `boot`.    identity: workosIdentityLayer,    // The WorkOS account API (me / api-keys / org), built per request so the    // service closes over the per-request postgres socket; carries the Autumn    // seat-gate. Self-combines `requestScopedMiddleware`.    account: workosAccountMiddleware(RequestScopedServicesLive),    db: cloudDb,    engine: {      codeExecutor: CloudCodeExecutorProvider,      // Billing-as-extension #1: the usage-metering decorator (reads AutumnService).      decorator: CloudMeteringEngineDecorator,    },    // No `sessions` seam: `server.ts` intercepts `/mcp` transport for the    // hibernatable Agent bridge, so the app envelope mounts only cloud's OAuth    // discovery docs (via the `auth` seam). No `reporter` either: it only wrapped    // the envelope's `/mcp` transport route, which cloud no longer serves here    // (the Agent DO captures its own defects via Sentry in `server.ts`).    mcp: {      auth: cloudMcpAuth,    },    plugins: { provider: CloudPluginsProvider, config: CloudHostConfig },    errorCapture: ErrorCaptureLive,  },  extensions: {    // Cloud's app-only HTTP surface: WorkOS session routes, domain-verification,    // Swagger/OpenAPI, the Autumn billing proxy, request-failure logging.    routes: makeCloudExtensionRoutes(RequestScopedServicesLive),  },  config: {    mountPrefix: CLOUD_MOUNT_PREFIX,    // Cloud renders the shared identity errors as its exact `{ error, code }`    // JSON at 401/403/503 (byte-identical to the old `HttpResponseError` bodies).    failure: cloudIdentityFailureStrategy,    // The MCP session Durable Object class — a top-level Workers export a Layer    // can't return; surfaced so `server.ts` can re-export it.  },  // The long-lived (boot-scoped) context provideMerge'd under everything: the  // WorkOS control plane (the raw `WorkOSClient` + `ApiKeyService` the per-request  // identity layer reads residually), billing's service shell (read by the  // metered decorator + free-limit gate), the worker tracer, and the HTTP  // platform. A boot-time WorkOS misconfig is unrecoverable -> `orDie`.  boot: controlPlane.pipe(    Layer.merge(      Layer.mergeAll(WorkerTelemetryLive, HttpServer.layerServices, AutumnService.Default),    ),    // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: a boot-time WorkOS misconfiguration is unrecoverable    Layer.orDie,  ),  // Per request: the postgres socket (`DbService` / `UserStoreService`). The facade  // provide-merges `providers.identity` over THIS layer, so the neutral  // `IdentityProvider` is rebuilt per request in the same fiber scope as the socket  // it reads (Cloudflare Workers' I/O isolation) — the identity layer's per-request  // `UserStoreService` is covered by this layer (its `WorkOSClient | ApiKeyService`  // by `boot`).  requestScoped: RequestScopedServicesLive,}); export const CloudAppLayer = appLayer; // The unified cloud web handler: serves /api/* (incl. /api/billing/*, /api/docs),// /mcp, /.well-known/* — everything the worker dispatches.export const cloudApiHandler = toWebHandler;