feat(ai-builder): Providing instructions from workflow builder on creation of data tables (#24595)

This commit is contained in:
Michael Drury
2026-01-22 14:23:11 +00:00
committed by GitHub
parent 8fc88bca5a
commit 6f2c1efc2f
12 changed files with 668 additions and 7 deletions
@@ -9,6 +9,7 @@ import {
buildRecursionErrorWithWorkflowGuidance,
buildRecursionErrorNoWorkflowGuidance,
buildGeneralErrorGuidance,
buildDataTableCreationGuidance,
} from '@/prompts/agents/responder.prompt';
import type { CoordinationLogEntry } from '../types/coordination';
@@ -21,6 +22,7 @@ import {
getConfiguratorOutput,
hasRecursionErrorsCleared,
} from '../utils/coordination-log';
import { extractDataTableInfo } from '../utils/data-table-helpers';
const systemPrompt = ChatPromptTemplate.fromMessages([
[
@@ -141,6 +143,14 @@ export class ResponderAgent {
contextParts.push(`**Configuration:**\n${configuratorOutput}`);
}
// Data Table creation guidance
// If the workflow contains Data Table nodes, inform user they need to create tables manually
const dataTableInfo = extractDataTableInfo(context.workflowJSON);
if (dataTableInfo.length > 0) {
const dataTableGuidance = buildDataTableCreationGuidance(dataTableInfo);
contextParts.push(dataTableGuidance);
}
if (contextParts.length === 0) {
return null;
}
@@ -5,9 +5,13 @@
* Does NOT configure node parameters - that's the Configurator Agent's job.
*/
import { DATA_TABLE_ROW_COLUMN_MAPPING_OPERATIONS } from '@/utils/data-table-helpers';
import { prompt } from '../builder';
import { structuredOutputParser } from '../shared/node-guidance';
const dataTableColumnOperationsList = DATA_TABLE_ROW_COLUMN_MAPPING_OPERATIONS.join(', ');
const BUILDER_ROLE = 'You are a Builder Agent specialized in constructing n8n workflows.';
const EXECUTION_SEQUENCE = `You MUST follow these steps IN ORDER. Do not skip any step.
@@ -342,6 +346,28 @@ Common mistake to avoid:
- NEVER connect Document Loader to main data outputs
- Document Loader is an AI sub-node that gives Vector Store document processing capability`;
const DATA_TABLE_PATTERN = `DATA TABLE NODE PATTERN:
**Row Column Operations (${dataTableColumnOperationsList}) - REQUIRE Set Node:**
When using Data Table nodes for row column operations, you MUST add a Set node immediately before the Data Table node.
Structure: Set Node → Data Table Node (operation: ${dataTableColumnOperationsList})
Why: The Set node defines the columns/fields to write. This tells users exactly which columns to create in their Data Table.
Example for storing data:
- add_nodes(nodeType: "n8n-nodes-base.set", name: "Prepare User Data")
- add_nodes(nodeType: "n8n-nodes-base.dataTable", name: "Store Users", initialParameters: {{ operation: "insert" }})
- connect_nodes(source: "Prepare User Data", target: "Store Users")
**Row Read Operations (get, getAll, delete) - NO Set Node needed:**
Read and delete operations don't write data, so they don't need a Set node before them.
Example for reading data:
- add_nodes(nodeType: "n8n-nodes-base.dataTable", name: "Get Users", initialParameters: {{ operation: "get" }})
IMPORTANT: For ${dataTableColumnOperationsList} operations, NEVER connect a Data Table node directly to other nodes without a Set node in between.`;
const SWITCH_NODE_PATTERN = `For Switch nodes with multiple routing paths:
- The number of outputs is determined by the number of entries in rules.values[]
- You MUST create the rules.values[] array with placeholder entries for each output branch
@@ -504,6 +530,7 @@ export function buildBuilderPrompt(): string {
.section('multi_trigger_workflows', MULTI_TRIGGER_WORKFLOWS)
.section('shared_memory_pattern', SHARED_MEMORY_PATTERN)
.section('rag_workflow_pattern', RAG_PATTERN)
.section('data_table_pattern', DATA_TABLE_PATTERN)
.section('switch_node_pattern', SWITCH_NODE_PATTERN)
.section('node_connection_examples', NODE_CONNECTION_EXAMPLES)
.section('connection_type_examples', CONNECTION_TYPES)
@@ -5,8 +5,12 @@
* Uses natural language instructions to configure each node's settings.
*/
import { DATA_TABLE_ROW_COLUMN_MAPPING_OPERATIONS } from '@/utils/data-table-helpers';
import { prompt } from '../builder';
const dataTableColumnOperationsList = DATA_TABLE_ROW_COLUMN_MAPPING_OPERATIONS.join(', ');
const CONFIGURATOR_ROLE =
'You are a Configurator Agent specialized in setting up n8n node parameters.';
@@ -108,6 +112,23 @@ const CRITICAL_PARAMETERS = `- HTTP Request: URL, method, headers (if auth neede
- AI nodes: Prompts, models, configurations
- Tool nodes: Use $fromAI for dynamic recipient/subject/message fields`;
const DATA_TABLE_CONFIGURATION = `DATA TABLE NODE CONFIGURATION:
When configuring Data Table nodes (n8n-nodes-base.dataTable):
**For Row Column Operations (${dataTableColumnOperationsList}):**
- There MUST be a Set node (n8n-nodes-base.set) immediately before the Data Table node
- Configure the Set node with all the fields the user wants to store
- Use a PLACEHOLDER for dataTableId (e.g., "<__PLACEHOLDER_VALUE__data_table_name__>")
- Use columns.mappingMode: "autoMapInputData" (this maps columns from the preceding Set node)
- Example: "Set dataTableId to placeholder <__PLACEHOLDER_VALUE__my_table__>, set columns mapping mode to autoMapInputData"
**For Row Read Operations (get, getAll, delete):**
- No Set node is required before the Data Table node
- Still use a PLACEHOLDER for dataTableId
- Configure any filter or query parameters as needed
WHY: Data Tables must be created manually by the user. Using a placeholder ensures users know to create and select their table. For column operations, the Set node defines what columns to create.`;
const DEFAULT_VALUES_GUIDE = `PRINCIPLE: User requests ALWAYS take precedence. When user specifies a model, parameter, or value - use exactly what they requested.
SAFE DEFAULTS - Trust these unless user specifies otherwise:
@@ -225,6 +246,7 @@ export function buildConfiguratorPrompt(): string {
.section('expression_techniques', EXPRESSION_TECHNIQUES)
.section('tool_node_expressions', TOOL_NODE_EXPRESSIONS)
.section('critical_parameters', CRITICAL_PARAMETERS)
.section('data_table_configuration', DATA_TABLE_CONFIGURATION)
.section('default_values_guide', DEFAULT_VALUES_GUIDE)
.section('switch_node_configuration', SWITCH_NODE_CONFIGURATION)
.section('node_configuration_examples', NODE_CONFIGURATION_EXAMPLES)
@@ -0,0 +1,27 @@
import type { DataTableInfo } from '@/utils/data-table-helpers';
import { buildDataTableCreationGuidance } from './responder.prompt';
describe('buildDataTableCreationGuidance', () => {
it('should include column definitions from Set node for row column operations', () => {
const dataTables: DataTableInfo[] = [
{
nodeName: 'Store Users',
tableName: 'users',
columns: [
{ name: 'email', type: 'text' },
{ name: 'age', type: 'number' },
],
setNodeName: 'Prepare User Data',
operation: 'insert',
},
];
const guidance = buildDataTableCreationGuidance(dataTables);
expect(guidance).toContain('Data Table Setup Required');
expect(guidance).toContain('`email` (text)');
expect(guidance).toContain('`age` (number)');
expect(guidance).toContain('Prepare User Data');
});
});
@@ -5,6 +5,8 @@
* Also handles conversational queries and explanations.
*/
import { type DataTableInfo, isDataTableRowColumnOperation } from '@/utils/data-table-helpers';
import { prompt } from '../builder';
const RESPONDER_ROLE = `You are a helpful AI assistant for n8n workflow automation.
@@ -18,8 +20,9 @@ const WORKFLOW_COMPLETION = `When you receive [Internal Context], synthesize a c
1. Summarize what was built in a friendly way
2. Explain the workflow structure briefly
3. Include setup instructions if provided
4. Ask if user wants adjustments
5. Do not tell user to activate/publish their workflow, because they will do this themselves when they are ready.
4. If Data Table setup is required, include the exact steps provided in the context (do NOT say data tables will be created automatically)
5. Ask if user wants adjustments
6. Do not tell user to activate/publish their workflow, because they will do this themselves when they are ready.
Example response structure:
"I've created your [workflow type] workflow! Here's what it does:
@@ -28,6 +31,8 @@ Example response structure:
**Setup Required:**
[List any configuration steps from the context]
[If data tables are used, include Data Table creation steps with link to Data Tables tab]
Let me know if you'd like to adjust anything."`;
const CONVERSATIONAL_RESPONSES = `- Be friendly and concise
@@ -78,6 +83,54 @@ export function buildGeneralErrorGuidance(): string {
);
}
/**
* Build guidance for data table creation.
* Data tables must be created manually - the AI workflow builder cannot create them automatically.
*/
export function buildDataTableCreationGuidance(dataTables: DataTableInfo[]): string {
if (dataTables.length === 0) {
return '';
}
const tableGuidance = dataTables.map((table) => {
const isColumnOperation = isDataTableRowColumnOperation(table.operation);
const columnInfo = buildColumnInfo(table, isColumnOperation);
return `- **${table.nodeName}** (${table.operation}): ${columnInfo}`;
});
return prompt({ format: 'markdown' })
.section(
'Data Table Setup Required',
`Data tables must be created manually before the workflow can run.
Do NOT tell the user that data tables will be created automatically.
Go to the [Data Tables tab](/home/datatables) to create the required tables:
${tableGuidance.join('\n')}
After creating each table, select it in the corresponding Data Table node.`,
)
.build();
}
function buildColumnInfo(table: DataTableInfo, isColumnOperation: boolean): string {
if (!isColumnOperation) {
return `Ensure the table has columns for reading/querying (uses "${table.operation}" operation)`;
}
if (table.columns.length > 0) {
const columnList = table.columns.map((c) => `\`${c.name}\` (${c.type})`).join(', ');
const source = table.setNodeName ? ` (from "${table.setNodeName}" node)` : '';
return `Add columns: ${columnList}${source}`;
}
if (table.setNodeName) {
return `Add columns matching the fields in the "${table.setNodeName}" node`;
}
return 'Add columns based on the data you want to store';
}
export function buildResponderPrompt(): string {
return prompt()
.section('role', RESPONDER_ROLE)
@@ -0,0 +1,174 @@
/**
* Data Table Helpers
*
* Utility functions for extracting and processing Data Table information
* from workflow JSON for use in the responder agent.
*/
import {
getParentNodes,
mapConnectionsByDestination,
type DataTableRowOperation,
} from 'n8n-workflow';
import type { SimpleWorkflow } from '../types';
export const DATA_TABLE_NODE_TYPE = 'n8n-nodes-base.dataTable';
export const SET_NODE_TYPE = 'n8n-nodes-base.set';
/** Row operations that require column definitions (from a preceding Set node) */
export const DATA_TABLE_ROW_COLUMN_MAPPING_OPERATIONS: readonly DataTableRowOperation[] = [
'insert',
'update',
'upsert',
];
export type DataTableRowColumnOperation = (typeof DATA_TABLE_ROW_COLUMN_MAPPING_OPERATIONS)[number];
/** Type guard to check if an operation requires column definitions */
export function isDataTableRowColumnOperation(
operation: string,
): operation is DataTableRowColumnOperation {
return (DATA_TABLE_ROW_COLUMN_MAPPING_OPERATIONS as readonly string[]).includes(operation);
}
/**
* Column definition with name and type
*/
export interface ColumnDefinition {
name: string;
type: string;
}
/**
* Information about a Data Table node in the workflow
*/
export interface DataTableInfo {
/** The node name in the workflow */
nodeName: string;
/** The table name/ID (may be a placeholder) */
tableName?: string;
/** Column definitions inferred from the preceding Set node */
columns: ColumnDefinition[];
/** The name of the Set node that defines the columns (if found) */
setNodeName?: string;
/** The operation (insert, update, upsert, get, delete) */
operation: DataTableRowOperation;
}
/**
* Map Set node field types to Data Table column types
*/
function mapSetNodeTypeToDataTableType(setNodeType: string): string {
switch (setNodeType) {
case 'number':
return 'number';
case 'boolean':
return 'boolean';
case 'string':
default:
return 'text';
}
}
interface SetNodeAssignment {
name: string;
type: string;
}
interface SetNodeAssignments {
assignments?: SetNodeAssignment[];
}
function isSetNodeAssignments(value: unknown): value is SetNodeAssignments {
if (typeof value !== 'object' || value === null) return false;
const obj = value as Record<string, unknown>;
if (!('assignments' in obj)) return true; // assignments is optional
if (!Array.isArray(obj.assignments)) return false;
return obj.assignments.every(
(item) =>
typeof item === 'object' &&
item !== null &&
'name' in item &&
'type' in item &&
typeof (item as Record<string, unknown>).name === 'string' &&
typeof (item as Record<string, unknown>).type === 'string',
);
}
/**
* Extract field definitions from a Set node's assignments
*/
export function extractSetNodeFields(
workflow: SimpleWorkflow,
nodeName: string,
): ColumnDefinition[] {
const node = workflow.nodes.find((n) => n.name === nodeName && n.type === SET_NODE_TYPE);
if (!node) return [];
const params = node.parameters ?? {};
const assignments = params.assignments;
if (!isSetNodeAssignments(assignments) || !assignments.assignments) return [];
return assignments.assignments
.filter((a) => a.name && a.type)
.map((a) => ({
name: a.name,
type: mapSetNodeTypeToDataTableType(a.type),
}));
}
/**
* Extract data table information from workflow nodes.
* Used to inform users about data tables they need to create manually.
*
* For row write operations (insert, update, upsert), the configurator agent
* is instructed to place a Set node before Data Table nodes. This function
* infers column definitions from the preceding Set node.
*
* For read operations (get, getAll, delete), no Set node is expected.
*/
export function extractDataTableInfo(workflow: SimpleWorkflow): DataTableInfo[] {
const dataTableNodes = workflow.nodes.filter((node) => node.type === DATA_TABLE_NODE_TYPE);
const connectionsByDestination = mapConnectionsByDestination(workflow.connections);
return dataTableNodes.map((node) => {
const params = node.parameters ?? {};
let tableName = undefined;
const dataTableId = params.dataTableId as { value?: string } | undefined;
if (dataTableId?.value) {
tableName = dataTableId.value;
}
// Get the operation type
const operation: DataTableRowOperation =
(params.operation as DataTableRowOperation) ?? 'insert';
// Only look for Set node columns on row write operations
let columns: ColumnDefinition[] = [];
let setNodeName: string | undefined = undefined;
// Look for a set node before the data table operation - this should contain the columns
if (isDataTableRowColumnOperation(operation)) {
// Get direct predecessors (depth=1) using getParentNodes from n8n-workflow
const predecessors = getParentNodes(connectionsByDestination, node.name, 'main', 1);
for (const predecessorName of predecessors) {
const setFields = extractSetNodeFields(workflow, predecessorName);
if (setFields.length > 0) {
columns = setFields;
setNodeName = predecessorName;
break;
}
}
}
return {
nodeName: node.name,
tableName,
columns,
setNodeName,
operation,
};
});
}
@@ -0,0 +1,157 @@
import { NodeConnectionTypes } from 'n8n-workflow';
import type { SimpleWorkflow } from '../../types';
import {
DATA_TABLE_NODE_TYPE,
SET_NODE_TYPE,
extractSetNodeFields,
extractDataTableInfo,
} from '../data-table-helpers';
describe('data-table-helpers', () => {
const createEmptyWorkflow = (): SimpleWorkflow => ({
name: 'Test Workflow',
nodes: [],
connections: {},
});
describe('extractSetNodeFields', () => {
it('should return empty array when node not found', () => {
const workflow = createEmptyWorkflow();
expect(extractSetNodeFields(workflow, 'NonExistent')).toEqual([]);
});
it('should return empty array when Set node has no assignments', () => {
const workflow: SimpleWorkflow = {
name: 'Test',
nodes: [
{
id: '1',
name: 'Set',
type: SET_NODE_TYPE,
typeVersion: 1,
position: [0, 0],
parameters: {},
},
],
connections: {},
};
expect(extractSetNodeFields(workflow, 'Set')).toEqual([]);
});
it('should extract fields from Set node assignments', () => {
const workflow: SimpleWorkflow = {
name: 'Test',
nodes: [
{
id: '1',
name: 'Set',
type: SET_NODE_TYPE,
typeVersion: 1,
position: [0, 0],
parameters: {
assignments: {
assignments: [
{ name: 'email', type: 'string' },
{ name: 'age', type: 'number' },
{ name: 'active', type: 'boolean' },
],
},
},
},
],
connections: {},
};
const fields = extractSetNodeFields(workflow, 'Set');
expect(fields).toEqual([
{ name: 'email', type: 'text' },
{ name: 'age', type: 'number' },
{ name: 'active', type: 'boolean' },
]);
});
});
describe('extractDataTableInfo', () => {
it('should return empty array for workflow with no Data Table nodes', () => {
const workflow = createEmptyWorkflow();
expect(extractDataTableInfo(workflow)).toEqual([]);
});
it('should extract basic Data Table info', () => {
const workflow: SimpleWorkflow = {
name: 'Test',
nodes: [
{
id: '1',
name: 'Data Table',
type: DATA_TABLE_NODE_TYPE,
typeVersion: 1,
position: [0, 0],
parameters: {
dataTableId: { __rl: true, mode: 'id', value: 'my_table' },
operation: 'insert',
},
},
],
connections: {},
};
const info = extractDataTableInfo(workflow);
expect(info).toHaveLength(1);
expect(info[0]).toEqual({
nodeName: 'Data Table',
tableName: 'my_table',
columns: [],
setNodeName: undefined,
operation: 'insert',
});
});
it('should infer columns from predecessor Set node', () => {
const workflow: SimpleWorkflow = {
name: 'Test',
nodes: [
{
id: '1',
name: 'Prepare Data',
type: SET_NODE_TYPE,
typeVersion: 1,
position: [0, 0],
parameters: {
assignments: {
assignments: [
{ name: 'userId', type: 'number' },
{ name: 'status', type: 'string' },
],
},
},
},
{
id: '2',
name: 'Data Table',
type: DATA_TABLE_NODE_TYPE,
typeVersion: 1,
position: [200, 0],
parameters: {
dataTableId: { value: 'users' },
operation: 'insert',
},
},
],
connections: {
'Prepare Data': {
main: [[{ node: 'Data Table', type: NodeConnectionTypes.Main, index: 0 }]],
},
},
};
const info = extractDataTableInfo(workflow);
expect(info[0].columns).toEqual([
{ name: 'userId', type: 'number' },
{ name: 'status', type: 'text' },
]);
expect(info[0].setNodeName).toBe('Prepare Data');
});
});
});
@@ -0,0 +1,109 @@
import type { INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import type { SimpleWorkflow } from '@/types';
import { DATA_TABLE_NODE_TYPE, SET_NODE_TYPE } from '@/utils/data-table-helpers';
import { validateConnections } from './connections';
describe('validateConnections', () => {
const createMockNodeType = (type: string): INodeTypeDescription =>
({
name: type,
displayName: type,
group: ['transform'],
version: 1,
description: 'Mock node',
defaults: { name: type },
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
properties: [],
}) as unknown as INodeTypeDescription;
const mockNodeTypes: INodeTypeDescription[] = [
createMockNodeType(DATA_TABLE_NODE_TYPE),
createMockNodeType(SET_NODE_TYPE),
createMockNodeType('n8n-nodes-base.httpRequest'),
];
describe('data-table-missing-set-node validation', () => {
it('should return violation when Data Table row column operation has no Set node predecessor', () => {
const workflow: SimpleWorkflow = {
name: 'Test',
nodes: [
{
id: '1',
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 1,
position: [0, 0],
parameters: {},
},
{
id: '2',
name: 'Store Data',
type: DATA_TABLE_NODE_TYPE,
typeVersion: 1,
position: [200, 0],
parameters: {
operation: 'insert',
},
},
],
connections: {
'HTTP Request': {
main: [[{ node: 'Store Data', type: NodeConnectionTypes.Main, index: 0 }]],
},
},
};
const violations = validateConnections(workflow, mockNodeTypes);
expect(violations).toContainEqual(
expect.objectContaining({
name: 'data-table-missing-set-node',
type: 'major',
}),
);
});
it('should NOT return violation when Data Table has Set node predecessor', () => {
const workflow: SimpleWorkflow = {
name: 'Test',
nodes: [
{
id: '1',
name: 'Prepare Data',
type: SET_NODE_TYPE,
typeVersion: 1,
position: [0, 0],
parameters: {},
},
{
id: '2',
name: 'Store Data',
type: DATA_TABLE_NODE_TYPE,
typeVersion: 1,
position: [200, 0],
parameters: {
operation: 'insert',
},
},
],
connections: {
'Prepare Data': {
main: [[{ node: 'Store Data', type: NodeConnectionTypes.Main, index: 0 }]],
},
},
};
const violations = validateConnections(workflow, mockNodeTypes);
expect(violations).not.toContainEqual(
expect.objectContaining({
name: 'data-table-missing-set-node',
}),
);
});
});
});
@@ -1,7 +1,17 @@
import type { INodeConnections, INodeTypeDescription, NodeConnectionType } from 'n8n-workflow';
import { mapConnectionsByDestination } from 'n8n-workflow';
import type {
IConnections,
INodeConnections,
INodeTypeDescription,
NodeConnectionType,
} from 'n8n-workflow';
import { getParentNodes, mapConnectionsByDestination, NodeConnectionTypes } from 'n8n-workflow';
import type { SimpleWorkflow } from '@/types';
import {
DATA_TABLE_NODE_TYPE,
isDataTableRowColumnOperation,
SET_NODE_TYPE,
} from '@/utils/data-table-helpers';
import { isSubNode } from '@/utils/node-helpers';
import { createNodeTypeMaps, getNodeTypeForNode } from '@/validation/utils/node-type-map';
import { resolveNodeInputs, resolveNodeOutputs } from '@/validation/utils/resolve-connections';
@@ -138,6 +148,48 @@ function checkMergeNodeConnections(
return issues;
}
function checkDataTableHasSetNodePredecessor(
connectionsByDestination: IConnections,
node: SimpleWorkflow['nodes'][number],
nodesByName: Map<string, SimpleWorkflow['nodes'][number]>,
): ProgrammaticViolation[] {
if (node.type !== DATA_TABLE_NODE_TYPE) {
return [];
}
// Only check for Set node on row column operations (insert, update, upsert)
// Read operations (get, getAll) and delete don't need a Set node
const operationParam = node.parameters?.operation;
const operation = typeof operationParam === 'string' ? operationParam : 'insert';
if (!isDataTableRowColumnOperation(operation)) {
return [];
}
// Check if any direct predecessor is a Set node
const predecessors = getParentNodes(
connectionsByDestination,
node.name,
NodeConnectionTypes.Main,
1,
);
const hasSetNodePredecessor = predecessors.some(
(name) => nodesByName.get(name)?.type === SET_NODE_TYPE,
);
if (hasSetNodePredecessor) {
return [];
}
return [
{
name: 'data-table-missing-set-node',
type: 'major',
description: `Data Table node "${node.name}" uses "${operation}" operation and should have a Set node (Edit Fields) immediately before it to define the columns. Add a Set node and connect it to the Data Table.`,
pointsDeducted: 20,
},
];
}
function checkSubNodeRootConnections(
workflow: SimpleWorkflow,
nodeInfo: NodeResolvedConnectionTypesInfo,
@@ -242,6 +294,10 @@ export function validateConnections(
violations.push(...checkMergeNodeConnections(nodeInfo, nodeConnections));
violations.push(...checkSubNodeRootConnections(workflow, nodeInfo, nodesByName));
violations.push(
...checkDataTableHasSetNodePredecessor(connectionsByDestination, node, nodesByName),
);
}
return violations;
@@ -31,6 +31,7 @@ export const PROGRAMMATIC_VIOLATION_NAMES = [
'workflow-similarity-evaluation-failed',
'http-request-hardcoded-credentials',
'set-node-credential-field',
'data-table-missing-set-node',
] as const;
export type ProgrammaticViolationName = (typeof PROGRAMMATIC_VIOLATION_NAMES)[number];
@@ -1,4 +1,10 @@
import type { IExecuteFunctions, INodeExecutionData, AllEntities } from 'n8n-workflow';
import type {
IExecuteFunctions,
INodeExecutionData,
AllEntities,
DataTableRowOperation,
DataTableTableOperation,
} from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import * as row from './row/Row.resource';
@@ -7,8 +13,8 @@ import { DATA_TABLE_ID_FIELD } from '../common/fields';
import { getDataTableProxyExecute } from '../common/utils';
type DataTableNodeType = AllEntities<{
row: 'insert' | 'get' | 'rowExists' | 'rowNotExists' | 'deleteRows' | 'update' | 'upsert';
table: 'create' | 'delete' | 'list' | 'update';
row: DataTableRowOperation;
table: DataTableTableOperation;
}>;
const BULK_OPERATIONS = ['insert'] as const;
+19
View File
@@ -1,5 +1,24 @@
export type DataTableColumnType = 'string' | 'number' | 'boolean' | 'date';
/**
* Data Table row operations
* Used by the Data Table node (n8n-nodes-base.dataTable) for row-level CRUD operations
*/
export type DataTableRowOperation =
| 'insert'
| 'get'
| 'rowExists'
| 'rowNotExists'
| 'deleteRows'
| 'update'
| 'upsert';
/**
* Data Table table operations
* Used by the Data Table node for table-level management operations
*/
export type DataTableTableOperation = 'create' | 'delete' | 'list' | 'update';
export type DataTableColumn = {
id: string;
name: string;