Initial Commit
This commit is contained in:
82
openspec/specs/database/adapter/spec.md
Normal file
82
openspec/specs/database/adapter/spec.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Spec: database/adapter
|
||||
|
||||
**Package**: `@suaveplan/db`
|
||||
**Version**: `0.1.0`
|
||||
**Status**: Shipped 2026-04-23
|
||||
|
||||
## Purpose
|
||||
|
||||
Universal relational database adapter interface for the genesis monorepo. Provides the `DatabaseAdapter` contract (extending `DatabaseLike` from `@suaveplan/types`), injectable connection singletons, structured `DB.*` error codes, Zod-validated config types, a `MigrationRunner` interface, and full OpenTelemetry instrumentation.
|
||||
|
||||
## Public API
|
||||
|
||||
### `DatabaseAdapter` (interface, extends `DatabaseLike`)
|
||||
|
||||
```ts
|
||||
interface DatabaseAdapter extends DatabaseLike {
|
||||
connect(config?: Partial<DatabaseConfig>, opts?: DbOperationOptions): Promise<void>;
|
||||
disconnect(opts?: DbOperationOptions): Promise<void>;
|
||||
query<T>(sql: string, params?: readonly unknown[], opts?: DbOperationOptions): Promise<T[]>;
|
||||
execute(sql: string, params?: readonly unknown[], opts?: DbOperationOptions): Promise<{ rowsAffected: number }>;
|
||||
transaction<T>(fn: (tx: DatabaseLike) => Promise<T>, opts?: DbOperationOptions): Promise<T>;
|
||||
migrate(config?: MigrationConfig, opts?: DbOperationOptions): Promise<MigrationResult>;
|
||||
readonly isConnected: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Connection singletons
|
||||
|
||||
```ts
|
||||
getSingleton(name, factory, config?, opts?, registry?): Promise<DatabaseAdapter>
|
||||
closeSingleton(name, opts?, registry?): Promise<void>
|
||||
hasSingleton(name, registry?): boolean
|
||||
closeAllSingletons(opts?, registry?): Promise<void>
|
||||
```
|
||||
|
||||
`registry` defaults to module-level `Map`; pass an explicit `Map` for test isolation.
|
||||
|
||||
### Error codes (`DB_CODES`)
|
||||
|
||||
| Code | When thrown |
|
||||
|---|---|
|
||||
| `DB.CONNECTION_FAILED` | `connect()` fails |
|
||||
| `DB.NOT_CONNECTED` | Operation before `connect()` |
|
||||
| `DB.QUERY_FAILED` | `query()` or `execute()` throws |
|
||||
| `DB.TRANSACTION_FAILED` | `transaction()` throws |
|
||||
| `DB.MIGRATION_FAILED` | Migration fails |
|
||||
|
||||
`DatabaseError extends SuaveplanError<DbCode>` · `MigrationError extends DatabaseError` (adds `migration: string`)
|
||||
|
||||
### Config types
|
||||
|
||||
- `DatabaseConfig` — `filename?`, `url?`, `readonlyMode?` (default `false`)
|
||||
- `MigrationConfig` — `migrationsFolder?` (default `"./drizzle"`), `migrationsTable?` (default `"__drizzle_migrations"`)
|
||||
- `MigrationResult` — `{ applied: number, migrations: string[] }`
|
||||
- `MigrationStatus` — `{ name: string, applied: boolean, appliedAt?: Date }`
|
||||
|
||||
Zod schemas: `DatabaseConfigSchema`, `MigrationConfigSchema` (both `.strict()`).
|
||||
|
||||
### `MigrationRunner` (interface)
|
||||
|
||||
```ts
|
||||
interface MigrationRunner {
|
||||
pendingMigrations(config?: MigrationConfig): Promise<MigrationStatus[]>;
|
||||
applyMigration(name: string, sql: string, opts?: DbOperationOptions): Promise<void>;
|
||||
run(config?: MigrationConfig, opts?: DbOperationOptions): Promise<MigrationResult>;
|
||||
}
|
||||
```
|
||||
|
||||
### OTel instrumentation
|
||||
|
||||
- `withDbSpan(name, fn, attrs?)` — wraps async fn in a named OTel span
|
||||
- `recordQueryDuration(ms, operation)` — histogram `db.query.duration_ms`
|
||||
- `recordConnectionDelta(±1, dbName)` — UpDownCounter `db.connection.count`
|
||||
- `recordTransaction(outcome)` — counter `db.transaction.count`
|
||||
- `recordMigration(name)` — counter `db.migration.count`
|
||||
- `resetInstruments()` — clears lazy singleton (test isolation)
|
||||
|
||||
## Quality
|
||||
|
||||
- 100% Istanbul coverage (statements/branches/functions/lines)
|
||||
- 63 unit tests + property-based tests (fast-check)
|
||||
- Benchmarks (mitata): `src/__benchmarks__/singleton.bench.ts`, `otel.bench.ts`
|
||||
Reference in New Issue
Block a user