From d5fe75300a5449f5c4a82f1939b97c85bc2f5f34 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Wed, 20 Aug 2025 11:25:07 +0200 Subject: [PATCH] overhaul node design --- .../nodes/DataStore/DataStore.node.ts | 76 ++----------- .../nodes/DataStore/RowDescription.ts | 100 ------------------ .../nodes/DataStore/actions/router.ts | 52 +++++++++ .../DataStore/actions/row/Row.resource.ts | 59 +++++++++++ .../DataStore/actions/row/get.operation.ts | 23 ++++ .../DataStore/actions/row/insert.operation.ts | 55 ++++++++++ .../nodes/DataStore/common/fields.ts | 28 +++++ .../nodes/DataStore/common/utils.ts | 31 ++++++ 8 files changed, 257 insertions(+), 167 deletions(-) delete mode 100644 packages/nodes-base/nodes/DataStore/RowDescription.ts create mode 100644 packages/nodes-base/nodes/DataStore/actions/router.ts create mode 100644 packages/nodes-base/nodes/DataStore/actions/row/Row.resource.ts create mode 100644 packages/nodes-base/nodes/DataStore/actions/row/get.operation.ts create mode 100644 packages/nodes-base/nodes/DataStore/actions/row/insert.operation.ts create mode 100644 packages/nodes-base/nodes/DataStore/common/fields.ts create mode 100644 packages/nodes-base/nodes/DataStore/common/utils.ts diff --git a/packages/nodes-base/nodes/DataStore/DataStore.node.ts b/packages/nodes-base/nodes/DataStore/DataStore.node.ts index f1e5324b0ac..90f691df87f 100644 --- a/packages/nodes-base/nodes/DataStore/DataStore.node.ts +++ b/packages/nodes-base/nodes/DataStore/DataStore.node.ts @@ -1,18 +1,17 @@ import type { - IDataObject, IExecuteFunctions, ILoadOptionsFunctions, - INodeExecutionData, INodeListSearchResult, INodeType, INodeTypeDescription, - JsonObject, ResourceMapperField, ResourceMapperFields, } from 'n8n-workflow'; import { NodeConnectionTypes } from 'n8n-workflow'; -import { rowOperations, rowFields } from './RowDescription'; +import { router } from './actions/router'; +import * as row from './actions/row/Row.resource'; +import { DATA_STORE_ID_FIELD } from './common/fields'; // TODO: hide this node export class DataStore implements INodeType { @@ -45,8 +44,7 @@ export class DataStore implements INodeType { ], default: 'row', }, - ...rowOperations, - ...rowFields, + ...row.description, ], }; @@ -56,7 +54,7 @@ export class DataStore implements INodeType { if (this.helpers.getDataStoreAggregateProxy === undefined) return { results: [] }; const proxy = await this.helpers.getDataStoreAggregateProxy(); - const result = await proxy.getManyAndCount({ take: 10000 }); + const result = await proxy.getManyAndCount({ take: 1000000 }); const results = result.data.map((row) => { return { @@ -74,7 +72,9 @@ export class DataStore implements INodeType { async getColumns(this: ILoadOptionsFunctions): Promise { if (this.helpers.getDataStoreProxy === undefined) return { fields: [] }; - const dataStoreId = this.getNodeParameter('tableId', '', { extractValue: true }) as string; + const dataStoreId = this.getNodeParameter(DATA_STORE_ID_FIELD, '', { + extractValue: true, + }) as string; const proxy = await this.helpers.getDataStoreProxy(dataStoreId); const result = await proxy.getColumns(); @@ -101,64 +101,6 @@ export class DataStore implements INodeType { }; async execute(this: IExecuteFunctions) { - const items = this.getInputData(); - if (this.helpers.getDataStoreProxy === undefined) return [items]; - - const returnData: INodeExecutionData[] = []; - const length = items.length; - const resource = this.getNodeParameter('resource', 0, 'table'); - const operation = this.getNodeParameter('operation', 0, 'create'); - - for (let i = 0; i < length; i++) { - try { - if (resource === 'row') { - const tableId = this.getNodeParameter('tableId', i, '', { - extractValue: true, - }) as string; - const dataStoreProxy = await this.helpers.getDataStoreProxy(tableId); - if (operation === 'insert') { - const dataMode = this.getNodeParameter('columns.mappingMode', 0) as string; - - let data: IDataObject; - - if (dataMode === 'autoMapInputData') { - data = items[i].json; - } else { - const fields = this.getNodeParameter('columns.value', i, {}) as IDataObject; - - data = fields; - } - const response = await dataStoreProxy.insertRows([data as never]); - if (response) - returnData.push({ - json: data, - }); - } - if (operation === 'get') { - const response = await dataStoreProxy.getManyRowsAndCount({}); - - (response?.data ?? []).forEach((item) => { - returnData.push({ - json: item, - }); - }); - } - } - } catch (error) { - if (this.continueOnFail()) { - returnData.push({ - json: { - error: (error as JsonObject).message, - }, - pairedItem: { - item: i, - }, - }); - continue; - } - throw error; - } - } - return [returnData]; + return await router.call(this); } } diff --git a/packages/nodes-base/nodes/DataStore/RowDescription.ts b/packages/nodes-base/nodes/DataStore/RowDescription.ts deleted file mode 100644 index c9984e0825a..00000000000 --- a/packages/nodes-base/nodes/DataStore/RowDescription.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { INodeProperties } from 'n8n-workflow'; - -export const rowOperations: INodeProperties[] = [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - displayOptions: { - show: { - resource: ['row'], - }, - }, - options: [ - { - name: 'Create or Update', - value: 'upsert', - description: 'Create a new record, or update the current one if it already exists (upsert)', - action: 'Create or update a row', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a row', - action: 'Delete a row', - }, - { - name: 'Get', - value: 'get', - description: 'Get a row', - action: 'Get a row', - }, - { - name: 'Get Many', - value: 'getAll', - description: 'Get many rows', - action: 'Get many rows', - }, - { - name: 'Insert', - value: 'insert', - description: 'Insert a new row', - action: 'Insert a row', - }, - ], - default: 'upsert', - }, -]; - -export const rowFields: INodeProperties[] = [ - { - displayName: 'Table', - name: 'tableId', - type: 'resourceLocator', - default: { mode: 'list', value: '' }, - required: true, - modes: [ - { - displayName: 'From List', - name: 'list', - type: 'list', - typeOptions: { - searchListMethod: 'tableSearch', - searchable: true, - }, - }, - { - displayName: 'ID', - name: 'id', - type: 'string', - }, - ], - displayOptions: { show: { resource: ['row'] } }, - }, - { - displayName: 'Columns', - name: 'columns', - type: 'resourceMapper', - default: { - mappingMode: 'defineBelow', - value: null, - }, - noDataExpression: true, - required: true, - typeOptions: { - loadOptionsDependsOn: ['tableId.value'], - resourceMapper: { - resourceMapperMethod: 'getColumns', - mode: 'add', - fieldWords: { - singular: 'column', - plural: 'columns', - }, - addAllFields: true, - multiKeyMatch: true, - }, - }, - displayOptions: { show: { resource: ['row'], operation: ['upsert', 'insert'] } }, - }, -]; diff --git a/packages/nodes-base/nodes/DataStore/actions/router.ts b/packages/nodes-base/nodes/DataStore/actions/router.ts new file mode 100644 index 00000000000..68866b788c5 --- /dev/null +++ b/packages/nodes-base/nodes/DataStore/actions/router.ts @@ -0,0 +1,52 @@ +import type { IExecuteFunctions, INodeExecutionData, AllEntities } from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; + +import * as row from './row/Row.resource'; + +type DataStoreNodeType = AllEntities<{ row: 'insert' | 'get' }>; + +export async function router(this: IExecuteFunctions): Promise { + const operationResult: INodeExecutionData[] = []; + let responseData: INodeExecutionData[] = []; + + const items = this.getInputData(); + const resource = this.getNodeParameter('resource', 0); + const operation = this.getNodeParameter('operation', 0); + + const dataStoreNodeData = { + resource, + operation, + } as DataStoreNodeType; + + for (let i = 0; i < items.length; i++) { + try { + switch (dataStoreNodeData.resource) { + case 'row': + responseData = await row[dataStoreNodeData.operation].execute.call(this, i); + break; + default: + throw new NodeOperationError( + this.getNode(), + `The resource "${resource}" is not supported!`, + ); + } + + const executionData = this.helpers.constructExecutionMetaData(responseData, { + itemData: { item: i }, + }); + + operationResult.push.apply(operationResult, executionData); + } catch (error) { + if (this.continueOnFail()) { + operationResult.push({ + json: this.getInputData(i)[0].json, + error: error as NodeOperationError, + }); + } else { + throw error; + } + } + } + + return [operationResult]; +} diff --git a/packages/nodes-base/nodes/DataStore/actions/row/Row.resource.ts b/packages/nodes-base/nodes/DataStore/actions/row/Row.resource.ts new file mode 100644 index 00000000000..0a54d1ea45b --- /dev/null +++ b/packages/nodes-base/nodes/DataStore/actions/row/Row.resource.ts @@ -0,0 +1,59 @@ +import type { INodeProperties } from 'n8n-workflow'; + +import * as get from './get.operation'; +import * as insert from './insert.operation'; + +export { insert, get }; + +export const description: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['row'], + }, + }, + options: [ + // { + // name: 'Create or Update', + // value: 'upsert', + // description: 'Create a new record, or update the current one if it already exists (upsert)', + // action: 'Create or update a row', + // }, + // { + // name: 'Delete', + // value: 'delete', + // description: 'Delete a row', + // action: 'Delete a row', + // }, + { + name: 'Get', + value: get.FIELD, + description: 'Get a row', + action: 'Get a row', + }, + // { + // name: 'Get Many', + // value: 'getAll', + // description: 'Get many rows', + // action: 'Get many rows', + // }, + { + name: 'Insert', + value: insert.FIELD, + description: 'Insert a new row', + action: 'Insert a row', + }, + ], + default: 'insert', + }, + + ...insert.description, + ...get.description, + // ...getMany.description, + // ...load.description, + // ...upload.description, +]; diff --git a/packages/nodes-base/nodes/DataStore/actions/row/get.operation.ts b/packages/nodes-base/nodes/DataStore/actions/row/get.operation.ts new file mode 100644 index 00000000000..b8b59440194 --- /dev/null +++ b/packages/nodes-base/nodes/DataStore/actions/row/get.operation.ts @@ -0,0 +1,23 @@ +import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow'; + +import { getDataStoreProxy } from '../../common/utils'; + +// const displayOptions = { +// show: { +// resource: ['row'], +// operation: ['get'], +// }, +// }; + +export const FIELD: string = 'get'; + +export const description: INodeProperties[] = []; + +export async function execute( + this: IExecuteFunctions, + index: number, +): Promise { + const dataStoreProxy = await getDataStoreProxy(this, index); + const response = await dataStoreProxy.getManyRowsAndCount({}); + return (response?.data ?? []).map((json) => ({ json })); +} diff --git a/packages/nodes-base/nodes/DataStore/actions/row/insert.operation.ts b/packages/nodes-base/nodes/DataStore/actions/row/insert.operation.ts new file mode 100644 index 00000000000..50f0534ffc7 --- /dev/null +++ b/packages/nodes-base/nodes/DataStore/actions/row/insert.operation.ts @@ -0,0 +1,55 @@ +import { + OperationalError, + type IDisplayOptions, + type IDataObject, + type IExecuteFunctions, + type INodeExecutionData, + type INodeProperties, +} from 'n8n-workflow'; + +import { COLUMNS } from '../../common/fields'; +import { getDataStoreProxy } from '../../common/utils'; + +const displayOptions: IDisplayOptions = { + show: { + resource: ['row'], + operation: ['insert'], + }, +}; + +export const FIELD: string = 'insert'; + +export const description: INodeProperties[] = [ + { + ...COLUMNS, + displayOptions, + }, +]; + +export async function execute( + this: IExecuteFunctions, + index: number, +): Promise { + const items = this.getInputData(); + + const dataStoreProxy = await getDataStoreProxy(this, index); + const dataMode = this.getNodeParameter('columns.mappingMode', index) as string; + + let data: IDataObject; + + if (dataMode === 'autoMapInputData') { + data = items[index].json; + } else { + const fields = this.getNodeParameter('columns.value', index, {}) as IDataObject; + + data = fields; + } + // Todo: insertRows should return the inserted rows including the id + const success = await dataStoreProxy.insertRows([data as never]); + + if (!success) { + throw new OperationalError('Failed to insert record into data store'); + } + + return [{ json: data }]; +} diff --git a/packages/nodes-base/nodes/DataStore/common/fields.ts b/packages/nodes-base/nodes/DataStore/common/fields.ts new file mode 100644 index 00000000000..50a841ad083 --- /dev/null +++ b/packages/nodes-base/nodes/DataStore/common/fields.ts @@ -0,0 +1,28 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const DATA_STORE_ID_FIELD = 'dataStoreId'; + +export const COLUMNS: INodeProperties = { + displayName: 'Columns', + name: 'columns', + type: 'resourceMapper', + default: { + mappingMode: 'defineBelow', + value: null, + }, + noDataExpression: true, + required: true, + typeOptions: { + loadOptionsDependsOn: [`${DATA_STORE_ID_FIELD}.value`], + resourceMapper: { + resourceMapperMethod: 'getColumns', + mode: 'add', + fieldWords: { + singular: 'column', + plural: 'columns', + }, + addAllFields: true, + multiKeyMatch: true, + }, + }, +}; diff --git a/packages/nodes-base/nodes/DataStore/common/utils.ts b/packages/nodes-base/nodes/DataStore/common/utils.ts new file mode 100644 index 00000000000..ae48294ae44 --- /dev/null +++ b/packages/nodes-base/nodes/DataStore/common/utils.ts @@ -0,0 +1,31 @@ +import { + type IDataStoreProjectAggregateService, + type IDataStoreProjectService, + UserError, + type IExecuteFunctions, +} from 'n8n-workflow'; + +import { DATA_STORE_ID_FIELD } from './fields'; + +export async function getDataStoreProxy( + ef: IExecuteFunctions, + i: number, +): Promise { + if (ef.helpers.getDataStoreProxy === undefined) + throw new UserError('Attempted to use Data Store node but the module is disabled'); + + const dataStoreId = ef.getNodeParameter(DATA_STORE_ID_FIELD, i, '', { + extractValue: true, + }) as string; + + return await ef.helpers.getDataStoreProxy(dataStoreId); +} + +export async function getDataStoreAggregateProxy( + this: IExecuteFunctions, +): Promise { + if (this.helpers.getDataStoreAggregateProxy === undefined) + throw new UserError('Attempted to use Data Store node but the module is disabled'); + + return await this.helpers.getDataStoreAggregateProxy(); +}