UNPKG

@ghaerdi/rustify

Version:

A TypeScript monad library inspired by Rust, providing Result and Option types for safe error handling and null management.

750 lines (657 loc) 22.5 kB
/** * Interface defining the structure for the pattern matching handlers used by the `match` method. * @template T The type of the value. * @template U The return type of the Some handler. * @template V The return type of the None handler. */ interface OptionMatcher<T, U, V> { /** * Handler function for the Some case. * @param value The value of type T. * @returns A value of type U. */ Some: (value: T) => U; /** * Handler function for the None case. * @returns A value of type V. */ None: () => V; } /** * BaseOption interface defines the common methods for Some and None implementations. * It allows iteration over the contained value *only* if it's `Some` and the value itself is iterable. * @template T The type of the value. */ interface BaseOption<T> extends Iterable<T extends Iterable<infer U> ? U : never> { /** * Checks if the option is Some. * @returns True if the option is Some, false otherwise. * @example * ```typescript * Some(5).isSome(); // true * None().isSome(); // false * ``` */ isSome(): boolean; /** * Checks if the option is Some and the contained value satisfies a predicate. * @param fn The predicate function to apply to the Some value. * @returns True if the option is Some and the predicate returns true, false otherwise. * @example * ```typescript * Some(5).isSomeAnd(x => x > 3); // true * Some(5).isSomeAnd(x => x < 3); // false * None().isSomeAnd(x => x > 3); // false * ``` */ isSomeAnd(fn: (value: T) => boolean): boolean; /** * Checks if the option is None. * @returns True if the option is None, false otherwise. * @example * ```typescript * None().isNone(); // true * Some(5).isNone(); // false * ``` */ isNone(): boolean; /** * Returns the contained Some value. * Throws an error if the value is None, using the provided message. * @param message The message to use if the value is None. * @returns The Some value. * @throws {Error} Throws an error with `message` if the option is None. * @example * ```typescript * Some(5).expect("should be Some"); // 5 * // None.expect("should be Some"); // Throws "should be Some" * ``` */ expect(message: string): T; /** * Returns the contained Some value. * Throws an error if the value is None. * @returns The Some value. * @throws {Error} Throws an error if the option is None. * @example * ```typescript * Some(5).unwrap(); // 5 * // None.unwrap(); // Throws "Tried to unwrap a None value" * ``` */ unwrap(): T; /** * Returns the contained Some value or a provided default value. * @param defaultValue The default value to return if the option is None. * @returns The Some value or `defaultValue`. * @example * ```typescript * Some(5).unwrapOr(0); // 5 * None().unwrapOr(0); // 0 * ``` */ unwrapOr(defaultValue: T): T; /** * Returns the contained Some value or computes it from a closure. * @param fn The closure to compute the default value. * @returns The Some value or the value computed by `fn()`. * @example * ```typescript * Some(5).unwrapOrElse(() => 0); // 5 * None().unwrapOrElse(() => 0); // 0 * ``` */ unwrapOrElse(fn: () => T): T; /** * Maps an `Option<T>` to `Option<U>` by applying a function to a contained Some value, * leaving a None value untouched. * @template U The type of the mapped Some value. * @param fn The function to apply to the Some value. * @returns A new Option with the mapped Some value or None. * @example * ```typescript * Some(5).map(x => x.toString()).unwrap(); // "5" * None().map((x:any) => x.toString()).isNone(); // true * ``` */ map<U>(fn: (value: T) => U): Option<U>; /** * Returns the provided default value (if None), or applies a function to the contained value (if Some). * @template U The type returned by the function `fn` and the type of `defaultValue`. * @param defaultValue The default value to return if the option is None. * @param fn The function to apply to the Some value. * @returns The result of `fn(Some_value)` or `defaultValue`. * @example * ```typescript * Some("foo").mapOr("bar", v => v.length); // 3 * None().mapOr("bar", (v:any) => v.length); // "bar" * ``` */ mapOr<U>(defaultValue: U, fn: (value: T) => U): U; /** * Computes a default value (if None), or applies a function to the contained value (if Some). * @template U The type returned by both functions. * @param defaultFn The function to compute the default value if None. * @param fn The function to apply to the Some value. * @returns The result of `fn(Some_value)` or `defaultFn()`. * @example * ```typescript * Some(5).mapOrElse(() => "None!", x => `Some: ${x}`); // "Some: 5" * None().mapOrElse(() => "None!", (x:any) => `Some: ${x}`); // "None!" * ``` */ mapOrElse<U>(defaultFn: () => U, fn: (value: T) => U): U; /** * Calls the provided function with the contained value (if Some). Returns the original option. * Useful for debugging or side-effects. * @param fn The function to call with the Some value. * @returns The original `Option<T>`. * @example * ```typescript * Some(5).inspect(x => console.log(x)); // Logs 5, returns Some(5) * None().inspect(x => console.log(x)); // Returns None * ``` */ inspect(fn: (value: T) => void): Option<T>; /** * Returns `res` if the option is Some, otherwise returns None. * @template U The type of the Some value of the `res` Option. * @param res The other Option to return if self is Some. * @returns `res` if self is Some, otherwise None. * @example * ```typescript * Some(2).and(Some("late success")).unwrap(); // "late success" * None().and(Some("late success")).isNone(); // true * Some(2).and(None).isNone(); // true * ``` */ and<U>(res: Option<U>): Option<U>; /** * Calls `fn` if the option is Some, otherwise returns None. * This is often used for chaining operations that might return an Option. * (Also known as `flatMap`) * @template U The type of the Some value of the Option returned by `fn`. * @param fn The function to call with the Some value, which returns a new Option. * @returns The Option returned by `fn`, or None if self is None. * @example * ```typescript * Some(5).andThen(x => Some(x > 0)).unwrap(); // true * Some(-1).andThen(x => x > 0 ? Some(x) : None).isNone(); // true * None().andThen((x:any) => Some(x > 0)).isNone(); // true * ``` */ andThen<U>(fn: (value: T) => Option<U>): Option<U>; // flatMap /** * Returns the option if it contains a value, otherwise returns `res`. * @param res The other Option to return if self is None. * @returns The option if it is Some, otherwise `res`. * @example * ```typescript * Some(5).or(Some(10)).unwrap(); // 5 * None().or(Some(10)).unwrap(); // 10 * None().or(None()).isNone(); // true * ``` */ or(res: Option<T>): Option<T>; /** * Returns the option if it contains a value, otherwise calls `fn` and returns its result. * @param fn The function to call to produce an alternative Option if self is None. * @returns The option if it is Some, otherwise the result of `fn()`. * @example * ```typescript * Some(5).orElse(() => Some(0)).unwrap(); // 5 * None().orElse(() => Some(0)).unwrap(); // 0 * None().orElse(() => None()).isNone(); // true * ``` */ orElse(fn: () => Option<T>): Option<T>; /** * Returns Some if exactly one of self, optb is Some, otherwise returns None. * If self is Some and optb is None, returns Some(self). * If self is None and optb is Some, returns Some(optb). * Otherwise (both are Some or both are None), returns None. * @param optb The other Option. * @returns An Option containing the value of the single Some, or None. * @example * ```typescript * Some(1).xor(None()).unwrap(); // 1 * None().xor(Some(2)).unwrap(); // 2 * Some(1).xor(Some(2)).isNone(); // true * None().xor(None()).isNone(); // true * ``` */ xor(optb: Option<T>): Option<T>; /** * Returns a new Option containing a clone of the contained Some value. * Uses `structuredClone` for objects. Primitive values are copied directly. * If the value is None, returns None. * @returns A new Option with a clone of the Some value, or None. * @note Uses `structuredClone`. * @example * ```typescript * Some({ a: 1 }).cloned().unwrap(); // { a: 1 } (new object) * None().cloned().isNone(); // true * ``` */ cloned(): Option<T>; /** * Zips self with another Option. * If self is `Some(s)` and other is `Some(o)`, returns `Some([s, o])`. * Otherwise, returns `None`. * @template U The type of the value in the other Option. * @param other The other Option. * @returns An Option containing a tuple of the two Some values, or None. * @example * ```typescript * Some(1).zip(Some("a")).unwrap(); // [1, "a"] * Some(1).zip(None).isNone(); // true * None().zip(Some("a")).isNone(); // true * ``` */ zip<U>(other: Option<U>): Option<[T, U]>; /** * Zips self with another Option using a custom function. * If self is `Some(s)` and other is `Some(o)`, returns `Some(fn(s, o))`. * Otherwise, returns `None`. * @template U The type of the value in the other Option. * @template R The type of the value returned by the zipping function. * @param other The other Option. * @param fn The function to combine the two Some values. * @returns An Option containing the result of `fn`, or None. * @example * ```typescript * Some(1).zipWith(Some("a"), (n, s) => `${n}${s}`).unwrap(); // "1a" * Some(1).zipWith(None, (n, s) => `${n}${s}`).isNone(); // true * ``` */ zipWith<U, R>(other: Option<U>, fn: (s: T, o: U) => R): Option<R>; /** * Executes one of two provided functions based on whether the Option is Some or None. * This allows for pattern matching on the Option type. * * @template U The return type of the `Some` handler. * @template V The return type of the `None` handler. * @param matcher An object containing `Some` and `None` functions. * @returns The value returned by the executed handler (`U` or `V`). * @example * ```typescript * const option: Option<number> = Some(10); * const message = option.match({ * Some: (value) => `Success: ${value}`, * None: () => `It was None` * }); * // message is "Success: 10" * * const option2: Option<number> = None; * const message2 = option2.match({ * Some: (value) => `Success: ${value}`, * None: () => `It was None` * }); * // message2 is "It was None" * ``` */ match<U, V>(matcher: OptionMatcher<T, U, V>): U | V; /** * Flattens an `Option<Option<T>>` to `Option<T>`. * @returns Some(value) if the option contains Some(Some(value)), None otherwise. * @example * ```typescript * Some(Some(5)).flatten().unwrap(); // 5 * Some(None).flatten().isNone(); // true * None().flatten().isNone(); // true * ``` */ flatten<U>(this: Option<Option<U>>): Option<U>; /** * Returns None if the option is None, otherwise calls predicate with the wrapped value and returns: * - Some(value) if predicate returns true * - None if predicate returns false * @param predicate The predicate function to test the Some value. * @returns Some(value) if predicate is true, None otherwise. * @example * ```typescript * Some(5).filter(x => x > 3).unwrap(); // 5 * Some(5).filter(x => x > 10).isNone(); // true * None().filter(x => x > 3).isNone(); // true * ``` */ filter(predicate: (value: T) => boolean): Option<T>; /** * Transforms the Option<T> into a Result<T, E>, mapping Some(v) to Ok(v) and None to Err(err). * @template E The error type. * @param err The error value to use if the option is None. * @returns Ok(value) if Some, Err(err) if None. * @example * ```typescript * Some(5).okOr("error").unwrap(); // 5 * None().okOr("error").unwrapErr(); // "error" * ``` */ okOr<E>(err: E): import("./result.ts").Result<T, E>; /** * Transforms the Option<T> into a Result<T, E>, mapping Some(v) to Ok(v) and None to Err(fn()). * @template E The error type. * @param fn The function to compute the error if the option is None. * @returns Ok(value) if Some, Err(fn()) if None. * @example * ```typescript * Some(5).okOrElse(() => "error").unwrap(); // 5 * None().okOrElse(() => "error").unwrapErr(); // "error" * ``` */ okOrElse<E>(fn: () => E): import("./result.ts").Result<T, E>; /** * Transposes an Option of a Result into a Result of an Option. * @returns Result<Option<T>, E> based on the inner Result. * @example * ```typescript * Some(Ok(5)).transpose().unwrap().unwrap(); // 5 * Some(Err("error")).transpose().unwrapErr(); // "error" * None().transpose().unwrap().isNone(); // true * ``` */ transpose<U, E>(this: Option<import("./result.ts").Result<U, E>>): import("./result.ts").Result<Option<U>, E>; /** * Returns the contained Some value or a default value for the type. * Note: Unlike Rust, TypeScript doesn't have a Default trait, so this method * will throw an error. Use unwrapOr(defaultValue) instead. * @returns The Some value or throws an error. * @throws {Error} Always throws for None since TypeScript has no Default trait. * @example * ```typescript * Some(5).unwrapOrDefault(); // 5 * None().unwrapOrDefault(); // throws Error * ``` */ unwrapOrDefault(): T; } /** * @internal Implementation of the Some case for Option. Users should use the `Some` factory function. */ class SomeImpl<T> implements BaseOption<T> { #value!: T; constructor(value: T) { if (!(this instanceof SomeImpl)) { return new SomeImpl(value); } this.#value = value; } isSome(): true { return true; } isSomeAnd(fn: (value: T) => boolean): boolean { return fn(this.#value); } isNone(): false { return false; } expect(_message: string): T { return this.#value; } unwrap(): T { return this.#value; } unwrapOr(_defaultValue: T): T { return this.#value; } unwrapOrElse(_fn: () => T): T { return this.#value; } map<U>(fn: (value: T) => U): Option<U> { return Some(fn(this.#value)); } mapOr<U>(_defaultValue: U, fn: (value: T) => U): U { return fn(this.#value); } mapOrElse<U>(_defaultFn: () => U, fn: (value: T) => U): U { return fn(this.#value); } inspect(fn: (value: T) => void): Option<T> { fn(this.#value); return this; } and<U>(res: Option<U>): Option<U> { return res; } andThen<U>(fn: (value: T) => Option<U>): Option<U> { return fn(this.#value); } or(_res: Option<T>): Option<T> { return this; } orElse(_fn: () => Option<T>): Option<T> { return this; } xor(optb: Option<T>): Option<T> { if (optb.isSome()) { return None(); } return this; } cloned(): Option<T> { try { const clonedValue = structuredClone(this.#value); return Some(clonedValue); } catch (e) { console.warn("Failed to structuredClone Some value:", this.#value, e); return this; } } zip<U>(other: Option<U>): Option<[T, U]> { if (other.isSome()) { return Some([this.#value, other.unwrap()] as [T, U]); } return None(); } zipWith<U, R>(other: Option<U>, fn: (s: T, o: U) => R): Option<R> { if (other.isSome()) { return Some(fn(this.#value, other.unwrap())); } return None(); } match<U, V>(matcher: OptionMatcher<T, U, V>): U | V { return matcher.Some(this.#value); } flatten<U>(this: SomeImpl<Option<U>>): Option<U> { return this.#value; } filter(predicate: (value: T) => boolean): Option<T> { if (predicate(this.#value)) { return this; } return None(); } okOr<E>(_err: E): import("./result.ts").Result<T, E> { const { Ok } = require("./result.ts"); return Ok(this.#value); } okOrElse<E>(_fn: () => E): import("./result.ts").Result<T, E> { const { Ok } = require("./result.ts"); return Ok(this.#value); } transpose<U, E>(this: SomeImpl<import("./result.ts").Result<U, E>>): import("./result.ts").Result<Option<U>, E> { const result = this.#value as any; if (result.isOk()) { const { Ok } = require("./result.ts"); return Ok(Some(result.unwrap())); } else { const { Err } = require("./result.ts"); return Err(result.unwrapErr()); } } unwrapOrDefault(): T { return this.#value; } [Symbol.iterator](): Iterator<T extends Iterable<infer U> ? U : never> { const value = this.#value as T; if (value !== null && value !== undefined && typeof (value as any)[Symbol.iterator] === "function") { return (value as unknown as Iterable<T extends Iterable<infer U> ? U : never>)[Symbol.iterator](); } else { return { next(): IteratorResult<T extends Iterable<infer U> ? U : never> { return { done: true, value: undefined! }; } } as Iterator<T extends Iterable<infer U> ? U : never>; } } } /** * @internal Implementation of the None case for Option. Users should use the `None` singleton. */ class NoneImpl<T = never> implements BaseOption<T> { #value?: T; constructor() { } isSome(): false { return false; } isSomeAnd(_fn: (value: T) => boolean): false { return false; } isNone(): true { return true; } expect(message: string): T { throw new Error(message); } unwrap(): T { throw new Error('Tried to unwrap a None value'); } unwrapOr(defaultValue: T): T { return defaultValue; } unwrapOrElse(fn: () => T): T { return fn(); } map<U>(_fn: (value: T) => U): Option<U> { return None(); } mapOr<U>(defaultValue: U, _fn: (value: T) => U): U { return defaultValue; } mapOrElse<U>(defaultFn: () => U, _fn: (value: T) => U): U { return defaultFn(); } inspect(_fn: (value: T) => void): Option<T> { return None(); } and<U>(_res: Option<U>): Option<U> { return None(); } andThen<U>(_fn: (value: T) => Option<U>): Option<U> { return None(); } or<U>(res: Option<U>): Option<U> { return res; } orElse<U>(fn: () => Option<U>): Option<U> { return fn(); } xor<U>(optb: Option<U>): Option<U> { return optb; } cloned(): Option<T> { return None(); } zip<U>(_other: Option<U>): Option<[T, U]> { return None() } zipWith<U, R>(_other: Option<U>, _fn: (s: T, o: U) => R): Option<R> { return None() } match<U, V>(matcher: OptionMatcher<T, U, V>): U | V { return matcher.None(); } flatten<U>(): Option<U> { return None(); } filter(_predicate: (value: T) => boolean): Option<T> { return None(); } okOr<E>(err: E): import("./result.ts").Result<T, E> { const { Err } = require("./result.ts"); return Err(err); } okOrElse<E>(fn: () => E): import("./result.ts").Result<T, E> { const { Err } = require("./result.ts"); return Err(fn()); } transpose<U, E>(): import("./result.ts").Result<Option<U>, E> { const { Ok } = require("./result.ts"); return Ok(None()); } unwrapOrDefault(): T { throw new Error("Cannot unwrap None to default value. TypeScript doesn't have a Default trait. Use unwrapOr(defaultValue) instead."); } [Symbol.iterator](): Iterator<never> { return { next(): IteratorResult<never> { return { done: true, value: undefined! }; } }; } } /** * Represents the presence of a value (`Some`) in an {@link Option}. * Contains the value of type `T`. * @template T The type of the value. * @example * ```typescript * Some(10).unwrap(); // 10 * ``` */ export type Some<T> = SomeImpl<T>; export const Some = <T>(value: T): Some<T> => new SomeImpl(value); /** * Represents the absence of a value (`None`) in an {@link Option}. * @template T The type parameter for the Option * @example * ```typescript * None().isNone(); // true * const noValue: Option<number> = None<number>(); * // Or with type inference: * const noValue: Option<number> = None(); * ``` */ export type None<T = never> = NoneImpl<T>; export const None = <T = never>(): None<T> => new NoneImpl<T>(); /** * `Option<T>` is a type that represents an optional value: * every `Option` is either `Some` and contains a value, or `None` and does not. * @template T The type of the value that might be contained. */ export type Option<T> = Some<T> | None<T>; interface OptionTypeStatics { /** * Wraps a function that might return null or undefined, converting its result to an `Option<T>`. * - If `fn()` returns a non-null/non-undefined value `v`, it's wrapped in `Some(v)`. * - If `fn()` returns `null` or `undefined`, it's converted to `None`. * * @template T The type of the successful result of `fn`. * @param fn The function to wrap and execute. It should return `T | null | undefined`. * @returns An `Option<T>` representing the outcome. * @example * ```typescript * Option.fromNullable((): number | null => null).isNone(); // true * Option.fromNullable((): number | null => 5).unwrap(); // 5 * Option.fromNullable(() => null) === None; // true * ``` */ fromNullable<T>(fn: () => T | null | undefined): Option<NonNullable<T>>; /** * Checks if a value is an Option (either Some or None) created by this library. * Useful for type guards or conditional logic. * @param value The value to check. * @returns True if the value is a Some or None instance, false otherwise. * @example * ```typescript * Option.isOption(Some(1)); // true * Option.isOption(None); // true * Option.isOption(null); // false * ``` */ isOption<T>(value: unknown): value is Option<T>; } /** * Provides static methods for creating and handling Option instances. */ export const Option: OptionTypeStatics = { fromNullable<T>(fn: () => T | null | undefined): Option<NonNullable<T>> { const value = fn(); if (value === null || value === undefined) { return None(); } return Some(value as NonNullable<T>); }, isOption<T>(value: unknown): value is Option<T> { return value instanceof SomeImpl || value instanceof NoneImpl; } };