// ---------------------------------------------------------------------------// 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), );
// ---------------------------------------------------------------------------
// 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),
);