Skip to content
Development
Skill

/migrate-better-result-3

Migrate a TypeScript codebase from better-result 2.x to 3.0. Use when upgrading better-result across the TaggedError syntax, removed Result serialization helpers, recovery inference, matching, or retry APIs.

From plugin
better-result
2k2 skills

Claude Code

$ npx -y skills add dmmulroy/better-result --skill migrate-better-result-3 --agent claude-code

SKILL.md

migrate-better-result-3.SKILL.md
name: migrate-better-result-3
description: Migrate a TypeScript codebase from better-result 2.x to 3.0. Use when upgrading better-result across the TaggedError syntax, removed Result serialization helpers, recovery inference, matching, or retry APIs.

Migrate better-result to 3.0

Treat the compiler as the migration ledger: inventory first, make mechanical changes, then resolve each remaining error by API branch.

1. Establish the migration surface

Read repository instructions, package manifests, lockfiles, TypeScript configuration, and validation commands. Verify that the source version is `better-result` 2.x and inspect the installed 3.0 declarations when available; package source outranks remembered APIs.

Search production code and tests for:

rg -n --glob '*.{ts,tsx,mts,cts}' \
  'TaggedError|Result\.(serialize|deserialize|hydrate|tryRecover|tryRecoverAsync|tryPromise|partition|gen)|matchError(Partial)?|TaggedErrorClass|Serialized(Result|Ok|Err)'

Classify every hit under the audited API changes in [references/v3-api-diff.md](references/v3-api-diff.md). Include tests that structurally compare a `Result` containing a tagged error; tagged errors become iterable in v3 even though the `Result.gen` signature is unchanged. Record generated or vendored hits separately rather than editing them.

**Complete when:** the installed source version, target 3.0 API, validation commands, every matching production/test site, and every tagged-error Result assertion are accounted for by file and migration branch.

2. Apply the TaggedError codemod

List safe trailing-call edits:

node <skill-dir>/scripts/migrate-tagged-error-v3.mjs . --list

Review the listed class heritage expressions, then apply them:

node <skill-dir>/scripts/migrate-tagged-error-v3.mjs . --write
node <skill-dir>/scripts/migrate-tagged-error-v3.mjs . --check

The transform changes only the v2 class heritage shape:

class NotFoundError extends TaggedError("NotFoundError")<{ id: string }>() {}
// becomes
class NotFoundError extends TaggedError("NotFoundError")<{ id: string }> {}

It preserves constructors, properties, formatting, and call sites. Manually update exported `TaggedErrorClass<Tag, Props>` annotations to the v3 class type `TaggedErrorClass<Tag>`; move payload typing to the subclass application shown above.

**Complete when:** the script's check exits successfully and searches find no v2 trailing factory calls or two-argument `TaggedErrorClass` uses outside generated/vendor code.

3. Replace removed serialization helpers

If the inventory contains `Result.serialize`, `Result.deserialize`, or `Result.hydrate`, follow [references/result-codec-migration.md](references/result-codec-migration.md). The owning codec validates one application contract: a method's actual Ok and Err payloads in both directions. Share schema fragments, factories, and error-policy helpers across codecs; keep distinct success contracts in distinct named codecs.

Account for changed control flow: serialization can now return `ResultSerializationError`; deserialization adds `ResultDeserializationError`; sync/async schemas determine whether codec operations return a `Result` or `Promise<Result>`. When the repository owns both producer and consumer, versions their schemas together, and treats contract mismatch as a defect, prefer `serializeUnsafe` and `deserializeUnsafe` to remove codec-error handling boilerplate. `serializeUnsafe` removes `ResultSerializationError` by panicking; `deserializeUnsafe` removes only `ResultDeserializationError` while preserving valid decoded domain Err values. Keep safe methods at public, independently versioned, persisted, or otherwise untrusted boundaries.

**Complete when:** every removed-helper call has a method- or boundary-specific codec with four payload schemas, every wire Err is reconstructed as the intended domain error, and every codec error or intentional unsafe Panic policy and async return is handled at its boundary.

4. Reconcile changed inference and optional APIs

Type-check after the mechanical and codec changes. Resolve diagnostics using [references/v3-api-diff.md](references/v3-api-diff.md), especially:

  • `tryRecover` and `tryRecoverAsync` now preserve the original success and union it with a different recovered success type.
  • `matchError`, `matchErrorPartial`, and the additive `TaggedError#match` method infer unions from divergent handler returns. Exhaustive matching turns thrown handlers into `Panic`; `match` is reserved, so rename payload or subclass collisions rejected by the v3 types.
  • `matchErrorPartial` may omit its fallback; an unhandled tagged error is then returned unchanged.
  • `Result.partition` now supports heterogeneous inputs; `all`, `allAsync`, and `partitionAsync` are new.
  • `Result.tryPromise` adds abort context, dynamic delays, and jitter while retaining valid v2 static retry configurations.
  • Tagged errors are iterable for direct `yield*` in `Result.gen`. Replace structural deep-equality assertions over tagged errors with separate Result-status and error identity/field assertions.

Keep existing runtime behavior unless the user requested adoption of a new 3.0 capability. Prefer accurate widened types and explicit narrowing over casts.

**Complete when:** every compiler diagnostic caused by a changed 3.0 signature is resolved, every intentional inferred union reaches an explicit handling point, and unrelated behavior remains unchanged.

5. Upgrade and prove the migration

Update the direct dependency and lockfile to the exact requested stable or prerelease 3.0 version. Keep that version fixed while diagnosing migration behavior.

Run one targeted migrated test first. Read the package script and package-manager argument-passthrough rules, then verify the runner's collected-file output contains only the intended test files. Run formatting, lint, type-check, the full test suite, and build commands required by the repository. R

Read more
Stats
2,014
Stars
44
Forks
Maintained
Maintenance
TypeScript
Language
MIT
License
Last commit
Created

Repo: dmmulroy/better-result

@dmmulroy
Ships withbetter-result

Lightweight Result type for TypeScript with generator-based composition.

Stars on GitHub

Momentum

stars in total
2.0k
Star history of dmmulroy/better-result on GitHub, a chart by star-history.com

Other skills on better-result.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills12d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react3d252k
@facebook@facebookInstall Skill

Questions about migrate-better-result-3.

Which coding agents can use migrate-better-result-3?
migrate-better-result-3 installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.