Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
# OpenSpec Change: enterprise-db
- **Change ID**: enterprise-db
- **Package**: `@suaveplan/db`
- **Category**: universal
- **Target path**: `universal/database/db`
- **Status**: IN PROGRESS
- **Date**: 2026-04-23
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/db.md`
## Summary
`@suaveplan/db` is the universal relational database interface for the genesis monorepo. It ships the abstract `DatabaseAdapter` contract, structured errors with stable `DB.*` codes, Zod-validated config types, injectable connection-singleton helpers, and an `AbortSignal`-aware migration runner interface. The package is the stable foundation that `@suaveplan/db-sqlite` (and future Postgres / Turso adapters) build upon — its public API surface must be locked before any concrete adapter ships.
This genesis build replaces the main-branch implementation with a fully enterprise-quality package: `DatabaseAdapter` extends `DatabaseLike` from `@suaveplan/types`, errors extend `@suaveplan/error` with a stable code registry, every async entrypoint accepts an `AbortSignal`, all public types have Zod schemas, and OTel spans / histograms / counters are wired throughout. Connection singletons shed global module state in favour of injectable registry maps so they are safe in test isolation and serverless environments.
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build. Main branch ships `packages/universal/db` with the surface listed below; genesis rewrites from scratch.
## Available workspace deps on genesis
| Package | Ref | Role |
|---|---|---|
| `@suaveplan/error` | `workspace:^` | Structured error base class + `defineCodes` |
| `@suaveplan/types` | `workspace:^` | `DatabaseLike` minimal adapter contract |
| `@suaveplan/events` | `workspace:^` | Optional lifecycle event bus (connect / disconnect / error events) |
| `zod` | `catalog:forms` | Runtime schema validation |
| `@opentelemetry/api` | `catalog:observability` | OTel spans, counters, histograms (no-op when no exporter) |
| `tslib` | `catalog:build` | TypeScript helpers |
Dev deps (all `catalog:testing`): `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata`.
## Preserved public API surface (from main branch)
All of the following symbols are preserved verbatim or as strict supersets.
### `DatabaseAdapter` (interface)
```ts
// main-branch shape — genesis extends this with DatabaseLike
export interface DatabaseAdapter {
connect(config?: Partial<DatabaseConfig>): Promise<void>;
disconnect(): Promise<void>;
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
execute(sql: string, params?: unknown[]): Promise<void>;
transaction<T>(fn: () => Promise<T>): Promise<T>;
migrate(config?: MigrationConfig): Promise<MigrationResult>;
readonly isConnected: boolean;
}
```
### Connection singleton helpers
```ts
getSingleton(
name: string,
factory: () => DatabaseAdapter,
config?: Partial<DatabaseConfig>
): Promise<DatabaseAdapter>;
closeSingleton(name: string): Promise<void>;
hasSingleton(name: string): boolean;
closeAllSingletons(): Promise<void>;
```
### Errors
```ts
class DatabaseError extends Error {
readonly code: string;
readonly cause?: unknown;
constructor(message: string, code: string, cause?: Error);
}
class MigrationError extends DatabaseError {
readonly migration: string;
constructor(migration: string, detail: string, cause?: Error);
}
```
### Config and result types
```ts
interface DatabaseConfig {
filename?: string; // embedded (SQLite, ':memory:')
url?: string; // network (postgres://, ...)
readonlyMode?: boolean;
}
interface MigrationConfig {
migrationsFolder?: string; // default './drizzle'
migrationsTable?: string; // default '__drizzle_migrations'
}
interface MigrationStatus {
name: string;
applied: boolean;
appliedAt?: Date;
}
interface MigrationResult {
applied: number;
migrations: string[];
}
```
## New additions for genesis
### `DatabaseAdapter` extends `DatabaseLike`
`DatabaseLike` (from `@suaveplan/types`) defines `query`, `execute`, `transaction`, and `close`. The genesis `DatabaseAdapter` extends this contract and adds:
- `connect(config?, opts?: { signal?: AbortSignal }): Promise<void>` — lifecycle open
- `disconnect(opts?: { signal?: AbortSignal }): Promise<void>` — lifecycle close (maps to `DatabaseLike.close`)
- `migrate(config?: MigrationConfig, opts?: { signal?: AbortSignal }): Promise<MigrationResult>`
- `readonly isConnected: boolean` — connection state predicate
`query`, `execute`, and `transaction` gain an optional trailing options object `{ signal?: AbortSignal }` while remaining backward-compatible.
The `transaction` callback receives a `DatabaseLike` (the active transaction handle) matching `DatabaseLike`'s own signature — fixing the main-branch deviation where `fn` accepted no argument.
### Structured errors extending `@suaveplan/error`
`DatabaseError` extends `SuaveplanError` (from `@suaveplan/error`) with stable codes registered via `defineCodes`:
| Code | When thrown |
|---|---|
| `DB.CONNECTION_FAILED` | `connect()` fails |
| `DB.NOT_CONNECTED` | Operation attempted before `connect()` |
| `DB.QUERY_FAILED` | `query()` or `execute()` throws |
| `DB.TRANSACTION_FAILED` | `transaction()` throws or rolls back |
| `DB.MIGRATION_FAILED` | `migrate()` fails on a specific migration |
`MigrationError` extends `DatabaseError` with `DB.MIGRATION_FAILED` and adds a `migration: string` field (the migration name).
### Zod schemas
`DatabaseConfigSchema` and `MigrationConfigSchema` (exported from `*.schemas.ts` siblings). Validated at the boundary of `getSingleton` and `migrate` helpers.
### Injectable registry for singleton helpers
`getSingleton`, `closeSingleton`, `hasSingleton`, and `closeAllSingletons` accept an optional `registry: Map<string, DatabaseAdapter>` parameter. When omitted a module-level default map is used (preserving the main-branch behaviour). This allows test suites to pass isolated registries with zero cross-test bleed.
### OTel instrumentation
All public async entrypoints are wrapped in `@opentelemetry/api` spans. Additionally:
- **Histogram** `db.query.duration_ms` — labelled by `db.operation` (`query` / `execute`)
- **Counter** `db.connection.count` — incremented on successful `connect`, decremented on `disconnect`
- **Counter** `db.transaction.count` — labelled by outcome (`committed` / `rolled_back`)
- **Counter** `db.migration.count` — incremented per applied migration
All instrumentation is lazy-initialised; no global OTel state at module load.
### AbortSignal propagation
Every async method (`connect`, `disconnect`, `query`, `execute`, `transaction`, `migrate`) accepts `opts?: { signal?: AbortSignal }`. The concrete adapter is responsible for honouring the signal — the abstract contract documents the expectation. Abort throws `DatabaseError` with code `DB.QUERY_FAILED` (or the relevant operation code) and `cause` set to an `AbortError`.
### Property-based tests (fast-check)
Fast-check arbitraries model the connection-state machine:
- An unconnected adapter may not have `query`, `execute`, `transaction`, or `migrate` succeed (invariant).
- `connect → disconnect → connect` is idempotent (no state leak).
- `getSingleton` with the same name and registry always returns the same reference.
- `closeAllSingletons` leaves the registry empty regardless of concurrency order.
### Benchmarks (mitata)
`src/__benchmarks__/adapter.bench.ts` — baseline numbers for singleton lookup, OTel span overhead, schema validation. Published in README.
## Impact
- Adds one new package: `universal/database/db`
- Adds one new spec capability: `database/adapter`
- Unblocks `@suaveplan/db-sqlite` (direct dep) and every higher-layer package that injects `DatabaseLike`
- No changes to existing packages; `@suaveplan/types.DatabaseLike` is already shipped