CHAN.RUN
A complete route handler with typed error handling
A complete route handler showing defineError, ensure, declares, tryAsync, and exhaustive match working together.
import { defineError, ensure, declares, tryAsync, match } from "@chan.run/ensure";
// Error catalog for the users domain
const UserNotFoundError = defineError("UserNotFoundError");
const DbError = defineError("DbError");
// Declare what getUser can throw — type flows through tryAsync
const getUser = declares([UserNotFoundError, DbError], async (id: string) => {
const row = await db.users.findById(id);
return ensure(row, UserNotFoundError, `No user: ${id}`);
});
export async function handleGetUser(req: Request): Promise<Response> {
const result = await tryAsync(getUser, req.params.id);
if (result.ok) {
return Response.json(result.data);
}
// TypeScript enforces you handle both error types
return match(result.error, [UserNotFoundError, DbError], {
UserNotFoundError: (err) => Response.json({ error: err.message }, { status: 404 }),
DbError: (err) => Response.json({ error: "Service unavailable" }, { status: 503 }),
});
}defineError creates typed error classes with isFault: truedeclares annotates getUser with the errors it can throw — zero runtime costensure guards the nullable database result — throws UserNotFoundError if nulltryAsync(getUser, id) passes the declared function directly, so result.error is typed as UserNotFoundError | DbErrormatch with 3 args is exhaustive — TypeScript errors if you forget a handler