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,71 @@
# `plugin`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<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.