better-result-migrate-v2

Migrate better-result TaggedError from v1 (class-based) to v2 (factory-based) API

1,002 stars

Best use case

better-result-migrate-v2 is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Migrate better-result TaggedError from v1 (class-based) to v2 (factory-based) API

Teams using better-result-migrate-v2 should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/v2/SKILL.md --create-dirs "https://raw.githubusercontent.com/dmmulroy/better-result/main/skills/migrations/v2/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/v2/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How better-result-migrate-v2 Compares

Feature / Agentbetter-result-migrate-v2Standard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Migrate better-result TaggedError from v1 (class-based) to v2 (factory-based) API

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# better-result-migrate

Migrate `better-result` TaggedError classes from v1 (class-based) to v2 (factory-based) API.

## When to Use

- Upgrading `better-result` from v1 to v2
- User asks to migrate TaggedError classes
- User mentions TaggedError v1/v2 migration

## V1 API (old)

```typescript
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)

```typescript
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)

```typescript
// 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:

```typescript
// 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:

```typescript
// 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

```typescript
// 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

```typescript
// BEFORE
import { TaggedError } from "better-result";

// AFTER
import { TaggedError, matchError, matchErrorPartial, isTaggedError } from "better-result";
```

## Workflow

1. **Find TaggedError classes**: Search for `extends TaggedError` in the codebase
2. **Analyze each class**:
   - Extract `_tag` value
   - Identify constructor params and their types
   - Check for constructor logic (validation, computed message, side effects)
3. **Transform class**:
   - Simple: Remove constructor, add props to type parameter
   - Complex: Keep custom constructor, transform to object args
4. **Update usages**: Change `new FooError(a, b)` to `new FooError({ a, b, message })`
5. **Migrate static methods**: `TaggedError.match` → `matchError`, etc.
6. **Update imports**: Add `matchError`, `matchErrorPartial`, `isTaggedError`

## Example Full Migration

**Input:**

```typescript
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:**

```typescript
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}`,
  });
```

Related Skills

better-result-adopt

1002
from dmmulroy/better-result

Migrate codebase from try/catch or Promise-based error handling to better-result. Use when adopting Result types, converting thrown exceptions to typed errors, or refactoring existing error handling to railway-oriented programming.

framework-migration-code-migrate

31392
from sickn33/antigravity-awesome-skills

You are a code migration expert specializing in transitioning codebases between frameworks, languages, versions, and platforms. Generate comprehensive migration plans, automated migration scripts, and

Code MigrationClaude

better-auth-best-practices

38786
from novuhq/novu

Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.

migrate

9958
from alirezarezvani/claude-skills

Migrate from Cypress or Selenium to Playwright. Use when user mentions "cypress", "selenium", "migrate tests", "convert tests", "switch to playwright", "move from cypress", or "replace selenium".

result-to-claim

5407
from wanshuiyin/Auto-claude-code-research-in-sleep

Use when experiments complete to judge what claims the results support, what they don't, and what evidence is still missing. Codex MCP evaluates results against intended claims and routes to next action (pivot, supplement, or confirm). Use after experiments finish — before writing the paper or running ablations.

analyze-results

5407
from wanshuiyin/Auto-claude-code-research-in-sleep

Analyze ML experiment results, compute statistics, generate comparison tables and insights. Use when user says "analyze results", "compare", or needs to interpret experimental data.

better-auth-best-practices

3940
from latitude-dev/latitude-llm

Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables. Use when users mention Better Auth, betterauth, auth.ts, or need to set up TypeScript authentication with email/password, OAuth, or plugin configuration.

Authentication (Better Auth)

3940
from latitude-dev/latitude-llm

**When to use:** Sessions, sign-in/sign-up flows, OAuth, magic links, or organization context on the session.

tc_migrate

3891
from openclaw/skills

腾讯云跨账号资源迁移工具。将源账号(账号A)的 VPC、CLB、NAT、CVM、安全组等资源迁移到目标账号(账号B),通过 CCN 云联网实现跨账号网络互通。支持自动扫描、配置生成、Terraform 部署。

better-soul

3891
from openclaw/skills

Write powerful SOUL.md files for AI agents. Use when creating, revising, or improving SOUL.md (the personality document for AI agents). Based on Anthropic's Claude soul document principles and SoulSpec standard.

migrate

3891
from openclaw/skills

Migrate from Cypress or Selenium to Playwright. Use when user mentions "cypress", "selenium", "migrate tests", "convert tests", "switch to playwright", "move from cypress", or "replace selenium".

claude-better-cli

3819
from openclaw/skills

Compatibility-first Claude CLI reimplementation with faster startup, lower memory, and drop-in command compatibility