UNPKG

@irrelon/forerunnerdb-core

Version:

ForerunnerDB core utilities for operating on JSON data.

129 lines (109 loc) 4.17 kB
import CoreClass from "./CoreClass"; import OperationResult from "../operations/OperationResult"; import OperationSuccess from "../operations/OperationSuccess"; import OperationFailure from "../operations/OperationFailure"; /** * @typedef {Object} ExecuteOptions * @property {Boolean} [$atomic] If true, any failure at any point in the * pipeline will result in the complete failure of the whole pipeline. * @property {Boolean} [$ordered] If true, any failure at any point in the * pipeline will result in the pipeline cancelling at that point but all * previous work will remain in place. */ /** * @callback StepFunction * @param {*} data The data the function is given by the pipeline. * @param {*} originalData The value of the data before any pipeline steps * changed it. This will always be the value that `data` was before it got * changed. */ class Pipeline extends CoreClass { constructor () { super(); this._steps = new Map(); } /** * Add a step to the pipeline. * @param {String} name The unique name of the step. * @param {StepFunction} func The function to run when the step is executed. * @param {ExecuteOptions} [options] An options object. * @returns {Boolean} True if the step was added, false if a step of * that name already exists. */ addStep (name, func, options) { if (this._steps.get(name)) return false; this._steps.set(name, {name, func, options}); return true; } /** * Removes a step from the pipeline by name. * @param {String} name The name of the step to remove. * @returns {void} Nothing. */ removeStep (name) { this._steps.delete(name); } /** * Execute the pipeline steps with the passed data. * @param {String} name The name of the step to execute. * @param {Object} [data] The data to pass to the pipeline. * @param {Object} [originalData] The original data passed to the * pipeline. This is different from `data` in that `data` can be * updated by a pipeline step whereas `originalData` remains the * value that `data` was originally before the pipeline was executed. * @returns {Promise<OperationSuccess|OperationFailure>} The result of the operation. */ executeStep = async (name, data, originalData) => { const step = this._steps.get(name); if (!step) throw new Error(`No step with the name "${name}" exists!`); const stepResult = await step.func(data, originalData); // Check the result conforms with our expected output if (!(stepResult instanceof OperationSuccess || stepResult instanceof OperationFailure)) { throw new Error(`Return value from step "${name}" was not an OperationSuccess or OperationFailure instance!`); } return stepResult; } /** * Execute the pipeline steps with the passed data. * @param {Object} [data] The data to pass to the pipeline. * @param {ExecuteOptions} options An options object. * @returns {Promise<OperationResult>} The result of the operation. */ execute = async (data, options = {"$atomic": false, "$ordered": false}) => { const operationResult = new OperationResult(); const originalData = data; const steps = this._steps.keys(); let iteratorResult; let currentStepData = data; while ((iteratorResult = steps.next())) { if (iteratorResult.done) { break; } const stepName = iteratorResult.value; const stepResult = await this.executeStep(stepName, currentStepData, originalData); // Check if we are atomic and have a failure if (stepResult instanceof OperationFailure) { if (options.$atomic) { // Fail the entire pipeline operationResult.clearSuccess(); operationResult.addFailure(stepResult); return operationResult; } if (options.$ordered) { // Fail the rest of the pipeline operationResult.addFailure(stepResult); return operationResult; } } // Add the result to the overall operation operationResult.addResult(stepResult); // Check if the result provided us with new data to pass to the // next step in the pipeline if (stepResult.data !== undefined) { currentStepData = stepResult.data; } } return operationResult; } } export default Pipeline;