diff --git a/packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.ts b/packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.ts
index c6ec5342e3209..15add2e03773b 100644
--- a/packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.ts
+++ b/packages/nodes-base/nodes/SplitInBatches/SplitInBatches.node.ts
@@ -1,160 +1,25 @@
-import type {
- IExecuteFunctions,
- INodeExecutionData,
- INodeType,
- INodeTypeDescription,
- IPairedItemData,
-} from 'n8n-workflow';
-import { deepCopy } from 'n8n-workflow';
-
-export class SplitInBatches implements INodeType {
- description: INodeTypeDescription = {
- displayName: 'Split In Batches',
- name: 'splitInBatches',
- icon: 'fa:th-large',
- group: ['organization'],
- version: 1,
- description: 'Split data into batches and iterate over each batch',
- defaults: {
- name: 'Split In Batches',
- color: '#007755',
- },
- inputs: ['main'],
- // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
- outputs: ['main', 'main'],
- outputNames: ['loop', 'done'],
- properties: [
- {
- displayName:
- 'You may not need this node — n8n nodes automatically run once for each input item. More info',
- name: 'splitInBatchesNotice',
- type: 'notice',
- default: '',
- },
- {
- displayName: 'Batch Size',
- name: 'batchSize',
- type: 'number',
- typeOptions: {
- minValue: 1,
- },
- default: 10,
- description: 'The number of items to return with each call',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- options: [
- {
- displayName: 'Reset',
- name: 'reset',
- type: 'boolean',
- default: false,
- description:
- 'Whether the node will be reset and so with the current input-data newly initialized',
- },
- ],
- },
- ],
- };
-
- async execute(this: IExecuteFunctions): Promise {
- // Get the input data and create a new array so that we can remove
- // items without a problem
- const items = this.getInputData().slice();
-
- const nodeContext = this.getContext('node');
-
- const batchSize = this.getNodeParameter('batchSize', 0) as number;
-
- const returnItems: INodeExecutionData[] = [];
-
- const options = this.getNodeParameter('options', 0, {});
-
- if (nodeContext.items === undefined || options.reset === true) {
- // Is the first time the node runs
-
- const sourceData = this.getInputSourceData();
-
- nodeContext.currentRunIndex = 0;
- nodeContext.maxRunIndex = Math.ceil(items.length / batchSize);
- nodeContext.sourceData = deepCopy(sourceData);
-
- // Get the items which should be returned
- returnItems.push.apply(returnItems, items.splice(0, batchSize));
-
- // Save the incoming items to be able to return them for later runs
- nodeContext.items = [...items];
-
- // Reset processedItems as they get only added starting from the first iteration
- nodeContext.processedItems = [];
- } else {
- // The node has been called before. So return the next batch of items.
- nodeContext.currentRunIndex += 1;
- returnItems.push.apply(
- returnItems,
- (nodeContext.items as INodeExecutionData[]).splice(0, batchSize),
- );
-
- const addSourceOverwrite = (pairedItem: IPairedItemData | number): IPairedItemData => {
- if (typeof pairedItem === 'number') {
- return {
- item: pairedItem,
- sourceOverwrite: nodeContext.sourceData,
- };
- }
-
- return {
- ...pairedItem,
- sourceOverwrite: nodeContext.sourceData,
- };
- };
-
- function getPairedItemInformation(
- item: INodeExecutionData,
- ): IPairedItemData | IPairedItemData[] {
- if (item.pairedItem === undefined) {
- return {
- item: 0,
- sourceOverwrite: nodeContext.sourceData,
- };
- }
-
- if (Array.isArray(item.pairedItem)) {
- return item.pairedItem.map(addSourceOverwrite);
- }
-
- return addSourceOverwrite(item.pairedItem);
- }
-
- const sourceOverwrite = this.getInputSourceData();
-
- const newItems = items.map((item, index) => {
- return {
- ...item,
- pairedItem: {
- sourceOverwrite,
- item: index,
- },
- };
- });
-
- nodeContext.processedItems = [...nodeContext.processedItems, ...newItems];
-
- returnItems.map((item) => {
- item.pairedItem = getPairedItemInformation(item);
- });
- }
-
- nodeContext.noItemsLeft = nodeContext.items.length === 0;
-
- if (returnItems.length === 0) {
- return [[], nodeContext.processedItems];
- }
-
- return [returnItems, []];
+import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
+import { VersionedNodeType } from 'n8n-workflow';
+
+import { SplitInBatchesV1 } from './v1/SplitInBatchesV1.node';
+import { SplitInBatchesV2 } from './v2/SplitInBatchesV2.node';
+
+export class SplitInBatches extends VersionedNodeType {
+ constructor() {
+ const baseDescription: INodeTypeBaseDescription = {
+ displayName: 'Split In Batches',
+ name: 'splitInBatches',
+ icon: 'fa:th-large',
+ group: ['organization'],
+ description: 'Split data into batches and iterate over each batch',
+ defaultVersion: 2,
+ };
+
+ const nodeVersions: IVersionedNodeType['nodeVersions'] = {
+ 1: new SplitInBatchesV1(),
+ 2: new SplitInBatchesV2(),
+ };
+
+ super(nodeVersions, baseDescription);
}
}
diff --git a/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.combineData.json b/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.combineData.json
index 2971f99e29ff6..94349deb7fd93 100644
--- a/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.combineData.json
+++ b/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.combineData.json
@@ -26,7 +26,7 @@
},
"name": "SplitInBatches1",
"type": "n8n-nodes-base.splitInBatches",
- "typeVersion": 1,
+ "typeVersion": 2,
"position": [1340, 400],
"id": "02d51797-ae62-4fd6-b703-426a4b3fb951"
},
diff --git a/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.workflow.json b/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.workflow_v1.json
similarity index 100%
rename from packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.workflow.json
rename to packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.workflow_v1.json
diff --git a/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.workflow_v2.json b/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.workflow_v2.json
new file mode 100644
index 0000000000000..a39dd9f41dbfb
--- /dev/null
+++ b/packages/nodes-base/nodes/SplitInBatches/test/SplitInBatches.workflow_v2.json
@@ -0,0 +1,178 @@
+{
+ "name": "Split in Batches Test",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "86b8149f-b0a0-489c-bb62-e59142988996",
+ "name": "When clicking \"Execute Workflow\"",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [400, 220]
+ },
+ {
+ "parameters": {
+ "batchSize": 1,
+ "options": {}
+ },
+ "id": "30c5546e-bdcc-44ff-bfca-89c5fb97b678",
+ "name": "Split In Batches",
+ "type": "n8n-nodes-base.splitInBatches",
+ "typeVersion": 2,
+ "position": [1100, 220]
+ },
+ {
+ "parameters": {
+ "values": {
+ "string": [
+ {
+ "name": "data[0]",
+ "value": "n8n"
+ },
+ {
+ "name": "data[1]",
+ "value": "test"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "92d386b8-60be-4f8b-801c-b6459ec206f7",
+ "name": "Set",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 1,
+ "position": [640, 220]
+ },
+ {
+ "parameters": {
+ "fieldToSplitOut": "data",
+ "options": {}
+ },
+ "id": "74b7e63e-a9f8-4a82-9e1f-7b2429d9118d",
+ "name": "Item Lists",
+ "type": "n8n-nodes-base.itemLists",
+ "typeVersion": 1,
+ "position": [860, 220]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "boolean": [
+ {
+ "value1": "={{ $node[\"Split In Batches\"].context[\"noItemsLeft\"] }}",
+ "value2": true
+ }
+ ]
+ }
+ },
+ "id": "a5f68369-4e70-4f16-b260-3c8b74517993",
+ "name": "IF",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 1,
+ "position": [1280, 220]
+ },
+ {
+ "parameters": {
+ "values": {
+ "string": [
+ {
+ "name": "maxRunIndex",
+ "value": "={{ $node[\"Split In Batches\"].context[\"maxRunIndex\"] }}"
+ },
+ {
+ "value": "={{ $node[\"Split In Batches\"].context[\"currentRunIndex\"] }}"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "id": "1f44eb0a-5fb7-43a7-8281-84a8a7ec8464",
+ "name": "Output",
+ "type": "n8n-nodes-base.set",
+ "typeVersion": 1,
+ "position": [1480, 200]
+ }
+ ],
+ "pinData": {
+ "Output": [
+ {
+ "json": {
+ "data": "test",
+ "maxRunIndex": 2,
+ "propertyName": 1
+ }
+ }
+ ]
+ },
+ "connections": {
+ "When clicking \"Execute Workflow\"": {
+ "main": [
+ [
+ {
+ "node": "Set",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Split In Batches": {
+ "main": [
+ [
+ {
+ "node": "IF",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Set": {
+ "main": [
+ [
+ {
+ "node": "Item Lists",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Item Lists": {
+ "main": [
+ [
+ {
+ "node": "Split In Batches",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "IF": {
+ "main": [
+ [
+ {
+ "node": "Output",
+ "type": "main",
+ "index": 0
+ }
+ ],
+ [
+ {
+ "node": "Split In Batches",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {},
+ "versionId": "de1a454e-43e9-4c2d-b786-18da5d97940f",
+ "id": "389",
+ "meta": {
+ "instanceId": "REMOVED"
+ },
+ "tags": []
+}
diff --git a/packages/nodes-base/nodes/SplitInBatches/v1/SplitInBatchesV1.node.ts b/packages/nodes-base/nodes/SplitInBatches/v1/SplitInBatchesV1.node.ts
new file mode 100644
index 0000000000000..ba7d2c497abfd
--- /dev/null
+++ b/packages/nodes-base/nodes/SplitInBatches/v1/SplitInBatchesV1.node.ts
@@ -0,0 +1,143 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import type {
+ IExecuteFunctions,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+ IPairedItemData,
+} from 'n8n-workflow';
+import { deepCopy } from 'n8n-workflow';
+
+export class SplitInBatchesV1 implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Split In Batches',
+ name: 'splitInBatches',
+ icon: 'fa:th-large',
+ group: ['organization'],
+ version: 1,
+ description: 'Split data into batches and iterate over each batch',
+ defaults: {
+ name: 'Split In Batches',
+ color: '#007755',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ properties: [
+ {
+ displayName:
+ 'You may not need this node — n8n nodes automatically run once for each input item. More info',
+ name: 'splitInBatchesNotice',
+ type: 'notice',
+ default: '',
+ },
+ {
+ displayName: 'Batch Size',
+ name: 'batchSize',
+ type: 'number',
+ typeOptions: {
+ minValue: 1,
+ },
+ default: 10,
+ description: 'The number of items to return with each call',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Reset',
+ name: 'reset',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the node will be reset and so with the current input-data newly initialized',
+ },
+ ],
+ },
+ ],
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ // Get the input data and create a new array so that we can remove
+ // items without a problem
+ const items = this.getInputData().slice();
+
+ const nodeContext = this.getContext('node');
+
+ const batchSize = this.getNodeParameter('batchSize', 0) as number;
+
+ const returnItems: INodeExecutionData[] = [];
+
+ const options = this.getNodeParameter('options', 0, {});
+
+ if (nodeContext.items === undefined || options.reset === true) {
+ // Is the first time the node runs
+
+ const sourceData = this.getInputSourceData();
+
+ nodeContext.currentRunIndex = 0;
+ nodeContext.maxRunIndex = Math.ceil(items.length / batchSize);
+ nodeContext.sourceData = deepCopy(sourceData);
+
+ // Get the items which should be returned
+ returnItems.push.apply(returnItems, items.splice(0, batchSize));
+
+ // Set the other items to be saved in the context to return at later runs
+ nodeContext.items = [...items];
+ } else {
+ // The node has been called before. So return the next batch of items.
+ nodeContext.currentRunIndex += 1;
+ returnItems.push.apply(
+ returnItems,
+ (nodeContext.items as INodeExecutionData[]).splice(0, batchSize),
+ );
+
+ const addSourceOverwrite = (pairedItem: IPairedItemData | number): IPairedItemData => {
+ if (typeof pairedItem === 'number') {
+ return {
+ item: pairedItem,
+ sourceOverwrite: nodeContext.sourceData,
+ };
+ }
+
+ return {
+ ...pairedItem,
+ sourceOverwrite: nodeContext.sourceData,
+ };
+ };
+
+ function getPairedItemInformation(
+ item: INodeExecutionData,
+ ): IPairedItemData | IPairedItemData[] {
+ if (item.pairedItem === undefined) {
+ return {
+ item: 0,
+ sourceOverwrite: nodeContext.sourceData,
+ };
+ }
+
+ if (Array.isArray(item.pairedItem)) {
+ return item.pairedItem.map(addSourceOverwrite);
+ }
+
+ return addSourceOverwrite(item.pairedItem);
+ }
+
+ returnItems.map((item) => {
+ item.pairedItem = getPairedItemInformation(item);
+ });
+ }
+
+ nodeContext.noItemsLeft = nodeContext.items.length === 0;
+
+ if (returnItems.length === 0) {
+ // No data left to return so stop execution of the branch
+ return null;
+ }
+
+ return [returnItems];
+ }
+}
diff --git a/packages/nodes-base/nodes/SplitInBatches/v2/SplitInBatchesV2.node.ts b/packages/nodes-base/nodes/SplitInBatches/v2/SplitInBatchesV2.node.ts
new file mode 100644
index 0000000000000..31252fd48384a
--- /dev/null
+++ b/packages/nodes-base/nodes/SplitInBatches/v2/SplitInBatchesV2.node.ts
@@ -0,0 +1,161 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import type {
+ IExecuteFunctions,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+ IPairedItemData,
+} from 'n8n-workflow';
+import { deepCopy } from 'n8n-workflow';
+
+export class SplitInBatchesV2 implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Split In Batches',
+ name: 'splitInBatches',
+ icon: 'fa:th-large',
+ group: ['organization'],
+ version: 2,
+ description: 'Split data into batches and iterate over each batch',
+ defaults: {
+ name: 'Split In Batches',
+ color: '#007755',
+ },
+ inputs: ['main'],
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
+ outputs: ['main', 'main'],
+ outputNames: ['loop', 'done'],
+ properties: [
+ {
+ displayName:
+ 'You may not need this node — n8n nodes automatically run once for each input item. More info',
+ name: 'splitInBatchesNotice',
+ type: 'notice',
+ default: '',
+ },
+ {
+ displayName: 'Batch Size',
+ name: 'batchSize',
+ type: 'number',
+ typeOptions: {
+ minValue: 1,
+ },
+ default: 10,
+ description: 'The number of items to return with each call',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Reset',
+ name: 'reset',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether the node will be reset and so with the current input-data newly initialized',
+ },
+ ],
+ },
+ ],
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ // Get the input data and create a new array so that we can remove
+ // items without a problem
+ const items = this.getInputData().slice();
+
+ const nodeContext = this.getContext('node');
+
+ const batchSize = this.getNodeParameter('batchSize', 0) as number;
+
+ const returnItems: INodeExecutionData[] = [];
+
+ const options = this.getNodeParameter('options', 0, {});
+
+ if (nodeContext.items === undefined || options.reset === true) {
+ // Is the first time the node runs
+
+ const sourceData = this.getInputSourceData();
+
+ nodeContext.currentRunIndex = 0;
+ nodeContext.maxRunIndex = Math.ceil(items.length / batchSize);
+ nodeContext.sourceData = deepCopy(sourceData);
+
+ // Get the items which should be returned
+ returnItems.push.apply(returnItems, items.splice(0, batchSize));
+
+ // Save the incoming items to be able to return them for later runs
+ nodeContext.items = [...items];
+
+ // Reset processedItems as they get only added starting from the first iteration
+ nodeContext.processedItems = [];
+ } else {
+ // The node has been called before. So return the next batch of items.
+ nodeContext.currentRunIndex += 1;
+ returnItems.push.apply(
+ returnItems,
+ (nodeContext.items as INodeExecutionData[]).splice(0, batchSize),
+ );
+
+ const addSourceOverwrite = (pairedItem: IPairedItemData | number): IPairedItemData => {
+ if (typeof pairedItem === 'number') {
+ return {
+ item: pairedItem,
+ sourceOverwrite: nodeContext.sourceData,
+ };
+ }
+
+ return {
+ ...pairedItem,
+ sourceOverwrite: nodeContext.sourceData,
+ };
+ };
+
+ function getPairedItemInformation(
+ item: INodeExecutionData,
+ ): IPairedItemData | IPairedItemData[] {
+ if (item.pairedItem === undefined) {
+ return {
+ item: 0,
+ sourceOverwrite: nodeContext.sourceData,
+ };
+ }
+
+ if (Array.isArray(item.pairedItem)) {
+ return item.pairedItem.map(addSourceOverwrite);
+ }
+
+ return addSourceOverwrite(item.pairedItem);
+ }
+
+ const sourceOverwrite = this.getInputSourceData();
+
+ const newItems = items.map((item, index) => {
+ return {
+ ...item,
+ pairedItem: {
+ sourceOverwrite,
+ item: index,
+ },
+ };
+ });
+
+ nodeContext.processedItems = [...nodeContext.processedItems, ...newItems];
+
+ returnItems.map((item) => {
+ item.pairedItem = getPairedItemInformation(item);
+ });
+ }
+
+ nodeContext.noItemsLeft = nodeContext.items.length === 0;
+
+ if (returnItems.length === 0) {
+ return [[], nodeContext.processedItems];
+ }
+
+ return [returnItems, []];
+ }
+}