mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
245 lines
7.1 KiB
TypeScript
245 lines
7.1 KiB
TypeScript
import { get } from 'lodash';
|
|
import type {
|
|
Workflow,
|
|
INode,
|
|
IWorkflowExecuteAdditionalData,
|
|
WorkflowExecuteMode,
|
|
IRunExecutionData,
|
|
INodeExecutionData,
|
|
ITaskDataConnections,
|
|
IExecuteData,
|
|
ICredentialDataDecryptedObject,
|
|
CallbackManager,
|
|
IExecuteWorkflowInfo,
|
|
RelatedExecution,
|
|
ExecuteWorkflowData,
|
|
ITaskMetadata,
|
|
ContextType,
|
|
IContextObject,
|
|
INodeInputConfiguration,
|
|
INodeOutputConfiguration,
|
|
IWorkflowDataProxyData,
|
|
ISourceData,
|
|
AiEvent,
|
|
NodeConnectionType,
|
|
} from 'n8n-workflow';
|
|
import { ApplicationError, NodeHelpers, WAIT_INDEFINITELY, WorkflowDataProxy } from 'n8n-workflow';
|
|
import { Container } from 'typedi';
|
|
|
|
import { BinaryDataService } from '@/BinaryData/BinaryData.service';
|
|
|
|
import { NodeExecutionContext } from './node-execution-context';
|
|
|
|
export class BaseExecuteContext extends NodeExecutionContext {
|
|
protected readonly binaryDataService = Container.get(BinaryDataService);
|
|
|
|
constructor(
|
|
workflow: Workflow,
|
|
node: INode,
|
|
additionalData: IWorkflowExecuteAdditionalData,
|
|
mode: WorkflowExecuteMode,
|
|
protected readonly runExecutionData: IRunExecutionData,
|
|
runIndex: number,
|
|
protected readonly connectionInputData: INodeExecutionData[],
|
|
protected readonly inputData: ITaskDataConnections,
|
|
protected readonly executeData: IExecuteData,
|
|
protected readonly abortSignal?: AbortSignal,
|
|
) {
|
|
super(workflow, node, additionalData, mode, runExecutionData, runIndex);
|
|
}
|
|
|
|
getExecutionCancelSignal() {
|
|
return this.abortSignal;
|
|
}
|
|
|
|
onExecutionCancellation(handler: () => unknown) {
|
|
const fn = () => {
|
|
this.abortSignal?.removeEventListener('abort', fn);
|
|
handler();
|
|
};
|
|
this.abortSignal?.addEventListener('abort', fn);
|
|
}
|
|
|
|
getExecuteData() {
|
|
return this.executeData;
|
|
}
|
|
|
|
setMetadata(metadata: ITaskMetadata): void {
|
|
this.executeData.metadata = {
|
|
...(this.executeData.metadata ?? {}),
|
|
...metadata,
|
|
};
|
|
}
|
|
|
|
getContext(type: ContextType): IContextObject {
|
|
return NodeHelpers.getContext(this.runExecutionData, type, this.node);
|
|
}
|
|
|
|
/** Returns if execution should be continued even if there was an error */
|
|
continueOnFail(): boolean {
|
|
const onError = get(this.node, 'onError', undefined);
|
|
|
|
if (onError === undefined) {
|
|
return get(this.node, 'continueOnFail', false);
|
|
}
|
|
|
|
return ['continueRegularOutput', 'continueErrorOutput'].includes(onError);
|
|
}
|
|
|
|
async getCredentials<T extends object = ICredentialDataDecryptedObject>(
|
|
type: string,
|
|
itemIndex: number,
|
|
) {
|
|
return await this._getCredentials<T>(
|
|
type,
|
|
this.executeData,
|
|
this.connectionInputData,
|
|
itemIndex,
|
|
);
|
|
}
|
|
|
|
async putExecutionToWait(waitTill: Date): Promise<void> {
|
|
this.runExecutionData.waitTill = waitTill;
|
|
if (this.additionalData.setExecutionStatus) {
|
|
this.additionalData.setExecutionStatus('waiting');
|
|
}
|
|
}
|
|
|
|
async executeWorkflow(
|
|
workflowInfo: IExecuteWorkflowInfo,
|
|
inputData?: INodeExecutionData[],
|
|
parentCallbackManager?: CallbackManager,
|
|
options?: {
|
|
doNotWaitToFinish?: boolean;
|
|
parentExecution?: RelatedExecution;
|
|
},
|
|
): Promise<ExecuteWorkflowData> {
|
|
const result = await this.additionalData.executeWorkflow(workflowInfo, this.additionalData, {
|
|
...options,
|
|
parentWorkflowId: this.workflow.id,
|
|
inputData,
|
|
parentWorkflowSettings: this.workflow.settings,
|
|
node: this.node,
|
|
parentCallbackManager,
|
|
});
|
|
|
|
// If a sub-workflow execution goes into the waiting state
|
|
if (result.waitTill) {
|
|
// then put the parent workflow execution also into the waiting state,
|
|
// but do not use the sub-workflow `waitTill` to avoid WaitTracker resuming the parent execution at the same time as the sub-workflow
|
|
await this.putExecutionToWait(WAIT_INDEFINITELY);
|
|
}
|
|
|
|
const data = await this.binaryDataService.duplicateBinaryData(
|
|
this.workflow.id,
|
|
this.additionalData.executionId!,
|
|
result.data,
|
|
);
|
|
return { ...result, data };
|
|
}
|
|
|
|
protected getInputItems(inputIndex: number, connectionType: NodeConnectionType) {
|
|
const inputData = this.inputData[connectionType];
|
|
if (inputData.length < inputIndex) {
|
|
throw new ApplicationError('Could not get input with given index', {
|
|
extra: { inputIndex, connectionType },
|
|
});
|
|
}
|
|
|
|
const allItems = inputData[inputIndex] as INodeExecutionData[] | null | undefined;
|
|
if (allItems === null) {
|
|
throw new ApplicationError('Input index was not set', {
|
|
extra: { inputIndex, connectionType },
|
|
});
|
|
}
|
|
|
|
return allItems;
|
|
}
|
|
|
|
getNodeInputs(): INodeInputConfiguration[] {
|
|
const nodeType = this.workflow.nodeTypes.getByNameAndVersion(
|
|
this.node.type,
|
|
this.node.typeVersion,
|
|
);
|
|
return NodeHelpers.getNodeInputs(this.workflow, this.node, nodeType.description).map((input) =>
|
|
typeof input === 'string' ? { type: input } : input,
|
|
);
|
|
}
|
|
|
|
getNodeOutputs(): INodeOutputConfiguration[] {
|
|
const nodeType = this.workflow.nodeTypes.getByNameAndVersion(
|
|
this.node.type,
|
|
this.node.typeVersion,
|
|
);
|
|
return NodeHelpers.getNodeOutputs(this.workflow, this.node, nodeType.description).map(
|
|
(output) => (typeof output === 'string' ? { type: output } : output),
|
|
);
|
|
}
|
|
|
|
getInputSourceData(inputIndex = 0, connectionType = 'main'): ISourceData {
|
|
if (this.executeData?.source === null) {
|
|
// Should never happen as n8n sets it automatically
|
|
throw new ApplicationError('Source data is missing');
|
|
}
|
|
return this.executeData.source[connectionType][inputIndex]!;
|
|
}
|
|
|
|
getWorkflowDataProxy(itemIndex: number): IWorkflowDataProxyData {
|
|
return new WorkflowDataProxy(
|
|
this.workflow,
|
|
this.runExecutionData,
|
|
this.runIndex,
|
|
itemIndex,
|
|
this.node.name,
|
|
this.connectionInputData,
|
|
{},
|
|
this.mode,
|
|
this.additionalKeys,
|
|
this.executeData,
|
|
).getDataProxy();
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
sendMessageToUI(...args: any[]): void {
|
|
if (this.mode !== 'manual') {
|
|
return;
|
|
}
|
|
try {
|
|
if (this.additionalData.sendDataToUI) {
|
|
args = args.map((arg) => {
|
|
// prevent invalid dates from being logged as null
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return
|
|
if (arg.isLuxonDateTime && arg.invalidReason) return { ...arg };
|
|
|
|
// log valid dates in human readable format, as in browser
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
if (arg.isLuxonDateTime) return new Date(arg.ts).toString();
|
|
if (arg instanceof Date) return arg.toString();
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return arg;
|
|
});
|
|
|
|
this.additionalData.sendDataToUI('sendConsoleMessage', {
|
|
source: `[Node: "${this.node.name}"]`,
|
|
messages: args,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
this.logger.warn(`There was a problem sending message to UI: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
logAiEvent(eventName: AiEvent, msg: string) {
|
|
return this.additionalData.logAiEvent(eventName, {
|
|
executionId: this.additionalData.executionId ?? 'unsaved-execution',
|
|
nodeName: this.node.name,
|
|
workflowName: this.workflow.name ?? 'Unnamed workflow',
|
|
nodeType: this.node.type,
|
|
workflowId: this.workflow.id ?? 'unsaved-workflow',
|
|
msg,
|
|
});
|
|
}
|
|
}
|