flat-result
v0.1.0
Published
Result<T, E> as plain data: a structurally-typed success-or-failure value that survives structuredClone, postMessage and JSON
Maintainers
Readme
flat-result
Result<T, E> as plain data — a success-or-failure value that is an object literal, not a
class instance:
{ ok: true, value: 42, error: undefined }
{ ok: false, value: undefined, error: someFailure }That choice is the whole library. Because a Result has no prototype and no methods, it survives
structuredClone, postMessage, JSON.stringify and a WebSocket hop unchanged — where a
class-based Result (neverthrow, true-myth, Effect) arrives on the other side as a shapeless
object with its methods gone. And because the discriminant is a boolean literal, it narrows under
if, under destructuring and in switch, with no as and no assertion function.
Hence flat: there is no method chain to enter and no wrapper to unwrap before you can look at
the value. Error handling stays an if on a plain object.
Status: early. The value half (
Result,Result.try) is here and tested. The structured failure taxonomy (Failure.define) andTask— a lazy, retryable Promise superset whose.result()settles to exactly thisResult— are being extracted from qunitx-cli and land in a later release. The API below is stable in shape; treat pre-1.0 versions as movable.
Install
npm install flat-resultUsage
import * as Result from 'flat-result';
const parsePort = (raw: string): Result.Result<number, string> =>
/^\d+$/.test(raw) ? Result.ok(Number(raw)) : Result.err(`not a port: ${raw}`);
const port = parsePort('8080');
if (port.ok) {
port.value; // narrowed to number
} else {
port.error; // narrowed to string
}Named imports work identically — neither style is a second-class citizen:
import { ok, err, unwrapOr, type Result } from 'flat-result';Result.try — the throw boundary
Result.try(fn, ...args) has Promise.try's shape: it calls fn(...args) now and reflects
the outcome. A sync source gives you a Result synchronously; a source returning a thenable gives
you a Promise<Result> that never rejects.
const parsed = Result.try(JSON.parse, raw);
if (!parsed.ok && !(parsed.error instanceof SyntaxError)) throw parsed.error; // a bug stays a bugThat visible rethrow line is the entire mechanism for separating an expected failure from a
bug: Result.try boxes every throw because it is the raw edge, and the call site — flat, where
a reader can see it — decides what was actually declared.
Since the returned promise never rejects, Promise.all stops being fail-fast:
const results = await Promise.all(files.map((f) => Result.try(() => readFile(f, 'utf8'))));
const { values, errors } = Result.partition(results); // successes kept, failures keptAPI
| | |
| ---------------------------- | --------------------------------------------------------------------------- |
| ok() / ok(value) | Success. The no-argument form returns a shared frozen singleton. |
| err(error) | Failure. Same key order as ok(), so result.ok stays a monomorphic load. |
| isResult(value) | Structural check for Results arriving from outside the program. |
| unwrap(result) | The value, or throws the failure — an Error rethrown by identity. |
| expect(result, message) | The value, or throws new Error(message, { cause: error }). |
| unwrapOr(result, fallback) | The value, or fallback. |
| all(results) | Result<T[], E>, short-circuiting on the first failure. |
| partition(results) | { values, errors } — keeps both halves. |
| Result.try(fn, ...args) | Reflects a call into a Result. Also exported as attempt. |
| isErrno(value, ...codes) | Whether a value is an Error with one of those Node codes. |
Types: Result<T, E = unknown>, Ok<T>, Err<E>, ErrnoError.
What is deliberately absent
There is no map / mapErr / andThen / match, and no isOk / isErr. A settled Result is
branched on with an if, which reads better and allocates nothing. Combinators earn their keep
only when the value is not here yet — that is Task's job, not this one.
E defaults to unknown, not Error: an un-narrowed error is exactly as untrustworthy as a
catch binding, and the type should say so at the use site.
License
MIT © Izel Nakri
