n8n/packages/nodes-base/nodes/Code/JsTaskRunnerSandbox.ts
Tomi Turtiainen e22d0f3877
perf(core): Batch items sent in runonceforeachitem mode (no-changelog) (#11870)
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
2024-11-26 12:21:51 +02:00

106 lines
2.8 KiB
TypeScript

import {
ApplicationError,
type CodeExecutionMode,
type IExecuteFunctions,
type INodeExecutionData,
type WorkflowExecuteMode,
} from 'n8n-workflow';
import { isWrappableError, WrappedExecutionError } from './errors/WrappedExecutionError';
import { validateNoDisallowedMethodsInRunForEach } from './JsCodeValidator';
/**
* JS Code execution sandbox that executes the JS code using task runner.
*/
export class JsTaskRunnerSandbox {
constructor(
private readonly jsCode: string,
private readonly nodeMode: CodeExecutionMode,
private readonly workflowMode: WorkflowExecuteMode,
private readonly executeFunctions: IExecuteFunctions,
private readonly chunkSize = 1000,
) {}
async runCodeAllItems(): Promise<INodeExecutionData[]> {
const itemIndex = 0;
const executionResult = await this.executeFunctions.startJob<INodeExecutionData[]>(
'javascript',
{
code: this.jsCode,
nodeMode: this.nodeMode,
workflowMode: this.workflowMode,
continueOnFail: this.executeFunctions.continueOnFail(),
},
itemIndex,
);
return executionResult.ok
? executionResult.result
: this.throwExecutionError(executionResult.error);
}
async runCodeForEachItem(numInputItems: number): Promise<INodeExecutionData[]> {
validateNoDisallowedMethodsInRunForEach(this.jsCode, 0);
const itemIndex = 0;
const chunks = this.chunkInputItems(numInputItems);
let executionResults: INodeExecutionData[] = [];
for (const chunk of chunks) {
const executionResult = await this.executeFunctions.startJob<INodeExecutionData[]>(
'javascript',
{
code: this.jsCode,
nodeMode: this.nodeMode,
workflowMode: this.workflowMode,
continueOnFail: this.executeFunctions.continueOnFail(),
chunk: {
startIndex: chunk.startIdx,
count: chunk.count,
},
},
itemIndex,
);
if (!executionResult.ok) {
return this.throwExecutionError(executionResult.error);
}
executionResults = executionResults.concat(executionResult.result);
}
return executionResults;
}
private throwExecutionError(error: unknown): never {
if (error instanceof Error) {
throw error;
} else if (isWrappableError(error)) {
// The error coming from task runner is not an instance of error,
// so we need to wrap it in an error instance.
throw new WrappedExecutionError(error);
}
throw new ApplicationError(`Unknown error: ${JSON.stringify(error)}`);
}
/** Chunks the input items into chunks of 1000 items each */
private chunkInputItems(numInputItems: number) {
const numChunks = Math.ceil(numInputItems / this.chunkSize);
const chunks = [];
for (let i = 0; i < numChunks; i++) {
const startIdx = i * this.chunkSize;
const isLastChunk = i === numChunks - 1;
const count = isLastChunk ? numInputItems - startIdx : this.chunkSize;
chunks.push({
startIdx,
count,
});
}
return chunks;
}
}