Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@suaveplan/api-auth-better-auth — Better-Auth adapter for the @suaveplan/api foundation. Ships two independently-wireable factories implementing the auth.session and auth.provider capability contracts against a Better-Auth instance backed by its own Postgres connection. Maps Better-Auth user+session onto the @suaveplan/api Principal contract, mounts canonical PROVIDER_ROUTE_SURFACE routes (translating Better-Auth's /sign-up etc. idiom), exposes a GitHub OAuth flow via Better-Auth's genericOAuth plugin (so the mock OAuth IdP can be wired end-to-end), guards route-prefix collisions against @suaveplan/api-host's reserved-prefix contract, and emits OTel sessions/oauth/mapping instruments. The ./testing subpath re-exports both conformance suites bound to Postgres-backed factories.
Stage-6 Better-Auth adapter for @suaveplan/api. Ships two
independently-wireable capability surfaces — createBetterAuthSession
(implements auth.session from @suaveplan/api-auth) and
createBetterAuthProvider (implements auth.provider from
@suaveplan/api-auth-provider). Operators MAY wire either side in
isolation OR both together; sharing one options bag between the two
factories shares the underlying Better-Auth instance + DB pool.
Two capability surfaces, wire independently. The session adapter reads cookies + bearer tokens; the provider adapter mounts the canonical
PROVIDER_ROUTE_SURFACE(9 routes) under a configurable prefix. They share no required code path — wiring only one side does NOT install the other side's surface.
Documentation tree
- db — Lazy holder for the underlying Better-Auth instance plus a helper that projects {@link BetterAuthOptions} onto the loos…
- database — Lazy holder for the underlying Better-Auth instance plus a helper that projects {@link BetterAuthOptions} onto the loos…
- error-codes — Stable, tracked error-code registry for
@suaveplan/api-auth-better-auth. Every fault path inside the adapter…- error-codes — Stable, tracked error-code registry for
@suaveplan/api-auth-better-auth. Every fault path inside the adapter…
- error-codes — Stable, tracked error-code registry for
- independence — Pin the design invariant from the Stage-6 spec change: the Better-Auth adapter ships two factories — `createBetterA…
- session-without-provider — Pin the design invariant from the Stage-6 spec change: the Better-Auth adapter ships two factories — `createBetterA…
- instruments — Lazily-bound OpenTelemetry instrument bundle for the Better-Auth adapter. Built once per process against the current gl…
- instruments — Lazily-bound OpenTelemetry instrument bundle for the Better-Auth adapter. Built once per process against the current gl…
- default-factory — Default
betterAuthFactoryused when an operator does not supply one viaBetterAuthOptions.betterAuthFactory. Dynami… - github — Project Suaveplan's {@link BetterAuthOauthProvider} entries onto the
genericOAuthplugin config Better-Auth consumes.… - options — Zod-validated boundary for the Better-Auth adapter's configuration bag. Both
createBetterAuthSessionand `createBette…- options — Zod-validated boundary for the Better-Auth adapter's configuration bag. Both
createBetterAuthSessionand `createBette…
- options — Zod-validated boundary for the Better-Auth adapter's configuration bag. Both
- principal — Default Better-Auth
(user, session)→Principalmapping consumed by bothcreateBetterAuthSessionand `createBette…- map-user-to-principal — Default Better-Auth
(user, session)→Principalmapping consumed by bothcreateBetterAuthSessionand `createBette…
- map-user-to-principal — Default Better-Auth
- create-provider — Factory for the
auth.providerhalf of the Stage-6 split. Returns anAuthProviderAdapter(per `@suaveplan/api-auth-p… - route-translation — Translation table mapping every entry in
PROVIDER_ROUTE_SURFACE(the canonical 9-route foundation surface) to the mat… - route-prefix — Resolve an operator-supplied
routePrefixagainst the host's reserved-prefix contract from `@suaveplan/api-host/reserv…- route-prefix — Resolve an operator-supplied
routePrefixagainst the host's reserved-prefix contract from `@suaveplan/api-host/reserv…
- route-prefix — Resolve an operator-supplied
- session — Factory for the
auth.sessionhalf of the Stage-6 split. Returns aSessionResolverthat asks Better-Auth's `auth.api…- create-session — Factory for the
auth.sessionhalf of the Stage-6 split. Returns aSessionResolverthat asks Better-Auth's `auth.api…
- create-session — Factory for the
- testing —
@suaveplan/api-auth-better-auth/testingis the subpath downstream packages import when they want to assert their own…- testing —
@suaveplan/api-auth-better-auth/testingis the subpath downstream packages import when they want to assert their own…
- testing —
Modules
error-codes/— 10-codeAUTH_BA.*registry +BetterAuthError.options/— Zod-validatedBetterAuthOptionsboundary + canonical defaults.db/—LazyBetterAuth(lazy + cached + concurrency-safe) +buildBetterAuthFactoryArgs(outer-options → Better-Auth-args projection).principal/— default Better-Auth(user, session)→Principalmapping; replaceable viaBetterAuthOptions.principalMapping.session/—createBetterAuthSession(opts)factory implementing theSessionResolvercontract.provider/—createBetterAuthProvider(opts)factory implementing theAuthProviderAdaptercontract; mounts canonical routes that rewrite onto Better-Auth's native sub-paths.route-prefix/—assertRoutePrefixAvailablecollision check against@suaveplan/api-host/reserved-prefix.oauth/—genericOAuthplugin config builder + defaultbetterAuth(...)factory that dynamically loads the plugin when OAuth providers are configured. The GitHub OAuth flow is the canonical reference; the implementation works against the real GitHub IdP in production and the mock IdP from@suaveplan/testing-integration-oauth-mockin tests because we usegenericOAuth(which accepts the full URL triple) rather than the hard-codedgithubsocial-provider.instruments/— OTel counters + histograms per the spec's metrics contract.independence/— invariant pin: session-only / provider-only wiring exposes nothing from the other side.testing/—./testingsubpath re-exporting both conformance harnesses bound to Better-Auth factories.
Usage
import { Pool } from "pg";
import {
createBetterAuthProvider,
createBetterAuthSession,
} from "@suaveplan/api-auth-better-auth";
const pool = new Pool({ connectionString: process.env.BETTER_AUTH_DB_URL! });
const opts = {
database: pool,
baseUrl: "https://api.example.com",
secret: process.env.BETTER_AUTH_SECRET!,
routePrefix: "/auth",
oauth: {
providers: {
github: {
providerId: "github",
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
authorizationUrl: "https://github.com/login/oauth/authorize",
tokenUrl: "https://github.com/login/oauth/access_token",
userInfoUrl: "https://api.github.com/user",
},
},
},
};
// Wire both sides — they share one Better-Auth instance via per-options WeakMap:
host.registerAuth({
resolver: createBetterAuthSession(opts),
csrf: true,
});
host.registerAuthProvider(createBetterAuthProvider(opts));
Error codes
| Code | Surface | When thrown |
|---|---|---|
AUTH_BA.INVALID_SESSION |
session | unexpected getSession failure |
AUTH_BA.DB_UNREACHABLE |
db | betterAuth({...}) construction threw |
AUTH_BA.OAUTH_PROVIDER_REJECTED |
oauth | provider returned 403 |
AUTH_BA.OAUTH_STATE_MISMATCH |
oauth | state cookie did not match |
AUTH_BA.USER_NOT_FOUND |
mapping | custom mapper signalled missing user |
AUTH_BA.EMAIL_NOT_VERIFIED |
session | session present but email unverified |
AUTH_BA.RATE_LIMITED |
provider | Better-Auth rate-limiter blocked |
AUTH_BA.INVALID_REDIRECT |
oauth | redirect URI outside allow-list |
AUTH_BA.PRINCIPAL_MAPPING_FAILED |
mapping | mapper threw |
AUTH_BA.ROUTE_PREFIX_COLLISION |
mount | prefix collides with host-reserved |
Conformance
Conformance tests live at:
src/session/create-session.spec-conformance.test.ts— drivesrunAuthSessionConformanceagainst a Postgres-shaped stubbedBetterAuthLike(the contract asserts the abstractSessionResolverinvariants; DB backing is orthogonal).src/provider/create-provider.spec-conformance.test.ts— drivesrunAuthProviderConformanceagainstcreateBetterAuthProviderwith the same stubbing topology.tests/integration/postgres-better-auth.test.ts— real Postgres container + real Better-Auth + register/login/resolve. Gracefully skips with a logged reason when Docker /pg/ schema bootstrap is unavailable in the runner environment.
Architecture
host config
│
▼
┌─────────────────────────────┐
│ BetterAuthOptions (Zod) │ parseBetterAuthOptions(...)
└──────────────┬──────────────┘
│
per-opts WeakMap
│
▼
┌──────────────┐
│ LazyBetterAuth│ ── betterAuth({ ...args, plugins: [genericOAuth(...)] })
└─────┬────────┘
│ shared (single connection pool)
┌───────┴────────┐
▼ ▼
createBetterAuthSession createBetterAuthProvider
(SessionResolver) (AuthProviderAdapter)
│ │
│ ├── mounts /register, /login, /logout, /refresh,
│ │ /oauth/:provider/start, /oauth/:provider/callback,
│ │ /verify-email/:token, /forgot-password, /reset-password
▼ ▼
Principal auth.handler(rewrittenRequest)