UNPKG

runflow

Version:

A fast and reliable flow engine for orchestration and more uses in *Node.js*

1,432 lines (1,411 loc) 44.8 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ArrayMapResolver: () => ArrayMapResolver, ConditionalResolver: () => ConditionalResolver, EchoResolver: () => EchoResolver, Flow: () => Flow, FlowManager: () => FlowManager, FlowStateEnum: () => FlowStateEnum, FlowTransitionEnum: () => FlowTransitionEnum, LoopResolver: () => LoopResolver, NoopResolver: () => NoopResolver, PauseResolver: () => PauseResolver, RepeaterResolver: () => RepeaterResolver, StopResolver: () => StopResolver, SubFlowResolver: () => SubFlowResolver, Task: () => Task, TaskResolver: () => TaskResolver, TaskResolverMap: () => TaskResolverMap, TaskSpecMap: () => TaskSpecMap, ThrowErrorResolver: () => ThrowErrorResolver, WaitResolver: () => WaitResolver }); module.exports = __toCommonJS(index_exports); // src/types.ts var FlowStateEnum = /* @__PURE__ */ ((FlowStateEnum2) => { FlowStateEnum2["Ready"] = "Ready"; FlowStateEnum2["Running"] = "Running"; FlowStateEnum2["Finished"] = "Finished"; FlowStateEnum2["Pausing"] = "Pausing"; FlowStateEnum2["Paused"] = "Paused"; FlowStateEnum2["Stopping"] = "Stopping"; FlowStateEnum2["Stopped"] = "Stopped"; return FlowStateEnum2; })(FlowStateEnum || {}); var FlowTransitionEnum = /* @__PURE__ */ ((FlowTransitionEnum2) => { FlowTransitionEnum2["Start"] = "Start"; FlowTransitionEnum2["Finished"] = "Finished"; FlowTransitionEnum2["Reset"] = "Reset"; FlowTransitionEnum2["Pause"] = "Pause"; FlowTransitionEnum2["Paused"] = "Paused"; FlowTransitionEnum2["Resume"] = "Resume"; FlowTransitionEnum2["Stop"] = "Stop"; FlowTransitionEnum2["Stopped"] = "Stopped"; return FlowTransitionEnum2; })(FlowTransitionEnum || {}); var TaskResolver = class { exec(_params, _context, _task, _debug, _log) { return {}; } }; var TaskResolverMap = class { }; // src/engine/flow-manager.ts var import_fs = require("fs"); var http = __toESM(require("http")); var https = __toESM(require("https")); // src/debug.ts var import_debug = __toESM(require("debug")); var debugs = {}; var debug_default = (scope) => { let d = debugs[scope]; if (typeof d === "undefined") { d = (0, import_debug.default)(`flowed:${scope}`); debugs[scope] = d; } return d; }; // src/engine/task-process.ts var _TaskProcess = class _TaskProcess { constructor(manager, id, task, taskResolverExecutor, context, automapParams, automapResults, flowId, debug2, log) { this.manager = manager; this.id = id; this.task = task; this.taskResolverExecutor = taskResolverExecutor; this.context = context; this.automapParams = automapParams; this.automapResults = automapResults; this.flowId = flowId; this.debug = debug2; this.log = log; this.pid = _TaskProcess.nextPid; _TaskProcess.nextPid = (_TaskProcess.nextPid + 1) % Number.MAX_SAFE_INTEGER; } getParams() { return this.params; } run() { var _a; this.params = this.task.mapParamsForResolver( this.task.runStatus.solvedReqs.popAll(), this.automapParams, this.flowId, this.log ); let resolverFn = this.taskResolverExecutor; let resolverThis = void 0; const isClassResolver = (_a = this.taskResolverExecutor.prototype) == null ? void 0 : _a.exec; if (isClassResolver) { const resolverInstance = new this.taskResolverExecutor(); resolverFn = resolverInstance.exec; resolverThis = resolverInstance; } return new Promise((resolve, reject) => { var _a2; const onResolverSuccess = (resolverValue) => { this.task.runStatus.solvedResults = this.task.mapResultsFromResolver( resolverValue, this.automapResults, this.flowId, this.log ); resolve(this.task.runStatus.solvedResults); }; const onResolverError = (error) => { reject(error); }; let resolverResult; try { resolverResult = resolverFn.call( resolverThis, this.params, this.context, this.task, this.debug, this.log ); } catch (error) { onResolverError(error); } const resultIsObject = typeof resolverResult === "object"; const resultIsPromise = ((_a2 = resolverResult == null ? void 0 : resolverResult.constructor) == null ? void 0 : _a2.name) === "Promise"; if (!resultIsObject) { throw new Error( `Expected resolver for task '${this.task.code}' to return an object or Promise that resolves to object. Returned value is of type '${typeof resolverResult}'.` ); } if (resultIsPromise) { resolverResult.then(onResolverSuccess).catch(onResolverError); } else { onResolverSuccess(resolverResult); } }); } }; _TaskProcess.nextPid = 1; var TaskProcess = _TaskProcess; // src/resolver-library.ts var NoopResolver = class { exec() { return {}; } }; var EchoResolver = class { exec(params) { return { out: params.in }; } }; var ThrowErrorResolver = class { exec(params) { throw new Error( typeof params.message !== "undefined" ? params.message : "ThrowErrorResolver resolver has thrown an error" ); } }; var ConditionalResolver = class { exec(params) { return params.condition ? { onTrue: params.trueResult } : { onFalse: params.falseResult }; } }; var WaitResolver = class { exec(params) { return new Promise((resolve) => { setTimeout(() => { resolve({ result: params.result }); }, params.ms); }); } }; var SubFlowResolver = class { async exec(params, context) { let flowResolvers = params.flowResolvers; if (typeof flowResolvers === "undefined") { flowResolvers = context.$flowed.getResolvers(); } let flowResult = await FlowManager.run( params.flowSpec, params.flowParams, params.flowExpectedResults, flowResolvers, context, context.$flowed.flow.runStatus.runOptions ); if (typeof params.uniqueResult === "string") { flowResult = flowResult[params.uniqueResult]; } return { flowResult }; } }; var RepeaterResolver = class { async exec(params, context, task, debug2, log) { const resolver = context.$flowed.getResolverByName(params.resolver); if (resolver === null) { throw new Error( `Task resolver '${params.resolver}' for inner flowed::Repeater task has no definition.` ); } const innerTask = new Task("task-repeat-model", params.taskSpec); const resultPromises = []; let results = []; for (let i = 0; i < params.count; i++) { innerTask.resetRunStatus(); innerTask.supplyReqs(params.taskParams); const process = new TaskProcess( context.$flowed.processManager, 0, innerTask, resolver, context, !!params.resolverAutomapParams, !!params.resolverAutomapResults, params.flowId, debug2, log ); const result = process.run(); if (params.parallel) { resultPromises.push(result); } else { results.push(await result); } } if (params.parallel) { results = await Promise.all(resultPromises); } return { results }; } }; var ArrayMapResolver = class { async exec(params, context, task, debug2, log) { const resolver = context.$flowed.getResolverByName(params.resolver); if (resolver === null) { throw new Error( `Task resolver '${params.resolver}' for inner flowed::ArrayMap task has no definition.` ); } const innerTask = new Task("task-loop-model", params.spec); const resultPromises = []; let results = []; for (const taskParams of params.params) { innerTask.resetRunStatus(); innerTask.supplyReqs(taskParams); const process = new TaskProcess( context.$flowed.processManager, 0, innerTask, resolver, context, !!params.automapParams, !!params.automapResults, params.flowId, debug2, log ); const result = process.run(); if (params.parallel) { resultPromises.push(result); } else { results.push(await result); } } if (params.parallel) { results = await Promise.all(resultPromises); } return { results }; } }; var LoopResolver = class { async exec(params, context, task, debug2, log) { const resolverName = params.subtask.resolver.name; const resolver = context.$flowed.getResolverByName(resolverName); if (resolver === null) { throw new Error( `Task resolver '${resolverName}' for inner flowed::Loop task has no definition.` ); } const innerTask = new Task("task-loop-model", params.subtask); const resultPromises = []; let outCollection = []; for (const item of params.inCollection) { const taskParams = { [params.inItemName]: item }; innerTask.resetRunStatus(); innerTask.supplyReqs(taskParams); const process = new TaskProcess( context.$flowed.processManager, 0, innerTask, resolver, context, !!params.automapParams, !!params.automapResults, params.flowId, debug2, log ); const itemResultPromise = process.run(); if (params.parallel) { resultPromises.push(itemResultPromise); } else { const itemResult = await itemResultPromise; outCollection.push(itemResult[params.outItemName]); } } if (params.parallel) { const outCollectionResults = await Promise.all(resultPromises); outCollection = outCollectionResults.map((itemResult) => itemResult[params.outItemName]); } return { outCollection }; } }; var StopResolver = class { exec(params, context) { return { promise: context.$flowed.flow.stop() }; } }; var PauseResolver = class { exec(params, context) { return { promise: context.$flowed.flow.pause() }; } }; // src/engine/flow-state/flow-state.ts var _FlowState = class _FlowState { constructor(runStatus) { this.runStatus = runStatus; } start(params, expectedResults, resolvers, context, _options = {}) { throw this.createTransitionError("Start" /* Start */); } finished(_error = false) { throw this.createTransitionError("Finished" /* Finished */); } pause() { throw this.createTransitionError("Pause" /* Pause */); } paused(_error = false) { throw this.createTransitionError("Paused" /* Paused */); } resume() { throw this.createTransitionError("Resume" /* Resume */); } stop() { throw this.createTransitionError("Stop" /* Stop */); } stopped(_error = false) { throw this.createTransitionError("Stopped" /* Stopped */); } reset() { throw this.createTransitionError("Reset" /* Reset */); } execFinishResolve() { this.runStatus.finishResolve(this.runStatus.results); } execFinishReject(error) { this.runStatus.finishReject(error); } isRunning() { return this.runStatus.processManager.runningCount() > 0; } setExpectedResults(expectedResults) { const missingExpected = expectedResults.filter( (r) => !this.runStatus.taskProvisions.includes(r) ); if (missingExpected.length > 0) { const msg = `The results [${missingExpected.join(", ")}] are not provided by any task`; if (this.runStatus.options.throwErrorOnUnsolvableResult) { throw new Error(msg); } else { this.log({ m: msg, l: "w" }); } } this.runStatus.expectedResults = [...expectedResults]; } getResults() { return this.runStatus.results; } setResolvers(resolvers) { this.runStatus.resolvers = resolvers; } setContext(context) { this.runStatus.context = __spreadValues({ $flowed: { getResolverByName: this.getResolverByName.bind(this), getResolvers: this.getResolvers.bind(this), processManager: this.runStatus.processManager, flow: this.runStatus.flow } }, context); } setRunOptions(options) { const defaultRunOptions = { debugKey: "flow", instanceId: null, // @todo check if it would be better to move this field into logFields logFields: {} }; this.runStatus.runOptions = Object.assign(defaultRunOptions, options); } supplyParameters(params) { for (const [paramCode, paramValue] of Object.entries(params)) { this.runStatus.state.supplyResult(paramCode, paramValue); } } createFinishPromise() { this.runStatus.finishPromise = new Promise((resolve, reject) => { this.runStatus.finishResolve = resolve; this.runStatus.finishReject = reject; }); } getResolverForTask(task) { const name = task.getResolverName(); const resolver = this.getResolverByName(name); if (resolver === null) { throw new Error( `Task resolver '${name}' for task '${task.code}' has no definition. Defined custom resolvers are: [${Object.keys( this.runStatus.resolvers ).join(", ")}].` ); } return resolver; } getResolverByName(name) { const resolvers = this.runStatus.resolvers; const hasCustomResolver = typeof resolvers[name] !== "undefined"; if (hasCustomResolver) { return resolvers[name]; } const hasPluginResolver = typeof FlowManager.plugins.resolvers[name] !== "undefined"; if (hasPluginResolver) { return FlowManager.plugins.resolvers[name]; } const hasBuiltInResolver = typeof _FlowState.builtInResolvers[name] !== "undefined"; if (hasBuiltInResolver) { return _FlowState.builtInResolvers[name]; } return null; } getResolvers() { const customResolvers = this.runStatus.resolvers; const pluginResolvers = FlowManager.plugins.resolvers; const builtInResolver = _FlowState.builtInResolvers; return __spreadValues(__spreadValues(__spreadValues({}, builtInResolver), pluginResolvers), customResolvers); } supplyResult(resultName, result) { const suppliesSomeTask = typeof this.runStatus.tasksByReq[resultName] !== "undefined"; if (suppliesSomeTask) { const suppliedTasks = this.runStatus.tasksByReq[resultName]; const suppliedTaskCodes = Object.keys(suppliedTasks); for (const taskCode of suppliedTaskCodes) { const suppliedTask = suppliedTasks[taskCode]; suppliedTask.supplyReq(resultName, result); if (suppliedTask.isReadyToRun()) { this.runStatus.tasksReady.push(suppliedTask); } } } const isExpectedResult = this.runStatus.expectedResults.indexOf(resultName) > -1; if (isExpectedResult) { this.runStatus.results[resultName] = result; } } getStateInstance(state) { return this.runStatus.states[state]; } startReadyTasks() { const readyTasks = this.runStatus.tasksReady; this.runStatus.tasksReady = []; for (const task of readyTasks) { const taskResolver = this.runStatus.state.getResolverForTask(task); const process = this.runStatus.processManager.createProcess( task, taskResolver, this.runStatus.context, !!this.runStatus.options.resolverAutomapParams, !!this.runStatus.options.resolverAutomapResults, this.runStatus.id, this.debug, this.log.bind(this) ); const errorHandler = (error) => { this.processFinished(process, error, true); }; process.run().then(() => { this.processFinished(process, false, true); }, errorHandler).catch(errorHandler); this.log({ n: this.runStatus.id, m: `Task '${task.code}(${task.getResolverName()})' started, params: %O`, mp: process.getParams(), e: "TS", pid: process.pid, task: { code: task.code, type: task.getResolverName() } }); } } setState(newState) { const prevState = this.runStatus.state.getStateCode(); this.runStatus.state = this.getStateInstance(newState); this.log({ n: this.runStatus.id, m: `Changed flow state from '${prevState}' to '${newState}'`, l: "d", e: "FC" }); } getSerializableState() { throw this.createMethodError("getSerializableState"); } processFinished(process, error, stopFlowExecutionOnError) { var _a; this.runStatus.processManager.removeProcess(process); const task = process.task; const taskCode = task.code; const taskSpec = task.spec; const taskProvisions = (_a = taskSpec.provides) != null ? _a : []; const taskResults = task.getResults(); const hasDefaultResult = Object.prototype.hasOwnProperty.call(taskSpec, "defaultResult"); if (error) { this.log({ n: this.runStatus.id, m: `Error in task '${taskCode}', results: %O`, mp: taskResults, l: "e", e: "TF", pid: process.pid, task: { code: task.code, type: task.getResolverName() } }); } else { this.log({ n: this.runStatus.id, m: `Finished task '${taskCode}', results: %O`, mp: taskResults, e: "TF", pid: process.pid, task: { code: task.code, type: task.getResolverName() } }); } for (const resultName of taskProvisions) { if (Object.prototype.hasOwnProperty.call(taskResults, resultName)) { this.runStatus.state.supplyResult(resultName, taskResults[resultName]); } else if (hasDefaultResult) { this.runStatus.state.supplyResult(resultName, taskSpec.defaultResult); } else { this.log({ n: this.runStatus.id, m: `Expected value '${resultName}' was not provided by task '${taskCode}' with resolver '${task.getResolverName()}'. Consider using the task field 'defaultResult' to provide values by default.`, l: "w" }); } } this.runStatus.state.postProcessFinished(error, stopFlowExecutionOnError); } postProcessFinished(_error, _stopFlowExecutionOnError) { } createTransitionError(transition) { return new Error( `Cannot execute transition ${transition} in current state ${this.getStateCode()}.` ); } createMethodError(method) { return new Error(`Cannot execute method ${method} in current state ${this.getStateCode()}.`); } debug(formatter, ...args) { const scope = (this == null ? void 0 : this.runStatus) && typeof this.runStatus.runOptions.debugKey === "string" ? this.runStatus.runOptions.debugKey : "init"; debug_default(scope)(formatter, ...args); } static formatDebugMessage({ n, m, l, e }) { var _a; const levelIcon = l === "w" ? "\u26A0\uFE0F " : ""; const eventIcons = { FS: "\u25B6 ", FF: "\u2714 ", TS: " \u2023 ", TF: " \u2713 ", FC: " \u24D8 ", FT: "\u25FC ", FP: "\u23F8 " }; let eventIcon = (_a = eventIcons[e != null ? e : ""]) != null ? _a : ""; if (e === "TF" && ["e", "f"].includes(l != null ? l : "")) { eventIcon = " \u2717"; } else if (e === "FF" && ["e", "f"].includes(l != null ? l : "")) { eventIcon = "\u2718"; } const icon = levelIcon + eventIcon; return `[${n}] ${icon}${m}`; } static createLogEntry({ n, m, mp, l, e, pid, task }, flowStatus) { const formatLevel = (level) => { level = level || "i"; switch (level) { case "e": return "error"; case "w": return "warning"; case "i": return "info"; case "d": return "debug"; default: throw new Error(`Not supported error level: "${level}"`); } }; const formatEvent = (event) => { switch (event) { case "TS": return "Task.Started"; case "TF": return "Task.Finished"; case "FC": return "Flow.StateChanged"; case "FS": return "Flow.Started"; case "FF": return "Flow.Finished"; case "FT": return "Flow.Stopped"; case "FP": return "Flow.Paused"; default: return "General"; } }; const formatMsg = (templateMsg, param) => { if (param) { const paramStr = JSON.stringify(param); return templateMsg.replace( "%O", paramStr.length > 100 ? paramStr.slice(0, 97) + "..." : paramStr ); } return templateMsg; }; let auditLogEntry = { level: formatLevel(l), eventType: formatEvent(e), message: formatMsg(m, mp), timestamp: /* @__PURE__ */ new Date(), extra: { pid, task, debugId: n, values: JSON.stringify(mp) } }; if (flowStatus) { auditLogEntry.objectId = flowStatus.runOptions.instanceId; auditLogEntry = Object.assign(flowStatus.runOptions.logFields, auditLogEntry); } return auditLogEntry; } log({ n, m, mp, l, e, pid, task }) { this.debug(_FlowState.formatDebugMessage({ n, m, mp, l, e }), [mp]); FlowManager.log(_FlowState.createLogEntry({ n, m, mp, l, e, pid, task }, this.runStatus)); } }; /** * Built-in resolver library. * @type {TaskResolverMap} */ _FlowState.builtInResolvers = { "flowed::Noop": NoopResolver, "flowed::Echo": EchoResolver, "flowed::ThrowError": ThrowErrorResolver, "flowed::Conditional": ConditionalResolver, "flowed::Wait": WaitResolver, "flowed::SubFlow": SubFlowResolver, "flowed::Repeater": RepeaterResolver, "flowed::Loop": LoopResolver, "flowed::ArrayMap": ArrayMapResolver, "flowed::Stop": StopResolver, "flowed::Pause": PauseResolver }; var FlowState = _FlowState; // src/engine/flow-state/flow-ready.ts var FlowReady = class extends FlowState { getStateCode() { return "Ready" /* Ready */; } start(params, expectedResults, resolvers, context, options = {}) { this.setRunOptions(options); this.log({ n: this.runStatus.id, m: "Flow started with params: %O", mp: params, e: "FS" }); this.setState("Running" /* Running */); this.setExpectedResults([...expectedResults]); this.setResolvers(resolvers); this.setContext(context); this.supplyParameters(params); this.createFinishPromise(); this.startReadyTasks(); if (!this.runStatus.state.isRunning()) { this.runStatus.state.finished(); } return this.runStatus.finishPromise; } getSerializableState() { return this.runStatus.toSerializable(); } }; // src/engine/flow-state/flow-finished.ts var FlowFinished = class extends FlowState { getStateCode() { return "Finished" /* Finished */; } reset() { this.setState("Ready" /* Ready */); this.runStatus.initRunStatus(this.runStatus.spec); } getSerializableState() { return this.runStatus.toSerializable(); } }; // src/engine/flow-state/flow-paused.ts var FlowPaused = class extends FlowState { getStateCode() { return "Paused" /* Paused */; } resume() { this.setState("Running" /* Running */); this.createFinishPromise(); this.startReadyTasks(); if (!this.runStatus.state.isRunning()) { this.runStatus.state.finished(); } return this.runStatus.finishPromise; } stop() { this.setState("Stopping" /* Stopping */); return Promise.resolve(this.getResults()); } getSerializableState() { return this.runStatus.toSerializable(); } }; // src/engine/flow-state/flow-pausing.ts var FlowPausing = class extends FlowState { getStateCode() { return "Pausing" /* Pausing */; } paused(error) { this.setState("Paused" /* Paused */); if (error) { this.log({ n: this.runStatus.id, m: "Flow paused with error.", e: "FP" }); this.execFinishReject(error); } else { this.log({ n: this.runStatus.id, m: "Flow paused.", e: "FP" }); this.execFinishResolve(); } } postProcessFinished(error, _stopFlowExecutionOnError) { this.runStatus.state.paused(error); } }; // src/engine/flow-state/flow-running.ts var FlowRunning = class extends FlowState { getStateCode() { return "Running" /* Running */; } pause() { this.setState("Pausing" /* Pausing */); return this.runStatus.finishPromise; } stop() { this.setState("Stopping" /* Stopping */); return this.runStatus.finishPromise; } finished(error = false) { this.setState("Finished" /* Finished */); if (error) { this.log({ n: this.runStatus.id, m: "Flow finished with error. Results: %O", mp: this.getResults(), l: "e", e: "FF" }); this.execFinishReject(error); } else { this.log({ n: this.runStatus.id, m: "Flow finished with results: %O", mp: this.getResults(), e: "FF" }); this.execFinishResolve(); } } postProcessFinished(error, stopFlowExecutionOnError) { const stopExecution = error && stopFlowExecutionOnError; if (!stopExecution) { this.runStatus.state.startReadyTasks(); } if (!this.runStatus.state.isRunning()) { this.runStatus.state.finished(error); } } }; // src/engine/flow-state/flow-stopped.ts var FlowStopped = class extends FlowState { getStateCode() { return "Stopped" /* Stopped */; } reset() { this.setState("Ready" /* Ready */); this.runStatus.initRunStatus(this.runStatus.spec); } getSerializableState() { return this.runStatus.toSerializable(); } }; // src/engine/flow-state/flow-stopping.ts var FlowStopping = class extends FlowState { getStateCode() { return "Stopping" /* Stopping */; } stopped(error = false) { this.setState("Stopped" /* Stopped */); if (error) { this.log({ n: this.runStatus.id, m: "Flow stopped with error.", e: "FT" }); this.execFinishReject(error); } else { this.log({ n: this.runStatus.id, m: "Flow stopped.", e: "FT" }); this.execFinishResolve(); } } postProcessFinished(error, _stopFlowExecutionOnError) { this.runStatus.state.stopped(error); } }; // src/engine/process-manager.ts var ProcessManager = class { constructor() { this.nextProcessId = 1; this.processes = []; } createProcess(task, taskResolverExecutor, context, automapParams, automapResults, flowId, debug2, log) { this.nextProcessId++; const process = new TaskProcess( this, this.nextProcessId, task, taskResolverExecutor, context, automapParams, automapResults, flowId, debug2, log ); this.processes.push(process); return process; } runningCount() { return this.processes.length; } removeProcess(process) { const processIndex = this.processes.findIndex((p) => p.id === process.id); this.processes.splice(processIndex, 1); } }; // src/engine/value-queue-manager.ts var ValueQueueManager = class _ValueQueueManager { static fromSerializable(serializable) { const queueNames = Object.keys(serializable); const instance = new _ValueQueueManager(queueNames); instance.queues = serializable; instance.nonEmptyQueues = queueNames.reduce((acc, name) => { if (instance.queues[name].length > 0) { acc.add(name); } return acc; }, /* @__PURE__ */ new Set()); return instance; } // List of queue names constructor(queueNames) { this.nonEmptyQueues = /* @__PURE__ */ new Set(); this.queueNames = [...queueNames]; this.queues = queueNames.reduce((acc, name) => { acc[name] = []; return acc; }, {}); } push(queueName, value) { if (!this.queueNames.includes(queueName)) { throw new Error( `Queue name ${queueName} does not exist in queue manager. Existing queues are: [${this.queueNames.join(", ")}].` ); } this.nonEmptyQueues.add(queueName); this.queues[queueName].push(value); } getEmptyQueueNames() { return this.queueNames.reduce((acc, name) => { if (this.queues[name].length === 0) { acc.push(name); } return acc; }, []); } popAll() { this.validateAllNonEmpty(); return this.queueNames.reduce((acc, name) => { acc[name] = this.queues[name].shift(); if (this.queues[name].length === 0) { this.nonEmptyQueues.delete(name); } return acc; }, {}); } topAll() { this.validateAllNonEmpty(); return this.queueNames.reduce((acc, name) => { acc[name] = this.queues[name][0]; return acc; }, {}); } // For this to work, all user values must be serializable to JSON toSerializable() { return JSON.parse(JSON.stringify(this.queues)); } validateAllNonEmpty() { if (!this.allHaveContent()) { throw new Error(`Some of the queues are empty: [${this.getEmptyQueueNames().join(", ")}].`); } } allHaveContent() { return this.nonEmptyQueues.size === this.queueNames.length; } }; // src/engine/task.ts var ST = require("flowed-st"); var Task = class { constructor(code, spec) { this.code = code; this.spec = spec; this.parseSpec(); } getResolverName() { var _a; return ((_a = this.spec.resolver) != null ? _a : { name: "flowed::Noop" }).name; } getSerializableState() { const result = JSON.parse(JSON.stringify(this.runStatus)); result.solvedReqs = this.runStatus.solvedReqs.toSerializable(); return result; } setSerializableState(runStatus) { this.runStatus = JSON.parse(JSON.stringify(runStatus)); this.runStatus.solvedReqs = ValueQueueManager.fromSerializable(runStatus.solvedReqs); } resetRunStatus() { var _a; const reqs = [...(_a = this.spec.requires) != null ? _a : []]; this.runStatus = { solvedReqs: new ValueQueueManager(reqs), solvedResults: {} }; } isReadyToRun() { return this.runStatus.solvedReqs.allHaveContent(); } getResults() { return this.runStatus.solvedResults; } supplyReq(reqName, value) { var _a; const reqIndex = ((_a = this.spec.requires) != null ? _a : []).indexOf(reqName); if (reqIndex === -1) { throw new Error(`Requirement '${reqName}' for task '${this.code}' is not valid.`); } this.runStatus.solvedReqs.push(reqName, value); } supplyReqs(reqsMap) { for (const [reqName, req] of Object.entries(reqsMap)) { this.supplyReq(reqName, req); } } mapParamsForResolver(solvedReqs, automap, flowId, log) { var _a, _b, _c; const params = {}; let resolverParams = (_b = (_a = this.spec.resolver) == null ? void 0 : _a.params) != null ? _b : {}; if (automap) { const requires = (_c = this.spec.requires) != null ? _c : []; const autoMappedParams = requires.map((req) => ({ [req]: req })).reduce((accum, peer) => Object.assign(accum, peer), {}); log({ n: flowId, m: ` \u24D8 Auto-mapped resolver params in task '${this.code}': %O`, mp: autoMappedParams, l: "d" }); resolverParams = Object.assign(autoMappedParams, resolverParams); } let paramValue; for (const [resolverParamName, paramSolvingInfo] of Object.entries(resolverParams)) { if (typeof paramSolvingInfo === "string") { paramValue = solvedReqs[paramSolvingInfo]; } else if (Object.prototype.hasOwnProperty.call(paramSolvingInfo, "value")) { paramValue = paramSolvingInfo.value; } else { const template = paramSolvingInfo.transform; paramValue = ST.select(solvedReqs).transformWith(template).root(); } params[resolverParamName] = paramValue; } return params; } mapResultsFromResolver(solvedResults, automap, flowId, log) { var _a, _b, _c; if (typeof solvedResults !== "object") { throw new Error( `Expected resolver for task '${this.code}' to return an object or Promise that resolves to object. Returned value is of type '${typeof solvedResults}'.` ); } const results = {}; let resolverResults = (_b = (_a = this.spec.resolver) == null ? void 0 : _a.results) != null ? _b : {}; if (automap) { const provides = (_c = this.spec.provides) != null ? _c : []; const autoMappedResults = provides.reduce( (acc, prov) => Object.assign(acc, { [prov]: prov }), {} ); log({ n: flowId, m: ` \u24D8 Auto-mapped resolver results in task '${this.code}': %O`, mp: autoMappedResults, l: "d" }); resolverResults = Object.assign(autoMappedResults, resolverResults); } for (const [resolverResultName, taskResultName] of Object.entries(resolverResults)) { if (Object.prototype.hasOwnProperty.call(solvedResults, resolverResultName)) { results[taskResultName] = solvedResults[resolverResultName]; } } return results; } parseSpec() { this.resetRunStatus(); } }; // src/engine/flow-run-status.ts var _FlowRunStatus = class _FlowRunStatus { constructor(flow, spec, runStatus) { this.tasksReady = []; this.tasksByReq = {}; this.resolvers = {}; this.expectedResults = []; this.results = {}; this.context = {}; // Stores the options passed from the outside on each particular run this.runOptions = {}; this.flow = flow; this.processManager = new ProcessManager(); this.id = _FlowRunStatus.nextId; _FlowRunStatus.nextId = (_FlowRunStatus.nextId + 1) % Number.MAX_SAFE_INTEGER; this.states = { Ready: new FlowReady(this), Running: new FlowRunning(this), Finished: new FlowFinished(this), Pausing: new FlowPausing(this), Paused: new FlowPaused(this), Stopping: new FlowStopping(this), Stopped: new FlowStopped(this) }; this.state = this.states["Ready" /* Ready */]; this.initRunStatus(spec, runStatus); } initRunStatus(spec, runState) { var _a, _b, _c; this.spec = spec; this.tasks = {}; const provisions = []; for (const [taskCode, taskSpec] of Object.entries((_a = this.spec.tasks) != null ? _a : {})) { provisions.push(...(_b = taskSpec.provides) != null ? _b : []); this.tasks[taskCode] = new Task(taskCode, taskSpec); } this.taskProvisions = Array.from(new Set(provisions)); this.options = __spreadValues(__spreadValues({}, this.spec.configs), this.spec.options); if (Object.prototype.hasOwnProperty.call(this.spec, "configs")) { this.flow.log({ m: "DEPRECATED: 'configs' field in flow spec. Use 'options' instead.", l: "w" }); } this.tasksByReq = {}; this.tasksReady = []; for (const task of Object.values(this.tasks)) { task.resetRunStatus(); if (task.isReadyToRun()) { this.tasksReady.push(task); } const taskReqs = (_c = task.spec.requires) != null ? _c : []; for (const req of taskReqs) { if (typeof this.tasksByReq[req] === "undefined") { this.tasksByReq[req] = {}; } this.tasksByReq[req][task.code] = task; } } this.results = {}; if (runState) { this.fromSerializable(runState); } } fromSerializable(runState) { this.id = runState.id; this.processManager.nextProcessId = runState.nextProcessId; this.processManager.processes = []; this.tasksReady = runState.tasksReady.map((taskCode) => this.tasks[taskCode]); this.tasksByReq = {}; for (const [req, tasks] of Object.entries(runState.tasksByReq)) { this.tasksByReq[req] = tasks.reduce((accum, taskCode) => { accum[taskCode] = this.tasks[taskCode]; return accum; }, {}); } this.taskProvisions = JSON.parse(JSON.stringify(runState.taskProvisions)); this.expectedResults = JSON.parse(JSON.stringify(runState.expectedResults)); this.results = JSON.parse(JSON.stringify(runState.results)); this.context = JSON.parse(JSON.stringify(runState.context)); this.options = JSON.parse(JSON.stringify(runState.options)); for (const [taskCode, taskStatus] of Object.entries(runState.taskStatuses)) { this.tasks[taskCode].setSerializableState(taskStatus); } } toSerializable() { const serialized = { id: this.id, nextProcessId: this.processManager.nextProcessId, tasksReady: this.tasksReady.map((task) => task.code), tasksByReq: {}, taskProvisions: JSON.parse(JSON.stringify(this.taskProvisions)), expectedResults: JSON.parse(JSON.stringify(this.expectedResults)), results: JSON.parse(JSON.stringify(this.results)), context: {}, options: JSON.parse(JSON.stringify(this.options)), taskStatuses: {} }; const serializableContext = __spreadValues({}, this.context); delete serializableContext.$flowed; serialized.context = JSON.parse(JSON.stringify(serializableContext)); for (const [req, taskMap] of Object.entries(this.tasksByReq)) { serialized.tasksByReq[req] = Object.keys(taskMap); } for (const [taskCode, task] of Object.entries(this.tasks)) { serialized.taskStatuses[taskCode] = JSON.parse(JSON.stringify(task.getSerializableState())); } return serialized; } }; /** * Flow instance id to be assigned to the next Flow instance. Intended to be used for debugging. * @type {number} */ _FlowRunStatus.nextId = 1; var FlowRunStatus = _FlowRunStatus; // src/engine/flow.ts var Flow = class { constructor(spec, runState) { this.runStatus = new FlowRunStatus(this, spec != null ? spec : {}, runState); } getStateCode() { return this.runStatus.state.getStateCode(); } start(params = {}, expectedResults = [], resolvers = {}, context = {}, options = {}) { return this.runStatus.state.start(params, expectedResults, resolvers, context, options); } pause() { return this.runStatus.state.pause(); } resume() { return this.runStatus.state.resume(); } stop() { return this.runStatus.state.stop(); } reset() { this.runStatus.state.reset(); } getSerializableState() { return this.runStatus.state.getSerializableState(); } debug(formatter, ...args) { (this == null ? void 0 : this.runStatus) ? this.runStatus.state.debug(formatter, ...args) : debug_default("init")(formatter, ...args); } log({ n, m, mp, l, e }) { this.debug(FlowState.formatDebugMessage({ n, m, mp, l, e }), [mp]); FlowManager.log(FlowState.createLogEntry({ n, m, mp, l, e }, this.runStatus)); } }; // src/engine/flow-manager.ts var _FlowManager = class _FlowManager { static run(flowSpec, params = {}, expectedResults = [], resolvers = {}, context = {}, options = {}) { const flow = new Flow(flowSpec); return flow.start(params, expectedResults, resolvers, context, options); } static runFromString(flowSpecJson, params = {}, expectedResults = [], resolvers = {}, context = {}, options = {}) { return new Promise((resolveFlow, reject) => { try { const flowSpec = JSON.parse(flowSpecJson); _FlowManager.run(flowSpec, params, expectedResults, resolvers, context, options).then( resolveFlow, reject ); } catch (error) { reject(error); } }); } static runFromFile(flowSpecFilepath, params = {}, expectedResults = [], resolvers = {}, context = {}, options = {}) { return new Promise((resolveFlow, reject) => { (0, import_fs.readFile)(flowSpecFilepath, "utf8", (err, fileContents) => { if (err) { reject(err); } else { _FlowManager.runFromString( fileContents, params, expectedResults, resolvers, context, options ).then(resolveFlow, reject); } }); }); } static runFromUrl(flowSpecUrl, params = {}, expectedResults = [], resolvers = {}, context = {}, options = {}) { let client = null; if (flowSpecUrl.startsWith("http://")) { client = http; } else if (flowSpecUrl.startsWith("https://")) { client = https; } if (client === null) { let actualProtocol = null; const matchResult = flowSpecUrl.match(/^([a-zA-Z]+):/); if (Array.isArray(matchResult) && matchResult.length === 2) { actualProtocol = matchResult[1]; return Promise.reject( new Error( `Protocol not supported: ${actualProtocol}. Supported protocols are: [http, https]` ) ); } else { return Promise.reject(new Error(`Invalid URL: ${flowSpecUrl}`)); } } return new Promise((resolveFlow, reject) => { client.get(flowSpecUrl, { agent: false }, (res) => { var _a; const { statusCode } = res; const contentType = (_a = res.headers["content-type"]) != null ? _a : "application/json"; let error; if (statusCode !== 200) { error = new Error(`Request failed with status code: ${statusCode}`); } else if (!(contentType.startsWith("application/json") || contentType.startsWith("text/plain"))) { error = new Error( `Invalid content-type: Expected 'application/json' or 'text/plain' but received '${contentType}'` ); } if (error) { reject(error); } else { res.setEncoding("utf8"); let rawData = ""; res.on("data", (chunk) => { rawData += chunk; }); res.on("end", () => { _FlowManager.runFromString( rawData, params, expectedResults, resolvers, context, options ).then(resolveFlow, reject); }); } }).on("error", (error) => { reject(error); }); }); } static installPlugin(plugin) { if (plugin.resolverLibrary) { for (const [name, resolver] of Object.entries(plugin.resolverLibrary)) { this.plugins.resolvers[name] = resolver; } } } static installLogger(logger) { this.logger = logger; } static log(entry) { if (_FlowManager.logger === null) { return; } _FlowManager.logger.log(entry); } }; _FlowManager.plugins = { resolvers: {} }; _FlowManager.logger = null; var FlowManager = _FlowManager; // src/engine/specs.ts var TaskSpecMap = class { }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ArrayMapResolver, ConditionalResolver, EchoResolver, Flow, FlowManager, FlowStateEnum, FlowTransitionEnum, LoopResolver, NoopResolver, PauseResolver, RepeaterResolver, StopResolver, SubFlowResolver, Task, TaskResolver, TaskResolverMap, TaskSpecMap, ThrowErrorResolver, WaitResolver }); //# sourceMappingURL=index.js.map