@mikkurogue/nothrow
v0.0.1
Published
Lightweight Result-based error handling for TypeScript.
Maintainers
Readme
nothrow
Lightweight Result-based error handling for TypeScript, with strong tagged-error ergonomics for both sync and async flows.
Why nothrow
- Model failures as data (
Err) instead of exceptions. - Compose operations with
map,andThen,catchTag, andcatchTags. - Use one mental model for sync and async code.
- Keep error handling explicit and type-driven.
Install
pnpm add @mikkurogue/nothrowESM-only
@mikkurogue/nothrow is published as ESM-only.
- Use
import/exportsyntax. - For Node.js projects, set
"type": "module"in yourpackage.json. - CommonJS (
require) is not supported.
If you are in a CommonJS codebase, you can still consume @mikkurogue/nothrow via dynamic import:
const { Result } = await import('@mikkurogue/nothrow');Quick Start
import { Result, err, ok } from '@mikkurogue/nothrow';
const parsePort = (input: string) =>
Result.try(() => {
const value = Number(input);
if (!Number.isInteger(value) || value <= 0) {
return err({ _tag: 'InvalidPort', input });
}
return ok(value);
});
const out = parsePort('3000')
.map((port) => port + 1)
.catchTag('InvalidPort', () => ok(8080))
.run();
console.log(out); // 3001API Overview
Top-level helpers:
ok,err,isOk,isErrmap,mapErr,andThen,match,unwrapOrfromThrowablehasTag,hasTagstaggedError,TaggedErrortry,tryAsync
Chain APIs:
SyncResultChain:map,mapErr,andThen,catchAll,catchTag,catchTags,tapTag,toResult,run,unwrapOr,matchAsyncResultChain:map,mapErr,andThen,catchAll,catchTag,catchTags,tapTag,toPromise,run,unwrapOr,match
Tagged Errors
Prefer TaggedError/taggedError for application and library boundaries.
- They are real
Errorinstances and work with logging/tracing tools. - They carry typed
_tagdiscriminants forcatchTag/catchTagsflows. - They can extend your own domain error classes when needed.
Plain object errors are also supported and remain useful for lightweight internal pipelines.
import { Result } from '@mikkurogue/nothrow';
const NotFound = Result.taggedError('NotFound')<{ id: string }>();
const loadUser = (id: string) =>
Result.try(() => {
if (id === '0') {
return Result.err(new NotFound({ id, message: 'User not found' }));
}
return Result.ok({ id, name: 'Ada' });
});
const user = loadUser('0')
.catchTag('NotFound', (e) => Result.ok({ id: e.id, name: 'Guest' }))
.run();Generator style (yield*)
Result.try and Result.tryAsync support generator-based composition. This gives you early-exit behavior with linear, imperative-looking code.
import { Result, err, ok } from '@mikkurogue/nothrow';
const readPort = (value: string) =>
Result.try(() => {
const parsed = Number(value);
if (!Number.isInteger(parsed)) {
return err({ _tag: 'InvalidPort', value });
}
return ok(parsed);
});
const normalizePort = (raw: string) =>
Result.try(function* () {
const port = yield* readPort(raw);
if (port < 1 || port > 65535) {
return err({ _tag: 'PortOutOfRange', port });
}
return ok(port);
});Async generators can mix sync and async chains in the same flow:
import { Result, ok } from '@mikkurogue/nothrow';
const loadConfig = () => Result.tryAsync(async () => ok({ retry: 2 }));
const readEnv = () => Result.try(() => ok('prod'));
const buildRuntime = Result.tryAsync(function* () {
const config = yield* loadConfig();
const env = yield* readEnv();
return ok({ env, retry: config.retry });
});Generator gotchas:
- Use
yield*withResult.try(...)/Result.tryAsync(...)chains. Plainyieldis not the intended API. - In
Result.try(sync), yielding async values throws aTypeErrorby design. - In
Result.tryAsync, both sync and async chains are supported. - Throwing inside the generator is captured and converted to
Err.
Development
This repo uses Vite+ (vp) for local tooling.
vp install
vp test
vp check
vp run buildSafety Notes
SyncResultChain.run()andSyncResultChain.valueare intended for chains where the error type isnever.- That guarantee is type-level: if you force-cast types, runtime failures are still possible.
- Prefer
match,unwrapOr, ortoResult/toPromisewhen you are not fully eliminating errors.
API Stability
- Current API is pre-1.0 (
0.x), so minor versions may include breaking changes. - Core constructors and chain combinators are intended to remain stable as the library matures.
Cookbook
Recipe-driven examples live in docs/cookbook.md.
