Tagged Result / ResultAsync + railway-oriented utilities for TypeScript. Pure tagged unions, neverthrow-shaped compat shim, FL-friendly. Bun-first.
| Package | npm | Description |
|---|---|---|
@onrails/result |
Core Result / ResultAsync + neverthrow compat shim |
|
@onrails/maybe |
Tagged Maybe for expected absence + Result interop |
|
@onrails/pattern |
Exhaustive matching for owned unions (ts-pattern-shaped, lighter) | |
@onrails/codemod |
Bun script: migrate neverthrow imports + package.json deps to @onrails/result/compat/neverthrow |
|
@onrails/eslint-plugin |
ESLint rules for @onrails/result boundaries — flags Promise<Result<…>> + _unsafeUnwrap* |
|
@onrails/biome-plugin |
GritQL plugin: same boundary rules for Biome users |
📚 API reference: alanrsoares.github.io/onrails — generated from tsdoc on every push to main.
Railway-oriented programming — encode failure as values, chain ok/err down two parallel tracks, never throw across a public boundary.
@onrails/result is a small, tagged-union take on this pattern. The data is { _tag: "Ok"; value } / { _tag: "Err"; error } — no class wrapper, tree-shake friendly, Fantasy Land-aware. @onrails/maybe models expected absence (Some / None). @onrails/pattern exhaustively matches owned unions. A drop-in compat/neverthrow shim makes migration a regex search-and-replace.
bun add @onrails/result
import { err, flatMap, isErr, isOk, map, match, ok, trySync } from "@onrails/result";
const parse = trySync(
(raw: string) => JSON.parse(raw) as { v: number },
(e) => ({ kind: "parse" as const, message: String(e) }),
);
const out = map(parse('{"v":1}'), (data) => data.v + 1);
if (isOk(out)) console.log(out.value);
else console.error(out.error);
Async, with a thenable ResultAsync:
import { fromAsync, isOk, ok, err } from "@onrails/result";
const fetchUser = fromAsync(async () => {
const res = await fetch("/api/user");
return res.ok ? ok(await res.json()) : err({ kind: "http" as const, status: res.status });
});
const r = await fetchUser(); // bare tagged Result
if (isOk(r)) console.log(r.value);
For composition patterns (pipe, flow, dual-form transforms, point-free pipelines) see packages/result/RECIPES.md.
bunx @onrails/codemod /path/to/your-repo --dry
bunx @onrails/codemod /path/to/your-repo
See packages/result/README.md for the compat surface and chain-by-chain mapping.
Experimental. Versions stay in 0.x until the public API + compat surface settle. Released and tagged per-package via release-please.
MIT — see LICENSE.