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
JavaScript
"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