n8n-mcp 2.7.10 → 2.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -47
- package/data/nodes.db +0 -0
- package/dist/database/database-adapter.d.ts +1 -0
- package/dist/database/database-adapter.d.ts.map +1 -1
- package/dist/database/database-adapter.js +20 -0
- package/dist/database/database-adapter.js.map +1 -1
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +334 -10
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools-documentation.d.ts.map +1 -1
- package/dist/mcp/tools-documentation.js +832 -52
- package/dist/mcp/tools-documentation.js.map +1 -1
- package/dist/mcp/tools-n8n-manager.d.ts.map +1 -1
- package/dist/mcp/tools-n8n-manager.js +10 -112
- package/dist/mcp/tools-n8n-manager.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +42 -36
- package/dist/mcp/tools.js.map +1 -1
- package/dist/scripts/fetch-templates.d.ts.map +1 -1
- package/dist/scripts/fetch-templates.js +37 -0
- package/dist/scripts/fetch-templates.js.map +1 -1
- package/dist/scripts/test-error-handling-validation.d.ts +3 -0
- package/dist/scripts/test-error-handling-validation.d.ts.map +1 -0
- package/dist/scripts/test-error-handling-validation.js +340 -0
- package/dist/scripts/test-error-handling-validation.js.map +1 -0
- package/dist/scripts/test-node-level-properties.d.ts +3 -0
- package/dist/scripts/test-node-level-properties.d.ts.map +1 -0
- package/dist/scripts/test-node-level-properties.js +196 -0
- package/dist/scripts/test-node-level-properties.js.map +1 -0
- package/dist/services/config-validator.d.ts +2 -2
- package/dist/services/config-validator.d.ts.map +1 -1
- package/dist/services/config-validator.js +123 -5
- package/dist/services/config-validator.js.map +1 -1
- package/dist/services/enhanced-config-validator.d.ts +2 -0
- package/dist/services/enhanced-config-validator.d.ts.map +1 -1
- package/dist/services/enhanced-config-validator.js +31 -1
- package/dist/services/enhanced-config-validator.js.map +1 -1
- package/dist/services/example-generator.d.ts.map +1 -1
- package/dist/services/example-generator.js +442 -28
- package/dist/services/example-generator.js.map +1 -1
- package/dist/services/n8n-validation.d.ts +10 -10
- package/dist/services/node-specific-validators.d.ts +8 -1
- package/dist/services/node-specific-validators.d.ts.map +1 -1
- package/dist/services/node-specific-validators.js +608 -59
- package/dist/services/node-specific-validators.js.map +1 -1
- package/dist/services/task-templates.d.ts +1 -0
- package/dist/services/task-templates.d.ts.map +1 -1
- package/dist/services/task-templates.js +858 -16
- package/dist/services/task-templates.js.map +1 -1
- package/dist/services/workflow-diff-engine.d.ts.map +1 -1
- package/dist/services/workflow-diff-engine.js +1 -0
- package/dist/services/workflow-diff-engine.js.map +1 -1
- package/dist/services/workflow-validator.d.ts +9 -0
- package/dist/services/workflow-validator.d.ts.map +1 -1
- package/dist/services/workflow-validator.js +270 -0
- package/dist/services/workflow-validator.js.map +1 -1
- package/dist/sse-server.d.ts +8 -0
- package/dist/sse-server.d.ts.map +1 -0
- package/dist/sse-server.js +652 -0
- package/dist/sse-server.js.map +1 -0
- package/dist/templates/template-repository.d.ts +4 -0
- package/dist/templates/template-repository.d.ts.map +1 -1
- package/dist/templates/template-repository.js +119 -7
- package/dist/templates/template-repository.js.map +1 -1
- package/dist/templates/template-service.d.ts.map +1 -1
- package/dist/templates/template-service.js +2 -0
- package/dist/templates/template-service.js.map +1 -1
- package/dist/types/n8n-api.d.ts +2 -1
- package/dist/types/n8n-api.d.ts.map +1 -1
- package/dist/types/n8n-api.js.map +1 -1
- package/dist/types/sse.d.ts +42 -0
- package/dist/types/sse.d.ts.map +1 -0
- package/dist/types/sse.js +3 -0
- package/dist/types/sse.js.map +1 -0
- package/dist/utils/sse-session-manager.d.ts +23 -0
- package/dist/utils/sse-session-manager.d.ts.map +1 -0
- package/dist/utils/sse-session-manager.js +178 -0
- package/dist/utils/sse-session-manager.js.map +1 -0
- package/package.json +1 -1
|
@@ -13,6 +13,9 @@ class TaskTemplates {
|
|
|
13
13
|
static getTaskTemplate(task) {
|
|
14
14
|
return this.templates[task];
|
|
15
15
|
}
|
|
16
|
+
static getTemplate(task) {
|
|
17
|
+
return this.getTaskTemplate(task);
|
|
18
|
+
}
|
|
16
19
|
static searchTasks(keyword) {
|
|
17
20
|
const lower = keyword.toLowerCase();
|
|
18
21
|
return Object.entries(this.templates)
|
|
@@ -23,13 +26,14 @@ class TaskTemplates {
|
|
|
23
26
|
}
|
|
24
27
|
static getTaskCategories() {
|
|
25
28
|
return {
|
|
26
|
-
'HTTP/API': ['get_api_data', 'post_json_request', 'call_api_with_auth'],
|
|
27
|
-
'Webhooks': ['receive_webhook', 'webhook_with_response'],
|
|
28
|
-
'Database': ['query_postgres', 'insert_postgres_data'],
|
|
29
|
-
'AI/LangChain': ['chat_with_ai', 'ai_agent_workflow', 'multi_tool_ai_agent'],
|
|
30
|
-
'Data Processing': ['transform_data', 'filter_data'],
|
|
29
|
+
'HTTP/API': ['get_api_data', 'post_json_request', 'call_api_with_auth', 'api_call_with_retry'],
|
|
30
|
+
'Webhooks': ['receive_webhook', 'webhook_with_response', 'webhook_with_error_handling', 'process_webhook_data'],
|
|
31
|
+
'Database': ['query_postgres', 'insert_postgres_data', 'database_transaction_safety'],
|
|
32
|
+
'AI/LangChain': ['chat_with_ai', 'ai_agent_workflow', 'multi_tool_ai_agent', 'ai_rate_limit_handling'],
|
|
33
|
+
'Data Processing': ['transform_data', 'filter_data', 'fault_tolerant_processing', 'process_webhook_data'],
|
|
31
34
|
'Communication': ['send_slack_message', 'send_email'],
|
|
32
|
-
'AI Tool Usage': ['use_google_sheets_as_tool', 'use_slack_as_tool', 'multi_tool_ai_agent']
|
|
35
|
+
'AI Tool Usage': ['use_google_sheets_as_tool', 'use_slack_as_tool', 'multi_tool_ai_agent'],
|
|
36
|
+
'Error Handling': ['modern_error_handling_patterns', 'api_call_with_retry', 'fault_tolerant_processing', 'webhook_with_error_handling', 'database_transaction_safety', 'ai_rate_limit_handling']
|
|
33
37
|
};
|
|
34
38
|
}
|
|
35
39
|
}
|
|
@@ -42,7 +46,12 @@ TaskTemplates.templates = {
|
|
|
42
46
|
configuration: {
|
|
43
47
|
method: 'GET',
|
|
44
48
|
url: '',
|
|
45
|
-
authentication: 'none'
|
|
49
|
+
authentication: 'none',
|
|
50
|
+
onError: 'continueRegularOutput',
|
|
51
|
+
retryOnFail: true,
|
|
52
|
+
maxTries: 3,
|
|
53
|
+
waitBetweenTries: 1000,
|
|
54
|
+
alwaysOutputData: true
|
|
46
55
|
},
|
|
47
56
|
userMustProvide: [
|
|
48
57
|
{
|
|
@@ -61,6 +70,11 @@ TaskTemplates.templates = {
|
|
|
61
70
|
property: 'sendHeaders',
|
|
62
71
|
description: 'Add custom headers if needed',
|
|
63
72
|
when: 'API requires specific headers'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
property: 'alwaysOutputData',
|
|
76
|
+
description: 'Set to true to capture error responses',
|
|
77
|
+
when: 'Need to debug API errors'
|
|
64
78
|
}
|
|
65
79
|
]
|
|
66
80
|
},
|
|
@@ -74,7 +88,12 @@ TaskTemplates.templates = {
|
|
|
74
88
|
sendBody: true,
|
|
75
89
|
contentType: 'json',
|
|
76
90
|
specifyBody: 'json',
|
|
77
|
-
jsonBody: ''
|
|
91
|
+
jsonBody: '',
|
|
92
|
+
onError: 'continueRegularOutput',
|
|
93
|
+
retryOnFail: true,
|
|
94
|
+
maxTries: 2,
|
|
95
|
+
waitBetweenTries: 1000,
|
|
96
|
+
alwaysOutputData: true
|
|
78
97
|
},
|
|
79
98
|
userMustProvide: [
|
|
80
99
|
{
|
|
@@ -92,11 +111,17 @@ TaskTemplates.templates = {
|
|
|
92
111
|
{
|
|
93
112
|
property: 'authentication',
|
|
94
113
|
description: 'Add authentication if required'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
property: 'onError',
|
|
117
|
+
description: 'Set to "continueRegularOutput" for non-critical operations',
|
|
118
|
+
when: 'Failure should not stop the workflow'
|
|
95
119
|
}
|
|
96
120
|
],
|
|
97
121
|
notes: [
|
|
98
122
|
'Make sure jsonBody contains valid JSON',
|
|
99
|
-
'Content-Type header is automatically set to application/json'
|
|
123
|
+
'Content-Type header is automatically set to application/json',
|
|
124
|
+
'Be careful with retries on non-idempotent operations'
|
|
100
125
|
]
|
|
101
126
|
},
|
|
102
127
|
'call_api_with_auth': {
|
|
@@ -109,6 +134,11 @@ TaskTemplates.templates = {
|
|
|
109
134
|
authentication: 'genericCredentialType',
|
|
110
135
|
genericAuthType: 'headerAuth',
|
|
111
136
|
sendHeaders: true,
|
|
137
|
+
onError: 'continueErrorOutput',
|
|
138
|
+
retryOnFail: true,
|
|
139
|
+
maxTries: 3,
|
|
140
|
+
waitBetweenTries: 2000,
|
|
141
|
+
alwaysOutputData: true,
|
|
112
142
|
headerParameters: {
|
|
113
143
|
parameters: [
|
|
114
144
|
{
|
|
@@ -149,7 +179,9 @@ TaskTemplates.templates = {
|
|
|
149
179
|
httpMethod: 'POST',
|
|
150
180
|
path: 'webhook',
|
|
151
181
|
responseMode: 'lastNode',
|
|
152
|
-
responseData: 'allEntries'
|
|
182
|
+
responseData: 'allEntries',
|
|
183
|
+
onError: 'continueRegularOutput',
|
|
184
|
+
alwaysOutputData: true
|
|
153
185
|
},
|
|
154
186
|
userMustProvide: [
|
|
155
187
|
{
|
|
@@ -182,7 +214,9 @@ TaskTemplates.templates = {
|
|
|
182
214
|
path: 'webhook',
|
|
183
215
|
responseMode: 'responseNode',
|
|
184
216
|
responseData: 'firstEntryJson',
|
|
185
|
-
responseCode: 200
|
|
217
|
+
responseCode: 200,
|
|
218
|
+
onError: 'continueRegularOutput',
|
|
219
|
+
alwaysOutputData: true
|
|
186
220
|
},
|
|
187
221
|
userMustProvide: [
|
|
188
222
|
{
|
|
@@ -195,13 +229,104 @@ TaskTemplates.templates = {
|
|
|
195
229
|
'responseMode: responseNode requires a Respond to Webhook node'
|
|
196
230
|
]
|
|
197
231
|
},
|
|
232
|
+
'process_webhook_data': {
|
|
233
|
+
task: 'process_webhook_data',
|
|
234
|
+
description: 'Process incoming webhook data with Code node (shows correct data access)',
|
|
235
|
+
nodeType: 'nodes-base.code',
|
|
236
|
+
configuration: {
|
|
237
|
+
language: 'javaScript',
|
|
238
|
+
jsCode: `// ⚠️ CRITICAL: Webhook data is nested under 'body' property!
|
|
239
|
+
// Connect this Code node after a Webhook node
|
|
240
|
+
|
|
241
|
+
// Access webhook payload data - it's under .body, not directly under .json
|
|
242
|
+
const webhookData = items[0].json.body; // ✅ CORRECT
|
|
243
|
+
const headers = items[0].json.headers; // HTTP headers
|
|
244
|
+
const query = items[0].json.query; // Query parameters
|
|
245
|
+
|
|
246
|
+
// Common mistake to avoid:
|
|
247
|
+
// const command = items[0].json.testCommand; // ❌ WRONG - will be undefined!
|
|
248
|
+
// const command = items[0].json.body.testCommand; // ✅ CORRECT
|
|
249
|
+
|
|
250
|
+
// Process the webhook data
|
|
251
|
+
try {
|
|
252
|
+
// Validate required fields
|
|
253
|
+
if (!webhookData.command) {
|
|
254
|
+
throw new Error('Missing required field: command');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Process based on command
|
|
258
|
+
let result = {};
|
|
259
|
+
switch (webhookData.command) {
|
|
260
|
+
case 'process':
|
|
261
|
+
result = {
|
|
262
|
+
status: 'processed',
|
|
263
|
+
data: webhookData.data,
|
|
264
|
+
processedAt: DateTime.now().toISO()
|
|
265
|
+
};
|
|
266
|
+
break;
|
|
267
|
+
|
|
268
|
+
case 'validate':
|
|
269
|
+
result = {
|
|
270
|
+
status: 'validated',
|
|
271
|
+
isValid: true,
|
|
272
|
+
validatedFields: Object.keys(webhookData.data || {})
|
|
273
|
+
};
|
|
274
|
+
break;
|
|
275
|
+
|
|
276
|
+
default:
|
|
277
|
+
result = {
|
|
278
|
+
status: 'unknown_command',
|
|
279
|
+
command: webhookData.command
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Return processed data
|
|
284
|
+
return [{
|
|
285
|
+
json: {
|
|
286
|
+
...result,
|
|
287
|
+
requestId: headers['x-request-id'] || crypto.randomUUID(),
|
|
288
|
+
source: query.source || 'webhook',
|
|
289
|
+
originalCommand: webhookData.command,
|
|
290
|
+
metadata: {
|
|
291
|
+
httpMethod: items[0].json.httpMethod,
|
|
292
|
+
webhookPath: items[0].json.webhookPath,
|
|
293
|
+
timestamp: DateTime.now().toISO()
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}];
|
|
297
|
+
|
|
298
|
+
} catch (error) {
|
|
299
|
+
// Return error response
|
|
300
|
+
return [{
|
|
301
|
+
json: {
|
|
302
|
+
status: 'error',
|
|
303
|
+
error: error.message,
|
|
304
|
+
timestamp: DateTime.now().toISO()
|
|
305
|
+
}
|
|
306
|
+
}];
|
|
307
|
+
}`,
|
|
308
|
+
onError: 'continueRegularOutput'
|
|
309
|
+
},
|
|
310
|
+
userMustProvide: [],
|
|
311
|
+
notes: [
|
|
312
|
+
'⚠️ WEBHOOK DATA IS AT items[0].json.body, NOT items[0].json',
|
|
313
|
+
'This is the most common webhook processing mistake',
|
|
314
|
+
'Headers are at items[0].json.headers',
|
|
315
|
+
'Query parameters are at items[0].json.query',
|
|
316
|
+
'Connect this Code node directly after a Webhook node'
|
|
317
|
+
]
|
|
318
|
+
},
|
|
198
319
|
'query_postgres': {
|
|
199
320
|
task: 'query_postgres',
|
|
200
321
|
description: 'Query data from PostgreSQL database',
|
|
201
322
|
nodeType: 'nodes-base.postgres',
|
|
202
323
|
configuration: {
|
|
203
324
|
operation: 'executeQuery',
|
|
204
|
-
query: ''
|
|
325
|
+
query: '',
|
|
326
|
+
onError: 'continueRegularOutput',
|
|
327
|
+
retryOnFail: true,
|
|
328
|
+
maxTries: 3,
|
|
329
|
+
waitBetweenTries: 1000
|
|
205
330
|
},
|
|
206
331
|
userMustProvide: [
|
|
207
332
|
{
|
|
@@ -230,7 +355,11 @@ TaskTemplates.templates = {
|
|
|
230
355
|
operation: 'insert',
|
|
231
356
|
table: '',
|
|
232
357
|
columns: '',
|
|
233
|
-
returnFields: '*'
|
|
358
|
+
returnFields: '*',
|
|
359
|
+
onError: 'stopWorkflow',
|
|
360
|
+
retryOnFail: true,
|
|
361
|
+
maxTries: 2,
|
|
362
|
+
waitBetweenTries: 1000
|
|
234
363
|
},
|
|
235
364
|
userMustProvide: [
|
|
236
365
|
{
|
|
@@ -264,7 +393,12 @@ TaskTemplates.templates = {
|
|
|
264
393
|
content: ''
|
|
265
394
|
}
|
|
266
395
|
]
|
|
267
|
-
}
|
|
396
|
+
},
|
|
397
|
+
onError: 'continueRegularOutput',
|
|
398
|
+
retryOnFail: true,
|
|
399
|
+
maxTries: 3,
|
|
400
|
+
waitBetweenTries: 5000,
|
|
401
|
+
alwaysOutputData: true
|
|
268
402
|
},
|
|
269
403
|
userMustProvide: [
|
|
270
404
|
{
|
|
@@ -386,7 +520,11 @@ return results;`
|
|
|
386
520
|
resource: 'message',
|
|
387
521
|
operation: 'post',
|
|
388
522
|
channel: '',
|
|
389
|
-
text: ''
|
|
523
|
+
text: '',
|
|
524
|
+
onError: 'continueRegularOutput',
|
|
525
|
+
retryOnFail: true,
|
|
526
|
+
maxTries: 2,
|
|
527
|
+
waitBetweenTries: 2000
|
|
390
528
|
},
|
|
391
529
|
userMustProvide: [
|
|
392
530
|
{
|
|
@@ -419,7 +557,12 @@ return results;`
|
|
|
419
557
|
fromEmail: '',
|
|
420
558
|
toEmail: '',
|
|
421
559
|
subject: '',
|
|
422
|
-
text: ''
|
|
560
|
+
text: '',
|
|
561
|
+
onError: 'continueRegularOutput',
|
|
562
|
+
retryOnFail: true,
|
|
563
|
+
maxTries: 3,
|
|
564
|
+
waitBetweenTries: 3000,
|
|
565
|
+
alwaysOutputData: true
|
|
423
566
|
},
|
|
424
567
|
userMustProvide: [
|
|
425
568
|
{
|
|
@@ -550,6 +693,705 @@ return results;`
|
|
|
550
693
|
'Test each tool individually before combining',
|
|
551
694
|
'Set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true for community nodes'
|
|
552
695
|
]
|
|
696
|
+
},
|
|
697
|
+
'api_call_with_retry': {
|
|
698
|
+
task: 'api_call_with_retry',
|
|
699
|
+
description: 'Resilient API call with automatic retry on failure',
|
|
700
|
+
nodeType: 'nodes-base.httpRequest',
|
|
701
|
+
configuration: {
|
|
702
|
+
method: 'GET',
|
|
703
|
+
url: '',
|
|
704
|
+
retryOnFail: true,
|
|
705
|
+
maxTries: 5,
|
|
706
|
+
waitBetweenTries: 2000,
|
|
707
|
+
alwaysOutputData: true,
|
|
708
|
+
sendHeaders: true,
|
|
709
|
+
headerParameters: {
|
|
710
|
+
parameters: [
|
|
711
|
+
{
|
|
712
|
+
name: 'X-Request-ID',
|
|
713
|
+
value: '={{ $workflow.id }}-{{ $itemIndex }}'
|
|
714
|
+
}
|
|
715
|
+
]
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
userMustProvide: [
|
|
719
|
+
{
|
|
720
|
+
property: 'url',
|
|
721
|
+
description: 'The API endpoint to call',
|
|
722
|
+
example: 'https://api.example.com/resource/{{ $json.id }}'
|
|
723
|
+
}
|
|
724
|
+
],
|
|
725
|
+
optionalEnhancements: [
|
|
726
|
+
{
|
|
727
|
+
property: 'authentication',
|
|
728
|
+
description: 'Add API authentication'
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
property: 'onError',
|
|
732
|
+
description: 'Change to "stopWorkflow" for critical API calls',
|
|
733
|
+
when: 'This is a critical API call that must succeed'
|
|
734
|
+
}
|
|
735
|
+
],
|
|
736
|
+
notes: [
|
|
737
|
+
'Retries help with rate limits and transient network issues',
|
|
738
|
+
'waitBetweenTries prevents hammering the API',
|
|
739
|
+
'alwaysOutputData captures error responses for debugging',
|
|
740
|
+
'Consider exponential backoff for production use'
|
|
741
|
+
]
|
|
742
|
+
},
|
|
743
|
+
'fault_tolerant_processing': {
|
|
744
|
+
task: 'fault_tolerant_processing',
|
|
745
|
+
description: 'Data processing that continues despite individual item failures',
|
|
746
|
+
nodeType: 'nodes-base.code',
|
|
747
|
+
configuration: {
|
|
748
|
+
language: 'javaScript',
|
|
749
|
+
jsCode: `// Process items with error handling
|
|
750
|
+
const results = [];
|
|
751
|
+
|
|
752
|
+
for (const item of items) {
|
|
753
|
+
try {
|
|
754
|
+
// Your processing logic here
|
|
755
|
+
const processed = {
|
|
756
|
+
...item.json,
|
|
757
|
+
processed: true,
|
|
758
|
+
timestamp: new Date().toISOString()
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
results.push({ json: processed });
|
|
762
|
+
} catch (error) {
|
|
763
|
+
// Log error but continue processing
|
|
764
|
+
console.error('Processing failed for item:', item.json.id, error);
|
|
765
|
+
|
|
766
|
+
// Add error item to results
|
|
767
|
+
results.push({
|
|
768
|
+
json: {
|
|
769
|
+
...item.json,
|
|
770
|
+
error: error.message,
|
|
771
|
+
processed: false
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return results;`,
|
|
778
|
+
onError: 'continueRegularOutput',
|
|
779
|
+
alwaysOutputData: true
|
|
780
|
+
},
|
|
781
|
+
userMustProvide: [
|
|
782
|
+
{
|
|
783
|
+
property: 'Processing logic',
|
|
784
|
+
description: 'Replace the comment with your data transformation logic'
|
|
785
|
+
}
|
|
786
|
+
],
|
|
787
|
+
optionalEnhancements: [
|
|
788
|
+
{
|
|
789
|
+
property: 'Error notification',
|
|
790
|
+
description: 'Add IF node after to handle error items separately'
|
|
791
|
+
}
|
|
792
|
+
],
|
|
793
|
+
notes: [
|
|
794
|
+
'Individual item failures won\'t stop processing of other items',
|
|
795
|
+
'Error items are marked and can be handled separately',
|
|
796
|
+
'continueOnFail ensures workflow continues even on total failure'
|
|
797
|
+
]
|
|
798
|
+
},
|
|
799
|
+
'webhook_with_error_handling': {
|
|
800
|
+
task: 'webhook_with_error_handling',
|
|
801
|
+
description: 'Webhook that gracefully handles processing errors',
|
|
802
|
+
nodeType: 'nodes-base.webhook',
|
|
803
|
+
configuration: {
|
|
804
|
+
httpMethod: 'POST',
|
|
805
|
+
path: 'resilient-webhook',
|
|
806
|
+
responseMode: 'responseNode',
|
|
807
|
+
responseData: 'firstEntryJson',
|
|
808
|
+
onError: 'continueRegularOutput',
|
|
809
|
+
alwaysOutputData: true
|
|
810
|
+
},
|
|
811
|
+
userMustProvide: [
|
|
812
|
+
{
|
|
813
|
+
property: 'path',
|
|
814
|
+
description: 'Unique webhook path',
|
|
815
|
+
example: 'order-processor'
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
property: 'Respond to Webhook node',
|
|
819
|
+
description: 'Add node to send appropriate success/error responses'
|
|
820
|
+
}
|
|
821
|
+
],
|
|
822
|
+
optionalEnhancements: [
|
|
823
|
+
{
|
|
824
|
+
property: 'Validation',
|
|
825
|
+
description: 'Add IF node to validate webhook payload'
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
property: 'Error logging',
|
|
829
|
+
description: 'Add error handler node for failed requests'
|
|
830
|
+
}
|
|
831
|
+
],
|
|
832
|
+
notes: [
|
|
833
|
+
'onError: continueRegularOutput ensures webhook always sends a response',
|
|
834
|
+
'Use Respond to Webhook node to send appropriate status codes',
|
|
835
|
+
'Log errors but don\'t expose internal errors to webhook callers',
|
|
836
|
+
'Consider rate limiting for public webhooks'
|
|
837
|
+
]
|
|
838
|
+
},
|
|
839
|
+
'modern_error_handling_patterns': {
|
|
840
|
+
task: 'modern_error_handling_patterns',
|
|
841
|
+
description: 'Examples of modern error handling using onError property',
|
|
842
|
+
nodeType: 'nodes-base.httpRequest',
|
|
843
|
+
configuration: {
|
|
844
|
+
method: 'GET',
|
|
845
|
+
url: '',
|
|
846
|
+
onError: 'continueRegularOutput',
|
|
847
|
+
retryOnFail: true,
|
|
848
|
+
maxTries: 3,
|
|
849
|
+
waitBetweenTries: 2000,
|
|
850
|
+
alwaysOutputData: true
|
|
851
|
+
},
|
|
852
|
+
userMustProvide: [
|
|
853
|
+
{
|
|
854
|
+
property: 'url',
|
|
855
|
+
description: 'The API endpoint'
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
property: 'onError',
|
|
859
|
+
description: 'Choose error handling strategy',
|
|
860
|
+
example: 'continueRegularOutput'
|
|
861
|
+
}
|
|
862
|
+
],
|
|
863
|
+
notes: [
|
|
864
|
+
'onError replaces the deprecated continueOnFail property',
|
|
865
|
+
'continueRegularOutput: Continue with normal output on error',
|
|
866
|
+
'continueErrorOutput: Route errors to error output for special handling',
|
|
867
|
+
'stopWorkflow: Stop the entire workflow on error',
|
|
868
|
+
'Combine with retryOnFail for resilient workflows'
|
|
869
|
+
]
|
|
870
|
+
},
|
|
871
|
+
'database_transaction_safety': {
|
|
872
|
+
task: 'database_transaction_safety',
|
|
873
|
+
description: 'Database operations with proper error handling',
|
|
874
|
+
nodeType: 'nodes-base.postgres',
|
|
875
|
+
configuration: {
|
|
876
|
+
operation: 'executeQuery',
|
|
877
|
+
query: 'BEGIN; INSERT INTO orders ...; COMMIT;',
|
|
878
|
+
onError: 'continueErrorOutput',
|
|
879
|
+
retryOnFail: false,
|
|
880
|
+
alwaysOutputData: true
|
|
881
|
+
},
|
|
882
|
+
userMustProvide: [
|
|
883
|
+
{
|
|
884
|
+
property: 'query',
|
|
885
|
+
description: 'Your SQL query or transaction'
|
|
886
|
+
}
|
|
887
|
+
],
|
|
888
|
+
notes: [
|
|
889
|
+
'Transactions should not be retried automatically',
|
|
890
|
+
'Use continueErrorOutput to handle errors separately',
|
|
891
|
+
'Consider implementing compensating transactions',
|
|
892
|
+
'Always log transaction failures for audit'
|
|
893
|
+
]
|
|
894
|
+
},
|
|
895
|
+
'ai_rate_limit_handling': {
|
|
896
|
+
task: 'ai_rate_limit_handling',
|
|
897
|
+
description: 'AI API calls with rate limit handling',
|
|
898
|
+
nodeType: 'nodes-base.openAi',
|
|
899
|
+
configuration: {
|
|
900
|
+
resource: 'chat',
|
|
901
|
+
operation: 'message',
|
|
902
|
+
modelId: 'gpt-4',
|
|
903
|
+
messages: {
|
|
904
|
+
values: [
|
|
905
|
+
{
|
|
906
|
+
role: 'user',
|
|
907
|
+
content: ''
|
|
908
|
+
}
|
|
909
|
+
]
|
|
910
|
+
},
|
|
911
|
+
onError: 'continueRegularOutput',
|
|
912
|
+
retryOnFail: true,
|
|
913
|
+
maxTries: 5,
|
|
914
|
+
waitBetweenTries: 5000,
|
|
915
|
+
alwaysOutputData: true
|
|
916
|
+
},
|
|
917
|
+
userMustProvide: [
|
|
918
|
+
{
|
|
919
|
+
property: 'messages.values[0].content',
|
|
920
|
+
description: 'The prompt for the AI'
|
|
921
|
+
}
|
|
922
|
+
],
|
|
923
|
+
notes: [
|
|
924
|
+
'AI APIs often have rate limits',
|
|
925
|
+
'Longer wait times help avoid hitting limits',
|
|
926
|
+
'Consider implementing exponential backoff in Code node',
|
|
927
|
+
'Monitor usage to stay within quotas'
|
|
928
|
+
]
|
|
929
|
+
},
|
|
930
|
+
'custom_ai_tool': {
|
|
931
|
+
task: 'custom_ai_tool',
|
|
932
|
+
description: 'Create a custom tool for AI agents using Code node',
|
|
933
|
+
nodeType: 'nodes-base.code',
|
|
934
|
+
configuration: {
|
|
935
|
+
language: 'javaScript',
|
|
936
|
+
mode: 'runOnceForEachItem',
|
|
937
|
+
jsCode: `// Custom AI Tool - Example: Text Analysis
|
|
938
|
+
// This code will be called by AI agents with $json containing the input
|
|
939
|
+
|
|
940
|
+
// Access the input from the AI agent
|
|
941
|
+
const text = $json.text || '';
|
|
942
|
+
const operation = $json.operation || 'analyze';
|
|
943
|
+
|
|
944
|
+
// Perform the requested operation
|
|
945
|
+
let result = {};
|
|
946
|
+
|
|
947
|
+
switch (operation) {
|
|
948
|
+
case 'wordCount':
|
|
949
|
+
result = {
|
|
950
|
+
wordCount: text.split(/\\s+/).filter(word => word.length > 0).length,
|
|
951
|
+
characterCount: text.length,
|
|
952
|
+
lineCount: text.split('\\n').length
|
|
953
|
+
};
|
|
954
|
+
break;
|
|
955
|
+
|
|
956
|
+
case 'extract':
|
|
957
|
+
// Extract specific patterns (emails, URLs, etc.)
|
|
958
|
+
result = {
|
|
959
|
+
emails: text.match(/[\\w.-]+@[\\w.-]+\\.\\w+/g) || [],
|
|
960
|
+
urls: text.match(/https?:\\/\\/[^\\s]+/g) || [],
|
|
961
|
+
numbers: text.match(/\\b\\d+\\b/g) || []
|
|
962
|
+
};
|
|
963
|
+
break;
|
|
964
|
+
|
|
965
|
+
default:
|
|
966
|
+
result = {
|
|
967
|
+
error: 'Unknown operation',
|
|
968
|
+
availableOperations: ['wordCount', 'extract']
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return [{
|
|
973
|
+
json: {
|
|
974
|
+
...result,
|
|
975
|
+
originalText: text,
|
|
976
|
+
operation: operation,
|
|
977
|
+
processedAt: DateTime.now().toISO()
|
|
978
|
+
}
|
|
979
|
+
}];`,
|
|
980
|
+
onError: 'continueRegularOutput'
|
|
981
|
+
},
|
|
982
|
+
userMustProvide: [],
|
|
983
|
+
notes: [
|
|
984
|
+
'Connect this to AI Agent node\'s tool input',
|
|
985
|
+
'AI will pass data in $json',
|
|
986
|
+
'Use "Run Once for Each Item" mode for AI tools',
|
|
987
|
+
'Return structured data the AI can understand'
|
|
988
|
+
]
|
|
989
|
+
},
|
|
990
|
+
'aggregate_data': {
|
|
991
|
+
task: 'aggregate_data',
|
|
992
|
+
description: 'Aggregate data from multiple items into summary statistics',
|
|
993
|
+
nodeType: 'nodes-base.code',
|
|
994
|
+
configuration: {
|
|
995
|
+
language: 'javaScript',
|
|
996
|
+
jsCode: `// Aggregate data from all items
|
|
997
|
+
const stats = {
|
|
998
|
+
count: 0,
|
|
999
|
+
sum: 0,
|
|
1000
|
+
min: Infinity,
|
|
1001
|
+
max: -Infinity,
|
|
1002
|
+
values: [],
|
|
1003
|
+
categories: {},
|
|
1004
|
+
errors: []
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
// Process each item
|
|
1008
|
+
for (const item of items) {
|
|
1009
|
+
try {
|
|
1010
|
+
const value = item.json.value || item.json.amount || 0;
|
|
1011
|
+
const category = item.json.category || 'uncategorized';
|
|
1012
|
+
|
|
1013
|
+
stats.count++;
|
|
1014
|
+
stats.sum += value;
|
|
1015
|
+
stats.min = Math.min(stats.min, value);
|
|
1016
|
+
stats.max = Math.max(stats.max, value);
|
|
1017
|
+
stats.values.push(value);
|
|
1018
|
+
|
|
1019
|
+
// Count by category
|
|
1020
|
+
stats.categories[category] = (stats.categories[category] || 0) + 1;
|
|
1021
|
+
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
stats.errors.push({
|
|
1024
|
+
item: item.json,
|
|
1025
|
+
error: error.message
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Calculate additional statistics
|
|
1031
|
+
const average = stats.count > 0 ? stats.sum / stats.count : 0;
|
|
1032
|
+
const sorted = [...stats.values].sort((a, b) => a - b);
|
|
1033
|
+
const median = sorted.length > 0
|
|
1034
|
+
? sorted[Math.floor(sorted.length / 2)]
|
|
1035
|
+
: 0;
|
|
1036
|
+
|
|
1037
|
+
return [{
|
|
1038
|
+
json: {
|
|
1039
|
+
totalItems: stats.count,
|
|
1040
|
+
sum: stats.sum,
|
|
1041
|
+
average: average,
|
|
1042
|
+
median: median,
|
|
1043
|
+
min: stats.min === Infinity ? 0 : stats.min,
|
|
1044
|
+
max: stats.max === -Infinity ? 0 : stats.max,
|
|
1045
|
+
categoryCounts: stats.categories,
|
|
1046
|
+
errorCount: stats.errors.length,
|
|
1047
|
+
errors: stats.errors,
|
|
1048
|
+
processedAt: DateTime.now().toISO()
|
|
1049
|
+
}
|
|
1050
|
+
}];`,
|
|
1051
|
+
onError: 'continueRegularOutput'
|
|
1052
|
+
},
|
|
1053
|
+
userMustProvide: [],
|
|
1054
|
+
notes: [
|
|
1055
|
+
'Assumes items have "value" or "amount" field',
|
|
1056
|
+
'Groups by "category" field if present',
|
|
1057
|
+
'Returns single item with all statistics',
|
|
1058
|
+
'Handles errors gracefully'
|
|
1059
|
+
]
|
|
1060
|
+
},
|
|
1061
|
+
'batch_process_with_api': {
|
|
1062
|
+
task: 'batch_process_with_api',
|
|
1063
|
+
description: 'Process items in batches with API calls',
|
|
1064
|
+
nodeType: 'nodes-base.code',
|
|
1065
|
+
configuration: {
|
|
1066
|
+
language: 'javaScript',
|
|
1067
|
+
jsCode: `// Batch process items with API calls
|
|
1068
|
+
const BATCH_SIZE = 10;
|
|
1069
|
+
const API_URL = 'https://api.example.com/batch-process'; // USER MUST UPDATE
|
|
1070
|
+
const results = [];
|
|
1071
|
+
|
|
1072
|
+
// Process items in batches
|
|
1073
|
+
for (let i = 0; i < items.length; i += BATCH_SIZE) {
|
|
1074
|
+
const batch = items.slice(i, i + BATCH_SIZE);
|
|
1075
|
+
|
|
1076
|
+
try {
|
|
1077
|
+
// Prepare batch data
|
|
1078
|
+
const batchData = batch.map(item => ({
|
|
1079
|
+
id: item.json.id,
|
|
1080
|
+
data: item.json
|
|
1081
|
+
}));
|
|
1082
|
+
|
|
1083
|
+
// Make API request for batch
|
|
1084
|
+
const response = await $helpers.httpRequest({
|
|
1085
|
+
method: 'POST',
|
|
1086
|
+
url: API_URL,
|
|
1087
|
+
body: {
|
|
1088
|
+
items: batchData
|
|
1089
|
+
},
|
|
1090
|
+
headers: {
|
|
1091
|
+
'Content-Type': 'application/json'
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
// Add results
|
|
1096
|
+
if (response.results && Array.isArray(response.results)) {
|
|
1097
|
+
response.results.forEach((result, index) => {
|
|
1098
|
+
results.push({
|
|
1099
|
+
json: {
|
|
1100
|
+
...batch[index].json,
|
|
1101
|
+
...result,
|
|
1102
|
+
batchNumber: Math.floor(i / BATCH_SIZE) + 1,
|
|
1103
|
+
processedAt: DateTime.now().toISO()
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Add delay between batches to avoid rate limits
|
|
1110
|
+
if (i + BATCH_SIZE < items.length) {
|
|
1111
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
// Add failed batch items with error
|
|
1116
|
+
batch.forEach(item => {
|
|
1117
|
+
results.push({
|
|
1118
|
+
json: {
|
|
1119
|
+
...item.json,
|
|
1120
|
+
error: error.message,
|
|
1121
|
+
status: 'failed',
|
|
1122
|
+
batchNumber: Math.floor(i / BATCH_SIZE) + 1
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
return results;`,
|
|
1130
|
+
onError: 'continueRegularOutput',
|
|
1131
|
+
retryOnFail: true,
|
|
1132
|
+
maxTries: 2
|
|
1133
|
+
},
|
|
1134
|
+
userMustProvide: [
|
|
1135
|
+
{
|
|
1136
|
+
property: 'jsCode',
|
|
1137
|
+
description: 'Update API_URL in the code',
|
|
1138
|
+
example: 'https://your-api.com/batch'
|
|
1139
|
+
}
|
|
1140
|
+
],
|
|
1141
|
+
notes: [
|
|
1142
|
+
'Processes items in batches of 10',
|
|
1143
|
+
'Includes delay between batches',
|
|
1144
|
+
'Handles batch failures gracefully',
|
|
1145
|
+
'Update API_URL and adjust BATCH_SIZE as needed'
|
|
1146
|
+
]
|
|
1147
|
+
},
|
|
1148
|
+
'error_safe_transform': {
|
|
1149
|
+
task: 'error_safe_transform',
|
|
1150
|
+
description: 'Transform data with comprehensive error handling',
|
|
1151
|
+
nodeType: 'nodes-base.code',
|
|
1152
|
+
configuration: {
|
|
1153
|
+
language: 'javaScript',
|
|
1154
|
+
jsCode: `// Safe data transformation with validation
|
|
1155
|
+
const results = [];
|
|
1156
|
+
const errors = [];
|
|
1157
|
+
|
|
1158
|
+
for (const item of items) {
|
|
1159
|
+
try {
|
|
1160
|
+
// Validate required fields
|
|
1161
|
+
const required = ['id', 'name']; // USER SHOULD UPDATE
|
|
1162
|
+
const missing = required.filter(field => !item.json[field]);
|
|
1163
|
+
|
|
1164
|
+
if (missing.length > 0) {
|
|
1165
|
+
throw new Error(\`Missing required fields: \${missing.join(', ')}\`);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Transform data with type checking
|
|
1169
|
+
const transformed = {
|
|
1170
|
+
// Ensure ID is string
|
|
1171
|
+
id: String(item.json.id),
|
|
1172
|
+
|
|
1173
|
+
// Clean and validate name
|
|
1174
|
+
name: String(item.json.name).trim(),
|
|
1175
|
+
|
|
1176
|
+
// Parse numbers safely
|
|
1177
|
+
amount: parseFloat(item.json.amount) || 0,
|
|
1178
|
+
|
|
1179
|
+
// Parse dates safely
|
|
1180
|
+
date: item.json.date
|
|
1181
|
+
? DateTime.fromISO(item.json.date).isValid
|
|
1182
|
+
? DateTime.fromISO(item.json.date).toISO()
|
|
1183
|
+
: null
|
|
1184
|
+
: null,
|
|
1185
|
+
|
|
1186
|
+
// Boolean conversion
|
|
1187
|
+
isActive: Boolean(item.json.active || item.json.isActive),
|
|
1188
|
+
|
|
1189
|
+
// Array handling
|
|
1190
|
+
tags: Array.isArray(item.json.tags)
|
|
1191
|
+
? item.json.tags.filter(tag => typeof tag === 'string')
|
|
1192
|
+
: [],
|
|
1193
|
+
|
|
1194
|
+
// Nested object handling
|
|
1195
|
+
metadata: typeof item.json.metadata === 'object'
|
|
1196
|
+
? item.json.metadata
|
|
1197
|
+
: {},
|
|
1198
|
+
|
|
1199
|
+
// Add processing info
|
|
1200
|
+
processedAt: DateTime.now().toISO(),
|
|
1201
|
+
originalIndex: items.indexOf(item)
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
results.push({
|
|
1205
|
+
json: transformed
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
errors.push({
|
|
1210
|
+
json: {
|
|
1211
|
+
error: error.message,
|
|
1212
|
+
originalData: item.json,
|
|
1213
|
+
index: items.indexOf(item),
|
|
1214
|
+
status: 'failed'
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Add summary at the end
|
|
1221
|
+
results.push({
|
|
1222
|
+
json: {
|
|
1223
|
+
_summary: {
|
|
1224
|
+
totalProcessed: results.length - errors.length,
|
|
1225
|
+
totalErrors: errors.length,
|
|
1226
|
+
successRate: ((results.length - errors.length) / items.length * 100).toFixed(2) + '%',
|
|
1227
|
+
timestamp: DateTime.now().toISO()
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
// Include errors at the end
|
|
1233
|
+
return [...results, ...errors];`,
|
|
1234
|
+
onError: 'continueRegularOutput'
|
|
1235
|
+
},
|
|
1236
|
+
userMustProvide: [
|
|
1237
|
+
{
|
|
1238
|
+
property: 'jsCode',
|
|
1239
|
+
description: 'Update required fields array',
|
|
1240
|
+
example: "const required = ['id', 'email', 'name'];"
|
|
1241
|
+
}
|
|
1242
|
+
],
|
|
1243
|
+
notes: [
|
|
1244
|
+
'Validates all data types',
|
|
1245
|
+
'Handles missing/invalid data gracefully',
|
|
1246
|
+
'Returns both successful and failed items',
|
|
1247
|
+
'Includes processing summary'
|
|
1248
|
+
]
|
|
1249
|
+
},
|
|
1250
|
+
'async_data_processing': {
|
|
1251
|
+
task: 'async_data_processing',
|
|
1252
|
+
description: 'Process data with async operations and proper error handling',
|
|
1253
|
+
nodeType: 'nodes-base.code',
|
|
1254
|
+
configuration: {
|
|
1255
|
+
language: 'javaScript',
|
|
1256
|
+
jsCode: `// Async processing with concurrent limits
|
|
1257
|
+
const CONCURRENT_LIMIT = 5;
|
|
1258
|
+
const results = [];
|
|
1259
|
+
|
|
1260
|
+
// Process items with concurrency control
|
|
1261
|
+
async function processItem(item, index) {
|
|
1262
|
+
try {
|
|
1263
|
+
// Simulate async operation (replace with actual logic)
|
|
1264
|
+
// Example: API call, database query, file operation
|
|
1265
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1266
|
+
|
|
1267
|
+
// Actual processing logic here
|
|
1268
|
+
const processed = {
|
|
1269
|
+
...item.json,
|
|
1270
|
+
processed: true,
|
|
1271
|
+
index: index,
|
|
1272
|
+
timestamp: DateTime.now().toISO()
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
// Example async operation - external API call
|
|
1276
|
+
if (item.json.needsEnrichment) {
|
|
1277
|
+
const enrichment = await $helpers.httpRequest({
|
|
1278
|
+
method: 'GET',
|
|
1279
|
+
url: \`https://api.example.com/enrich/\${item.json.id}\`
|
|
1280
|
+
});
|
|
1281
|
+
processed.enrichment = enrichment;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return { json: processed };
|
|
1285
|
+
|
|
1286
|
+
} catch (error) {
|
|
1287
|
+
return {
|
|
1288
|
+
json: {
|
|
1289
|
+
...item.json,
|
|
1290
|
+
error: error.message,
|
|
1291
|
+
status: 'failed',
|
|
1292
|
+
index: index
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Process in batches with concurrency limit
|
|
1299
|
+
for (let i = 0; i < items.length; i += CONCURRENT_LIMIT) {
|
|
1300
|
+
const batch = items.slice(i, i + CONCURRENT_LIMIT);
|
|
1301
|
+
const batchPromises = batch.map((item, batchIndex) =>
|
|
1302
|
+
processItem(item, i + batchIndex)
|
|
1303
|
+
);
|
|
1304
|
+
|
|
1305
|
+
const batchResults = await Promise.all(batchPromises);
|
|
1306
|
+
results.push(...batchResults);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
return results;`,
|
|
1310
|
+
onError: 'continueRegularOutput',
|
|
1311
|
+
retryOnFail: true,
|
|
1312
|
+
maxTries: 2
|
|
1313
|
+
},
|
|
1314
|
+
userMustProvide: [],
|
|
1315
|
+
notes: [
|
|
1316
|
+
'Processes 5 items concurrently',
|
|
1317
|
+
'Prevents overwhelming external services',
|
|
1318
|
+
'Each item processed independently',
|
|
1319
|
+
'Errors don\'t affect other items'
|
|
1320
|
+
]
|
|
1321
|
+
},
|
|
1322
|
+
'python_data_analysis': {
|
|
1323
|
+
task: 'python_data_analysis',
|
|
1324
|
+
description: 'Analyze data using Python with statistics',
|
|
1325
|
+
nodeType: 'nodes-base.code',
|
|
1326
|
+
configuration: {
|
|
1327
|
+
language: 'python',
|
|
1328
|
+
pythonCode: `# Python data analysis - use underscore prefix for built-in variables
|
|
1329
|
+
import json
|
|
1330
|
+
from datetime import datetime
|
|
1331
|
+
import statistics
|
|
1332
|
+
|
|
1333
|
+
# Collect data for analysis
|
|
1334
|
+
values = []
|
|
1335
|
+
categories = {}
|
|
1336
|
+
dates = []
|
|
1337
|
+
|
|
1338
|
+
# Use _input.all() to get items in Python
|
|
1339
|
+
for item in _input.all():
|
|
1340
|
+
# Convert JsProxy to Python dict for safe access
|
|
1341
|
+
item_data = item.json.to_py()
|
|
1342
|
+
|
|
1343
|
+
# Extract numeric values
|
|
1344
|
+
if 'value' in item_data or 'amount' in item_data:
|
|
1345
|
+
value = item_data.get('value', item_data.get('amount', 0))
|
|
1346
|
+
if isinstance(value, (int, float)):
|
|
1347
|
+
values.append(value)
|
|
1348
|
+
|
|
1349
|
+
# Count categories
|
|
1350
|
+
category = item_data.get('category', 'uncategorized')
|
|
1351
|
+
categories[category] = categories.get(category, 0) + 1
|
|
1352
|
+
|
|
1353
|
+
# Collect dates
|
|
1354
|
+
if 'date' in item_data:
|
|
1355
|
+
dates.append(item_data['date'])
|
|
1356
|
+
|
|
1357
|
+
# Calculate statistics
|
|
1358
|
+
result = {
|
|
1359
|
+
'itemCount': len(_input.all()),
|
|
1360
|
+
'values': {
|
|
1361
|
+
'count': len(values),
|
|
1362
|
+
'sum': sum(values) if values else 0,
|
|
1363
|
+
'mean': statistics.mean(values) if values else 0,
|
|
1364
|
+
'median': statistics.median(values) if values else 0,
|
|
1365
|
+
'min': min(values) if values else 0,
|
|
1366
|
+
'max': max(values) if values else 0,
|
|
1367
|
+
'stdev': statistics.stdev(values) if len(values) > 1 else 0
|
|
1368
|
+
},
|
|
1369
|
+
'categories': categories,
|
|
1370
|
+
'dateRange': {
|
|
1371
|
+
'earliest': min(dates) if dates else None,
|
|
1372
|
+
'latest': max(dates) if dates else None,
|
|
1373
|
+
'count': len(dates)
|
|
1374
|
+
},
|
|
1375
|
+
'analysis': {
|
|
1376
|
+
'hasNumericData': len(values) > 0,
|
|
1377
|
+
'hasCategoricalData': len(categories) > 0,
|
|
1378
|
+
'hasTemporalData': len(dates) > 0,
|
|
1379
|
+
'dataQuality': 'good' if len(values) > len(items) * 0.8 else 'partial'
|
|
1380
|
+
},
|
|
1381
|
+
'processedAt': datetime.now().isoformat()
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
# Return single summary item
|
|
1385
|
+
return [{'json': result}]`,
|
|
1386
|
+
onError: 'continueRegularOutput'
|
|
1387
|
+
},
|
|
1388
|
+
userMustProvide: [],
|
|
1389
|
+
notes: [
|
|
1390
|
+
'Uses Python statistics module',
|
|
1391
|
+
'Analyzes numeric, categorical, and date data',
|
|
1392
|
+
'Returns comprehensive summary',
|
|
1393
|
+
'Handles missing data gracefully'
|
|
1394
|
+
]
|
|
553
1395
|
}
|
|
554
1396
|
};
|
|
555
1397
|
//# sourceMappingURL=task-templates.js.map
|