better-result-adopt
Adopt better-result in an existing TypeScript codebase. Use when replacing try/catch, Promise rejection handling, null sentinels, or thrown domain exceptions with typed Result workflows.
Best use case
better-result-adopt is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Adopt better-result in an existing TypeScript codebase. Use when replacing try/catch, Promise rejection handling, null sentinels, or thrown domain exceptions with typed Result workflows.
Teams using better-result-adopt 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/better-result-adopt/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How better-result-adopt Compares
| Feature / Agent | better-result-adopt | 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?
Adopt better-result in an existing TypeScript codebase. Use when replacing try/catch, Promise rejection handling, null sentinels, or thrown domain exceptions with typed Result workflows.
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 Adopt
Adopt `better-result` incrementally in existing codebases without rewriting everything at once.
## When to Use
Use this skill when the user wants to:
- migrate from try/catch to `Result.try` or `Result.tryPromise`
- replace nullable return values with typed `Result<T, E>`
- define domain-specific `TaggedError` types
- refactor nested error handling into `andThen` chains or `Result.gen`
- standardize error handling across a service or module
## Reading Order
| Task | Files to Read |
| -------------------------------------- | ----------------------------- |
| Adopt better-result in a module | This file |
| Define or review error types | `references/tagged-errors.md` |
| Inspect library implementation details | `opensrc/` if present |
## Prerequisites
Before editing code:
1. Confirm `better-result` is already installed in the target project.
2. Check for an `opensrc/` directory. If present, read the package source there for current patterns.
3. Identify the migration scope first: one file, one module, or one boundary layer.
## Migration Strategy
### 1. Start at boundaries
Begin with I/O boundaries and exception-heavy code:
- HTTP clients
- database access
- file system operations
- parsing and validation
- framework adapters
Do not convert the whole codebase at once.
### 2. Follow official Best Practices
Use the official better-result Best Practices as the source of truth:
- Use `Result` for expected failures that are part of normal flow.
- Wrap throwing third-party and generated-client calls with `Result.try` / `Result.tryPromise`.
- Use `TaggedError` for discriminated domain and infrastructure error unions.
- Preserve context on errors with fields such as `message`, `cause`, ids, status, operation, and reason.
- Compose multi-step flows with `Result.gen`; in async generators, use `yield* Result.await(...)`.
- Avoid premature unwrapping. Keep values in the Result context until a framework or serialization boundary.
- Use `Result.isError` / `Result.isOk` type guards or `.match(...)`; both are valid. Choose the clearer option for the call site.
- Use `matchError` when handling a tagged error union exhaustively.
- Never use `Result<T, any>`.
- Do not ignore Result errors. Handle, propagate, or log them intentionally.
- Do not call `.unwrap()` without a preceding type guard or an intentional, documented throw.
- Test both success and error paths.
### 3. Classify existing failures
| Category | Examples | Target shape |
| --------------------- | --------------------------- | -------------------------------------------------------------- |
| Domain errors | not found, validation, auth | `TaggedError` + `Result.err` |
| Infrastructure errors | network, DB, file I/O | `Result.tryPromise` + mapped error |
| Programmer defects | bad assumptions, null deref | leave throwing; defects become `Panic` inside Result callbacks |
### 4. Migrate in this order
1. Define error types.
2. Wrap throwing boundaries with `Result.try` / `Result.tryPromise`.
3. Replace null or boolean sentinel returns with `Result`.
4. Refactor call sites to propagate `Result` values.
5. Collapse nested branching into `andThen`, `mapError`, or `Result.gen`.
## Serialization Boundaries
When a function is called through RPC, server functions, route loaders, or any serialization boundary:
- Use `Result` internally to model and compose failures.
- Do not return `Ok`, `Err`, `TaggedError`, `Error`, `Response`, or other class instances directly.
- Convert the final outcome to a plain serializable object at the boundary.
- Prefer inferred return types unless the framework produces `unknown`/`any` or the function is an explicit public API boundary.
```ts
const result = await Result.gen(async function* () {
const user = yield* Result.await(fetchUser(input));
const session = yield* createSession(user);
return Result.ok(session);
});
if (Result.isError(result)) {
return { error: true as const, message: result.error.message };
}
return { error: false as const, session: result.value };
```
## Core Transformations
### Try/catch → `Result.try`
```ts
function parseConfig(json: string): Result<Config, ParseError> {
return Result.try({
try: () => JSON.parse(json) as Config,
catch: (cause) => new ParseError({ cause, message: `Parse failed: ${cause}` }),
});
}
```
### Async throws → `Result.tryPromise`
```ts
async function fetchUser(id: string): Promise<Result<User, ApiError | UnhandledException>> {
return Result.tryPromise({
try: async () => {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new ApiError({ status: res.status, message: `API ${res.status}` });
return res.json() as Promise<User>;
},
catch: (cause) => (cause instanceof ApiError ? cause : new UnhandledException({ cause })),
});
}
```
### Null sentinel → `Result`
```ts
function findUser(id: string): Result<User, NotFoundError> {
const user = users.find((candidate) => candidate.id === id);
return user
? Result.ok(user)
: Result.err(new NotFoundError({ id, message: `User ${id} not found` }));
}
```
### Nested flow → `Result.gen`
```ts
async function processOrder(orderId: string): Promise<Result<OrderResult, OrderError>> {
return Result.gen(async function* () {
const order = yield* Result.await(fetchOrder(orderId));
const validated = yield* validateOrder(order);
const result = yield* Result.await(submitOrder(validated));
return Result.ok(result);
});
}
```
## Execution Workflow
1. Audit the target module for `try`, `catch`, `.catch(...)`, `throw`, `null`, `undefined`, and status-flag error handling.
2. Define or update `TaggedError` classes before changing control flow.
3. Convert boundary functions first and change their signatures to `Result<T, E>` or `Promise<Result<T, E>>`.
4. Update immediate callers so they handle or propagate the new `Result`.
5. Where multiple Result-returning steps compose, use `Result.gen` or `andThen`.
6. Preserve error context by keeping `cause`, IDs, messages, and other structured fields.
7. Run tests and add coverage for both success and error paths.
## Completion Criteria
A migration is complete when:
- target functions no longer rely on try/catch for expected domain failures
- nullable or sentinel error returns are replaced with explicit `Result` values
- domain failures use typed `TaggedError` classes
- callers either propagate `Result` or explicitly unwrap/match it
- serialization boundaries return plain objects, not Result or Error instances
- tests cover at least one success path and one representative error path
## Common Pitfalls
- Over-wrapping everything instead of starting at boundaries
- Losing original failure context when mapping errors
- Mixing `throw`-based and `Result`-based APIs deep in the same flow
- Catching `Panic` instead of fixing the underlying defect
- Returning Result or TaggedError instances from server functions or RPC handlers that require serialization
- Treating `.match(...)` as mandatory when a `Result.isError` / `Result.isOk` type guard is clearer
## In This Reference
| File | Purpose |
| ----------------------------- | --------------------------------------------------------- |
| `references/tagged-errors.md` | TaggedError patterns, matching, type guards, and examples |
If `opensrc/` exists, treat it as the source of truth for implementation details and current API behavior.Related Skills
test
Advanced test implementation command with unit/E2E support, auto-execution, and smart fixing capabilities
serena
Token-efficient Serena MCP command for structured app development and problem-solving
project-guidelines-example
Example project-specific skill template based on a real production application.
notion-bug-pr
Skill that pulls bug tickets (titles containing 「不具合」) from a Notion database, investigates root cause in a GitHub repo, applies fixes, and opens draft PRs. Supports three modes — daily recurring schedule, one-shot at a specific time, or immediate on-demand run. Takes three or four args: Notion database URL, repo path or name, and either HH:MM (daily), "once HH:MM" (one-shot), or "now" (immediate).
graphify
any input (code, docs, papers, images, videos) to knowledge graph. Use when user asks any question about a codebase, documents, or project content - especially if graphify-out/ exists, treat the question as a /graphify query.
chrome
Comprehensive Chrome DevTools development system with native Chrome capabilities for debugging, E2E testing, performance analysis, and browser automation
webapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
web-design-guidelines
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
typescript-advanced-types
Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects.
tanstack-start
Full-stack React framework powered by TanStack Router with SSR, streaming, server functions, and deployment to any hosting provider.
tanstack-start-server-fn-testing
Unit-test TanStack Start createServerFn handlers via a global vi.mock that combines two patterns from Discussion #2701
tailwind-css-patterns
Provides comprehensive Tailwind CSS utility-first styling patterns including responsive design, layout utilities, flexbox, grid, spacing, typography, colors, and modern CSS best practices. Use when styling React/Vue/Svelte components, building responsive layouts, implementing design systems, or optimizing CSS workflow.