integreat
Version:
Node.js integration layer
151 lines • 6.61 kB
JavaScript
import { nanoid } from 'nanoid';
import Schedule from './Schedule.js';
import Step, { createPreconditionsValidator, statusFromResponses, getPrevStepId, getLastJobWithResponse, prepareMutation, mutateResponse, breakSymbol, } from './Step.js';
import { isObject, isOkResponse, isErrorResponse } from '../utils/is.js';
import { setResponseOnAction } from '../utils/action.js';
import { setOrigin } from '../utils/response.js';
const isActionJob = (job) => isObject(job) && isObject(job.action);
const isFlowJob = (job) => isObject(job) && Array.isArray(job.flow);
const generateSubMeta = ({ ident, project, cid }, jobId, gid) => ({
ident,
jobId,
...(project ? { project } : {}),
...(cid ? { cid } : {}),
...(gid ? { gid } : {}),
});
const messageFromResponse = (response) => response.error || response.warning || 'Unknown error';
const messageFromStep = (isFlow) => (response) => {
return isErrorResponse(response) || response.warning
? isFlow
? `- '${response.origin}': ${messageFromResponse(response)} (${response.status})`
: `- ${messageFromResponse(response)} (${response.status})`
: undefined;
};
function generateJobMessage(responses, jobId, isFlow, isOk) {
if (responses.length > 1) {
const stepMessage = responses
.map(messageFromStep(isFlow))
.filter(Boolean)
.join('\n');
return isOk
? `Message from steps:\n${stepMessage}`
: isFlow
? `Steps failed:\n${stepMessage}`
: `Could not finish job '${jobId}': ${stepMessage}`;
}
else {
return !isOk || responses[0].warning
? messageFromResponse(responses[0])
: undefined;
}
}
const removeOriginAndWarning = ({ origin, warning, ...response }) => response;
const setOriginOnResponses = (responses, jobId) => responses.map((response) => setOrigin(response, `job:${jobId}:step`, true));
function setMessageAndOrigin(response, jobId, isFlow) {
const responses = response.responses || [response];
const isOk = isOkResponse(response);
const bareResponse = removeOriginAndWarning(response);
if (isOk) {
const warningResponses = responses.filter((resp) => resp.warning);
const jobWarning = warningResponses.length > 0
? generateJobMessage(warningResponses, jobId, isFlow, isOk)
: undefined;
return {
...bareResponse,
...(jobWarning ? { warning: jobWarning } : {}),
...(response.responses
? { responses: setOriginOnResponses(response.responses, jobId) }
: {}),
};
}
else {
const errorResponses = responses.filter(isErrorResponse);
const jobError = errorResponses.length > 0
? generateJobMessage(errorResponses, jobId, isFlow, isOk)
: undefined;
const origin = errorResponses.length === 1
? `job:${jobId}:step:${errorResponses[0].origin}`
: `job:${jobId}`;
return {
...bareResponse,
status: statusFromResponses(responses),
error: jobError,
origin,
responses: setOriginOnResponses(responses, jobId),
};
}
}
function getResponse(jobId, steps, responses, isFlow) {
const lastResponse = getLastJobWithResponse(steps, responses);
if (!lastResponse) {
return { status: 'noaction' };
}
else {
return setMessageAndOrigin(lastResponse, jobId, isFlow);
}
}
const getId = (jobDef) => typeof jobDef.id === 'string' && jobDef.id ? jobDef.id : nanoid();
const calculateProgress = (index, stepsCount) => (index + 1) / (stepsCount + 1);
export default class Job {
id;
schedule;
#steps = [];
#validatePreconditions = async () => [null, false];
#postmutator;
#isFlow = false;
constructor(jobDef, mapTransform, mapOptions, failOnErrorInPostconditions = false) {
this.id = getId(jobDef);
if (isFlowJob(jobDef)) {
this.#isFlow = true;
this.#steps = jobDef.flow
.filter((step) => Array.isArray(step) || isActionJob(step))
.map((stepDef, index, steps) => new Step(stepDef, mapTransform, mapOptions, failOnErrorInPostconditions, getPrevStepId(index, steps)));
this.#validatePreconditions = createPreconditionsValidator(jobDef.preconditions, undefined, mapTransform, mapOptions, failOnErrorInPostconditions, undefined);
const postmutation = jobDef.postmutation || jobDef.responseMutation;
this.#postmutator = postmutation
? prepareMutation(postmutation, mapTransform, mapOptions, !!jobDef.responseMutation)
: undefined;
}
else if (isActionJob(jobDef)) {
this.#steps = [
new Step(jobDef, mapTransform, mapOptions, failOnErrorInPostconditions, undefined, true),
];
}
if (jobDef.cron && this.#steps.length > 0) {
this.schedule = new Schedule(jobDef);
}
}
async run(action, dispatch, setProgress, gid) {
if (this.#steps.length === 0) {
return {
status: 'noaction',
warning: `Job '${this.id}' has no action or flow`,
origin: `job:${this.id}`,
};
}
let actionResponses = { action };
const [preconditionsResponse] = await this.#validatePreconditions(actionResponses);
if (!preconditionsResponse) {
const meta = generateSubMeta(action.meta || {}, this.id, gid);
for (const [index, step] of this.#steps.entries()) {
const { [breakSymbol]: doBreak, ...responses } = await step.run(meta, actionResponses, dispatch);
setProgress(calculateProgress(index, this.#steps.length));
actionResponses = { ...actionResponses, ...responses };
if (doBreak) {
break;
}
}
}
const response = preconditionsResponse ??
getResponse(this.id, this.#steps, actionResponses, this.#isFlow);
if (this.#postmutator) {
actionResponses = { ...actionResponses, action: { ...action, response } };
const { response: mutatedResponse } = await mutateResponse(setResponseOnAction(action, response), actionResponses, `job:${this.id}`, this.#postmutator);
return mutatedResponse || response;
}
else {
return response;
}
}
}
//# sourceMappingURL=Job.js.map