@n8n/workflow-sdk 0.12.0 → 0.14.0
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 -11
- 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 +2 -1
- package/dist/generate-types/generate-types.js +137 -45
- package/dist/generate-types/generate-types.js.map +1 -1
- package/dist/generate-types/generate-zod-schemas.js +95 -102
- package/dist/generate-types/generate-zod-schemas.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/prompts/best-practices/guides/chatbot.js +13 -2
- package/dist/prompts/best-practices/guides/chatbot.js.map +1 -1
- package/dist/prompts/node-guidance/index.d.ts +0 -1
- package/dist/prompts/node-guidance/index.js +0 -1
- package/dist/prompts/node-guidance/index.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/node-guidance/parameter-guides/set-node.js +14 -15
- package/dist/prompts/node-guidance/parameter-guides/set-node.js.map +1 -1
- package/dist/prompts/node-selection/ai-nodes.d.ts +2 -2
- package/dist/prompts/node-selection/ai-nodes.js +10 -10
- package/dist/prompts/node-selection/ai-nodes.js.map +1 -1
- package/dist/prompts/node-selection/connection-parameters.d.ts +1 -1
- package/dist/prompts/node-selection/connection-parameters.js +2 -2
- package/dist/prompts/sdk-reference/additional-functions.d.ts +1 -1
- package/dist/prompts/sdk-reference/additional-functions.js +5 -2
- 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-detailed.d.ts +1 -1
- package/dist/prompts/sdk-reference/workflow-patterns-detailed.js +9 -7
- package/dist/prompts/sdk-reference/workflow-patterns-detailed.js.map +1 -1
- package/dist/prompts/sdk-reference/workflow-patterns.d.ts +1 -1
- package/dist/prompts/sdk-reference/workflow-patterns.js +114 -44
- 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 +24 -7
- package/dist/prompts/sdk-reference/workflow-rules.js.map +1 -1
- package/dist/types/base.d.ts +9 -10
- package/dist/types/base.js.map +1 -1
- package/dist/validation/display-options.js +8 -0
- package/dist/validation/display-options.js.map +1 -1
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +112 -6
- 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 +89 -11
- package/dist/validation/schema-helpers.js +20 -5
- 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/validation.d.ts +1 -0
- package/dist/validation.js +3 -1
- package/dist/validation.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/layout-utils.d.ts +1 -2
- package/dist/workflow-builder/layout-utils.js +21 -58
- package/dist/workflow-builder/layout-utils.js.map +1 -1
- package/dist/workflow-builder/node-builders/node-builder.d.ts +3 -2
- package/dist/workflow-builder/node-builders/node-builder.js +65 -14
- 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 +2 -3
- package/dist/workflow-builder/pin-data-utils.js.map +1 -1
- package/dist/workflow-builder/plugins/composite-handlers/branch-handler-utils.js +23 -2
- package/dist/workflow-builder/plugins/composite-handlers/branch-handler-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 +18 -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/merge-node-validator.js +1 -1
- package/dist/workflow-builder/plugins/validators/merge-node-validator.js.map +1 -1
- 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/dist/workflow-builder/string-utils.d.ts +2 -0
- package/dist/workflow-builder/string-utils.js +12 -0
- package/dist/workflow-builder/string-utils.js.map +1 -1
- package/dist/workflow-builder/validation-helpers.js +5 -6
- package/dist/workflow-builder/validation-helpers.js.map +1 -1
- package/dist/workflow-builder.js +14 -0
- package/dist/workflow-builder.js.map +1 -1
- package/package.json +4 -10
- package/dist/prompts/node-guidance/node-tips/index.d.ts +0 -3
- package/dist/prompts/node-guidance/node-tips/index.js +0 -8
- package/dist/prompts/node-guidance/node-tips/index.js.map +0 -1
- package/dist/prompts/node-guidance/node-tips/structured-output-parser.d.ts +0 -2
- package/dist/prompts/node-guidance/node-tips/structured-output-parser.js +0 -35
- package/dist/prompts/node-guidance/node-tips/structured-output-parser.js.map +0 -1
- package/dist/prompts/node-guidance/node-tips/types.d.ts +0 -7
- package/dist/prompts/node-guidance/node-tips/types.js +0 -3
- package/dist/prompts/node-guidance/node-tips/types.js.map +0 -1
- package/dist/prompts/node-guidance/node-tips/webhook.d.ts +0 -2
- package/dist/prompts/node-guidance/node-tips/webhook.js +0 -38
- package/dist/prompts/node-guidance/node-tips/webhook.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatbot.js","sourceRoot":"","sources":["../../../../src/prompts/best-practices/guides/chatbot.ts"],"names":[],"mappings":";;;AACA,oCAA6C;AAE7C,MAAa,oBAAoB;IAAjC;QACU,cAAS,GAAG,yBAAiB,CAAC,OAAO,CAAC;QACtC,YAAO,GAAG,OAAO,CAAC;QAEV,kBAAa,GAAG
|
|
1
|
+
{"version":3,"file":"chatbot.js","sourceRoot":"","sources":["../../../../src/prompts/best-practices/guides/chatbot.ts"],"names":[],"mappings":";;;AACA,oCAA6C;AAE7C,MAAa,oBAAoB;IAAjC;QACU,cAAS,GAAG,yBAAiB,CAAC,OAAO,CAAC;QACtC,YAAO,GAAG,OAAO,CAAC;QAEV,kBAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmHjC,CAAC;IAKF,CAAC;IAHA,gBAAgB;QACf,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;CACD;AA5HD,oDA4HC"}
|
|
@@ -15,6 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./parameter-guides"), exports);
|
|
18
|
-
__exportStar(require("./node-tips"), exports);
|
|
19
18
|
__exportStar(require("./node-recommendations"), exports);
|
|
20
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/prompts/node-guidance/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/prompts/node-guidance/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,yDAAuC"}
|
|
@@ -13,6 +13,13 @@ AI Agent nodes (n8n-nodes-langchain.agent) wrap their response in an "output" ob
|
|
|
13
13
|
- Use \`$('AI Agent').item.json.output.fieldName\` when referencing a node, instead of \`$('AI Agent').item.json.fieldName\`
|
|
14
14
|
- WRONG: \`$json.summary\` → CORRECT: \`$json.output.summary\`
|
|
15
15
|
|
|
16
|
+
#### AI Agent Subnode Input Context
|
|
17
|
+
AI Agent subnodes (memory, language models, tools, parsers, retrievers, and vector stores) are connected through AI connections, not the normal main data path.
|
|
18
|
+
- For memory custom session keys, do NOT use \`$json.chatId\` or \`$json.sessionId\`; reference the trigger/source node explicitly.
|
|
19
|
+
- Use \`nodeJson(triggerNode, 'message.chat.id')\` or \`$('Trigger Node').item.json.message.chat.id\`.
|
|
20
|
+
- For tool parameters controlled by the agent, use \`$fromAI(...)\` instead of upstream JSON.
|
|
21
|
+
- The built-in Chat Trigger memory shortcut is \`sessionIdType: 'fromInput'\`, where no custom session key expression is needed.
|
|
22
|
+
|
|
16
23
|
#### Webhook Node Output Structure
|
|
17
24
|
When referencing data from a Webhook node (n8n-nodes-base.webhook), the incoming request is structured under \`$json\`:
|
|
18
25
|
- \`$json.headers\` - HTTP headers, example: \`$json.headers.authorization\`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"predecessor-output.js","sourceRoot":"","sources":["../../../../src/prompts/node-guidance/parameter-guides/predecessor-output.ts"],"names":[],"mappings":";;;AASa,QAAA,wBAAwB,GAAkB;IACtD,QAAQ,EAAE,CAAC,GAAG,CAAC;IACf,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"predecessor-output.js","sourceRoot":"","sources":["../../../../src/prompts/node-guidance/parameter-guides/predecessor-output.ts"],"names":[],"mappings":";;;AASa,QAAA,wBAAwB,GAAkB;IACtD,QAAQ,EAAE,CAAC,GAAG,CAAC;IACf,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;+JAwBqJ;CAC9J,CAAC"}
|
|
@@ -51,22 +51,21 @@ The Set node uses assignments to create or modify data fields. Each assignment h
|
|
|
51
51
|
- **Use when**: Flags, toggles, yes/no values, active/inactive states
|
|
52
52
|
|
|
53
53
|
##### Array Type
|
|
54
|
-
- **Format**:
|
|
54
|
+
- **Format**: Expression or literal that evaluates to an array
|
|
55
55
|
- **Examples**:
|
|
56
|
-
- Simple array: \`
|
|
57
|
-
- String array: \`
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
- **CRITICAL**: Arrays must be JSON stringified
|
|
56
|
+
- Simple array: \`[1, 2, 3]\`
|
|
57
|
+
- String array: \`["apple", "banana", "orange"]\`
|
|
58
|
+
- Expression: \`"={{ $('Previous Node').item.json.items }}"\`
|
|
59
|
+
- **CRITICAL**: Do not use \`JSON.stringify()\` unless you intentionally want a string field
|
|
61
60
|
- **Use when**: Lists, collections, multiple values
|
|
62
61
|
|
|
63
62
|
##### Object Type
|
|
64
|
-
- **Format**:
|
|
63
|
+
- **Format**: Expression or literal that evaluates to a plain object
|
|
65
64
|
- **Examples**:
|
|
66
|
-
- Simple object: \`
|
|
67
|
-
- Nested object: \`
|
|
68
|
-
- Expression: \`"={{
|
|
69
|
-
- **CRITICAL**:
|
|
65
|
+
- Simple object: \`{ name: "John", age: 30 }\`
|
|
66
|
+
- Nested object: \`{ user: { id: 123, role: "admin" } }\`
|
|
67
|
+
- Expression: \`"={{ $('Set').item.json.userData }}"\`
|
|
68
|
+
- **CRITICAL**: Do not use \`JSON.stringify()\` unless you intentionally want a string field
|
|
70
69
|
- **Use when**: Complex data structures, grouped properties
|
|
71
70
|
|
|
72
71
|
#### Important Type Selection Rules
|
|
@@ -75,11 +74,11 @@ The Set node uses assignments to create or modify data fields. Each assignment h
|
|
|
75
74
|
- "Set count to 5" → number type with value: \`5\`
|
|
76
75
|
- "Set message to hello" → string type with value: \`"hello"\`
|
|
77
76
|
- "Set active to true" → boolean type with value: \`true\`
|
|
78
|
-
- "Set tags to apple, banana" → array type with value: \`
|
|
77
|
+
- "Set tags to apple, banana" → array type with value: \`["apple", "banana"]\`
|
|
79
78
|
|
|
80
79
|
2. **Expression handling**:
|
|
81
80
|
- All types can use expressions with \`"={{ ... }}"\`
|
|
82
|
-
- For arrays/objects from expressions,
|
|
81
|
+
- For arrays/objects from expressions, return the array/object directly
|
|
83
82
|
|
|
84
83
|
3. **Common mistakes to avoid**:
|
|
85
84
|
- WRONG: Setting number as string: \`{ "value": "123", "type": "number" }\`
|
|
@@ -88,7 +87,7 @@ The Set node uses assignments to create or modify data fields. Each assignment h
|
|
|
88
87
|
- CORRECT: \`{ "value": false, "type": "boolean" }\`
|
|
89
88
|
- WRONG: Using type-specific field names: \`{ "booleanValue": true, "type": "boolean" }\`
|
|
90
89
|
- CORRECT: \`{ "value": true, "type": "boolean" }\`
|
|
91
|
-
- WRONG:
|
|
92
|
-
- CORRECT: \`{ "value": "
|
|
90
|
+
- WRONG: Stringifying an array: \`{ "value": "={{ JSON.stringify($json.items) }}", "type": "array" }\`
|
|
91
|
+
- CORRECT: \`{ "value": "={{ $json.items }}", "type": "array" }\``,
|
|
93
92
|
};
|
|
94
93
|
//# sourceMappingURL=set-node.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set-node.js","sourceRoot":"","sources":["../../../../src/prompts/node-guidance/parameter-guides/set-node.ts"],"names":[],"mappings":";;;AAEa,QAAA,cAAc,GAAkB;IAC5C,QAAQ,EAAE,CAAC,oBAAoB,CAAC;IAChC,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"set-node.js","sourceRoot":"","sources":["../../../../src/prompts/node-guidance/parameter-guides/set-node.ts"],"names":[],"mappings":";;;AAEa,QAAA,cAAc,GAAkB;IAC5C,QAAQ,EAAE,CAAC,oBAAoB,CAAC;IAChC,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qEAqF2D;CACpE,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const AI_NODE_SELECTION = "AI node selection guidance:\n\nAI Agent: Use for text analysis, summarization, classification, or any AI reasoning tasks.\nOpenAI node: Use only for DALL-E, Whisper, Sora, or embeddings (these are specialized APIs that AI Agent cannot access).\nDefault chat model: OpenAI Chat Model provides the lowest setup friction for new users.\nTool nodes (ending in \"Tool\"): Connect to AI Agent via
|
|
2
|
-
export declare const AI_TOOL_PATTERNS = "AI Agent tool connection patterns:\n\nWhen AI Agent needs external capabilities, use TOOL nodes (not regular nodes):\n- Research: SerpAPI Tool, Perplexity Tool -> AI Agent [
|
|
1
|
+
export declare const AI_NODE_SELECTION = "AI node selection guidance:\n\nAI Agent: Use for text analysis, summarization, classification, or any AI reasoning tasks.\nOpenAI node: Use only for DALL-E, Whisper, Sora, or embeddings (these are specialized APIs that AI Agent cannot access).\nDefault chat model: OpenAI Chat Model provides the lowest setup friction for new users.\nTool nodes (ending in \"Tool\"): Connect to AI Agent via tool() for agent-controlled actions.\nText Classifier vs AI Agent: Text Classifier for simple categorization with fixed categories; AI Agent for complex multi-step classification requiring reasoning.\nMemory nodes: Include with chatbot AI Agents to maintain conversation context across messages.\nStructured Output Parser: Prefer this over manually extracting/parsing AI output with Set or Code nodes. Define the desired schema and the LLM handles parsing automatically. Use for classification, data extraction, or any workflow where AI output feeds into database storage, API calls, or Switch routing.\n\nMulti-agent systems:\nAI Agent Tool (@n8n/n8n-nodes-langchain.agentTool) contains an embedded AI Agent \u2014 it's a complete sub-agent that the main agent can call through tool(). Each AgentTool needs its own Chat Model. Node selection: 1 AI Agent + N AgentTools + (N+1) Chat Models.";
|
|
2
|
+
export declare const AI_TOOL_PATTERNS = "AI Agent tool connection patterns:\n\nWhen AI Agent needs external capabilities, use TOOL nodes (not regular nodes):\n- Research: SerpAPI Tool, Perplexity Tool -> AI Agent [tool()]\n- Calendar: Google Calendar Tool -> AI Agent [tool()]\n- Messaging: Slack Tool, Gmail Tool -> AI Agent [tool()]\n- HTTP calls: HTTP Request Tool -> AI Agent [tool()]\n- Calculations: Calculator Tool -> AI Agent [tool()]\n- Sub-agents: AI Agent Tool -> AI Agent [tool()] (for multi-agent systems)\n\nTool nodes: AI Agent decides when/if to use them based on reasoning.\nRegular nodes: Execute at that workflow step regardless of context.\n\nVector Store patterns:\n- Insert documents: Document Loader -> Vector Store (mode='insert') [documentLoader()]\n- RAG with AI Agent: Vector Store (mode='retrieve-as-tool') -> AI Agent [tool()]\n The retrieve-as-tool mode makes the Vector Store act as a tool the Agent can call.\n\nStructured Output Parser: Connect to AI Agent when structured JSON output is required.";
|
|
@@ -6,29 +6,29 @@ exports.AI_NODE_SELECTION = `AI node selection guidance:
|
|
|
6
6
|
AI Agent: Use for text analysis, summarization, classification, or any AI reasoning tasks.
|
|
7
7
|
OpenAI node: Use only for DALL-E, Whisper, Sora, or embeddings (these are specialized APIs that AI Agent cannot access).
|
|
8
8
|
Default chat model: OpenAI Chat Model provides the lowest setup friction for new users.
|
|
9
|
-
Tool nodes (ending in "Tool"): Connect to AI Agent via
|
|
9
|
+
Tool nodes (ending in "Tool"): Connect to AI Agent via tool() for agent-controlled actions.
|
|
10
10
|
Text Classifier vs AI Agent: Text Classifier for simple categorization with fixed categories; AI Agent for complex multi-step classification requiring reasoning.
|
|
11
11
|
Memory nodes: Include with chatbot AI Agents to maintain conversation context across messages.
|
|
12
12
|
Structured Output Parser: Prefer this over manually extracting/parsing AI output with Set or Code nodes. Define the desired schema and the LLM handles parsing automatically. Use for classification, data extraction, or any workflow where AI output feeds into database storage, API calls, or Switch routing.
|
|
13
13
|
|
|
14
14
|
Multi-agent systems:
|
|
15
|
-
AI Agent Tool (@n8n/n8n-nodes-langchain.agentTool) contains an embedded AI Agent — it's a complete sub-agent that the main agent can call through
|
|
15
|
+
AI Agent Tool (@n8n/n8n-nodes-langchain.agentTool) contains an embedded AI Agent — it's a complete sub-agent that the main agent can call through tool(). Each AgentTool needs its own Chat Model. Node selection: 1 AI Agent + N AgentTools + (N+1) Chat Models.`;
|
|
16
16
|
exports.AI_TOOL_PATTERNS = `AI Agent tool connection patterns:
|
|
17
17
|
|
|
18
18
|
When AI Agent needs external capabilities, use TOOL nodes (not regular nodes):
|
|
19
|
-
- Research: SerpAPI Tool, Perplexity Tool -> AI Agent [
|
|
20
|
-
- Calendar: Google Calendar Tool -> AI Agent [
|
|
21
|
-
- Messaging: Slack Tool, Gmail Tool -> AI Agent [
|
|
22
|
-
- HTTP calls: HTTP Request Tool -> AI Agent [
|
|
23
|
-
- Calculations: Calculator Tool -> AI Agent [
|
|
24
|
-
- Sub-agents: AI Agent Tool -> AI Agent [
|
|
19
|
+
- Research: SerpAPI Tool, Perplexity Tool -> AI Agent [tool()]
|
|
20
|
+
- Calendar: Google Calendar Tool -> AI Agent [tool()]
|
|
21
|
+
- Messaging: Slack Tool, Gmail Tool -> AI Agent [tool()]
|
|
22
|
+
- HTTP calls: HTTP Request Tool -> AI Agent [tool()]
|
|
23
|
+
- Calculations: Calculator Tool -> AI Agent [tool()]
|
|
24
|
+
- Sub-agents: AI Agent Tool -> AI Agent [tool()] (for multi-agent systems)
|
|
25
25
|
|
|
26
26
|
Tool nodes: AI Agent decides when/if to use them based on reasoning.
|
|
27
27
|
Regular nodes: Execute at that workflow step regardless of context.
|
|
28
28
|
|
|
29
29
|
Vector Store patterns:
|
|
30
|
-
- Insert documents: Document Loader -> Vector Store (mode='insert') [
|
|
31
|
-
- RAG with AI Agent: Vector Store (mode='retrieve-as-tool') -> AI Agent [
|
|
30
|
+
- Insert documents: Document Loader -> Vector Store (mode='insert') [documentLoader()]
|
|
31
|
+
- RAG with AI Agent: Vector Store (mode='retrieve-as-tool') -> AI Agent [tool()]
|
|
32
32
|
The retrieve-as-tool mode makes the Vector Store act as a tool the Agent can call.
|
|
33
33
|
|
|
34
34
|
Structured Output Parser: Connect to AI Agent when structured JSON output is required.`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-nodes.js","sourceRoot":"","sources":["../../../src/prompts/node-selection/ai-nodes.ts"],"names":[],"mappings":";;;AAAa,QAAA,iBAAiB,GAAG;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"ai-nodes.js","sourceRoot":"","sources":["../../../src/prompts/node-selection/ai-nodes.ts"],"names":[],"mappings":";;;AAAa,QAAA,iBAAiB,GAAG;;;;;;;;;;;kQAWiO,CAAC;AAEtP,QAAA,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;uFAkBuD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CONNECTION_CHANGING_PARAMETERS = "Connection-changing parameters (affect node inputs/outputs):\n\nCommon connection-changing parameters:\n- Vector Store: mode (insert/retrieve/retrieve-as-tool) \u2014 changes output type between main,
|
|
1
|
+
export declare const CONNECTION_CHANGING_PARAMETERS = "Connection-changing parameters (affect node inputs/outputs):\n\nCommon connection-changing parameters:\n- Vector Store: mode (insert/retrieve/retrieve-as-tool) \u2014 changes output type between main, vectorStore(), and tool()\n- AI Agent: hasOutputParser (true/false) \u2014 enables outputParser() input\n- Merge: numberInputs (default 2) \u2014 requires mode=\"append\" OR mode=\"combine\" + combineBy=\"combineByPosition\"\n- Switch: mode (expression/rules) \u2014 affects routing behavior\n\nA parameter is connection-changing if it appears in node input/output expressions (patterns like $parameter.mode, $parameter.hasOutputParser).";
|
|
@@ -4,8 +4,8 @@ exports.CONNECTION_CHANGING_PARAMETERS = void 0;
|
|
|
4
4
|
exports.CONNECTION_CHANGING_PARAMETERS = `Connection-changing parameters (affect node inputs/outputs):
|
|
5
5
|
|
|
6
6
|
Common connection-changing parameters:
|
|
7
|
-
- Vector Store: mode (insert/retrieve/retrieve-as-tool) — changes output type between main,
|
|
8
|
-
- AI Agent: hasOutputParser (true/false) — enables
|
|
7
|
+
- Vector Store: mode (insert/retrieve/retrieve-as-tool) — changes output type between main, vectorStore(), and tool()
|
|
8
|
+
- AI Agent: hasOutputParser (true/false) — enables outputParser() input
|
|
9
9
|
- Merge: numberInputs (default 2) — requires mode="append" OR mode="combine" + combineBy="combineByPosition"
|
|
10
10
|
- Switch: mode (expression/rules) — affects routing behavior
|
|
11
11
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const ADDITIONAL_FUNCTIONS = "Additional SDK functions:\n\n- `placeholder('hint')` \u2014 marks a parameter value for user input. Use directly as the parameter value \u2014 never wrap in `expr()`, objects, or arrays.\n Example: `parameters: { url: placeholder('Your API URL (e.g. https://api.example.com/v1)') }`\n\n- `sticky('content', nodes?, config?)` \u2014 creates a sticky note on the canvas.\n Example: `sticky('## Data Processing', [httpNode, setNode], { color: 2 })`\n\n- `.output(n)` \u2014 selects a specific output index for multi-output nodes. IF and Switch have dedicated methods (`onTrue/onFalse`, `onCase`), but `.output(n)` works as a generic alternative.\n Example: `classifier.output(1).to(categoryB)`\n\n- `.onError(handler)` \u2014 connects a node's error output to a handler node. Requires `onError: 'continueErrorOutput'` in the node config.\n Example: `httpNode.onError(errorHandler)` (with `config: { onError: 'continueErrorOutput' }`)\n\n- Additional subnode factories (all follow the same pattern as `languageModel()` and `tool()`):\n `memory()`, `outputParser()`, `embeddings()`, `vectorStore()`, `retriever()`, `documentLoader()`, `textSplitter()`";
|
|
1
|
+
export declare const ADDITIONAL_FUNCTIONS = "Additional SDK functions:\n\n- `placeholder('hint')` \u2014 marks a parameter value for user input. Use directly as the parameter value \u2014 never wrap in `expr()`, objects, or arrays.\n Example: `parameters: { url: placeholder('Your API URL (e.g. https://api.example.com/v1)') }`\n\n- `sticky('content', nodes?, config?)` \u2014 creates a sticky note on the canvas.\n Example: `sticky('## Data Processing', [httpNode, setNode], { color: 2 })`\n\n- `.output(n)` \u2014 selects a specific output index for multi-output nodes. The index is **0-based**: `.output(0)` is the first output, `.output(1)` is the second. IF and Switch have dedicated methods (`onTrue/onFalse`, `onCase`), but `.output(n)` works as a generic alternative.\n Example: `classifier.output(0).to(categoryA); classifier.output(1).to(categoryB)`\n\n- `.onError(handler)` \u2014 connects a node's error output to a handler node. Requires `onError: 'continueErrorOutput'` in the node config.\n Example: `httpNode.onError(errorHandler)` (with `config: { onError: 'continueErrorOutput' }`)\n\n- `nodeJson(node, 'field.path')` \u2014 creates an explicit expression reference to JSON data from a specific node. Use this instead of `$json` in AI Agent subnodes, fan-in nodes, or when reading further upstream data.\n Example: `sessionKey: nodeJson(telegramTrigger, 'message.chat.id')`\n\n- Additional subnode factories (all follow the same pattern as `languageModel()` and `tool()`):\n `memory()`, `outputParser()`, `embeddings()`, `vectorStore()`, `retriever()`, `documentLoader()`, `textSplitter()`";
|
|
@@ -9,12 +9,15 @@ exports.ADDITIONAL_FUNCTIONS = `Additional SDK functions:
|
|
|
9
9
|
- \`sticky('content', nodes?, config?)\` — creates a sticky note on the canvas.
|
|
10
10
|
Example: \`sticky('## Data Processing', [httpNode, setNode], { color: 2 })\`
|
|
11
11
|
|
|
12
|
-
- \`.output(n)\` — selects a specific output index for multi-output nodes. IF and Switch have dedicated methods (\`onTrue/onFalse\`, \`onCase\`), but \`.output(n)\` works as a generic alternative.
|
|
13
|
-
Example: \`classifier.output(1).to(categoryB)\`
|
|
12
|
+
- \`.output(n)\` — selects a specific output index for multi-output nodes. The index is **0-based**: \`.output(0)\` is the first output, \`.output(1)\` is the second. IF and Switch have dedicated methods (\`onTrue/onFalse\`, \`onCase\`), but \`.output(n)\` works as a generic alternative.
|
|
13
|
+
Example: \`classifier.output(0).to(categoryA); classifier.output(1).to(categoryB)\`
|
|
14
14
|
|
|
15
15
|
- \`.onError(handler)\` — connects a node's error output to a handler node. Requires \`onError: 'continueErrorOutput'\` in the node config.
|
|
16
16
|
Example: \`httpNode.onError(errorHandler)\` (with \`config: { onError: 'continueErrorOutput' }\`)
|
|
17
17
|
|
|
18
|
+
- \`nodeJson(node, 'field.path')\` — creates an explicit expression reference to JSON data from a specific node. Use this instead of \`$json\` in AI Agent subnodes, fan-in nodes, or when reading further upstream data.
|
|
19
|
+
Example: \`sessionKey: nodeJson(telegramTrigger, 'message.chat.id')\`
|
|
20
|
+
|
|
18
21
|
- Additional subnode factories (all follow the same pattern as \`languageModel()\` and \`tool()\`):
|
|
19
22
|
\`memory()\`, \`outputParser()\`, \`embeddings()\`, \`vectorStore()\`, \`retriever()\`, \`documentLoader()\`, \`textSplitter()\``;
|
|
20
23
|
//# sourceMappingURL=additional-functions.js.map
|
|
@@ -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 equivalent to `expr('{{ $(\"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 equivalent to \`expr('{{ $("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 +1 @@
|
|
|
1
|
-
export declare const WORKFLOW_PATTERNS_DETAILED = "<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, reranker, 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', position: [240, 300] },\n output: [{}]\n});\n\nconst fetchData = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Data', parameters: { method: 'GET', url: '...' }, position: [540, 300] },\n output: [{ id: 1, title: 'Item 1' }]\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Data', parameters: {}, position: [840, 300] },\n output: [{ id: 1, title: 'Item 1', processed: true }]\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\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// sourceB runs once per item from sourceA.\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)\n// Pairs items by index, merging fields from both inputs into one item.\n// @example input0: [{ a: 1 }, { a: 2 }] input1: [{ b: 10, c: 'x' }, { b: 20 }]\n// output: [{ a: 1, b: 10, c: 'x' }, { a: 2, b: 20, c: undefined }]\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)\n// Concatenates all items from all inputs into one list sequentially.\n// @example input0: [{ a: 1 }, { a: 2 }] input1: [{ b: 10 }]\n// output: [{ a: 1 }, { a: 2 }, { b: 10 }]\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<conditional_branching>\n\n**CRITICAL:** Each branch defines a COMPLETE processing path. Chain multiple steps INSIDE the branch using .to().\n\n```javascript\n// Assume other nodes are declared\nconst checkValid = ifElse({ version: 2.2, config: { name: 'Check Valid', parameters: {...} } });\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\n```javascript\n// Assume other nodes are declared\nconst routeByPriority = switchCase({ version: 3.2, config: { name: 'Route by Priority', parameters: {...} } });\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(routeByPriority\n .onCase(0, processUrgent.to(notifyTeam.to(escalate))) // Chain of 3 nodes\n .onCase(1, processNormal)\n .onCase(2, archive));\n```\n\n</multi_way_routing>\n\n<parallel_execution>\n```javascript\nconst combineResults = merge({\n version: 3.2,\n config: {\n name: 'Combine Results',\n parameters: { mode: 'combine' },\n position: [840, 300]\n }\n});\n\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)))\n .add(trigger({ ... }))\n .to(branch2.to(combineResults.input(1)))\n .add(combineResults)\n .to(processResults);\n```\n\n</parallel_execution>\n\n<batch_processing>\n```javascript\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger', version: 1,\n config: { name: 'Start', position: [240, 300] },\n output: [{}]\n});\n\nconst fetchRecords = node({\n type: 'n8n-nodes-base.httpRequest', version: 4.3,\n config: { name: 'Fetch Records', parameters: { method: 'GET', url: '...' }, position: [540, 300] },\n output: [{ id: 1 }, { id: 2 }, { id: 3 }]\n});\n\nconst finalizeResults = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Finalize', parameters: {}, position: [1140, 200] },\n output: [{ summary: 'Processed 3 records' }]\n});\n\nconst processRecord = node({\n type: 'n8n-nodes-base.httpRequest', version: 4.3,\n config: { name: 'Process Record', parameters: { method: 'POST', url: '...' }, position: [1140, 400] },\n output: [{ id: 1, status: 'processed' }]\n});\n\nconst sibNode = splitInBatches({ version: 3, config: { name: 'Batch Process', parameters: { batchSize: 10 }, position: [840, 300] } });\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', version: 2.1,\n config: { name: 'Webhook', position: [240, 200] },\n output: [{ body: { data: 'webhook payload' } }]\n});\n\nconst processWebhook = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Process Webhook', parameters: {}, position: [540, 200] },\n output: [{ data: 'webhook payload', processed: true }]\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger', version: 1.3,\n config: { name: 'Daily Schedule', parameters: {}, position: [240, 500] },\n output: [{}]\n});\n\nconst processSchedule = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Process Schedule', parameters: {}, position: [540, 500] },\n output: [{ scheduled: true }]\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', version: 2.1,\n config: { name: 'Webhook Trigger', position: [240, 200] },\n output: [{ source: 'webhook' }]\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger', version: 1.3,\n config: { name: 'Daily Schedule', position: [240, 500] },\n output: [{ source: 'schedule' }]\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Process Data', parameters: {}, position: [540, 350] },\n output: [{ processed: true }]\n});\n\nconst sendNotification = node({\n type: 'n8n-nodes-base.slack', version: 2.3,\n config: { name: 'Notify Slack', parameters: {}, position: [840, 350] },\n output: [{ ok: true }]\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', version: 1.3,\n config: { name: 'OpenAI Model', parameters: {}, position: [540, 500] }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger', version: 1,\n config: { name: 'Start', position: [240, 300] },\n output: [{}]\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', version: 3.1,\n config: {\n name: 'AI Assistant',\n parameters: { promptType: 'define', text: 'You are a helpful assistant' },\n subnodes: { model: openAiModel },\n position: [540, 300]\n },\n output: [{ output: 'AI response text' }]\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', version: 1.3,\n config: {\n name: 'OpenAI Model', parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') },\n position: [540, 500]\n }\n});\n\nconst calculatorTool = tool({\n type: '@n8n/n8n-nodes-langchain.toolCalculator', version: 1,\n config: { name: 'Calculator', parameters: {}, position: [700, 500] }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger', version: 1,\n config: { name: 'Start', position: [240, 300] },\n output: [{}]\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', 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 position: [540, 300]\n },\n output: [{ output: '42' }]\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 gmailTool = tool({\n type: 'n8n-nodes-base.gmailTool', 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 position: [700, 500]\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', 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 position: [540, 300]\n },\n output: [{ output: 'Email sent successfully' }]\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', 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 position: [700, 500]\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', 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 position: [540, 300]\n },\n output: [{ sentiment: 'positive', confidence: 0.95, summary: 'The text expresses satisfaction' }]\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_PATTERNS_DETAILED = "<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, reranker, 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', position: [240, 300] },\n output: [{}]\n});\n\nconst fetchData = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Data', parameters: { method: 'GET', url: '...' }, position: [540, 300] },\n output: [{ id: 1, title: 'Item 1' }]\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Data', parameters: {}, position: [840, 300] },\n output: [{ id: 1, title: 'Item 1', processed: true }]\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\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// sourceB runs once per item from sourceA.\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)\n// Pairs items by index, merging fields from both inputs into one item.\n// @example input0: [{ a: 1 }, { a: 2 }] input1: [{ b: 10, c: 'x' }, { b: 20 }]\n// output: [{ a: 1, b: 10, c: 'x' }, { a: 2, b: 20, c: undefined }]\n// .input(n) is 0-based: .input(0) = first input, .input(1) = second input.\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))) // first input (index 0)\n .add(startTrigger)\n .to(sourceB.to(combineResults.input(1))) // second input (index 1)\n .add(combineResults)\n .to(processResults);\n\n// FIX 3 - parallel branches + Merge (append)\n// Concatenates all items from all inputs into one list sequentially.\n// @example input0: [{ a: 1 }, { a: 2 }] input1: [{ b: 10 }]\n// output: [{ a: 1 }, { a: 2 }, { b: 10 }]\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))) // first input (index 0)\n .add(startTrigger)\n .to(sourceB.to(allResults.input(1))) // second input (index 1)\n .add(allResults)\n .to(processResults);\n```\n\n</independent_sources>\n\n<conditional_branching>\n\n**CRITICAL:** Each branch defines a COMPLETE processing path. Chain multiple steps INSIDE the branch using .to().\n\n```javascript\n// Assume other nodes are declared\nconst checkValid = ifElse({ version: 2.2, config: { name: 'Check Valid', parameters: {...} } });\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\n```javascript\n// Assume other nodes are declared\nconst routeByPriority = switchCase({ version: 3.2, config: { name: 'Route by Priority', parameters: {...} } });\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(routeByPriority\n .onCase(0, processUrgent.to(notifyTeam.to(escalate))) // Chain of 3 nodes\n .onCase(1, processNormal)\n .onCase(2, archive));\n```\n\n</multi_way_routing>\n\n<parallel_execution>\n```javascript\nconst combineResults = merge({\n version: 3.2,\n config: {\n name: 'Combine Results',\n parameters: { mode: 'combine' },\n position: [840, 300]\n }\n});\n\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).\n// Indices are 0-based: .input(0) is the FIRST input, .input(1) is the SECOND.\nexport default workflow('id', 'name')\n .add(trigger({ ... }))\n .to(branch1.to(combineResults.input(0))) // first input (index 0)\n .add(trigger({ ... }))\n .to(branch2.to(combineResults.input(1))) // second input (index 1)\n .add(combineResults)\n .to(processResults);\n```\n\n</parallel_execution>\n\n<batch_processing>\n```javascript\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger', version: 1,\n config: { name: 'Start', position: [240, 300] },\n output: [{}]\n});\n\nconst fetchRecords = node({\n type: 'n8n-nodes-base.httpRequest', version: 4.3,\n config: { name: 'Fetch Records', parameters: { method: 'GET', url: '...' }, position: [540, 300] },\n output: [{ id: 1 }, { id: 2 }, { id: 3 }]\n});\n\nconst finalizeResults = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Finalize', parameters: {}, position: [1140, 200] },\n output: [{ summary: 'Processed 3 records' }]\n});\n\nconst processRecord = node({\n type: 'n8n-nodes-base.httpRequest', version: 4.3,\n config: { name: 'Process Record', parameters: { method: 'POST', url: '...' }, position: [1140, 400] },\n output: [{ id: 1, status: 'processed' }]\n});\n\nconst sibNode = splitInBatches({ version: 3, config: { name: 'Batch Process', parameters: { batchSize: 10 }, position: [840, 300] } });\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', version: 2.1,\n config: { name: 'Webhook', position: [240, 200] },\n output: [{ body: { data: 'webhook payload' } }]\n});\n\nconst processWebhook = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Process Webhook', parameters: {}, position: [540, 200] },\n output: [{ data: 'webhook payload', processed: true }]\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger', version: 1.3,\n config: { name: 'Daily Schedule', parameters: {}, position: [240, 500] },\n output: [{}]\n});\n\nconst processSchedule = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Process Schedule', parameters: {}, position: [540, 500] },\n output: [{ scheduled: true }]\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', version: 2.1,\n config: { name: 'Webhook Trigger', position: [240, 200] },\n output: [{ source: 'webhook' }]\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger', version: 1.3,\n config: { name: 'Daily Schedule', position: [240, 500] },\n output: [{ source: 'schedule' }]\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set', version: 3.4,\n config: { name: 'Process Data', parameters: {}, position: [540, 350] },\n output: [{ processed: true }]\n});\n\nconst sendNotification = node({\n type: 'n8n-nodes-base.slack', version: 2.3,\n config: { name: 'Notify Slack', parameters: {}, position: [840, 350] },\n output: [{ ok: true }]\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', version: 1.3,\n config: { name: 'OpenAI Model', parameters: {}, position: [540, 500] }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger', version: 1,\n config: { name: 'Start', position: [240, 300] },\n output: [{}]\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', version: 3.1,\n config: {\n name: 'AI Assistant',\n parameters: { promptType: 'define', text: 'You are a helpful assistant' },\n subnodes: { model: openAiModel },\n position: [540, 300]\n },\n output: [{ output: 'AI response text' }]\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', version: 1.3,\n config: {\n name: 'OpenAI Model', parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') },\n position: [540, 500]\n }\n});\n\nconst calculatorTool = tool({\n type: '@n8n/n8n-nodes-langchain.toolCalculator', version: 1,\n config: { name: 'Calculator', parameters: {}, position: [700, 500] }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger', version: 1,\n config: { name: 'Start', position: [240, 300] },\n output: [{}]\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', 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 position: [540, 300]\n },\n output: [{ output: '42' }]\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 gmailTool = tool({\n type: 'n8n-nodes-base.gmailTool', 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 position: [700, 500]\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', 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 position: [540, 300]\n },\n output: [{ output: 'Email sent successfully' }]\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', 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 position: [700, 500]\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent', 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 position: [540, 300]\n },\n output: [{ sentiment: 'positive', confidence: 0.95, summary: 'The text expresses satisfaction' }]\n});\n\nexport default workflow('ai-sentiment', 'AI Sentiment Analyzer')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_structured_output>";
|
|
@@ -56,15 +56,16 @@ startTrigger.to(sourceA.to(sourceB.to(processResults)));
|
|
|
56
56
|
// Pairs items by index, merging fields from both inputs into one item.
|
|
57
57
|
// @example input0: [{ a: 1 }, { a: 2 }] input1: [{ b: 10, c: 'x' }, { b: 20 }]
|
|
58
58
|
// output: [{ a: 1, b: 10, c: 'x' }, { a: 2, b: 20, c: undefined }]
|
|
59
|
+
// .input(n) is 0-based: .input(0) = first input, .input(1) = second input.
|
|
59
60
|
const combineResults = merge({
|
|
60
61
|
version: 3.2,
|
|
61
62
|
config: { name: 'Combine Results', parameters: { mode: 'combine', combineBy: 'combineByPosition' } }
|
|
62
63
|
});
|
|
63
64
|
export default workflow('id', 'name')
|
|
64
65
|
.add(startTrigger)
|
|
65
|
-
.to(sourceA.to(combineResults.input(0)))
|
|
66
|
+
.to(sourceA.to(combineResults.input(0))) // first input (index 0)
|
|
66
67
|
.add(startTrigger)
|
|
67
|
-
.to(sourceB.to(combineResults.input(1)))
|
|
68
|
+
.to(sourceB.to(combineResults.input(1))) // second input (index 1)
|
|
68
69
|
.add(combineResults)
|
|
69
70
|
.to(processResults);
|
|
70
71
|
|
|
@@ -78,9 +79,9 @@ const allResults = merge({
|
|
|
78
79
|
});
|
|
79
80
|
export default workflow('id', 'name')
|
|
80
81
|
.add(startTrigger)
|
|
81
|
-
.to(sourceA.to(allResults.input(0)))
|
|
82
|
+
.to(sourceA.to(allResults.input(0))) // first input (index 0)
|
|
82
83
|
.add(startTrigger)
|
|
83
|
-
.to(sourceB.to(allResults.input(1)))
|
|
84
|
+
.to(sourceB.to(allResults.input(1))) // second input (index 1)
|
|
84
85
|
.add(allResults)
|
|
85
86
|
.to(processResults);
|
|
86
87
|
\`\`\`
|
|
@@ -135,12 +136,13 @@ const branch1 = node({ type: 'n8n-nodes-base.httpRequest', ... });
|
|
|
135
136
|
const branch2 = node({ type: 'n8n-nodes-base.httpRequest', ... });
|
|
136
137
|
const processResults = node({ type: 'n8n-nodes-base.set', ... });
|
|
137
138
|
|
|
138
|
-
// Connect branches to specific merge inputs using .input(n)
|
|
139
|
+
// Connect branches to specific merge inputs using .input(n).
|
|
140
|
+
// Indices are 0-based: .input(0) is the FIRST input, .input(1) is the SECOND.
|
|
139
141
|
export default workflow('id', 'name')
|
|
140
142
|
.add(trigger({ ... }))
|
|
141
|
-
.to(branch1.to(combineResults.input(0)))
|
|
143
|
+
.to(branch1.to(combineResults.input(0))) // first input (index 0)
|
|
142
144
|
.add(trigger({ ... }))
|
|
143
|
-
.to(branch2.to(combineResults.input(1)))
|
|
145
|
+
.to(branch2.to(combineResults.input(1))) // second input (index 1)
|
|
144
146
|
.add(combineResults)
|
|
145
147
|
.to(processResults);
|
|
146
148
|
\`\`\`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-patterns-detailed.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-patterns-detailed.ts"],"names":[],"mappings":";;;AASA,MAAM,UAAU,GACf,wSAAwS,CAAC;AAE7R,QAAA,0BAA0B,GAAG;;EAExC,UAAU
|
|
1
|
+
{"version":3,"file":"workflow-patterns-detailed.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-patterns-detailed.ts"],"names":[],"mappings":";;;AASA,MAAM,UAAU,GACf,wSAAwS,CAAC;AAE7R,QAAA,0BAA0B,GAAG;;EAExC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCAwYuB,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)\n// .input(n) is 0-based: .input(0) = first input, .input(1) = second input.\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))) // first input (index 0)\n .add(startTrigger)\n .to(sourceB.to(combineResults.input(1))) // second input (index 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))) // first input (index 0)\n .add(startTrigger)\n .to(sourceB.to(allResults.input(1))) // second input (index 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: expr('{{ $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: expr('{{ $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: expr('{{ $json.priority }}'), operator: { type: 'string', operation: 'equals' }, rightValue: 'urgent' }], combinator: 'and' } },\n { outputKey: 'normal', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: expr('{{ $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).\n// Indices are 0-based: .input(0) is the FIRST input, .input(1) is the SECOND.\nexport default workflow('id', 'name')\n .add(trigger({ ... }))\n .to(branch1.to(combineResults.input(0))) // first input (index 0)\n .add(trigger({ ... }))\n .to(branch2.to(combineResults.input(1))) // second input (index 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 };
|