first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
# `auth-client`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<unknown>`
- `signUp(data: RegisterData): Promise<unknown>`
- `signOut(): Promise<unknown>`
- `getSession(): Promise<unknown>`
- `forgotPassword(email: string): Promise<unknown>`
- `resetPassword(data: { token: string; password: string }): Promise<unknown>`
- `changePassword(data: ChangePasswordData): Promise<unknown>`
- `verifyEmail(token: string): Promise<unknown>`
- `resendVerificationEmail(email: string): Promise<unknown>`
- `updateProfile(data: UpdateProfileData): Promise<unknown>`
- `deleteAccount(password: string): Promise<unknown>`
- `listSessions(): Promise<Session[]>` — Date fields ISO-normalised.
- `revokeSession(sessionId: string): Promise<unknown>`
- `revokeAllSessions(): Promise<unknown>`
- `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.