# `auth-client` _(no submodules)_ ## Purpose `auth-client.ts` ships a typed `AuthClient` class plus the `createAuthClient(config)` factory that wraps `better-auth/client`. The wrapper centralises three concerns the underlying client leaves to the caller: (1) consistent injection of an optional `x-site-domain` header across every request, used for multi-tenant routing on the auth server; (2) a structural surface that does not depend on the dynamic plugin generic graph emitted by `better-auth` (each method we call is typed as an optional callable so plugin presence is detected at runtime and reported as a no-op when absent rather than as a compile-time error); and (3) normalisation of the `listSessions` payload from native `Date` to ISO string for serialisation safety. Configuration is validated through Zod at the boundary and a `ReactAuthConfigInvalidError` is thrown with code `REACT_AUTH.CONFIG_INVALID` when the URL or site domain is malformed. ## Features - `AuthClient` class + `createAuthClient(config)` factory. - Optional `siteDomain` propagates as the `x-site-domain` header on every call. - Optional `fetch` injection seam for testing (no `vi.mock` required). - Zod-validated config; invalid input throws `ReactAuthConfigInvalidError`. - `listSessions()` normalises `Date` fields to ISO strings. - Every method is plugin-aware: when the underlying client lacks the method it returns `undefined` (or `[]` for `listSessions`). ## Usage: Basic ```ts import { createAuthClient } from "@suaveplan/react-auth"; const client = createAuthClient({ baseURL: "https://auth.example.com", siteDomain: "tenant-a", }); await client.signIn({ email: "a@b.com", password: "secret" }); const session = await client.getSession(); ``` ## Usage: Advanced Inject a custom `fetch` (e.g. a recorder or a happy-path stub) without ever mocking the `better-auth` module: ```ts import { createAuthClient } from "@suaveplan/react-auth"; const captured: Request[] = []; const recordingFetch: typeof fetch = async (input, init) => { const req = new Request(input, init); captured.push(req); return new Response(JSON.stringify({ data: null }), { status: 200 }); }; const client = createAuthClient({ baseURL: "https://auth.example.com", siteDomain: "tenant-b", fetch: recordingFetch, }); await client.getSession(); expect(captured[0].headers.get("x-site-domain")).toBe("tenant-b"); ``` ## API Reference - `interface AuthClientConfig` — `baseURL: string` (URL), `siteDomain?: string`, `fetch?: typeof fetch`. - `class AuthClient` - `constructor(config: AuthClientConfig)` — validates `config`, throws `ReactAuthConfigInvalidError` on failure. - `signIn(credentials: LoginCredentials): Promise` - `signUp(data: RegisterData): Promise` - `signOut(): Promise` - `getSession(): Promise` - `forgotPassword(email: string): Promise` - `resetPassword(data: { token: string; password: string }): Promise` - `changePassword(data: ChangePasswordData): Promise` - `verifyEmail(token: string): Promise` - `resendVerificationEmail(email: string): Promise` - `updateProfile(data: UpdateProfileData): Promise` - `deleteAccount(password: string): Promise` - `listSessions(): Promise` — Date fields ISO-normalised. - `revokeSession(sessionId: string): Promise` - `revokeAllSessions(): Promise` - `getRawClient(): BetterAuthClientLike` — escape hatch for plugin-specific access. - `function createAuthClient(config: AuthClientConfig): AuthClient` — thin factory. ## Implementation Notes The client does not throw on missing plugin methods. The upstream package emitted a single `@ts-expect-error` to work around `forgetPassword` not being statically typed; the wrapper replaces that with a structural interface (`BetterAuthClientLike`) where every method is optional. This keeps the call sites compile-safe while also covering deployments where the auth server registers a different plugin set. `listSessions` returns `[]` when no `data` array is present so callers can render an empty state without branching on `undefined`. The `x-site-domain` header is computed per-call from `this.config.siteDomain` so that future hot-swaps of the client config (out of scope for Wave 1) automatically pick up the new value. Constructor-time validation uses Zod with `z.string().url()` for `baseURL` — failing fast catches misconfiguration during app boot rather than on first request.