| name | tsentials-maybe |
| description | Use when representing optional values explicitly — Maybe<T> as a null-safe container with hasValue discriminant, Maybe.from()/some()/none()/fromTry() for creation, map/bind/filter/tap/tapNone chaining, match/switch for consumption, conditional mapIf/bindIf, fallback or/orElse, extraction getOrDefault/getOrElse/getOrUndefined/getOrThrow/deconstruct, flatten/toArray utilities, async support with mapAsync/bindAsync/filterAsync/tapAsync/matchAsync/orAsync, and collection utilities tryFirst/tryLast/tryFind/choose/asMaybe. |
tsentials/maybe
Maybe<T> makes optionality explicit. No null reference errors — the absence of a value is a first-class concept.
Installation
npm install tsentials
Import
import { Maybe, tryFirst, tryLast, tryFind, choose, asMaybe } from 'tsentials/maybe';
Type Definition
type Maybe<T> =
| { readonly hasValue: true; readonly value: T }
| { readonly hasValue: false };
The discriminant is hasValue: boolean — there is no kind property.
Creating Maybe
const name: Maybe<string> = Maybe.from(user.nickname);
const some: Maybe<string> = Maybe.some('Alice');
const none: Maybe<string> = Maybe.none();
const parsed: Maybe<Config> = Maybe.fromTry(() => JSON.parse(raw));
Type Guards
if (Maybe.isSome(maybe)) {
console.log(maybe.value);
}
if (Maybe.isNone(maybe)) {
console.log('No value');
}
Checking Value
const display = Maybe.match(
maybe,
name => `Hello, ${name}`,
() => 'Hello, stranger',
);
Maybe.switch(maybe,
user => console.log(`Found ${user.name}`),
() => console.log('User not found'),
);
Transforming
const upper: Maybe<string> = Maybe.map(
Maybe.from(user.email),
e => e.toLowerCase(),
);
const nonEmpty: Maybe<string> = Maybe.filter(upper, s => s.length > 0);
const address: Maybe<Address> = Maybe.bind(
Maybe.from(user),
u => Maybe.from(u.address),
);
const flat: Maybe<string> = Maybe.flatten(nestedMaybe);
const arr: string[] = Maybe.toArray(maybe);
Side Effects
Maybe.tap(maybe, value => console.log('Got:', value));
Maybe.tapNone(maybe, () => console.log('Value was missing'));
Conditional Pipeline
const formatted = Maybe.mapIf(maybe, s => s.length > 0, s => s.trim());
const toggled = Maybe.mapIf(maybe, shouldFormat, s => s.toUpperCase());
const enriched = Maybe.bindIf(maybe, shouldEnrich, id => lookupById(id));
Consuming / Extraction
const email = Maybe.getOrDefault(Maybe.from(user.email), 'no-email@fallback.com');
const email2 = Maybe.getOrElse(Maybe.from(user.email), () => 'no-email@fallback.com');
const email3: string | undefined = Maybe.getOrUndefined(Maybe.from(user.email));
const email4 = Maybe.getOrThrow(Maybe.from(user.email), 'Email is required');
const email5 = Maybe.getOrThrowFactory(maybe, () => new DomainError('User.NoEmail'));
const label = Maybe.match(maybe, v => `Found: ${v}`, () => 'Not found');
const [hasVal, val] = Maybe.deconstruct(maybe);
const [ok, value] = Maybe.tryGet(maybe);
Fallback Chain
const result = Maybe.or(primary, fallbackMaybe);
const result2 = Maybe.orElse(primary, () => computeFallback());
Async Pipeline
const fetched: Maybe<User> = await Maybe.mapAsync(
Maybe.some(userId),
async id => fetchUser(id),
);
const detail: Maybe<Profile> = await Maybe.bindAsync(
Maybe.some(userId),
async id => {
const user = await fetchUser(id);
return Maybe.from(user?.profile);
},
);
const valid: Maybe<User> = await Maybe.filterAsync(
Maybe.some(user),
async u => await checkIsActive(u.id),
);
await Maybe.tapAsync(maybe, async value => await logAccess(value));
const result = await Maybe.matchAsync(
maybe,
async user => await enrichUser(user),
async () => await getDefaultUser(),
);
const result2 = await Maybe.orAsync(maybe, async () => await fetchFallback());
Collection Utilities
import { tryFirst, tryLast, tryFind, choose, asMaybe } from 'tsentials/maybe';
const first: Maybe<User> = tryFirst(users);
const last: Maybe<User> = tryLast(users);
const admin: Maybe<User> = tryFind(users, u => u.role === 'admin');
const values: number[] = choose([Maybe.some(1), Maybe.none(), Maybe.some(3)]);
const m: Maybe<string> = asMaybe(nullableValue);
Full Example
const users = [
{ name: 'Alice', role: 'admin', email: 'alice@example.com' },
{ name: 'Bob', role: 'user', email: null },
];
const adminName = Maybe.match(
tryFind(users, u => u.role === 'admin'),
u => u.name,
() => 'No admin',
);
const emails = choose(users.map(u => Maybe.from(u.email)));
const config = Maybe.fromTry(() => JSON.parse(rawInput));
const display = Maybe.getOrElse(
Maybe.or(Maybe.from(user.nickname), Maybe.from(user.email)),
() => 'anonymous',
);
Complete API Reference
| Category | Method | Signature |
|---|
| Factory | some | <T>(value: T) => Maybe<T> |
| none | <T>() => Maybe<T> |
| from | <T>(value: T | null | undefined) => Maybe<T> |
| fromTry | <T>(fn: () => T | null | undefined) => Maybe<T> |
| Type Guard | isSome | <T>(m: Maybe<T>) => m is { hasValue: true; value: T } |
| isNone | <T>(m: Maybe<T>) => m is { hasValue: false } |
| Transform | map | <T, U>(m, fn) => Maybe<U> |
| bind | <T, U>(m, fn: (v: T) => Maybe<U>) => Maybe<U> |
| filter | <T>(m, pred) => Maybe<T> |
| flatten | <T>(m: Maybe<Maybe<T>>) => Maybe<T> |
| toArray | <T>(m) => T[] |
| Side Effect | tap | <T>(m, fn: (v: T) => void) => Maybe<T> |
| tapNone | <T>(m, fn: () => void) => Maybe<T> |
| Conditional | mapIf | <T>(m, cond: boolean | ((v: T) => boolean), fn) => Maybe<T> |
| bindIf | <T>(m, cond: boolean | ((v: T) => boolean), fn) => Maybe<T> |
| Match | match | <T, U>(m, onSome, onNone) => U |
| switch | <T>(m, onSome, onNone) => void |
| Extraction | getOrDefault | <T>(m, default: T) => T |
| getOrElse | <T>(m, fn: () => T) => T |
| getOrUndefined | <T>(m) => T | undefined |
| getOrThrow | <T>(m, message?) => T |
| getOrThrowFactory | <T>(m, factory: () => Error) => T |
| deconstruct | <T>(m) => [boolean, T | undefined] |
| tryGet | <T>(m) => [boolean, T | undefined] |
| Fallback | or | <T>(m, fallback: Maybe<T>) => Maybe<T> |
| orElse | <T>(m, fn: () => Maybe<T>) => Maybe<T> |
| Async | mapAsync | <T, U>(m, fn) => Promise<Maybe<U>> |
| bindAsync | <T, U>(m, fn) => Promise<Maybe<U>> |
| filterAsync | <T>(m, pred) => Promise<Maybe<T>> |
| tapAsync | <T>(m, fn) => Promise<Maybe<T>> |
| matchAsync | <T, U>(m, onSome, onNone) => Promise<U> |
| orAsync | <T>(m, fn) => Promise<Maybe<T>> |
| Collection | tryFirst | <T>(arr) => Maybe<T> |
| tryLast | <T>(arr) => Maybe<T> |
| tryFind | <T>(arr, pred) => Maybe<T> |
| choose | <T>(maybes: Maybe<T>[]) => T[] |
| asMaybe | <T>(value: T | null | undefined) => Maybe<T> |
Best Practices
- Use
Maybe.from() for all nullable-to-Maybe conversions
- Prefer
Maybe.match() over hasValue checks to avoid branching
- Use
Maybe.isSome() / Maybe.isNone() type guards when you need narrowing in if-blocks
- Use
Maybe.bind() when the transform can itself be absent (returns Maybe<T>)
- Use
Maybe.fromTry() to safely wrap expressions that may throw
- Use
choose() to extract present values from a Maybe<T>[] array
- The discriminant is
hasValue: boolean — there is no kind property