import { ActionResult, failureOn, isActionResult, successOn, } from "./ActionResult"; export type TOp = (p: T) => Promise; export type TAction = (t: T) => Promise>; export type Chainable = TAction | TOp; /** * Chain the actions, in the given order * @param {ProjectEditor} steps * @return {ProjectEditor} */ export function actionChain(...steps: Array>): TAction { return actionChainWithCombiner((r1, r2) => ({ ...r1, // the clojure ppl will LOVE this (I love it) ...r2, }), ...steps); } export function actionChainWithCombiner = ActionResult>( combiner: (a: R, b: R) => R, ...steps: Array>): TAction { return steps.length === 0 ? NoAction : steps.reduce((c1, c2) => { const ed1: TAction = toAction(c1); const ed2: TAction = toAction(c2); return p => ed1(p).then(r1 => { // console.log("Applied action " + c1.toString()); if (!r1.success) { return r1; } else { return ed2(r1.target).then(r2 => { // console.log("Applied action " + c2.toString()); const combinedResult: any = combiner((r1 as R), (r2 as R)); return combinedResult; }); } }); }) as TAction; // Consider adding R as a type parameter to TAction } function toAction(link: Chainable): TAction { return p => { try { const oneOrTheOther: Promise> = (link as TOp)(p); return oneOrTheOther .catch(err => failureOn(p, err, link)) .then(r => { // See what it returns return isActionResult(r) ? r : successOn(r) as ActionResult; }); } catch (error) { // console.error("Failure: " + error.message); return Promise.resolve(failureOn(p, error, link)); } }; } /** * Useful starting point for chaining */ export const NoAction: TAction = t => Promise.resolve(successOn(t));