This package has been archived, and as such it is read-only.
Works with
•JSR Score100%•This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers




Downloads3/wk
•Published2 months ago (0.5.0)
A small, focused TypeScript library for safer code through functional patterns using natural language. Inspired by folktale.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450// Copyright © 2025 Cassidy Spring (Bee). // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. import type { Nullable } from '../utility/types.ts' /** * Represents an optional value that may or may not exist. * * Maybe is a type-safe way to handle nullable values without using null or undefined * directly. A Maybe is either Just (contains a value) or Nothing (empty). * * This pattern eliminates null errors by forcing you to explicitly handle the * absence of a value at compile time. * * @typeParam Type - The type of the value when it exists (Just case) * * @example * ```ts * // Basic usage * function findUser(id: string): Maybe<User> { * const user = users.get(id) * return Maybe.FromNullable(user) * } * * const result = findUser('123') * result.matchWith({ * Just: (user) => console.log(`Found: ${user.name}`), * Nothing: () => console.log('User not found') * }) * ``` * * @example * ```ts * // Chaining operations * const userName = findUser(id) * .map(user => user.profile) * .map(profile => profile.name) * .getOrElse('Anonymous') * ``` * * @example * ```ts * // Creating Maybe values * const just = Maybe.Just(42) * const nothing = Maybe.Nothing<number>() * const fromNullable = Maybe.FromNullable(possiblyNull) * ``` */ export class Maybe<Type> { private constructor(private readonly value: Nullable<Type> = undefined) {} /** * Type guard to check if a value is a Maybe instance. * * @typeParam Type - The expected type of the Maybe's value * @param value - The value to check * @returns True if the value is a Maybe instance * * @example * ```ts * const value: unknown = Maybe.Just(42) * if (Maybe.HasInstance(value)) { * // TypeScript now knows value is Maybe<unknown> * console.log(value.isJust()) * } * ``` */ public static HasInstance<Type>(value: unknown): value is Maybe<Type> { return value instanceof Maybe } /** * Checks if this Maybe contains a value. * * @returns True if this is a Just (has a value), false if it's Nothing * * @example * ```ts * const maybe = Maybe.Just(42) * if (maybe.isJust()) { * console.log('Has a value!') * } * ``` * * @see {@link isNothing} to check if empty * @see {@link matchWith} for handling both cases */ public isJust(): boolean { return this.value != null } /** * Checks if this Maybe is empty (has no value). * * @returns True if this is Nothing (empty), false if it's Just * * @example * ```ts * const maybe = Maybe.Nothing<number>() * if (maybe.isNothing()) { * console.log('No value present') * } * ``` * * @see {@link isJust} to check if has a value * @see {@link matchWith} for handling both cases */ public isNothing(): boolean { return this.value == null } /** * Pattern matches on the Maybe, executing different handlers for Just and Nothing cases. * * This is the primary way to extract values from a Maybe. Both cases must be handled, * ensuring you never forget to handle the absence of a value. * * @typeParam JustReturnType - The return type of the Just handler * @typeParam NothingReturnType - The return type of the Nothing handler * @param pattern - An object with Just and Nothing handler functions * @returns The result of whichever handler was executed * * @example * ```ts * const maybe = findUser('123') * const message = maybe.matchWith({ * Just: (user) => `Hello, ${user.name}!`, * Nothing: () => 'User not found' * }) * ``` * * @example * ```ts * // Different return types from each branch * const value = maybe.matchWith({ * Just: (val) => val.length, * Nothing: () => 0 * }) * ``` * * @see {@link map} for transforming only Just values * @see {@link getOrElse} for a simpler way to provide defaults */ public matchWith<JustReturnType, NothingReturnType>(pattern: { Just: (value: NonNullable<Type>) => JustReturnType Nothing: () => NothingReturnType }): JustReturnType | NothingReturnType { if (this.isJust()) { return pattern.Just(this.value!) } else { return pattern.Nothing() } } /** * Extracts the value from a Just or throws an error if it's Nothing. * * **Warning:** This method throws exceptions and should be used sparingly. * Prefer {@link getOrElse} or {@link matchWith} for safer error handling. * * @param error - The error message to throw if this is Nothing * @returns The value if Just * @throws {Error} If this is Nothing * * @experimental May change or be removed in patch releases * @see https://github.com/beeauvin/folklore/issues/23 * * @example * ```ts * const maybe = Maybe.Just(42) * const value = maybe.getOrThrow() // 42 * ``` * * @example * ```ts * const maybe = Maybe.Nothing<number>() * const value = maybe.getOrThrow('Value required!') * // throws Error: Value required! * ``` * * @see {@link getOrElse} for a safer alternative with default values * @see {@link matchWith} for handling both cases without exceptions */ public getOrThrow(error = 'tried to get a maybe value that was nothing'): Type { return this.matchWith({ Just: (value) => value, Nothing: () => { throw new Error(error) }, }) } /** * Gets the Just value or returns a default value if this is Nothing. * * This provides a simple way to extract a value from a Maybe with a fallback, * without needing to use pattern matching. * * @param defaultValue - The value to return if this is Nothing * @returns The Just value or the default value * * @example * ```ts * const maybe = Maybe.Just(42) * const value = maybe.getOrElse(0) // 42 * ``` * * @example * ```ts * const maybe = Maybe.Nothing<number>() * const value = maybe.getOrElse(0) // 0 * ``` * * @example * ```ts * const userName = findUser(id) * .map(user => user.name) * .getOrElse('Anonymous') * ``` * * @see {@link orElse} for providing an alternative Maybe * @see {@link matchWith} to handle Nothing differently * @see {@link getOrThrow} to throw instead of using a default */ public getOrElse(defaultValue: NonNullable<Type>): NonNullable<Type> { return this.isJust() ? this.value! : defaultValue } /** * Returns this Maybe if it's Just, otherwise calls the handler to provide an alternative. * * This allows you to provide an alternative Maybe when this one is Nothing, * useful for trying fallback operations or providing computed defaults. * * @typeParam HandlerType - The type of value in the alternative Maybe * @param handler - Function that returns an alternative Maybe * @returns This Maybe if Just, otherwise the Maybe from the handler * * @example * ```ts * const result = findInCache(key) * .orElse(() => findInDatabase(key)) * .orElse(() => Maybe.Just(defaultValue)) * ``` * * @example * ```ts * const config = getLocalConfig() * .orElse(() => fetchRemoteConfig()) * .orElse(() => Maybe.Just(DEFAULT_CONFIG)) * ``` * * @see {@link getOrElse} for simple default values * @see {@link chain} for chaining operations on Just values */ public orElse<HandlerType>(handler: () => Maybe<HandlerType>): Maybe<Type> | Maybe<HandlerType> { if (this.isJust()) { return Maybe.Just(this.value!) } else { return handler() } } /** * Transforms the Just value using the provided function. * * If the Maybe is Just, applies the handler to the value and returns a new Just Maybe. * If the Maybe is Nothing, returns Nothing without calling the handler. * * This allows you to chain transformations on present values while automatically * propagating Nothing. * * @typeParam HandlerType - The type returned by the transformation function * @param handler - Function to transform the Just value * @returns A new Maybe with the transformed value or Nothing * * @example * ```ts * const maybe = Maybe.Just('hello') * .map(s => s.toUpperCase()) * .map(s => s.length) * // Maybe.Just(5) * ``` * * @example * ```ts * const nothing = Maybe.Nothing<string>() * .map(s => s.toUpperCase()) * // Maybe.Nothing() - handler never called * ``` * * @example * ```ts * // Chaining property access safely * const email = findUser(id) * .map(user => user.profile) * .map(profile => profile.email) * ``` * * @see {@link chain} for transformations that return Maybe * @see {@link matchWith} to handle both Just and Nothing cases */ public map<HandlerType>(handler: (value: Type) => NonNullable<HandlerType>): Maybe<HandlerType> { if (this.isJust()) { return Maybe.Just(handler(this.value!)) } else { return Maybe.Nothing<HandlerType>() } } /** * Transforms the Just value using a function that returns a Maybe (also known as flatMap). * * Similar to {@link map}, but the handler function returns a Maybe instead of a plain value. * This is useful for chaining operations that may themselves return Nothing, avoiding nested Maybes. * * @typeParam HandlerType - The type of the value in the Maybe returned by the handler * @param handler - Function that takes the Just value and returns a new Maybe * @returns The Maybe returned by the handler, or Nothing * * @example * ```ts * // Chaining operations that may return Nothing * const result = findUser(userId) * .chain(user => findDepartment(user.deptId)) * .chain(dept => findManager(dept.managerId)) * ``` * * @example * ```ts * // Without chain, you'd get Maybe<Maybe<T>> * const nested = Maybe.Just(5).map(x => findValue(x)) * // Maybe<Maybe<number>> * * // With chain, the Maybe is flattened * const flat = Maybe.Just(5).chain(x => findValue(x)) * // Maybe<number> * ``` * * @see {@link map} for simple value transformations */ public chain<HandlerType>(handler: (value: Type) => Maybe<HandlerType>): Maybe<HandlerType> { if (this.isJust()) { return handler(this.value!) } else { return Maybe.Nothing<HandlerType>() } } /** * Converts a nullable value into a Maybe. * * This is the primary way to create Maybe values from existing code that uses * null or undefined. If the value is null or undefined, returns Nothing. * Otherwise, returns Just with the value. * * @typeParam Type - The type of the value * @param value - A value that may be null or undefined * @returns Just if the value exists, Nothing if it's null or undefined * * @example * ```ts * const map = new Map([['key', 'value']]) * const maybe = Maybe.FromNullable(map.get('key')) * // Maybe.Just('value') * ``` * * @example * ```ts * const maybe = Maybe.FromNullable(null) * // Maybe.Nothing() * ``` * * @example * ```ts * // Wrapping optional object properties * function getEmail(user: { email?: string }): Maybe<string> { * return Maybe.FromNullable(user.email) * } * ``` * * @see {@link Just} for creating a Maybe with a guaranteed value * @see {@link Nothing} for creating an empty Maybe */ public static FromNullable<Type>(value: Nullable<Type>): Maybe<Type> { if (value == null) return Maybe.Nothing() else return Maybe.Just(value!) } /** * Creates a Maybe containing a value. * * Use this when you have a value that definitely exists and want to wrap it * in a Maybe for use with other Maybe-returning functions. * * @typeParam Type - The type of the value * @param value - A non-null, non-undefined value * @returns A Maybe containing the value * * @example * ```ts * const maybe = Maybe.Just(42) * ``` * * @example * ```ts * // Useful for creating a known value in a Maybe chain * const result = Maybe.Just(user) * .map(u => u.settings) * .chain(s => findPreference(s)) * ``` * * @see {@link Nothing} for creating an empty Maybe * @see {@link FromNullable} for converting nullable values */ public static Just<Type>(value: NonNullable<Type>): Maybe<Type> { return new Maybe(value) } /** * Creates an empty Maybe representing the absence of a value. * * @typeParam Type - The type the Maybe would contain if it had a value * @returns An empty Maybe * * @example * ```ts * function findUser(id: string): Maybe<User> { * if (!isValid(id)) { * return Maybe.Nothing() * } * // ... search logic * } * ``` * * @example * ```ts * const nothing = Maybe.Nothing<number>() * nothing.isNothing() // true * ``` * * @see {@link Just} for creating a Maybe with a value * @see {@link FromNullable} for converting nullable values */ public static Nothing<Type>(): Maybe<Type> { return new Maybe() } }