
Migrate Better Result to 3.0
FreeStreamline your TypeScript migration to better-result 3.0.
Free · Opens the source repo
What Migrate Better Result to 3.0 does
Migrating a TypeScript codebase from better-result 2.x to 3.0 can be a complex task, especially when dealing with changes in syntax and API behavior. This skill provides a structured approach to facilitate this transition, ensuring that developers can efficiently adapt their codebases to the latest version without losing functionality. By treating the compiler as a migration ledger, the skill guides users through the necessary steps to inventory their existing code and make mechanical changes before resolving any remaining errors based on API differences.
The migration process begins with establishing the migration surface, where users are instructed to review repository instructions, package manifests, and TypeScript configurations. This step ensures that all aspects of the codebase are accounted for, including identifying any TaggedError or Result serialization helpers that need to be addressed. Following this, the skill provides a codemod to apply necessary changes to the TaggedError syntax, preserving the integrity of existing constructors and properties while updating to the new class type requirements.
As the migration progresses, users will replace any removed serialization helpers and reconcile changes in inference and optional APIs. This involves careful attention to how methods like tryRecover and matchError have evolved in version 3.0, ensuring that the new behavior aligns with the intended functionality of the application. Finally, the skill culminates in an upgrade phase, where users can verify their migration through comprehensive testing and validation, ensuring that the transition to better-result 3.0 is successful and maintains the desired application behavior.
When to use it
Use this skill when you need to migrate your TypeScript project from better-result version 2.x to 3.0, particularly if your codebase relies on TaggedError syntax or Result serialization helpers.
When not to use it
This skill is not suitable for projects that do not use better-result or do not require migration to version 3.0. It may also not be ideal for those unfamiliar with TypeScript or the specific APIs involved.
What you can build with it
Upgrading Legacy Code
A development team needs to upgrade their legacy TypeScript application that relies on better-result 2.x features to the latest version.
Refactoring TaggedError Usage
A developer wants to refactor their code to utilize the new TaggedError syntax while ensuring existing functionality remains intact.
Testing Migration Outcomes
After migrating to better-result 3.0, a team runs tests to validate that all functionality works as expected and no errors are introduced.
How to install Migrate Better Result to 3.0
View source1. Install with the skills CLI
npx skills add dmmulroy/better-result/migrate-better-result-3 --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by dmmulroyMigrate 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. 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. 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, especially:
tryRecoverandtryRecoverAsyncnow preserve the original success and union it with a different recovered success type.matchError,matchErrorPartial, and the additiveTaggedError#matchmethod infer unions from divergent handler returns. Exhaustive matching turns thrown handlers intoPanic;matchis reserved, so rename payload or subclass collisions rejected by the v3 types.matchErrorPartialmay omit its fallback; an unhandled tagged error is then returned unchanged.Result.partitionnow supports heterogeneous inputs;all,allAsync, andpartitionAsyncare new.Result.tryPromiseadds abort context, dynamic delays, and jitter while retaining valid v2 static retry configurations.- Tagged errors are iterable for direct
yield*inResult.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. Repeat the inventory search and the codemod check. Preserve unrelated files when a repository-wide formatter reports pre-existing failures.
Report the version change, files migrated by branch, codec/error-handling decisions, adopted optional features, and validation evidence.
Complete when: the targeted run exercised only its intended files, no removed API or v2 TaggedError syntax remains outside recorded generated/vendor code, all inventoried sites are closed, the requested version is still installed, and every repository check passes or has a concrete reported failure.
Frequently asked questions about Migrate Better Result to 3.0
Similar skills
React Composition Patterns
Streamline your React component architecture with proven patterns.
Pester Should Migration
Easily convert Pester v5 assertions to v6 syntax.
Radix to Base UI Migration
Seamlessly migrate React components from Radix UI to Base UI.
Migrate Next.js to Vinext
Seamlessly transition your Next.js projects to Vinext.
WinUI 3 Migration Guide
Streamline your UWP to WinUI 3 migration process.
Refactor
Enhance code maintainability without altering behavior.
