4.5 KiB
4.5 KiB
plugin/plugin
(no submodules)
Purpose
createAuthPlugin(options) is the factory that returns a typed
PluginDescriptor<AuthPluginConfig> ready to register with the host's
PluginRegistry. It is the canonical exemplar of the 3-way
plug-and-play contract every @suaveplan/api-plugin-* package follows:
the plugin owns its .surql DDL, its Zod schemas, and its OpenAPI
routes, and applies all three during onInit.
Features
- Zod-validated options via
AuthPluginOptionsSchema— invalid input raisesAuthError(AUTH_INVALID_INPUT)carrying theZodError. - 6-step
onInitlifecycle: resolve services → apply schema → construct Better-Auth → registerauthService→ register routes → install session middleware. routePrefix-aware route mounting — paths are rebuilt at the prefix the operator chose, both in the served Hono app and in theOpenAPIRegistrysnapshot.- Graceful degradation — when
openAPIRegistryis absent, routes mount on the Hono app directly and a warning is logged. When neithermigrationRunnernordbis available withapplySchemaOnInit: true, init fails fast withAUTH_SERVICE_NOT_AVAILABLE. - Collision detection — pre-existing routes under the configured
prefix raise
AUTH_ROUTE_PREFIX_COLLISION.
Usage: Basic
import { defineApp } from "@suaveplan/api";
import { createAuthPlugin } from "@suaveplan/api-plugin-auth";
const app = await defineApp()
.name("my-api")
.use(
createAuthPlugin({
betterAuth: {
secret: process.env.BETTER_AUTH_SECRET!,
baseURL: "http://localhost:3000",
emailAndPassword: { enabled: true },
database: surrealAdapter,
},
})
)
.start();
Usage: Advanced
Mount the plugin under a versioned prefix, disable the in-plugin schema apply step (an external migration tool handles DDL), and inject a custom principal mapping that decorates the Better-Auth user with roles derived from the active organisation:
import { defineApp } from "@suaveplan/api";
import {
createAuthPlugin,
mapBetterAuthUserToPrincipal,
} from "@suaveplan/api-plugin-auth";
const app = await defineApp()
.name("my-api")
.use(
createAuthPlugin({
betterAuth: { /* … */ },
routePrefix: "/api/v1/auth",
applySchemaOnInit: false,
principalMapping: (view) => {
const base = mapBetterAuthUserToPrincipal(view);
const session = view.session as { roles?: string[] };
return {
...base,
roles: [...base.roles, ...(session.roles ?? [])],
};
},
})
)
.start();
API Reference
function createAuthPlugin(options: AuthPluginOptions): PluginDescriptor<AuthPluginConfig>
- Validates
optionsviaAuthPluginOptionsSchema. - Returns a
PluginDescriptorwithname: "auth",version: "0.1.0",tags: ["auth", "security"], and the 4 lifecycle hooks (onlyonInit+onCloseare wired;onRegisterandonReadyare default no-ops). onInitruns the canonical 6-step lifecycle described above. It raisesAUTH_*codes on the explicit failure modes — see../error-codes/error-codes.md.onClosereleases the cached Better-Auth reference so the registry can re-init the plugin on subsequent boots.
Implementation Notes
- The descriptor's
configSchemais theAuthPluginOptionsSchemaso the host registry re-validates the merged config at registration time. Thedefaultsfield carries the resolved options so plugins registered without an explicit config still see the parsed shape. - The session middleware is installed AFTER routes are registered so the OpenAPI doc snapshot is taken before any per-request work begins. Order does not affect runtime behaviour but keeps the registry's events ordered consistently across reboots.
- The runtime check against
openAPIRegistry === undefinedexists even though the registry's TS-level slot is non-optional —ctx.getServiceadds| undefinedto every slot's return type, so the absent-runtime case still type-checks. applySchemaprefers the migration runner over the rawdbadapter so operators get migration-record tracking when the runner is wired. Falling back toapplyBetterAuthSchema(db)ensures the plugin still boots in lightweight deployments that ship without@suaveplan/api-migrations.