Skip to content

feat!: replace @swan-io/boxed with neverthrow#206

Merged
btravers merged 3 commits intomainfrom
feat/neverthrow
May 4, 2026
Merged

feat!: replace @swan-io/boxed with neverthrow#206
btravers merged 3 commits intomainfrom
feat/neverthrow

Conversation

@btravers
Copy link
Copy Markdown
Owner

@btravers btravers commented May 4, 2026

Summary

Replaces @swan-io/boxed with neverthrow across every public surface — typed client, worker, activities, workflow context, cancellation scopes — and removes the @temporal-contract/boxed package entirely. Major version bump for contract, worker, client, and testing.

Why

  • Critical issue in @swan-io/boxed affecting our usage prompted the move.
  • neverthrow is the most widely-used Result library on npm, with a more familiar API for new contributors.
  • Single dependency now covers both sync (Result<T, E>) and async (ResultAsync<T, E>) cases — the custom Future class and the @temporal-contract/boxed re-export package both go away.

What changed

  • API surface: Future<Result<T, E>>ResultAsync<T, E> everywhere a typed-client method, activity, or workflow helper used to return one. Result<T, E> is now imported from neverthrow.
  • Package removed: @temporal-contract/boxed. packages/boxed/ deleted, dropped from changeset config, knip config, docs scripts, and the docs sidebar.
  • 18 source files migrated across packages/client, packages/worker, and the order-processing examples.
  • Tests: 216 unit tests green (pnpm test). Full workspace pnpm typecheck is clean.
  • Migration guide added at docs/guide/migrating-to-neverthrow.md and linked from the docs sidebar, README, installation guide, and API index.
  • All other docs rewritten: 13 user-facing guides + per-package READMEs + .agents/rules/* + the root README.

API mapping highlights

boxed neverthrow
Result.Ok(v) / Result.Error(e) ok(v) / err(e)
Future.value(Result.Ok(v)) okAsync(v)
Future.fromPromise(p, mapErr) ResultAsync.fromPromise(p, mapErr)
.isError() .isErr()
.flatMap / .flatMapOk .andThen
.mapError .mapErr
.getOr .unwrapOr
.match({ Ok, Error }) .match(okFn, errFn) (positional)
.tap / .tapOk / .tapError inline as .map(v => { sideEffect(v); return v })

Full mapping in docs/guide/migrating-to-neverthrow.md.

Test plan

  • CI green (lint, format, typecheck, knip, test, test-integration, build, security-audit, bundle-size).
  • Verify the migration guide renders on the docs site preview.
  • Confirm pnpm install --frozen-lockfile succeeds with the new lockfile.
  • Smoke-test the order-processing example workers/clients locally against a Temporal dev server.

Independence from #205

Branched off main, not from #205 (the CI publish fix). The two are orthogonal — landing order doesn't matter, and either rebase will be trivial.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 4, 2026 13:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 migrates temporal-contract’s public API from the @swan-io/boxed / Future<Result<...>> pattern to neverthrow’s Result / ResultAsync, and removes the now-obsolete @temporal-contract/boxed package and related documentation/tooling references.

Changes:

  • Replaced Future<Result<T, E>> return types with ResultAsync<T, E> across worker workflow context helpers, cancellation scopes, typed client APIs, and examples/tests.
  • Removed the packages/boxed/ package and scrubbed references from workspace config, knip, docs build scripts, and docs navigation.
  • Updated docs site and READMEs, including a new migration guide for consumers upgrading from boxed.

Reviewed changes

Copilot reviewed 61 out of 62 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Updates high-level docs and installation guidance for neverthrow-based API.
pnpm-workspace.yaml Removes @swan-io/boxed from catalog; adds neverthrow.
pnpm-lock.yaml Reflects dependency graph updates: removes boxed packages, adds neverthrow.
packages/worker/src/workflow.ts Migrates workflow context child-workflow APIs to ResultAsync and neverthrow constructors.
packages/worker/src/errors.ts Updates cancellation-scope error docs to match neverthrow terminology.
packages/worker/src/cancellation.ts Reimplements cancellation scope helpers to return ResultAsync.
packages/worker/src/cancellation.spec.ts Updates cancellation tests for .isErr() semantics.
packages/worker/src/activity.ts Changes activity implementation types from Future/Result to ResultAsync and adjusts wrapper logic/docs.
packages/worker/src/activity.spec.ts Updates unit tests to use neverthrow helpers (okAsync/errAsync/ResultAsync).
packages/worker/src/tests/worker.spec.ts Updates integration tests to assert neverthrow Result behavior.
packages/worker/README.md Updates worker package docs/examples to use ResultAsync.
packages/worker/package.json Replaces boxed deps with neverthrow dependency.
packages/client/src/types.ts Migrates client-facing inferred signatures to ResultAsync.
packages/client/src/schedule.ts Ports schedule client wrapper from Future/Result to ResultAsync.
packages/client/src/schedule.spec.ts Updates schedule tests for neverthrow results.
packages/client/src/internal.ts Replaces makeFuture helper with makeResultAsync using neverthrow.
packages/client/src/tests/client.spec.ts Updates client integration tests for neverthrow results/match signature.
packages/client/package.json Replaces boxed dep with neverthrow dependency.
packages/boxed/vitest.config.ts Deletes boxed package test config (package removal).
packages/boxed/typedoc.json Deletes boxed package typedoc config (package removal).
packages/boxed/tsconfig.json Deletes boxed package tsconfig (package removal).
packages/boxed/src/result.ts Deletes boxed Result implementation (package removal).
packages/boxed/src/result.spec.ts Deletes boxed Result tests (package removal).
packages/boxed/src/interop.ts Deletes boxed interop helpers (package removal).
packages/boxed/src/interop.spec.ts Deletes boxed interop tests (package removal).
packages/boxed/src/index.ts Deletes boxed package entrypoint (package removal).
packages/boxed/src/future.ts Deletes boxed Future implementation (package removal).
packages/boxed/src/future.spec.ts Deletes boxed Future tests (package removal).
packages/boxed/README.md Deletes boxed package README (package removal).
packages/boxed/package.json Deletes boxed package manifest (package removal).
packages/boxed/CHANGELOG.md Deletes boxed package changelog (package removal).
knip.json Removes boxed package from knip workspace config.
examples/order-processing-worker/src/application/workflows.ts Updates example workflow docs/comments for neverthrow terminology.
examples/order-processing-worker/src/application/activities.ts Ports example activities to ResultAsync.fromPromise / okAsync.
examples/order-processing-worker/package.json Swaps boxed dependency for neverthrow.
examples/order-processing-client/src/client.ts Updates example client to .isErr() and neverthrow naming.
docs/scripts/copy-docs.ts Removes boxed package from docs copy/build dependency list.
docs/package.json Removes docs dependency on removed @temporal-contract/boxed package.
docs/index.md Updates landing page examples to neverthrow usage and .match(okFn, errFn).
docs/guide/worker-usage.md Migrates guide from Future/Result to neverthrow and updates snippets.
docs/guide/worker-implementation.md Migrates deep-dive guide to neverthrow patterns and examples.
docs/guide/troubleshooting.md Updates troubleshooting guidance and snippets for neverthrow.
docs/guide/result-pattern.md Rewrites Result pattern guide around Result / ResultAsync.
docs/guide/migrating-to-neverthrow.md Adds a dedicated migration mapping guide from boxed to neverthrow.
docs/guide/installation.md Updates install instructions to include neverthrow and remove boxed.
docs/guide/getting-started.md Updates getting-started snippets and terminology for neverthrow.
docs/guide/entry-points.md Updates architecture guide snippets to ResultAsync-based APIs.
docs/guide/client-usage.md Updates client guide to ResultAsync returns and positional .match.
docs/guide/boxed-vs-swan.md Removes boxed-vs-swan page (no longer relevant after migration).
docs/guide/activity-handlers.md Updates activity handler typing guide for neverthrow.
docs/examples/index.md Updates examples index wording to neverthrow.
docs/examples/basic-order-processing.md Updates example narrative to Result / ResultAsync terminology.
docs/api/index.md Removes boxed API link and explains neverthrow as the source of Result types.
docs/.vitepress/config.ts Updates sidebar navigation to include migration guide; removes boxed API entry.
.changeset/swap-boxed-for-neverthrow.md Adds major-version changeset describing the breaking migration.
.changeset/config.json Removes @temporal-contract/boxed from the fixed release group.
.agents/rules/project-overview.md Updates agent docs to reflect neverthrow-based error handling.
.agents/rules/handlers.md Updates handler guidance/snippets to ResultAsync.
.agents/rules/dependencies.md Updates canonical dependency list (neverthrow replaces boxed).
.agents/rules/code-style.md Updates code-style rules to reference neverthrow Result/ResultAsync.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread README.md
Benoit TRAVERS and others added 2 commits May 4, 2026 16:02
The Result/Future surface returned by every typed-client method, activity
implementation, and workflow context helper now uses neverthrow's
ResultAsync instead of @swan-io/boxed's Future<Result>. The
@temporal-contract/boxed package has been removed entirely.

This is a breaking change for every downstream consumer.

Notable mappings:
  Result.Ok / Result.Error      -> ok / err (or okAsync / errAsync)
  Future<Result<T, E>>          -> ResultAsync<T, E>
  Future.fromPromise(p, mapErr) -> ResultAsync.fromPromise(p, mapErr)
  .isError()                    -> .isErr()
  .flatMap / .flatMapOk         -> .andThen
  .mapError                     -> .mapErr
  .getOr                        -> .unwrapOr
  .match({ Ok, Error })         -> .match(okFn, errFn)  // positional
  .tap / .tapOk / .tapError     -> inline via .map / .mapErr

A migration guide lives at docs/guide/migrating-to-neverthrow.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Worker and client both expose `ResultAsync<T, E>` types directly in their
public API surface. With neverthrow as a regular dependency, a consumer
who installs their own copy at a different version ends up with two
disjoint nominal `ResultAsync` types in their type-checker — the one
emitted in our `.d.ts` and the one their code imports — even though the
runtime classes are compatible.

Move it to peerDependencies (^8) and keep it in devDependencies so the
local build still resolves it. Matches the pattern already used for
`@temporalio/*`. Also resolves the inconsistency the Copilot reviewer
flagged on the README (which already described neverthrow as a peer dep).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@btravers btravers force-pushed the feat/neverthrow branch from aa40d27 to 9921c55 Compare May 4, 2026 14:03
The order-processing example's integration spec still asserted on the
boxed `{ tag: "Ok" | "Error", value | error }` shape. neverthrow's
Result has no `tag` field, so every `expect.objectContaining({ tag })`
matcher silently fell through and the suite failed in CI.

Translate the remaining six tests to the `.isOk()` / `.isErr()` +
narrow-then-assert pattern used elsewhere in the migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@btravers btravers merged commit 168fe98 into main May 4, 2026
12 checks passed
@btravers btravers deleted the feat/neverthrow branch May 4, 2026 14:39
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