@n8n/workflow-sdk 0.12.0 → 0.13.1
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/dist/ast-interpreter/validators.js +1 -0
- package/dist/ast-interpreter/validators.js.map +1 -1
- package/dist/build.tsbuildinfo +1 -1
- package/dist/codegen/parse-workflow-code.js +1 -0
- package/dist/codegen/parse-workflow-code.js.map +1 -1
- package/dist/expression/index.d.ts +4 -1
- package/dist/expression/index.js +30 -0
- package/dist/expression/index.js.map +1 -1
- package/dist/expression.d.ts +1 -1
- package/dist/expression.js +2 -1
- package/dist/expression.js.map +1 -1
- package/dist/generate-types/generate-types.d.ts +1 -0
- package/dist/generate-types/generate-types.js +87 -24
- package/dist/generate-types/generate-types.js.map +1 -1
- package/dist/generate-types/generate-zod-schemas.d.ts +5 -1
- package/dist/generate-types/generate-zod-schemas.js +123 -77
- package/dist/generate-types/generate-zod-schemas.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/prompts/best-practices/guides/chatbot.js +11 -0
- package/dist/prompts/best-practices/guides/chatbot.js.map +1 -1
- package/dist/prompts/node-guidance/parameter-guides/predecessor-output.js +7 -0
- package/dist/prompts/node-guidance/parameter-guides/predecessor-output.js.map +1 -1
- package/dist/prompts/sdk-reference/additional-functions.d.ts +1 -1
- package/dist/prompts/sdk-reference/additional-functions.js +3 -0
- package/dist/prompts/sdk-reference/additional-functions.js.map +1 -1
- package/dist/prompts/sdk-reference/expressions.d.ts +1 -1
- package/dist/prompts/sdk-reference/expressions.js +14 -1
- package/dist/prompts/sdk-reference/expressions.js.map +1 -1
- package/dist/prompts/sdk-reference/workflow-patterns.d.ts +1 -1
- package/dist/prompts/sdk-reference/workflow-patterns.js +102 -34
- package/dist/prompts/sdk-reference/workflow-patterns.js.map +1 -1
- package/dist/prompts/sdk-reference/workflow-rules.d.ts +1 -1
- package/dist/prompts/sdk-reference/workflow-rules.js +14 -7
- package/dist/prompts/sdk-reference/workflow-rules.js.map +1 -1
- package/dist/types/base.d.ts +6 -5
- package/dist/validation/display-options.js +8 -0
- package/dist/validation/display-options.js.map +1 -1
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +88 -5
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/resolve-schema.d.ts +14 -0
- package/dist/validation/resolve-schema.js +20 -0
- package/dist/validation/resolve-schema.js.map +1 -1
- package/dist/validation/schema-helpers.d.ts +2 -2
- package/dist/validation/schema-helpers.js +2 -1
- package/dist/validation/schema-helpers.js.map +1 -1
- package/dist/validation/schema-validator.js +1 -0
- package/dist/validation/schema-validator.js.map +1 -1
- package/dist/workflow-builder/control-flow-builders/if-else.d.ts +1 -2
- package/dist/workflow-builder/control-flow-builders/split-in-batches.d.ts +2 -2
- package/dist/workflow-builder/control-flow-builders/split-in-batches.js +26 -0
- package/dist/workflow-builder/control-flow-builders/split-in-batches.js.map +1 -1
- package/dist/workflow-builder/control-flow-builders/switch-case.d.ts +1 -2
- package/dist/workflow-builder/node-builders/node-builder.d.ts +1 -0
- package/dist/workflow-builder/node-builders/node-builder.js +34 -1
- package/dist/workflow-builder/node-builders/node-builder.js.map +1 -1
- package/dist/workflow-builder/node-builders/subnode-builders.js +2 -1
- package/dist/workflow-builder/node-builders/subnode-builders.js.map +1 -1
- package/dist/workflow-builder/pin-data-utils.js.map +1 -1
- package/dist/workflow-builder/plugins/defaults.js +1 -0
- package/dist/workflow-builder/plugins/defaults.js.map +1 -1
- package/dist/workflow-builder/plugins/serializers/json-serializer.js +12 -1
- package/dist/workflow-builder/plugins/serializers/json-serializer.js.map +1 -1
- package/dist/workflow-builder/plugins/validators/index.d.ts +1 -0
- package/dist/workflow-builder/plugins/validators/index.js +3 -1
- package/dist/workflow-builder/plugins/validators/index.js.map +1 -1
- package/dist/workflow-builder/plugins/validators/memory-session-key-validator.d.ts +2 -0
- package/dist/workflow-builder/plugins/validators/memory-session-key-validator.js +50 -0
- package/dist/workflow-builder/plugins/validators/memory-session-key-validator.js.map +1 -0
- package/dist/workflow-builder/plugins/validators/set-node-validator.js +110 -4
- package/dist/workflow-builder/plugins/validators/set-node-validator.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"additional-functions.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/additional-functions.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG
|
|
1
|
+
{"version":3,"file":"additional-functions.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/additional-functions.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;mIAkB+F,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const EXPRESSION_REFERENCE = "Available variables inside `expr('{{ ... }}')`:\n\n- `$json` \u2014 current item's JSON data from the immediate predecessor node\n- `$('NodeName').item.json` \u2014 access any node's output by name\n- `$input.first()` \u2014 first item from immediate predecessor\n- `$input.all()` \u2014 all items from immediate predecessor\n- `$input.item` \u2014 current item being processed\n- `$binary` \u2014 binary data of current item from immediate predecessor\n- `$now` \u2014 current date/time (Luxon DateTime). Example: `$now.toISO()`\n- `$today` \u2014 start of today (Luxon DateTime). Example: `$today.plus(1, 'days')`\n- `$itemIndex` \u2014 index of current item being processed\n- `$runIndex` \u2014 current run index\n- `$execution.id` \u2014 unique execution ID\n- `$execution.mode` \u2014 'test' or 'production'\n- `$workflow.id` \u2014 workflow ID\n- `$workflow.name` \u2014 workflow name\n\nString composition \u2014 variables MUST always be inside `{{ }}`, never outside as JS variables:\n\n- `expr('Hello {{ $json.name }}, welcome!')` \u2014 variable embedded in text\n- `expr('Report for {{ $now.toFormat(\"MMMM d, yyyy\") }} - {{ $json.title }}')` \u2014 multiple variables with method call\n- `expr('{{ $json.firstName }} {{ $json.lastName }}')` \u2014 combining multiple fields\n- `expr('Total: {{ $json.items.length }} items, updated {{ $now.toISO() }}')` \u2014 expressions with method calls\n- `expr('Status: {{ $json.count > 0 ? \"active\" : \"empty\" }}')` \u2014 inline ternary\n\nDynamic data from other nodes \u2014 `$()` MUST always be inside `{{ }}`, never used as plain JavaScript:\n\n- WRONG: `expr('{{ ' + JSON.stringify($('Source').all().map(i => i.json.name)) + ' }}')` \u2014 $() outside {{ }}\n- CORRECT: `expr('{{ $(\"Source\").all().map(i => ({ option: i.json.name })) }}')` \u2014 $() inside {{ }}\n- CORRECT: `expr('{{ { \"fields\": [{ \"values\": $(\"Fetch Projects\").all().map(i => ({ option: i.json.name })) }] } }}')` \u2014 complex JSON inside {{ }}";
|
|
1
|
+
export declare const EXPRESSION_REFERENCE = "Available variables inside `expr('{{ ... }}')`:\n\n- `$json` \u2014 current item's JSON data from the immediate predecessor node\n- `$('NodeName').item.json` \u2014 access any node's output by name\n- `nodeJson(node, 'field.path')` \u2014 SDK helper that creates `={{ $('NodeName').item.json.field.path }}`\n- `$input.first()` \u2014 first item from immediate predecessor\n- `$input.all()` \u2014 all items from immediate predecessor\n- `$input.item` \u2014 current item being processed\n- `$binary` \u2014 binary data of current item from immediate predecessor\n- `$now` \u2014 current date/time (Luxon DateTime). Example: `$now.toISO()`\n- `$today` \u2014 start of today (Luxon DateTime). Example: `$today.plus(1, 'days')`\n- `$itemIndex` \u2014 index of current item being processed\n- `$runIndex` \u2014 current run index\n- `$execution.id` \u2014 unique execution ID\n- `$execution.mode` \u2014 'test' or 'production'\n- `$workflow.id` \u2014 workflow ID\n- `$workflow.name` \u2014 workflow name\n\nString composition \u2014 variables MUST always be inside `{{ }}`, never outside as JS variables:\n\n- `expr('Hello {{ $json.name }}, welcome!')` \u2014 variable embedded in text\n- `expr('Report for {{ $now.toFormat(\"MMMM d, yyyy\") }} - {{ $json.title }}')` \u2014 multiple variables with method call\n- `expr('{{ $json.firstName }} {{ $json.lastName }}')` \u2014 combining multiple fields\n- `expr('Total: {{ $json.items.length }} items, updated {{ $now.toISO() }}')` \u2014 expressions with method calls\n- `expr('Status: {{ $json.count > 0 ? \"active\" : \"empty\" }}')` \u2014 inline ternary\n\nDynamic data from other nodes \u2014 `$()` MUST always be inside `{{ }}`, never used as plain JavaScript:\n\n- WRONG: `expr('{{ ' + JSON.stringify($('Source').all().map(i => i.json.name)) + ' }}')` \u2014 $() outside {{ }}\n- CORRECT: `expr('{{ $(\"Source\").all().map(i => ({ option: i.json.name })) }}')` \u2014 $() inside {{ }}\n- CORRECT: `expr('{{ { \"fields\": [{ \"values\": $(\"Fetch Projects\").all().map(i => ({ option: i.json.name })) }] } }}')` \u2014 complex JSON inside {{ }}\n\nWhen `$json` is unsafe - use `nodeJson(node, 'path')` or `$('NodeName').item.json.path` instead:\n\n- AI Agent subnodes: memory, language model, parser, retriever, vector store, and tool subnodes do not have the same immediate predecessor context as a main-flow node.\n WRONG: `sessionKey: expr('{{ $json.chatId }}')`\n CORRECT: `sessionKey: nodeJson(telegramTrigger, 'message.chat.id')`\n- Multi-branch fan-in: if a node receives data after IF/Switch/Merge-style branching, `$json` only means the current incoming item and may not contain the source field you need.\n WRONG: `expr('{{ $json.userId }}')`\n CORRECT: `nodeJson(userLookup, 'user.id')`\n- Further-upstream data: if the value comes from any node other than the immediate main predecessor, reference that node explicitly.\n WRONG: `expr('{{ $json.email }}')`\n CORRECT: `nodeJson(formTrigger, 'body.email')`";
|
|
@@ -5,6 +5,7 @@ exports.EXPRESSION_REFERENCE = `Available variables inside \`expr('{{ ... }}')\`
|
|
|
5
5
|
|
|
6
6
|
- \`$json\` — current item's JSON data from the immediate predecessor node
|
|
7
7
|
- \`$('NodeName').item.json\` — access any node's output by name
|
|
8
|
+
- \`nodeJson(node, 'field.path')\` — SDK helper that creates \`={{ $('NodeName').item.json.field.path }}\`
|
|
8
9
|
- \`$input.first()\` — first item from immediate predecessor
|
|
9
10
|
- \`$input.all()\` — all items from immediate predecessor
|
|
10
11
|
- \`$input.item\` — current item being processed
|
|
@@ -30,5 +31,17 @@ Dynamic data from other nodes — \`$()\` MUST always be inside \`{{ }}\`, never
|
|
|
30
31
|
|
|
31
32
|
- WRONG: \`expr('{{ ' + JSON.stringify($('Source').all().map(i => i.json.name)) + ' }}')\` — $() outside {{ }}
|
|
32
33
|
- CORRECT: \`expr('{{ $("Source").all().map(i => ({ option: i.json.name })) }}')\` — $() inside {{ }}
|
|
33
|
-
- CORRECT: \`expr('{{ { "fields": [{ "values": $("Fetch Projects").all().map(i => ({ option: i.json.name })) }] } }}')\` — complex JSON inside {{ }}
|
|
34
|
+
- CORRECT: \`expr('{{ { "fields": [{ "values": $("Fetch Projects").all().map(i => ({ option: i.json.name })) }] } }}')\` — complex JSON inside {{ }}
|
|
35
|
+
|
|
36
|
+
When \`$json\` is unsafe - use \`nodeJson(node, 'path')\` or \`$('NodeName').item.json.path\` instead:
|
|
37
|
+
|
|
38
|
+
- AI Agent subnodes: memory, language model, parser, retriever, vector store, and tool subnodes do not have the same immediate predecessor context as a main-flow node.
|
|
39
|
+
WRONG: \`sessionKey: expr('{{ $json.chatId }}')\`
|
|
40
|
+
CORRECT: \`sessionKey: nodeJson(telegramTrigger, 'message.chat.id')\`
|
|
41
|
+
- Multi-branch fan-in: if a node receives data after IF/Switch/Merge-style branching, \`$json\` only means the current incoming item and may not contain the source field you need.
|
|
42
|
+
WRONG: \`expr('{{ $json.userId }}')\`
|
|
43
|
+
CORRECT: \`nodeJson(userLookup, 'user.id')\`
|
|
44
|
+
- Further-upstream data: if the value comes from any node other than the immediate main predecessor, reference that node explicitly.
|
|
45
|
+
WRONG: \`expr('{{ $json.email }}')\`
|
|
46
|
+
CORRECT: \`nodeJson(formTrigger, 'body.email')\``;
|
|
34
47
|
//# sourceMappingURL=expressions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/expressions.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG
|
|
1
|
+
{"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/expressions.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDA0Ce,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const WORKFLOW_SDK_PATTERNS = "<linear_chain>\n```javascript\nimport { workflow, node, trigger, sticky, placeholder, newCredential, ifElse, switchCase, merge, splitInBatches, nextBatch, languageModel, memory, tool, outputParser, embedding, embeddings, vectorStore, retriever, documentLoader, textSplitter, fromAi, expr } from '@n8n/workflow-sdk';\n\n// 1. Define all nodes first\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchData = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Data', parameters: { method: 'GET', url: '...' } }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Data', parameters: {} }\n});\n\n// 2. Compose workflow\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchData)\n .to(processData);\n```\n\n</linear_chain>\n\n<independent_sources>\nWhen nodes return more than 1 item, chaining causes item multiplication: if Source A returns N items, a chained Source B runs N times instead of once.\n\n**When to use `executeOnce: true`:**\n- A node fetches data independently but is chained after another data source (prevents N\u00D7M multiplication)\n- A node should summarize/aggregate all upstream items in a single call (e.g., AI summary, send one notification)\n- A node calls an API that doesn't vary per input item\n\nFix with `executeOnce: true` (simplest) or parallel branches + Merge (when combining results):\n\n```javascript\n// sourceA outputs 10 items. sourceB outputs 10 items.\n// WRONG - processResults runs 100 times\n// startTrigger.to(sourceA.to(sourceB.to(processResults)))\n\n// FIX 1 - executeOnce: sourceB runs once regardless of input items\nconst sourceB = node({ ..., config: { ..., executeOnce: true } });\nstartTrigger.to(sourceA.to(sourceB.to(processResults)));\n\n// FIX 2 - parallel branches + Merge (combine by position)\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine', combineBy: 'combineByPosition' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(combineResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(combineResults.input(1)))\n .add(combineResults)\n .to(processResults);\n\n// FIX 3 - parallel branches + Merge (append)\nconst allResults = merge({\n version: 3.2,\n config: { name: 'All Results', parameters: { mode: 'append' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(allResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(allResults.input(1)))\n .add(allResults)\n .to(processResults);\n```\n\n</independent_sources>\n\n<zero_item_safety>\nNodes that fetch or filter data may return 0 items, which stops the entire downstream chain.\nUse `alwaysOutputData: true` on data-fetching nodes to ensure the chain continues with an empty item `{json: {}}`.\n\n```javascript\n// Data Table might be empty (fresh table, no matching rows)\nconst getReflections = node({\n type: 'n8n-nodes-base.dataTable',\n version: 1.1,\n config: {\n name: 'Get Reflections',\n alwaysOutputData: true, // Chain continues even if table is empty\n parameters: { resource: 'row', operation: 'get', returnAll: true }\n }\n});\n\n// Downstream Code node handles the empty case\nconst processData = node({\n type: 'n8n-nodes-base.code',\n version: 2,\n config: {\n name: 'Process Data',\n parameters: {\n mode: 'runOnceForAllItems',\n jsCode: \\`\nconst items = $input.all();\n// items will be [{json: {}}] if upstream had no data\nconst hasData = items.length > 0 && Object.keys(items[0].json).length > 0;\n// ... handle both cases\n\\`.trim()\n }\n }\n});\n```\n\n**When to use `alwaysOutputData: true`:**\n- Data Table with `operation: 'get'` (table may be empty or freshly created)\n- Any lookup/search/filter node whose result feeds into downstream processing\n- HTTP Request that may return an empty array\n\n**When NOT to use it:**\n- Trigger nodes (they always produce output)\n- Code nodes (handle empty input in your code logic instead)\n- Nodes at the end of the chain (no downstream to protect)\n\n</zero_item_safety>\n\n<conditional_branching>\n\n**CRITICAL:** Each branch defines a COMPLETE processing path. Chain multiple steps INSIDE the branch using .to().\n\nEvery IF/Filter `conditions` parameter MUST include `options`, `conditions`, and `combinator`:\n```javascript\nconst checkValid = ifElse({\n version: 2.2,\n config: {\n name: 'Check Valid',\n parameters: {\n conditions: {\n options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' },\n conditions: [{ leftValue: '={{ $json.status }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'active' }],\n combinator: 'and'\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(checkValid\n .onTrue(formatData.to(enrichData.to(saveToDb))) // Chain 3 nodes on true branch\n .onFalse(logError));\n```\n\n</conditional_branching>\n\n<multi_way_routing>\n\nSwitch rules use `rules.values` (NOT `rules.rules`). Each rule needs `outputKey` and a complete `conditions` object:\n```javascript\nconst routeByPriority = switchCase({\n version: 3.2,\n config: {\n name: 'Route by Priority',\n parameters: {\n rules: {\n values: [\n { outputKey: 'urgent', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'urgent' }], combinator: 'and' } },\n { outputKey: 'normal', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'normal' }], combinator: 'and' } },\n ]\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(routeByPriority\n .onCase('urgent', processUrgent.to(notifyTeam.to(escalate)))\n .onCase('normal', processNormal)\n .onDefault(archive));\n```\n\n</multi_way_routing>\n\n<parallel_execution>\n```javascript\n// First declare the Merge node using merge()\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine' } }\n});\n\n// Declare branch nodes\nconst branch1 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst branch2 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst processResults = node({ type: 'n8n-nodes-base.set', ... });\n\n// Connect branches to specific merge inputs using .input(n)\nexport default workflow('id', 'name')\n .add(trigger({ ... }))\n .to(branch1.to(combineResults.input(0))) // Connect to input 0\n .add(trigger({ ... }))\n .to(branch2.to(combineResults.input(1))) // Connect to input 1\n .add(combineResults)\n .to(processResults); // Process merged results\n```\n\n</parallel_execution>\n\n<batch_processing>\n```javascript\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchRecords = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Records', parameters: { method: 'GET', url: '...' } }\n});\n\nconst finalizeResults = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Finalize', parameters: {} }\n});\n\nconst processRecord = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Process Record', parameters: { method: 'POST', url: '...' } }\n});\n\nconst sibNode = splitInBatches({ version: 3, config: { name: 'Batch Process', parameters: { batchSize: 10 } } });\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchRecords)\n .to(sibNode\n .onDone(finalizeResults)\n .onEachBatch(processRecord.to(nextBatch(sibNode)))\n );\n```\n\n</batch_processing>\n\n<multiple_triggers>\n```javascript\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook' }\n});\n\nconst processWebhook = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Webhook', parameters: {} }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule', parameters: {} }\n});\n\nconst processSchedule = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Schedule', parameters: {} }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processWebhook)\n .add(scheduleTrigger)\n .to(processSchedule);\n```\n\n</multiple_triggers>\n\n<fan_in>\n```javascript\n// Each trigger's execution runs in COMPLETE ISOLATION.\n// Different branches have no effect on each other.\n// Never duplicate chains for \"isolation\" - it's already guaranteed.\n\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook Trigger' }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule' }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Data', parameters: {} }\n});\n\nconst sendNotification = node({\n type: 'n8n-nodes-base.slack',\n version: 2.3,\n config: { name: 'Notify Slack', parameters: {} }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processData)\n .to(sendNotification)\n .add(scheduleTrigger)\n .to(processData);\n```\n\n</fan_in>\n\n<ai_agent_basic>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: { name: 'OpenAI Model', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'AI Assistant',\n parameters: { promptType: 'define', text: 'You are a helpful assistant' },\n subnodes: { model: openAiModel }\n }\n});\n\nexport default workflow('ai-assistant', 'AI Assistant')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_basic>\n\n<ai_agent_with_tools>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst calculatorTool = tool({\n type: '@n8n/n8n-nodes-langchain.toolCalculator',\n version: 1,\n config: { name: 'Calculator', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Math Agent',\n parameters: { promptType: 'define', text: 'You can use tools to help users' },\n subnodes: { model: openAiModel, tools: [calculatorTool] }\n }\n});\n\nexport default workflow('ai-calculator', 'AI Calculator')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_with_tools>\n\n<ai_agent_with_from_ai>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst gmailTool = tool({\n type: 'n8n-nodes-base.gmailTool',\n version: 1,\n config: {\n name: 'Gmail Tool',\n parameters: {\n sendTo: fromAi('recipient', 'Email address'),\n subject: fromAi('subject', 'Email subject'),\n message: fromAi('body', 'Email content')\n },\n credentials: { gmailOAuth2: newCredential('Gmail') }\n }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Email Agent',\n parameters: { promptType: 'define', text: 'You can send emails' },\n subnodes: { model: openAiModel, tools: [gmailTool] }\n }\n});\n\nexport default workflow('ai-email', 'AI Email Sender')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_from_ai>\n\n<ai_agent_with_structured_output>\n```javascript\nconst structuredParser = outputParser({\n type: '@n8n/n8n-nodes-langchain.outputParserStructured',\n version: 1.3,\n config: {\n name: 'Structured Output Parser',\n parameters: {\n schemaType: 'fromJson',\n jsonSchemaExample: '{ \"sentiment\": \"positive\", \"confidence\": 0.95, \"summary\": \"brief summary\" }'\n }\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Sentiment Analyzer',\n parameters: { promptType: 'define', text: 'Analyze the sentiment of the input text', hasOutputParser: true },\n subnodes: { model: openAiModel, outputParser: structuredParser }\n }\n});\n\nexport default workflow('ai-sentiment', 'AI Sentiment Analyzer')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_structured_output>";
|
|
1
|
+
export declare const WORKFLOW_SDK_PATTERNS = "<linear_chain>\n```javascript\nimport { workflow, node, trigger, sticky, placeholder, newCredential, ifElse, switchCase, merge, splitInBatches, nextBatch, languageModel, memory, tool, outputParser, embedding, embeddings, vectorStore, retriever, documentLoader, textSplitter, fromAi, expr } from '@n8n/workflow-sdk';\n\n// 1. Define all nodes first\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchData = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Data', parameters: { method: 'GET', url: '...' } }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Data',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'processed-title', name: 'processedTitle', value: expr('{{ $json.title }}'), type: 'string' }\n ]\n }\n }\n }\n});\n\n// 2. Compose workflow\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchData)\n .to(processData);\n```\n\n</linear_chain>\n\n<independent_sources>\nWhen nodes return more than 1 item, chaining causes item multiplication: if Source A returns N items, a chained Source B runs N times instead of once.\n\n**When to use `executeOnce: true`:**\n- A node fetches data independently but is chained after another data source (prevents N\u00D7M multiplication)\n- A node should summarize/aggregate all upstream items in a single call (e.g., AI summary, send one notification)\n- A node calls an API that doesn't vary per input item\n\nFix with `executeOnce: true` (simplest) or parallel branches + Merge (when combining results):\n\n```javascript\n// sourceA outputs 10 items. sourceB outputs 10 items.\n// WRONG - processResults runs 100 times\n// startTrigger.to(sourceA.to(sourceB.to(processResults)))\n\n// FIX 1 - executeOnce: sourceB runs once regardless of input items\nconst sourceB = node({ ..., config: { ..., executeOnce: true } });\nstartTrigger.to(sourceA.to(sourceB.to(processResults)));\n\n// FIX 2 - parallel branches + Merge (combine by position)\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine', combineBy: 'combineByPosition' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(combineResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(combineResults.input(1)))\n .add(combineResults)\n .to(processResults);\n\n// FIX 3 - parallel branches + Merge (append)\nconst allResults = merge({\n version: 3.2,\n config: { name: 'All Results', parameters: { mode: 'append' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(allResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(allResults.input(1)))\n .add(allResults)\n .to(processResults);\n```\n\n</independent_sources>\n\n<zero_item_safety>\nWhen a node returns 0 items, downstream nodes are skipped for that execution. **This is usually the correct behavior** \u2014 the scheduler / trigger fires again later, and when there is data, the chain runs normally. Don't paper over an empty result with `alwaysOutputData: true` by default.\n\n**`alwaysOutputData: true` forces a synthetic `{json: {}}` item downstream.** This is a footgun: downstream nodes will try to read fields that don't exist, HTTP requests will hit `GET undefined`, and loops will run once on a fake item. Use it *only* when the empty case has its own dedicated branch that you want to execute.\n\n**Correct pattern \u2014 no `alwaysOutputData`:**\n```javascript\n// Scheduler that processes pending work\nworkflow('ingest', 'Ingest Worker')\n .add(scheduleTrigger) // fires every 5 min\n .to(getPending) // returns 0..N rows; no alwaysOutputData\n .to(splitInBatches({version: 3, config: {parameters: {batchSize: 1}}})\n .onEachBatch(fetchUrl.to(embed).to(saveChunk))\n );\n// On runs where getPending returns 0 items, the loop simply doesn't execute.\n// On runs where it returns rows, the loop iterates. No gate, no filter needed.\n```\n\n**Correct pattern \u2014 empty case needs its own branch:**\n```javascript\n// \"No matches found\" deserves a notification\nconst search = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.4,\n config: {\n name: 'Search',\n alwaysOutputData: true, // empty-case branch below needs to execute\n parameters: { /* ... */ }\n }\n});\nconst hasResults = ifElse({\n version: 2.2,\n config: {\n name: 'Has Results?',\n parameters: {\n conditions: {\n options: { caseSensitive: true, typeValidation: 'loose' },\n conditions: [{ leftValue: '={{ $json.results }}', operator: { type: 'array', operation: 'notEmpty' } }],\n combinator: 'and'\n }\n }\n }\n});\nworkflow('search', 'Search').add(trigger).to(search).to(\n hasResults.onTrue(processResults).onFalse(notifyNoMatches)\n);\n```\n\n**When to use `alwaysOutputData: true`:** only when you've paired it with an explicit empty-case branch, AND the downstream branch doesn't blindly read item fields.\n\n**When NOT to use it:**\n- Scheduled/polling triggers where the \"no work\" case should silently skip\n- Before a `splitInBatches` loop \u2014 loops already no-op on empty input\n- Before a `filter` \u2014 the filter already no-ops on empty input\n- When all you'd do on the empty case is \"nothing\"\n\n**Don't gate loops with an `IF`.** `ifElse.onTrue(splitInBatches)` to check \"are there items?\" is redundant \u2014 the loop already does the right thing with 0 items. Drop the IF.\n\n</zero_item_safety>\n\n<conditional_branching>\n\n**CRITICAL:** Each branch defines a COMPLETE processing path. Chain multiple steps INSIDE the branch using .to().\n\nEvery IF/Filter `conditions` parameter MUST include `options`, `conditions`, and `combinator`:\n```javascript\nconst checkValid = ifElse({\n version: 2.2,\n config: {\n name: 'Check Valid',\n parameters: {\n conditions: {\n options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' },\n conditions: [{ leftValue: '={{ $json.status }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'active' }],\n combinator: 'and'\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(checkValid\n .onTrue(formatData.to(enrichData.to(saveToDb))) // Chain 3 nodes on true branch\n .onFalse(logError));\n```\n\n</conditional_branching>\n\n<multi_way_routing>\n\nSwitch rules use `rules.values` (NOT `rules.rules`). Each rule needs `outputKey` and a complete `conditions` object:\n```javascript\nconst routeByPriority = switchCase({\n version: 3.2,\n config: {\n name: 'Route by Priority',\n parameters: {\n rules: {\n values: [\n { outputKey: 'urgent', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'urgent' }], combinator: 'and' } },\n { outputKey: 'normal', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'normal' }], combinator: 'and' } },\n ]\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(routeByPriority\n .onCase('urgent', processUrgent.to(notifyTeam.to(escalate)))\n .onCase('normal', processNormal)\n .onDefault(archive));\n```\n\n</multi_way_routing>\n\n<parallel_execution>\n```javascript\n// First declare the Merge node using merge()\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine' } }\n});\n\n// Declare branch nodes\nconst branch1 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst branch2 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst processResults = node({ type: 'n8n-nodes-base.set', ... });\n\n// Connect branches to specific merge inputs using .input(n)\nexport default workflow('id', 'name')\n .add(trigger({ ... }))\n .to(branch1.to(combineResults.input(0))) // Connect to input 0\n .add(trigger({ ... }))\n .to(branch2.to(combineResults.input(1))) // Connect to input 1\n .add(combineResults)\n .to(processResults); // Process merged results\n```\n\n</parallel_execution>\n\n<batch_processing>\n```javascript\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchRecords = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Records', parameters: { method: 'GET', url: '...' } }\n});\n\nconst finalizeResults = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Finalize',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'processed-at', name: 'processedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }\n ]\n }\n }\n }\n});\n\nconst processRecord = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Process Record', parameters: { method: 'POST', url: '...' } }\n});\n\nconst sibNode = splitInBatches({ version: 3, config: { name: 'Batch Process', parameters: { batchSize: 10 } } });\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchRecords)\n .to(sibNode\n .onDone(finalizeResults)\n .onEachBatch(processRecord.to(nextBatch(sibNode)))\n );\n```\n\n</batch_processing>\n\n<multiple_triggers>\n```javascript\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook' }\n});\n\nconst processWebhook = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Webhook',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'source', name: 'source', value: 'webhook', type: 'string' }\n ]\n }\n }\n }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule', parameters: {} }\n});\n\nconst processSchedule = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Schedule',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'source', name: 'source', value: 'schedule', type: 'string' }\n ]\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processWebhook)\n .add(scheduleTrigger)\n .to(processSchedule);\n```\n\n</multiple_triggers>\n\n<fan_in>\n```javascript\n// Each trigger's execution runs in COMPLETE ISOLATION.\n// Different branches have no effect on each other.\n// Never duplicate chains for \"isolation\" - it's already guaranteed.\n\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook Trigger' }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule' }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Data',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'received-at', name: 'receivedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }\n ]\n }\n }\n }\n});\n\nconst sendNotification = node({\n type: 'n8n-nodes-base.slack',\n version: 2.3,\n config: { name: 'Notify Slack', parameters: {} }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processData)\n .to(sendNotification)\n .add(scheduleTrigger)\n .to(processData);\n```\n\n</fan_in>\n\n<ai_agent_basic>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: { name: 'OpenAI Model', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'AI Assistant',\n parameters: { promptType: 'define', text: 'You are a helpful assistant' },\n subnodes: { model: openAiModel }\n }\n});\n\nexport default workflow('ai-assistant', 'AI Assistant')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_basic>\n\n<ai_agent_with_tools>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst calculatorTool = tool({\n type: '@n8n/n8n-nodes-langchain.toolCalculator',\n version: 1,\n config: { name: 'Calculator', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Math Agent',\n parameters: { promptType: 'define', text: 'You can use tools to help users' },\n subnodes: { model: openAiModel, tools: [calculatorTool] }\n }\n});\n\nexport default workflow('ai-calculator', 'AI Calculator')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_with_tools>\n\n<ai_agent_with_from_ai>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst gmailTool = tool({\n type: 'n8n-nodes-base.gmailTool',\n version: 1,\n config: {\n name: 'Gmail Tool',\n parameters: {\n sendTo: fromAi('recipient', 'Email address'),\n subject: fromAi('subject', 'Email subject'),\n message: fromAi('body', 'Email content')\n },\n credentials: { gmailOAuth2: newCredential('Gmail') }\n }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Email Agent',\n parameters: { promptType: 'define', text: 'You can send emails' },\n subnodes: { model: openAiModel, tools: [gmailTool] }\n }\n});\n\nexport default workflow('ai-email', 'AI Email Sender')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_from_ai>\n\n<ai_agent_with_structured_output>\n```javascript\nconst structuredParser = outputParser({\n type: '@n8n/n8n-nodes-langchain.outputParserStructured',\n version: 1.3,\n config: {\n name: 'Structured Output Parser',\n parameters: {\n schemaType: 'fromJson',\n jsonSchemaExample: '{ \"sentiment\": \"positive\", \"confidence\": 0.95, \"summary\": \"brief summary\" }'\n }\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Sentiment Analyzer',\n parameters: { promptType: 'define', text: 'Analyze the sentiment of the input text', hasOutputParser: true },\n subnodes: { model: openAiModel, outputParser: structuredParser }\n }\n});\n\nexport default workflow('ai-sentiment', 'AI Sentiment Analyzer')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_structured_output>";
|
|
2
2
|
export { WORKFLOW_SDK_PATTERNS as WORKFLOW_PATTERNS_CONCISE };
|
|
@@ -22,7 +22,18 @@ const fetchData = node({
|
|
|
22
22
|
const processData = node({
|
|
23
23
|
type: 'n8n-nodes-base.set',
|
|
24
24
|
version: 3.4,
|
|
25
|
-
config: {
|
|
25
|
+
config: {
|
|
26
|
+
name: 'Process Data',
|
|
27
|
+
parameters: {
|
|
28
|
+
mode: 'manual',
|
|
29
|
+
includeOtherFields: true,
|
|
30
|
+
assignments: {
|
|
31
|
+
assignments: [
|
|
32
|
+
{ id: 'processed-title', name: 'processedTitle', value: expr('{{ $json.title }}'), type: 'string' }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
26
37
|
});
|
|
27
38
|
|
|
28
39
|
// 2. Compose workflow
|
|
@@ -83,49 +94,62 @@ export default workflow('id', 'name')
|
|
|
83
94
|
</independent_sources>
|
|
84
95
|
|
|
85
96
|
<zero_item_safety>
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
When a node returns 0 items, downstream nodes are skipped for that execution. **This is usually the correct behavior** — the scheduler / trigger fires again later, and when there is data, the chain runs normally. Don't paper over an empty result with \`alwaysOutputData: true\` by default.
|
|
98
|
+
|
|
99
|
+
**\`alwaysOutputData: true\` forces a synthetic \`{json: {}}\` item downstream.** This is a footgun: downstream nodes will try to read fields that don't exist, HTTP requests will hit \`GET undefined\`, and loops will run once on a fake item. Use it *only* when the empty case has its own dedicated branch that you want to execute.
|
|
100
|
+
|
|
101
|
+
**Correct pattern — no \`alwaysOutputData\`:**
|
|
102
|
+
\`\`\`javascript
|
|
103
|
+
// Scheduler that processes pending work
|
|
104
|
+
workflow('ingest', 'Ingest Worker')
|
|
105
|
+
.add(scheduleTrigger) // fires every 5 min
|
|
106
|
+
.to(getPending) // returns 0..N rows; no alwaysOutputData
|
|
107
|
+
.to(splitInBatches({version: 3, config: {parameters: {batchSize: 1}}})
|
|
108
|
+
.onEachBatch(fetchUrl.to(embed).to(saveChunk))
|
|
109
|
+
);
|
|
110
|
+
// On runs where getPending returns 0 items, the loop simply doesn't execute.
|
|
111
|
+
// On runs where it returns rows, the loop iterates. No gate, no filter needed.
|
|
112
|
+
\`\`\`
|
|
88
113
|
|
|
114
|
+
**Correct pattern — empty case needs its own branch:**
|
|
89
115
|
\`\`\`javascript
|
|
90
|
-
//
|
|
91
|
-
const
|
|
92
|
-
type: 'n8n-nodes-base.
|
|
93
|
-
version:
|
|
116
|
+
// "No matches found" deserves a notification
|
|
117
|
+
const search = node({
|
|
118
|
+
type: 'n8n-nodes-base.httpRequest',
|
|
119
|
+
version: 4.4,
|
|
94
120
|
config: {
|
|
95
|
-
name: '
|
|
96
|
-
alwaysOutputData: true,
|
|
97
|
-
parameters: {
|
|
121
|
+
name: 'Search',
|
|
122
|
+
alwaysOutputData: true, // empty-case branch below needs to execute
|
|
123
|
+
parameters: { /* ... */ }
|
|
98
124
|
}
|
|
99
125
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const processData = node({
|
|
103
|
-
type: 'n8n-nodes-base.code',
|
|
104
|
-
version: 2,
|
|
126
|
+
const hasResults = ifElse({
|
|
127
|
+
version: 2.2,
|
|
105
128
|
config: {
|
|
106
|
-
name: '
|
|
129
|
+
name: 'Has Results?',
|
|
107
130
|
parameters: {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// ... handle both cases
|
|
114
|
-
\\\`.trim()
|
|
131
|
+
conditions: {
|
|
132
|
+
options: { caseSensitive: true, typeValidation: 'loose' },
|
|
133
|
+
conditions: [{ leftValue: '={{ $json.results }}', operator: { type: 'array', operation: 'notEmpty' } }],
|
|
134
|
+
combinator: 'and'
|
|
135
|
+
}
|
|
115
136
|
}
|
|
116
137
|
}
|
|
117
138
|
});
|
|
139
|
+
workflow('search', 'Search').add(trigger).to(search).to(
|
|
140
|
+
hasResults.onTrue(processResults).onFalse(notifyNoMatches)
|
|
141
|
+
);
|
|
118
142
|
\`\`\`
|
|
119
143
|
|
|
120
|
-
**When to use \`alwaysOutputData: true\`:**
|
|
121
|
-
- Data Table with \`operation: 'get'\` (table may be empty or freshly created)
|
|
122
|
-
- Any lookup/search/filter node whose result feeds into downstream processing
|
|
123
|
-
- HTTP Request that may return an empty array
|
|
144
|
+
**When to use \`alwaysOutputData: true\`:** only when you've paired it with an explicit empty-case branch, AND the downstream branch doesn't blindly read item fields.
|
|
124
145
|
|
|
125
146
|
**When NOT to use it:**
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
147
|
+
- Scheduled/polling triggers where the "no work" case should silently skip
|
|
148
|
+
- Before a \`splitInBatches\` loop — loops already no-op on empty input
|
|
149
|
+
- Before a \`filter\` — the filter already no-ops on empty input
|
|
150
|
+
- When all you'd do on the empty case is "nothing"
|
|
151
|
+
|
|
152
|
+
**Don't gate loops with an \`IF\`.** \`ifElse.onTrue(splitInBatches)\` to check "are there items?" is redundant — the loop already does the right thing with 0 items. Drop the IF.
|
|
129
153
|
|
|
130
154
|
</zero_item_safety>
|
|
131
155
|
|
|
@@ -229,7 +253,18 @@ const fetchRecords = node({
|
|
|
229
253
|
const finalizeResults = node({
|
|
230
254
|
type: 'n8n-nodes-base.set',
|
|
231
255
|
version: 3.4,
|
|
232
|
-
config: {
|
|
256
|
+
config: {
|
|
257
|
+
name: 'Finalize',
|
|
258
|
+
parameters: {
|
|
259
|
+
mode: 'manual',
|
|
260
|
+
includeOtherFields: true,
|
|
261
|
+
assignments: {
|
|
262
|
+
assignments: [
|
|
263
|
+
{ id: 'processed-at', name: 'processedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
233
268
|
});
|
|
234
269
|
|
|
235
270
|
const processRecord = node({
|
|
@@ -262,7 +297,18 @@ const webhookTrigger = trigger({
|
|
|
262
297
|
const processWebhook = node({
|
|
263
298
|
type: 'n8n-nodes-base.set',
|
|
264
299
|
version: 3.4,
|
|
265
|
-
config: {
|
|
300
|
+
config: {
|
|
301
|
+
name: 'Process Webhook',
|
|
302
|
+
parameters: {
|
|
303
|
+
mode: 'manual',
|
|
304
|
+
includeOtherFields: true,
|
|
305
|
+
assignments: {
|
|
306
|
+
assignments: [
|
|
307
|
+
{ id: 'source', name: 'source', value: 'webhook', type: 'string' }
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
266
312
|
});
|
|
267
313
|
|
|
268
314
|
const scheduleTrigger = trigger({
|
|
@@ -274,7 +320,18 @@ const scheduleTrigger = trigger({
|
|
|
274
320
|
const processSchedule = node({
|
|
275
321
|
type: 'n8n-nodes-base.set',
|
|
276
322
|
version: 3.4,
|
|
277
|
-
config: {
|
|
323
|
+
config: {
|
|
324
|
+
name: 'Process Schedule',
|
|
325
|
+
parameters: {
|
|
326
|
+
mode: 'manual',
|
|
327
|
+
includeOtherFields: true,
|
|
328
|
+
assignments: {
|
|
329
|
+
assignments: [
|
|
330
|
+
{ id: 'source', name: 'source', value: 'schedule', type: 'string' }
|
|
331
|
+
]
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
278
335
|
});
|
|
279
336
|
|
|
280
337
|
export default workflow('id', 'name')
|
|
@@ -307,7 +364,18 @@ const scheduleTrigger = trigger({
|
|
|
307
364
|
const processData = node({
|
|
308
365
|
type: 'n8n-nodes-base.set',
|
|
309
366
|
version: 3.4,
|
|
310
|
-
config: {
|
|
367
|
+
config: {
|
|
368
|
+
name: 'Process Data',
|
|
369
|
+
parameters: {
|
|
370
|
+
mode: 'manual',
|
|
371
|
+
includeOtherFields: true,
|
|
372
|
+
assignments: {
|
|
373
|
+
assignments: [
|
|
374
|
+
{ id: 'received-at', name: 'receivedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
311
379
|
});
|
|
312
380
|
|
|
313
381
|
const sendNotification = node({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-patterns.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-patterns.ts"],"names":[],"mappings":";;;AAUA,MAAM,UAAU,GACf,8RAA8R,CAAC;AAMnR,QAAA,qBAAqB,GAAG;;EAEnC,UAAU
|
|
1
|
+
{"version":3,"file":"workflow-patterns.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-patterns.ts"],"names":[],"mappings":";;;AAUA,MAAM,UAAU,GACf,8RAA8R,CAAC;AAMnR,QAAA,qBAAqB,GAAG;;EAEnC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA0hBuB,CAAC;AAEF,oCA9hBrB,6BAAqB,CA8hByB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const WORKFLOW_RULES = "Follow these rules strictly when generating workflows:\n\n1. **Always use newCredential() for authentication**\n - When a node needs credentials, always use `newCredential('Name')` in the credentials config\n - NEVER use placeholder strings, fake API keys, or hardcoded auth values\n - Example: `credentials: { slackApi: newCredential('Slack Bot') }`\n - The credential type must match what the node expects\n\n2. **
|
|
1
|
+
export declare const WORKFLOW_RULES = "Follow these rules strictly when generating workflows:\n\n1. **Always use newCredential() for authentication**\n - When a node needs credentials, always use `newCredential('Name')` in the credentials config\n - NEVER use placeholder strings, fake API keys, or hardcoded auth values\n - Example: `credentials: { slackApi: newCredential('Slack Bot') }`\n - The credential type must match what the node expects\n\n2. **Trust empty item lists \u2014 don't synthesize fake items**\n - When a query returns 0 items, downstream nodes simply don't run for that execution. For scheduled or polling triggers this is the correct \"nothing to do this round\" signal \u2014 the next run will execute normally when data appears.\n - DO NOT add `alwaysOutputData: true` just to \"keep the chain alive.\" Forcing an empty `{}` item downstream is what causes `undefined` reads, failed HTTP calls to `GET undefined`, and Code-node crashes on missing fields.\n - DO NOT add an IF gate before a loop to check \"has items?\" \u2014 loops (`splitInBatches`, per-item nodes, `filter`) already no-op on empty input. The gate is redundant and adds a failure surface.\n - `alwaysOutputData: true` is only correct when you specifically need a downstream branch to run on the \"empty\" case \u2014 e.g. a dedicated \"no matches found\" notification path. In that case, pair it with an `IF` that explicitly checks for the empty case and routes accordingly. Never use it as a default.\n - To drop invalid items mid-pipeline, use a `filter` node. A `filter` that rejects everything emits 0 items and the chain correctly stops \u2014 no `IF` + `splitInBatches` composition needed.\n\n3. **Use `executeOnce: true` for single-execution nodes**\n - When a node receives N items but should only execute once (not N times), set `executeOnce: true`\n - Common cases: sending a summary notification, generating a report, calling an API that doesn't need per-item execution\n - Example: `config: { ..., executeOnce: true }`\n\n4. **Pick the right control-flow primitive**\n - **Per-item loop with side effects (fetch, embed, write)** \u2192 `splitInBatches` with `batchSize: 1` feeding the per-item work, loop back via `nextBatch`. No `IF` gate before it.\n - **Drop items that don't match a predicate** \u2192 `filter`. It emits 0 items when nothing matches, and the chain stops cleanly.\n - **Two mutually exclusive paths that both do real work** \u2192 `IF` (`onTrue` / `onFalse`).\n - **Many mutually exclusive paths keyed off a value** \u2192 `switch` (`onCase`).\n - Nested control flow is supported: `ifNode.onTrue(loopBuilder)`, `switchNode.onCase(0, loopBuilder)`, and `splitInBatches(sib).onEachBatch(ifElseBuilder)` all compile and wire correctly. Use them when the semantics genuinely call for it, not as a workaround for empty-list handling.";
|
|
@@ -9,15 +9,22 @@ exports.WORKFLOW_RULES = `Follow these rules strictly when generating workflows:
|
|
|
9
9
|
- Example: \`credentials: { slackApi: newCredential('Slack Bot') }\`
|
|
10
10
|
- The credential type must match what the node expects
|
|
11
11
|
|
|
12
|
-
2. **
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
12
|
+
2. **Trust empty item lists — don't synthesize fake items**
|
|
13
|
+
- When a query returns 0 items, downstream nodes simply don't run for that execution. For scheduled or polling triggers this is the correct "nothing to do this round" signal — the next run will execute normally when data appears.
|
|
14
|
+
- DO NOT add \`alwaysOutputData: true\` just to "keep the chain alive." Forcing an empty \`{}\` item downstream is what causes \`undefined\` reads, failed HTTP calls to \`GET undefined\`, and Code-node crashes on missing fields.
|
|
15
|
+
- DO NOT add an IF gate before a loop to check "has items?" — loops (\`splitInBatches\`, per-item nodes, \`filter\`) already no-op on empty input. The gate is redundant and adds a failure surface.
|
|
16
|
+
- \`alwaysOutputData: true\` is only correct when you specifically need a downstream branch to run on the "empty" case — e.g. a dedicated "no matches found" notification path. In that case, pair it with an \`IF\` that explicitly checks for the empty case and routes accordingly. Never use it as a default.
|
|
17
|
+
- To drop invalid items mid-pipeline, use a \`filter\` node. A \`filter\` that rejects everything emits 0 items and the chain correctly stops — no \`IF\` + \`splitInBatches\` composition needed.
|
|
18
18
|
|
|
19
19
|
3. **Use \`executeOnce: true\` for single-execution nodes**
|
|
20
20
|
- When a node receives N items but should only execute once (not N times), set \`executeOnce: true\`
|
|
21
21
|
- Common cases: sending a summary notification, generating a report, calling an API that doesn't need per-item execution
|
|
22
|
-
- Example: \`config: { ..., executeOnce: true }
|
|
22
|
+
- Example: \`config: { ..., executeOnce: true }\`
|
|
23
|
+
|
|
24
|
+
4. **Pick the right control-flow primitive**
|
|
25
|
+
- **Per-item loop with side effects (fetch, embed, write)** → \`splitInBatches\` with \`batchSize: 1\` feeding the per-item work, loop back via \`nextBatch\`. No \`IF\` gate before it.
|
|
26
|
+
- **Drop items that don't match a predicate** → \`filter\`. It emits 0 items when nothing matches, and the chain stops cleanly.
|
|
27
|
+
- **Two mutually exclusive paths that both do real work** → \`IF\` (\`onTrue\` / \`onFalse\`).
|
|
28
|
+
- **Many mutually exclusive paths keyed off a value** → \`switch\` (\`onCase\`).
|
|
29
|
+
- Nested control flow is supported: \`ifNode.onTrue(loopBuilder)\`, \`switchNode.onCase(0, loopBuilder)\`, and \`splitInBatches(sib).onEachBatch(ifElseBuilder)\` all compile and wire correctly. Use them when the semantics genuinely call for it, not as a workaround for empty-list handling.`;
|
|
23
30
|
//# sourceMappingURL=workflow-rules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-rules.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-rules.ts"],"names":[],"mappings":";;;AAQa,QAAA,cAAc,GAAG
|
|
1
|
+
{"version":3,"file":"workflow-rules.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-rules.ts"],"names":[],"mappings":";;;AAQa,QAAA,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;qSAyBuQ,CAAC"}
|
package/dist/types/base.d.ts
CHANGED
|
@@ -136,7 +136,7 @@ export interface WorkflowContext {
|
|
|
136
136
|
}
|
|
137
137
|
export interface NodeConfig<TParams = IDataObject> {
|
|
138
138
|
parameters?: TParams;
|
|
139
|
-
credentials?: Record<string, CredentialReference | NewCredentialValue>;
|
|
139
|
+
credentials?: Record<string, CredentialReference | NewCredentialValue | PlaceholderValue>;
|
|
140
140
|
name?: string;
|
|
141
141
|
position?: [number, number];
|
|
142
142
|
webhookId?: string;
|
|
@@ -236,8 +236,9 @@ export interface SwitchCaseComposite {
|
|
|
236
236
|
readonly switchNode: NodeInstance<'n8n-nodes-base.switch', string, unknown>;
|
|
237
237
|
readonly cases: Array<NodeInstance<string, string, unknown> | Array<NodeInstance<string, string, unknown>> | null>;
|
|
238
238
|
}
|
|
239
|
-
export type IfElseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown>;
|
|
240
|
-
export type SwitchCaseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown>;
|
|
239
|
+
export type IfElseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown> | SplitInBatchesBuilder<unknown>;
|
|
240
|
+
export type SwitchCaseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown> | SplitInBatchesBuilder<unknown>;
|
|
241
|
+
export type SplitInBatchesTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown> | SplitInBatchesBuilder<unknown>;
|
|
241
242
|
export interface IfElseBuilder<TOutput = unknown> {
|
|
242
243
|
readonly _isIfElseBuilder: true;
|
|
243
244
|
readonly ifNode: NodeInstance<'n8n-nodes-base.if', string, TOutput>;
|
|
@@ -266,8 +267,8 @@ export interface SplitInBatchesConfig extends NodeConfig {
|
|
|
266
267
|
}
|
|
267
268
|
export interface SplitInBatchesBuilder<TOutput = unknown> {
|
|
268
269
|
readonly sibNode: NodeInstance<'n8n-nodes-base.splitInBatches', string, unknown>;
|
|
269
|
-
onEachBatch(target:
|
|
270
|
-
onDone(target:
|
|
270
|
+
onEachBatch(target: SplitInBatchesTarget): SplitInBatchesBuilder<TOutput>;
|
|
271
|
+
onDone(target: SplitInBatchesTarget): SplitInBatchesBuilder<TOutput>;
|
|
271
272
|
}
|
|
272
273
|
export interface GeneratePinDataOptions {
|
|
273
274
|
beforeWorkflow?: WorkflowJSON;
|
|
@@ -80,15 +80,23 @@ function checkConditions(conditions, actualValues) {
|
|
|
80
80
|
return propertyValue >= from && propertyValue <= to;
|
|
81
81
|
}
|
|
82
82
|
if (key === 'includes') {
|
|
83
|
+
if (typeof propertyValue !== 'string')
|
|
84
|
+
return false;
|
|
83
85
|
return propertyValue.includes(targetValue);
|
|
84
86
|
}
|
|
85
87
|
if (key === 'startsWith') {
|
|
88
|
+
if (typeof propertyValue !== 'string')
|
|
89
|
+
return false;
|
|
86
90
|
return propertyValue.startsWith(targetValue);
|
|
87
91
|
}
|
|
88
92
|
if (key === 'endsWith') {
|
|
93
|
+
if (typeof propertyValue !== 'string')
|
|
94
|
+
return false;
|
|
89
95
|
return propertyValue.endsWith(targetValue);
|
|
90
96
|
}
|
|
91
97
|
if (key === 'regex') {
|
|
98
|
+
if (typeof propertyValue !== 'string')
|
|
99
|
+
return false;
|
|
92
100
|
return new RegExp(targetValue).test(propertyValue);
|
|
93
101
|
}
|
|
94
102
|
if (key === 'exists') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-options.js","sourceRoot":"","sources":["../../src/validation/display-options.ts"],"names":[],"mappings":";;;;;AA2JA,
|
|
1
|
+
{"version":3,"file":"display-options.js","sourceRoot":"","sources":["../../src/validation/display-options.ts"],"names":[],"mappings":";;;;;AA2JA,0CA4DC;AAeD,4CAqCC;AA2CD,sDAoEC;AAzWD,qDAA6B;AAC7B,6DAAqC;AA0DrC,SAAS,kBAAkB,CAAC,KAAc;IACzC,OAAO,CACN,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,MAAM,IAAI,KAAK;QACf,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAC/B,CAAC;AACH,CAAC;AAMD,SAAS,WAAW,CAAC,IAAY;IAChC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACjD,CAAC;AAMD,SAAS,WAAW,CAAC,GAAY,EAAE,MAAM,GAAG,EAAE;IAC7C,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAClE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/B,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAA8B,CAAC,EAAE,CAAC;QAC/D,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACpD,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAE,GAA+B,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAMD,SAAS,qBAAqB,CAAC,OAA8B,EAAE,SAAiB;IAC/E,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC;IAEhE,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAI3E,MAAM,YAAY,GAAG,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC;IACxF,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;IAEvC,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,cAAc,GAAgB,EAAE,CAAC;IAEvC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,IAAA,aAAG,EAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YACpC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACzB,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7D,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,cAAc,CAAC;AACvB,CAAC;AAkBD,SAAgB,eAAe,CAAC,UAAqB,EAAE,YAAuB;IAC7E,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;QACpC,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAG7D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/B,IAAI,GAAG,KAAK,KAAK;oBAAE,OAAO,IAAI,CAAC;gBAC/B,OAAO,KAAK,CAAC;YACd,CAAC;YAED,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,EAAE;gBAC3C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;oBAClB,OAAO,IAAA,iBAAO,EAAC,aAAa,EAAE,WAAW,CAAC,CAAC;gBAC5C,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBACnB,OAAO,CAAC,IAAA,iBAAO,EAAC,aAAa,EAAE,WAAW,CAAC,CAAC;gBAC7C,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBACnB,OAAQ,aAAwB,IAAK,WAAsB,CAAC;gBAC7D,CAAC;gBACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;oBACnB,OAAQ,aAAwB,IAAK,WAAsB,CAAC;gBAC7D,CAAC;gBACD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;oBAClB,OAAQ,aAAwB,GAAI,WAAsB,CAAC;gBAC5D,CAAC;gBACD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;oBAClB,OAAQ,aAAwB,GAAI,WAAsB,CAAC;gBAC5D,CAAC;gBACD,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACvB,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,WAA2C,CAAC;oBACjE,OAAQ,aAAwB,IAAI,IAAI,IAAK,aAAwB,IAAI,EAAE,CAAC;gBAC7E,CAAC;gBACD,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;oBACxB,IAAI,OAAO,aAAa,KAAK,QAAQ;wBAAE,OAAO,KAAK,CAAC;oBACpD,OAAO,aAAa,CAAC,QAAQ,CAAC,WAAqB,CAAC,CAAC;gBACtD,CAAC;gBACD,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;oBAC1B,IAAI,OAAO,aAAa,KAAK,QAAQ;wBAAE,OAAO,KAAK,CAAC;oBACpD,OAAO,aAAa,CAAC,UAAU,CAAC,WAAqB,CAAC,CAAC;gBACxD,CAAC;gBACD,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;oBACxB,IAAI,OAAO,aAAa,KAAK,QAAQ;wBAAE,OAAO,KAAK,CAAC;oBACpD,OAAO,aAAa,CAAC,QAAQ,CAAC,WAAqB,CAAC,CAAC;gBACtD,CAAC;gBACD,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;oBACrB,IAAI,OAAO,aAAa,KAAK,QAAQ;wBAAE,OAAO,KAAK,CAAC;oBACpD,OAAO,IAAI,MAAM,CAAC,WAAqB,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;gBAC9D,CAAC;gBACD,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;oBACtB,OAAO,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,EAAE,CAAC;gBACtF,CAAC;gBACD,OAAO,KAAK,CAAC;YACd,CAAC,CAAC,CAAC;QACJ,CAAC;QAGD,OAAO,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACJ,CAAC;AAeD,SAAgB,gBAAgB,CAAC,OAA8B,EAAE,YAAoB;IACpF,IAAI,KAAc,CAAC;IAEnB,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QAEpC,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC;QAChE,KAAK,GAAG,IAAA,aAAG,EAAC,UAAU,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAE/C,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YAC7C,KAAK,GAAG,IAAA,aAAG,EAAC,OAAO,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;IACF,CAAC;SAAM,IAAI,YAAY,KAAK,UAAU,EAAE,CAAC;QACxC,KAAK,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;IAClC,CAAC;SAAM,IAAI,YAAY,KAAK,OAAO,EAAE,CAAC;QACrC,KAAK,GAAG,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC;IACrC,CAAC;SAAM,CAAC;QAEP,KAAK,GAAG,IAAA,aAAG,EAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAE9C,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YAC7C,KAAK,GAAG,IAAA,aAAG,EAAC,OAAO,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC;IAGD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,KAAgC,CAAC;QACjD,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;YACjD,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACvB,CAAC;IACF,CAAC;IAGD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAMD,SAAS,mBAAmB,CAAC,OAA8B,EAAE,YAAoB;IAChF,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC;QAChE,OAAO,IAAA,aAAG,EAAC,UAAU,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,YAAY,KAAK,UAAU,IAAI,YAAY,KAAK,OAAO,EAAE,CAAC;QAC7D,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,IAAA,aAAG,EAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAC9C,CAAC;AAMD,SAAS,2BAA2B,CAAC,KAAc;IAClD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5E,MAAM,EAAE,GAAG,KAAgC,CAAC;IAC5C,OAAO,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;AAC5C,CAAC;AAkBD,SAAgB,qBAAqB,CACpC,OAA8B,EAC9B,cAA8B;IAE9B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,cAAc,CAAC;IAEtC,IAAI,IAAI,EAAE,CAAC;QAEV,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;YAGtC,IAAI,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC/B,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;gBAGxE,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACrC,OAAO,KAAK,CAAC;gBACd,CAAC;gBAGD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;oBACnD,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;wBACtE,OAAO,IAAI,CAAC;oBACb,CAAC;oBACD,OAAO,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;gBAC5C,CAAC,CAAC,CAAC;gBAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACf,OAAO,KAAK,CAAC;gBACd,CAAC;gBACD,SAAS;YACV,CAAC;YAGD,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAGvD,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;gBACtE,OAAO,IAAI,CAAC;YACb,CAAC;YAED,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC1C,OAAO,KAAK,CAAC;YACd,CAAC;QACF,CAAC;IACF,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QAEV,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAI9C,IAAI,2BAA2B,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;gBAC7E,SAAS;YACV,CAAC;YAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAGvD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;gBACxE,OAAO,KAAK,CAAC;YACd,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { INodeTypes } from 'n8n-workflow';
|
|
2
2
|
import type { WorkflowBuilder, WorkflowJSON } from '../types/base';
|
|
3
3
|
export { setSchemaBaseDirs } from './schema-validator';
|
|
4
|
-
export type ValidationErrorCode = 'NO_NODES' | 'MISSING_TRIGGER' | 'DISCONNECTED_NODE' | 'MISSING_PARAMETER' | 'INVALID_CONNECTION' | 'CIRCULAR_REFERENCE' | 'INVALID_EXPRESSION' | 'AGENT_STATIC_PROMPT' | 'AGENT_NO_SYSTEM_MESSAGE' | 'HARDCODED_CREDENTIALS' | 'SET_CREDENTIAL_FIELD' | 'MERGE_SINGLE_INPUT' | 'TOOL_NO_PARAMETERS' | 'FROM_AI_IN_NON_TOOL' | 'MISSING_EXPRESSION_PREFIX' | 'INVALID_PARAMETER' | 'INVALID_INPUT_INDEX' | 'SUBNODE_NOT_CONNECTED' | 'SUBNODE_PARAMETER_MISMATCH' | 'UNSUPPORTED_SUBNODE_INPUT' | 'MISSING_REQUIRED_INPUT' | 'MAX_NODES_EXCEEDED' | 'INVALID_EXPRESSION_PATH' | 'PARTIAL_EXPRESSION_PATH' | 'INVALID_DATE_METHOD';
|
|
4
|
+
export type ValidationErrorCode = 'NO_NODES' | 'MISSING_TRIGGER' | 'DISCONNECTED_NODE' | 'MISSING_PARAMETER' | 'INVALID_CONNECTION' | 'CIRCULAR_REFERENCE' | 'INVALID_EXPRESSION' | 'AGENT_STATIC_PROMPT' | 'AGENT_NO_SYSTEM_MESSAGE' | 'HARDCODED_CREDENTIALS' | 'SET_CREDENTIAL_FIELD' | 'MERGE_SINGLE_INPUT' | 'TOOL_NO_PARAMETERS' | 'FROM_AI_IN_NON_TOOL' | 'MISSING_EXPRESSION_PREFIX' | 'INVALID_PARAMETER' | 'INVALID_INPUT_INDEX' | 'SUBNODE_NOT_CONNECTED' | 'SUBNODE_PARAMETER_MISMATCH' | 'UNSUPPORTED_SUBNODE_INPUT' | 'MISSING_REQUIRED_INPUT' | 'INVALID_OUTPUT_FOR_MODE' | 'MAX_NODES_EXCEEDED' | 'INVALID_EXPRESSION_PATH' | 'PARTIAL_EXPRESSION_PATH' | 'INVALID_DATE_METHOD';
|
|
5
5
|
export declare class ValidationError {
|
|
6
6
|
readonly code: ValidationErrorCode;
|
|
7
7
|
readonly message: string;
|