UNPKG

integreat

Version:
297 lines 13.9 kB
import pLimit from 'p-limit'; import { ensureArray, unwrapArrayOfOne } from '../utils/array.js'; import { isObject, isOkResponse, isOkStatus } from '../utils/is.js'; import xor from '../utils/xor.js'; import { setDataOnActionPayload, setResponseOnAction, setOriginOnAction, setErrorOnAction, setMetaOnAction, } from '../utils/action.js'; import { combineResponses, setOrigin } from '../utils/response.js'; import validateFilters from '../utils/validateFilters.js'; import prepareValidator from '../utils/validation.js'; import { populateActionAfterMutation } from '../utils/mutationHelpers.js'; export const breakSymbol = Symbol('break'); export const statusFromResponses = (responses) => responses.reduce((status, response) => status === response.status || !status ? response.status : 'error', undefined); const adjustPrevalidationResponse = ({ break: _, message, ...response }) => message ? isOkResponse(response) ? { ...response, warning: message } : { ...response, error: message } : response; const setOkStatusOnErrorResponse = ({ error, origin, ...response } = {}) => ({ ...response, status: 'ok', ...(error ? { warning: error } : {}), }); const ensureOkResponse = (response) => isOkResponse(response) ? response : setOkStatusOnErrorResponse(response); const removeError = ({ error, ...response }) => response; const adjustNoActionResponse = (response) => response.status === 'noaction' && response.error ? removeError({ ...response, warning: response.error }) : response; const adjustValidationResponse = (responses, response) => responses.length > 0 ? combineResponses(responses.map(adjustNoActionResponse)) : response ? ensureOkResponse(response) : null; const addModify = (mutation) => isObject(mutation) ? { $modify: true, ...mutation } : mutation; const putMutationInPipeline = (mutation, useMagic) => Array.isArray(mutation) ? ['$action', ...mutation.map(addModify)] : useMagic ? ['$action', { '.': '.', ...mutation }] : ['$action', addModify(mutation)]; const putMutationInPipelineForCondition = (conditionObject) => ({ ...conditionObject, condition: Array.isArray(conditionObject.condition) ? ['$action', ...conditionObject.condition] : conditionObject.condition ? ['$action', conditionObject.condition] : null, }); const getThisResponse = (actionResponses) => actionResponses.$action?.response; const getPreviousResponse = (actionResponses, prevStepId) => (prevStepId ? actionResponses[prevStepId]?.response : undefined); function createConditionsValidator(conditions, mapTransform, mapOptions, isPreconditions, failOnErrorInPostconditions, prevStepId) { const breakByDefault = !isPreconditions && failOnErrorInPostconditions; if (Array.isArray(conditions)) { const defaultFailStatus = isPreconditions ? 'noaction' : 'error'; const validator = prepareValidator(conditions, mapTransform, mapOptions, defaultFailStatus, breakByDefault); return async function validate(actionResponses) { const [responses, doBreak] = await validator(actionResponses); const stepResponse = getThisResponse(actionResponses); const response = adjustValidationResponse(responses, stepResponse); return [response, doBreak]; }; } else if (isObject(conditions)) { const validator = validateFilters(conditions, true); return async function validate(actionResponses) { const responses = validator(actionResponses); const doBreak = responses.some(({ break: doBreak = breakByDefault }) => doBreak); return responses.length > 0 ? [adjustPrevalidationResponse(combineResponses(responses)), doBreak] : [null, false]; }; } else if (xor(isPreconditions, failOnErrorInPostconditions)) { return async function validatePrevWasOk(actionResponses) { const response = failOnErrorInPostconditions ? getThisResponse(actionResponses) : getPreviousResponse(actionResponses, prevStepId); return response && !isOkResponse(response) ? [response, true] : [null, false]; }; } else { return async function validateAlwaysOk(_actionResponses) { return [null, false]; }; } } export function createPreconditionsValidator(preconditions, validationFilters, mapTransform, mapOptions, failOnErrorInPostconditions, prevStepId) { const conditions = preconditions || validationFilters; return createConditionsValidator(conditions, mapTransform, mapOptions, true, failOnErrorInPostconditions, prevStepId); } function createPostconditionsValidator(conditions, mapTransform, mapOptions, failOnErrorInPostconditions) { if (Array.isArray(conditions)) { conditions = conditions.map(putMutationInPipelineForCondition); } return createConditionsValidator(conditions, mapTransform, mapOptions, false, failOnErrorInPostconditions); } export function getLastJobWithResponse(steps, actionResponses) { for (let i = steps.length - 1; i > -1; i--) { const step = steps.at(i); const actionResponse = actionResponses[step.id] || {}; if (actionResponse.response?.status) { return actionResponse.response; } } return undefined; } const setResponseStatus = (response = {}) => ({ ...response, status: response.status ? response.status : response.error ? 'error' : 'ok', }); async function mutateAction(action, mutator, actionsWithResponses) { if (mutator) { const responsesIncludingAction = { ...actionsWithResponses, $action: action, }; return (await mutator(responsesIncludingAction)); } else { return action; } } export async function mutateResponse(action, responses, origin, postmutator, prefixOrigin = false) { const { response: mutatedResponse } = await mutateAction(action, postmutator, responses); return setOriginOnAction(populateActionAfterMutation(action, setResponseOnAction(action, setResponseStatus(mutatedResponse))), origin, prefixOrigin); } function responseFromSteps(actionResponses) { const errorResponses = Object.values(actionResponses) .map(({ response }) => response) .filter((response) => !isOkResponse(response) || !!response?.warning); if (errorResponses.length === 0) { return { response: { status: 'ok' } }; } else { const status = statusFromResponses(errorResponses); return { response: { status: isOkStatus(status) ? 'ok' : status, responses: errorResponses, }, }; } } function generateIterateResponse(action, responses) { const actionsWithResponses = Object.values(responses); const errorResponses = actionsWithResponses .map(({ response }) => response) .filter((response) => !!response && !isOkResponse(response)); const status = errorResponses.length === 0 ? 'ok' : 'error'; const error = errorResponses .map((response) => response ? `[${response.status}] ${response.error}` : undefined) .filter(Boolean) .join(' | '); const data = actionsWithResponses.flatMap(({ response }) => response?.data); return setResponseOnAction(action, { status, data, ...(error && { error, responses: errorResponses }), }); } export const prepareMutation = (pipeline, mapTransform, mapOptions, useMagic = false) => mapTransform(putMutationInPipeline(pipeline, useMagic), mapOptions); function getIterateMutator(step, mapTransform, mapOptions) { const pipeline = step.iterate || step.iteratePath; if (pipeline) { return mapTransform(pipeline, mapOptions); } else { return undefined; } } export function getPrevStepId(index, steps) { const prevStep = index > 0 ? steps[index - 1] : undefined; return Array.isArray(prevStep) ? prevStep .map((step) => step?.id) .filter(Boolean) .join(':') : prevStep?.id; } async function runOneAction(action, dispatch) { try { return setResponseOnAction(action, await dispatch(action)); } catch (error) { return setErrorOnAction(action, error); } } export default class Step { id; #action; #subSteps; #validatePreconditions = async () => [null, false]; #validatePostconditions = async () => [null, false]; #premutator; #postmutator; #iterateMutator; #iterateConcurrency; #isJob; constructor(stepDef, mapTransform, mapOptions, failOnErrorInPostconditions = false, prevStepId, isJob = false) { this.#isJob = isJob; const def = unwrapArrayOfOne(stepDef); if (Array.isArray(def)) { this.id = def.map((step) => step.id).join(':'); this.#subSteps = def.map((step, index, steps) => new Step(step, mapTransform, mapOptions, failOnErrorInPostconditions, getPrevStepId(index, steps))); } else { this.id = def.id; this.#validatePreconditions = createPreconditionsValidator(def.preconditions, def.conditions, mapTransform, mapOptions, failOnErrorInPostconditions, prevStepId); this.#validatePostconditions = createPostconditionsValidator(def.postconditions, mapTransform, mapOptions, failOnErrorInPostconditions); this.#action = def.action; const premutation = def.premutation || def.mutation; const postmutation = def.postmutation || def.responseMutation; this.#premutator = premutation ? prepareMutation(premutation, mapTransform, mapOptions, !!def.mutation) : undefined; this.#postmutator = postmutation ? prepareMutation(postmutation, mapTransform, mapOptions, !!def.responseMutation) : undefined; this.#iterateMutator = getIterateMutator(def, mapTransform, mapOptions); this.#iterateConcurrency = def.iterateConcurrency; } } async runIteration(actionResponses, dispatch, meta) { if (!this.#iterateMutator || !this.#action) { return undefined; } const action = setMetaOnAction(this.#action, meta); const items = ensureArray(await this.#iterateMutator(actionResponses)); const actions = items.map((item) => setDataOnActionPayload(action, item)); const limit = pLimit(this.#iterateConcurrency ?? 1); return Object.fromEntries((await Promise.all(actions.map((action, index) => limit(async () => runOneAction(await mutateAction(setMetaOnAction(action, { ...action.meta, stepId: `${this.id}_${index}`, }), this.#premutator, actionResponses), dispatch))))).map((response, index) => [ `${this.id}_${index}`, setOriginOnAction(response, `${this.id}_${index}`), ])); } async runAction(actionResponses, dispatch, meta) { if (!this.#action) { return {}; } const action = setMetaOnAction(this.#action, meta); const iterateResponses = await this.runIteration(actionResponses, dispatch, meta); const responseAction = iterateResponses ? generateIterateResponse(action, iterateResponses) : await runOneAction(await mutateAction(this.#isJob ? action : setMetaOnAction(action, { ...meta, stepId: this.id }), this.#premutator, actionResponses), dispatch); if (this.#isJob) { actionResponses.action.response = responseAction.response; } return { ...iterateResponses, [this.id]: await mutateResponse(responseAction, { ...actionResponses, ...iterateResponses }, this.id, this.#postmutator, true), }; } async runSubSteps(actionResponses, dispatch, meta) { if (!this.#subSteps) { return {}; } const arrayOfResponses = await Promise.all(this.#subSteps.map((step) => step.run({ ...meta, stepId: step.id }, actionResponses, dispatch))); const doBreak = arrayOfResponses.some((response) => response[breakSymbol]); const responsesObj = Object.fromEntries(arrayOfResponses.flatMap((responses) => Object.entries(responses))); const thisStep = responseFromSteps(responsesObj); return thisStep ? { ...responsesObj, [this.id]: thisStep, [breakSymbol]: doBreak } : { ...responsesObj, [breakSymbol]: doBreak }; } async run(meta, actionResponses, dispatch) { const [preconditionsResponse, doPreBreak] = await this.#validatePreconditions(actionResponses); if (preconditionsResponse || doPreBreak) { return { [this.id]: { response: setOrigin(preconditionsResponse || {}, this.id), }, [breakSymbol]: doPreBreak, }; } const responseObject = this.#action ? await this.runAction(actionResponses, dispatch, meta) : await this.runSubSteps(actionResponses, dispatch, meta); const thisActionResponse = responseObject[this.id]; const [postconditionsResponse, doPostBreak] = await this.#validatePostconditions({ ...actionResponses, $action: thisActionResponse, }); if (postconditionsResponse || doPostBreak) { return { ...responseObject, [this.id]: setResponseOnAction(thisActionResponse, setOrigin(postconditionsResponse || {}, this.id)), [breakSymbol]: doPostBreak, }; } return responseObject; } } //# sourceMappingURL=Step.js.map