import type { Extensions, Unwrapped, Transform, Wrappable, Promised, Identity, Resolve, Wrapper, Wrapped, Reject, Monad, All, } from "./monad.types"; import { thenable } from "../utils/async"; import { errorify } from "../utils/error"; const empty = Symbol(); const nothing = [empty] as const; type Nothing = typeof empty | typeof nothing; const error = Symbol(); const state = Symbol(); function monad( transformer: Wrapper = (value, fn) => fn(value), extensions: Extensions = {} ) { const proto: Monad = { then(resolve, reject): any { if (native(reject) && native(resolve)) { try { resolve(this.unwrap()); } catch (message) { reject(message); } return create(this[state]); } return create(transform(this[state], resolve, reject, transformer)); }, catch(reject): any { return this.then((x) => x, reject); }, finally(handler): any { return this.then( (x) => (queueMicrotask(handler || (() => {})), x), (x) => { queueMicrotask(handler || (() => {})); throw x; } ); }, unwrap(fallback) { return unwrap(this[state], fallback); }, expose(): any { try { const data = unwrap(this[state]); if (thenable(data)) { return data.then( (x) => ({ data: x }), (e) => ({ error: invalidate(e)[error] }) ); } return { data }; } catch (reason) { return { error: invalidate(reason)[error] }; } }, get [Symbol.toStringTag]() { return "Monad"; }, }; const create = (value: T): Monad => { while (value && Object.getPrototypeOf(value) === extensions) { value = (value as any)[state]; } const instance = { [state]: value }; return Object.setPrototypeOf(instance, extensions); }; Object.setPrototypeOf(extensions, proto); return (value: T) => create(value).then((x) => x); } function all(values: T) { let container = monad()([] as any); let buffer: any[] = []; for (const value of values) { if (!thenable(value)) { buffer.push(value); continue; } const current = [...buffer]; container = container.then((x) => value.then((y) => [...x, ...current, y])); buffer = []; } return container.then((x) => [...x, ...buffer]) as All; } function unwrap( value: Monad | PromiseLike | T, fallback?: U ): Unwrapped | Promised { if (unwrappable(value)) value = value.unwrap(fallback) as any; if (thenable(value)) { value = value.then( (x) => unwrap(x, fallback) as any, (e) => { if (fallback !== undefined) return fallback; throw e; } ) as any; } if (invalid(value)) { if (fallback === undefined) throw value[error]; return fallback as any; } return value as any; } function transform( value: T, resolve?: Resolve, reject?: Reject, transformer: Wrapper = (value, fn) => fn(value) ) { return apply( (value) => { if (invalid(value)) throw value[error]; return resolve ? transformer(value, resolve as any) : value; }, (reason) => { try { return reject ? transformer(reason, reject) : invalidate(reason); } catch (reason) { return invalidate(reason); } } )(value); } function apply( resolve: Resolve, U>, reject: Reject ) { return function next(value: any): any { if (thenable(value)) return value.then(next, reject); try { return resolve ? resolve(value) : value; } catch (reason) { return reject ? reject(reason) : reason; } }; } function unwrappable(value: any): value is Wrappable { return ( value !== null && typeof value === "object" && "unwrap" in value && typeof value["unwrap"] === "function" ); } function native(fn: any, args = 1): fn is (...args: any[]) => void { if (typeof fn !== "function") return false; const signature = Function.prototype.toString.call(fn); return ( signature === "function () { [native code] }" && fn.length === args && fn.name === "" ); } function invalid(value: any): value is { [error]: any } { return value !== null && typeof value === "object" && error in value; } function invalidate(what: unknown): { [error]: Error } { if (invalid(what)) what = what[error]; return { [error]: errorify(what) }; } export { monad, all, unwrap, transform, state, nothing }; export type { Transform as Monad, Nothing };