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,241 @@
# Tasks: enterprise-migration
**Change ID**: enterprise-migration
**Package**: `@suaveplan/migration`
**Target path**: `universal/database/migration`
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/db`, `@suaveplan/logger`, `@suaveplan/events`
---
## 1. Scaffold
- [x] Create directory `universal/database/migration/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories per the layout in `proposal.md`.
- [x] Write `package.json` at version `0.1.0` with:
- `"type": "module"`, `"sideEffects": false`
- `exports` map: top-level `"."` barrel plus subpaths for `./planner/migration-planner`, `./runner/migration-runner`, `./runner/migration-executor`, `./runner/dry-run-reporter`, `./store/memory-store`, `./store/database-store`, `./lock/memory-lock`, `./lock/distributed-lock`, `./lock/redis-lock`, `./status/migration-status`, `./batch/batch-migrator`, `./adapters/surrealdb-adapter`
- `dependencies`: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/db workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/events workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `fast-check catalog:testing`, `mitata catalog:testing`, `@suaveplan/testing workspace:^`, `@suaveplan/db-sqlite workspace:^`
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`.
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` (pool: `forks`).
- [x] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `bench` scripts to `package.json`.
---
## 2. Types + errors
### 2a. Migration types (`src/types/migration.types.ts` + `src/types/migration.types.md`)
- [x] Define all public TS interfaces: `Migration`, `MigrationContext`, `MigrationDirection`, `MigrationStatus`, `MigrationRecord`, `MigrationPlan`, `MigrationResult`, `ValidationError`, `ValidationResult`, `MigrationRunnerConfig`, `RunOptions`, `MigrationStoreAdapter`, `MigrationLockAdapter`, `DatabaseAdapter`, `DatabaseStoreConfig`, `DistributedLockAdapter`, `DistributedLockConfig`, `RedisLockClient`, `RedisLockConfig`, `BatchMigratorConfig`, `BatchMigrationResult`, `BatchTargetStatus`, `BatchTargetProgress`, `SurrealDbAdapterConfig`, `DryRunEntry`, `DryRunReport`, `RiskLevel`, `ExtendedMigrationStatus`, `MigrationSummary`, `MigrationHealthCheck`, `MigrationHistoryEntry`, `MigrationStatusEntry`.
- [x] Write `src/types/migration.types.md` (≥200 words).
### 2b. Error codes (`src/error-codes.ts`)
- [x] Import `CodeMetadata`, `ErrorCodes`, `SuaveplanError` from `@suaveplan/error`.
- [x] Define `MigrationErrorCode` union — eight codes: `MIGRATION_FAILED`, `MIGRATION_LOCK_TIMEOUT`, `MIGRATION_CHECKSUM_MISMATCH`, `MIGRATION_NOT_FOUND`, `MIGRATION_STORE_ERROR`, `MIGRATION_PLAN_INVALID`, `MIGRATION_ABORTED`, `MIGRATION_DRY_RUN`.
- [x] Define `MIGRATION_ERROR_METADATA` with `httpStatus`, `severity`, `retryable`, `userMessageKey`, `category`, `description` per code.
- [x] Export `registerMigrationErrorCodes()`. Idempotent; called eagerly at module scope.
- [x] Export `MigrationError` extending `SuaveplanError` carrying `MigrationErrorCode` discriminant.
### 2c. Zod schemas (`src/types/schemas.ts` + `src/types/schemas.md`)
- [x] Write `MigrationRunnerConfigSchema`, `RunOptionsSchema`, `BatchMigratorConfigSchema`, `DatabaseStoreConfigSchema`, `DistributedLockConfigSchema`, `RedisLockConfigSchema`.
- [x] Write `src/types/schemas.md` (≥200 words).
### 2d. Events (`src/migration-events.ts`)
- [x] Define `MigrationEventMap` interface: `migration:before`, `migration:after`, `migration:failed`, `migration:plan`.
- [x] Export `migrationEvents` — a lazily-initialised `EventBus<MigrationEventMap>` singleton from `@suaveplan/events`.
---
## 3. Constants (`src/constants/index.ts`)
- [x] Export `DEFAULT_MIGRATION_TABLE = 'migrations'`, `DEFAULT_LOCK_TTL_MS = 30_000`, `DEFAULT_BATCH_CONCURRENCY = 5`.
---
## 4. Planner (`src/planner/migration-planner/`)
- [x] Implement `sortMigrations(migrations)` — sort ascending by migration `id` (lexicographic).
- [x] Implement `checksumMigration(migration)` — serialise `migration.up.toString()` and produce a hex hash (use `@suaveplan/crypto-utils` if available; otherwise use Web Crypto `crypto.subtle.digest('SHA-256', ...)`).
- [x] Implement `planUp(all, applied)`:
- Sort `all` with `sortMigrations`.
- Filter to migrations whose `id` is not in `applied` (by id + checksum if `checksumValidation`).
- Return `{ toApply, toRollback: [] }`.
- [x] Implement `planDown(all, applied, steps?)`:
- Return `steps` most-recently-applied in reverse order for rollback.
- Default `steps = 1`.
- [x] Write `migration-planner.test.ts` (≤500 lines): happy path up/down, already applied skipped, checksum mismatch throws, empty sets, partial rollback with steps.
- [x] Write `migration-planner.md` (≥200 words).
---
## 5. Store adapters
### 5a. Memory store (`src/store/memory-store/`)
- [x] Implement `MemoryMigrationStore` implementing `MigrationStoreAdapter`.
- Internal `Map<string, MigrationRecord>`.
- Thread-safety: all operations are synchronous but wrapped in `Promise.resolve()` to honour the async interface.
- [x] Write `memory-store.test.ts`: record/getApplied/remove/clear.
- [x] Write `memory-store.md` (≥200 words).
### 5b. Database store (`src/store/database-store/`)
- [x] Implement `DatabaseMigrationStore(config: DatabaseStoreConfig)`:
- On construction, creates the ledger table if it does not exist (via `CREATE TABLE IF NOT EXISTS` on `config.adapter`).
- `record` inserts; `remove` deletes; `getApplied` queries all rows; `clear` truncates.
- Wraps errors as `MigrationError(MIGRATION_STORE_ERROR)`.
- [x] Write `database-store.test.ts` (integration: `@suaveplan/db-sqlite` SQLite adapter in-memory database).
- [x] Write `database-store.md` (≥200 words).
---
## 6. Lock adapters
### 6a. Memory lock (`src/lock/memory-lock/`)
- [x] Implement `MemoryMigrationLock` — in-process boolean flag; `acquire` with timeout using injectable clock.
- [x] Write `memory-lock.test.ts`: acquire, double-acquire times out, release.
- [x] Write `memory-lock.md` (≥200 words).
### 6b. Distributed lock (`src/lock/distributed-lock/`)
- [x] Implement `DistributedMigrationLock(config)` — delegates to `config.adapter.acquire/release`.
- [x] Implement `InMemoryDistributedLockAdapter` — a test double implementing `DistributedLockAdapter` using an in-process Map.
- [x] Write `distributed-lock.test.ts`.
- [x] Write `distributed-lock.md` (≥200 words).
### 6c. Redis lock (`src/lock/redis-lock/`)
- [x] Implement `RedisDistributedLock(config)` — uses `config.client.set(key, value, { NX: true, PX: ttl })` for acquire; `config.client.del(key)` for release.
- [x] Write `redis-lock.test.ts` — DI the `RedisLockClient` interface with a real in-memory stub (no vi.mock).
- [x] Write `redis-lock.md` (≥200 words).
---
## 7. Executor (`src/runner/migration-executor/`)
- [x] Implement `MigrationExecutor(options)`:
- `execute(plan, direction, runOptions?)`:
- Checks `runOptions?.signal` before each migration; throws `MigrationError(MIGRATION_ABORTED)` on abort.
- For each migration in plan: emits `migration:before`, calls `migration[direction](ctx)`, emits `migration:after` with duration, calls `store.record/remove`.
- On error: emits `migration:failed`, records to result.failed, continues to next.
- Dry-run: skips execution, records to `DryRunReporter`.
- OTel: per-migration span `migration.execute`, histogram `migration.duration_ms`.
- Logs via injected `LoggerLike`.
- [x] Write `migration-executor.test.ts` (unit: inject `MemoryMigrationStore` + `MemoryMigrationLock`).
- [x] Write `migration-executor.md` (≥200 words).
---
## 8. Runner (`src/runner/migration-runner/`)
- [x] Implement `MigrationRunner(config)`:
- `up(options?)`:
1. Acquire lock (if configured); throw `MigrationError(MIGRATION_LOCK_TIMEOUT)` on failure.
2. Validate checksums if `checksumValidation: true`.
3. Call `planUp`; emit `migration:plan`.
4. Delegate to `MigrationExecutor.execute(plan, 'up', options)`.
5. Release lock in `finally`.
6. OTel: span `migration.run` with attribute `migration.direction = 'up'`.
- `down(options?)`: analogous with `planDown`.
- `status()` — compares `config.migrations` against `store.getApplied()`, returns `MigrationStatus[]`.
- `validate()` — checks checksums for all applied migrations, returns `ValidationResult`.
- [x] Write `migration-runner.test.ts` (≤500 lines): up/down happy path, abort mid-run, checksum mismatch, lock contention, dry-run.
- [x] Write `migration-runner.md` (≥200 words).
---
## 9. Dry-run reporter (`src/runner/dry-run-reporter/`)
- [x] Implement `DryRunReporter`: `record(entry)` appends; `report()` computes `totalRisk` (highest entry risk), generates plain-text `summary`.
- [x] Write `dry-run-reporter.test.ts`.
- [x] Write `dry-run-reporter.md` (≥200 words).
---
## 10. Status dashboard (`src/status/migration-status/`)
- [x] Implement `MigrationStatusDashboard(config)`:
- `getAll()` — joins registered migrations with applied records.
- `getSummary()` — counts pending/applied/failed.
- `healthCheck()` — returns healthy if zero failed, at-risk if any pending for >24h.
- `getHistory(id)` — returns store records for a specific migration id.
- [x] Write `migration-status.test.ts`.
- [x] Write `migration-status.md` (≥200 words).
---
## 11. Batch migrator (`src/batch/batch-migrator/`)
- [x] Implement `BatchMigrator(config)` — runs `config.targets` runners concurrently up to `config.concurrency` (default 5) using a semaphore; collects results.
- [x] Write `batch-migrator.test.ts`: two targets, one fails; concurrency respected.
- [x] Write `batch-migrator.md` (≥200 words).
---
## 12. SurrealDB adapter (`src/adapters/surrealdb-adapter/`)
- [x] Implement `SurrealDbAdapter(config)` wrapping a SurrealDB `Surreal` instance (typed as `unknown` to avoid hard dep on `surrealdb.js` — callers cast):
- `query<T>(sql, params?)` — calls `db.query(sql, params)`, flattens the `QueryResult<T>[][]` to `T[]`.
- Note: avoid `db.create()` — use `INSERT INTO` query (see memory `feedback_surrealdb_create_content_drop`).
- [x] Write `surrealdb-adapter.test.ts` — DI a mock `{ query }` conforming to `SurrealDbAdapterConfig`.
- [x] Write `surrealdb-adapter.md` (≥200 words).
---
## 13. Property-based tests (`tests/property.test.ts`)
Use `fast-check`. All timeouts: `{ timeout: 30000 }`.
- [x] **`planUp` idempotency**: apply all migrations, then call `planUp` again — `toApply` is empty.
- [x] **`sortMigrations` stability**: for any permutation of N migrations, `sortMigrations` returns the same order.
- [x] **`checksumMigration` determinism**: for the same migration object, checksum called twice returns the same string.
- [x] **Partial batch failure**: BatchMigrator with K/N failing targets yields exactly K entries in `result.failed`.
---
## 14. Benchmarks (`src/__benchmarks__/migration.bench.ts`)
Use `mitata`.
- [x] `planUp_100`: plan for 100 pending migrations (pure, no IO).
- [x] `runner_up_10_memory`: run 10 no-op migrations against `MemoryMigrationStore`.
- [x] `checksumMigration_1k`: 1000 checksum computations.
- [x] Add `bench` script and placeholder benchmark table to `README.md`.
---
## 15. Docs
- [x] `src/types/migration.types.md`
- [x] `src/types/schemas.md`
- [x] `src/planner/migration-planner/migration-planner.md`
- [x] `src/runner/migration-runner/migration-runner.md`
- [x] `src/runner/migration-executor/migration-executor.md`
- [x] `src/runner/dry-run-reporter/dry-run-reporter.md`
- [x] `src/store/memory-store/memory-store.md`
- [x] `src/store/database-store/database-store.md`
- [x] `src/lock/memory-lock/memory-lock.md`
- [x] `src/lock/distributed-lock/distributed-lock.md`
- [x] `src/lock/redis-lock/redis-lock.md`
- [x] `src/status/migration-status/migration-status.md`
- [x] `src/batch/batch-migrator/batch-migrator.md`
- [x] `src/adapters/surrealdb-adapter/surrealdb-adapter.md`
- [x] Root `universal/database/migration/README.md`.
---
## 16. Verify and publish
- [x] Run `bunx turbo lint --filter=@suaveplan/migration` — zero errors.
- [x] Run `bunx turbo typecheck --filter=@suaveplan/migration` — zero errors.
- [x] Run `bunx turbo test --filter=@suaveplan/migration` — all tests pass.
- [x] Run `bunx turbo test:coverage --filter=@suaveplan/migration` — 100% on all touched modules.
- [x] Run `bun run docs:check --filter=@suaveplan/migration` — zero errors.
- [x] Confirm no `TODO`, `FIXME`, `@deprecated`, untyped `any`, `ts-ignore`, or `.skip` in `src/` or `tests/`.
- [x] Confirm `"sideEffects": false` in `package.json`.
- [x] Run `bun scripts/publish.ts --package @suaveplan/migration`.
- [x] Update `CLAUDE.md` package listing to include `@suaveplan/migration`.
- [x] Archive: move `openspec/changes/enterprise-migration/` to `openspec/archive/2026-04-24-enterprise-migration/` and fold spec delta into `openspec/specs/database/spec.md`.