Files
skills/docs/api-stack/api/api-plugin-database/src/plugin/plugin.md
2026-05-29 08:20:56 +01:00

3.9 KiB

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

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

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<DatabasePluginConfigParsed>

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.