ts-results

A TypeScript implementation of Rust's Result and Option types for explicit, compile-time-checked error handling.

Library
npm
v3.3.0
1,407stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
37/100Needs Attention
Development Activity0
Maintenance0
Community48
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
62/100Good
Architecture78
Code Quality68
Innovation62
Learning Curve40

ts-results ports Rust’s Result<T, E> and Option<T> types to TypeScript, turning functions that might fail or return nothing into values the type checker forces you to handle. Instead of throwing exceptions callers can silently miss, or returning T | undefined that gets forgotten downstream, functions return an Ok/Err or Some/None wrapper whose .ok/.some discriminant narrows the type at each branch.

The library is a small, dependency-free set of two classes per type (OkImpl/ErrImpl, SomeImpl/NoneImpl) exposing a fluent chain of Rust-style methods — map, mapErr, andThen, unwrapOr, expect — plus static helpers like Result.all, Result.any, and Result.wrap for combining or adapting existing throwing code. An optional ts-results/rxjs-operators subpath adds operators (resultMap, elseMap, resultSwitchMap, and others) for working with streams of Result/Option values without pulling rxjs into projects that don’t use it.

What You Get

  • Result<T, E> type - Ok/Err wrapper classes with a .ok/.err discriminant that TypeScript narrows automatically in if branches.
  • Option<T> type - Some/None wrapper (with None as a frozen singleton) replacing T | undefined for optional values.
  • Fluent combinators - map, mapErr, andThen, unwrapOr, expect, expectErr, and toOption()/toResult() conversions between the two types.
  • Static aggregation helpers - Result.all/Result.any and Option.all/Option.any for combining multiple Results/Options into one.
  • Result.wrap/Result.wrapAsync - adapters that catch a throwing (sync or async) function and return a Result instead.
  • Optional rxjs operators - a separate ts-results/rxjs-operators entry point (resultMap, resultMapErr, elseMap, resultSwitchMap, resultMergeMap, filterResultOk, filterResultErr) for streams of Result/Option values, isolated so the core package has zero dependencies.

Common Use Cases

  • Wrapping fallible file/network/parsing calls - a function that reads a file or parses input returns Result<string, 'invalid path'> instead of throwing, forcing every call site to check .ok before touching the value.
  • Replacing T | undefined return types - functions like getLoggedInUsername() return Option<string> so .andThen() chains replace manual null checks.
  • Composing multiple fallible steps - Result.all(pizzaResult, toppingsResult) short-circuits to the first error while giving back all Ok values as a tuple when every step succeeds.
  • Adapting existing throwing APIs - Result.wrap(() => JSON.parse(input)) turns a try/catch-only API into a typed Result without rewriting the underlying call.
  • Reactive pipelines over fallible streams - the rxjs operators let an Observable<Result<T, E>> be mapped, filtered, or switched on its Ok/Err state without unwrapping manually at every step.

Under The Hood

Architecture The library is tiny and flat — three source files (result.ts, option.ts, utils.ts) re-exported through index.ts, plus a separately packaged rxjs-operators submodule with its own package.json so it resolves as its own subpath and doesn’t force rxjs on consumers who don’t use it. Both Result and Option follow the same shape: a pair of implementation classes (OkImpl/ErrImpl, SomeImpl/NoneImpl) implementing a shared BaseResult/BaseOption interface, with the exported Ok/Err/Some bindings cast to a combined class-and-function type so they’re callable both as new Ok(x) and Ok(x). None is a single frozen singleton rather than a class instantiated per use, avoiding allocation for the empty case. There’s no DI or plugin surface — it’s a closed pair of data types with fluent instance methods and static namespace helpers (Result.all, Result.any, Result.wrap), and any change to the shared base interface would have to propagate through both Impl classes and the rxjs operator functions that pattern-match on the .ok/.some flags.

Tech Stack Pure TypeScript with zero runtime dependencies in the core package, compiled to an ES5 target (needed for the callable-class trick used by Ok/Err/Some). Dev tooling is jest ^26 with ts-jest for tests, prettier ^2 for formatting, TypeScript ^4.2, and copyfiles to assemble the publish output — the build runs tsc twice (once for CommonJS, once with -m esnext for an ESM build into dist/esm/) and copies README.md/LICENSE/per-subpackage package.json files into dist afterward. rxjs ^6.6 is a dev dependency used only to build and test the optional operators submodule; no bundler is used, just plain tsc output.

Code Quality Test coverage is reasonable for the surface area — result.test.ts, ok.test.ts, err.test.ts, and option.test.ts cover map/andThen/mapErr/unwrap/expect/toOption for each variant, and a separate rxjs.test.ts exercises every rxjs operator, all run under jest/ts-jest. Error handling is largely the point of the library rather than a separate concern — though unwrap()/expect() intentionally still throw when misused, and ErrImpl captures a trimmed stack trace at construction for debugging. Naming follows Rust conventions (unwrap, expect, mapErr, andThen) rather than idiomatic JS, a deliberate tradeoff for developers porting Rust patterns, and a code comment flags a known TypeScript narrowing limitation (microsoft/TypeScript#10564) that requires strictNullChecks. No CI configuration is present in the repository and no linter is configured beyond prettier for formatting.

API Design The public API deliberately mirrors Rust’s Result/Option vocabulary (ok/err/some/none flags, unwrap, expect, map, mapErr, andThen, unwrapOr) so developers already comfortable with Rust’s error handling can adopt it with minimal relearning, and the exported Ok/Err/Some helpers work both as constructors and as plain functions to cut call-site boilerplate. The README documents nearly every method with before/after examples contrasting thrown-exception code against the Result-based equivalent, but the surface intentionally omits several Rust Result methods (contains, map_or, or_else, unwrap_or_else, and others, tracked as a comment in result.ts), so consumers expecting full Rust parity will hit gaps. Keeping the rxjs operators behind a separate subpath is a sensible ergonomic choice that keeps the core package dependency-free. Overall this is a faithful, well-executed port of an established pattern rather than a novel API design.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search