# Tasks: enterprise-db ## 1. Scaffold - [x] 1.1 Create directory `universal/database/db/` with `package.json` (`name: "@suaveplan/db"`, `version: "0.1.0"`, `sideEffects: false`). Add deps: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/events workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`. Add dev deps: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `fast-check catalog:testing`, `mitata catalog:testing`. - [x] 1.2 Create `tsconfig.json` extending `@suaveplan/typescript-config/library.json`. Create `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` with `pool: 'forks'`, `provider: 'istanbul'`, thresholds `100/100/100/100`. - [x] 1.3 Add Turborepo pipeline entries (`lint`, `typecheck`, `test`, `test:coverage`, `build`) in `turbo.json` if not already covered by root glob. Verify `universal/database/*` is in the root `package.json` workspace glob. - [x] 1.4 Add `@suaveplan/db` to `CLAUDE.md` package listing and `README.md` table in the repo root. ## 2. Types and errors - [x] 2.1 Create `src/errors/db-codes.ts`: call `defineCodes` from `@suaveplan/error` to register `DB.CONNECTION_FAILED`, `DB.NOT_CONNECTED`, `DB.QUERY_FAILED`, `DB.TRANSACTION_FAILED`, `DB.MIGRATION_FAILED` with stable integer codes and human-readable descriptions. - [x] 2.2 Create `src/errors/database-error.ts`: `DatabaseError extends SuaveplanError` with `readonly code: string` and typed `cause`. Constructor accepts `(message, code, cause?)`. Export from `src/errors/index.ts`. - [x] 2.3 Create `src/errors/migration-error.ts`: `MigrationError extends DatabaseError` with `readonly migration: string`. Constructor sets code `DB.MIGRATION_FAILED` and formats `message` as `"Migration '' failed: "`. Export from barrel. - [x] 2.4 Write `src/errors/database-error.test.ts` and `src/errors/migration-error.test.ts`: verify `instanceof SuaveplanError`, stable `code` value, `cause` chain, `name` property, and message format. { timeout: 5000 }. - [x] 2.5 Write `src/errors/database-error.md` and `src/errors/migration-error.md` (≥200 words each, all required sections). - [x] 2.6 Create `src/types/database-config.ts`: `DatabaseConfig` interface (`filename?`, `url?`, `readonlyMode?`). Create `src/types/migration-config.ts`: `MigrationConfig` (`migrationsFolder?`, `migrationsTable?`). Create `src/types/migration-result.ts`: `MigrationResult` (`applied: number`, `migrations: string[]`) and `MigrationStatus` (`name: string`, `applied: boolean`, `appliedAt?: Date`). Export all from `src/types/index.ts`. - [x] 2.7 Create `src/types/database-config.schemas.ts` and `src/types/migration-config.schemas.ts` with Zod schemas `DatabaseConfigSchema` and `MigrationConfigSchema`. Include `z.object(...)` with `.optional()` fields and sensible `.default()` coercions where applicable. - [x] 2.8 Write `src/types/types.test.ts`: parse valid and invalid inputs against both schemas; verify Zod error messages and coerced defaults. { timeout: 5000 }. - [x] 2.9 Write `src/types/database-config.md` and `src/types/migration-config.md` (≥200 words each). ## 3. `DatabaseAdapter` abstract interface - [x] 3.1 Create `src/adapter/database-adapter.ts`: define `DatabaseAdapter` extending `DatabaseLike` (from `@suaveplan/types`). Full signature: ```ts interface DatabaseAdapter extends DatabaseLike { connect(config?: Partial, opts?: { signal?: AbortSignal }): Promise; disconnect(opts?: { signal?: AbortSignal }): Promise; query>(sql: string, params?: readonly unknown[], opts?: { signal?: AbortSignal }): Promise; execute(sql: string, params?: readonly unknown[], opts?: { signal?: AbortSignal }): Promise<{ rowsAffected: number }>; transaction(fn: (tx: DatabaseLike) => Promise, opts?: { signal?: AbortSignal }): Promise; migrate(config?: MigrationConfig, opts?: { signal?: AbortSignal }): Promise; readonly isConnected: boolean; } ``` Note: `disconnect` maps to `DatabaseLike.close` — `DatabaseAdapter` must satisfy `DatabaseLike` structurally; add a `close(): Promise` alias declaration if needed. - [x] 3.2 Create `src/adapter/index.ts` barrel re-exporting `DatabaseAdapter`. - [x] 3.3 Write `src/adapter/database-adapter.test.ts`: create a minimal in-memory stub (no `vi.mock`) that implements every method; verify structural compatibility with `DatabaseLike`; verify `isConnected` transitions on connect/disconnect; verify `AbortSignal` respected by the stub. { timeout: 5000 }. - [x] 3.4 Write `src/adapter/database-adapter.md` (≥200 words, full API reference, both examples). ## 4. Connection singleton helpers - [x] 4.1 Create `src/connection/singleton.ts`: implement `getSingleton`, `closeSingleton`, `hasSingleton`, `closeAllSingletons`. Each function accepts an optional trailing `registry?: Map` parameter; when omitted, fall back to a module-level default `Map`. `getSingleton` validates `config` with `DatabaseConfigSchema` before calling `adapter.connect`. Wrap `connect` in a try/catch; throw `DatabaseError` with `DB.CONNECTION_FAILED` on failure. - [x] 4.2 Create `src/connection/index.ts` barrel. - [x] 4.3 Write `src/connection/singleton.test.ts`: - Pass an isolated registry `Map` to every call — no cross-test bleed. - `getSingleton` creates and caches; second call returns same reference. - `hasSingleton` reflects registry state accurately. - `closeSingleton` calls `disconnect` and removes entry. - `closeAllSingletons` drains the registry even when one disconnect rejects (test error-isolation path). - `getSingleton` with already-connected adapter skips `connect`. - Abort via `AbortSignal` propagated to `connect`. - { timeout: 30000 } for async tests. - [x] 4.4 Write `src/connection/singleton.md` (≥200 words, registry-isolation example mandatory in advanced section). ## 5. Migration types and runner interface - [x] 5.1 Create `src/migration/migration-runner.ts`: define `MigrationRunner` interface: ```ts interface MigrationRunner { pendingMigrations(config?: MigrationConfig): Promise; applyMigration(name: string, sql: string, opts?: { signal?: AbortSignal }): Promise; run(config?: MigrationConfig, opts?: { signal?: AbortSignal }): Promise; } ``` This is the contract `DatabaseAdapter.migrate` delegates to in concrete adapters. - [x] 5.2 Create `src/migration/index.ts` barrel re-exporting `MigrationRunner`, `MigrationStatus`, `MigrationResult`, `MigrationConfig`, and `MigrationError`. - [x] 5.3 Write `src/migration/migration-runner.test.ts`: stub `MigrationRunner` implementation; verify `run` aggregates `applyMigration` calls; verify `MigrationError` thrown with correct `migration` field on failure; verify `AbortSignal` halts mid-run. { timeout: 30000 }. - [x] 5.4 Write `src/migration/migration-runner.md` (≥200 words). ## 6. OTel instrumentation - [x] 6.1 Create `src/telemetry/instruments.ts`: lazy-initialise (using a module-level singleton guard, NOT top-level `getMeter()` calls) the following instruments via `@opentelemetry/api`: - Histogram `db.query.duration_ms` — attributes: `db.operation` (`query` | `execute`) - UpDownCounter `db.connection.count` — attributes: `db.name` - Counter `db.transaction.count` — attributes: `db.outcome` (`committed` | `rolled_back`) - Counter `db.migration.count` — attributes: `db.migration.name` - [x] 6.2 Create `src/telemetry/trace.ts`: helper `withDbSpan(tracer, name, fn, attrs?)` that creates a span, calls `fn(span)`, sets span status on success/error, and ends the span. Used internally by singleton helpers and the migration runner helper. - [x] 6.3 Create `src/telemetry/index.ts` barrel. - [x] 6.4 Write `src/telemetry/instruments.test.ts` and `src/telemetry/trace.test.ts`: verify no OTel error thrown when no SDK is registered (no-op provider); verify span name and attributes passed correctly; verify histogram records timing. { timeout: 5000 }. - [x] 6.5 Write `src/telemetry/instruments.md` and `src/telemetry/trace.md` (≥200 words each). ## 7. Benchmarks - [x] 7.1 Create `src/__benchmarks__/singleton.bench.ts` (mitata): benchmark `getSingleton` cache-hit path (no connect), `hasSingleton` lookup, and `DatabaseConfigSchema.parse` for a valid config. Publish numbers in `README.md`. - [x] 7.2 Create `src/__benchmarks__/otel.bench.ts` (mitata): benchmark `withDbSpan` overhead against a raw async call using the OTel no-op provider. ## 8. Property-based tests (fast-check) - [x] 8.1 Create `src/__tests__/state-machine.property.test.ts`: fast-check arbitraries for: - Connection-state invariant: any sequence of `connect`/`disconnect` on a stub adapter leaves `isConnected` consistent with the last operation. - Registry invariant: any sequence of `getSingleton` / `closeSingleton` / `closeAllSingletons` calls on an isolated registry map never leaves a dangling reference. - `DatabaseConfigSchema` round-trip: arbitrary partial config objects parse without throwing when all present fields are valid. - `getSingleton` idempotency: calling with the same `name` twice on a connected registry returns `===` the same object. - { timeout: 30000 } ## 9. Docs and README - [x] 9.1 Write `README.md` at the package root: description, `bun add @suaveplan/db`, quick-start (getSingleton + query), **Modules** section linking all co-located `.md` files, benchmark table (placeholder — fill after 7.x runs). - [x] 9.2 Verify every implementation `.ts` (non-test, non-barrel, non-types) has a sibling `.md` with ≥200 words and both required code examples. - [x] 9.3 Run the missing-md scan from `openspec/AGENTS.md §11` — zero output required. ## 10. Barrel and subpath exports - [x] 10.1 Create `src/index.ts` top-level barrel re-exporting: `DatabaseAdapter`, `getSingleton`, `hasSingleton`, `closeSingleton`, `closeAllSingletons`, `DatabaseError`, `MigrationError`, `DatabaseConfig`, `MigrationConfig`, `MigrationResult`, `MigrationStatus`, `MigrationRunner`, `DatabaseConfigSchema`, `MigrationConfigSchema`, DB error codes. - [x] 10.2 Add subpath exports to `package.json`: - `"."` — top-level barrel - `"./adapter/database-adapter"` — adapter contract - `"./connection/singleton"` — singleton helpers - `"./errors/database-error"` — DatabaseError + MigrationError - `"./migration/migration-runner"` — MigrationRunner + migration types - `"./telemetry/instruments"` — OTel instruments - `"./telemetry/trace"` — withDbSpan helper - `"./types/database-config"` — DatabaseConfig + schema - `"./types/migration-config"` — MigrationConfig + schema - [x] 10.3 Add a tree-shake test (`src/__tests__/tree-shake.test.ts`) that dynamically imports each subpath and asserts no unexpected side effects (global state mutations, OTel registration). Confirm `sideEffects: false` is set in `package.json`. ## 11. Verify and publish - [x] 11.1 Run the full pre-ship verification sequence from `openspec/AGENTS.md §11`: 1. `bunx turbo lint typecheck test --filter=@suaveplan/db` — green 2. `bunx turbo test:coverage --filter=@suaveplan/db` — 100/100/100/100 3. `bunx turbo build --filter=@suaveplan/db` — clean dist 4. Banned-pattern scan — zero matches 5. Missing-md scan — zero matches 6. README check — present 7. Bare-version check — zero bare versions 8. Subpath export count matches feature module count - [x] 11.2 Confirm `@suaveplan/db-sqlite` can be started (it depends on this package — interface stability check only; do not build db-sqlite as part of this task). - [x] 11.3 `bun scripts/publish.ts --package @suaveplan/db` - [x] 11.4 Fold spec delta into `openspec/specs/database/adapter/spec.md` (create if absent). - [x] 11.5 Move `openspec/changes/enterprise-db/` → `openspec/archive/2026-04-23-enterprise-db/`.