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.
Migrate existing error handling (try/catch, Promise rejections, thrown exceptions) to typed Result-based error handling with better-result.
When to Use
Adopting better-result in existing codebase
Converting try/catch blocks to Result types
Replacing thrown exceptions with typed errors
Migrating Promise-based code to Result.tryPromise
Introducing railway-oriented programming patterns
Migration Strategy
1. Start at Boundaries
Begin migration at I/O boundaries (API calls, DB queries, file ops) and work inward. Don't attempt full-codebase migration at once.
2. Identify Error Categories
Before migrating, categorize errors in target code:
Category
Example
Migration Target
Domain errors
NotFound, Validation
TaggedError + Result.err
Infrastructure
Network, DB connection
Result.tryPromise + TaggedError
Bugs/defects
null deref, type error
Let throw (becomes Panic if in Result callback)
3. Migration Order
Define TaggedError classes for domain errors
Wrap throwing functions with Result.try/tryPromise
Convert imperative error checks to Result chains
Refactor callbacks to generator composition
Pattern Transformations
Try/Catch to Result.try
// BEFORE
function parseConfig(json: string): Config {
try {
return JSON.parse(json);
} catch (e) {
throw new ParseError(e);
}
}
// AFTER
function parseConfig(json: string): Result<Config, ParseError> {
return Result.try({
try: () => JSON.parse(json) as Config,
catch: (e) => new ParseError({ cause: e, message: `Parse failed: ${e}` }),
});
}
Async/Await to Result.tryPromise
// BEFORE
async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new ApiError(res.status);
return res.json();
}
// AFTER
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: (e) => (e instanceof ApiError ? e : new UnhandledException({ cause: e })),
});
}
Null Checks to Result
// BEFORE
function findUser(id: string): User | null {
return users.find((u) => u.id === id) ?? null;
}
// Caller must check: if (user === null) ...
// AFTER
function findUser(id: string): Result<User, NotFoundError> {
const user = users.find((u) => u.id === id);
return user
? Result.ok(user)
: Result.err(new NotFoundError({ id, message: `User ${id} not found` }));
}
// Caller: yield* findUser(id) in Result.gen, or .match()
Callback Hell to Generator
// BEFORE
async function processOrder(orderId: string) {
try {
const order = await fetchOrder(orderId);
if (!order) throw new NotFoundError(orderId);
const validated = validateOrder(order);
if (!validated.ok) throw new ValidationError(validated.errors);
const result = await submitOrder(validated.data);
return result;
} catch (e) {
if (e instanceof NotFoundError) return { error: "not_found" };
if (e instanceof ValidationError) return { error: "invalid" };
throw e;
}
}
// AFTER
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);
});
}
// Error type is union of all yielded errors
Ask your AI agent questions about libraries and frameworks by searching the actual source code, not outdated docs.
[!WARNING]
btca is being rewritten right now. The new version will overwrite the current one with full backwards compatibility, while fixing a ton of issues and massively improving performance. It is being rebuilt around pi agent and node to improve the Windows experience.
A note on this project
I've been testing a ton, and I mean a ton of stuff for how to make the local experience better.
Custom pi agent
New cli
A pi extension
A complex resource management cli
And as I've done all of these things I've come to realize, that the local version of this probably just should have been a skill.
So at least for now, it is. I know that sounds dumb, but just trust me the experience is great. I highly recommend doing this with pi or codex:
getting started
install the skill: npx skills add https://github.com/davis7dotsh/better-context --skill btca-local
three ways to use it:
during any prompt simply say "use btca" and your coding agent will now clone/search important repos in ~/.btca/agent/sandbox, improving your output quality
invoke the skill from your coding agent with "/btc..." or "$btc..." (depends on your agent) and it will feel like the old tui
invoke the skill the same way as above, but this time include a question/prompt. basically the same thing as btca ask
the future of this project
the web app will continue to be expanded and supported. I can do things there I can't do locally, and I don't want to make that experience worse
the local version might get revisited in the future. it being a "subagent" for search was actually quite nice. main issue with the new system is it clogs the main context window...