better-result-migrate-v2
Migrate better-result TaggedError from v1 (class-based) to v2 (factory-based) API
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
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/v2/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How better-result-migrate-v2 Compares
| Feature / Agent | better-result-migrate-v2 | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/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
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
You are a code migration expert specializing in transitioning codebases between frameworks, languages, versions, and platforms. Generate comprehensive migration plans, automated migration scripts, and
better-auth-best-practices
Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.
migrate
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
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
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
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)
**When to use:** Sessions, sign-in/sign-up flows, OAuth, magic links, or organization context on the session.
tc_migrate
腾讯云跨账号资源迁移工具。将源账号(账号A)的 VPC、CLB、NAT、CVM、安全组等资源迁移到目标账号(账号B),通过 CCN 云联网实现跨账号网络互通。支持自动扫描、配置生成、Terraform 部署。
better-soul
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
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
Compatibility-first Claude CLI reimplementation with faster startup, lower memory, and drop-in command compatibility