@zireal/result-kit
v4.0.0
Published
Type-safe result and structured error utilities for TypeScript, with optional NestJS adapters.
Maintainers
Readme
@zireal/result-kit
Type-safe fluent results for TypeScript, with TypedError as the default error model and an optional NestJS adapter.
Packages
@zireal/result-kit@zireal/result-kit/core@zireal/result-kit/nest
The package root re-exports the framework-agnostic core only. Nest-specific helpers live in @zireal/result-kit/nest.
Installation
pnpm add @zireal/result-kitIf you use the Nest adapter:
pnpm add @nestjs/commonCore Concepts
TypedError
interface TypedError<
TType extends string = string,
TDetails extends Record<string, unknown> | undefined =
| Record<string, unknown>
| undefined,
> {
type: TType;
message: string;
details?: TDetails;
cause?: unknown;
}TypedError is the package's default error convention. It gives failures a stable type discriminator while preserving a human-readable message and optional structured metadata.
fail(...) preserves the full error subtype, so richer domain-specific payloads remain intact.
Use TypedErrorUnion<...> to model domain-specific failures:
type UserError = TypedErrorUnion<"not_found" | "validation_error">;Result<T, E>
Result<T, E> is a discriminated union with two fluent concrete variants:
Ok<T>for success valuesErr<E>for failure values
Construct results with:
ok(value)for successfail(typedError)for the defaultTypedErrorfailure patherr(error)for generic non-typed failuresResultKit.ok(...),ResultKit.fail(...), andResultKit.err(...)when you want the same fluent behavior through the package-branded facade
Core Usage
import {
ResultKit,
fail,
ok,
type Result,
type TypedErrorUnion,
} from "@zireal/result-kit";
type UserError = TypedErrorUnion<"not_found" | "validation_error">;
const findUser = (id: string): Result<{ id: string }, UserError> => {
if (!id.trim()) {
return fail({
type: "validation_error",
message: "id is required",
});
}
if (id !== "123") {
return fail({
type: "not_found",
message: "User not found",
details: { id },
});
}
return ok({ id });
};Compose result-producing services directly on the instance:
type AuthError = TypedErrorUnion<"missing_token">;
const requireSession = (
token: string,
): Result<{ userId: string }, AuthError> => {
if (!token.trim()) {
return fail({
type: "missing_token",
message: "token is required",
});
}
return ok({ userId: "123" });
};
const result = ok("session-token")
.andThen(requireSession)
.andThen((session) => findUser(session.userId));
const message = result.ok ? result.value.id : result.error.message;
const branded = ResultKit
.ok("session-token")
.andThen(requireSession)
.andThen((session) => findUser(session.userId));Async Usage
Use ResultAsync for fluent async composition:
import { ResultAsync, fail, ok, type TypedErrorUnion } from "@zireal/result-kit";
type ApiError = TypedErrorUnion<"network_error">;
const fetchUser = (id: string) =>
ResultAsync.fromPromise(fetch(`/users/${id}`).then((res) => res.json()), () =>
({
type: "network_error",
message: "Unable to fetch user",
}) satisfies ApiError,
);
const user = await fetchUser("123")
.andThen((payload) => ok(payload.user))
.unwrapOr({ id: "fallback" });Async-capable fluent chains support callbacks that return Result, ResultAsync, or PromiseLike<Result> on different steps or branches, so complex service flows do not need typed intermediate variables just to keep callback payloads concrete.
Nest Usage
import { Controller, Get, Param } from "@nestjs/common";
import { type Result, type TypedErrorUnion } from "@zireal/result-kit";
import { unwrapOrThrow } from "@zireal/result-kit/nest";
type UserError = TypedErrorUnion<"not_found" | "validation_error">;
class UserService {
async findUser(id: string): Promise<Result<{ id: string }, UserError>> {
// return ok(...) or fail(...)
}
}
@Controller("users")
export class UserController {
constructor(private readonly service: UserService) {}
@Get(":id")
async getUser(@Param("id") id: string) {
return unwrapOrThrow(await this.service.findUser(id));
}
}Use a mapper when your domain errors need custom HTTP status behavior:
import { BadRequestException, NotFoundException } from "@nestjs/common";
import { isTypedError } from "@zireal/result-kit";
import { unwrapOrThrow } from "@zireal/result-kit/nest";
const user = unwrapOrThrow(result, {
mapError: (error) => {
if (isTypedError(error, "validation_error")) {
return new BadRequestException(error.message);
}
if (isTypedError(error, "not_found")) {
return new NotFoundException(error.message);
}
return undefined;
},
});API Surface
Core
TypedError,TypedErrorOf,TypedErrorUnion,isTypedErrorResultValue,ResultError,ResultOk,ResultErrAsyncResultValue,AsyncResultErrorOk,Err,Result,ResultAsyncok,fail,err,okAsync,errAsyncResultKitResult.fromThrowable,Result.fromNullable,Result.fromPredicateResult.combine,Result.combineWithAllErrorsResultAsync.fromPromise,ResultAsync.fromThrowableResultAsync.combine,ResultAsync.combineWithAllErrors
Result instance methods
isOk,isErrmap,mapErrandThen,orElsematchunwrapOr,unwrapOrElseasyncMap,asyncAndThenandTee,orTee,andThrough
ResultAsync instance methods
map,mapErrandThen,orElsematchunwrapOrandTee,orTee,andThroughthen
Nest
toHttpExceptionunwrapOrThrowunwrapPromise
