1import type { RuntimeContext } from "alchemy";
2import type { Auth } from "better-auth";
3import * as Effect from "effect/Effect";
4import { BetterAuthApiError, isAPIErrorLike } from "./Errors.ts";
5
6/**
7 * Every `auth.api.*` endpoint mirrored as an Effect: same arguments, same
8 * success type, with the thrown `APIError` surfaced as a typed
9 * {@link BetterAuthApiError} failure. Non-`APIError` throws become defects.
10 *
11 * Known (accepted) lossage vs the raw promise API: conditional-type
12 * inference collapses overload sets to their last signature, so the
13 * `asResponse`/`returnHeaders` return-type refinements degrade on some
14 * endpoints. Use the raw `auth` escape hatch for those.
15 */
16export type BetterAuthApi<Api> = {
17 readonly [K in keyof Api]: Api[K] extends (
18 ...args: infer Args
19 ) => Promise<infer A>
20 ? (...args: Args) => Effect.Effect<A, BetterAuthApiError, RuntimeContext>
21 : never;
22};
23
24/**
25 * Build the effectified `api` Proxy over the per-execution auth instance.
26 *
27 * @internal
28 */
29export const makeApiProxy = <A extends { api: unknown }>(
30 makeAuth: Effect.Effect<A, never, RuntimeContext>,
31): BetterAuthApi<A["api"]> => {
32 const wrappers = new Map<
33 PropertyKey,
34 (
35 ...args: unknown[]
36 ) => Effect.Effect<unknown, BetterAuthApiError, RuntimeContext>
37 >();
38 return new Proxy(
39 {},
40 {
41 get(_target, key) {
42 // Never look like a thenable / iterable — the proxy is a plain
43 // record of endpoint wrappers.
44 if (typeof key !== "string" || key === "then") {
45 return undefined;
46 }
47 let wrapper = wrappers.get(key);
48 if (wrapper === undefined) {
49 wrapper = (...args: unknown[]) =>
50 Effect.flatMap(makeAuth, (auth) =>
51 Effect.tryPromise({
52 try: () =>
53 (
54 auth.api as Record<
55 string,
56 (...args: unknown[]) => Promise<unknown>
57 >
58 )[key]!(...args),
59 catch: (error) => error,
60 }).pipe(
61 Effect.catch((error: unknown) =>
62 isAPIErrorLike(error)
63 ? Effect.fail(BetterAuthApiError.fromAPIError(error))
64 : Effect.die(error),
65 ),
66 ),
67 );
68 wrappers.set(key, wrapper);
69 }
70 return wrapper;
71 },
72 has(_target, key) {
73 return typeof key === "string" && key !== "then";
74 },
75 },
76 ) as BetterAuthApi<A["api"]>;
77};