UNPKG

fluent-results

Version:

Tiny, dependency-free TypeScript implementation of the Fluent Results pattern for railway-oriented programming.

287 lines (286 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Result = exports.AResult = void 0; const AError_1 = require("./AError"); const ExceptionalError_1 = require("./ExceptionalError"); const PromiseRejection_1 = require("./PromiseRejection"); class AResult { /** `true` when *no* {@link AError} has been recorded. */ get isSuccess() { return !this.isFailed; } /** `true` when **at least one** {@link AError} exists. */ get isFailed() { return this._reasons.some((r) => r instanceof AError_1.AError); } /** * A descriptive name for the routine, useful for logging or debugging. */ get routineName() { return this._routineName; } /** * The parent result that created this result (usually as to execute a contingent routine) */ get parent() { return this._parent; } /** * A child that was created as a result of execution of a contingent path */ get child() { return this._child; } set child(child) { this._child = child; } /** * @param routineName A descriptive name for the routine, useful for logging or debugging. * @param [parent] The parent result that created this result (usually as to execute a contingent routine) */ constructor(routineName, parent) { /** Informational messages *and* errors gathered so far. */ this._reasons = []; /** Single‑slot cache for the latest successful value. */ this.stateCache = []; this._routineName = ""; this._routineName = routineName; this._parent = parent; } } exports.AResult = AResult; /** * `Result` represents the outcome **and** the flowing state of a pipeline that can * short‑circuit on the first error ("railway‑oriented programming"). * * A `Result` starts out *successful* and accumulates {@link AReason | reasons}; * any {@link AError | error} automatically flips the result into the *failed* state. * */ class Result extends AResult { /** * The most recent value produced by the pipeline. * @throws {Error} If no value has been cached yet (typically because the pipeline only ran parameter‑less steps). */ get currentState() { if (this.stateCache.length === 1) { return this.stateCache[0]; } throw new Error('No state present. Ensure a previous delegate returns a value before attempting to read currentState.'); } /** Immutable copy of informational reasons **and** errors. */ get reasons() { return this._reasons.slice(); } /** Convenience subset of {@link reasons} limited to errors. */ get errors() { return this._reasons.filter((r) => r instanceof AError_1.AError); } /** * Executes `action` and wraps its outcome into a new **root** `Result`. * * • If `action` throws, the exception is captured as an {@link ExceptionalError}. * • If it completes successfully, the return value is stored as {@link currentState}. * * @param action A synchronous delegate that may return a value and/or throw. * @param routineName A descriptive name for the routine, useful for logging or debugging. */ static try(action, routineName) { const result = new Result(routineName); try { result.cacheState(action()); } catch (e) { result._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } finally { return result; } } /** * Executes `action` and wraps its awaited outcome into a new **root** `Result`. * * • If `action` throws, the exception is captured as an {@link ExceptionalError}. * • If it completes successfully, the awaited outcome is stored as {@link currentState}. * * @param action A synchronous delegate that may return a value and/or throw. * @param routineName A descriptive name for the routine, useful for logging or debugging. */ static async tryAsync(action, routineName) { const result = new Result(routineName); try { await action().then(value => result.cacheState(value)).catch(reason => result._reasons.push(new PromiseRejection_1.PromiseRejection(reason))); } catch (e) { result._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } finally { return result; } } /** * Chains another synchronous function into the pipeline. * * @param func Delegate to execute. * • If the previous step succeeded, its return value becomes the input when `func` has an arity of **1**. * • If the previous step failed, `func` is **skipped**. * * @returns **this** so that calls can be fluently chained. */ bind(func) { if (this.isSuccess) { try { const out = func.length === 0 ? func() : func(this.currentState); this.cacheState(out); } catch (e) { this._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } } return this; } /** * Chains another synchronous function into the pipeline and captures its awaited outcome as {@link currentState}. * * @param func A delegate returning a promise. * • If the previous step succeeded, its return value becomes the input when `func` has an arity of **1**. * • If the previous step failed, `func` is **skipped**. * * @returns **this** so that calls can be fluently chained. */ async bindAsync(func) { if (this.isSuccess) { try { const promise = func.length === 0 ? func() : func(this.currentState); await promise.then(value => this.cacheState(value)).catch(reason => this._reasons.push(new PromiseRejection_1.PromiseRejection(reason))); } catch (e) { this._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } } return this; } /** * Keeps the pipeline successful **only if** the `predicate` evaluates to `true`. * * @param predicate Condition to evaluate (optionally with `currentState` input). * @param error Error instance to push when the predicate fails. * * @returns **this** for chaining. */ okIf(predicate, error) { if (this.isSuccess) { try { const pass = predicate.length === 0 ? predicate() : predicate(this.currentState); if (!pass) this._reasons.push(error); } catch (e) { this._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } } return this; } /** * Keeps the pipeline successful **only if** the `predicate` evaluates to `true`. * * @param predicate A function returning promise that returns boolean when awaited (optionally with `currentState` input). * @param error Error instance to push when the predicate fails. * * @returns **this** for chaining. */ async okIfAsync(predicate, error) { if (this.isSuccess) { try { const pass = predicate.length === 0 ? await predicate() : await predicate(this.currentState); if (!pass) this._reasons.push(error); } catch (e) { this._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } } return this; } /** * Fails the pipeline **only if** the `predicate` evaluates to `true`. * * @param predicate Condition to evaluate (optionally with `currentState` input). * @param error Error instance to push when the predicate **passes**. * @param [contingency] - Optional object defining a contingent route. * @param next.func - A function to be executed if {@link predicate} evaluates to false * @param next.routineName - A descriptive name for the routine, useful for logging or debugging. * * @returns **this** for chaining. */ failIf(predicate, error, contingency) { if (this.isSuccess) { try { const fail = predicate.length === 0 ? predicate() : predicate(this.currentState); if (fail) { this._reasons.push(error); if (contingency) { let child = new Result(contingency.routineName, this); this.child = child; contingency.func(child); } } } catch (e) { this._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } } return this; } /** * Fails the pipeline **only if** the `predicate` evaluates to `true`. * * @param predicate A function returning promise that returns boolean when awaited (optionally with `currentState` input). * @param error Error instance to push when the predicate **passes**. * @param [contingency] - Optional object defining a contingent route. * @param next.func - A function to be executed if {@link predicate} evaluates to false * @param next.routineName - A descriptive name for the routine, useful for logging or debugging. * * @returns **this** for chaining. */ async failIfAsync(predicate, error, contingency) { if (this.isSuccess) { try { const fail = predicate.length === 0 ? await predicate() : await predicate(this.currentState); if (fail) { this._reasons.push(error); if (contingency) { let child = new Result(contingency.routineName, this); this.child = child; contingency.func(child); } } } catch (e) { this._reasons.push(new ExceptionalError_1.ExceptionalError(e)); } } return this; } /** * Internal helper—overwrites the single‑slot {@link stateCache}. * Not exposed publicly on purpose. */ cacheState(value) { this.stateCache.length = 0; this.stateCache.push(value); } } exports.Result = Result;