ok<T>(value: T): Ok<T, never>
Creates a success Result containing value. The error type defaults to never since an Ok can't hold an error.
- The returned object has a
.value property holding your value.
.isOk() returns true, .isErr() returns false.
- Accepts any value including
null, undefined, 0, and ''.
const result = ok(42) // Ok<number, never>
const user = ok({ name: 'alice' }) // Ok<{ name: string }, never>
err<E>(error: E): Err<never, E>
Creates a failure Result containing error. The success type defaults to never since an Err can't hold a value.
- The returned object has an
.error property holding your error.
.isOk() returns false, .isErr() returns true.
- The error can be any type — string, object,
ResultError, etc.
const result = err('not found')
const typed = err(new ResultError('TIMEOUT', 'request timed out'))
okAsync<T>(value: T): ResultAsync<T, never>
Creates an async success. Equivalent to ok(value).toAsync() but more concise. Useful when a function returns ResultAsync and you need to return a known-good value.
const result = okAsync(42)
const value = await result // Ok<number, never>
errAsync<E>(error: E): ResultAsync<never, E>
Creates an async failure. Equivalent to err(error).toAsync(). Useful when a function returns ResultAsync and you need to return a known error.
const result = errAsync({ code: 'TIMEOUT', ms: 5000 })
fromThrowable<A, R, E>(fn: (...args: A) => R, errorFn: (e: unknown) => E): (...args: A) => Result<R, E>
Wraps a function that might throw into one that returns a Result. The errorFn parameter is required — this is how resultkit enforces typed errors at every boundary. If fn succeeds, you get Ok. If it throws, the caught error is passed to errorFn and you get Err.
- Returns a new function with the same arguments as
fn, but returning Result<R, E> instead of R.
errorFn receives unknown because JavaScript can throw anything. You decide how to type it.
- This is the only place
try/catch should exist in your codebase.
const safeParse = fromThrowable(
(s: string) => JSON.parse(s),
(e) => new ResultError('PARSE', e instanceof Error ? e.message : 'unknown')
)
const result = safeParse('{"valid": true}') // Ok
const bad = safeParse('nope') // Err
fromThrowableAsync<A, R, E>(fn: (...args: A) => Promise<R>, errorFn: (e: unknown) => E): (...args: A) => ResultAsync<R, E>
Same as fromThrowable but for async functions. Wraps a function that returns a Promise into one that returns ResultAsync. Catches both synchronous throws and promise rejections.
const safeFetch = fromThrowableAsync(
async (url: string) => {
const r = await fetch(url)
return r.json()
},
(e) => new ResultError('FETCH', String(e))
)
const result = await safeFetch('/api/users')
fromNullable<T, E>(value: T | null | undefined, error: E): Result<T, E>
Converts a nullable value into a Result. If value is null or undefined, returns Err(error). Otherwise returns Ok(value).
- Uses strict equality —
0, '', and false are treated as valid values and return Ok.
- Only
null and undefined produce Err.
fromNullable(user.email, 'no email') // Ok('alice@...') or Err('no email')
fromNullable(0, 'missing') // Ok(0) — not Err
fromNullable(null, 'missing') // Err('missing')
ResultAsync.fromPromise<T, E>(promise: PromiseLike<T>, errorFn: (e: unknown) => E): ResultAsync<T, E>
Wraps a promise that might reject into a ResultAsync. If the promise resolves, you get Ok. If it rejects, the rejection value is passed to errorFn and you get Err.
- This is the primary way to convert promise-based APIs into the Result pattern.
errorFn is required — same philosophy as fromThrowable.
const result = ResultAsync.fromPromise(
fetch('/api/data').then(r => r.json()),
(e) => ({ code: 'NETWORK', message: String(e) })
)
ResultAsync.fromSafePromise<T>(promise: PromiseLike<T>): ResultAsync<T, never>
Wraps a promise that you guarantee will never reject. No errorFn needed because there's no rejection to handle. If the promise does reject, the rejection propagates as an unhandled rejection.
- Use only when you are certain the promise cannot reject.
- If unsure, use
ResultAsync.fromPromise instead.
const config = ResultAsync.fromSafePromise(
readFile('./config.json', 'utf-8')
)
.isOk(): boolean / .isErr(): boolean
Type-narrowing guards. After calling .isOk() in an if block, TypeScript knows you have an Ok and can access .value. After .isErr(), you can access .error.
const result: Result<number, string> = divide(10, 2)
if (result.isOk()) {
console.log(result.value) // TypeScript knows this is number
}
if (result.isErr()) {
console.log(result.error) // TypeScript knows this is string
}
.map<U>(fn: (value: T) => U): Result<U, E>
Transforms the success value using fn. If the Result is an Err, the function is not called and the Err passes through unchanged.
- On
Ok: calls fn(value) and wraps the return in a new Ok.
- On
Err: returns the same Err instance (no allocation).
- On
ResultAsync: fn can return U or Promise<U>. If fn throws, the error is caught and returned as Err.
ok(5).map(n => n * 2) // Ok(10)
err('bad').map(n => n * 2) // Err('bad') — fn not called
ok('hello').map(s => s.length) // Ok(5)
.mapErr<F>(fn: (error: E) => F): Result<T, F>
Transforms the error value using fn. If the Result is an Ok, the function is not called and the Ok passes through unchanged.
- On
Err: calls fn(error) and wraps the return in a new Err.
- On
Ok: returns the same Ok instance (no allocation).
err('bad').mapErr(e => e.toUpperCase()) // Err('BAD')
ok(5).mapErr(e => e.toUpperCase()) // Ok(5) — fn not called
.andThen<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F>
Chains into another Result-returning function. This is flatMap — use it when the transformation itself can fail. The error type becomes E | F because either the original error or the new error could occur.
- On
Ok: calls fn(value) which must return a Result.
- On
Err: returns the same Err (short-circuits).
- On
ResultAsync: fn can return Result or ResultAsync.
function divide(a: number, b: number): Result<number, 'DIV_ZERO'> {
return b === 0 ? err('DIV_ZERO') : ok(a / b)
}
ok(10).andThen(n => divide(n, 2)) // Ok(5)
ok(10).andThen(n => divide(n, 0)) // Err('DIV_ZERO')
err('bad').andThen(n => ok(n)) // Err('bad') — fn not called
.orElse<U, F>(fn: (error: E) => Result<U, F>): Result<T | U, F>
Attempts to recover from an error. The inverse of .andThen(). If the Result is Err, calls fn with the error to produce a new Result. If Ok, passes through unchanged.
err('bad').orElse(() => ok(0)) // Ok(0) — recovered
ok(5).orElse(() => ok(0)) // Ok(5) — fn not called
err('bad').orElse(() => err('worse')) // Err('worse')
.match<A, B>(okFn, errFn): A | B or .match({ ok, err }): A | B
Exhaustively handles both paths and returns a value. Two calling styles: positional arguments or an object with ok and err keys. Both are equivalent.
- On
Ok: calls the ok handler with the value.
- On
Err: calls the err handler with the error.
- On
ResultAsync: returns Promise<A | B>.
- You must handle both paths — this is how you "exit" the Result chain.
// Positional style
result.match(
value => `got ${value}`,
error => `failed: ${error}`
)
// Object style — more readable in complex cases
result.match({
ok: value => renderSuccess(value),
err: error => renderError(error)
})
.tap(fn: (value: T) => void): this / .tapErr(fn: (error: E) => void): this
Inspect the value or error without transforming it. The function's return value is ignored and the original Result is returned unchanged. Use for logging, metrics, or other side effects mid-chain.
.tap(fn): calls fn only on Ok. Returns the same Result.
.tapErr(fn): calls fn only on Err. Returns the same Result.
- On sync Results, returns the exact same object reference (zero allocation).
ok(5)
.tap(v => console.log('value:', v)) // logs "value: 5"
.map(v => v * 2) // Ok(10)
err('bad')
.tapErr(e => console.error('error:', e)) // logs "error: bad"
.orElse(() => ok(0)) // Ok(0)
.unwrapOr<A>(defaultValue: A): T | A
Extracts the success value, or returns defaultValue if the Result is an Err. This is a safe way to exit a Result chain when you have a sensible default.
ok(5).unwrapOr(0) // 5
err('bad').unwrapOr(0) // 0
.unwrap(): T / .unwrapErr(): E
Unsafe extraction. .unwrap() returns the value if Ok, or throws if Err. .unwrapErr() returns the error if Err, or throws if Ok. Both include the original value/error as Error.cause for debugging. .unwrap() additionally appends string errors to the message.
- Primarily intended for tests where you've already asserted the Result state.
- The eslint preset will flag these in production code.
- On
ResultAsync: returns Promise<T> / Promise<E> and rejects on the wrong variant.
ok(5).unwrap() // 5
err('bad').unwrap() // throws Error('Called unwrap on an Err value: bad')
// cause: 'bad'
err('bad').unwrapErr() // 'bad'
ok(5).unwrapErr() // throws Error('Called unwrapErr on an Ok value')
// cause: 5
.toAsync(): ResultAsync<T, E>
Converts a sync Result into a ResultAsync. Use when you need to chain a sync Result into an async pipeline.
const sync = ok(5)
const async = sync.toAsync() // ResultAsync<number, never>
// Now you can chain with async operations
const result = await sync.toAsync()
.andThen(n => fetchUser(n))
match<V extends string>(value: V, handlers: { [K in V]: (value: K) => R }): R
Pattern match on a string union type. Pass a value and an object of handlers — one per variant. TypeScript enforces that every variant has a handler (exhaustive) unless you include a _ wildcard.
- Exhaustive mode: provide a handler for every variant. Missing one is a compile error.
- Wildcard mode: include a
_ key to catch any unhandled variants.
- Each handler receives the matched value as its argument.
- Throws at runtime if a value has no matching handler and no wildcard.
type Color = 'red' | 'green' | 'blue'
// Exhaustive — all variants required
match(color, {
red: () => '#ff0000',
green: () => '#00ff00',
blue: () => '#0000ff'
})
// Wildcard — handle some, catch-all the rest
match(color, {
red: () => 'danger',
_: () => 'safe'
})
matchOn<T, K>(obj: T, discriminant: K, handlers: { ... }): R
Pattern match on a discriminated union using a key. Pass an object, the name of its discriminant property, and handlers for each variant. TypeScript narrows the type inside each handler so you get access to variant-specific properties.
- Supports exhaustive and wildcard modes, same as
match().
- Each handler receives the full object narrowed to the matching variant.
- Works with
ResultError<Code> — use 'code' as the discriminant.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rect'; w: number; h: number }
matchOn(shape, 'kind', {
circle: s => Math.PI * s.radius ** 2, // s: { kind, radius }
rect: s => s.w * s.h // s: { kind, w, h }
})
combine(results: readonly [...Results]): Result<[T1, T2, ...], E1 | E2 | ...>
Merges an array of Results into a single Result containing a tuple of all success values. If any Result is Err, returns the first Err encountered. The type system infers tuple types and unions all error types.
- Use
as const on the array to get tuple inference instead of T[].
- Returns
Ok([]) for an empty array.
- Short-circuits on the first
Err — does not check remaining results.
combine([ok(1), ok('hello')] as const)
// Ok<[number, string], never> → Ok([1, 'hello'])
combine([ok(1), err('bad'), ok(3)])
// Err('bad') → first error wins
combineAsync(results: readonly [...ResultAsyncs]): ResultAsync<[T1, ...], E1 | ...>
Async variant of combine. Uses Promise.all internally — all ResultAsyncs run concurrently and ordering matches the input array. Same first-error-wins behavior and tuple type inference. Note: all promises are awaited even if an early one is Err (Promise.all semantics), but only the first Err is returned.
const result = await combineAsync([
fetchUser(1),
fetchConfig()
] as const)
// Result<[User, Config], UserErr | ConfigErr>
partition<T, E>(results: readonly Result<T, E>[]): [T[], E[]]
Splits an array of Results into two arrays: success values and error values. Unlike combine, this processes every result — it doesn't short-circuit.
const results = [ok(1), err('bad'), ok(3), err('worse')]
const [oks, errs] = partition(results)
// oks: [1, 3]
// errs: ['bad', 'worse']
flatten<T, E1, E2>(result: Result<Result<T, E2>, E1>): Result<T, E1 | E2>
Unwraps a nested Result. If you have a Result<Result<T, E2>, E1>, this collapses it to Result<T, E1 | E2>. Useful when you accidentally end up with double-wrapped results.
- If the outer Result is
Err, returns that Err.
- If the outer is
Ok, returns the inner Result as-is.
flatten(ok(ok(42))) // Ok(42)
flatten(ok(err('inner'))) // Err('inner')
flatten(err('outer')) // Err('outer')
class ResultError<Code extends string> extends Error
A branded error class designed for use with matchOn. Extends Error with a typed code property and optional context data. The Code generic makes it possible to define discriminated union error types.
- Properties:
.code (readonly, typed), .message (from Error), .context (optional, any data).
.name is set to 'ResultError'.
- Works as a standard Error — has stack trace, instanceof checks, etc.
type AppError =
| ResultError<'NOT_FOUND'>
| ResultError<'TIMEOUT'>
const e = new ResultError('NOT_FOUND', 'User not found', { userId: 123 })
e.code // 'NOT_FOUND' (typed as literal)
e.message // 'User not found'
e.context // { userId: 123 }
isResultError(value: unknown): value is ResultError<string>
Type guard that narrows unknown to ResultError. Uses instanceof under the hood.
if (isResultError(caught)) {
console.log(caught.code, caught.message) // typed access
}
resultToJSON<T, E>(result: Result<T, E>): ResultJSON<T, E>
Serializes a Result into a plain object with a _tag discriminant. The output is safe to pass through JSON.stringify for crossing process boundaries (API responses, message queues, etc).
- Ok produces
{ _tag: 'Ok', value: T }.
- Err produces
{ _tag: 'Err', error: E }.
resultToJSON(ok(42)) // { _tag: 'Ok', value: 42 }
resultToJSON(err('bad')) // { _tag: 'Err', error: 'bad' }
// Round-trip
const wire = JSON.stringify(resultToJSON(result))
resultFromJSON<T, E>(json: Record<string, unknown>): Result<T, E>
Deserializes a plain object back into a Result. Expects the _tag format produced by resultToJSON. Throws if the _tag is missing or invalid.
const data = JSON.parse(wire)
const result = resultFromJSON<User, AppError>(data)
type InferOkType<R> / type InferErrType<R>
Utility types that extract the success or error type from a Result or ResultAsync. Useful when you need to reference the inner types without manually spelling them out.
type MyResult = Result<number, string>
type T = InferOkType<MyResult> // number
type E = InferErrType<MyResult> // string
// Also works with ResultAsync
type T2 = InferOkType<ResultAsync<User, AppError>> // User
import resultkit from '@valencets/resultkit/eslint'
resultkit ships three eslint flat-config presets. strict (errors) and recommended (warnings) enforce the core error-handling rules. opinionated adds strict plus stylistic rules like banning export default.
// eslint.config.js
import resultkit from '@valencets/resultkit/eslint'
export default [
...resultkit.strict, // or resultkit.recommended for warnings
// your overrides here
]
Rules enforced:
throw statements → use err() or fromThrowable()
try/catch blocks → use fromThrowable() at the boundary
switch statements → use match() or matchOn()
.catch() calls → use ResultAsync.fromPromise()
.finally() calls → use .tap() for cleanup
enum declarations → use const unions with match()
.unwrap() / .unwrapErr() → use .match() or .unwrapOr() (override in test files)