12 KiB
12 KiB
Tasks: enterprise-db
1. Scaffold
- 1.1 Create directory
universal/database/db/withpackage.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. - 1.2 Create
tsconfig.jsonextending@suaveplan/typescript-config/library.json. Createvitest.config.tsusingcreateTestConfig()from@suaveplan/testing/configwithpool: 'forks',provider: 'istanbul', thresholds100/100/100/100. - 1.3 Add Turborepo pipeline entries (
lint,typecheck,test,test:coverage,build) inturbo.jsonif not already covered by root glob. Verifyuniversal/database/*is in the rootpackage.jsonworkspace glob. - 1.4 Add
@suaveplan/dbtoCLAUDE.mdpackage listing andREADME.mdtable in the repo root.
2. Types and errors
- 2.1 Create
src/errors/db-codes.ts: calldefineCodesfrom@suaveplan/errorto registerDB.CONNECTION_FAILED,DB.NOT_CONNECTED,DB.QUERY_FAILED,DB.TRANSACTION_FAILED,DB.MIGRATION_FAILEDwith stable integer codes and human-readable descriptions. - 2.2 Create
src/errors/database-error.ts:DatabaseError extends SuaveplanErrorwithreadonly code: stringand typedcause. Constructor accepts(message, code, cause?). Export fromsrc/errors/index.ts. - 2.3 Create
src/errors/migration-error.ts:MigrationError extends DatabaseErrorwithreadonly migration: string. Constructor sets codeDB.MIGRATION_FAILEDand formatsmessageas"Migration '<name>' failed: <detail>". Export from barrel. - 2.4 Write
src/errors/database-error.test.tsandsrc/errors/migration-error.test.ts: verifyinstanceof SuaveplanError, stablecodevalue,causechain,nameproperty, and message format. { timeout: 5000 }. - 2.5 Write
src/errors/database-error.mdandsrc/errors/migration-error.md(≥200 words each, all required sections). - 2.6 Create
src/types/database-config.ts:DatabaseConfiginterface (filename?,url?,readonlyMode?). Createsrc/types/migration-config.ts:MigrationConfig(migrationsFolder?,migrationsTable?). Createsrc/types/migration-result.ts:MigrationResult(applied: number,migrations: string[]) andMigrationStatus(name: string,applied: boolean,appliedAt?: Date). Export all fromsrc/types/index.ts. - 2.7 Create
src/types/database-config.schemas.tsandsrc/types/migration-config.schemas.tswith Zod schemasDatabaseConfigSchemaandMigrationConfigSchema. Includez.object(...)with.optional()fields and sensible.default()coercions where applicable. - 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 }. - 2.9 Write
src/types/database-config.mdandsrc/types/migration-config.md(≥200 words each).
3. DatabaseAdapter abstract interface
- 3.1 Create
src/adapter/database-adapter.ts: defineDatabaseAdapterextendingDatabaseLike(from@suaveplan/types). Full signature:Note: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; }disconnectmaps toDatabaseLike.close—DatabaseAdaptermust satisfyDatabaseLikestructurally; add aclose(): Promise<void>alias declaration if needed. - 3.2 Create
src/adapter/index.tsbarrel re-exportingDatabaseAdapter. - 3.3 Write
src/adapter/database-adapter.test.ts: create a minimal in-memory stub (novi.mock) that implements every method; verify structural compatibility withDatabaseLike; verifyisConnectedtransitions on connect/disconnect; verifyAbortSignalrespected by the stub. { timeout: 5000 }. - 3.4 Write
src/adapter/database-adapter.md(≥200 words, full API reference, both examples).
4. Connection singleton helpers
- 4.1 Create
src/connection/singleton.ts: implementgetSingleton,closeSingleton,hasSingleton,closeAllSingletons. Each function accepts an optional trailingregistry?: Map<string, DatabaseAdapter>parameter; when omitted, fall back to a module-level defaultMap.getSingletonvalidatesconfigwithDatabaseConfigSchemabefore callingadapter.connect. Wrapconnectin a try/catch; throwDatabaseErrorwithDB.CONNECTION_FAILEDon failure. - 4.2 Create
src/connection/index.tsbarrel. - 4.3 Write
src/connection/singleton.test.ts:- Pass an isolated registry
Mapto every call — no cross-test bleed. getSingletoncreates and caches; second call returns same reference.hasSingletonreflects registry state accurately.closeSingletoncallsdisconnectand removes entry.closeAllSingletonsdrains the registry even when one disconnect rejects (test error-isolation path).getSingletonwith already-connected adapter skipsconnect.- Abort via
AbortSignalpropagated toconnect. - { timeout: 30000 } for async tests.
- Pass an isolated registry
- 4.4 Write
src/connection/singleton.md(≥200 words, registry-isolation example mandatory in advanced section).
5. Migration types and runner interface
- 5.1 Create
src/migration/migration-runner.ts: defineMigrationRunnerinterface:This is the contractinterface MigrationRunner { pendingMigrations(config?: MigrationConfig): Promise<MigrationStatus[]>; applyMigration(name: string, sql: string, opts?: { signal?: AbortSignal }): Promise<void>; run(config?: MigrationConfig, opts?: { signal?: AbortSignal }): Promise<MigrationResult>; }DatabaseAdapter.migratedelegates to in concrete adapters. - 5.2 Create
src/migration/index.tsbarrel re-exportingMigrationRunner,MigrationStatus,MigrationResult,MigrationConfig, andMigrationError. - 5.3 Write
src/migration/migration-runner.test.ts: stubMigrationRunnerimplementation; verifyrunaggregatesapplyMigrationcalls; verifyMigrationErrorthrown with correctmigrationfield on failure; verifyAbortSignalhalts mid-run. { timeout: 30000 }. - 5.4 Write
src/migration/migration-runner.md(≥200 words).
6. OTel instrumentation
- 6.1 Create
src/telemetry/instruments.ts: lazy-initialise (using a module-level singleton guard, NOT top-levelgetMeter()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
- Histogram
- 6.2 Create
src/telemetry/trace.ts: helperwithDbSpan<T>(tracer, name, fn, attrs?)that creates a span, callsfn(span), sets span status on success/error, and ends the span. Used internally by singleton helpers and the migration runner helper. - 6.3 Create
src/telemetry/index.tsbarrel. - 6.4 Write
src/telemetry/instruments.test.tsandsrc/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 }. - 6.5 Write
src/telemetry/instruments.mdandsrc/telemetry/trace.md(≥200 words each).
7. Benchmarks
- 7.1 Create
src/__benchmarks__/singleton.bench.ts(mitata): benchmarkgetSingletoncache-hit path (no connect),hasSingletonlookup, andDatabaseConfigSchema.parsefor a valid config. Publish numbers inREADME.md. - 7.2 Create
src/__benchmarks__/otel.bench.ts(mitata): benchmarkwithDbSpanoverhead against a raw async call using the OTel no-op provider.
8. Property-based tests (fast-check)
- 8.1 Create
src/__tests__/state-machine.property.test.ts: fast-check arbitraries for:- Connection-state invariant: any sequence of
connect/disconnecton a stub adapter leavesisConnectedconsistent with the last operation. - Registry invariant: any sequence of
getSingleton/closeSingleton/closeAllSingletonscalls on an isolated registry map never leaves a dangling reference. DatabaseConfigSchemaround-trip: arbitrary partial config objects parse without throwing when all present fields are valid.getSingletonidempotency: calling with the samenametwice on a connected registry returns===the same object.- { timeout: 30000 }
- Connection-state invariant: any sequence of
9. Docs and README
- 9.1 Write
README.mdat the package root: description,bun add @suaveplan/db, quick-start (getSingleton + query), Modules section linking all co-located.mdfiles, benchmark table (placeholder — fill after 7.x runs). - 9.2 Verify every implementation
.ts(non-test, non-barrel, non-types) has a sibling.mdwith ≥200 words and both required code examples. - 9.3 Run the missing-md scan from
openspec/AGENTS.md §11— zero output required.
10. Barrel and subpath exports
- 10.1 Create
src/index.tstop-level barrel re-exporting:DatabaseAdapter,getSingleton,hasSingleton,closeSingleton,closeAllSingletons,DatabaseError,MigrationError,DatabaseConfig,MigrationConfig,MigrationResult,MigrationStatus,MigrationRunner,DatabaseConfigSchema,MigrationConfigSchema, DB error codes. - 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
- 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). ConfirmsideEffects: falseis set inpackage.json.
11. Verify and publish
- 11.1 Run the full pre-ship verification sequence from
openspec/AGENTS.md §11:bunx turbo lint typecheck test --filter=@suaveplan/db— greenbunx turbo test:coverage --filter=@suaveplan/db— 100/100/100/100bunx turbo build --filter=@suaveplan/db— clean dist- Banned-pattern scan — zero matches
- Missing-md scan — zero matches
- README check — present
- Bare-version check — zero bare versions
- Subpath export count matches feature module count
- 11.2 Confirm
@suaveplan/db-sqlitecan be started (it depends on this package — interface stability check only; do not build db-sqlite as part of this task). - 11.3
bun scripts/publish.ts --package @suaveplan/db - 11.4 Fold spec delta into
openspec/specs/database/adapter/spec.md(create if absent). - 11.5 Move
openspec/changes/enterprise-db/→openspec/archive/2026-04-23-enterprise-db/.