Migrate better-result TaggedError from v1 (class-based) to v2 (factory-based) API
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
skills listSkill Instructions
name: better-result-migrate-v2 description: Migrate better-result TaggedError from v1 (class-based) to v2 (factory-based) API
better-result-migrate
Migrate better-result TaggedError classes from v1 (class-based) to v2 (factory-based) API.
When to Use
- Upgrading
better-resultfrom v1 to v2 - User asks to migrate TaggedError classes
- User mentions TaggedError v1/v2 migration
V1 API (old)
class FooError extends TaggedError {
readonly _tag = "FooError" as const;
constructor(readonly id: string) {
super(`Foo: ${id}`);
}
}
// Static methods on TaggedError
TaggedError.match(err, { ... })
TaggedError.matchPartial(err, { ... }, fallback)
TaggedError.isTaggedError(value)
V2 API (new)
class FooError extends TaggedError("FooError")<{
id: string;
message: string;
}>() {}
// Standalone functions
matchError(err, { ... })
matchErrorPartial(err, { ... }, fallback)
isTaggedError(value)
TaggedError.is(value) // also available
FooError.is(value) // class-specific check
Migration Rules
1. Simple class (no constructor logic)
// BEFORE
class FooError extends TaggedError {
readonly _tag = "FooError" as const;
constructor(readonly id: string) {
super(`Foo: ${id}`);
}
}
// AFTER
class FooError extends TaggedError("FooError")<{
id: string;
message: string;
}>() {}
// Usage changes:
// BEFORE: new FooError("123")
// AFTER: new FooError({ id: "123", message: "Foo: 123" })
2. Class with computed message
Keep custom constructor to derive message:
// BEFORE
class NotFoundError extends TaggedError {
readonly _tag = "NotFoundError" as const;
constructor(readonly resource: string, readonly id: string) {
super(`${resource} not found: ${id}`);
}
}
// AFTER
class NotFoundError extends TaggedError("NotFoundError")<{
resource: string;
id: string;
message: string;
}>() {
constructor(args: { resource: string; id: string }) {
super({ ...args, message: `${args.resource} not found: ${args.id}` });
}
}
// Usage: new NotFoundError({ resource: "User", id: "123" })
3. Class with validation
Keep validation in custom constructor:
// BEFORE
class ValidationError extends TaggedError {
readonly _tag = "ValidationError" as const;
constructor(readonly field: string) {
if (!field) throw new Error("field required");
super(`Invalid: ${field}`);
}
}
// AFTER
class ValidationError extends TaggedError("ValidationError")<{
field: string;
message: string;
}>() {
constructor(args: { field: string }) {
if (!args.field) throw new Error("field required");
super({ ...args, message: `Invalid: ${args.field}` });
}
}
4. Class with additional runtime properties
// BEFORE
class TimestampedError extends TaggedError {
readonly _tag = "TimestampedError" as const;
readonly timestamp = Date.now();
constructor(readonly reason: string) {
super(reason);
}
}
// AFTER
class TimestampedError extends TaggedError("TimestampedError")<{
reason: string;
timestamp: number;
message: string;
}>() {
constructor(args: { reason: string }) {
super({ ...args, message: args.reason, timestamp: Date.now() });
}
}
5. Static method migrations
| V1 | V2 |
|---|---|
TaggedError.match(err, handlers) | matchError(err, handlers) |
TaggedError.matchPartial(err, handlers, fallback) | matchErrorPartial(err, handlers, fallback) |
TaggedError.isTaggedError(x) | isTaggedError(x) or TaggedError.is(x) |
6. Import updates
// BEFORE
import { TaggedError } from "better-result";
// AFTER
import { TaggedError, matchError, matchErrorPartial, isTaggedError } from "better-result";
Workflow
- Find TaggedError classes: Search for
extends TaggedErrorin the codebase - Analyze each class:
- Extract
_tagvalue - Identify constructor params and their types
- Check for constructor logic (validation, computed message, side effects)
- Extract
- Transform class:
- Simple: Remove constructor, add props to type parameter
- Complex: Keep custom constructor, transform to object args
- Update usages: Change
new FooError(a, b)tonew FooError({ a, b, message }) - Migrate static methods:
TaggedError.match→matchError, etc. - Update imports: Add
matchError,matchErrorPartial,isTaggedError
Example Full Migration
Input:
import { TaggedError } from "better-result";
class NotFoundError extends TaggedError {
readonly _tag = "NotFoundError" as const;
constructor(readonly id: string) {
super(`Not found: ${id}`);
}
}
class NetworkError extends TaggedError {
readonly _tag = "NetworkError" as const;
constructor(readonly url: string, readonly status: number) {
super(`Request to ${url} failed with ${status}`);
}
}
type AppError = NotFoundError | NetworkError;
const handleError = (err: AppError) =>
TaggedError.match(err, {
NotFoundError: (e) => `Missing: ${e.id}`,
NetworkError: (e) => `Failed: ${e.url}`,
});
Output:
import { TaggedError, matchError } from "better-result";
class NotFoundError extends TaggedError("NotFoundError")<{
id: string;
message: string;
}>() {
constructor(args: { id: string }) {
super({ ...args, message: `Not found: ${args.id}` });
}
}
class NetworkError extends TaggedError("NetworkError")<{
url: string;
status: number;
message: string;
}>() {
constructor(args: { url: string; status: number }) {
super({ ...args, message: `Request to ${args.url} failed with ${args.status}` });
}
}
type AppError = NotFoundError | NetworkError;
const handleError = (err: AppError) =>
matchError(err, {
NotFoundError: (e) => `Missing: ${e.id}`,
NetworkError: (e) => `Failed: ${e.url}`,
});
More by dmmulroy
View allComprehensive Cloudflare platform skill covering Workers, Pages, storage (KV, D1, R2), AI (Workers AI, Vectorize, Agents SDK), networking (Tunnel, Spectrum), security (WAF, DDoS), and infrastructure-as-code (Terraform, Pulumi). Use for any Cloudflare development task.
Create Product Requirements Documents (PRDs) that define the end state of a feature. Use when planning new features, migrations, or refactors. Generates structured PRDs with acceptance criteria.
Detect whether the current project uses jj (Jujutsu) or git for version control. Run this BEFORE any VCS command to use the correct tool.
Convert markdown PRDs to executable JSON format. Use after creating a PRD with the prd skill to generate the prd.json for autonomous task completion.
