UNPKG

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