feat(fp): add Try module (try_, tryPromise, attempt, withReporting, classifyError) - #439
Merged
Merged
Conversation
…lassifyError)
Delivers the Try<T, E> module that the README and
docs/internal/product/features/try.md have been advertising since
v1.0. Wraps synchronous and asynchronous throwing functions into a
typed value, eliminating silent try/catch blocks at call sites.
- try_<T>(thunk) and try_<T, E>({ onSuccess, onError }) — sync wrap.
- tryPromise<T>(thunk) and tryPromise<T, E>({ onSuccess, onError }) —
async wrap; onError may itself be async.
- attempt(config) — returns { execute(), clientSafe() } with optional
single-attempt retry and error normalisation.
- withReporting(onSuccess, name, reporter, metadata?) — forwards
caught errors to a caller-supplied ErrorReporter.
- classifyError(e, rules) — returns 'retryable' | 'non-retryable'
based on instanceof matching.
- toResultTry() — converts a Try<T, E> into a Result<T, E> so existing
pipe(...) pipelines compose naturally.
Internal classes SuccessImpl / FailureImpl follow rule 0014 and live
in src/try/internal/. Public types are type aliases pointing at them
(rule 0012). The discriminated union uses _tag: 'Success' | 'Failure'
to mirror the existing Ok/Err and Some/None naming.
Coverage 100% on lines / branches / functions / statements across the
8 covered files (types.ts and index.ts are excluded by vitest config).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Coverage report
Per-file thresholds: 100% on statements / branches / functions / lines (ADR 0002). Files with no branches render |
`attempt()` is now a thin factory returning `new AttemptImpl(config)`. The class lives in `src/try/internal/attempt-impl.ts` and is not exported (rule 0014). Construction stays lazy: `attempt()` does not invoke `onSuccess`; the wrapped operation runs only when `execute()` or `clientSafe()` is called. The previous closure-based implementation is preserved verbatim inside the class. The public surface (`Attempt<T>`, `execute`, `clientSafe`) is unchanged. Coverage 100% on lines / branches / functions / statements. Tests for the impl surface now live in `tests/try/attempt-impl.test.ts`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Try module shipped in #439 duplicated Result one-for-one: SuccessImpl mirrored OkImpl, FailureImpl mirrored ErrImpl, and fifteen `*Try` pipeables (`mapTry`, `flatMapTry`, `matchTry`, ...) shadowed the Result combinators under different names. The `toResultTry` bridge existed only to cross between the two isomorphic types. This commit collapses the reasoning on `Result<T, E>`. There is now one machine of states, one set of pipeables, one vocabulary. Public surface changes: - NEW `Result.fromThrowable(thunk | { onSuccess, onError })` — sync wrap. Returns `Result<T, E>`. - NEW `Result.fromAsyncThrowable(thunk | { onSuccess, onError })` — async wrap. Returns `Promise<Result<T, E>>`. - NEW `UnhandledException`, `AttemptConfig`, `Attempt`, `NormalizedError`, `RetryConfig`, `DelayStrategy`, `ErrorReporter`, `ErrorContext`, `ReportableError`, `ErrorClassification`, `ClassificationRule`, `ErrorConstructor` — Result-side types. - MOVED `attempt`, `withReporting`, `classifyError` from `src/try/` to `src/result/`. Internal `AttemptImpl` class moved to `src/result/internal/`. - KEPT as aliases at the top level: `try_`, `tryPromise` (both resolve to `fromThrowable` / `fromAsyncThrowable`). - REMOVED: `Success`, `Failure`, `Try`, the `success` / `failure` factories, the `*Try` pipeables, the `_tag: 'Success'` / `_tag: 'Failure'` discriminants. Files: - src/result/{wrapping,attempt,reporting,classify}.ts (new) - src/result/types.ts, constants.ts, index.ts (extended) - src/result/internal/attempt-impl.ts (new, moved from src/try/) - src/try/index.ts (now a one-file facade re-exporting from result/) - src/index.ts (root barrel updated) - src/try/{types,constants,functions,attempt,reporting,classify, internal/success-impl, internal/failure-impl, internal/attempt-impl}.ts (deleted) - tests/result/{wrapping,attempt-impl,reporting,classify,index}.test.ts (new / moved) - tests/try/* (deleted) - docs/internal/product/features/try.md (reframed as a Result adapter note) - docs/internal/product/features/result.md (new "Wrapping Throwing Functions" section) Coverage stays at 100% on lines / branches / functions / statements. The 24 test files now hold 316 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #439 shipped a Try module and then collapsed it onto Result through aliasing. This commit goes the rest of the way and removes the Try facade entirely. What changed: - DELETED src/try/ (the directory, including the facade index.ts). - DELETED docs/internal/product/features/try.md. - The top-level aliases `try_` and `tryPromise` are gone. Consumers wrap throwing code through `Result.fromThrowable` / `Result.fromAsyncThrowable` (which already return `Result<T, E>`). - The root barrel no longer imports from `./try/`. All wrapping helpers come from `./result/`. - The product README no longer lists Try as a primitive. - wrapping.test.ts drops its alias smoke checks; the canonical fromThrowable / fromAsyncThrowable tests stay. - The unify-on-result changeset is rewritten to reflect that the Try facade is fully removed. Coverage stays at 100% on lines / branches / functions / statements across the 24 test files (314 tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Delivers the
Try<T, E>module that the README anddocs/internal/product/features/try.mdhave been advertising since v1.0. Wraps synchronous and asynchronous throwing functions into a typed value, eliminating silenttry/catchblocks at call sites.What's shipped
try_<T>(thunk)andtry_<T, E>({ onSuccess, onError })— sync wrap, with or without an explicit error mapper.tryPromise<T>(thunk)andtryPromise<T, E>({ onSuccess, onError })— async wrap.onErrormay itself be async.attempt(config)— returns{ execute(), clientSafe() }with optional single-attempt retry and error normalisation.withReporting(onSuccess, name, reporter, metadata?)— forwards caught errors to a caller-suppliedErrorReporterand returns aResult<T, ReportableError>.classifyError(e, rules)— returns'retryable' | 'non-retryable'based oninstanceofmatching against a rule list.toResultTry()— converts aTry<T, E>into aResult<T, E>so existingpipe(... , map, getOrElse)pipelines compose naturally.Plus the standard pipeable surface:
mapTry,flatMapTry,mapErrorTry,tapTry,tapAsyncTry,flatMapAsyncTry,matchTry,foldTry,getOrElseTry,getOrThrowTry,getOrNullTry,getOrUndefinedTry,toResultTry,isSuccess,isFailure.Design choices
SuccessImpl/FailureImplfollow rule 0014 and live insrc/try/internal/; public types aretypealiases pointing at them (rule 0012). The factory functionssuccess()/failure()are the only public construction entry points._tag: 'Success' | 'Failure'to mirror the existingOk/ErrandSome/Nonenaming.{ onSuccess, onError }object form is the canonical shape for the wrapping helpers.try_andtryPromisealso keep the thunk-only overload that captures the cause in anUnhandledException.pipecomposition across modules works:pipe(tryResult, toResultTry(), map(x => x * 2))composes aTrypipeable with aResultpipeable.Verification
Out of scope (deliberately)
The
DelayStrategyandRetryConfigtypes ship for forward compatibility, but noretry/exponential/constant/linearhelpers are implemented.attempt().execute()performs at most one retry whenretry.shouldRetry(cause)returnstrue. A retry loop and the delay helpers are planned for a follow-up PR.The existing
docs/internal/product/features/try.mdis rewritten to match the shipped surface; references to unimplemented helpers (partition,sanitizeMessage,toClientSafe) are removed.Closes the gap between the README/ADR 0001 §5 and the source code.
🤖 Generated with Claude Code