Result everywhere
Every fallible entry point answers with a value instead of throwing, and what that buys you at the call site.
Last updated 2026-09-21
A function in this collection that can fail returns a
Result rather than throwing. The type is a discriminated union:
type Result<T, E extends Error> = Success<T> | Failure<E>;
interface Success<T> {
status: "success";
data: T;
}
interface Failure<E extends Error> {
status: "failure";
error: E;
}
The compiler will not let you read data before you have handled the failure branch, which is
the whole point: a failure is a case in the signature rather than a control-flow surprise.
Working with one
isSuccess and isFailure narrow it, and early return is the ordinary shape:
import { isFailure } from "@sdxc/result";
let parsed = Markdown.parse(source, { frontmatter: Frontmatter });
if (isFailure(parsed)) return badRequest({ line: parsed.error.position?.start.line });
// parsed.data is typed from here on
match handles both branches in one expression when there is no early exit to take:
import { match } from "@sdxc/result";
let message = match(await fetchUser(id), {
success: (user) => `Hello, ${user.name}!`,
failure: (error) => `Error: ${error.message}`,
});
unwrap takes the value and throws on a failure, optionally through a fallback. It is the
escape hatch, and it belongs where a failure genuinely is a programming error — a test, a
startup check — rather than in a request path.
import { unwrap } from "@sdxc/result";
let tag = unwrap(await etag(body));
let port = unwrap(parsePort(input), () => 3000);
Crossing the boundary
Code outside the collection throws. wrap turns that into a Result so the rest of a function
keeps one shape:
import { isSuccess, wrap } from "@sdxc/result";
let parsed = wrap(() => JSON.parse(input));
if (isSuccess(parsed)) use(parsed.data);
partition splits a batch into what succeeded and what did not, which is what you want when
one failure should not cost you the other nine results:
import { partition } from "@sdxc/result";
let [responses, errors] = partition(await Promise.all(urls.map((url) => wrap(() => fetch(url)))));
Errors carry position
A parse failure names where it happened. A Markdown error carries a position with the line
and column it stopped at; a YAML one carries the line, and says so in its message. Either way
an error points at the input rather than describing it. Where an error wraps another one, the
original is kept as its cause, so a caller that recognizes a specific failure can look for it
without losing everything else.
What still throws
Nothing throws past you from a fallible entry point, but a programming error still throws: a function called with an argument its signature forbids, or a visitor of your own that throws inside a walk. In that last case the walk catches it, attaches the position of the node it was standing on, and hands it back on the failure branch — so even that arrives as a value.