Goal: implement hardtry incrementally with behavior locked by
.context/design.md, validating each method before moving forward.
- Phase 0 - Foundation
- Phase 1 -
run(sync) - Phase 2 -
run(async) - Phase 3 -
retryOptions+retry - Phase 4 -
timeout(v1 total scope only) - Phase 5 -
.signal(...)cancellation - Phase 6 -
wrapmiddleware (full-run scope) - Phase 7 - Builder API + root namespace exports
- Phase 8 -
disposewithAsyncDisposableStack - Phase 9 -
gen - Phase 10 -
allandallSettled - Phase 11 -
flow+$exit - Phase 12 - Hardening + release readiness
- Build the smallest working slice first, then add one capability at a time.
- Add runtime tests and type tests at each step.
- Keep API surface stable (
try$,run,retryOptions, etc.). - Avoid behavior drift once a phase is marked complete.
- Create base files and exports
src/lib/types.tssrc/lib/errors.tssrc/lib/run-sync.tssrc/lib/run.tssrc/lib/builder.ts- wire minimal
src/index.ts
- Define core types
MaybePromise<T>TryCtxRunOptionsandRunTryFn- shared config types used by builder
- Define error classes and codes
CancellationError(EXEC_CANCELLED)TimeoutError(EXEC_TIMEOUT)RetryExhaustedError(EXEC_RETRY_EXHAUSTED)UnhandledException(EXEC_UNHANDLED_EXCEPTION)Panic(EXEC_PANIC)- all include
cause
Exit criteria:
- project compiles with basic stubs in place.
- Implement
run(tryFn)sync path- if
tryFnreturns sync value, return sync value - on throw without catch, return
UnhandledException
- if
- Implement
run({ try, catch })sync pathrun({ ... })requires bothtryandcatch- if
trythrows, callcatchand return mapped error - if
catchthrows, throwPanic
- Add tests
- success sync
- throw to
UnhandledException - throw plus catch mapping
- catch throws
Panic
Exit criteria:
- sync contract is stable and covered.
- Add async-aware execution
- if
tryreturns a Promise, return a Promise - if
catchreturns a Promise, return a Promise - preserve sync return when both are sync
- object form still requires both
tryandcatch
- if
- Keep same semantics as sync
- no behavior drift between sync and async
Panicremains highest precedence for catch-throw path and is thrown
- Add tests
- async success
- async reject to mapped catch
- async catch reject throws
Panic - mixed sync and async combinations
Exit criteria:
- overload behavior is deterministic and typed.
- Implement
retryOptions(policy)helper- normalize
{ limit, delayMs, backoff, shouldRetry?, maxDelayMs?, jitter? }
- normalize
- Implement
.retry(...)on builderlimitincludes first attempt- explicit backoff formulas
- support
shouldRetry(error, ctx)
- Retry rules
- do not retry control errors (
Panic,CancellationError,TimeoutError)
- do not retry control errors (
- Add tests
- attempt counting
- linear and exponential formula correctness
- predicate-gated retry
Exit criteria:
- retry behavior matches docs.
- Implement
.timeout(ms | { ms, scope: "total" }) - Total scope includes
- all attempts
- backoff waits
- catch execution
- Timeout maps to
TimeoutErrorwith cause - Add tests
- timeout during try
- timeout during backoff
- timeout during catch
Exit criteria:
- total-timeout semantics are locked.
Future follow-up (post-v1 experiment):
- evaluate
scope: "attempt"timeout semantics and interaction with retry/backoff.
- Implement external signal integration
- compose external
AbortSignalinto internal execution - map to
CancellationErrorwith cause
- compose external
- Add precedence handling
Panic > CancellationError > TimeoutError > catch-mapped > UnhandledException
- Add tests
- pre-aborted signal
- mid-flight abort
- abort + timeout race
Exit criteria:
- cancellation semantics are stable and typed.
- Implement
.wrap(fn)additive chain - Wrap applies around full run execution (not per attempt)
- Ensure context includes retry metadata
- Add tests
- wrap order
- runs once per
run - interaction with retry, timeout, and signal
Exit criteria:
- wrap scope and ordering are fixed.
- Finalize immutable
TryBuilder - Finalize
src/index.tswith onerootbuilder instance- bound exports:
retry,timeout,signal,wrap,run,all,allSettled,flow - standalone exports:
gen,dispose,retryOptions
- bound exports:
- Add API-level tests for namespace behavior
Exit criteria:
- public API is stable and consistent with docs.
- Implement
try$.dispose() - Support
.use(resource)and.defer(fn) - Reverse-order cleanup
- Continue cleanups if one fails and aggregate cleanup failures
- Add tests including early-exit and abort scenarios
Exit criteria:
- deterministic cleanup guarantees are enforced.
- Implement generator helper for result unwrapping
- Preserve union typing for accumulated error types
- Add runtime and type tests
Exit criteria:
genergonomics and typing are stable.
- Implement
all(tasks)with named task map - Implement
allSettled(tasks)with native-like settled typing - Provide
this.$result,this.$signal, andthis.$disposer - Add tests
- dependency access via
$result - mixed outcomes
- cancellation mid-flight
- dependency access via
Exit criteria:
- task APIs behave consistently and inference is acceptable.
- Implement
flow(tasks)orchestration - Implement
$exit(value)early-return mechanism - Ensure early exit still triggers disposer cleanup
- Add tests
- early exit with pending tasks
- cleanup on exit
- typed
FlowExitextraction
Exit criteria:
- flow control and cleanup semantics are deterministic.
- Add race-condition matrix tests
- retry + timeout + abort races
- catch throwing, async catch, timeout during catch
- backoff interrupted by abort
- Add public type tests for all APIs
- Final docs consistency pass (
design.md+api-proposal.md) - Run quality gates
bun run formatbun run checkbun run typecheckbun run test
Exit criteria:
- v1 implementation complete and doc-aligned.