feat!: replace neverthrow with unthrown#263
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR performs a breaking migration of the repo’s Result-based error-handling API from neverthrow to unthrown, updating public types (ResultAsync → AsyncResult), adopting TaggedError-based error classes, and aligning docs/examples/tooling with unthrown’s three-channel model (ok / err / defect).
Changes:
- Replace
neverthrowwithunthrownacross worker/client/contract APIs and tests, including updated matching/narrowing patterns. - Update worker/client error classes to
TaggedError(...)and shift “unexpected” failures to thedefectchannel (not modelederr). - Update workspace catalog/lockfile and configure pnpm
minimumReleaseAgeExcludefor first-partyunthrown.
Reviewed changes
Copilot reviewed 58 out of 59 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates top-level examples and dependency guidance to unthrown / AsyncResult. |
| pnpm-workspace.yaml | Swaps catalog entry to unthrown@0.1.0 and excludes it from minimum release age. |
| pnpm-lock.yaml | Locks unthrown@0.1.0 and removes neverthrow entries. |
| packages/worker/src/workflow.ts | Updates workflow context types/docs to AsyncResult and defect semantics (incl. scope behavior). |
| packages/worker/src/internal.ts | Re-exports updated async-result helper and keeps shared worker internals aligned with new model. |
| packages/worker/src/errors.ts | Migrates worker error hierarchy to TaggedError (keeping validation errors as ApplicationFailure). |
| packages/worker/src/child-workflow.ts | Migrates child-workflow helpers to AsyncResult, explicit defect handling, and updated matching/narrowing. |
| packages/worker/src/cancellation.ts | Changes cancellation scopes to return AsyncResult with cancellation modeled and non-cancellation throws as defects. |
| packages/worker/src/cancellation.spec.ts | Updates tests to assert defect behavior and free-function narrowing (isOk/isErr/isDefect). |
| packages/worker/src/activity.ts | Updates activity handler wrapper to fold ok/err/defect and validate outputs on ok. |
| packages/worker/src/activity.spec.ts | Updates activity tests to unthrown primitives and replaces okAsync/errAsync usage with .toAsync(). |
| packages/worker/src/tests/worker.spec.ts | Updates integration tests to unthrown narrowing and async lifting helpers. |
| packages/worker/src/tests/test.workflows.ts | Updates workflow test codepaths to handle ok/err/defect explicitly. |
| packages/worker/README.md | Updates worker package documentation/examples to unthrown. |
| packages/worker/package.json | Replaces neverthrow with unthrown in keywords/devDeps/peerDeps metadata. |
| packages/contract/src/result-async.ts | Replaces the shared ResultAsync wrapper with an AsyncResult/defect-based wrapper. |
| packages/contract/src/result-async.spec.ts | Updates unit tests for the shared async wrapper to assert defect behavior. |
| packages/contract/package.json | Swaps peer/dev dependency from neverthrow to unthrown. |
| packages/client/src/types.ts | Updates client-side type signatures to AsyncResult. |
| packages/client/src/schedule.ts | Migrates schedule client to AsyncResult, free-function narrowing, and defect forwarding. |
| packages/client/src/schedule.spec.ts | Updates schedule tests to unthrown narrowing helpers. |
| packages/client/src/internal.ts | Updates shared client wrapper to return AsyncResult and forward unexpected failures as defects. |
| packages/client/src/errors.ts | Migrates client error types to TaggedError with structured payloads. |
| packages/client/src/client.ts | Migrates typed client APIs to AsyncResult, updates docs/examples, and adds defect-forwarding checks. |
| packages/client/src/client.spec.ts | Updates unit tests to use unthrown narrowing/match object form. |
| packages/client/src/tests/client.spec.ts | Updates integration tests to use unthrown narrowing helpers. |
| packages/client/package.json | Replaces neverthrow with unthrown in metadata and peer deps. |
| examples/order-processing-worker/src/integration.spec.ts | Updates example integration tests to unthrown narrowing. |
| examples/order-processing-worker/src/application/workflows.ts | Updates example workflow docs/comments to unthrown terminology. |
| examples/order-processing-worker/src/application/activities.ts | Migrates example activities to fromPromise + AsyncResult style. |
| examples/order-processing-worker/package.json | Swaps example dependency from neverthrow to unthrown. |
| examples/order-processing-client/src/client.ts | Updates example client to handle err vs defect and use free-function narrowing. |
| examples/order-processing-client/package.json | Adds unthrown dependency to the example client. |
| docs/index.md | Updates landing page messaging/examples to unthrown. |
| docs/guide/worker-usage.md | Updates worker guide installation and examples to unthrown patterns. |
| docs/guide/worker-implementation.md | Updates implementation guide examples/messaging to AsyncResult and defect handling. |
| docs/guide/why-temporal-contract.md | Updates conceptual examples to the new match object form and defect channel. |
| docs/guide/troubleshooting.md | Updates troubleshooting guidance for AsyncResult and migration mapping. |
| docs/guide/result-pattern.md | Updates the result pattern guide with unthrown APIs, narrowing rules, and defect channel. |
| docs/guide/migrating-to-unthrown.md | Adds a new migration guide from neverthrow to unthrown. |
| docs/guide/installation.md | Updates install instructions to require unthrown instead of neverthrow. |
| docs/guide/getting-started.md | Updates getting-started guide examples/match patterns to unthrown. |
| docs/guide/entry-points.md | Updates architecture guide examples to unthrown result handling. |
| docs/guide/core-concepts.md | Updates core concepts examples for match keys and defect channel. |
| docs/guide/client-usage.md | Updates client guide examples to AsyncResult + match object form. |
| docs/guide/activity-handlers.md | Updates activity handler docs/examples and typing to AsyncResult. |
| docs/examples/index.md | Updates examples overview copy to unthrown. |
| docs/examples/basic-order-processing.md | Updates example narrative to AsyncResult and unthrown terminology. |
| docs/api/index.md | Updates API docs index to reference unthrown and the new migration doc. |
| docs/.vitepress/config.ts | Adds the new migration guide to the VitePress sidebar. |
| AGENTS.md | Updates agent rule #2 to reflect unthrown’s API, narrowing, and defect channel behavior. |
| .changeset/migrate-to-unthrown.md | Adds a major changeset describing the breaking migration and its key semantics. |
| .agents/rules/workflow-determinism.md | Updates determinism rule docs to reference AsyncResult in cancellation helpers. |
| .agents/rules/project-overview.md | Updates project overview to describe unthrown usage and defect channel. |
| .agents/rules/handlers.md | Updates handler guidance/examples to unthrown, including cancellation semantics. |
| .agents/rules/dependencies.md | Updates peer-dependency policy documentation to name unthrown instead of neverthrow. |
| .agents/rules/commands.md | Updates commit-message example to reference unthrown migration. |
| .agents/rules/code-style.md | Updates error-handling style guide to unthrown and free-function narrowing. |
| .agents/rules/adding-a-package.md | Updates dependency guidance to forbid putting unthrown in dependencies. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
btravers
commented
Jun 26, 2026
Migrate the Result/error-handling spine from neverthrow to the btravstack-owned unthrown (unthrown@0.1.0) across all packages. This is a breaking change to the public API of the published packages. - ResultAsync<T, E> -> AsyncResult<T, E> in every public signature; the unthrown peer dependency replaces neverthrow. - No okAsync/errAsync: lift a sync Result with .toAsync(). Promise boundaries use fromPromise/fromSafePromise. - Narrowing uses the free functions isOk(r)/isErr(r)/isDefect(r) — the method forms return plain boolean and do not narrow. - New defect channel: unanticipated throws surface as defects (re-thrown at the edge), not typed errors. WorkflowScopeError is removed (scope throws are now defects); the client's "unexpected" RuntimeClientError wrap is gone. - Error classes use unthrown's TaggedError; ChildWorkflowCancelledError is now a sibling (distinct _tag) of ChildWorkflowError. The worker's ValidationError subclasses stay as ApplicationFailure for Temporal's terminal-failure semantics. - First-party unthrown is excluded from the pnpm minimumReleaseAge policy. - Docs, agent rules, READMEs, and the example apps updated; new migrating-to-unthrown guide added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename the internal `makeResultAsync` helper to `makeAsyncResult` across the worker and client packages so the name matches its `AsyncResult` return type and the contract's `_internal_makeAsyncResult`. - Fix a misleading test comment that attributed pre-await throw capture to `fromPromise` instead of `makeAsyncResult` (via `fromSafePromise`). - Grammar: "a AsyncResult" -> "an AsyncResult" in the troubleshooting guide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR review: prefix every TaggedError `_tag` with the package scope (e.g. "@temporal-contract/WorkflowExecutionNotFoundError") so the library's error discriminants never collide with a consumer's own tags or another library's. Each constructor overrides `this.name` back to the bare class name, so logs / stack traces / Temporal UI keep the conventional identifier and stay consistent with the ValidationError family. The tags are internal discriminants that do not cross the Temporal wire (only the ApplicationFailure-based ValidationErrors do, and their `type` is unchanged). Docs (result-pattern, migrating-to-unthrown), the agent rule, and the changeset note the namespacing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add `_internal_assertNoDefect` (contract) and use it to replace the manual `if (!isOk(r)) throw r.cause` double-guards in the worker child-workflow and client schedule/client work functions — narrows an internally-built Result (known to carry only ok/err) to `Ok | Err` in one call. - Document the activity wrapper's defect handling: a defect re-throws its raw cause so Temporal applies the default (retryable) policy, preserving the pre-unthrown behaviour; we deliberately do not force `nonRetryable`. - Rewrite the example client to the idiomatic unthrown railway: `tap().flatMap()` chains start -> result, then a single exhaustive `matchTags` fold replaces the ts-pattern `P.instanceOf` matching. Drop the now-unused `ts-pattern` dep. - Wire up `@unthrown/vitest` matchers (`toBeOk`/`toBeOkWith`/`toBeErr`/ `toBeDefect`) via a per-package setup file, and convert the discriminant assertions across the specs (narrowing blocks stay for value introspection). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
unthrown 0.2.0 resolves the friction points surfaced during the migration:
- `TaggedError(tag, { name })` decouples the discriminant from `Error.name`.
Use it to set the package-namespaced `_tag` and the bare-class-name
`Error.name` declaratively, removing the 14 manual `this.name = "..."`
constructor overrides.
- `.isOk()` / `.isErr()` / `.isDefect()` methods are now type guards (like the
free functions). The codebase keeps using the free functions, but the docs
and agent rules that claimed the methods "don't narrow" are corrected.
- `fromPromise` now returns `AsyncResult<T, Exclude<R, Defect>>` and `matchTags`
has an async overload — both available if needed; current call sites are
unaffected.
Bumps the `unthrown` / `@unthrown/vitest` catalog entries and the published
packages' `unthrown` peer range to `^0.2` (the `{ name }` option is used at
runtime, so 0.2.0 is the minimum).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that unthrown 0.2.0 makes `.isOk()` / `.isErr()` / `.isDefect()` type guards, switch every narrowing site from the free-function form `isOk(r)` to the method form `r.isOk()` and drop the now-unused free-function imports. `assertNoDefect` stays (a `Result` still always includes `Defect`, so it discharges the impossible case before `.value`/`.error`). Docs, agent rules, and the changeset updated to reflect the method form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b809c02 to
e102856
Compare
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.
Summary
Migrates the Result/error-handling spine from
neverthrowto the btravstack-ownedunthrown(unthrown@0.1.0) across all four published packages. This is the idiomatic adoption — not a mechanical rename — embracing unthrown'sdefectchannel andTaggedError. Breaking change for the whole fixed-version package group (major bump).What changed
Public API
ResultAsync<T, E>→AsyncResult<T, E>in every activity, workflow-context, child-workflow, schedule, and typed-client signature.unthrownreplacesneverthrowas the peer dependency.okAsync/errAsync— lift a syncResultwith.toAsync()(ok(v).toAsync(),err(e).toAsync()). Promise boundaries usefromPromise(p, qualify)/fromSafePromise(p).isOk(r)/isErr(r)/isDefect(r)— unthrown'sr.isOk()methods return plainbooleanand do not narrow.value/.error/.cause.Defect channel (behavior change)
defectchannel (inspected viaisDefect(r)/r.cause, re-thrown at the edge) rather than as a typederr. Deliberate boundary classification (e.g. Temporal not-found →WorkflowExecutionNotFoundError) still produces a modelederr.WorkflowScopeErrorremoved — non-cancellation throws insidecancellableScope/nonCancellableScopeare now defects; the scopes' error union narrows toWorkflowCancelledError.RuntimeClientErrorwrap is gone (now a defect).Error classes
WorkerErrorhierarchy and the entire clientTypedClientErrorhierarchy useTaggedError("Name")<{ ...payload }>(each gets a_tag, foldable withmatchTags).ChildWorkflowCancelledErroris now a sibling ofChildWorkflowError(distinct_tag) rather than a subclass — discriminate via_tag/instanceof ChildWorkflowCancelledError.ValidationErrorsubclasses still extend Temporal'sApplicationFailure(load-bearing for terminal-failure semantics, issue Contract validation throws plain Error → infinite Workflow Task retry instead of failing the execution (TS SDK) #251) —TaggedErrorcan't replace that.Tooling / docs
unthrownis excluded from the pnpmminimumReleaseAgepolicy (the maturity delay guards against third-party supply-chain risk).AGENTS.mdrule chore(deps): bump actions/setup-node from 4 to 6 #2 +.agents/rules/*), READMEs, and the example apps updated. Newmigrating-to-unthrown.mdguide + sidebar entry (the historicalmigrating-to-neverthrow.mdis kept).majorchangeset covering all four packages.Verification
typecheck(full repo, strict +exactOptionalPropertyTypes)lint(oxlint) ·format(oxfmt) ·knipunthrown@0.1.0was published today, so it is younger than the 7-dayminimumReleaseAgeproposed in #259. This PR excludesunthrownfrom that policy viaminimumReleaseAgeExclude, so it installs fine here. If #259 merges first, no conflict — the exclusion already covers it.🤖 Generated with Claude Code