UNPKG

n8n

Version:

n8n Workflow Automation Tool

573 lines 27.5 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getLifecycleHooksForSubExecutions = getLifecycleHooksForSubExecutions; exports.getLifecycleHooksForScalingWorker = getLifecycleHooksForScalingWorker; exports.getLifecycleHooksForScalingMain = getLifecycleHooksForScalingMain; exports.getLifecycleHooksForRegularMain = getLifecycleHooksForRegularMain; const backend_common_1 = require("@n8n/backend-common"); const db_1 = require("@n8n/db"); const decorators_1 = require("@n8n/decorators"); const di_1 = require("@n8n/di"); const flatted_1 = require("flatted"); const n8n_core_1 = require("n8n-core"); const event_service_1 = require("../events/event.service"); const execution_persistence_1 = require("../executions/execution-persistence"); const execution_redaction_proxy_service_1 = require("../executions/execution-redaction-proxy.service"); const external_hooks_1 = require("../external-hooks"); const push_1 = require("../push"); const workflow_statistics_service_1 = require("../services/workflow-statistics.service"); const utils_1 = require("../utils"); const get_item_count_by_connection_type_1 = require("../utils/get-item-count-by-connection-type"); const workflow_helpers_1 = require("../workflow-helpers"); const workflow_static_data_service_1 = require("../workflows/workflow-static-data.service"); const execute_error_workflow_1 = require("./execute-error-workflow"); const restore_binary_data_id_1 = require("./restore-binary-data-id"); const save_execution_progress_1 = require("./save-execution-progress"); const shared_hook_functions_1 = require("./shared/shared-hook-functions"); const to_save_settings_1 = require("./to-save-settings"); let ModulesHooksRegistry = class ModulesHooksRegistry { addHooks(hooks) { const handlers = di_1.Container.get(decorators_1.LifecycleMetadata).getHandlers(); for (const { handlerClass, methodName, eventName } of handlers) { const instance = di_1.Container.get(handlerClass); switch (eventName) { case 'workflowExecuteAfter': hooks.addHandler(eventName, async function (runData, newStaticData) { const context = { type: 'workflowExecuteAfter', workflow: this.workflowData, runData, newStaticData, executionId: this.executionId, retryOf: this.retryOf, }; return await instance[methodName].call(instance, context); }); break; case 'nodeExecuteBefore': hooks.addHandler(eventName, async function (nodeName, taskData) { const context = { type: 'nodeExecuteBefore', workflow: this.workflowData, nodeName, taskData, executionId: this.executionId, }; return await instance[methodName].call(instance, context); }); break; case 'nodeExecuteAfter': hooks.addHandler(eventName, async function (nodeName, taskData, executionData) { const context = { type: 'nodeExecuteAfter', workflow: this.workflowData, nodeName, taskData, executionData, executionId: this.executionId, }; return await instance[methodName].call(instance, context); }); break; case 'workflowExecuteBefore': hooks.addHandler(eventName, async function (workflowInstance, executionData) { const context = { type: 'workflowExecuteBefore', workflow: this.workflowData, workflowInstance, executionData, executionId: this.executionId, }; return await instance[methodName].call(instance, context); }); break; case 'workflowExecuteResume': hooks.addHandler(eventName, async function (workflowInstance, executionData) { const context = { type: 'workflowExecuteResume', workflow: this.workflowData, workflowInstance, executionData, executionId: this.executionId, }; return await instance[methodName].call(instance, context); }); break; } } } }; ModulesHooksRegistry = __decorate([ (0, di_1.Service)() ], ModulesHooksRegistry); function hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName) { const eventService = di_1.Container.get(event_service_1.EventService); hooks.addHandler('workflowExecuteBefore', function () { const { executionId, workflowData, mode } = this; eventService.emit('workflow-pre-execute', { executionId, data: workflowData, mode, projectId, projectName, }); }); hooks.addHandler('workflowExecuteAfter', function (runData) { if (runData.status === 'waiting') { const { executionId, workflowData: workflow } = this; const lastNodeName = runData.data.resultData.lastNodeExecuted; const lastNodeTaskData = lastNodeName ? runData.data.resultData.runData[lastNodeName] : undefined; const latestTask = lastNodeTaskData?.at(-1); const isWaitingForWebhook = latestTask?.metadata?.resumeUrl; if (isWaitingForWebhook) { eventService.emit('execution-waiting', { executionId, workflowId: workflow.id, }); } return; } const { executionId, workflowData: workflow } = this; if (runData.data.startData) { const originalDestination = runData.data.startData.originalDestinationNode; if (originalDestination) { runData.data.startData.destinationNode = originalDestination; runData.data.startData.originalDestinationNode = undefined; } } eventService.emit('workflow-post-execute', { executionId, runData, workflow, userId, projectId, projectName, }); }); } function hookFunctionsNodeEvents(hooks) { const eventService = di_1.Container.get(event_service_1.EventService); hooks.addHandler('nodeExecuteBefore', function (nodeName) { const { executionId, workflowData: workflow } = this; const node = workflow.nodes.find((n) => n.name === nodeName); eventService.emit('node-pre-execute', { executionId, workflow, nodeId: node?.id, nodeName, nodeType: node?.type, }); }); hooks.addHandler('nodeExecuteAfter', function (nodeName) { const { executionId, workflowData: workflow } = this; const node = workflow.nodes.find((n) => n.name === nodeName); eventService.emit('node-post-execute', { executionId, workflow, nodeId: node?.id, nodeName, nodeType: node?.type, }); }); } function buildRedactableExecution(hooks, runData, executionData) { return { id: hooks.executionId, mode: hooks.mode, workflowId: hooks.workflowData.id, data: { resultData: { runData }, executionData: executionData?.executionData, }, workflowData: { settings: hooks.workflowData.settings, nodes: hooks.workflowData.nodes, }, }; } function hookFunctionsPush(hooks, { pushRef, retryOf }, userId) { if (!pushRef) return; const logger = di_1.Container.get(backend_common_1.Logger); const pushInstance = di_1.Container.get(push_1.Push); const redactionProxy = di_1.Container.get(execution_redaction_proxy_service_1.ExecutionRedactionServiceProxy); const userRepository = di_1.Container.get(db_1.UserRepository); let resolvedUser; async function getUser() { if (resolvedUser !== undefined) return resolvedUser; resolvedUser = userId ? await userRepository.findOne({ where: { id: userId }, relations: ['role'] }) : null; return resolvedUser; } hooks.addHandler('nodeExecuteBefore', function (nodeName, data) { const { executionId } = this; logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, { executionId, pushRef, workflowId: this.workflowData.id, }); pushInstance.send({ type: 'nodeExecuteBefore', data: { executionId, nodeName, data } }, pushRef); }); hooks.addHandler('nodeExecuteAfter', async function (nodeName, data, executionData) { const { executionId } = this; logger.debug(`Executing hook on node "${nodeName}" (hookFunctionsPush)`, { executionId, pushRef, workflowId: this.workflowData.id, }); const itemCountByConnectionType = (0, get_item_count_by_connection_type_1.getItemCountByConnectionType)(data?.data); const { data: _, ...taskData } = data; pushInstance.send({ type: 'nodeExecuteAfter', data: { executionId, nodeName, itemCountByConnectionType, data: taskData }, }, pushRef); const user = await getUser(); if (!user) { logger.warn('Skipping execution data push: unable to resolve user for redaction', { executionId, nodeName, userId, }); return; } let dataToSend = data; try { const dummy = buildRedactableExecution(this, { [nodeName]: [data] }, executionData); const result = await redactionProxy.processExecution(dummy, { user, keepOriginal: true, }); if (result !== dummy) { dataToSend = result.data.resultData.runData[nodeName][0]; } } catch (error) { logger.error('Failed to redact push data, skipping nodeExecuteAfterData', { executionId, nodeName, error, }); return; } const asBinary = true; pushInstance.send({ type: 'nodeExecuteAfterData', data: { executionId, nodeName, itemCountByConnectionType, data: dataToSend }, }, pushRef, asBinary); }); hooks.addHandler('workflowExecuteBefore', async function (_workflow, data) { const { executionId } = this; const { id: workflowId, name: workflowName } = this.workflowData; logger.debug('Executing hook (hookFunctionsPush)', { executionId, pushRef, workflowId, }); const user = await getUser(); let runDataToStringify = {}; const hasRunData = data?.resultData.runData && Object.keys(data.resultData.runData).length > 0; if (hasRunData && user) { try { const dummy = buildRedactableExecution(this, data.resultData.runData, data); const result = await redactionProxy.processExecution(dummy, { user, keepOriginal: true, }); runDataToStringify = result !== dummy ? result.data.resultData.runData : data.resultData.runData; } catch (error) { logger.error('Failed to redact execution start data, sending empty runData', { executionId, workflowId, error, }); } } else if (hasRunData && !user) { logger.warn('Cannot redact execution start data: unable to resolve user', { executionId, workflowId, userId, }); } pushInstance.send({ type: 'executionStarted', data: { executionId, mode: this.mode, startedAt: new Date(), retryOf, workflowId, workflowName, flattedRunData: (0, flatted_1.stringify)(runDataToStringify), }, }, pushRef); }); hooks.addHandler('workflowExecuteAfter', function (fullRunData) { const { executionId } = this; const { id: workflowId } = this.workflowData; logger.debug('Executing hook (hookFunctionsPush)', { executionId, pushRef, workflowId, }); const { status } = fullRunData; if (status === 'waiting') { pushInstance.send({ type: 'executionWaiting', data: { executionId } }, pushRef); } else { pushInstance.send({ type: 'executionFinished', data: { executionId, workflowId, status } }, pushRef); } }); } function hookFunctionsExternalHooks(hooks) { const externalHooks = di_1.Container.get(external_hooks_1.ExternalHooks); hooks.addHandler('workflowExecuteBefore', async function (workflow) { await externalHooks.run('workflow.preExecute', [workflow, this.mode]); }); hooks.addHandler('workflowExecuteAfter', async function (fullRunData) { await externalHooks.run('workflow.postExecute', [ fullRunData, this.workflowData, this.executionId, ]); }); } function hookFunctionsSaveProgress(hooks, { saveSettings }) { if (!saveSettings.progress) return; hooks.addHandler('nodeExecuteAfter', async function (nodeName, data, executionData) { await (0, save_execution_progress_1.saveExecutionProgress)(this.workflowData.id, this.executionId, nodeName, data, executionData); }); } function hookFunctionsFinalizeExecutionStatus(hooks) { hooks.addHandler('workflowExecuteAfter', (fullRunData) => { fullRunData.status = (0, shared_hook_functions_1.determineFinalExecutionStatus)(fullRunData); }); } function hookFunctionsStatistics(hooks) { const workflowStatisticsService = di_1.Container.get(workflow_statistics_service_1.WorkflowStatisticsService); hooks.addHandler('nodeFetchedData', (workflowId, node) => { workflowStatisticsService.emit('nodeFetchedData', { workflowId, node }); }); } async function duplicateBinaryDataToParent(fullRunData, parentExecution, binaryDataService) { const outputData = (0, workflow_helpers_1.getDataLastExecutedNodeData)(fullRunData); if (outputData?.data?.main) { const duplicatedData = await binaryDataService.duplicateBinaryData(n8n_core_1.FileLocation.ofExecution(parentExecution.workflowId, parentExecution.executionId), outputData.data.main); outputData.data.main = duplicatedData; } } function hookFunctionsSave(hooks, { pushRef, retryOf, saveSettings, parentExecution }) { const logger = di_1.Container.get(backend_common_1.Logger); const errorReporter = di_1.Container.get(n8n_core_1.ErrorReporter); const executionRepository = di_1.Container.get(db_1.ExecutionRepository); const executionPersistence = di_1.Container.get(execution_persistence_1.ExecutionPersistence); const binaryDataService = di_1.Container.get(n8n_core_1.BinaryDataService); const workflowStaticDataService = di_1.Container.get(workflow_static_data_service_1.WorkflowStaticDataService); const workflowStatisticsService = di_1.Container.get(workflow_statistics_service_1.WorkflowStatisticsService); hooks.addHandler('workflowExecuteAfter', async function (fullRunData, newStaticData) { logger.debug('Executing hook (hookFunctionsSave)', { executionId: this.executionId, workflowId: this.workflowData.id, }); await (0, restore_binary_data_id_1.restoreBinaryDataId)(fullRunData, this.executionId, this.mode); if (parentExecution) { await duplicateBinaryDataToParent(fullRunData, parentExecution, binaryDataService); } const isManualMode = this.mode === 'manual'; try { if (!isManualMode && (0, utils_1.isWorkflowIdValid)(this.workflowData.id) && newStaticData) { try { await workflowStaticDataService.saveStaticDataById(this.workflowData.id, newStaticData); } catch (e) { errorReporter.error(e); logger.error(`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (hookFunctionsSave)`, { executionId: this.executionId, workflowId: this.workflowData.id }); } } if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) { await executionRepository.softDelete(this.executionId); return; } const shouldNotSave = (fullRunData.status === 'success' && !saveSettings.success) || (fullRunData.status !== 'success' && !saveSettings.error); if (shouldNotSave && !fullRunData.waitTill && !isManualMode) { (0, execute_error_workflow_1.executeErrorWorkflow)(this.workflowData, fullRunData, this.mode, this.executionId, retryOf); await executionPersistence.deleteInFlightExecution({ workflowId: this.workflowData.id, executionId: this.executionId, storedAt: fullRunData.storedAt, }); return; } const fullExecutionData = (0, shared_hook_functions_1.prepareExecutionDataForDbUpdate)({ runData: fullRunData, workflowData: this.workflowData, workflowStatusFinal: fullRunData.status, retryOf, }); if (fullRunData.waitTill && isManualMode) { fullExecutionData.data.pushRef = pushRef; } await (0, shared_hook_functions_1.updateExistingExecution)({ executionId: this.executionId, workflowId: this.workflowData.id, executionData: fullExecutionData, }); await (0, shared_hook_functions_1.updateExistingExecutionMetadata)(this.executionId, fullRunData.data?.resultData?.metadata); if (!isManualMode) { (0, execute_error_workflow_1.executeErrorWorkflow)(this.workflowData, fullRunData, this.mode, this.executionId, retryOf); } } finally { workflowStatisticsService.emit('workflowExecutionCompleted', { workflowData: this.workflowData, fullRunData, }); } }); } function hookFunctionsSaveWorker(hooks, { pushRef, retryOf }) { const logger = di_1.Container.get(backend_common_1.Logger); const errorReporter = di_1.Container.get(n8n_core_1.ErrorReporter); const workflowStaticDataService = di_1.Container.get(workflow_static_data_service_1.WorkflowStaticDataService); const workflowStatisticsService = di_1.Container.get(workflow_statistics_service_1.WorkflowStatisticsService); hooks.addHandler('workflowExecuteAfter', async function (fullRunData, newStaticData) { logger.debug('Executing hook (hookFunctionsSaveWorker)', { executionId: this.executionId, workflowId: this.workflowData.id, }); const isManualMode = this.mode === 'manual'; try { if (!isManualMode && (0, utils_1.isWorkflowIdValid)(this.workflowData.id) && newStaticData) { try { await workflowStaticDataService.saveStaticDataById(this.workflowData.id, newStaticData); } catch (e) { errorReporter.error(e); logger.error(`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: "${e.message}" (workflowExecuteAfter)`, { workflowId: this.workflowData.id }); } } if (!isManualMode && fullRunData.status !== 'success' && fullRunData.status !== 'waiting') { (0, execute_error_workflow_1.executeErrorWorkflow)(this.workflowData, fullRunData, this.mode, this.executionId, retryOf); } const fullExecutionData = (0, shared_hook_functions_1.prepareExecutionDataForDbUpdate)({ runData: fullRunData, workflowData: this.workflowData, workflowStatusFinal: fullRunData.status, retryOf, }); if (fullRunData.waitTill && isManualMode) { fullExecutionData.data.pushRef = pushRef; } await (0, shared_hook_functions_1.updateExistingExecution)({ executionId: this.executionId, workflowId: this.workflowData.id, executionData: fullExecutionData, }); } finally { workflowStatisticsService.emit('workflowExecutionCompleted', { workflowData: this.workflowData, fullRunData, }); } }); } function getLifecycleHooksForSubExecutions(mode, executionId, workflowData, userId, parentExecution, projectId, projectName) { const hooks = new n8n_core_1.ExecutionLifecycleHooks(mode, executionId, workflowData); const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings); hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName); hookFunctionsNodeEvents(hooks); hookFunctionsFinalizeExecutionStatus(hooks); hookFunctionsSave(hooks, { saveSettings, parentExecution }); hookFunctionsSaveProgress(hooks, { saveSettings }); hookFunctionsStatistics(hooks); hookFunctionsExternalHooks(hooks); di_1.Container.get(ModulesHooksRegistry).addHooks(hooks); return hooks; } function getLifecycleHooksForScalingWorker(data, executionId) { const { pushRef, retryOf, executionMode, workflowData } = data; const hooks = new n8n_core_1.ExecutionLifecycleHooks(executionMode, executionId, workflowData, retryOf ?? undefined); const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings); const optionalParameters = { pushRef, retryOf: retryOf ?? undefined, saveSettings }; hookFunctionsNodeEvents(hooks); hookFunctionsFinalizeExecutionStatus(hooks); hookFunctionsSaveWorker(hooks, optionalParameters); hookFunctionsSaveProgress(hooks, optionalParameters); hookFunctionsStatistics(hooks); hookFunctionsExternalHooks(hooks); if (executionMode === 'manual' && di_1.Container.get(n8n_core_1.InstanceSettings).isWorker) { hookFunctionsPush(hooks, optionalParameters, data.userId); } di_1.Container.get(ModulesHooksRegistry).addHooks(hooks); return hooks; } function getLifecycleHooksForScalingMain(data, executionId) { const { pushRef, retryOf, executionMode, workflowData, userId, projectId, projectName } = data; const hooks = new n8n_core_1.ExecutionLifecycleHooks(executionMode, executionId, workflowData, retryOf ?? undefined); const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings); const optionalParameters = { pushRef, retryOf: retryOf ?? undefined, saveSettings }; const executionRepository = di_1.Container.get(db_1.ExecutionRepository); const executionPersistence = di_1.Container.get(execution_persistence_1.ExecutionPersistence); hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName); hookFunctionsSaveProgress(hooks, optionalParameters); hookFunctionsExternalHooks(hooks); hookFunctionsFinalizeExecutionStatus(hooks); hooks.addHandler('workflowExecuteAfter', async function (fullRunData) { const terminalStatuses = ['success', 'error', 'crashed', 'canceled']; if (!terminalStatuses.includes(fullRunData.status) && !fullRunData.waitTill) return; const isManualMode = this.mode === 'manual'; if (isManualMode && !saveSettings.manual && !fullRunData.waitTill) { await executionRepository.softDelete(this.executionId); return; } const shouldNotSave = (fullRunData.status === 'success' && !saveSettings.success) || (fullRunData.status !== 'success' && !saveSettings.error); if (!isManualMode && shouldNotSave && !fullRunData.waitTill) { await executionPersistence.deleteInFlightExecution({ workflowId: this.workflowData.id, executionId: this.executionId, storedAt: fullRunData.storedAt, }); } else { await (0, shared_hook_functions_1.updateExistingExecutionMetadata)(this.executionId, fullRunData.data?.resultData?.metadata); } }); hooks.handlers.nodeExecuteBefore = []; hooks.handlers.nodeExecuteAfter = []; di_1.Container.get(ModulesHooksRegistry).addHooks(hooks); return hooks; } function getLifecycleHooksForRegularMain(data, executionId) { const { pushRef, retryOf, executionMode, workflowData, userId, projectId, projectName } = data; const hooks = new n8n_core_1.ExecutionLifecycleHooks(executionMode, executionId, workflowData, retryOf ?? undefined); const saveSettings = (0, to_save_settings_1.toSaveSettings)(workflowData.settings); const optionalParameters = { pushRef, retryOf: retryOf ?? undefined, saveSettings }; hookFunctionsWorkflowEvents(hooks, userId, projectId, projectName); hookFunctionsNodeEvents(hooks); hookFunctionsFinalizeExecutionStatus(hooks); hookFunctionsSave(hooks, optionalParameters); hookFunctionsPush(hooks, optionalParameters, userId); hookFunctionsSaveProgress(hooks, optionalParameters); hookFunctionsStatistics(hooks); hookFunctionsExternalHooks(hooks); di_1.Container.get(ModulesHooksRegistry).addHooks(hooks); return hooks; } //# sourceMappingURL=execution-lifecycle-hooks.js.map