Skip to content

feat!: replace neverthrow with unthrown#263

Merged
btravers merged 6 commits into
mainfrom
feat/migrate-to-unthrown
Jun 26, 2026
Merged

feat!: replace neverthrow with unthrown#263
btravers merged 6 commits into
mainfrom
feat/migrate-to-unthrown

Conversation

@btravers

Copy link
Copy Markdown
Collaborator

Summary

Migrates the Result/error-handling spine from neverthrow to the btravstack-owned unthrown (unthrown@0.1.0) across all four published packages. This is the idiomatic adoption — not a mechanical rename — embracing unthrown's defect channel and TaggedError. 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. unthrown replaces neverthrow as the peer dependency.
  • No okAsync/errAsync — lift a sync Result with .toAsync() (ok(v).toAsync(), err(e).toAsync()). Promise boundaries use fromPromise(p, qualify) / fromSafePromise(p).
  • Narrowing uses free functions isOk(r) / isErr(r) / isDefect(r) — unthrown's r.isOk() methods return plain boolean and do not narrow .value/.error/.cause.

Defect channel (behavior change)

  • Unanticipated throws now surface on unthrown's third defect channel (inspected via isDefect(r) / r.cause, re-thrown at the edge) rather than as a typed err. Deliberate boundary classification (e.g. Temporal not-found → WorkflowExecutionNotFoundError) still produces a modeled err.
  • WorkflowScopeError removed — non-cancellation throws inside cancellableScope/nonCancellableScope are now defects; the scopes' error union narrows to WorkflowCancelledError.
  • The client's "unexpected" RuntimeClientError wrap is gone (now a defect).

Error classes

  • The worker WorkerError hierarchy and the entire client TypedClientError hierarchy use TaggedError("Name")<{ ...payload }> (each gets a _tag, foldable with matchTags).
  • ChildWorkflowCancelledError is now a sibling of ChildWorkflowError (distinct _tag) rather than a subclass — discriminate via _tag / instanceof ChildWorkflowCancelledError.
  • ⚠️ Kept unchanged: the worker's ValidationError subclasses still extend Temporal's ApplicationFailure (load-bearing for terminal-failure semantics, issue Contract validation throws plain Error → infinite Workflow Task retry instead of failing the execution (TS SDK) #251) — TaggedError can't replace that.

Tooling / docs

  • First-party unthrown is excluded from the pnpm minimumReleaseAge policy (the maturity delay guards against third-party supply-chain risk).
  • All 15 docs, agent rules (AGENTS.md rule chore(deps): bump actions/setup-node from 4 to 6 #2 + .agents/rules/*), READMEs, and the example apps updated. New migrating-to-unthrown.md guide + sidebar entry (the historical migrating-to-neverthrow.md is kept).
  • major changeset covering all four packages.

Verification

  • typecheck (full repo, strict + exactOptionalPropertyTypes)
  • lint (oxlint) · format (oxfmt) · knip
  • 271 unit tests (contract 78, client 80, worker 113)
  • integration tests (testcontainers Temporal e2e — worker/client/examples, all 8 turbo tasks)
  • ✅ docs build (VitePress via turbo)

⚠️ Note for reviewers / CI

unthrown@0.1.0 was published today, so it is younger than the 7-day minimumReleaseAge proposed in #259. This PR excludes unthrown from that policy via minimumReleaseAgeExclude, so it installs fine here. If #259 merges first, no conflict — the exclusion already covers it.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 26, 2026 12:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (ResultAsyncAsyncResult), adopting TaggedError-based error classes, and aligning docs/examples/tooling with unthrown’s three-channel model (ok / err / defect).

Changes:

  • Replace neverthrow with unthrown across worker/client/contract APIs and tests, including updated matching/narrowing patterns.
  • Update worker/client error classes to TaggedError(...) and shift “unexpected” failures to the defect channel (not modeled err).
  • Update workspace catalog/lockfile and configure pnpm minimumReleaseAgeExclude for first-party unthrown.

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

Comment thread packages/worker/src/cancellation.spec.ts Outdated
Comment thread docs/guide/troubleshooting.md Outdated
Comment thread packages/worker/src/internal.ts Outdated
Comment thread packages/worker/src/errors.ts Outdated
btravers and others added 6 commits June 26, 2026 18:50
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>
@btravers btravers force-pushed the feat/migrate-to-unthrown branch from b809c02 to e102856 Compare June 26, 2026 16:50
@btravers btravers enabled auto-merge (rebase) June 26, 2026 16:50
@btravers btravers merged commit a502075 into main Jun 26, 2026
9 checks passed
@btravers btravers deleted the feat/migrate-to-unthrown branch June 26, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants