# `plugin` _(no submodules)_ ## Purpose Exports `createDatabasePlugin`, the top-level factory that produces the `PluginDescriptor` consumed by `@suaveplan/api-plugin`'s `PluginRegistry`. The factory composes every other module in the package — `DatabasePluginOptionsSchema` validates options at the boundary, `ConnectionPool` owns lifecycle and concurrency control, `runMigrations` handles ledger-backed `.surql` application under an advisory lock, the `db` service slot is augmented onto `ServiceRegistry`, and the OTel instrument bundle is shared with the pool. This file is the single integration point — every consumer of the package builds its descriptor through this one function. The descriptor binds three lifecycle hooks. `onInit` constructs the pool, eagerly acquires the primary adapter, and registers it under `ServiceRegistry["db"]`. `onReady` runs migrations under advisory lock when `migrations.autoRun` is `true`. `onClose` drains the pool. Construction-time Zod validation surfaces invalid options as `DatabaseError(DATABASE.INVALID_CONFIG)` so misconfigurations fail at registry intake rather than during the first request. ## Features - Zod-validated options at construction time; invalid input surfaces `DATABASE.INVALID_CONFIG`. - Three lifecycle hooks wired against a single `PluginRuntime` cell — no cross-hook globals. - Injectable `adapterFactory` so tests run against a fake adapter without touching surrealdb. - Auto-registers the canonical `db` service slot via module augmentation. - Pool-acquired primary adapter exposed as `db` — every other plugin sees a fully connected `DatabaseAdapter`. ## Usage: Basic ```ts import { createDatabasePlugin } from "@suaveplan/api-plugin-database/plugin/plugin"; import { createPluginRegistry } from "@suaveplan/api-plugin"; const registry = createPluginRegistry(); registry.register( createDatabasePlugin({ url: "ws://localhost:8000/rpc" }) ); await registry.initialize(); ``` ## Usage: Advanced ```ts import { createDatabasePlugin } from "@suaveplan/api-plugin-database"; import { PooledSurrealAdapter } from "@suaveplan/api-plugin-database"; const plugin = createDatabasePlugin({ url: process.env.SURREAL_URL, namespace: "prod", database: "main", username: process.env.SURREAL_USER, password: process.env.SURREAL_PASS, pool: { min: 2, max: 50, idleMs: 60_000, acquireTimeoutMs: 5_000 }, retry: { maxAttempts: 5, baseBackoffMs: 100, maxBackoffMs: 10_000, jitter: true }, tenancy: { strategy: "namespace-per-tenant" }, migrations: { autoRun: true, directory: "./surql", ledgerTable: "_migrations", lockTable: "_migration_lock", leaseMs: 60_000, }, adapterFactory: (cfg) => new PooledSurrealAdapter({ ...cfg }), }); ``` ## API Reference ### `createDatabasePlugin(options?): PluginDescriptor` Returns the plugin descriptor consumed by `PluginRegistry.register`. Throws `DatabaseError(DATABASE.INVALID_CONFIG)` synchronously if `options` fail Zod validation. ## Implementation Notes - The factory keeps the pool, the primary adapter, and the runtime cell inside a closure so multiple `createDatabasePlugin` calls produce isolated runtimes. There is no module-level global state. - `onReady` calls `getRawClient()` on the primary adapter to obtain the raw `SurrealClientLike` the migration runner needs. Because the primary adapter is acquired in `onInit` and held until `onClose`, the raw client is always live by the time `onReady` runs. - `onClose` releases the primary adapter back to the pool before calling `close()` so the pool's drain phase isn't fighting an `inUse` member. The release is best-effort — `pool.release(adapter)` silently ignores unknown adapters, so multiple shutdown attempts are safe. - The descriptor declares zero `dependencies` — consumers that need the database service register this plugin first.