Files
2026-05-27 23:29:53 +01:00

5.7 KiB

Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/control-flow/retry.

Tasks: enterprise-retry

1. Scaffold

  • 1.1 Create packages/universal/retry/ with package.json, tsconfig.json, tsconfig.lib.json, vite.config.ts, vitest.config.ts, README.md skeleton
  • 1.2 Register coverage thresholds at 100/100/100/100 in vitest.config.ts; provider = istanbul
  • 1.3 Add cross-cutting guard tests in tests/: dep-graph, no-disallowed, tree-shake
  • 1.4 Add package to root CLAUDE.md, README.md, and STRUCTURE.yml

2. Types + errors

  • 2.1 Implement src/types/retry.types.ts — RetryConfig, RetryResult, RetryStrategy, DelayFunction, JitterMode, ConstantBackoffOptions, LinearBackoffOptions, ExponentialBackoffOptions
  • 2.2 Implement src/errors/retry-error.ts — RetryError base extends SuaveplanError; RetryExhaustedError, AttemptTimeoutError extend RetryError; defineCodes for RETRY.EXHAUSTED, RETRY.ABORTED, RETRY.TIMEOUT
  • 2.3 Implement src/circuit-breaker/circuit-breaker.types.ts — CircuitBreakerConfig, CircuitBreakerEventMap, CircuitState; CircuitOpenError extends RetryError with RETRY.CIRCUIT_OPEN code
  • 2.4 Zod schemas: RetryConfigSchema, ConstantBackoffOptionsSchema, LinearBackoffOptionsSchema, ExponentialBackoffOptionsSchema, CircuitBreakerConfigSchema
  • 2.5 Tests: error construction, code narrowing, .attempts / .lastError / .attemptNumber / .timeoutMs fields
  • 2.6 Implement src/error-codes.ts and auto-register via side-effect import in src/index.ts

3. Backoff strategies

  • 3.1 src/strategies/constant/constant.ts — createConstantBackoff(opts) → RetryStrategy; ConstantBackoffOptions
  • 3.2 src/strategies/linear/linear.ts — createLinearBackoff(opts) → RetryStrategy; LinearBackoffOptions
  • 3.3 src/strategies/exponential/exponential.ts — createExponentialBackoff(opts) → RetryStrategy; applyJitter(delay, mode, rng?), clampDelay(delay, min, max); ExponentialBackoffOptions
  • 3.4 All strategies accept injectable rng?: Rng for deterministic jitter
  • 3.5 Unit tests: delay values at each attempt, clamping, jitter modes (full/equal/decorrelated/none)
  • 3.6 Property-based tests: exponential delay(n+1) >= delay(n) with no jitter; jitter in [0, d]; clamp always in [min, max]
  • 3.7 Co-located .md for each strategy

4. Core retry

  • 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>
  • 4.2 retry uses injected clock?: Clock for sleep duration; rng?: Rng from config forwarded to strategy
  • 4.3 retry honours AbortSignal between attempts (checks before each delay and after each call)
  • 4.4 Unit tests: all config options, abort between attempts, timeout, exhaustion, shouldRetry predicate
  • 4.5 Property-based tests: withRetry never exceeds maxAttempts calls to fn
  • 4.6 Co-located src/retry/retry.md

5. Circuit breaker

  • 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)
  • 5.2 Unit tests: failure threshold transitions, half-open probing, success promotion, manual reset, signal passing, clock injection
  • 5.3 Property-based tests: after failureThreshold consecutive failures → state is 'open'
  • 5.4 Co-located src/circuit-breaker/circuit-breaker.md

6. OTel instrumentation

  • 6.1 Counter retry.attempt.total by outcome label (success / failure / exhausted / aborted)
  • 6.2 Histogram retry.attempt.delay_ms by strategy label
  • 6.3 Counter circuit_breaker.state_change.total by from/to label
  • 6.4 Histogram circuit_breaker.call.duration_ms
  • 6.5 No-op without exporter (tested)

7. Benchmarks

  • 7.1 src/__benchmarks__/sleep.bench.ts — sleep(0) call overhead baseline (mitata)
  • 7.2 src/__benchmarks__/retry.bench.ts — withRetry with always-succeeding fn (0-retry overhead)
  • 7.3 src/__benchmarks__/backoff.bench.ts — createExponentialBackoff delay computation 1 000 iterations
  • 7.4 src/__benchmarks__/circuit-breaker.bench.ts — CircuitBreaker.call closed-state hot path 10 000 iterations
  • 7.5 Publish baseline numbers in README

8. Docs

  • 8.1 Co-located .md for every implementation file (≥200 words, examples, API reference)
  • 8.2 Rewrite README.md with public API, circuit breaker state diagram, strategy comparison table, consumer guide
  • 8.3 bun run docs:check --filter=@suaveplan/retry ≥ 90%

9. Verify and publish

  • 9.1 bunx turbo lint typecheck test --filter=@suaveplan/retry green
  • 9.2 bunx turbo test:coverage --filter=@suaveplan/retry at 100/100/100/100
  • 9.3 bunx turbo build --filter=@suaveplan/retry green
  • 9.4 bun scripts/publish.ts --package @suaveplan/retry to Verdaccio at 0.0.0
  • 9.5 Archive OpenSpec change to openspec/archive/2026-04-18-enterprise-retry/