Move docs/ package mirror into document-navigator plugin

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>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
# @suaveplan/api-db-adapter — bridge from @suaveplan/db engine adapters to the plugin DatabaseLike surface. Provides AdminDatabaseLike + TenantScopedDatabaseLike factories, a per-engine migration runner, the db.unsafe-queries capability gate, an open DatabaseEngineRegistry, and a worker-proxy variant for the worker host. Both direct + proxy surfaces pass the same conformance suite.
Stage-4 bridge from the `@suaveplan/db` engine adapters to the
plugin `AdminDatabaseLike` / `TenantScopedDatabaseLike` surfaces exposed by
`@suaveplan/api-types/db`.
The package contains five orthogonal pieces:
1. `options` — Zod-validated `DbAdapterOptions` (`engine`, `url`, `pool?`,
`tenantColumn`, `migrationsDir?`, `autoInjectInserts`).
2. `engine-registry` — open registry of engine factories. The package
itself ships no engines pre-installed; host code (or a downstream
adapter package) calls `registerEngine("sqlite" | "postgres" |
"surrealdb" | <merged>, factory)` once at boot. The shape keeps the
package universally importable.
3. `tenant-scoped` — the SQL rewriter that injects `WHERE tenant_id = ?`
on every SELECT/UPDATE/DELETE, verifies / auto-injects the tenant
column on INSERT, and rejects DDL or unsupported SQL with
`DB_ADAPTER.TENANT_SCOPE_VIOLATION`. Strict / lenient INSERT mode is
`autoInjectInserts` on the options.
4. `migrations` — per-engine migration runner consuming a directory of
`*.sql` files; tracks applied versions in `__plugin_migrations` and
is idempotent on re-run. Failed migrations roll back where the engine
supports transactions.
5. `unsafe` — the `db.unsafe` escape hatch, gated on the
`db.unsafe-queries` capability flag in the plugin manifest. Every
permitted call increments `plugin.unsafe_query_total{plugin}`.
The package also ships a worker-side counterpart (`createDbAdapterWorkerProxy`)
that wires the worker-side `api-db-worker-proxy` client into the same surface
so direct + proxy variants are interchangeable in plugin code.
<!-- begin:auto-docs -->
## Documentation tree
- [engine-registry](./src/engine-registry/engine-registry.md) — The engine registry is the extension point that lets `api-db-adapter` stay universally importable while still supportin…
- [engine-registry](./src/engine-registry/engine-registry.md) — The engine registry is the extension point that lets `api-db-adapter` stay universally importable while still supportin…
- [error-codes](./src/error-codes/error-codes.md) — This module is the single source of truth for every public failure code raised by `@suaveplan/api-db-adapter`. Codes ar…
- [error-codes](./src/error-codes/error-codes.md) — This module is the single source of truth for every public failure code raised by `@suaveplan/api-db-adapter`. Codes ar…
- [factory](./src/factory/create-db-adapter.md) — `createDbAdapter` is the public entry point for host code that wants to wire the `@suaveplan/db` engine adapter…
- [create-db-adapter](./src/factory/create-db-adapter.md) — `createDbAdapter` is the public entry point for host code that wants to wire the `@suaveplan/db` engine adapter…
- [migrations](./src/migrations/run-migrations.md) — `runMigrations` applies a plugin's pending migrations from a directory of `*.sql` files, recording applied versions in…
- [run-migrations](./src/migrations/run-migrations.md) — `runMigrations` applies a plugin's pending migrations from a directory of `*.sql` files, recording applied versions in…
- [options](./src/options/db-adapter-options.md) — `DbAdapterOptions` is the single config object accepted by every public factory in this package — `createDbAdapter`, `c…
- [db-adapter-options](./src/options/db-adapter-options.md) — `DbAdapterOptions` is the single config object accepted by every public factory in this package — `createDbAdapter`, `c…
- [proxy](./src/proxy/create-db-adapter-worker-proxy.md) — `createDbAdapterWorkerProxy` is the worker-side counterpart to `createDbAdapter`. Inside an `api-worker-host` worker, p…
- [create-db-adapter-worker-proxy](./src/proxy/create-db-adapter-worker-proxy.md) — `createDbAdapterWorkerProxy` is the worker-side counterpart to `createDbAdapter`. Inside an `api-worker-host` worker, p…
- [tenant-scoped](./src/tenant-scoped/create-tenant-scoped-wrapper.md) — This module is the load-bearing safety boundary between a plugin route handler and the underlying admin database surfac…
- [create-tenant-scoped-wrapper](./src/tenant-scoped/create-tenant-scoped-wrapper.md) — This module is the load-bearing safety boundary between a plugin route handler and the underlying admin database surfac…
- [unsafe](./src/unsafe/unsafe-query-gate.md) — `createUnsafeGate` builds the `ctx.db.unsafe` API exposed on every tenant- scoped surface. The gate is the only sanctio…
- [unsafe-query-gate](./src/unsafe/unsafe-query-gate.md) — `createUnsafeGate` builds the `ctx.db.unsafe` API exposed on every tenant- scoped surface. The gate is the only sanctio…
<!-- end:auto-docs -->
## Quick start
```ts
import { createDbAdapter } from "@suaveplan/api-db-adapter/adapter";
import { registerEngine } from "@suaveplan/api-db-adapter/engine-registry";
import { runMigrations } from "@suaveplan/api-db-adapter/migrations";
registerEngine("sqlite", async (opts) => myBunSqliteFactory(opts));
const db = await createDbAdapter({ engine: "sqlite", url: ":memory:" });
await runMigrations({
plugin: "audit",
capabilities: [],
admin: db.admin,
migrationsDir: "./db/migrations",
fs: nodeFsReader,
});
const scoped = db.scoped("acme" as TenantId);
const rows = await scoped.query<User[]>("SELECT * FROM users");
// actual SQL: SELECT * FROM users WHERE tenant_id = ?
// bound: ["acme"]
```
## Tenant-scope semantics
The wrapper rewrites SQL based on the leading keyword (case-insensitive):
| Shape | Rewrite |
|---|---|
| `SELECT ... [WHERE ...]` | append `WHERE`/`AND tenant_id = ?` before any ORDER BY / LIMIT / RETURNING / GROUP BY tail |
| `UPDATE ... [WHERE ...]` | same |
| `DELETE ... [WHERE ...]` | same |
| `INSERT INTO t (cols) VALUES (vals)` w/o `tenant_id` and `autoInjectInserts: true` | inject column + value |
| same w/o `tenant_id` and `autoInjectInserts: false` | throw `TENANT_SCOPE_VIOLATION` |
| `INSERT ...` w/ conflicting `tenant_id` (literal or bound) | throw `TENANT_SCOPE_VIOLATION` |
| anything else (DDL, INSERT...SELECT, multi-row VALUES, record-shaped params) | throw `TENANT_SCOPE_VIOLATION` — use a migration or `db.unsafe` |
## `db.unsafe` escape hatch
`createUnsafeGate({ plugin, capabilities, admin })` returns an
`UnsafeQueryApi` (`query`, `execute`). The capability check is array
membership on `db.unsafe-queries`; absent → throw
`DB_ADAPTER.UNSAFE_NOT_PERMITTED`. Permitted calls increment
`plugin.unsafe_query_total{plugin}`.
## Migration directory layout
Files are read from `migrationsDir` and applied lexicographically. The
recommended naming convention is `NNN_<short-description>.sql`:
```
db/migrations/
001_init.sql
002_add_invoices.sql
003_index_invoices_on_org.sql
```
The runner records each applied file in `__plugin_migrations(plugin,
version, applied_at)` and is idempotent — re-running with no new files is
a no-op.
## Error codes
- `DB_ADAPTER.TENANT_SCOPE_VIOLATION` — scoped wrapper saw conflicting
tenant value, DDL, or unsupported SQL shape.
- `DB_ADAPTER.UNSAFE_NOT_PERMITTED` — `db.unsafe.*` invoked without
`db.unsafe-queries` capability.
- `DB_ADAPTER.MIGRATION_FAILED` — migration body threw; rolled back where
supported.
- `DB_ADAPTER.ENGINE_UNSUPPORTED` — `engine` value has no registered
factory.
- `DB_ADAPTER.INVALID_OPTIONS` — Zod validation failed; issues attached
to `error.context.issues`.