option-t
Core API
import { createOk, createErr, isOk, isErr, unwrapOk } from "option-t/plain_result";
import { mapForResult } from "option-t/plain_result/map";
import { andThenForResult } from "option-t/plain_result/and_then";
import { andThenAsyncForResult } from "option-t/plain_result/and_then_async";
import { mapErrForResult } from "option-t/plain_result/map_err";
import { orElseForResult } from "option-t/plain_result/or_else";
Or with a namespace import:
import { Result } from "option-t/plain_result/namespace";
// Result.createOk, Result.map, Result.andThen, etc.
| Function / Type | Description |
|---|---|
Result<T, E> | Result type (Ok<T> \| Err<E> discriminated union, plain object) |
createOk(value) | Constructs a success value ({ ok: true, val: T, err: null }) |
createErr(error) | Constructs a failure value ({ ok: false, val: null, err: E }) |
Key differences from neverthrow:
- Plain objects instead of classes (discriminant is the
okfield) - Composition via standalone functions instead of method chaining
- Async operations use
*Asyncvariant functions (return type isPromise<Result<T, E>>)
Composition with Functions
import { mapForResult } from "option-t/plain_result/map";
import { mapErrForResult } from "option-t/plain_result/map_err";
import { andThenForResult } from "option-t/plain_result/and_then";
import { orElseForResult } from "option-t/plain_result/or_else";
const mapped = mapForResult(result, (value) => transform(value));
const mappedErr = mapErrForResult(result, (error) => transformErr(error));
const chained = andThenForResult(result, (value) => nextResult(value));
const recovered = orElseForResult(result, (error) => recover(error));
// Branch with a type guard or the ok field
if (isOk(result)) {
console.log(result.val);
} else {
console.log(result.err);
}
Example: State-Transition Pipeline
For the design of RequestResolver / RequestStore and how to persist state and domain events in a single transaction, see state-modeling.md#domain-events.
import { createOk, createErr, isOk, isErr, type Result } from "option-t/plain_result";
import { andThenForResult } from "option-t/plain_result/and_then";
import { andThenAsyncForResult } from "option-t/plain_result/and_then_async";
import { mapAsyncForResult } from "option-t/plain_result/map_async";
// --- Branded Types ---
declare const RequestIdBrand: unique symbol;
type RequestId = string & { readonly [RequestIdBrand]: never };
declare const DriverIdBrand: unique symbol;
type DriverId = string & { readonly [DriverIdBrand]: never };
declare const PassengerIdBrand: unique symbol;
type PassengerId = string & { readonly [PassengerIdBrand]: never };
// --- State Types ---
type Waiting = Readonly<{
kind: "Waiting";
requestId: RequestId;
passengerId: PassengerId;
}>;
type EnRoute = Readonly<{
kind: "EnRoute";
requestId: RequestId;
passengerId: PassengerId;
driverId: DriverId;
}>;
// --- Repository Types ---
type RequestResolver = Readonly<{
findById: (id: RequestId) => Promise<Result<Waiting | undefined, RepositoryError>>;
}>;
type RequestStore = Readonly<{
save: (state: EnRoute) => Promise<Result<void, RepositoryError>>;
}>;
// --- Error Types ---
type AssignDriverError =
| Readonly<{ kind: "RequestNotFound"; requestId: RequestId }>
| Readonly<{ kind: "DriverNotAvailable"; driverId: DriverId }>
| Readonly<{ kind: "RepositoryError"; cause: unknown }>;
type RepositoryError = Readonly<{ kind: "RepositoryError"; cause: unknown }>;
// --- Use Case ---
const assignDriverUseCase =
(requestResolver: RequestResolver, requestStore: RequestStore) =>
async (
requestId: RequestId,
driverId: DriverId,
isDriverAvailable: boolean,
): Promise<Result<EnRoute, AssignDriverError>> => {
const requestResult = await requestResolver.findById(requestId);
const waitingResult = andThenForResult(requestResult, (request) =>
request !== undefined
? createOk(request)
: createErr({ kind: "RequestNotFound" as const, requestId }),
);
if (isErr(waitingResult)) return waitingResult;
const waiting = waitingResult.val;
if (!isDriverAvailable) {
return createErr({ kind: "DriverNotAvailable" as const, driverId });
}
const enRoute: EnRoute = {
kind: "EnRoute",
requestId: waiting.requestId,
passengerId: waiting.passengerId,
driverId,
};
const saveResult = await requestStore.save(enRoute);
if (isErr(saveResult)) return saveResult;
return createOk(enRoute);
};