mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
extract out execute-single and webhook contexts
This commit is contained in:
parent
7b945afebd
commit
b1c5559832
|
@ -138,7 +138,7 @@ export class N8nLlmTracing extends BaseCallbackHandler {
|
||||||
this.executionFunctions.addOutputData(this.connectionType, runDetails.index, [
|
this.executionFunctions.addOutputData(this.connectionType, runDetails.index, [
|
||||||
[{ json: { ...response } }],
|
[{ json: { ...response } }],
|
||||||
]);
|
]);
|
||||||
void logAiEvent(this.executionFunctions, 'ai-llm-generated-output', {
|
logAiEvent(this.executionFunctions, 'ai-llm-generated-output', {
|
||||||
messages: parsedMessages,
|
messages: parsedMessages,
|
||||||
options: runDetails.options,
|
options: runDetails.options,
|
||||||
response,
|
response,
|
||||||
|
@ -186,7 +186,7 @@ export class N8nLlmTracing extends BaseCallbackHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void logAiEvent(this.executionFunctions, 'ai-llm-errored', {
|
logAiEvent(this.executionFunctions, 'ai-llm-errored', {
|
||||||
error: Object.keys(error).length === 0 ? error.toString() : error,
|
error: Object.keys(error).length === 0 ? error.toString() : error,
|
||||||
runId,
|
runId,
|
||||||
parentRunId,
|
parentRunId,
|
||||||
|
|
|
@ -281,7 +281,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
resultData.push(...serializedDocs);
|
resultData.push(...serializedDocs);
|
||||||
void logAiEvent(this, 'ai-vector-store-searched', { query: prompt });
|
logAiEvent(this, 'ai-vector-store-searched', { query: prompt });
|
||||||
}
|
}
|
||||||
|
|
||||||
return [resultData];
|
return [resultData];
|
||||||
|
@ -311,7 +311,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
try {
|
try {
|
||||||
await args.populateVectorStore(this, embeddings, processedDocuments, itemIndex);
|
await args.populateVectorStore(this, embeddings, processedDocuments, itemIndex);
|
||||||
|
|
||||||
void logAiEvent(this, 'ai-vector-store-populated');
|
logAiEvent(this, 'ai-vector-store-populated');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -365,7 +365,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
ids: [documentId],
|
ids: [documentId],
|
||||||
});
|
});
|
||||||
|
|
||||||
void logAiEvent(this, 'ai-vector-store-updated');
|
logAiEvent(this, 'ai-vector-store-updated');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,13 +133,13 @@ export function getSessionId(
|
||||||
return sessionId;
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logAiEvent(
|
export function logAiEvent(
|
||||||
executeFunctions: IExecuteFunctions,
|
executeFunctions: IExecuteFunctions,
|
||||||
event: AiEvent,
|
event: AiEvent,
|
||||||
data?: IDataObject,
|
data?: IDataObject,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await executeFunctions.logAiEvent(event, data ? jsonStringify(data) : undefined);
|
executeFunctions.logAiEvent(event, data ? jsonStringify(data) : undefined);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
executeFunctions.logger.debug(`Error logging AI event: ${event}`);
|
executeFunctions.logger.debug(`Error logging AI event: ${event}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,7 +190,7 @@ export function logWrapper(
|
||||||
const payload = { action: 'getMessages', response };
|
const payload = { action: 'getMessages', response };
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: payload }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: payload }]]);
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-messages-retrieved-from-memory', { response });
|
logAiEvent(executeFunctions, 'ai-messages-retrieved-from-memory', { response });
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
} else if (prop === 'addMessage' && 'addMessage' in target) {
|
} else if (prop === 'addMessage' && 'addMessage' in target) {
|
||||||
|
@ -207,7 +207,7 @@ export function logWrapper(
|
||||||
arguments: [message],
|
arguments: [message],
|
||||||
});
|
});
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-message-added-to-memory', { message });
|
logAiEvent(executeFunctions, 'ai-message-added-to-memory', { message });
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: payload }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: payload }]]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ export function logWrapper(
|
||||||
arguments: [query, config],
|
arguments: [query, config],
|
||||||
})) as Array<Document<Record<string, any>>>;
|
})) as Array<Document<Record<string, any>>>;
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-documents-retrieved', { query });
|
logAiEvent(executeFunctions, 'ai-documents-retrieved', { query });
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
@ -258,7 +258,7 @@ export function logWrapper(
|
||||||
arguments: [documents],
|
arguments: [documents],
|
||||||
})) as number[][];
|
})) as number[][];
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-document-embedded');
|
logAiEvent(executeFunctions, 'ai-document-embedded');
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
@ -278,7 +278,7 @@ export function logWrapper(
|
||||||
method: target[prop],
|
method: target[prop],
|
||||||
arguments: [query],
|
arguments: [query],
|
||||||
})) as number[];
|
})) as number[];
|
||||||
void logAiEvent(executeFunctions, 'ai-query-embedded');
|
logAiEvent(executeFunctions, 'ai-query-embedded');
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
@ -323,7 +323,7 @@ export function logWrapper(
|
||||||
arguments: [item, itemIndex],
|
arguments: [item, itemIndex],
|
||||||
})) as number[];
|
})) as number[];
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-document-processed');
|
logAiEvent(executeFunctions, 'ai-document-processed');
|
||||||
executeFunctions.addOutputData(connectionType, index, [
|
executeFunctions.addOutputData(connectionType, index, [
|
||||||
[{ json: { response }, pairedItem: { item: itemIndex } }],
|
[{ json: { response }, pairedItem: { item: itemIndex } }],
|
||||||
]);
|
]);
|
||||||
|
@ -349,7 +349,7 @@ export function logWrapper(
|
||||||
arguments: [text],
|
arguments: [text],
|
||||||
})) as string[];
|
})) as string[];
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-text-split');
|
logAiEvent(executeFunctions, 'ai-text-split');
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
@ -373,7 +373,7 @@ export function logWrapper(
|
||||||
arguments: [query],
|
arguments: [query],
|
||||||
})) as string;
|
})) as string;
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-tool-called', { query, response });
|
logAiEvent(executeFunctions, 'ai-tool-called', { query, response });
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
@ -403,7 +403,7 @@ export function logWrapper(
|
||||||
arguments: [query, k, filter, _callbacks],
|
arguments: [query, k, filter, _callbacks],
|
||||||
})) as Array<Document<Record<string, any>>>;
|
})) as Array<Document<Record<string, any>>>;
|
||||||
|
|
||||||
void logAiEvent(executeFunctions, 'ai-vector-store-searched', { query });
|
logAiEvent(executeFunctions, 'ai-vector-store-searched', { query });
|
||||||
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
executeFunctions.addOutputData(connectionType, index, [[{ json: { response } }]]);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class N8nOutputFixingParser extends BaseOutputParser {
|
||||||
try {
|
try {
|
||||||
// First attempt to parse the completion
|
// First attempt to parse the completion
|
||||||
const response = await this.outputParser.parse(completion, callbacks, (e) => e);
|
const response = await this.outputParser.parse(completion, callbacks, (e) => e);
|
||||||
void logAiEvent(this.context, 'ai-output-parsed', { text: completion, response });
|
logAiEvent(this.context, 'ai-output-parsed', { text: completion, response });
|
||||||
|
|
||||||
this.context.addOutputData(NodeConnectionType.AiOutputParser, index, [
|
this.context.addOutputData(NodeConnectionType.AiOutputParser, index, [
|
||||||
[{ json: { action: 'parse', response } }],
|
[{ json: { action: 'parse', response } }],
|
||||||
|
|
|
@ -39,7 +39,7 @@ export class N8nStructuredOutputParser extends StructuredOutputParser<
|
||||||
get(parsed, STRUCTURED_OUTPUT_KEY) ??
|
get(parsed, STRUCTURED_OUTPUT_KEY) ??
|
||||||
parsed) as Record<string, unknown>;
|
parsed) as Record<string, unknown>;
|
||||||
|
|
||||||
void logAiEvent(this.context, 'ai-output-parsed', { text, response: result });
|
logAiEvent(this.context, 'ai-output-parsed', { text, response: result });
|
||||||
|
|
||||||
this.context.addOutputData(NodeConnectionType.AiOutputParser, index, [
|
this.context.addOutputData(NodeConnectionType.AiOutputParser, index, [
|
||||||
[{ json: { action: 'parse', response: result } }],
|
[{ json: { action: 'parse', response: result } }],
|
||||||
|
@ -56,7 +56,7 @@ export class N8nStructuredOutputParser extends StructuredOutputParser<
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
void logAiEvent(this.context, 'ai-output-parsed', {
|
logAiEvent(this.context, 'ai-output-parsed', {
|
||||||
text,
|
text,
|
||||||
response: e.message ?? e,
|
response: e.message ?? e,
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,7 +78,6 @@ import type {
|
||||||
IPollFunctions,
|
IPollFunctions,
|
||||||
IRequestOptions,
|
IRequestOptions,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ISourceData,
|
|
||||||
ITaskData,
|
ITaskData,
|
||||||
ITaskDataConnections,
|
ITaskDataConnections,
|
||||||
ITriggerFunctions,
|
ITriggerFunctions,
|
||||||
|
@ -169,6 +168,8 @@ import { getSecretsProxy } from './Secrets';
|
||||||
import { SSHClientsManager } from './SSHClientsManager';
|
import { SSHClientsManager } from './SSHClientsManager';
|
||||||
import { PollContext } from './node-execution-context';
|
import { PollContext } from './node-execution-context';
|
||||||
import { TriggerContext } from './node-execution-context/trigger-context';
|
import { TriggerContext } from './node-execution-context/trigger-context';
|
||||||
|
import { ExecuteSingleContext } from './node-execution-context/execute-single-context';
|
||||||
|
import { WebhookContext } from './node-execution-context/webhook-context';
|
||||||
|
|
||||||
axios.defaults.timeout = 300000;
|
axios.defaults.timeout = 300000;
|
||||||
// Prevent axios from adding x-form-www-urlencoded headers by default
|
// Prevent axios from adding x-form-www-urlencoded headers by default
|
||||||
|
@ -2712,7 +2713,7 @@ const addExecutionDataFunctions = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getInputConnectionData(
|
export async function getInputConnectionData(
|
||||||
context: IAllExecuteFunctions,
|
context: IAllExecuteFunctions,
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
runExecutionData: IRunExecutionData,
|
runExecutionData: IRunExecutionData,
|
||||||
|
@ -2998,14 +2999,6 @@ const getSSHTunnelFunctions = (): SSHTunnelFunctions => ({
|
||||||
await Container.get(SSHClientsManager).getClient(credentials),
|
await Container.get(SSHClientsManager).getClient(credentials),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSchedulingFunctions = (workflow: Workflow): SchedulingFunctions => {
|
|
||||||
const scheduledTaskManager = Container.get(ScheduledTaskManager);
|
|
||||||
return {
|
|
||||||
registerCron: (cronExpression, onTick) =>
|
|
||||||
scheduledTaskManager.registerCron(workflow, cronExpression, onTick),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAllowedPaths = () => {
|
const getAllowedPaths = () => {
|
||||||
const restrictFileAccessTo = process.env[RESTRICT_FILE_ACCESS_TO];
|
const restrictFileAccessTo = process.env[RESTRICT_FILE_ACCESS_TO];
|
||||||
if (!restrictFileAccessTo) {
|
if (!restrictFileAccessTo) {
|
||||||
|
@ -3530,7 +3523,7 @@ export function getExecuteFunctions(
|
||||||
constructExecutionMetaData,
|
constructExecutionMetaData,
|
||||||
},
|
},
|
||||||
nodeHelpers: getNodeHelperFunctions(additionalData, workflow.id),
|
nodeHelpers: getNodeHelperFunctions(additionalData, workflow.id),
|
||||||
logAiEvent: async (eventName: AiEvent, msg: string) => {
|
logAiEvent: (eventName: AiEvent, msg: string) => {
|
||||||
return additionalData.logAiEvent(eventName, {
|
return additionalData.logAiEvent(eventName, {
|
||||||
executionId: additionalData.executionId ?? 'unsaved-execution',
|
executionId: additionalData.executionId ?? 'unsaved-execution',
|
||||||
nodeName: node.name,
|
nodeName: node.name,
|
||||||
|
@ -3558,161 +3551,33 @@ export function getExecuteFunctions(
|
||||||
})(workflow, runExecutionData, connectionInputData, inputData, node) as IExecuteFunctions;
|
})(workflow, runExecutionData, connectionInputData, inputData, node) as IExecuteFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @deprecated */
|
||||||
* Returns the execute functions regular nodes have access to when single-function is defined.
|
|
||||||
*/
|
|
||||||
export function getExecuteSingleFunctions(
|
export function getExecuteSingleFunctions(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
|
node: INode,
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
runExecutionData: IRunExecutionData,
|
runExecutionData: IRunExecutionData,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
connectionInputData: INodeExecutionData[],
|
connectionInputData: INodeExecutionData[],
|
||||||
inputData: ITaskDataConnections,
|
inputData: ITaskDataConnections,
|
||||||
node: INode,
|
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
|
||||||
executeData: IExecuteData,
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
abortSignal?: AbortSignal,
|
abortSignal?: AbortSignal,
|
||||||
): IExecuteSingleFunctions {
|
): IExecuteSingleFunctions {
|
||||||
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
return new ExecuteSingleContext(
|
||||||
return {
|
workflow,
|
||||||
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
node,
|
||||||
...executionCancellationFunctions(abortSignal),
|
additionalData,
|
||||||
continueOnFail: () => continueOnFail(node),
|
runExecutionData,
|
||||||
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
|
runIndex,
|
||||||
evaluateItemIndex = evaluateItemIndex === undefined ? itemIndex : evaluateItemIndex;
|
connectionInputData,
|
||||||
return workflow.expression.resolveSimpleParameterValue(
|
inputData,
|
||||||
`=${expression}`,
|
itemIndex,
|
||||||
{},
|
executeData,
|
||||||
runExecutionData,
|
mode,
|
||||||
runIndex,
|
abortSignal,
|
||||||
evaluateItemIndex,
|
);
|
||||||
node.name,
|
|
||||||
connectionInputData,
|
|
||||||
mode,
|
|
||||||
getAdditionalKeys(additionalData, mode, runExecutionData),
|
|
||||||
executeData,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getContext(type: ContextType): IContextObject {
|
|
||||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
|
||||||
},
|
|
||||||
getCredentials: async (type) =>
|
|
||||||
await getCredentials(
|
|
||||||
workflow,
|
|
||||||
node,
|
|
||||||
type,
|
|
||||||
additionalData,
|
|
||||||
mode,
|
|
||||||
executeData,
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
connectionInputData,
|
|
||||||
itemIndex,
|
|
||||||
),
|
|
||||||
getInputData: (inputIndex = 0, inputName = 'main') => {
|
|
||||||
if (!inputData.hasOwnProperty(inputName)) {
|
|
||||||
// Return empty array because else it would throw error when nothing is connected to input
|
|
||||||
return { json: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if nodeType has input with that index defined
|
|
||||||
if (inputData[inputName].length < inputIndex) {
|
|
||||||
throw new ApplicationError('Could not get input index', {
|
|
||||||
extra: { inputIndex, inputName },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const allItems = inputData[inputName][inputIndex];
|
|
||||||
|
|
||||||
if (allItems === null) {
|
|
||||||
throw new ApplicationError('Input index was not set', {
|
|
||||||
extra: { inputIndex, inputName },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allItems[itemIndex] === null) {
|
|
||||||
throw new ApplicationError('Value of input with given index was not set', {
|
|
||||||
extra: { inputIndex, inputName, itemIndex },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return allItems[itemIndex];
|
|
||||||
},
|
|
||||||
getInputSourceData: (inputIndex = 0, inputName = 'main') => {
|
|
||||||
if (executeData?.source === null) {
|
|
||||||
// Should never happen as n8n sets it automatically
|
|
||||||
throw new ApplicationError('Source data is missing');
|
|
||||||
}
|
|
||||||
return executeData.source[inputName][inputIndex] as ISourceData;
|
|
||||||
},
|
|
||||||
getItemIndex: () => itemIndex,
|
|
||||||
getMode: () => mode,
|
|
||||||
getExecuteData: () => executeData,
|
|
||||||
getNodeParameter: (
|
|
||||||
parameterName: string,
|
|
||||||
fallbackValue?: any,
|
|
||||||
options?: IGetNodeParameterOptions,
|
|
||||||
): NodeParameterValueType | object => {
|
|
||||||
return getNodeParameter(
|
|
||||||
workflow,
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
connectionInputData,
|
|
||||||
node,
|
|
||||||
parameterName,
|
|
||||||
itemIndex,
|
|
||||||
mode,
|
|
||||||
getAdditionalKeys(additionalData, mode, runExecutionData),
|
|
||||||
executeData,
|
|
||||||
fallbackValue,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getWorkflowDataProxy: (): IWorkflowDataProxyData => {
|
|
||||||
const dataProxy = new WorkflowDataProxy(
|
|
||||||
workflow,
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
itemIndex,
|
|
||||||
node.name,
|
|
||||||
connectionInputData,
|
|
||||||
{},
|
|
||||||
mode,
|
|
||||||
getAdditionalKeys(additionalData, mode, runExecutionData),
|
|
||||||
executeData,
|
|
||||||
);
|
|
||||||
return dataProxy.getDataProxy();
|
|
||||||
},
|
|
||||||
helpers: {
|
|
||||||
createDeferredPromise,
|
|
||||||
returnJsonArray,
|
|
||||||
...getRequestHelperFunctions(
|
|
||||||
workflow,
|
|
||||||
node,
|
|
||||||
additionalData,
|
|
||||||
runExecutionData,
|
|
||||||
connectionInputData,
|
|
||||||
),
|
|
||||||
...getBinaryHelperFunctions(additionalData, workflow.id),
|
|
||||||
|
|
||||||
assertBinaryData: (propertyName, inputIndex = 0) =>
|
|
||||||
assertBinaryData(inputData, node, itemIndex, propertyName, inputIndex),
|
|
||||||
getBinaryDataBuffer: async (propertyName, inputIndex = 0) =>
|
|
||||||
await getBinaryDataBuffer(inputData, itemIndex, propertyName, inputIndex),
|
|
||||||
},
|
|
||||||
logAiEvent: async (eventName: AiEvent, msg: string) => {
|
|
||||||
return additionalData.logAiEvent(eventName, {
|
|
||||||
executionId: additionalData.executionId ?? 'unsaved-execution',
|
|
||||||
nodeName: node.name,
|
|
||||||
workflowName: workflow.name ?? 'Unnamed workflow',
|
|
||||||
nodeType: node.type,
|
|
||||||
workflowId: workflow.id ?? 'unsaved-workflow',
|
|
||||||
msg,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCredentialTestFunctions(): ICredentialTestFunctions {
|
export function getCredentialTestFunctions(): ICredentialTestFunctions {
|
||||||
|
@ -3871,10 +3736,7 @@ export function getExecuteHookFunctions(
|
||||||
})(workflow, node);
|
})(workflow, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @deprecated */
|
||||||
* Returns the execute functions regular nodes have access to when webhook-function is defined.
|
|
||||||
*/
|
|
||||||
// TODO: check where it is used and make sure close functions are called
|
|
||||||
export function getExecuteWebhookFunctions(
|
export function getExecuteWebhookFunctions(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
node: INode,
|
node: INode,
|
||||||
|
@ -3884,169 +3746,13 @@ export function getExecuteWebhookFunctions(
|
||||||
closeFunctions: CloseFunction[],
|
closeFunctions: CloseFunction[],
|
||||||
runExecutionData: IRunExecutionData | null,
|
runExecutionData: IRunExecutionData | null,
|
||||||
): IWebhookFunctions {
|
): IWebhookFunctions {
|
||||||
return ((workflow: Workflow, node: INode, runExecutionData: IRunExecutionData | null) => {
|
return new WebhookContext(
|
||||||
return {
|
workflow,
|
||||||
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
node,
|
||||||
getBodyData(): IDataObject {
|
additionalData,
|
||||||
if (additionalData.httpRequest === undefined) {
|
mode,
|
||||||
throw new ApplicationError('Request is missing');
|
webhookData,
|
||||||
}
|
closeFunctions,
|
||||||
return additionalData.httpRequest.body;
|
runExecutionData,
|
||||||
},
|
);
|
||||||
getCredentials: async (type) =>
|
|
||||||
await getCredentials(workflow, node, type, additionalData, mode),
|
|
||||||
getHeaderData(): IncomingHttpHeaders {
|
|
||||||
if (additionalData.httpRequest === undefined) {
|
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return additionalData.httpRequest.headers;
|
|
||||||
},
|
|
||||||
async getInputConnectionData(
|
|
||||||
inputName: NodeConnectionType,
|
|
||||||
itemIndex: number,
|
|
||||||
): Promise<unknown> {
|
|
||||||
// To be able to use expressions like "$json.sessionId" set the
|
|
||||||
// body data the webhook received to what is normally used for
|
|
||||||
// incoming node data.
|
|
||||||
const connectionInputData: INodeExecutionData[] = [
|
|
||||||
{ json: additionalData.httpRequest?.body || {} },
|
|
||||||
];
|
|
||||||
const runExecutionData: IRunExecutionData = {
|
|
||||||
resultData: {
|
|
||||||
runData: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const executeData: IExecuteData = {
|
|
||||||
data: {
|
|
||||||
main: [connectionInputData],
|
|
||||||
},
|
|
||||||
node,
|
|
||||||
source: null,
|
|
||||||
};
|
|
||||||
const runIndex = 0;
|
|
||||||
|
|
||||||
return await getInputConnectionData(
|
|
||||||
this,
|
|
||||||
workflow,
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
connectionInputData,
|
|
||||||
additionalData,
|
|
||||||
executeData,
|
|
||||||
mode,
|
|
||||||
closeFunctions,
|
|
||||||
inputName,
|
|
||||||
itemIndex,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getMode: () => mode,
|
|
||||||
evaluateExpression: (expression: string, evaluateItemIndex?: number) => {
|
|
||||||
const itemIndex = evaluateItemIndex === undefined ? 0 : evaluateItemIndex;
|
|
||||||
const runIndex = 0;
|
|
||||||
|
|
||||||
let connectionInputData: INodeExecutionData[] = [];
|
|
||||||
let executionData: IExecuteData | undefined;
|
|
||||||
|
|
||||||
if (runExecutionData?.executionData !== undefined) {
|
|
||||||
executionData = runExecutionData.executionData.nodeExecutionStack[0];
|
|
||||||
|
|
||||||
if (executionData !== undefined) {
|
|
||||||
connectionInputData = executionData.data.main[0]!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalKeys = getAdditionalKeys(additionalData, mode, runExecutionData);
|
|
||||||
|
|
||||||
return workflow.expression.resolveSimpleParameterValue(
|
|
||||||
`=${expression}`,
|
|
||||||
{},
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
itemIndex,
|
|
||||||
node.name,
|
|
||||||
connectionInputData,
|
|
||||||
mode,
|
|
||||||
additionalKeys,
|
|
||||||
executionData,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getNodeParameter: (
|
|
||||||
parameterName: string,
|
|
||||||
fallbackValue?: any,
|
|
||||||
options?: IGetNodeParameterOptions,
|
|
||||||
): NodeParameterValueType | object => {
|
|
||||||
const itemIndex = 0;
|
|
||||||
const runIndex = 0;
|
|
||||||
|
|
||||||
let connectionInputData: INodeExecutionData[] = [];
|
|
||||||
let executionData: IExecuteData | undefined;
|
|
||||||
|
|
||||||
if (runExecutionData?.executionData !== undefined) {
|
|
||||||
executionData = runExecutionData.executionData.nodeExecutionStack[0];
|
|
||||||
|
|
||||||
if (executionData !== undefined) {
|
|
||||||
connectionInputData = executionData.data.main[0]!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalKeys = getAdditionalKeys(additionalData, mode, runExecutionData);
|
|
||||||
|
|
||||||
return getNodeParameter(
|
|
||||||
workflow,
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
connectionInputData,
|
|
||||||
node,
|
|
||||||
parameterName,
|
|
||||||
itemIndex,
|
|
||||||
mode,
|
|
||||||
additionalKeys,
|
|
||||||
executionData,
|
|
||||||
fallbackValue,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getParamsData(): object {
|
|
||||||
if (additionalData.httpRequest === undefined) {
|
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return additionalData.httpRequest.params;
|
|
||||||
},
|
|
||||||
getQueryData(): object {
|
|
||||||
if (additionalData.httpRequest === undefined) {
|
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return additionalData.httpRequest.query;
|
|
||||||
},
|
|
||||||
getRequestObject(): Request {
|
|
||||||
if (additionalData.httpRequest === undefined) {
|
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return additionalData.httpRequest;
|
|
||||||
},
|
|
||||||
getResponseObject(): Response {
|
|
||||||
if (additionalData.httpResponse === undefined) {
|
|
||||||
throw new ApplicationError('Response is missing');
|
|
||||||
}
|
|
||||||
return additionalData.httpResponse;
|
|
||||||
},
|
|
||||||
getNodeWebhookUrl: (name: string): string | undefined =>
|
|
||||||
getNodeWebhookUrl(
|
|
||||||
name,
|
|
||||||
workflow,
|
|
||||||
node,
|
|
||||||
additionalData,
|
|
||||||
mode,
|
|
||||||
getAdditionalKeys(additionalData, mode, null),
|
|
||||||
),
|
|
||||||
getWebhookName: () => webhookData.webhookDescription.name,
|
|
||||||
helpers: {
|
|
||||||
createDeferredPromise,
|
|
||||||
...getRequestHelperFunctions(workflow, node, additionalData),
|
|
||||||
...getBinaryHelperFunctions(additionalData, workflow.id),
|
|
||||||
returnJsonArray,
|
|
||||||
},
|
|
||||||
nodeHelpers: getNodeHelperFunctions(additionalData, workflow.id),
|
|
||||||
};
|
|
||||||
})(workflow, node, runExecutionData);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
import type {
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
IGetNodeParameterOptions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
IRunExecutionData,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
Workflow,
|
||||||
|
WorkflowExecuteMode,
|
||||||
|
ITaskDataConnections,
|
||||||
|
IExecuteData,
|
||||||
|
ContextType,
|
||||||
|
AiEvent,
|
||||||
|
ISourceData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
ApplicationError,
|
||||||
|
createDeferredPromise,
|
||||||
|
NodeHelpers,
|
||||||
|
WorkflowDataProxy,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
assertBinaryData,
|
||||||
|
continueOnFail,
|
||||||
|
getAdditionalKeys,
|
||||||
|
getBinaryDataBuffer,
|
||||||
|
getCredentials,
|
||||||
|
getNodeParameter,
|
||||||
|
returnJsonArray,
|
||||||
|
} from '@/NodeExecuteFunctions';
|
||||||
|
import { BaseContext } from './base-contexts';
|
||||||
|
import { BinaryHelpers } from './helpers/binary-helpers';
|
||||||
|
import { RequestHelpers } from './helpers/request-helpers';
|
||||||
|
|
||||||
|
export class ExecuteSingleContext extends BaseContext implements IExecuteSingleFunctions {
|
||||||
|
readonly helpers: IExecuteSingleFunctions['helpers'];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
workflow: Workflow,
|
||||||
|
node: INode,
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
private readonly runExecutionData: IRunExecutionData,
|
||||||
|
private readonly runIndex: number,
|
||||||
|
private readonly connectionInputData: INodeExecutionData[],
|
||||||
|
private readonly inputData: ITaskDataConnections,
|
||||||
|
private readonly itemIndex: number,
|
||||||
|
private readonly executeData: IExecuteData,
|
||||||
|
private readonly mode: WorkflowExecuteMode,
|
||||||
|
private readonly abortSignal?: AbortSignal,
|
||||||
|
) {
|
||||||
|
super(workflow, node, additionalData);
|
||||||
|
|
||||||
|
const binaryHelpers = new BinaryHelpers(workflow, additionalData);
|
||||||
|
const requestHelpers = new RequestHelpers(this, workflow, node, additionalData);
|
||||||
|
|
||||||
|
this.helpers = {
|
||||||
|
createDeferredPromise: () => createDeferredPromise(),
|
||||||
|
returnJsonArray: (items) => returnJsonArray(items),
|
||||||
|
|
||||||
|
getBinaryPath: (id) => binaryHelpers.getBinaryPath(id),
|
||||||
|
getBinaryMetadata: (id) => binaryHelpers.getBinaryMetadata(id),
|
||||||
|
getBinaryStream: (id) => binaryHelpers.getBinaryStream(id),
|
||||||
|
binaryToBuffer: (body) => binaryHelpers.binaryToBuffer(body),
|
||||||
|
binaryToString: (body) => binaryHelpers.binaryToString(body),
|
||||||
|
prepareBinaryData: binaryHelpers.prepareBinaryData.bind(binaryHelpers),
|
||||||
|
setBinaryDataBuffer: binaryHelpers.setBinaryDataBuffer.bind(binaryHelpers),
|
||||||
|
copyBinaryFile: () => binaryHelpers.copyBinaryFile(),
|
||||||
|
assertBinaryData: (propertyName, inputIndex = 0) =>
|
||||||
|
assertBinaryData(inputData, node, itemIndex, propertyName, inputIndex),
|
||||||
|
getBinaryDataBuffer: async (propertyName, inputIndex = 0) =>
|
||||||
|
await getBinaryDataBuffer(inputData, itemIndex, propertyName, inputIndex),
|
||||||
|
|
||||||
|
httpRequest: requestHelpers.httpRequest.bind(requestHelpers),
|
||||||
|
httpRequestWithAuthentication:
|
||||||
|
requestHelpers.httpRequestWithAuthentication.bind(requestHelpers),
|
||||||
|
requestWithAuthenticationPaginated:
|
||||||
|
requestHelpers.requestWithAuthenticationPaginated.bind(requestHelpers),
|
||||||
|
request: requestHelpers.request.bind(requestHelpers),
|
||||||
|
requestWithAuthentication: requestHelpers.requestWithAuthentication.bind(requestHelpers),
|
||||||
|
requestOAuth1: requestHelpers.requestOAuth1.bind(requestHelpers),
|
||||||
|
requestOAuth2: requestHelpers.requestOAuth2.bind(requestHelpers),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getExecutionCancelSignal() {
|
||||||
|
return this.abortSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
onExecutionCancellation(handler: () => unknown) {
|
||||||
|
const fn = () => {
|
||||||
|
this.abortSignal?.removeEventListener('abort', fn);
|
||||||
|
handler();
|
||||||
|
};
|
||||||
|
this.abortSignal?.addEventListener('abort', fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
continueOnFail() {
|
||||||
|
return continueOnFail(this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluateExpression(expression: string, evaluateItemIndex: number | undefined) {
|
||||||
|
evaluateItemIndex = evaluateItemIndex === undefined ? this.itemIndex : evaluateItemIndex;
|
||||||
|
return this.workflow.expression.resolveSimpleParameterValue(
|
||||||
|
`=${expression}`,
|
||||||
|
{},
|
||||||
|
this.runExecutionData,
|
||||||
|
this.runIndex,
|
||||||
|
evaluateItemIndex,
|
||||||
|
this.node.name,
|
||||||
|
this.connectionInputData,
|
||||||
|
this.mode,
|
||||||
|
getAdditionalKeys(this.additionalData, this.mode, this.runExecutionData),
|
||||||
|
this.executeData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext(type: ContextType) {
|
||||||
|
return NodeHelpers.getContext(this.runExecutionData, type, this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputData(inputIndex = 0, inputName = 'main') {
|
||||||
|
if (!this.inputData.hasOwnProperty(inputName)) {
|
||||||
|
// Return empty array because else it would throw error when nothing is connected to input
|
||||||
|
return { json: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check if nodeType has input with that index defined
|
||||||
|
if (this.inputData[inputName].length < inputIndex) {
|
||||||
|
throw new ApplicationError('Could not get input index', {
|
||||||
|
extra: { inputIndex, inputName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const allItems = this.inputData[inputName][inputIndex];
|
||||||
|
|
||||||
|
if (allItems === null) {
|
||||||
|
throw new ApplicationError('Input index was not set', {
|
||||||
|
extra: { inputIndex, inputName },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allItems[this.itemIndex] === null) {
|
||||||
|
throw new ApplicationError('Value of input with given index was not set', {
|
||||||
|
extra: { inputIndex, inputName, itemIndex: this.itemIndex },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return allItems[this.itemIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemIndex() {
|
||||||
|
return this.itemIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeParameter(parameterName: string, fallbackValue?: any, options?: IGetNodeParameterOptions) {
|
||||||
|
return getNodeParameter(
|
||||||
|
this.workflow,
|
||||||
|
this.runExecutionData,
|
||||||
|
this.runIndex,
|
||||||
|
this.connectionInputData,
|
||||||
|
this.node,
|
||||||
|
parameterName,
|
||||||
|
this.itemIndex,
|
||||||
|
this.mode,
|
||||||
|
getAdditionalKeys(this.additionalData, this.mode, this.runExecutionData),
|
||||||
|
this.executeData,
|
||||||
|
fallbackValue,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCredentials<T extends object = ICredentialDataDecryptedObject>(type: string) {
|
||||||
|
return await getCredentials<T>(
|
||||||
|
this.workflow,
|
||||||
|
this.node,
|
||||||
|
type,
|
||||||
|
this.additionalData,
|
||||||
|
this.mode,
|
||||||
|
this.executeData,
|
||||||
|
this.runExecutionData,
|
||||||
|
this.runIndex,
|
||||||
|
this.connectionInputData,
|
||||||
|
this.itemIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMode() {
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
getExecuteData() {
|
||||||
|
return this.executeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorkflowDataProxy() {
|
||||||
|
return new WorkflowDataProxy(
|
||||||
|
this.workflow,
|
||||||
|
this.runExecutionData,
|
||||||
|
this.runIndex,
|
||||||
|
this.itemIndex,
|
||||||
|
this.node.name,
|
||||||
|
this.connectionInputData,
|
||||||
|
{},
|
||||||
|
this.mode,
|
||||||
|
getAdditionalKeys(this.additionalData, this.mode, this.runExecutionData),
|
||||||
|
this.executeData,
|
||||||
|
).getDataProxy();
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputSourceData(inputIndex = 0, inputName = '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[inputName][inputIndex] as ISourceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,4 @@
|
||||||
|
export { ExecuteSingleContext } from './execute-single-context';
|
||||||
export { PollContext } from './poll-context';
|
export { PollContext } from './poll-context';
|
||||||
export { TriggerContext } from './trigger-context';
|
export { TriggerContext } from './trigger-context';
|
||||||
|
export { WebhookContext } from './webhook-context';
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class PollContext extends BaseContext implements IPollFunctions {
|
||||||
|
|
||||||
const binaryHelpers = new BinaryHelpers(workflow, additionalData);
|
const binaryHelpers = new BinaryHelpers(workflow, additionalData);
|
||||||
const requestHelpers = new RequestHelpers(this, workflow, node, additionalData);
|
const requestHelpers = new RequestHelpers(this, workflow, node, additionalData);
|
||||||
const schedulingHelepers = new SchedulingHelpers(workflow);
|
const schedulingHelpers = new SchedulingHelpers(workflow);
|
||||||
|
|
||||||
this.helpers = {
|
this.helpers = {
|
||||||
createDeferredPromise: () => createDeferredPromise(),
|
createDeferredPromise: () => createDeferredPromise(),
|
||||||
|
@ -73,7 +73,7 @@ export class PollContext extends BaseContext implements IPollFunctions {
|
||||||
requestOAuth1: requestHelpers.requestOAuth1.bind(requestHelpers),
|
requestOAuth1: requestHelpers.requestOAuth1.bind(requestHelpers),
|
||||||
requestOAuth2: requestHelpers.requestOAuth2.bind(requestHelpers),
|
requestOAuth2: requestHelpers.requestOAuth2.bind(requestHelpers),
|
||||||
|
|
||||||
registerCron: schedulingHelepers.registerCron.bind(schedulingHelepers),
|
registerCron: schedulingHelpers.registerCron.bind(schedulingHelpers),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ export class TriggerContext extends BaseContext implements ITriggerFunctions {
|
||||||
|
|
||||||
const binaryHelpers = new BinaryHelpers(workflow, additionalData);
|
const binaryHelpers = new BinaryHelpers(workflow, additionalData);
|
||||||
const requestHelpers = new RequestHelpers(this, workflow, node, additionalData);
|
const requestHelpers = new RequestHelpers(this, workflow, node, additionalData);
|
||||||
const schedulingHelepers = new SchedulingHelpers(workflow);
|
const schedulingHelpers = new SchedulingHelpers(workflow);
|
||||||
const sshTunnelHelpers = new SSHTunnelHelpers();
|
const sshTunnelHelpers = new SSHTunnelHelpers();
|
||||||
|
|
||||||
// TODO: This is almost identical to the helpers in PollContext.
|
// TODO: This is almost identical to the helpers in PollContext.
|
||||||
|
@ -76,7 +76,7 @@ export class TriggerContext extends BaseContext implements ITriggerFunctions {
|
||||||
requestOAuth1: requestHelpers.requestOAuth1.bind(requestHelpers),
|
requestOAuth1: requestHelpers.requestOAuth1.bind(requestHelpers),
|
||||||
requestOAuth2: requestHelpers.requestOAuth2.bind(requestHelpers),
|
requestOAuth2: requestHelpers.requestOAuth2.bind(requestHelpers),
|
||||||
|
|
||||||
registerCron: schedulingHelepers.registerCron.bind(schedulingHelepers),
|
registerCron: schedulingHelpers.registerCron.bind(schedulingHelpers),
|
||||||
|
|
||||||
getSSHClient: sshTunnelHelpers.getSSHClient.bind(sshTunnelHelpers),
|
getSSHClient: sshTunnelHelpers.getSSHClient.bind(sshTunnelHelpers),
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
import type { Request, Response } from 'express';
|
||||||
|
import type {
|
||||||
|
CloseFunction,
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteData,
|
||||||
|
IGetNodeParameterOptions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
IRunExecutionData,
|
||||||
|
IWebhookData,
|
||||||
|
IWebhookFunctions,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
NodeConnectionType,
|
||||||
|
NodeParameterValueType,
|
||||||
|
Workflow,
|
||||||
|
WorkflowExecuteMode,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { ApplicationError, createDeferredPromise } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
copyBinaryFile,
|
||||||
|
getAdditionalKeys,
|
||||||
|
getCredentials,
|
||||||
|
getInputConnectionData,
|
||||||
|
getNodeParameter,
|
||||||
|
getNodeWebhookUrl,
|
||||||
|
returnJsonArray,
|
||||||
|
} from '@/NodeExecuteFunctions';
|
||||||
|
import { BaseContext } from './base-contexts';
|
||||||
|
import { BinaryHelpers } from './helpers/binary-helpers';
|
||||||
|
import { RequestHelpers } from './helpers/request-helpers';
|
||||||
|
|
||||||
|
export class WebhookContext extends BaseContext implements IWebhookFunctions {
|
||||||
|
readonly helpers: IWebhookFunctions['helpers'];
|
||||||
|
|
||||||
|
readonly nodeHelpers: IWebhookFunctions['nodeHelpers'];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
workflow: Workflow,
|
||||||
|
node: INode,
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
private readonly mode: WorkflowExecuteMode,
|
||||||
|
private readonly webhookData: IWebhookData,
|
||||||
|
private readonly closeFunctions: CloseFunction[],
|
||||||
|
private readonly runExecutionData: IRunExecutionData | null,
|
||||||
|
) {
|
||||||
|
super(workflow, node, additionalData);
|
||||||
|
|
||||||
|
const binaryHelpers = new BinaryHelpers(workflow, additionalData);
|
||||||
|
const requestHelpers = new RequestHelpers(this, workflow, node, additionalData);
|
||||||
|
|
||||||
|
this.helpers = {
|
||||||
|
createDeferredPromise: () => createDeferredPromise(),
|
||||||
|
returnJsonArray: (items) => returnJsonArray(items),
|
||||||
|
|
||||||
|
getBinaryPath: (id) => binaryHelpers.getBinaryPath(id),
|
||||||
|
getBinaryMetadata: (id) => binaryHelpers.getBinaryMetadata(id),
|
||||||
|
getBinaryStream: (id) => binaryHelpers.getBinaryStream(id),
|
||||||
|
binaryToBuffer: (body) => binaryHelpers.binaryToBuffer(body),
|
||||||
|
binaryToString: (body) => binaryHelpers.binaryToString(body),
|
||||||
|
prepareBinaryData: binaryHelpers.prepareBinaryData.bind(binaryHelpers),
|
||||||
|
setBinaryDataBuffer: binaryHelpers.setBinaryDataBuffer.bind(binaryHelpers),
|
||||||
|
copyBinaryFile: () => binaryHelpers.copyBinaryFile(),
|
||||||
|
|
||||||
|
httpRequest: requestHelpers.httpRequest.bind(requestHelpers),
|
||||||
|
httpRequestWithAuthentication:
|
||||||
|
requestHelpers.httpRequestWithAuthentication.bind(requestHelpers),
|
||||||
|
requestWithAuthenticationPaginated:
|
||||||
|
requestHelpers.requestWithAuthenticationPaginated.bind(requestHelpers),
|
||||||
|
request: requestHelpers.request.bind(requestHelpers),
|
||||||
|
requestWithAuthentication: requestHelpers.requestWithAuthentication.bind(requestHelpers),
|
||||||
|
requestOAuth1: requestHelpers.requestOAuth1.bind(requestHelpers),
|
||||||
|
requestOAuth2: requestHelpers.requestOAuth2.bind(requestHelpers),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.nodeHelpers = {
|
||||||
|
copyBinaryFile: async (filePath, fileName, mimeType) =>
|
||||||
|
await copyBinaryFile(
|
||||||
|
this.workflow.id,
|
||||||
|
this.additionalData.executionId!,
|
||||||
|
filePath,
|
||||||
|
fileName,
|
||||||
|
mimeType,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getMode() {
|
||||||
|
return this.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCredentials<T extends object = ICredentialDataDecryptedObject>(type: string) {
|
||||||
|
return await getCredentials<T>(this.workflow, this.node, type, this.additionalData, this.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBodyData() {
|
||||||
|
if (this.additionalData.httpRequest === undefined) {
|
||||||
|
throw new ApplicationError('Request is missing');
|
||||||
|
}
|
||||||
|
return this.additionalData.httpRequest.body as IDataObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaderData() {
|
||||||
|
if (this.additionalData.httpRequest === undefined) {
|
||||||
|
throw new ApplicationError('Request is missing');
|
||||||
|
}
|
||||||
|
return this.additionalData.httpRequest.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
getParamsData(): object {
|
||||||
|
if (this.additionalData.httpRequest === undefined) {
|
||||||
|
throw new ApplicationError('Request is missing');
|
||||||
|
}
|
||||||
|
return this.additionalData.httpRequest.params;
|
||||||
|
}
|
||||||
|
|
||||||
|
getQueryData(): object {
|
||||||
|
if (this.additionalData.httpRequest === undefined) {
|
||||||
|
throw new ApplicationError('Request is missing');
|
||||||
|
}
|
||||||
|
return this.additionalData.httpRequest.query;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequestObject(): Request {
|
||||||
|
if (this.additionalData.httpRequest === undefined) {
|
||||||
|
throw new ApplicationError('Request is missing');
|
||||||
|
}
|
||||||
|
return this.additionalData.httpRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponseObject(): Response {
|
||||||
|
if (this.additionalData.httpResponse === undefined) {
|
||||||
|
throw new ApplicationError('Response is missing');
|
||||||
|
}
|
||||||
|
return this.additionalData.httpResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeWebhookUrl(name: string): string | undefined {
|
||||||
|
return getNodeWebhookUrl(
|
||||||
|
name,
|
||||||
|
this.workflow,
|
||||||
|
this.node,
|
||||||
|
this.additionalData,
|
||||||
|
this.mode,
|
||||||
|
getAdditionalKeys(this.additionalData, this.mode, null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWebhookName() {
|
||||||
|
return this.webhookData.webhookDescription.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInputConnectionData(inputName: NodeConnectionType, itemIndex: number): Promise<unknown> {
|
||||||
|
// To be able to use expressions like "$json.sessionId" set the
|
||||||
|
// body data the webhook received to what is normally used for
|
||||||
|
// incoming node data.
|
||||||
|
const connectionInputData: INodeExecutionData[] = [
|
||||||
|
{ json: this.additionalData.httpRequest?.body || {} },
|
||||||
|
];
|
||||||
|
const runExecutionData: IRunExecutionData = {
|
||||||
|
resultData: {
|
||||||
|
runData: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const executeData: IExecuteData = {
|
||||||
|
data: {
|
||||||
|
main: [connectionInputData],
|
||||||
|
},
|
||||||
|
node: this.node,
|
||||||
|
source: null,
|
||||||
|
};
|
||||||
|
const runIndex = 0;
|
||||||
|
|
||||||
|
return await getInputConnectionData(
|
||||||
|
this,
|
||||||
|
this.workflow,
|
||||||
|
runExecutionData,
|
||||||
|
runIndex,
|
||||||
|
connectionInputData,
|
||||||
|
this.additionalData,
|
||||||
|
executeData,
|
||||||
|
this.mode,
|
||||||
|
this.closeFunctions,
|
||||||
|
inputName,
|
||||||
|
itemIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluateExpression(expression: string, evaluateItemIndex?: number) {
|
||||||
|
const itemIndex = evaluateItemIndex === undefined ? 0 : evaluateItemIndex;
|
||||||
|
const runIndex = 0;
|
||||||
|
|
||||||
|
let connectionInputData: INodeExecutionData[] = [];
|
||||||
|
let executionData: IExecuteData | undefined;
|
||||||
|
|
||||||
|
if (this.runExecutionData?.executionData !== undefined) {
|
||||||
|
executionData = this.runExecutionData.executionData.nodeExecutionStack[0];
|
||||||
|
|
||||||
|
if (executionData !== undefined) {
|
||||||
|
connectionInputData = executionData.data.main[0]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalKeys = getAdditionalKeys(this.additionalData, this.mode, this.runExecutionData);
|
||||||
|
|
||||||
|
return this.workflow.expression.resolveSimpleParameterValue(
|
||||||
|
`=${expression}`,
|
||||||
|
{},
|
||||||
|
this.runExecutionData,
|
||||||
|
runIndex,
|
||||||
|
itemIndex,
|
||||||
|
this.node.name,
|
||||||
|
connectionInputData,
|
||||||
|
this.mode,
|
||||||
|
additionalKeys,
|
||||||
|
executionData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeParameter(
|
||||||
|
parameterName: string,
|
||||||
|
fallbackValue?: any,
|
||||||
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
|
const itemIndex = 0;
|
||||||
|
const runIndex = 0;
|
||||||
|
|
||||||
|
let connectionInputData: INodeExecutionData[] = [];
|
||||||
|
let executionData: IExecuteData | undefined;
|
||||||
|
|
||||||
|
if (this.runExecutionData?.executionData !== undefined) {
|
||||||
|
executionData = this.runExecutionData.executionData.nodeExecutionStack[0];
|
||||||
|
|
||||||
|
if (executionData !== undefined) {
|
||||||
|
connectionInputData = executionData.data.main[0]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalKeys = getAdditionalKeys(this.additionalData, this.mode, this.runExecutionData);
|
||||||
|
|
||||||
|
return getNodeParameter(
|
||||||
|
this.workflow,
|
||||||
|
this.runExecutionData,
|
||||||
|
runIndex,
|
||||||
|
connectionInputData,
|
||||||
|
this.node,
|
||||||
|
parameterName,
|
||||||
|
itemIndex,
|
||||||
|
this.mode,
|
||||||
|
additionalKeys,
|
||||||
|
executionData,
|
||||||
|
fallbackValue,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -438,13 +438,13 @@ export interface IGetExecuteFunctions {
|
||||||
export interface IGetExecuteSingleFunctions {
|
export interface IGetExecuteSingleFunctions {
|
||||||
(
|
(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
|
node: INode,
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
runExecutionData: IRunExecutionData,
|
runExecutionData: IRunExecutionData,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
connectionInputData: INodeExecutionData[],
|
connectionInputData: INodeExecutionData[],
|
||||||
inputData: ITaskDataConnections,
|
inputData: ITaskDataConnections,
|
||||||
node: INode,
|
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
|
||||||
executeData: IExecuteData,
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
abortSignal?: AbortSignal,
|
abortSignal?: AbortSignal,
|
||||||
|
@ -938,7 +938,7 @@ type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & {
|
||||||
getInputSourceData(inputIndex?: number, inputName?: string): ISourceData;
|
getInputSourceData(inputIndex?: number, inputName?: string): ISourceData;
|
||||||
getExecutionCancelSignal(): AbortSignal | undefined;
|
getExecutionCancelSignal(): AbortSignal | undefined;
|
||||||
onExecutionCancellation(handler: () => unknown): void;
|
onExecutionCancellation(handler: () => unknown): void;
|
||||||
logAiEvent(eventName: AiEvent, msg?: string | undefined): Promise<void>;
|
logAiEvent(eventName: AiEvent, msg?: string | undefined): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Create later own type only for Config-Nodes
|
// TODO: Create later own type only for Config-Nodes
|
||||||
|
|
|
@ -170,13 +170,13 @@ export class RoutingNode {
|
||||||
itemContext.push({
|
itemContext.push({
|
||||||
thisArgs: nodeExecuteFunctions.getExecuteSingleFunctions(
|
thisArgs: nodeExecuteFunctions.getExecuteSingleFunctions(
|
||||||
this.workflow,
|
this.workflow,
|
||||||
|
this.node,
|
||||||
|
this.additionalData,
|
||||||
this.runExecutionData,
|
this.runExecutionData,
|
||||||
runIndex,
|
runIndex,
|
||||||
this.connectionInputData,
|
this.connectionInputData,
|
||||||
inputData,
|
inputData,
|
||||||
this.node,
|
|
||||||
itemIndex,
|
itemIndex,
|
||||||
this.additionalData,
|
|
||||||
executeData,
|
executeData,
|
||||||
this.mode,
|
this.mode,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
|
|
|
@ -2128,13 +2128,13 @@ describe('RoutingNode', () => {
|
||||||
|
|
||||||
const routingNodeExecutionContext = nodeExecuteFunctions.getExecuteSingleFunctions(
|
const routingNodeExecutionContext = nodeExecuteFunctions.getExecuteSingleFunctions(
|
||||||
routingNode.workflow,
|
routingNode.workflow,
|
||||||
|
routingNode.node,
|
||||||
|
routingNode.additionalData,
|
||||||
routingNode.runExecutionData,
|
routingNode.runExecutionData,
|
||||||
runIndex,
|
runIndex,
|
||||||
routingNode.connectionInputData,
|
routingNode.connectionInputData,
|
||||||
inputData,
|
inputData,
|
||||||
routingNode.node,
|
|
||||||
iteration,
|
iteration,
|
||||||
routingNode.additionalData,
|
|
||||||
executeData,
|
executeData,
|
||||||
routingNode.mode,
|
routingNode.mode,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue