flowed
Version:
A fast and reliable flow engine for orchestration and more uses in *Node.js*, *Deno* and the browser
328 lines • 13.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowState = void 0;
const debug_1 = require("../../debug");
const resolver_library_1 = require("../../resolver-library");
const types_1 = require("../../types");
const flow_manager_1 = require("../flow-manager");
class FlowState {
constructor(runStatus) {
this.runStatus = runStatus;
}
start(params, expectedResults, resolvers, context, _options = {}) {
throw this.createTransitionError(types_1.FlowTransitionEnum.Start);
}
finished(_error = false) {
throw this.createTransitionError(types_1.FlowTransitionEnum.Finished);
}
pause() {
throw this.createTransitionError(types_1.FlowTransitionEnum.Pause);
}
paused(_error = false) {
throw this.createTransitionError(types_1.FlowTransitionEnum.Paused);
}
resume() {
throw this.createTransitionError(types_1.FlowTransitionEnum.Resume);
}
stop() {
throw this.createTransitionError(types_1.FlowTransitionEnum.Stop);
}
stopped(_error = false) {
throw this.createTransitionError(types_1.FlowTransitionEnum.Stopped);
}
reset() {
throw this.createTransitionError(types_1.FlowTransitionEnum.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 = Object.assign({ $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,
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 flow_manager_1.FlowManager.plugins.resolvers[name] !== 'undefined';
if (hasPluginResolver) {
return flow_manager_1.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 = flow_manager_1.FlowManager.plugins.resolvers;
const builtInResolver = FlowState.builtInResolvers;
return Object.assign(Object.assign(Object.assign({}, 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 !== void 0 ? _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 || this === void 0 ? void 0 : this.runStatus) && typeof this.runStatus.runOptions.debugKey === 'string' ? this.runStatus.runOptions.debugKey : 'init';
(0, debug_1.default)(scope)(formatter, ...args);
}
static formatDebugMessage({ n, m, l, e }) {
var _a;
const levelIcon = l === 'w' ? '⚠️ ' : '';
const eventIcons = { FS: '▶ ', FF: '✔ ', TS: ' ‣ ', TF: ' ✓ ', FC: ' ⓘ ', FT: '◼ ', FP: '⏸ ' };
let eventIcon = (_a = eventIcons[e !== null && e !== void 0 ? e : '']) !== null && _a !== void 0 ? _a : '';
if (e === 'TF' && ['e', 'f'].includes(l !== null && l !== void 0 ? l : '')) {
eventIcon = ' ✗';
}
else if (e === 'FF' && ['e', 'f'].includes(l !== null && l !== void 0 ? l : '')) {
eventIcon = '✘';
}
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: 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]);
flow_manager_1.FlowManager.log(FlowState.createLogEntry({ n, m, mp, l, e, pid, task }, this.runStatus));
}
}
exports.FlowState = FlowState;
FlowState.builtInResolvers = {
'flowed::Noop': resolver_library_1.NoopResolver,
'flowed::Echo': resolver_library_1.EchoResolver,
'flowed::ThrowError': resolver_library_1.ThrowErrorResolver,
'flowed::Conditional': resolver_library_1.ConditionalResolver,
'flowed::Wait': resolver_library_1.WaitResolver,
'flowed::SubFlow': resolver_library_1.SubFlowResolver,
'flowed::Repeater': resolver_library_1.RepeaterResolver,
'flowed::Loop': resolver_library_1.LoopResolver,
'flowed::ArrayMap': resolver_library_1.ArrayMapResolver,
'flowed::Stop': resolver_library_1.StopResolver,
'flowed::Pause': resolver_library_1.PauseResolver,
};
//# sourceMappingURL=flow-state.js.map