@n8n/workflow-sdk 0.11.3 → 0.13.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.
Files changed (94) hide show
  1. package/dist/ast-interpreter/validators.js +2 -0
  2. package/dist/ast-interpreter/validators.js.map +1 -1
  3. package/dist/build.tsbuildinfo +1 -1
  4. package/dist/codegen/parse-workflow-code.js +1 -0
  5. package/dist/codegen/parse-workflow-code.js.map +1 -1
  6. package/dist/expression/index.d.ts +4 -1
  7. package/dist/expression/index.js +30 -0
  8. package/dist/expression/index.js.map +1 -1
  9. package/dist/expression.d.ts +1 -1
  10. package/dist/expression.js +2 -1
  11. package/dist/expression.js.map +1 -1
  12. package/dist/generate-types/generate-types.d.ts +9 -0
  13. package/dist/generate-types/generate-types.js +208 -51
  14. package/dist/generate-types/generate-types.js.map +1 -1
  15. package/dist/generate-types/generate-zod-schemas.d.ts +6 -1
  16. package/dist/generate-types/generate-zod-schemas.js +199 -90
  17. package/dist/generate-types/generate-zod-schemas.js.map +1 -1
  18. package/dist/index.d.ts +2 -1
  19. package/dist/index.js +3 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/prompts/best-practices/guides/chatbot.js +11 -0
  22. package/dist/prompts/best-practices/guides/chatbot.js.map +1 -1
  23. package/dist/prompts/best-practices/guides/web-app.d.ts +7 -0
  24. package/dist/prompts/best-practices/guides/web-app.js +206 -0
  25. package/dist/prompts/best-practices/guides/web-app.js.map +1 -0
  26. package/dist/prompts/best-practices/index.d.ts +1 -0
  27. package/dist/prompts/best-practices/index.js +5 -1
  28. package/dist/prompts/best-practices/index.js.map +1 -1
  29. package/dist/prompts/best-practices/types.d.ts +1 -0
  30. package/dist/prompts/best-practices/types.js +2 -0
  31. package/dist/prompts/best-practices/types.js.map +1 -1
  32. package/dist/prompts/node-guidance/parameter-guides/predecessor-output.js +7 -0
  33. package/dist/prompts/node-guidance/parameter-guides/predecessor-output.js.map +1 -1
  34. package/dist/prompts/sdk-reference/additional-functions.d.ts +1 -1
  35. package/dist/prompts/sdk-reference/additional-functions.js +3 -0
  36. package/dist/prompts/sdk-reference/additional-functions.js.map +1 -1
  37. package/dist/prompts/sdk-reference/expressions.d.ts +1 -1
  38. package/dist/prompts/sdk-reference/expressions.js +14 -1
  39. package/dist/prompts/sdk-reference/expressions.js.map +1 -1
  40. package/dist/prompts/sdk-reference/workflow-patterns.d.ts +1 -1
  41. package/dist/prompts/sdk-reference/workflow-patterns.js +102 -34
  42. package/dist/prompts/sdk-reference/workflow-patterns.js.map +1 -1
  43. package/dist/prompts/sdk-reference/workflow-rules.d.ts +1 -1
  44. package/dist/prompts/sdk-reference/workflow-rules.js +14 -7
  45. package/dist/prompts/sdk-reference/workflow-rules.js.map +1 -1
  46. package/dist/types/base.d.ts +14 -5
  47. package/dist/types/base.js +43 -0
  48. package/dist/types/base.js.map +1 -1
  49. package/dist/validation/display-options.js +8 -0
  50. package/dist/validation/display-options.js.map +1 -1
  51. package/dist/validation/index.d.ts +1 -1
  52. package/dist/validation/index.js +125 -2
  53. package/dist/validation/index.js.map +1 -1
  54. package/dist/validation/resolve-schema.d.ts +14 -0
  55. package/dist/validation/resolve-schema.js +20 -0
  56. package/dist/validation/resolve-schema.js.map +1 -1
  57. package/dist/validation/schema-helpers.d.ts +2 -2
  58. package/dist/validation/schema-helpers.js +2 -1
  59. package/dist/validation/schema-helpers.js.map +1 -1
  60. package/dist/validation/schema-validator.js +1 -0
  61. package/dist/validation/schema-validator.js.map +1 -1
  62. package/dist/workflow-builder/connection-utils.js +1 -1
  63. package/dist/workflow-builder/connection-utils.js.map +1 -1
  64. package/dist/workflow-builder/control-flow-builders/if-else.d.ts +1 -2
  65. package/dist/workflow-builder/control-flow-builders/split-in-batches.d.ts +2 -2
  66. package/dist/workflow-builder/control-flow-builders/split-in-batches.js +28 -0
  67. package/dist/workflow-builder/control-flow-builders/split-in-batches.js.map +1 -1
  68. package/dist/workflow-builder/control-flow-builders/switch-case.d.ts +1 -2
  69. package/dist/workflow-builder/node-builders/node-builder.d.ts +1 -0
  70. package/dist/workflow-builder/node-builders/node-builder.js +41 -2
  71. package/dist/workflow-builder/node-builders/node-builder.js.map +1 -1
  72. package/dist/workflow-builder/node-builders/subnode-builders.js +2 -1
  73. package/dist/workflow-builder/node-builders/subnode-builders.js.map +1 -1
  74. package/dist/workflow-builder/pin-data-utils.js.map +1 -1
  75. package/dist/workflow-builder/plugins/defaults.js +1 -0
  76. package/dist/workflow-builder/plugins/defaults.js.map +1 -1
  77. package/dist/workflow-builder/plugins/serializers/json-serializer.js +14 -1
  78. package/dist/workflow-builder/plugins/serializers/json-serializer.js.map +1 -1
  79. package/dist/workflow-builder/plugins/validators/index.d.ts +1 -0
  80. package/dist/workflow-builder/plugins/validators/index.js +3 -1
  81. package/dist/workflow-builder/plugins/validators/index.js.map +1 -1
  82. package/dist/workflow-builder/plugins/validators/memory-session-key-validator.d.ts +2 -0
  83. package/dist/workflow-builder/plugins/validators/memory-session-key-validator.js +50 -0
  84. package/dist/workflow-builder/plugins/validators/memory-session-key-validator.js.map +1 -0
  85. package/dist/workflow-builder/plugins/validators/set-node-validator.js +110 -4
  86. package/dist/workflow-builder/plugins/validators/set-node-validator.js.map +1 -1
  87. package/dist/workflow-builder/validation-helpers.d.ts +1 -0
  88. package/dist/workflow-builder/validation-helpers.js +7 -0
  89. package/dist/workflow-builder/validation-helpers.js.map +1 -1
  90. package/dist/workflow-builder/workflow-import.js +1 -0
  91. package/dist/workflow-builder/workflow-import.js.map +1 -1
  92. package/dist/workflow-builder.js +8 -2
  93. package/dist/workflow-builder.js.map +1 -1
  94. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -8,8 +8,9 @@ export { languageModel, memory, tool, outputParser, embedding, embeddings, vecto
8
8
  export type { IfElseTarget } from './workflow-builder/control-flow-builders/if-else';
9
9
  export type { SwitchCaseTarget } from './workflow-builder/control-flow-builders/switch-case';
10
10
  export { splitInBatches } from './workflow-builder/control-flow-builders/split-in-batches';
11
+ export type { SplitInBatchesTarget } from './types/base';
11
12
  export { nextBatch } from './workflow-builder/control-flow-builders/next-batch';
12
- export { parseExpression, isExpression, expr, createFromAIExpression, } from './expression';
13
+ export { parseExpression, isExpression, expr, nodeJson, createFromAIExpression, } from './expression';
13
14
  export { runOnceForAllItems, runOnceForEachItem } from './utils/code-helpers';
14
15
  export { isPlainObject, getProperty, hasProperty } from './utils/safe-access';
15
16
  export { layoutWorkflowJSON } from './workflow-builder/layout-utils';
package/dist/index.js CHANGED
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.NODE_TYPES = exports.matchesDisplayOptions = exports.normalizePinData = exports.inferSchemasFromRunData = exports.discoverOutputSchemaForNode = exports.needsPinData = exports.registerDefaultPlugins = exports.pluginRegistry = exports.PluginRegistry = exports.parseWorkflowCodeToBuilder = exports.parseWorkflowCode = exports.generateWorkflowCode = exports.setSchemaBaseDirs = exports.ValidationWarning = exports.ValidationError = exports.validateWorkflow = exports.layoutWorkflowJSON = exports.hasProperty = exports.getProperty = exports.isPlainObject = exports.runOnceForEachItem = exports.runOnceForAllItems = exports.createFromAIExpression = exports.expr = exports.isExpression = exports.parseExpression = exports.nextBatch = exports.splitInBatches = exports.fromAi = exports.textSplitter = exports.documentLoader = exports.retriever = exports.vectorStore = exports.embeddings = exports.embedding = exports.outputParser = exports.tool = exports.memory = exports.languageModel = exports.merge = exports.switchCase = exports.ifElse = exports.newCredential = exports.placeholder = exports.sticky = exports.trigger = exports.node = exports.workflow = exports.isNodeInstance = exports.isNodeChain = void 0;
18
- exports.isDataTableType = exports.isWebhookType = exports.isHttpRequestType = exports.isSplitInBatchesType = exports.isStickyNoteType = exports.isMergeNodeType = exports.isSwitchNodeType = exports.isIfNodeType = void 0;
17
+ exports.matchesDisplayOptions = exports.normalizePinData = exports.inferSchemasFromRunData = exports.discoverOutputSchemaForNode = exports.needsPinData = exports.registerDefaultPlugins = exports.pluginRegistry = exports.PluginRegistry = exports.parseWorkflowCodeToBuilder = exports.parseWorkflowCode = exports.generateWorkflowCode = exports.setSchemaBaseDirs = exports.ValidationWarning = exports.ValidationError = exports.validateWorkflow = exports.layoutWorkflowJSON = exports.hasProperty = exports.getProperty = exports.isPlainObject = exports.runOnceForEachItem = exports.runOnceForAllItems = exports.createFromAIExpression = exports.nodeJson = exports.expr = exports.isExpression = exports.parseExpression = exports.nextBatch = exports.splitInBatches = exports.fromAi = exports.textSplitter = exports.documentLoader = exports.retriever = exports.vectorStore = exports.embeddings = exports.embedding = exports.outputParser = exports.tool = exports.memory = exports.languageModel = exports.merge = exports.switchCase = exports.ifElse = exports.newCredential = exports.placeholder = exports.sticky = exports.trigger = exports.node = exports.workflow = exports.isNodeInstance = exports.isNodeChain = void 0;
18
+ exports.isDataTableType = exports.isWebhookType = exports.isHttpRequestType = exports.isSplitInBatchesType = exports.isStickyNoteType = exports.isMergeNodeType = exports.isSwitchNodeType = exports.isIfNodeType = exports.NODE_TYPES = void 0;
19
19
  var base_1 = require("./types/base");
20
20
  Object.defineProperty(exports, "isNodeChain", { enumerable: true, get: function () { return base_1.isNodeChain; } });
21
21
  Object.defineProperty(exports, "isNodeInstance", { enumerable: true, get: function () { return base_1.isNodeInstance; } });
@@ -50,6 +50,7 @@ var expression_1 = require("./expression");
50
50
  Object.defineProperty(exports, "parseExpression", { enumerable: true, get: function () { return expression_1.parseExpression; } });
51
51
  Object.defineProperty(exports, "isExpression", { enumerable: true, get: function () { return expression_1.isExpression; } });
52
52
  Object.defineProperty(exports, "expr", { enumerable: true, get: function () { return expression_1.expr; } });
53
+ Object.defineProperty(exports, "nodeJson", { enumerable: true, get: function () { return expression_1.nodeJson; } });
53
54
  Object.defineProperty(exports, "createFromAIExpression", { enumerable: true, get: function () { return expression_1.createFromAIExpression; } });
54
55
  var code_helpers_1 = require("./utils/code-helpers");
55
56
  Object.defineProperty(exports, "runOnceForAllItems", { enumerable: true, get: function () { return code_helpers_1.runOnceForAllItems; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA8EA,qCAA2D;AAAlD,mGAAA,WAAW,OAAA;AAAE,sGAAA,cAAc,OAAA;AAMpC,uDAA8C;AAArC,4GAAA,QAAQ,OAAA;AAGjB,8EASuD;AARtD,oGAAA,IAAI,OAAA;AACJ,uGAAA,OAAO,OAAA;AACP,sGAAA,MAAM,OAAA;AACN,2GAAA,WAAW,OAAA;AACX,6GAAA,aAAa,OAAA;AACb,sGAAA,MAAM,OAAA;AACN,0GAAA,UAAU,OAAA;AACV,qGAAA,KAAK,OAAA;AAON,sFAY2D;AAX1D,iHAAA,aAAa,OAAA;AACb,0GAAA,MAAM,OAAA;AACN,wGAAA,IAAI,OAAA;AACJ,gHAAA,YAAY,OAAA;AACZ,6GAAA,SAAS,OAAA;AACT,8GAAA,UAAU,OAAA;AACV,+GAAA,WAAW,OAAA;AACX,6GAAA,SAAS,OAAA;AACT,kHAAA,cAAc,OAAA;AACd,gHAAA,YAAY,OAAA;AACZ,0GAAA,MAAM,OAAA;AAaP,8FAA2F;AAAlF,kHAAA,cAAc,OAAA;AAMvB,kFAAgF;AAAvE,uGAAA,SAAS,OAAA;AAGlB,2CAKsB;AAJrB,6GAAA,eAAe,OAAA;AACf,0GAAA,YAAY,OAAA;AACZ,kGAAA,IAAI,OAAA;AACJ,oHAAA,sBAAsB,OAAA;AAIvB,qDAA8E;AAArE,kHAAA,kBAAkB,OAAA;AAAE,kHAAA,kBAAkB,OAAA;AAG/C,mDAA8E;AAArE,4GAAA,aAAa,OAAA;AAAE,0GAAA,WAAW,OAAA;AAAE,0GAAA,WAAW,OAAA;AAGhD,gEAAqE;AAA5D,kHAAA,kBAAkB,OAAA;AAG3B,2CAQsB;AAPrB,8GAAA,gBAAgB,OAAA;AAChB,6GAAA,eAAe,OAAA;AACf,+GAAA,iBAAiB,OAAA;AACjB,+GAAA,iBAAiB,OAAA;AAOlB,yCAAuD;AAA9C,6GAAA,oBAAoB,OAAA;AAC7B,qEAA8F;AAArF,wHAAA,iBAAiB,OAAA;AAAE,iIAAA,0BAA0B,OAAA;AAGtD,mDAAiC;AAGjC,sDAaoC;AAXnC,yGAAA,cAAc,OAAA;AACd,yGAAA,cAAc,OAAA;AAEd,iHAAA,sBAAsB,OAAA;AAWvB,mDAM0B;AALzB,8GAAA,YAAY,OAAA;AACZ,6HAAA,2BAA2B,OAAA;AAC3B,yHAAA,uBAAuB,OAAA;AACvB,kHAAA,gBAAgB,OAAA;AAKjB,gEAIsC;AAHrC,wHAAA,qBAAqB,OAAA;AAMtB,yCAWqB;AAVpB,uGAAA,UAAU,OAAA;AAEV,yGAAA,YAAY,OAAA;AACZ,6GAAA,gBAAgB,OAAA;AAChB,4GAAA,eAAe,OAAA;AACf,6GAAA,gBAAgB,OAAA;AAChB,iHAAA,oBAAoB,OAAA;AACpB,8GAAA,iBAAiB,OAAA;AACjB,0GAAA,aAAa,OAAA;AACb,4GAAA,eAAe,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA8EA,qCAA2D;AAAlD,mGAAA,WAAW,OAAA;AAAE,sGAAA,cAAc,OAAA;AAMpC,uDAA8C;AAArC,4GAAA,QAAQ,OAAA;AAGjB,8EASuD;AARtD,oGAAA,IAAI,OAAA;AACJ,uGAAA,OAAO,OAAA;AACP,sGAAA,MAAM,OAAA;AACN,2GAAA,WAAW,OAAA;AACX,6GAAA,aAAa,OAAA;AACb,sGAAA,MAAM,OAAA;AACN,0GAAA,UAAU,OAAA;AACV,qGAAA,KAAK,OAAA;AAON,sFAY2D;AAX1D,iHAAA,aAAa,OAAA;AACb,0GAAA,MAAM,OAAA;AACN,wGAAA,IAAI,OAAA;AACJ,gHAAA,YAAY,OAAA;AACZ,6GAAA,SAAS,OAAA;AACT,8GAAA,UAAU,OAAA;AACV,+GAAA,WAAW,OAAA;AACX,6GAAA,SAAS,OAAA;AACT,kHAAA,cAAc,OAAA;AACd,gHAAA,YAAY,OAAA;AACZ,0GAAA,MAAM,OAAA;AAaP,8FAA2F;AAAlF,kHAAA,cAAc,OAAA;AAOvB,kFAAgF;AAAvE,uGAAA,SAAS,OAAA;AAGlB,2CAMsB;AALrB,6GAAA,eAAe,OAAA;AACf,0GAAA,YAAY,OAAA;AACZ,kGAAA,IAAI,OAAA;AACJ,sGAAA,QAAQ,OAAA;AACR,oHAAA,sBAAsB,OAAA;AAIvB,qDAA8E;AAArE,kHAAA,kBAAkB,OAAA;AAAE,kHAAA,kBAAkB,OAAA;AAG/C,mDAA8E;AAArE,4GAAA,aAAa,OAAA;AAAE,0GAAA,WAAW,OAAA;AAAE,0GAAA,WAAW,OAAA;AAGhD,gEAAqE;AAA5D,kHAAA,kBAAkB,OAAA;AAG3B,2CAQsB;AAPrB,8GAAA,gBAAgB,OAAA;AAChB,6GAAA,eAAe,OAAA;AACf,+GAAA,iBAAiB,OAAA;AACjB,+GAAA,iBAAiB,OAAA;AAOlB,yCAAuD;AAA9C,6GAAA,oBAAoB,OAAA;AAC7B,qEAA8F;AAArF,wHAAA,iBAAiB,OAAA;AAAE,iIAAA,0BAA0B,OAAA;AAGtD,mDAAiC;AAGjC,sDAaoC;AAXnC,yGAAA,cAAc,OAAA;AACd,yGAAA,cAAc,OAAA;AAEd,iHAAA,sBAAsB,OAAA;AAWvB,mDAM0B;AALzB,8GAAA,YAAY,OAAA;AACZ,6HAAA,2BAA2B,OAAA;AAC3B,yHAAA,uBAAuB,OAAA;AACvB,kHAAA,gBAAgB,OAAA;AAKjB,gEAIsC;AAHrC,wHAAA,qBAAqB,OAAA;AAMtB,yCAWqB;AAVpB,uGAAA,UAAU,OAAA;AAEV,yGAAA,YAAY,OAAA;AACZ,6GAAA,gBAAgB,OAAA;AAChB,4GAAA,eAAe,OAAA;AACf,6GAAA,gBAAgB,OAAA;AAChB,iHAAA,oBAAoB,OAAA;AACpB,8GAAA,iBAAiB,OAAA;AACjB,0GAAA,aAAa,OAAA;AACb,4GAAA,eAAe,OAAA"}
@@ -34,6 +34,17 @@ Include information with the user prompt such as timestamp, user ID, or session
34
34
  If there are other agents involved in the workflow you should share memory between the chatbot and those other agents where it makes sense.
35
35
  Connect the same memory node to multiple agents to enable data sharing and context continuity.
36
36
 
37
+ ### Memory Session Keys
38
+
39
+ Memory session keys must uniquely identify the user or chat. In AI Agent memory subnodes, do not use $json for a custom session key because the memory subnode does not have the same immediate predecessor context as a main-flow node.
40
+
41
+ Use nodeJson(triggerNode, 'field.path') for external chat platforms:
42
+ - Telegram: sessionIdType = customKey, sessionKey = nodeJson(telegramTrigger, 'message.chat.id')
43
+ - Slack: sessionIdType = customKey, sessionKey = nodeJson(slackTrigger, 'event.channel')
44
+ - WhatsApp: sessionIdType = customKey, sessionKey = nodeJson(whatsAppTrigger, 'messages.0.from')
45
+
46
+ For the built-in n8n Chat Trigger, prefer memory parameters sessionIdType = fromInput and omit a custom sessionKey, because the Chat Trigger provides the session ID directly to the AI Agent.
47
+
37
48
  ## Context Engineering & AI Agent Output
38
49
 
39
50
  It can be beneficial to respond to the user as a tool of the chatbot agent rather than using the agent output - this allows the agent to loop/carry out multiple responses if necessary.
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwGjC,CAAC;IAKF,CAAC;IAHA,gBAAgB;QACf,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;CACD;AAjHD,oDAiHC"}
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"}
@@ -0,0 +1,7 @@
1
+ import type { BestPracticesDocument } from '../types';
2
+ export declare class WebAppBestPractices implements BestPracticesDocument {
3
+ readonly technique: "web_app";
4
+ readonly version = "1.0.0";
5
+ private readonly documentation;
6
+ getDocumentation(): string;
7
+ }
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebAppBestPractices = void 0;
4
+ const types_1 = require("../types");
5
+ class WebAppBestPractices {
6
+ constructor() {
7
+ this.technique = types_1.WorkflowTechnique.WEB_APP;
8
+ this.version = '1.0.0';
9
+ this.documentation = `# Best Practices: Web App Workflows (SPA served from a webhook)
10
+
11
+ ## Architecture
12
+
13
+ Webhook (responseNode) → Code node (build HTML) → respondToWebhook (Content-Type: text/html).
14
+
15
+ Serve a single-page application from an n8n webhook. The workflow fetches data, then renders a full HTML page with a client-side framework (Alpine.js + Tailwind via CDN is the default stack — no build step needed).
16
+
17
+ ## File-based HTML (REQUIRED for pages > ~50 lines)
18
+
19
+ Write the HTML to a separate file (e.g., \`chunks/dashboard.html\`), then in the SDK TypeScript code use \`readFileSync\` + \`JSON.stringify\` to safely embed it in a Code node. This eliminates ALL escaping problems:
20
+
21
+ 1. Write your full HTML (with CSS, JS, Alpine.js/Tailwind) to \`chunks/page.html\`.
22
+ 2. In \`src/workflow.ts\`: \`const htmlTemplate = readFileSync(join(__dirname, '../chunks/page.html'), 'utf8');\`
23
+ 3. Use \`JSON.stringify(htmlTemplate)\` to create a safe JS string literal for the Code node's \`jsCode\`.
24
+ 4. For data injection, embed a \`__DATA_PLACEHOLDER__\` token in the HTML and replace it at runtime.
25
+
26
+ **Do not embed large HTML directly in \`jsCode\`** — neither as template literals nor as arrays of quoted lines. Both break for real-world pages (20KB+). Always use the file-based pattern.
27
+
28
+ For small static HTML (< 50 lines), you may inline as an array of quoted strings + \`.join('\\n')\`, but the file-based approach is still preferred.
29
+
30
+ ## Data injection patterns
31
+
32
+ - **Static page (no server data):** embed HTML directly, no placeholder needed.
33
+ - **Dynamic data:** put \`<script id="__data" type="application/json">__DATA_PLACEHOLDER__</script>\` in the HTML. At runtime the Code node replaces \`__DATA_PLACEHOLDER__\` with base64-encoded JSON. Client-side: \`JSON.parse(atob(document.getElementById('__data').textContent))\`.
34
+ - Do not place bare \`{{ $json... }}\` expressions inside an HTML string parameter — they won't be evaluated.
35
+
36
+ ## Multi-route SPA (dashboard with API endpoints)
37
+
38
+ Use multiple webhooks in one workflow — one serves the HTML page, others serve JSON API endpoints. The HTML's JavaScript uses \`fetch()\` to call sibling webhook paths.
39
+
40
+ ## Responding correctly
41
+
42
+ Use \`respondToWebhook\` with \`respondWith: "text"\`, put the HTML in \`responseBody\` via expression, and set the \`Content-Type\` header to \`text/html; charset=utf-8\`.
43
+
44
+ ## Example: Multi-route dashboard with DataTable API
45
+
46
+ **chunks/dashboard.html** — the full HTML page (write this file first):
47
+
48
+ \`\`\`html
49
+ <!DOCTYPE html>
50
+ <html lang="en">
51
+ <head>
52
+ <meta charset="UTF-8">
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
54
+ <title>Dashboard</title>
55
+ <script src="https://cdn.tailwindcss.com"></script>
56
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>
57
+ </head>
58
+ <body class="bg-gray-50 min-h-screen p-8">
59
+ <h1 class="text-2xl font-bold mb-6">Dashboard</h1>
60
+ <div x-data="app()" x-init="loadItems()">
61
+ <template x-for="item in items" :key="item.id">
62
+ <div class="bg-white rounded-lg shadow p-4 mb-3 flex items-center gap-3">
63
+ <input type="checkbox" :checked="item.completed" @change="toggle(item)">
64
+ <span x-text="item.title" :class="item.completed && 'line-through text-gray-400'"></span>
65
+ </div>
66
+ </template>
67
+ <form @submit.prevent="addItem()" class="mt-4 flex gap-2">
68
+ <input x-model="newTitle" placeholder="New item..." class="border rounded px-3 py-2 flex-1">
69
+ <button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded">Add</button>
70
+ </form>
71
+ </div>
72
+ <!-- Server data injected at runtime (base64-encoded JSON) -->
73
+ <script id="__data" type="application/json">__DATA_PLACEHOLDER__</script>
74
+ <script>
75
+ function app() {
76
+ return {
77
+ items: JSON.parse(atob(document.getElementById('__data').textContent)),
78
+ newTitle: '',
79
+ async toggle(item) {
80
+ await fetch('/webhook/app/items/toggle', {
81
+ method: 'POST', headers: {'Content-Type': 'application/json'},
82
+ body: JSON.stringify({ id: item.id, completed: !item.completed })
83
+ });
84
+ item.completed = !item.completed;
85
+ },
86
+ async addItem() {
87
+ if (!this.newTitle.trim()) return;
88
+ const res = await fetch('/webhook/app/items/add', {
89
+ method: 'POST', headers: {'Content-Type': 'application/json'},
90
+ body: JSON.stringify({ title: this.newTitle })
91
+ });
92
+ const created = await res.json();
93
+ this.items.push(created);
94
+ this.newTitle = '';
95
+ },
96
+ loadItems() { /* items already loaded from __data */ }
97
+ };
98
+ }
99
+ </script>
100
+ </body>
101
+ </html>
102
+ \`\`\`
103
+
104
+ **src/workflow.ts** — the workflow with 4 webhook routes:
105
+
106
+ \`\`\`javascript
107
+ import { workflow, node, trigger, expr } from '@n8n/workflow-sdk';
108
+ import { readFileSync } from 'fs';
109
+ import { join } from 'path';
110
+
111
+ // Read the HTML template at build time — eliminates all escaping issues
112
+ const htmlTemplate = readFileSync(join(__dirname, '../chunks/dashboard.html'), 'utf8');
113
+
114
+ // ── Webhooks ──────────────────────────────────────────────
115
+ const pageWebhook = trigger({
116
+ type: 'n8n-nodes-base.webhook', version: 2.1,
117
+ config: { name: 'GET /app', parameters: { httpMethod: 'GET', path: 'app', responseMode: 'responseNode', options: {} } }
118
+ });
119
+ const getItemsWebhook = trigger({
120
+ type: 'n8n-nodes-base.webhook', version: 2.1,
121
+ config: { name: 'GET /app/items', parameters: { httpMethod: 'GET', path: 'app/items', responseMode: 'responseNode', options: {} } }
122
+ });
123
+ const toggleWebhook = trigger({
124
+ type: 'n8n-nodes-base.webhook', version: 2.1,
125
+ config: { name: 'POST /app/items/toggle', parameters: { httpMethod: 'POST', path: 'app/items/toggle', responseMode: 'responseNode', options: {} } }
126
+ });
127
+ const addWebhook = trigger({
128
+ type: 'n8n-nodes-base.webhook', version: 2.1,
129
+ config: { name: 'POST /app/items/add', parameters: { httpMethod: 'POST', path: 'app/items/add', responseMode: 'responseNode', options: {} } }
130
+ });
131
+
132
+ // ── Route 1: Serve HTML page with pre-loaded data ─────────
133
+ const fetchAllItems = node({
134
+ type: 'n8n-nodes-base.dataTable', version: 1.1,
135
+ config: { name: 'Fetch Items', parameters: { resource: 'row', operation: 'get', dataTableId: { __rl: true, mode: 'name', value: 'items' }, returnAll: true, options: {} } }
136
+ });
137
+ const aggregateItems = node({
138
+ type: 'n8n-nodes-base.aggregate', version: 1,
139
+ config: { name: 'Aggregate', parameters: { aggregate: 'aggregateAllItemData', destinationFieldName: 'data', options: {} } }
140
+ });
141
+ // JSON.stringify in the SDK code creates a safe JS string literal — no escaping issues
142
+ const buildPage = node({
143
+ type: 'n8n-nodes-base.code', version: 2,
144
+ config: {
145
+ name: 'Build Page',
146
+ parameters: {
147
+ mode: 'runOnceForAllItems',
148
+ jsCode: 'var data = $input.all()[0].json.data || [];\\n'
149
+ + 'var encoded = Buffer.from(JSON.stringify(data)).toString("base64");\\n'
150
+ + 'var html = ' + JSON.stringify(htmlTemplate) + '.replace("__DATA_PLACEHOLDER__", encoded);\\n'
151
+ + 'return [{ json: { html: html } }];'
152
+ }
153
+ }
154
+ });
155
+ const respondHtml = node({
156
+ type: 'n8n-nodes-base.respondToWebhook', version: 1.1,
157
+ config: { name: 'Respond HTML', parameters: { respondWith: 'text', responseBody: expr('{{ $json.html }}'), options: { responseHeaders: { entries: [{ name: 'Content-Type', value: 'text/html; charset=utf-8' }] } } } }
158
+ });
159
+
160
+ // ── Route 2: GET items as JSON ────────────────────────────
161
+ const fetchItemsJson = node({
162
+ type: 'n8n-nodes-base.dataTable', version: 1.1,
163
+ config: { name: 'Get Items JSON', parameters: { resource: 'row', operation: 'get', dataTableId: { __rl: true, mode: 'name', value: 'items' }, returnAll: true, options: {} } }
164
+ });
165
+ const respondItems = node({
166
+ type: 'n8n-nodes-base.respondToWebhook', version: 1.1,
167
+ config: { name: 'Respond Items', parameters: { respondWith: 'allEntries', options: {} } }
168
+ });
169
+
170
+ // ── Route 3: Toggle item completion ───────────────────────
171
+ const updateItem = node({
172
+ type: 'n8n-nodes-base.dataTable', version: 1.1,
173
+ config: { name: 'Update Item', parameters: { resource: 'row', operation: 'update', dataTableId: { __rl: true, mode: 'name', value: 'items' }, matchingColumns: ['id'], columns: { mappingMode: 'defineBelow', value: { id: expr('{{ $json.body.id }}'), completed: expr('{{ $json.body.completed }}') }, schema: [{ id: 'id', displayName: 'id', required: false, defaultMatch: true, display: true, type: 'string', canBeUsedToMatch: true }, { id: 'completed', displayName: 'completed', required: false, defaultMatch: false, display: true, type: 'boolean', canBeUsedToMatch: false }] }, options: {} } }
174
+ });
175
+ const respondToggle = node({
176
+ type: 'n8n-nodes-base.respondToWebhook', version: 1.1,
177
+ config: { name: 'Respond Toggle', parameters: { respondWith: 'allEntries', options: {} } }
178
+ });
179
+
180
+ // ── Route 4: Add new item ─────────────────────────────────
181
+ const insertItem = node({
182
+ type: 'n8n-nodes-base.dataTable', version: 1.1,
183
+ config: { name: 'Insert Item', parameters: { resource: 'row', operation: 'insert', dataTableId: { __rl: true, mode: 'name', value: 'items' }, columns: { mappingMode: 'defineBelow', value: { title: expr('{{ $json.body.title }}'), completed: false }, schema: [{ id: 'title', displayName: 'title', required: false, defaultMatch: false, display: true, type: 'string', canBeUsedToMatch: true }, { id: 'completed', displayName: 'completed', required: false, defaultMatch: false, display: true, type: 'boolean', canBeUsedToMatch: false }] }, options: {} } }
184
+ });
185
+ const respondAdd = node({
186
+ type: 'n8n-nodes-base.respondToWebhook', version: 1.1,
187
+ config: { name: 'Respond Add', parameters: { respondWith: 'allEntries', options: {} } }
188
+ });
189
+
190
+ // ── Wire it all together ──────────────────────────────────
191
+ export default workflow('id', 'Item Dashboard')
192
+ .add(pageWebhook).to(fetchAllItems).to(aggregateItems).to(buildPage).to(respondHtml)
193
+ .add(getItemsWebhook).to(fetchItemsJson).to(respondItems)
194
+ .add(toggleWebhook).to(updateItem).to(respondToggle)
195
+ .add(addWebhook).to(insertItem).to(respondAdd);
196
+ \`\`\`
197
+
198
+ **Key takeaway:** \`JSON.stringify(htmlTemplate)\` at build time produces a perfectly escaped JS string. The Code node's \`jsCode\` is just four lines. No escaping problems, no matter how large the HTML.
199
+ `;
200
+ }
201
+ getDocumentation() {
202
+ return this.documentation;
203
+ }
204
+ }
205
+ exports.WebAppBestPractices = WebAppBestPractices;
206
+ //# sourceMappingURL=web-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-app.js","sourceRoot":"","sources":["../../../../src/prompts/best-practices/guides/web-app.ts"],"names":[],"mappings":";;;AACA,oCAA6C;AAE7C,MAAa,mBAAmB;IAAhC;QACU,cAAS,GAAG,yBAAiB,CAAC,OAAO,CAAC;QACtC,YAAO,GAAG,OAAO,CAAC;QAEV,kBAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8LjC,CAAC;IAKF,CAAC;IAHA,gBAAgB;QACf,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;CACD;AAvMD,kDAuMC"}
@@ -16,5 +16,6 @@ export { NotificationBestPractices } from './guides/notification';
16
16
  export { SchedulingBestPractices } from './guides/scheduling';
17
17
  export { ScrapingAndResearchBestPractices } from './guides/scraping-and-research';
18
18
  export { TriageBestPractices } from './guides/triage';
19
+ export { WebAppBestPractices } from './guides/web-app';
19
20
  import type { WorkflowTechniqueType, BestPracticesDocument } from './types';
20
21
  export declare const bestPracticesRegistry: Record<WorkflowTechniqueType, BestPracticesDocument | undefined>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.bestPracticesRegistry = exports.TriageBestPractices = exports.ScrapingAndResearchBestPractices = exports.SchedulingBestPractices = exports.NotificationBestPractices = exports.MonitoringBestPractices = exports.KnowledgeBaseBestPractices = exports.HumanInTheLoopBestPractices = exports.FormInputBestPractices = exports.EnrichmentBestPractices = exports.DocumentProcessingBestPractices = exports.DataTransformationBestPractices = exports.DataPersistenceBestPractices = exports.DataExtractionBestPractices = exports.DataAnalysisBestPractices = exports.ContentGenerationBestPractices = exports.ChatbotBestPractices = exports.TechniqueDescription = exports.WorkflowTechnique = void 0;
3
+ exports.bestPracticesRegistry = exports.WebAppBestPractices = exports.TriageBestPractices = exports.ScrapingAndResearchBestPractices = exports.SchedulingBestPractices = exports.NotificationBestPractices = exports.MonitoringBestPractices = exports.KnowledgeBaseBestPractices = exports.HumanInTheLoopBestPractices = exports.FormInputBestPractices = exports.EnrichmentBestPractices = exports.DocumentProcessingBestPractices = exports.DataTransformationBestPractices = exports.DataPersistenceBestPractices = exports.DataExtractionBestPractices = exports.DataAnalysisBestPractices = exports.ContentGenerationBestPractices = exports.ChatbotBestPractices = exports.TechniqueDescription = exports.WorkflowTechnique = void 0;
4
4
  var types_1 = require("./types");
5
5
  Object.defineProperty(exports, "WorkflowTechnique", { enumerable: true, get: function () { return types_1.WorkflowTechnique; } });
6
6
  Object.defineProperty(exports, "TechniqueDescription", { enumerable: true, get: function () { return types_1.TechniqueDescription; } });
@@ -36,6 +36,8 @@ var scraping_and_research_1 = require("./guides/scraping-and-research");
36
36
  Object.defineProperty(exports, "ScrapingAndResearchBestPractices", { enumerable: true, get: function () { return scraping_and_research_1.ScrapingAndResearchBestPractices; } });
37
37
  var triage_1 = require("./guides/triage");
38
38
  Object.defineProperty(exports, "TriageBestPractices", { enumerable: true, get: function () { return triage_1.TriageBestPractices; } });
39
+ var web_app_1 = require("./guides/web-app");
40
+ Object.defineProperty(exports, "WebAppBestPractices", { enumerable: true, get: function () { return web_app_1.WebAppBestPractices; } });
39
41
  const chatbot_2 = require("./guides/chatbot");
40
42
  const content_generation_2 = require("./guides/content-generation");
41
43
  const data_extraction_2 = require("./guides/data-extraction");
@@ -47,6 +49,7 @@ const notification_2 = require("./guides/notification");
47
49
  const scheduling_2 = require("./guides/scheduling");
48
50
  const scraping_and_research_2 = require("./guides/scraping-and-research");
49
51
  const triage_2 = require("./guides/triage");
52
+ const web_app_2 = require("./guides/web-app");
50
53
  const types_2 = require("./types");
51
54
  exports.bestPracticesRegistry = {
52
55
  [types_2.WorkflowTechnique.SCHEDULING]: new scheduling_2.SchedulingBestPractices(),
@@ -65,5 +68,6 @@ exports.bestPracticesRegistry = {
65
68
  [types_2.WorkflowTechnique.NOTIFICATION]: new notification_2.NotificationBestPractices(),
66
69
  [types_2.WorkflowTechnique.KNOWLEDGE_BASE]: undefined,
67
70
  [types_2.WorkflowTechnique.HUMAN_IN_THE_LOOP]: undefined,
71
+ [types_2.WorkflowTechnique.WEB_APP]: new web_app_2.WebAppBestPractices(),
68
72
  };
69
73
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/prompts/best-practices/index.ts"],"names":[],"mappings":";;;AAAA,iCAAkE;AAAzD,0GAAA,iBAAiB,OAAA;AAAE,6GAAA,oBAAoB,OAAA;AAGhD,4CAAwD;AAA/C,+GAAA,oBAAoB,OAAA;AAC7B,kEAA6E;AAApE,oIAAA,8BAA8B,OAAA;AACvC,wDAAmE;AAA1D,0HAAA,yBAAyB,OAAA;AAClC,4DAAuE;AAA9D,8HAAA,2BAA2B,OAAA;AACpC,8DAAyE;AAAhE,gIAAA,4BAA4B,OAAA;AACrC,oEAA+E;AAAtE,sIAAA,+BAA+B,OAAA;AACxC,oEAA+E;AAAtE,sIAAA,+BAA+B,OAAA;AACxC,kDAA8D;AAArD,qHAAA,uBAAuB,OAAA;AAChC,kDAA6D;AAApD,oHAAA,sBAAsB,OAAA;AAC/B,gEAAyE;AAAhE,gIAAA,2BAA2B,OAAA;AACpC,0DAAqE;AAA5D,4HAAA,0BAA0B,OAAA;AACnC,kDAA8D;AAArD,qHAAA,uBAAuB,OAAA;AAChC,sDAAkE;AAAzD,yHAAA,yBAAyB,OAAA;AAClC,kDAA8D;AAArD,qHAAA,uBAAuB,OAAA;AAChC,wEAAkF;AAAzE,yIAAA,gCAAgC,OAAA;AACzC,0CAAsD;AAA7C,6GAAA,mBAAmB,OAAA;AAE5B,8CAAwD;AACxD,oEAA6E;AAC7E,8DAAuE;AACvE,gEAAyE;AACzE,sEAA+E;AAC/E,sEAA+E;AAC/E,oDAA6D;AAC7D,wDAAkE;AAClE,oDAA8D;AAC9D,0EAAkF;AAClF,4CAAsD;AAEtD,mCAA4C;AAE/B,QAAA,qBAAqB,GAG9B;IACH,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,IAAI,oCAAuB,EAAE;IAC7D,CAAC,yBAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,8BAAoB,EAAE;IACvD,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,IAAI,mCAAsB,EAAE;IAC5D,CAAC,yBAAiB,CAAC,qBAAqB,CAAC,EAAE,IAAI,wDAAgC,EAAE;IACjF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,SAAS;IACzC,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,SAAS;IACzC,CAAC,yBAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,4BAAmB,EAAE;IACrD,CAAC,yBAAiB,CAAC,kBAAkB,CAAC,EAAE,IAAI,mDAA8B,EAAE;IAC5E,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EAAE,IAAI,qDAA+B,EAAE;IAC9E,CAAC,yBAAiB,CAAC,eAAe,CAAC,EAAE,IAAI,6CAA2B,EAAE;IACtE,CAAC,yBAAiB,CAAC,aAAa,CAAC,EAAE,SAAS;IAC5C,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EAAE,IAAI,qDAA+B,EAAE;IAC9E,CAAC,yBAAiB,CAAC,gBAAgB,CAAC,EAAE,IAAI,+CAA4B,EAAE;IACxE,CAAC,yBAAiB,CAAC,YAAY,CAAC,EAAE,IAAI,wCAAyB,EAAE;IACjE,CAAC,yBAAiB,CAAC,cAAc,CAAC,EAAE,SAAS;IAC7C,CAAC,yBAAiB,CAAC,iBAAiB,CAAC,EAAE,SAAS;CAChD,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/prompts/best-practices/index.ts"],"names":[],"mappings":";;;AAAA,iCAAkE;AAAzD,0GAAA,iBAAiB,OAAA;AAAE,6GAAA,oBAAoB,OAAA;AAGhD,4CAAwD;AAA/C,+GAAA,oBAAoB,OAAA;AAC7B,kEAA6E;AAApE,oIAAA,8BAA8B,OAAA;AACvC,wDAAmE;AAA1D,0HAAA,yBAAyB,OAAA;AAClC,4DAAuE;AAA9D,8HAAA,2BAA2B,OAAA;AACpC,8DAAyE;AAAhE,gIAAA,4BAA4B,OAAA;AACrC,oEAA+E;AAAtE,sIAAA,+BAA+B,OAAA;AACxC,oEAA+E;AAAtE,sIAAA,+BAA+B,OAAA;AACxC,kDAA8D;AAArD,qHAAA,uBAAuB,OAAA;AAChC,kDAA6D;AAApD,oHAAA,sBAAsB,OAAA;AAC/B,gEAAyE;AAAhE,gIAAA,2BAA2B,OAAA;AACpC,0DAAqE;AAA5D,4HAAA,0BAA0B,OAAA;AACnC,kDAA8D;AAArD,qHAAA,uBAAuB,OAAA;AAChC,sDAAkE;AAAzD,yHAAA,yBAAyB,OAAA;AAClC,kDAA8D;AAArD,qHAAA,uBAAuB,OAAA;AAChC,wEAAkF;AAAzE,yIAAA,gCAAgC,OAAA;AACzC,0CAAsD;AAA7C,6GAAA,mBAAmB,OAAA;AAC5B,4CAAuD;AAA9C,8GAAA,mBAAmB,OAAA;AAE5B,8CAAwD;AACxD,oEAA6E;AAC7E,8DAAuE;AACvE,gEAAyE;AACzE,sEAA+E;AAC/E,sEAA+E;AAC/E,oDAA6D;AAC7D,wDAAkE;AAClE,oDAA8D;AAC9D,0EAAkF;AAClF,4CAAsD;AACtD,8CAAuD;AAEvD,mCAA4C;AAE/B,QAAA,qBAAqB,GAG9B;IACH,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,IAAI,oCAAuB,EAAE;IAC7D,CAAC,yBAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,8BAAoB,EAAE;IACvD,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,IAAI,mCAAsB,EAAE;IAC5D,CAAC,yBAAiB,CAAC,qBAAqB,CAAC,EAAE,IAAI,wDAAgC,EAAE;IACjF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,SAAS;IACzC,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,SAAS;IACzC,CAAC,yBAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,4BAAmB,EAAE;IACrD,CAAC,yBAAiB,CAAC,kBAAkB,CAAC,EAAE,IAAI,mDAA8B,EAAE;IAC5E,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EAAE,IAAI,qDAA+B,EAAE;IAC9E,CAAC,yBAAiB,CAAC,eAAe,CAAC,EAAE,IAAI,6CAA2B,EAAE;IACtE,CAAC,yBAAiB,CAAC,aAAa,CAAC,EAAE,SAAS;IAC5C,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EAAE,IAAI,qDAA+B,EAAE;IAC9E,CAAC,yBAAiB,CAAC,gBAAgB,CAAC,EAAE,IAAI,+CAA4B,EAAE;IACxE,CAAC,yBAAiB,CAAC,YAAY,CAAC,EAAE,IAAI,wCAAyB,EAAE;IACjE,CAAC,yBAAiB,CAAC,cAAc,CAAC,EAAE,SAAS;IAC7C,CAAC,yBAAiB,CAAC,iBAAiB,CAAC,EAAE,SAAS;IAChD,CAAC,yBAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,6BAAmB,EAAE;CACtD,CAAC"}
@@ -15,6 +15,7 @@ export declare const WorkflowTechnique: {
15
15
  readonly NOTIFICATION: "notification";
16
16
  readonly KNOWLEDGE_BASE: "knowledge_base";
17
17
  readonly HUMAN_IN_THE_LOOP: "human_in_the_loop";
18
+ readonly WEB_APP: "web_app";
18
19
  };
19
20
  export type WorkflowTechniqueType = (typeof WorkflowTechnique)[keyof typeof WorkflowTechnique];
20
21
  export declare const TechniqueDescription: Record<WorkflowTechniqueType, string>;
@@ -18,6 +18,7 @@ exports.WorkflowTechnique = {
18
18
  NOTIFICATION: 'notification',
19
19
  KNOWLEDGE_BASE: 'knowledge_base',
20
20
  HUMAN_IN_THE_LOOP: 'human_in_the_loop',
21
+ WEB_APP: 'web_app',
21
22
  };
22
23
  exports.TechniqueDescription = {
23
24
  [exports.WorkflowTechnique.SCHEDULING]: 'Running an action at a specific time or interval',
@@ -36,5 +37,6 @@ exports.TechniqueDescription = {
36
37
  [exports.WorkflowTechnique.NOTIFICATION]: 'Sending alerts or updates via email, chat, SMS when events occur',
37
38
  [exports.WorkflowTechnique.KNOWLEDGE_BASE]: 'Building or using a centralized information collection (usually vector database for LLM use)',
38
39
  [exports.WorkflowTechnique.HUMAN_IN_THE_LOOP]: 'Pausing for human decision/input before resuming',
40
+ [exports.WorkflowTechnique.WEB_APP]: 'Serving a single-page application (HTML + JS) from a webhook — dashboards, admin UIs, forms that need custom rendering',
39
41
  };
40
42
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/prompts/best-practices/types.ts"],"names":[],"mappings":";;;AAGa,QAAA,iBAAiB,GAAG;IAChC,UAAU,EAAE,YAAY;IACxB,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,YAAY;IACxB,qBAAqB,EAAE,uBAAuB;IAC9C,UAAU,EAAE,YAAY;IACxB,UAAU,EAAE,YAAY;IACxB,MAAM,EAAE,QAAQ;IAChB,kBAAkB,EAAE,oBAAoB;IACxC,mBAAmB,EAAE,qBAAqB;IAC1C,eAAe,EAAE,iBAAiB;IAClC,aAAa,EAAE,eAAe;IAC9B,mBAAmB,EAAE,qBAAqB;IAC1C,gBAAgB,EAAE,kBAAkB;IACpC,YAAY,EAAE,cAAc;IAC5B,cAAc,EAAE,gBAAgB;IAChC,iBAAiB,EAAE,mBAAmB;CAC7B,CAAC;AAIE,QAAA,oBAAoB,GAA0C;IAC1E,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,kDAAkD;IAClF,CAAC,yBAAiB,CAAC,OAAO,CAAC,EAC1B,uFAAuF;IACxF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,qCAAqC;IACrE,CAAC,yBAAiB,CAAC,qBAAqB,CAAC,EACxC,sFAAsF;IACvF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAC7B,sFAAsF;IACvF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAC7B,iFAAiF;IAClF,CAAC,yBAAiB,CAAC,MAAM,CAAC,EAAE,gDAAgD;IAC5E,CAAC,yBAAiB,CAAC,kBAAkB,CAAC,EAAE,2CAA2C;IACnF,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EACtC,iEAAiE;IAClE,CAAC,yBAAiB,CAAC,eAAe,CAAC,EAClC,qEAAqE;IACtE,CAAC,yBAAiB,CAAC,aAAa,CAAC,EAChC,iEAAiE;IAClE,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EACtC,uEAAuE;IACxE,CAAC,yBAAiB,CAAC,gBAAgB,CAAC,EACnC,kHAAkH;IACnH,CAAC,yBAAiB,CAAC,YAAY,CAAC,EAC/B,kEAAkE;IACnE,CAAC,yBAAiB,CAAC,cAAc,CAAC,EACjC,8FAA8F;IAC/F,CAAC,yBAAiB,CAAC,iBAAiB,CAAC,EAAE,kDAAkD;CACzF,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/prompts/best-practices/types.ts"],"names":[],"mappings":";;;AAGa,QAAA,iBAAiB,GAAG;IAChC,UAAU,EAAE,YAAY;IACxB,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,YAAY;IACxB,qBAAqB,EAAE,uBAAuB;IAC9C,UAAU,EAAE,YAAY;IACxB,UAAU,EAAE,YAAY;IACxB,MAAM,EAAE,QAAQ;IAChB,kBAAkB,EAAE,oBAAoB;IACxC,mBAAmB,EAAE,qBAAqB;IAC1C,eAAe,EAAE,iBAAiB;IAClC,aAAa,EAAE,eAAe;IAC9B,mBAAmB,EAAE,qBAAqB;IAC1C,gBAAgB,EAAE,kBAAkB;IACpC,YAAY,EAAE,cAAc;IAC5B,cAAc,EAAE,gBAAgB;IAChC,iBAAiB,EAAE,mBAAmB;IACtC,OAAO,EAAE,SAAS;CACT,CAAC;AAIE,QAAA,oBAAoB,GAA0C;IAC1E,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,kDAAkD;IAClF,CAAC,yBAAiB,CAAC,OAAO,CAAC,EAC1B,uFAAuF;IACxF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAAE,qCAAqC;IACrE,CAAC,yBAAiB,CAAC,qBAAqB,CAAC,EACxC,sFAAsF;IACvF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAC7B,sFAAsF;IACvF,CAAC,yBAAiB,CAAC,UAAU,CAAC,EAC7B,iFAAiF;IAClF,CAAC,yBAAiB,CAAC,MAAM,CAAC,EAAE,gDAAgD;IAC5E,CAAC,yBAAiB,CAAC,kBAAkB,CAAC,EAAE,2CAA2C;IACnF,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EACtC,iEAAiE;IAClE,CAAC,yBAAiB,CAAC,eAAe,CAAC,EAClC,qEAAqE;IACtE,CAAC,yBAAiB,CAAC,aAAa,CAAC,EAChC,iEAAiE;IAClE,CAAC,yBAAiB,CAAC,mBAAmB,CAAC,EACtC,uEAAuE;IACxE,CAAC,yBAAiB,CAAC,gBAAgB,CAAC,EACnC,kHAAkH;IACnH,CAAC,yBAAiB,CAAC,YAAY,CAAC,EAC/B,kEAAkE;IACnE,CAAC,yBAAiB,CAAC,cAAc,CAAC,EACjC,8FAA8F;IAC/F,CAAC,yBAAiB,CAAC,iBAAiB,CAAC,EAAE,kDAAkD;IACzF,CAAC,yBAAiB,CAAC,OAAO,CAAC,EAC1B,wHAAwH;CACzH,CAAC"}
@@ -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;;;;;;;;;;;;;;;;;+JAiBqJ;CAC9J,CAAC"}
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"}
@@ -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. 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- `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()`";
@@ -15,6 +15,9 @@ exports.ADDITIONAL_FUNCTIONS = `Additional SDK functions:
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;;;;;;;;;;;;;;;mIAe+F,CAAC"}
1
+ {"version":3,"file":"additional-functions.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/additional-functions.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;mIAkB+F,CAAC"}
@@ -1 +1 @@
1
- export declare const EXPRESSION_REFERENCE = "Available variables inside `expr('{{ ... }}')`:\n\n- `$json` \u2014 current item's JSON data from the immediate predecessor node\n- `$('NodeName').item.json` \u2014 access any node's output by name\n- `$input.first()` \u2014 first item from immediate predecessor\n- `$input.all()` \u2014 all items from immediate predecessor\n- `$input.item` \u2014 current item being processed\n- `$binary` \u2014 binary data of current item from immediate predecessor\n- `$now` \u2014 current date/time (Luxon DateTime). Example: `$now.toISO()`\n- `$today` \u2014 start of today (Luxon DateTime). Example: `$today.plus(1, 'days')`\n- `$itemIndex` \u2014 index of current item being processed\n- `$runIndex` \u2014 current run index\n- `$execution.id` \u2014 unique execution ID\n- `$execution.mode` \u2014 'test' or 'production'\n- `$workflow.id` \u2014 workflow ID\n- `$workflow.name` \u2014 workflow name\n\nString composition \u2014 variables MUST always be inside `{{ }}`, never outside as JS variables:\n\n- `expr('Hello {{ $json.name }}, welcome!')` \u2014 variable embedded in text\n- `expr('Report for {{ $now.toFormat(\"MMMM d, yyyy\") }} - {{ $json.title }}')` \u2014 multiple variables with method call\n- `expr('{{ $json.firstName }} {{ $json.lastName }}')` \u2014 combining multiple fields\n- `expr('Total: {{ $json.items.length }} items, updated {{ $now.toISO() }}')` \u2014 expressions with method calls\n- `expr('Status: {{ $json.count > 0 ? \"active\" : \"empty\" }}')` \u2014 inline ternary\n\nDynamic data from other nodes \u2014 `$()` MUST always be inside `{{ }}`, never used as plain JavaScript:\n\n- WRONG: `expr('{{ ' + JSON.stringify($('Source').all().map(i => i.json.name)) + ' }}')` \u2014 $() outside {{ }}\n- CORRECT: `expr('{{ $(\"Source\").all().map(i => ({ option: i.json.name })) }}')` \u2014 $() inside {{ }}\n- CORRECT: `expr('{{ { \"fields\": [{ \"values\": $(\"Fetch Projects\").all().map(i => ({ option: i.json.name })) }] } }}')` \u2014 complex JSON inside {{ }}";
1
+ export declare const EXPRESSION_REFERENCE = "Available variables inside `expr('{{ ... }}')`:\n\n- `$json` \u2014 current item's JSON data from the immediate predecessor node\n- `$('NodeName').item.json` \u2014 access any node's output by name\n- `nodeJson(node, 'field.path')` \u2014 SDK helper that creates `={{ $('NodeName').item.json.field.path }}`\n- `$input.first()` \u2014 first item from immediate predecessor\n- `$input.all()` \u2014 all items from immediate predecessor\n- `$input.item` \u2014 current item being processed\n- `$binary` \u2014 binary data of current item from immediate predecessor\n- `$now` \u2014 current date/time (Luxon DateTime). Example: `$now.toISO()`\n- `$today` \u2014 start of today (Luxon DateTime). Example: `$today.plus(1, 'days')`\n- `$itemIndex` \u2014 index of current item being processed\n- `$runIndex` \u2014 current run index\n- `$execution.id` \u2014 unique execution ID\n- `$execution.mode` \u2014 'test' or 'production'\n- `$workflow.id` \u2014 workflow ID\n- `$workflow.name` \u2014 workflow name\n\nString composition \u2014 variables MUST always be inside `{{ }}`, never outside as JS variables:\n\n- `expr('Hello {{ $json.name }}, welcome!')` \u2014 variable embedded in text\n- `expr('Report for {{ $now.toFormat(\"MMMM d, yyyy\") }} - {{ $json.title }}')` \u2014 multiple variables with method call\n- `expr('{{ $json.firstName }} {{ $json.lastName }}')` \u2014 combining multiple fields\n- `expr('Total: {{ $json.items.length }} items, updated {{ $now.toISO() }}')` \u2014 expressions with method calls\n- `expr('Status: {{ $json.count > 0 ? \"active\" : \"empty\" }}')` \u2014 inline ternary\n\nDynamic data from other nodes \u2014 `$()` MUST always be inside `{{ }}`, never used as plain JavaScript:\n\n- WRONG: `expr('{{ ' + JSON.stringify($('Source').all().map(i => i.json.name)) + ' }}')` \u2014 $() outside {{ }}\n- CORRECT: `expr('{{ $(\"Source\").all().map(i => ({ option: i.json.name })) }}')` \u2014 $() inside {{ }}\n- CORRECT: `expr('{{ { \"fields\": [{ \"values\": $(\"Fetch Projects\").all().map(i => ({ option: i.json.name })) }] } }}')` \u2014 complex JSON inside {{ }}\n\nWhen `$json` is unsafe - use `nodeJson(node, 'path')` or `$('NodeName').item.json.path` instead:\n\n- AI Agent subnodes: memory, language model, parser, retriever, vector store, and tool subnodes do not have the same immediate predecessor context as a main-flow node.\n WRONG: `sessionKey: expr('{{ $json.chatId }}')`\n CORRECT: `sessionKey: nodeJson(telegramTrigger, 'message.chat.id')`\n- Multi-branch fan-in: if a node receives data after IF/Switch/Merge-style branching, `$json` only means the current incoming item and may not contain the source field you need.\n WRONG: `expr('{{ $json.userId }}')`\n CORRECT: `nodeJson(userLookup, 'user.id')`\n- Further-upstream data: if the value comes from any node other than the immediate main predecessor, reference that node explicitly.\n WRONG: `expr('{{ $json.email }}')`\n CORRECT: `nodeJson(formTrigger, 'body.email')`";
@@ -5,6 +5,7 @@ exports.EXPRESSION_REFERENCE = `Available variables inside \`expr('{{ ... }}')\`
5
5
 
6
6
  - \`$json\` — current item's JSON data from the immediate predecessor node
7
7
  - \`$('NodeName').item.json\` — access any node's output by name
8
+ - \`nodeJson(node, 'field.path')\` — SDK helper that creates \`={{ $('NodeName').item.json.field.path }}\`
8
9
  - \`$input.first()\` — first item from immediate predecessor
9
10
  - \`$input.all()\` — all items from immediate predecessor
10
11
  - \`$input.item\` — current item being processed
@@ -30,5 +31,17 @@ Dynamic data from other nodes — \`$()\` MUST always be inside \`{{ }}\`, never
30
31
 
31
32
  - WRONG: \`expr('{{ ' + JSON.stringify($('Source').all().map(i => i.json.name)) + ' }}')\` — $() outside {{ }}
32
33
  - CORRECT: \`expr('{{ $("Source").all().map(i => ({ option: i.json.name })) }}')\` — $() inside {{ }}
33
- - CORRECT: \`expr('{{ { "fields": [{ "values": $("Fetch Projects").all().map(i => ({ option: i.json.name })) }] } }}')\` — complex JSON inside {{ }}`;
34
+ - CORRECT: \`expr('{{ { "fields": [{ "values": $("Fetch Projects").all().map(i => ({ option: i.json.name })) }] } }}')\` — complex JSON inside {{ }}
35
+
36
+ When \`$json\` is unsafe - use \`nodeJson(node, 'path')\` or \`$('NodeName').item.json.path\` instead:
37
+
38
+ - AI Agent subnodes: memory, language model, parser, retriever, vector store, and tool subnodes do not have the same immediate predecessor context as a main-flow node.
39
+ WRONG: \`sessionKey: expr('{{ $json.chatId }}')\`
40
+ CORRECT: \`sessionKey: nodeJson(telegramTrigger, 'message.chat.id')\`
41
+ - Multi-branch fan-in: if a node receives data after IF/Switch/Merge-style branching, \`$json\` only means the current incoming item and may not contain the source field you need.
42
+ WRONG: \`expr('{{ $json.userId }}')\`
43
+ CORRECT: \`nodeJson(userLookup, 'user.id')\`
44
+ - Further-upstream data: if the value comes from any node other than the immediate main predecessor, reference that node explicitly.
45
+ WRONG: \`expr('{{ $json.email }}')\`
46
+ CORRECT: \`nodeJson(formTrigger, 'body.email')\``;
34
47
  //# sourceMappingURL=expressions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/expressions.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qJA6BiH,CAAC"}
1
+ {"version":3,"file":"expressions.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/expressions.ts"],"names":[],"mappings":";;;AAQa,QAAA,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDA0Ce,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const WORKFLOW_SDK_PATTERNS = "<linear_chain>\n```javascript\nimport { workflow, node, trigger, sticky, placeholder, newCredential, ifElse, switchCase, merge, splitInBatches, nextBatch, languageModel, memory, tool, outputParser, embedding, embeddings, vectorStore, retriever, documentLoader, textSplitter, fromAi, expr } from '@n8n/workflow-sdk';\n\n// 1. Define all nodes first\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchData = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Data', parameters: { method: 'GET', url: '...' } }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Data', parameters: {} }\n});\n\n// 2. Compose workflow\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchData)\n .to(processData);\n```\n\n</linear_chain>\n\n<independent_sources>\nWhen nodes return more than 1 item, chaining causes item multiplication: if Source A returns N items, a chained Source B runs N times instead of once.\n\n**When to use `executeOnce: true`:**\n- A node fetches data independently but is chained after another data source (prevents N\u00D7M multiplication)\n- A node should summarize/aggregate all upstream items in a single call (e.g., AI summary, send one notification)\n- A node calls an API that doesn't vary per input item\n\nFix with `executeOnce: true` (simplest) or parallel branches + Merge (when combining results):\n\n```javascript\n// sourceA outputs 10 items. sourceB outputs 10 items.\n// WRONG - processResults runs 100 times\n// startTrigger.to(sourceA.to(sourceB.to(processResults)))\n\n// FIX 1 - executeOnce: sourceB runs once regardless of input items\nconst sourceB = node({ ..., config: { ..., executeOnce: true } });\nstartTrigger.to(sourceA.to(sourceB.to(processResults)));\n\n// FIX 2 - parallel branches + Merge (combine by position)\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine', combineBy: 'combineByPosition' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(combineResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(combineResults.input(1)))\n .add(combineResults)\n .to(processResults);\n\n// FIX 3 - parallel branches + Merge (append)\nconst allResults = merge({\n version: 3.2,\n config: { name: 'All Results', parameters: { mode: 'append' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(allResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(allResults.input(1)))\n .add(allResults)\n .to(processResults);\n```\n\n</independent_sources>\n\n<zero_item_safety>\nNodes that fetch or filter data may return 0 items, which stops the entire downstream chain.\nUse `alwaysOutputData: true` on data-fetching nodes to ensure the chain continues with an empty item `{json: {}}`.\n\n```javascript\n// Data Table might be empty (fresh table, no matching rows)\nconst getReflections = node({\n type: 'n8n-nodes-base.dataTable',\n version: 1.1,\n config: {\n name: 'Get Reflections',\n alwaysOutputData: true, // Chain continues even if table is empty\n parameters: { resource: 'row', operation: 'get', returnAll: true }\n }\n});\n\n// Downstream Code node handles the empty case\nconst processData = node({\n type: 'n8n-nodes-base.code',\n version: 2,\n config: {\n name: 'Process Data',\n parameters: {\n mode: 'runOnceForAllItems',\n jsCode: \\`\nconst items = $input.all();\n// items will be [{json: {}}] if upstream had no data\nconst hasData = items.length > 0 && Object.keys(items[0].json).length > 0;\n// ... handle both cases\n\\`.trim()\n }\n }\n});\n```\n\n**When to use `alwaysOutputData: true`:**\n- Data Table with `operation: 'get'` (table may be empty or freshly created)\n- Any lookup/search/filter node whose result feeds into downstream processing\n- HTTP Request that may return an empty array\n\n**When NOT to use it:**\n- Trigger nodes (they always produce output)\n- Code nodes (handle empty input in your code logic instead)\n- Nodes at the end of the chain (no downstream to protect)\n\n</zero_item_safety>\n\n<conditional_branching>\n\n**CRITICAL:** Each branch defines a COMPLETE processing path. Chain multiple steps INSIDE the branch using .to().\n\nEvery IF/Filter `conditions` parameter MUST include `options`, `conditions`, and `combinator`:\n```javascript\nconst checkValid = ifElse({\n version: 2.2,\n config: {\n name: 'Check Valid',\n parameters: {\n conditions: {\n options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' },\n conditions: [{ leftValue: '={{ $json.status }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'active' }],\n combinator: 'and'\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(checkValid\n .onTrue(formatData.to(enrichData.to(saveToDb))) // Chain 3 nodes on true branch\n .onFalse(logError));\n```\n\n</conditional_branching>\n\n<multi_way_routing>\n\nSwitch rules use `rules.values` (NOT `rules.rules`). Each rule needs `outputKey` and a complete `conditions` object:\n```javascript\nconst routeByPriority = switchCase({\n version: 3.2,\n config: {\n name: 'Route by Priority',\n parameters: {\n rules: {\n values: [\n { outputKey: 'urgent', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'urgent' }], combinator: 'and' } },\n { outputKey: 'normal', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'normal' }], combinator: 'and' } },\n ]\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(routeByPriority\n .onCase('urgent', processUrgent.to(notifyTeam.to(escalate)))\n .onCase('normal', processNormal)\n .onDefault(archive));\n```\n\n</multi_way_routing>\n\n<parallel_execution>\n```javascript\n// First declare the Merge node using merge()\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine' } }\n});\n\n// Declare branch nodes\nconst branch1 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst branch2 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst processResults = node({ type: 'n8n-nodes-base.set', ... });\n\n// Connect branches to specific merge inputs using .input(n)\nexport default workflow('id', 'name')\n .add(trigger({ ... }))\n .to(branch1.to(combineResults.input(0))) // Connect to input 0\n .add(trigger({ ... }))\n .to(branch2.to(combineResults.input(1))) // Connect to input 1\n .add(combineResults)\n .to(processResults); // Process merged results\n```\n\n</parallel_execution>\n\n<batch_processing>\n```javascript\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchRecords = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Records', parameters: { method: 'GET', url: '...' } }\n});\n\nconst finalizeResults = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Finalize', parameters: {} }\n});\n\nconst processRecord = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Process Record', parameters: { method: 'POST', url: '...' } }\n});\n\nconst sibNode = splitInBatches({ version: 3, config: { name: 'Batch Process', parameters: { batchSize: 10 } } });\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchRecords)\n .to(sibNode\n .onDone(finalizeResults)\n .onEachBatch(processRecord.to(nextBatch(sibNode)))\n );\n```\n\n</batch_processing>\n\n<multiple_triggers>\n```javascript\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook' }\n});\n\nconst processWebhook = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Webhook', parameters: {} }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule', parameters: {} }\n});\n\nconst processSchedule = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Schedule', parameters: {} }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processWebhook)\n .add(scheduleTrigger)\n .to(processSchedule);\n```\n\n</multiple_triggers>\n\n<fan_in>\n```javascript\n// Each trigger's execution runs in COMPLETE ISOLATION.\n// Different branches have no effect on each other.\n// Never duplicate chains for \"isolation\" - it's already guaranteed.\n\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook Trigger' }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule' }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: { name: 'Process Data', parameters: {} }\n});\n\nconst sendNotification = node({\n type: 'n8n-nodes-base.slack',\n version: 2.3,\n config: { name: 'Notify Slack', parameters: {} }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processData)\n .to(sendNotification)\n .add(scheduleTrigger)\n .to(processData);\n```\n\n</fan_in>\n\n<ai_agent_basic>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: { name: 'OpenAI Model', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'AI Assistant',\n parameters: { promptType: 'define', text: 'You are a helpful assistant' },\n subnodes: { model: openAiModel }\n }\n});\n\nexport default workflow('ai-assistant', 'AI Assistant')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_basic>\n\n<ai_agent_with_tools>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst calculatorTool = tool({\n type: '@n8n/n8n-nodes-langchain.toolCalculator',\n version: 1,\n config: { name: 'Calculator', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Math Agent',\n parameters: { promptType: 'define', text: 'You can use tools to help users' },\n subnodes: { model: openAiModel, tools: [calculatorTool] }\n }\n});\n\nexport default workflow('ai-calculator', 'AI Calculator')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_with_tools>\n\n<ai_agent_with_from_ai>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst gmailTool = tool({\n type: 'n8n-nodes-base.gmailTool',\n version: 1,\n config: {\n name: 'Gmail Tool',\n parameters: {\n sendTo: fromAi('recipient', 'Email address'),\n subject: fromAi('subject', 'Email subject'),\n message: fromAi('body', 'Email content')\n },\n credentials: { gmailOAuth2: newCredential('Gmail') }\n }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Email Agent',\n parameters: { promptType: 'define', text: 'You can send emails' },\n subnodes: { model: openAiModel, tools: [gmailTool] }\n }\n});\n\nexport default workflow('ai-email', 'AI Email Sender')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_from_ai>\n\n<ai_agent_with_structured_output>\n```javascript\nconst structuredParser = outputParser({\n type: '@n8n/n8n-nodes-langchain.outputParserStructured',\n version: 1.3,\n config: {\n name: 'Structured Output Parser',\n parameters: {\n schemaType: 'fromJson',\n jsonSchemaExample: '{ \"sentiment\": \"positive\", \"confidence\": 0.95, \"summary\": \"brief summary\" }'\n }\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Sentiment Analyzer',\n parameters: { promptType: 'define', text: 'Analyze the sentiment of the input text', hasOutputParser: true },\n subnodes: { model: openAiModel, outputParser: structuredParser }\n }\n});\n\nexport default workflow('ai-sentiment', 'AI Sentiment Analyzer')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_structured_output>";
1
+ export declare const WORKFLOW_SDK_PATTERNS = "<linear_chain>\n```javascript\nimport { workflow, node, trigger, sticky, placeholder, newCredential, ifElse, switchCase, merge, splitInBatches, nextBatch, languageModel, memory, tool, outputParser, embedding, embeddings, vectorStore, retriever, documentLoader, textSplitter, fromAi, expr } from '@n8n/workflow-sdk';\n\n// 1. Define all nodes first\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchData = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Data', parameters: { method: 'GET', url: '...' } }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Data',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'processed-title', name: 'processedTitle', value: expr('{{ $json.title }}'), type: 'string' }\n ]\n }\n }\n }\n});\n\n// 2. Compose workflow\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchData)\n .to(processData);\n```\n\n</linear_chain>\n\n<independent_sources>\nWhen nodes return more than 1 item, chaining causes item multiplication: if Source A returns N items, a chained Source B runs N times instead of once.\n\n**When to use `executeOnce: true`:**\n- A node fetches data independently but is chained after another data source (prevents N\u00D7M multiplication)\n- A node should summarize/aggregate all upstream items in a single call (e.g., AI summary, send one notification)\n- A node calls an API that doesn't vary per input item\n\nFix with `executeOnce: true` (simplest) or parallel branches + Merge (when combining results):\n\n```javascript\n// sourceA outputs 10 items. sourceB outputs 10 items.\n// WRONG - processResults runs 100 times\n// startTrigger.to(sourceA.to(sourceB.to(processResults)))\n\n// FIX 1 - executeOnce: sourceB runs once regardless of input items\nconst sourceB = node({ ..., config: { ..., executeOnce: true } });\nstartTrigger.to(sourceA.to(sourceB.to(processResults)));\n\n// FIX 2 - parallel branches + Merge (combine by position)\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine', combineBy: 'combineByPosition' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(combineResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(combineResults.input(1)))\n .add(combineResults)\n .to(processResults);\n\n// FIX 3 - parallel branches + Merge (append)\nconst allResults = merge({\n version: 3.2,\n config: { name: 'All Results', parameters: { mode: 'append' } }\n});\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(sourceA.to(allResults.input(0)))\n .add(startTrigger)\n .to(sourceB.to(allResults.input(1)))\n .add(allResults)\n .to(processResults);\n```\n\n</independent_sources>\n\n<zero_item_safety>\nWhen a node returns 0 items, downstream nodes are skipped for that execution. **This is usually the correct behavior** \u2014 the scheduler / trigger fires again later, and when there is data, the chain runs normally. Don't paper over an empty result with `alwaysOutputData: true` by default.\n\n**`alwaysOutputData: true` forces a synthetic `{json: {}}` item downstream.** This is a footgun: downstream nodes will try to read fields that don't exist, HTTP requests will hit `GET undefined`, and loops will run once on a fake item. Use it *only* when the empty case has its own dedicated branch that you want to execute.\n\n**Correct pattern \u2014 no `alwaysOutputData`:**\n```javascript\n// Scheduler that processes pending work\nworkflow('ingest', 'Ingest Worker')\n .add(scheduleTrigger) // fires every 5 min\n .to(getPending) // returns 0..N rows; no alwaysOutputData\n .to(splitInBatches({version: 3, config: {parameters: {batchSize: 1}}})\n .onEachBatch(fetchUrl.to(embed).to(saveChunk))\n );\n// On runs where getPending returns 0 items, the loop simply doesn't execute.\n// On runs where it returns rows, the loop iterates. No gate, no filter needed.\n```\n\n**Correct pattern \u2014 empty case needs its own branch:**\n```javascript\n// \"No matches found\" deserves a notification\nconst search = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.4,\n config: {\n name: 'Search',\n alwaysOutputData: true, // empty-case branch below needs to execute\n parameters: { /* ... */ }\n }\n});\nconst hasResults = ifElse({\n version: 2.2,\n config: {\n name: 'Has Results?',\n parameters: {\n conditions: {\n options: { caseSensitive: true, typeValidation: 'loose' },\n conditions: [{ leftValue: '={{ $json.results }}', operator: { type: 'array', operation: 'notEmpty' } }],\n combinator: 'and'\n }\n }\n }\n});\nworkflow('search', 'Search').add(trigger).to(search).to(\n hasResults.onTrue(processResults).onFalse(notifyNoMatches)\n);\n```\n\n**When to use `alwaysOutputData: true`:** only when you've paired it with an explicit empty-case branch, AND the downstream branch doesn't blindly read item fields.\n\n**When NOT to use it:**\n- Scheduled/polling triggers where the \"no work\" case should silently skip\n- Before a `splitInBatches` loop \u2014 loops already no-op on empty input\n- Before a `filter` \u2014 the filter already no-ops on empty input\n- When all you'd do on the empty case is \"nothing\"\n\n**Don't gate loops with an `IF`.** `ifElse.onTrue(splitInBatches)` to check \"are there items?\" is redundant \u2014 the loop already does the right thing with 0 items. Drop the IF.\n\n</zero_item_safety>\n\n<conditional_branching>\n\n**CRITICAL:** Each branch defines a COMPLETE processing path. Chain multiple steps INSIDE the branch using .to().\n\nEvery IF/Filter `conditions` parameter MUST include `options`, `conditions`, and `combinator`:\n```javascript\nconst checkValid = ifElse({\n version: 2.2,\n config: {\n name: 'Check Valid',\n parameters: {\n conditions: {\n options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' },\n conditions: [{ leftValue: '={{ $json.status }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'active' }],\n combinator: 'and'\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(checkValid\n .onTrue(formatData.to(enrichData.to(saveToDb))) // Chain 3 nodes on true branch\n .onFalse(logError));\n```\n\n</conditional_branching>\n\n<multi_way_routing>\n\nSwitch rules use `rules.values` (NOT `rules.rules`). Each rule needs `outputKey` and a complete `conditions` object:\n```javascript\nconst routeByPriority = switchCase({\n version: 3.2,\n config: {\n name: 'Route by Priority',\n parameters: {\n rules: {\n values: [\n { outputKey: 'urgent', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'urgent' }], combinator: 'and' } },\n { outputKey: 'normal', conditions: { options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' }, conditions: [{ leftValue: '={{ $json.priority }}', operator: { type: 'string', operation: 'equals' }, rightValue: 'normal' }], combinator: 'and' } },\n ]\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(routeByPriority\n .onCase('urgent', processUrgent.to(notifyTeam.to(escalate)))\n .onCase('normal', processNormal)\n .onDefault(archive));\n```\n\n</multi_way_routing>\n\n<parallel_execution>\n```javascript\n// First declare the Merge node using merge()\nconst combineResults = merge({\n version: 3.2,\n config: { name: 'Combine Results', parameters: { mode: 'combine' } }\n});\n\n// Declare branch nodes\nconst branch1 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst branch2 = node({ type: 'n8n-nodes-base.httpRequest', ... });\nconst processResults = node({ type: 'n8n-nodes-base.set', ... });\n\n// Connect branches to specific merge inputs using .input(n)\nexport default workflow('id', 'name')\n .add(trigger({ ... }))\n .to(branch1.to(combineResults.input(0))) // Connect to input 0\n .add(trigger({ ... }))\n .to(branch2.to(combineResults.input(1))) // Connect to input 1\n .add(combineResults)\n .to(processResults); // Process merged results\n```\n\n</parallel_execution>\n\n<batch_processing>\n```javascript\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst fetchRecords = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Fetch Records', parameters: { method: 'GET', url: '...' } }\n});\n\nconst finalizeResults = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Finalize',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'processed-at', name: 'processedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }\n ]\n }\n }\n }\n});\n\nconst processRecord = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: { name: 'Process Record', parameters: { method: 'POST', url: '...' } }\n});\n\nconst sibNode = splitInBatches({ version: 3, config: { name: 'Batch Process', parameters: { batchSize: 10 } } });\n\nexport default workflow('id', 'name')\n .add(startTrigger)\n .to(fetchRecords)\n .to(sibNode\n .onDone(finalizeResults)\n .onEachBatch(processRecord.to(nextBatch(sibNode)))\n );\n```\n\n</batch_processing>\n\n<multiple_triggers>\n```javascript\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook' }\n});\n\nconst processWebhook = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Webhook',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'source', name: 'source', value: 'webhook', type: 'string' }\n ]\n }\n }\n }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule', parameters: {} }\n});\n\nconst processSchedule = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Schedule',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'source', name: 'source', value: 'schedule', type: 'string' }\n ]\n }\n }\n }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processWebhook)\n .add(scheduleTrigger)\n .to(processSchedule);\n```\n\n</multiple_triggers>\n\n<fan_in>\n```javascript\n// Each trigger's execution runs in COMPLETE ISOLATION.\n// Different branches have no effect on each other.\n// Never duplicate chains for \"isolation\" - it's already guaranteed.\n\nconst webhookTrigger = trigger({\n type: 'n8n-nodes-base.webhook',\n version: 2.1,\n config: { name: 'Webhook Trigger' }\n});\n\nconst scheduleTrigger = trigger({\n type: 'n8n-nodes-base.scheduleTrigger',\n version: 1.3,\n config: { name: 'Daily Schedule' }\n});\n\nconst processData = node({\n type: 'n8n-nodes-base.set',\n version: 3.4,\n config: {\n name: 'Process Data',\n parameters: {\n mode: 'manual',\n includeOtherFields: true,\n assignments: {\n assignments: [\n { id: 'received-at', name: 'receivedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }\n ]\n }\n }\n }\n});\n\nconst sendNotification = node({\n type: 'n8n-nodes-base.slack',\n version: 2.3,\n config: { name: 'Notify Slack', parameters: {} }\n});\n\nexport default workflow('id', 'name')\n .add(webhookTrigger)\n .to(processData)\n .to(sendNotification)\n .add(scheduleTrigger)\n .to(processData);\n```\n\n</fan_in>\n\n<ai_agent_basic>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: { name: 'OpenAI Model', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'AI Assistant',\n parameters: { promptType: 'define', text: 'You are a helpful assistant' },\n subnodes: { model: openAiModel }\n }\n});\n\nexport default workflow('ai-assistant', 'AI Assistant')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_basic>\n\n<ai_agent_with_tools>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst calculatorTool = tool({\n type: '@n8n/n8n-nodes-langchain.toolCalculator',\n version: 1,\n config: { name: 'Calculator', parameters: {} }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Math Agent',\n parameters: { promptType: 'define', text: 'You can use tools to help users' },\n subnodes: { model: openAiModel, tools: [calculatorTool] }\n }\n});\n\nexport default workflow('ai-calculator', 'AI Calculator')\n .add(startTrigger)\n .to(aiAgent);\n```\n\n</ai_agent_with_tools>\n\n<ai_agent_with_from_ai>\n```javascript\nconst openAiModel = languageModel({\n type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',\n version: 1.3,\n config: {\n name: 'OpenAI Model',\n parameters: {},\n credentials: { openAiApi: newCredential('OpenAI') }\n }\n});\n\nconst gmailTool = tool({\n type: 'n8n-nodes-base.gmailTool',\n version: 1,\n config: {\n name: 'Gmail Tool',\n parameters: {\n sendTo: fromAi('recipient', 'Email address'),\n subject: fromAi('subject', 'Email subject'),\n message: fromAi('body', 'Email content')\n },\n credentials: { gmailOAuth2: newCredential('Gmail') }\n }\n});\n\nconst startTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Start' }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Email Agent',\n parameters: { promptType: 'define', text: 'You can send emails' },\n subnodes: { model: openAiModel, tools: [gmailTool] }\n }\n});\n\nexport default workflow('ai-email', 'AI Email Sender')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_from_ai>\n\n<ai_agent_with_structured_output>\n```javascript\nconst structuredParser = outputParser({\n type: '@n8n/n8n-nodes-langchain.outputParserStructured',\n version: 1.3,\n config: {\n name: 'Structured Output Parser',\n parameters: {\n schemaType: 'fromJson',\n jsonSchemaExample: '{ \"sentiment\": \"positive\", \"confidence\": 0.95, \"summary\": \"brief summary\" }'\n }\n }\n});\n\nconst aiAgent = node({\n type: '@n8n/n8n-nodes-langchain.agent',\n version: 3.1,\n config: {\n name: 'Sentiment Analyzer',\n parameters: { promptType: 'define', text: 'Analyze the sentiment of the input text', hasOutputParser: true },\n subnodes: { model: openAiModel, outputParser: structuredParser }\n }\n});\n\nexport default workflow('ai-sentiment', 'AI Sentiment Analyzer')\n .add(startTrigger)\n .to(aiAgent);\n```\n</ai_agent_with_structured_output>";
2
2
  export { WORKFLOW_SDK_PATTERNS as WORKFLOW_PATTERNS_CONCISE };