// API surface: the typed Effect `HttpApiClient` a real consumer codes against, // over the wire to the target's dev server. Auth comes from the scenario's // Identity — either ready-made headers (cloud's stub session cookie) or a // Better Auth email sign-in (selfhost). Assertions and failure output are // vitest's job; a failed call surfaces as a typed HttpClientError in the // test output. import { Effect } from "effect"; import { HttpApiClient } from "effect/unstable/httpapi"; import { HttpClient, HttpClientRequest } from "effect/unstable/http"; import type { Identity, Target } from "../target"; type AnyApi = Parameters[0]; export interface ApiSurface { /** Typed client for `apiDef`, authenticated as `identity`. */ readonly client: ( apiDef: A, identity: Identity, ) => Effect.Effect, unknown, HttpClient.HttpClient>; } export const makeApiSurface = (target: Target): ApiSurface => ({ client: (apiDef, identity) => Effect.gen(function* () { const headers = identity.headers ?? (yield* signInHeaders(target.baseUrl, identity)); return yield* HttpApiClient.make(apiDef, { baseUrl: new URL("/api", target.baseUrl).toString(), transformClient: HttpClient.mapRequest((request) => Object.entries(headers).reduce( (req, [key, value]) => HttpClientRequest.setHeader(req, key, value), request, ), ), }); }), }); // Better Auth email sign-in → session cookie (selfhost). The `origin` header is // required: Better Auth rejects state-changing requests without one. const signInHeaders = (baseUrl: string, identity: Identity) => Effect.promise(async (): Promise> => { const credentials = identity.credentials; if (!credentials) throw new Error(`identity ${identity.label} has no headers or credentials`); const response = await fetch(new URL("/api/auth/sign-in/email", baseUrl), { method: "POST", headers: { "content-type": "application/json", origin: new URL(baseUrl).origin }, body: JSON.stringify(credentials), redirect: "manual", }); const cookie = (response.headers.getSetCookie?.() ?? []).map((c) => c.split(";")[0]).join("; "); if (!cookie) throw new Error(`api: sign-in set no cookie (${response.status})`); return { cookie }; });