82 lines · 3.6 KB · TypeScript
RawDownload
// ---------------------------------------------------------------------------// The WorkOS session + org route Layers, and the `/api`-prefixed router view// they register on.// ---------------------------------------------------------------------------//// Split out of `./routes` so BOTH planes can mount the SAME Layer values:////   - the full app plane (`../app` -> `ExecutorApp.make`'s `extensions.routes`)//   - the auth plane (`../app-auth`), the small handler `server.ts` dispatches//     session/auth traffic to without evaluating the plugin/OpenAPI/MCP graph.//// Sharing the constructors rather than re-deriving them is what makes the two// planes byte-identical on these routes: same handlers, same middleware order,// same prefixed router, same error rendering.// --------------------------------------------------------------------------- import { Effect, Layer } from "effect";import { HttpRouter } from "effect/unstable/http";import { HttpApiBuilder } from "effect/unstable/httpapi"; import { requestScopedMiddleware, type MemberDirectory } from "@executor-js/api/server"; import { UserStoreService } from "../auth/context";import { WorkOsMirror } from "../auth/workos-mirror";import {  CloudAuthPublicHandlers,  CloudSessionAuthHandlers,  NonProtectedApi,} from "../auth/handlers";import { SessionAuthLive } from "../auth/middleware-live";import { OrgHttpApi } from "../org/api";import { orgAuthMiddleware } from "../org/auth-middleware";import { OrgHandlers } from "../org/handlers";import { AutumnService } from "./billing/service";import { DbService } from "../db/db"; /** * The `/api`-prefixed `HttpRouter` view every cloud HttpApi group registers on, * so `/auth/me` serves at `/api/auth/me` (matching the protected + account * plane). Derived from the ambient router, exactly as `ExecutorApp.make` builds * its own internal prefixed view for the protected API. */export const apiPrefixedRouter = Layer.effect(HttpRouter.HttpRouter)(  Effect.map(HttpRouter.HttpRouter.asEffect(), (router) => router.prefixed("/api")),); /** The per-request layer the session + org handlers read (the postgres socket). */export type SessionRequestScoped = Layer.Layer<  DbService | UserStoreService | WorkOsMirror | MemberDirectory>; /** * Session routes (login / callback / logout / me / organizations / …). * Handlers yield `UserStoreService` directly; the per-request DB combine keeps * the postgres socket request-scoped. * * `AutumnService.Default` is provided because the `createOrganization` free-limit * gate, `deleteOrganization`, and the seat report every sign-in fires read it — * the few app-only billing touchpoints. It is NOT on the neutral boot core. */export const makeSessionRoutes = (rsLive: SessionRequestScoped) =>  HttpApiBuilder.layer(NonProtectedApi).pipe(    Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)),    Layer.provide(requestScopedMiddleware(rsLive).layer),    Layer.provideMerge(SessionAuthLive),    Layer.provideMerge(AutumnService.Default),    Layer.provide(apiPrefixedRouter),  ); /** * Cloud-only WorkOS domain-verification routes; the auth middleware resolves * the URL org selector header before falling back to the session org, so slug * lookup needs the same request-scoped UserStoreService as other org-scoped * APIs. The verification-link handler gates on billing, hence `AutumnService`. */export const makeOrgRoutes = (rsLive: SessionRequestScoped) =>  HttpApiBuilder.layer(OrgHttpApi).pipe(    Layer.provide(OrgHandlers),    Layer.provide(orgAuthMiddleware(rsLive)),    Layer.provideMerge(AutumnService.Default),    Layer.provide(apiPrefixedRouter),  );