# Tasks: retry-injectable-timers **Change ID**: retry-injectable-timers **Packages touched**: `@suaveplan/types`, `@suaveplan/retry`, `@suaveplan/cron`, `@suaveplan/job-scheduler` --- ## 1. Land `TimersLike` in `@suaveplan/types` - [x] Add `src/timers-like.ts` exporting `TimersLike` per proposal. - [x] Add sibling `src/timers-like.md` (≥200 words, Purpose / Features / Usage Basic / Usage Advanced / API Reference / Implementation Notes). - [x] Re-export `TimersLike` from `src/index.ts` (explicit named, alphabetic placement). - [x] `bunx turbo lint typecheck --filter=@suaveplan/types` clean. ## 2. Alias `SchedulerTimers` to `TimersLike` in `@suaveplan/cron` - [x] In `packages/primitives/cron/src/scheduler/cron-scheduler.types.ts`: - Replace the local `SchedulerTimers` interface declaration with: `import type { TimersLike } from "@suaveplan/types"; export type SchedulerTimers = TimersLike;` - [x] Verify no internal cron code structurally needs anything beyond the two methods (the alias is transparent — no call-site changes expected). - [x] `bunx turbo lint typecheck test --filter=@suaveplan/cron` — all green, 100% coverage preserved. ## 3. Thread `TimersLike` through `@suaveplan/retry` - [x] In `packages/control-flow/retry/src/types/retry.types.ts`: add `timers?: TimersLike` to `RetryConfig`. Update the Zod schema in `retry.schemas.ts` if it strict-checks unknown keys (must permit `timers`). - [x] In `packages/control-flow/retry/src/retry/retry.ts`: - `sleep(ms, signal, timers?)`: pull `setTimer` / `clearTimer` from `timers` when supplied; fall back to `globalThis.setTimeout` / `clearTimeout`. - `withTimeout(fn, ms, attemptNumber, signal, timers?)`: same pattern. - `retry()`: pass `config.timers` through both `sleep()` calls. - `withRetry()`: no change beyond config passthrough (already delegates to `retry()`). - [x] Update `retry.md` to mention the new option in API Reference + add an "Advanced: deterministic tests with FakeClock" example. - [x] Add `retry/retry-timers.test.ts` (or extend `retry.test.ts` if still under 500 lines): FakeClock-driven exponential backoff exercising injected timers end-to-end — assert the run completes in zero real wall-clock time after enough `clock.advance(ms)` calls. - [x] `bunx turbo lint typecheck test:coverage --filter=@suaveplan/retry` — 100% coverage on `retry.ts` and `retry.types.ts` preserved. ## 4. Replace inline retry loop in `@suaveplan/job-scheduler` - [x] In `packages/control-flow/job-scheduler/src/execution/job-execution.ts`: - Import `retry` from `@suaveplan/retry` and a `RetryStrategy` builder. - Build a `delay: RetryStrategy` from `JobOptions.retryDelayMs` (constant strategy via `@suaveplan/retry`'s `constantBackoff`). - Compose `retry({ maxAttempts: retries + 1, delay, signal, timers, shouldRetry: () => true })` around the handler invocation. - Translate `RetryExhaustedError` → `JOB_SCHEDULER.EXECUTION_FAILED` (preserve `cause`) and `AttemptTimeoutError` → `JOB_SCHEDULER.TIMEOUT`. - Preserve `job:start` event emission per attempt; emit `job:fail` on final exhaustion; emit `job:complete` on success. - Delete the dead inline-loop helpers in `job-execution.ts` once their callers are gone. - [x] If `executeJob`'s test file references the inline retry helpers, port the assertions to the new behavior. Add a regression test proving FakeClock + `timers` injection still produces deterministic retry timing. - [x] `bunx turbo lint typecheck test:coverage --filter=@suaveplan/job-scheduler` — 100% coverage preserved. No skipped tests, no lowered thresholds. ## 5. Cross-package verification - [x] `bunx turbo lint typecheck test --filter=@suaveplan/types --filter=@suaveplan/retry --filter=@suaveplan/cron --filter=@suaveplan/job-scheduler` — every gate green. - [x] Banned-pattern scan over the four touched packages: ```bash grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only\|console\.log\|biome-ignore" \ packages/core/types/src/timers-like.ts \ packages/control-flow/retry/src/ \ packages/primitives/cron/src/scheduler/ \ packages/control-flow/job-scheduler/src/ --include="*.ts" ``` - [x] Verify no direct runner imports introduced (`from "vitest"` / `from "bun:test"`) in any modified test file. - [x] Verify no `node:` imports introduced in any modified universal-tier file. ## 6. Publish + archive - [ ] `bunx turbo build --filter=@suaveplan/types --filter=@suaveplan/retry --filter=@suaveplan/cron --filter=@suaveplan/job-scheduler` — confirm `dist/` outputs. - [ ] `bun scripts/publish.ts --package @suaveplan/types` (publish-or-skip; bumps allowed if Verdaccio rejects the static `0.1.0`). - [ ] `bun scripts/publish.ts --package @suaveplan/retry`. - [ ] `bun scripts/publish.ts --package @suaveplan/cron`. - [ ] `bun scripts/publish.ts --package @suaveplan/job-scheduler`. - [ ] Update `CLAUDE.md` `retry` entry to mention `timers?: TimersLike`; update `job-scheduler` entry to drop the FakeClock-determinism caveat and note it now delegates to `@suaveplan/retry`. - [ ] Update `MEMORY.md` if any in-flight memory references the inline retry-loop justification. - [ ] `bun scripts/folder-reorg/generate-manifest.ts` — no manifest changes expected (no new packages) but run to confirm. - [ ] Archive proposal + tasks + spec delta to `openspec/archive/-retry-injectable-timers/`. Fold the `## ADDED Requirements` from `specs/types/spec.md` into `openspec/specs/types/spec.md` (create the file if it does not yet exist); fold the `## MODIFIED Requirements` from `specs/retry/spec.md` into `openspec/specs/retry/spec.md`.