89 lines
5.7 KiB
Markdown
89 lines
5.7 KiB
Markdown
> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/control-flow/retry.
|
|
|
|
# Tasks: enterprise-retry
|
|
|
|
## 1. Scaffold
|
|
|
|
- [x] 1.1 Create `packages/universal/retry/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton
|
|
- [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul
|
|
- [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph, no-disallowed, tree-shake
|
|
- [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml`
|
|
|
|
## 2. Types + errors
|
|
|
|
- [x] 2.1 Implement `src/types/retry.types.ts` — `RetryConfig`, `RetryResult`, `RetryStrategy`, `DelayFunction`, `JitterMode`, `ConstantBackoffOptions`, `LinearBackoffOptions`, `ExponentialBackoffOptions`
|
|
- [x] 2.2 Implement `src/errors/retry-error.ts` — `RetryError` base extends `SuaveplanError`; `RetryExhaustedError`, `AttemptTimeoutError` extend `RetryError`; `defineCodes` for `RETRY.EXHAUSTED`, `RETRY.ABORTED`, `RETRY.TIMEOUT`
|
|
- [x] 2.3 Implement `src/circuit-breaker/circuit-breaker.types.ts` — `CircuitBreakerConfig`, `CircuitBreakerEventMap`, `CircuitState`; `CircuitOpenError` extends `RetryError` with `RETRY.CIRCUIT_OPEN` code
|
|
- [x] 2.4 Zod schemas: `RetryConfigSchema`, `ConstantBackoffOptionsSchema`, `LinearBackoffOptionsSchema`, `ExponentialBackoffOptionsSchema`, `CircuitBreakerConfigSchema`
|
|
- [x] 2.5 Tests: error construction, code narrowing, `.attempts` / `.lastError` / `.attemptNumber` / `.timeoutMs` fields
|
|
- [x] 2.6 Implement `src/error-codes.ts` and auto-register via side-effect import in `src/index.ts`
|
|
|
|
## 3. Backoff strategies
|
|
|
|
- [x] 3.1 `src/strategies/constant/constant.ts` — `createConstantBackoff(opts)` → `RetryStrategy`; `ConstantBackoffOptions`
|
|
- [x] 3.2 `src/strategies/linear/linear.ts` — `createLinearBackoff(opts)` → `RetryStrategy`; `LinearBackoffOptions`
|
|
- [x] 3.3 `src/strategies/exponential/exponential.ts` — `createExponentialBackoff(opts)` → `RetryStrategy`; `applyJitter(delay, mode, rng?)`, `clampDelay(delay, min, max)`; `ExponentialBackoffOptions`
|
|
- [x] 3.4 All strategies accept injectable `rng?: Rng` for deterministic jitter
|
|
- [x] 3.5 Unit tests: delay values at each attempt, clamping, jitter modes (full/equal/decorrelated/none)
|
|
- [x] 3.6 Property-based tests: exponential `delay(n+1) >= delay(n)` with no jitter; jitter in `[0, d]`; clamp always in `[min, max]`
|
|
- [x] 3.7 Co-located `.md` for each strategy
|
|
|
|
## 4. Core retry
|
|
|
|
- [x] 4.1 Implement `src/retry/retry.ts`:
|
|
- `sleep(ms, signal?)` — cancellable; resolves early on abort
|
|
- `retry(fn, config)` → `Promise<RetryResult<T>>`
|
|
- `withRetry(fn, config)` → `Promise<T>` — throws `RetryExhaustedError` on exhaustion, `RetryError` on abort
|
|
- `withTimeout(fn, ms, signal?)` → `Promise<T>` — throws `AttemptTimeoutError`
|
|
- `createDelayFn(strategy)` → `DelayFunction`
|
|
- `withCircuitBreaker(fn, breaker)` → `Promise<T>`
|
|
- [x] 4.2 `retry` uses injected `clock?: Clock` for sleep duration; `rng?: Rng` from config forwarded to strategy
|
|
- [x] 4.3 `retry` honours `AbortSignal` between attempts (checks before each delay and after each call)
|
|
- [x] 4.4 Unit tests: all config options, abort between attempts, timeout, exhaustion, `shouldRetry` predicate
|
|
- [x] 4.5 Property-based tests: `withRetry` never exceeds `maxAttempts` calls to fn
|
|
- [x] 4.6 Co-located `src/retry/retry.md`
|
|
|
|
## 5. Circuit breaker
|
|
|
|
- [x] 5.1 Implement `src/circuit-breaker/circuit-breaker.ts` — `CircuitBreaker` class
|
|
- State machine: `closed → open → half-open → closed` (or back to open)
|
|
- `.call(fn, signal?)` — throws `CircuitOpenError` when open
|
|
- `.reset()` — force back to closed
|
|
- `.state` getter — `CircuitState`
|
|
- Event emitter via `@suaveplan/events`: `stateChange` event with `CircuitBreakerEventMap`
|
|
- `halfOpenProbes` attempts before promoting to closed
|
|
- `timeout` window after which open resets to half-open (uses `clock?: Clock`)
|
|
- [x] 5.2 Unit tests: failure threshold transitions, half-open probing, success promotion, manual reset, signal passing, clock injection
|
|
- [x] 5.3 Property-based tests: after `failureThreshold` consecutive failures → state is `'open'`
|
|
- [x] 5.4 Co-located `src/circuit-breaker/circuit-breaker.md`
|
|
|
|
## 6. OTel instrumentation
|
|
|
|
- [x] 6.1 Counter `retry.attempt.total` by outcome label (success / failure / exhausted / aborted)
|
|
- [x] 6.2 Histogram `retry.attempt.delay_ms` by strategy label
|
|
- [x] 6.3 Counter `circuit_breaker.state_change.total` by from/to label
|
|
- [x] 6.4 Histogram `circuit_breaker.call.duration_ms`
|
|
- [x] 6.5 No-op without exporter (tested)
|
|
|
|
## 7. Benchmarks
|
|
|
|
- [x] 7.1 `src/__benchmarks__/sleep.bench.ts` — `sleep(0)` call overhead baseline (mitata)
|
|
- [x] 7.2 `src/__benchmarks__/retry.bench.ts` — `withRetry` with always-succeeding fn (0-retry overhead)
|
|
- [x] 7.3 `src/__benchmarks__/backoff.bench.ts` — `createExponentialBackoff` delay computation 1 000 iterations
|
|
- [x] 7.4 `src/__benchmarks__/circuit-breaker.bench.ts` — `CircuitBreaker.call` closed-state hot path 10 000 iterations
|
|
- [x] 7.5 Publish baseline numbers in README
|
|
|
|
## 8. Docs
|
|
|
|
- [x] 8.1 Co-located `.md` for every implementation file (≥200 words, examples, API reference)
|
|
- [x] 8.2 Rewrite `README.md` with public API, circuit breaker state diagram, strategy comparison table, consumer guide
|
|
- [x] 8.3 `bun run docs:check --filter=@suaveplan/retry` ≥ 90%
|
|
|
|
## 9. Verify and publish
|
|
|
|
- [x] 9.1 `bunx turbo lint typecheck test --filter=@suaveplan/retry` green
|
|
- [x] 9.2 `bunx turbo test:coverage --filter=@suaveplan/retry` at 100/100/100/100
|
|
- [x] 9.3 `bunx turbo build --filter=@suaveplan/retry` green
|
|
- [x] 9.4 `bun scripts/publish.ts --package @suaveplan/retry` to Verdaccio at 0.0.0
|
|
- [x] 9.5 Archive OpenSpec change to `openspec/archive/2026-04-18-enterprise-retry/`
|