first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,95 @@
import {
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
type SupplyData,
} from 'n8n-workflow';
import { N8nItemListOutputParser } from '@utils/output_parsers/N8nItemListOutputParser';
import { getConnectionHintNoticeField } from '@n8n/ai-utilities';
export class OutputParserItemList implements INodeType {
description: INodeTypeDescription = {
displayName: 'Item List Output Parser',
name: 'outputParserItemList',
icon: 'fa:bars',
iconColor: 'black',
group: ['transform'],
version: 1,
description: 'Return the results as separate items',
defaults: {
name: 'Item List Output Parser',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Output Parsers'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.outputparseritemlist/',
},
],
},
},
inputs: [],
outputs: [NodeConnectionTypes.AiOutputParser],
outputNames: ['Output Parser'],
properties: [
getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]),
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Number Of Items',
name: 'numberOfItems',
type: 'number',
default: -1,
description:
'Defines how many items should be returned maximally. If set to -1, there is no limit.',
},
// For that to be easily possible the metadata would have to be returned and be able to be read.
// Would also be possible with a wrapper but that would be even more hacky and the output types
// would not be correct anymore.
// {
// displayName: 'Parse Output',
// name: 'parseOutput',
// type: 'boolean',
// default: true,
// description: 'Whether the output should be automatically be parsed or left RAW',
// },
{
displayName: 'Separator',
name: 'separator',
type: 'string',
default: '\\n',
description:
'Defines the separator that should be used to split the results into separate items. Defaults to a new line but can be changed depending on the data that should be returned.',
},
],
},
],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const options = this.getNodeParameter('options', itemIndex, {}) as {
numberOfItems?: number;
separator?: string;
};
const parser = new N8nItemListOutputParser(options);
return {
response: parser,
};
}
}
@@ -0,0 +1,139 @@
import { mock } from 'jest-mock-extended';
import { normalizeItems } from 'n8n-core';
import {
ApplicationError,
type ISupplyDataFunctions,
type IWorkflowDataProxyData,
} from 'n8n-workflow';
import { N8nItemListOutputParser } from '@utils/output_parsers/N8nItemListOutputParser';
import { OutputParserItemList } from '../OutputParserItemList.node';
describe('OutputParserItemList', () => {
let outputParser: OutputParserItemList;
const thisArg = mock<ISupplyDataFunctions>({
helpers: { normalizeItems },
});
const workflowDataProxy = mock<IWorkflowDataProxyData>({ $input: mock() });
beforeEach(() => {
outputParser = new OutputParserItemList();
thisArg.getWorkflowDataProxy.mockReturnValue(workflowDataProxy);
thisArg.addInputData.mockReturnValue({ index: 0 });
thisArg.addOutputData.mockReturnValue();
thisArg.getNodeParameter.mockReset();
});
describe('supplyData', () => {
it('should create a parser with default options', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return {};
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
expect(response).toBeInstanceOf(N8nItemListOutputParser);
expect((response as any).numberOfItems).toBe(3);
});
it('should create a parser with custom number of items', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return { numberOfItems: 5 };
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
expect(response).toBeInstanceOf(N8nItemListOutputParser);
expect((response as any).numberOfItems).toBe(5);
});
it('should create a parser with unlimited number of items', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return { numberOfItems: -1 };
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
expect(response).toBeInstanceOf(N8nItemListOutputParser);
expect((response as any).numberOfItems).toBeUndefined();
});
it('should create a parser with custom separator', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return { separator: ',' };
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
expect(response).toBeInstanceOf(N8nItemListOutputParser);
expect((response as any).separator).toBe(',');
});
});
describe('parse', () => {
it('should parse a list with default separator', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return {};
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
const result = await (response as N8nItemListOutputParser).parse('item1\nitem2\nitem3');
expect(result).toEqual(['item1', 'item2', 'item3']);
});
it('should parse a list with custom separator', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return { separator: ',' };
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
const result = await (response as N8nItemListOutputParser).parse('item1,item2,item3');
expect(result).toEqual(['item1', 'item2', 'item3']);
});
it('should limit the number of items returned', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return { numberOfItems: 2 };
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
const result = await (response as N8nItemListOutputParser).parse(
'item1\nitem2\nitem3\nitem4',
);
expect(result).toEqual(['item1', 'item2']);
});
it('should throw an error if not enough items are returned', async () => {
thisArg.getNodeParameter.mockImplementation((parameterName) => {
if (parameterName === 'options') {
return { numberOfItems: 5 };
}
throw new ApplicationError('Not implemented');
});
const { response } = await outputParser.supplyData.call(thisArg, 0);
await expect(
(response as N8nItemListOutputParser).parse('item1\nitem2\nitem3'),
).rejects.toThrow('Wrong number of items returned');
});
});
});