import { MapOption, NoneValue, Ok, Option, OptionFrom, OptionLike, OptionMapOption, OptionMapOrElse, OptionMapResult, OptionPromise, OptionPromiseMapOption, OptionPromiseMapOrElse, OptionPromiseMapResult, Result, ResultPromise, SomeValue, UnwrapableOption, } from "./mod"; /** * Create an {@linkcode OptionPromise} from a value. * @example * ```typescript * declare function calculate(n: number): OptionPromise; * Some(42) * .mapOption( * // when using Some here, the compiler will error on calculate with an Argument Error * () => SomePromise(-1), * calculate * ); * ``` */ export function SomePromise(value: T): OptionPromise { return Some(Promise.resolve(value)) as OptionPromise; } /** * `None` creates an Option that has no associated value. It might be useful to * pass a type argument: * ```typescript * const token = None(); * token.insert(12); // will give a compile error that the argument must be string * ``` */ export function None(): Option { return Some() as Option; // Not a mistake Some(undefined) returns a None } /** * Create an {@linkcode OptionPromise} without a value. * @example * ```typescript * declare function calculate(n: number): OptionPromise; * Some(42) * .mapOption( * // when using Some here, the compiler will error on calculate with an Argument Error * () => NonePromise, * calculate * ); * ``` */ export function NonePromise(): OptionPromise { return Some(Promise.resolve()) as OptionPromise; } /** * `Some` creates an Option which has an associated value. The type argument can be * inferred from the argument. Actually, `Some` is a bit special, as it also might * return a `None` value: * ```typescript * // All the statements below return a None * Some(); * Some(null); * Some(Infinity); * Some(NaN); * ``` * Passing a promise to `Some` results in an {@linkcode OptionPromise}, which can be * convenient. When a `OptionPromise` is required e.g., for {@linkcode mapOrElse}, * {@linkcode NonePromise} or {@linkcode SomePromise} */ export function Some( value?: T | undefined | null, ): OptionFrom { return (value instanceof PromisedOption || value instanceof OptionValue ? value : value instanceof SomeValue || value instanceof NoneValue ? OptionValue.from(value) : value instanceof Promise ? PromisedOption.from(value) : value === undefined || (typeof value == "object" && value == null) || (typeof value == "number" && (Number.isNaN(value) || value == Infinity)) ? OptionValue.from(new NoneValue()) : OptionValue.from(new SomeValue(value))) as OptionFrom; } /** * Test that a variable implements the {@linkcode Option} interface * @returns true if variable can be cast to `Option` * * @example * ```typescript * const vut: unknown = Some(42); * if (isOption(vut)) { * console.log(vut.unwrapOr(-99)); * } * ``` */ export function isOption( possibleOption: unknown, ): possibleOption is Option { return possibleOption instanceof OptionValue; } /** * Test that a variable implements the {@linkcode OptionPromise} interface * @returns true if variable can be cast to `OptionPromise` * * @example * const vut: unknown = Some(Promise.resolve(42)); * if (isOptionPromise(vut)) { * vut.map(console.log); * } */ export function isOptionPromise( possibleOption: unknown, ): possibleOption is OptionPromise { return possibleOption instanceof PromisedOption; } export function isOptionLike( possibleOption: unknown, ): possibleOption is OptionLike { return isOption(possibleOption) || isOptionPromise(possibleOption); } export class OptionValue implements Option, UnwrapableOption { constructor( private option: UnwrapableOption, ) {} get type(): symbol { return this.option.type; } static from(option: UnwrapableOption): Option { return new OptionValue(option); } and(optb: Option): Option { return this.option.and(optb); } andThen(fn: (some: T) => Promise>): OptionPromise; andThen(fn: (some: T) => Option): Option; andThen( fn: (some: T) => Option | Promise>, ): OptionPromise | Option { return this.option.andThen(fn as (some: T) => Option); } filter(predicate: (some: T) => boolean): Option { return this.option.filter(predicate); } flatten(this: Option): Option; flatten(this: Option>): Option; flatten(this: Option | Option>): Option { return ((this as unknown as OptionValue).option as unknown as Option< Option >).flatten(); } getOrInsert(value: T): T { const optional = this.option.orElse(() => { this.option = new SomeValue(value); return this; }); return (optional as unknown as UnwrapableOption).unwrap(); } getOrInsertWith(fn: () => T): T { const optional = this.option.orElse(() => { this.option = new SomeValue(fn()); return this; }); return (optional as unknown as UnwrapableOption).unwrap(); } insert(value: T) { this.option = new SomeValue(value); return value; } isSome(): boolean { return this.option.isSome(); } isNone(): boolean { return this.option.isNone(); } map(fn: (some: T) => U): MapOption { return this.option.map(fn as (some: T) => U); } mapOption( def: () => U, fn: (some: T) => U, ): OptionMapOption { return this.option.mapOption(def, fn); } mapResult( def: () => U, fn: (some: T) => U, ): OptionMapResult { return this.option.mapResult(def, fn); } mapOrElse( def: () => U, fn: (some: T) => U, ): OptionMapOrElse { return this.option.mapOrElse(def, fn); } okOr(err: E): Result { return this.option.okOr(err); } okOrElse(fn: () => Promise): ResultPromise; okOrElse(fn: () => E): Result; okOrElse(fn: () => Promise | E): ResultPromise | Result { return this.option.okOrElse(fn as () => E); } or(optb: Option): Option { return this.option.or(optb); } orElse(fn: () => Promise>): OptionPromise; orElse(fn: () => Option): Option; orElse( fn: () => Option | Promise>, ): OptionPromise | Option { return this.option.orElse(fn as () => Option); } replace(value: T): Option { const old = OptionValue.from(this.option); this.insert(value); return old; } take(): Option { const result = this.flatten(); this.option = new NoneValue(); return result; } unwrap(): T { return this.option.unwrap(); } unwrapOr(def: T): T { return this.option.unwrapOr(def); } unwrapOrElse(def: () => T): T; unwrapOrElse(def: () => Promise): T | Promise; unwrapOrElse(def: () => T | Promise): T | Promise { return this.option.unwrapOrElse(def as () => T); } xor(optb: Option): Option { return this.option.xor(optb); } [Symbol.iterator]() { return this.option[Symbol.iterator](); } } export class PromisedOption implements OptionPromise { promise: Promise>; constructor( promise: Promise>, ) { this.promise = promise.then( (resolved) => isOptionLike(resolved) ? resolved : Some(resolved), (_reason) => None(), ); } get [Symbol.toStringTag](): string { return `OptionPromise`; } static from(promise: Promise>): OptionPromise { return new PromisedOption(promise); } then, TResult2 = never>( onfulfilled?: | ((value: Option) => TResult1 | PromiseLike) | undefined | null, onrejected?: // deno-lint-ignore no-explicit-any | ((reason: any) => TResult2 | PromiseLike) | undefined | null, ): Promise { return this.promise.then(onfulfilled, onrejected); } catch( onrejected?: | ((reason: unknown) => TResult | PromiseLike) | null | undefined, ): Promise | TResult> { return this.promise.catch(onrejected); } finally(onfinally?: (() => void) | null | undefined): Promise> { return this.promise.finally(onfinally); } and(optb: Option): OptionPromise { return PromisedOption.from( this.promise.then((option) => option.and(optb)), ); } andThen(fn: (some: T) => OptionPromise): OptionPromise; andThen(fn: (some: T) => Promise>): OptionPromise; andThen(fn: (some: T) => Option): OptionPromise; andThen( fn: (some: T) => Option | Promise>, ): OptionPromise { return PromisedOption.from( this.promise.then((option) => { return option.andThen(fn as (some: T) => Option); }), ); } filter(predicate: (some: T) => boolean): OptionPromise { return PromisedOption.from( this.promise.then((option) => option.filter(predicate)), ); } flatten(this: Option>): OptionPromise; flatten(this: Option): OptionPromise; flatten(this: Option | Option>): OptionPromise { return PromisedOption.from( (this as unknown as PromisedOption).promise.then((option) => option.flatten() as unknown as Option ), ); } isSome(): Promise { return this.promise.then((option) => option.isSome()); } isNone(): Promise { return this.promise.then((option) => option.isNone()); } map(fn: (some: T) => U): OptionPromise { return Some( this.promise.then((option) => option.map(fn as (some: T) => U) as OptionPromise ), ); } mapOption( def: () => U, fn: (some: T) => U, ): OptionPromiseMapOption { return Some(this.promise.then( (option) => option.mapOption(def, fn) as Promise>, )) as OptionPromiseMapOption; } mapResult( def: () => U, fn: (some: T) => U, ): OptionPromiseMapResult { return Ok(this.promise.then( (option) => option.mapResult(def, fn) as Promise>, )) as OptionPromiseMapResult; } mapOrElse( def: () => U, fn: (some: T) => U, ): OptionPromiseMapOrElse { return this.promise.then((option) => { return option.mapOrElse(def, fn); }) as OptionPromiseMapOrElse; } okOr(err: E): ResultPromise { return Ok( this.promise.then((option) => option.okOr(err)), ); } okOrElse(fn: () => Promise): ResultPromise; okOrElse(fn: () => E | Promise): ResultPromise { return Ok( this.promise.then((option) => option.okOrElse(fn as () => Promise)), ); } or(optb: Option): OptionPromise { return PromisedOption.from( this.promise.then((option) => option.or(optb)), ); } orElse(fn: () => Promise>): OptionPromise; orElse(fn: () => Option): OptionPromise; orElse(fn: () => Option | Promise>): OptionPromise { return PromisedOption.from( this.promise.then((option) => { return option.orElse(fn as () => Option); }), ); } unwrapOr(def: T): Promise { return this.promise.then((option) => option.unwrapOr(def)); } unwrapOrElse(def: () => T): Promise; unwrapOrElse(def: () => Promise): Promise; unwrapOrElse(def: () => T | Promise): Promise { return this.promise.then((option) => option.unwrapOrElse(def as () => T)); } xor(optb: Option): OptionPromise { return PromisedOption.from( this.promise.then((option) => option.xor(optb)), ); } }