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

5.5 KiB

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

  • Add src/timers-like.ts exporting TimersLike per proposal.
  • Add sibling src/timers-like.md (≥200 words, Purpose / Features / Usage Basic / Usage Advanced / API Reference / Implementation Notes).
  • Re-export TimersLike from src/index.ts (explicit named, alphabetic placement).
  • bunx turbo lint typecheck --filter=@suaveplan/types clean.

2. Alias SchedulerTimers to TimersLike in @suaveplan/cron

  • 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;
  • Verify no internal cron code structurally needs anything beyond the two methods (the alias is transparent — no call-site changes expected).
  • bunx turbo lint typecheck test --filter=@suaveplan/cron — all green, 100% coverage preserved.

3. Thread TimersLike through @suaveplan/retry

  • 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).
  • 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()).
  • Update retry.md to mention the new option in API Reference + add an "Advanced: deterministic tests with FakeClock" example.
  • 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.
  • 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

  • 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.
  • 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.
  • bunx turbo lint typecheck test:coverage --filter=@suaveplan/job-scheduler — 100% coverage preserved. No skipped tests, no lowered thresholds.

5. Cross-package verification

  • bunx turbo lint typecheck test --filter=@suaveplan/types --filter=@suaveplan/retry --filter=@suaveplan/cron --filter=@suaveplan/job-scheduler — every gate green.
  • Banned-pattern scan over the four touched packages:
    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"
    
  • Verify no direct runner imports introduced (from "vitest" / from "bun:test") in any modified test file.
  • 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/<today>-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.