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,132 @@
# 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 '<name>' failed: <detail>"`. 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<DatabaseConfig>, opts?: { signal?: AbortSignal }): Promise<void>;
disconnect(opts?: { signal?: AbortSignal }): Promise<void>;
query<T = Record<string, unknown>>(sql: string, params?: readonly unknown[], opts?: { signal?: AbortSignal }): Promise<T[]>;
execute(sql: string, params?: readonly unknown[], opts?: { signal?: AbortSignal }): Promise<{ rowsAffected: number }>;
transaction<T>(fn: (tx: DatabaseLike) => Promise<T>, opts?: { signal?: AbortSignal }): Promise<T>;
migrate(config?: MigrationConfig, opts?: { signal?: AbortSignal }): Promise<MigrationResult>;
readonly isConnected: boolean;
}
```
Note: `disconnect` maps to `DatabaseLike.close` — `DatabaseAdapter` must satisfy `DatabaseLike` structurally; add a `close(): Promise<void>` 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<string, DatabaseAdapter>` 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<MigrationStatus[]>;
applyMigration(name: string, sql: string, opts?: { signal?: AbortSignal }): Promise<void>;
run(config?: MigrationConfig, opts?: { signal?: AbortSignal }): Promise<MigrationResult>;
}
```
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<T>(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/`.