Why Use This
This skill provides specialized capabilities for dmmulroy's codebase.
Use Cases
- Developing new features in the dmmulroy repository
- Refactoring existing code to follow dmmulroy standards
- Understanding and working with dmmulroy's codebase structure
Install Guide
2 steps - 1
- 2
Install inside Ananke
Click Install Skill, paste the link below, then press Install.
https://github.com/dmmulroy/better-result/tree/main/skills/migrations/v2
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Updated At Jan 25, 2026, 08:16 PM
Skill Stats
SKILL.md 249 Lines
Total Files 1
Total Size 6.0 KB
License NOASSERTION
---
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-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}`,
});
```