resultkit@0.4.0
λ resultkit --strict
Type-safe errors with pattern matching
and zero escape hatches.
Strict errors
No unknown escape hatches
Every boundary function requires errorFn. If a function can fail, the caller sees the error type in the signature. Always.
Pattern matching
First-class match()
Standalone match() and matchOn() replace switch and if/else chains. Exhaustive at compile time, safe at runtime.
Enforced workflow
Ships an ESLint preset
Bans throw, try/catch, .catch(), .finally(), switch. The discipline isn't optional.
import { ok, err, fromThrowable } from '@valencets/resultkit'
import { ResultError } from '@valencets/resultkit'
// errorFn required — no unknown leaks
const safeParse = fromThrowable(
(s: string) => JSON.parse(s),
() => new ResultError('PARSE', 'invalid json')
)
// Errors short-circuit. Both paths visible in types.
const result = safeParse(input)
.map(data => data.userId)
.andThen(id => findUser(id))
.tap(user => console.log('found:', user.name))
// Object-style match — self-documenting
result.match({
ok: user => renderProfile(user),
err: e => matchOn(e, 'code', {
PARSE: () => show('Bad input'),
NOT_FOUND: () => show('User missing'),
TIMEOUT: () => retry()
})
})
Error types
- fromThrowable(fn) // E = unknown
+ fromThrowable(fn, errorFn) // E typed
Pattern matching
- result.match(okFn, errFn) // only
+ match(status, { OK: ..., ERR: ... })
+ matchOn(err, 'code', { ... })
Escape hatches
- throw / try-catch / .catch() // allowed
+ eslint preset bans all of them
Extras
- // no branded errors, no serialization
+ ResultError<Code> + toJSON / fromJSON
Zero dependencies · ESM only · Node ≥ 22 (oldest LTS with active security support — Node 20 EOL Apr 2026)
import { match, matchOn } from '@valencets/resultkit'
// Exhaustive string-union match
type Status = 'active' | 'banned' | 'pending'
match(status, {
active: () => grantAccess(),
banned: () => denyAccess(),
pending: () => showVerify()
})
// Wildcard — handle some, catch-all the rest
match(status, {
banned: () => denyAccess(),
_: () => grantAccess()
})
// Discriminated union — type narrows per handler
type AppError =
| { code: 'NOT_FOUND'; path: string }
| { code: 'TIMEOUT'; ms: number }
matchOn(error, 'code', {
NOT_FOUND: e => `Missing: ${e.path}`,
TIMEOUT: e => `Waited ${e.ms}ms`
})
import { ResultAsync, combine, partition }
from '@valencets/resultkit'
// Wrap rejectable promises — errorFn required
const fetchUser = (id: number) =>
ResultAsync.fromPromise(
fetch(`/api/users/${id}`).then(r => r.json()),
e => new ResultError('FETCH', String(e))
)
// Same API as sync Result
const name = await fetchUser(1)
.map(u => u.name)
.tap(n => console.log('fetched:', n))
.unwrapOr('Unknown')
// Tuple-aware combine — infers types + unions errors
const both = combine([
fetchUser(1), fetchConfig()
] as const)
// Split batch results into [ok[], err[]]
const [users, errors] = partition(results)
import resultkit from '@valencets/resultkit/eslint'
export default [...resultkit.strict]
export default [...resultkit.strict]
Banned
throw / try-catch
→ err() or fromThrowable()
Banned
.catch() / .finally()
→ ResultAsync.fromPromise()
Banned
switch / enum
→ match() or matchOn()
Warned
.unwrap() / .unwrapErr()
→ prefer .match() or .unwrapOr()
resultkit.opinionated adds a ban on export default (style rule, unrelated to error handling).
Constructors
ok(value) — success Resulterr(error) — failure ResultokAsync(value) — async successerrAsync(error) — async failurefromThrowable(fn, errFn) — wrap throwerfromThrowableAsync(fn, errFn)fromNullable(val, err) — null → ErrResultAsync.fromPromise(p, errFn)ResultAsync.fromSafePromise(p)Transform & terminal
.map(fn) — transform value.mapErr(fn) — transform error.andThen(fn) — chain (flatMap).orElse(fn) — recover from error.tap(fn) — inspect value.tapErr(fn) — inspect error.match(okFn, errFn) — positional.match({ ok, err }) — object style.unwrapOr(default) — safe extract.unwrap() — extract or throw.unwrapErr() — extract error or throw.isOk() / .isErr() — type guards.toAsync() — sync → asyncCombinators & extras
combine(results) — tuple mergecombineAsync(results) — async mergepartition(results) — [ok[], err[]]flatten(result) — unwrap nestedmatch(val, handlers) — string unionmatchOn(obj, key, handlers)ResultError<Code> — branded errorisResultError(val) — type guardresultToJSON(r) — serializeresultFromJSON(j) — deserializeInferOkType<R> — extract TInferErrType<R> — extract E