@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.
- package/dist/ast-interpreter/validators.js +2 -0
- package/dist/ast-interpreter/validators.js.map +1 -1
- package/dist/build.tsbuildinfo +1 -1
- package/dist/codegen/parse-workflow-code.js +1 -0
- package/dist/codegen/parse-workflow-code.js.map +1 -1
- package/dist/expression/index.d.ts +4 -1
- package/dist/expression/index.js +30 -0
- package/dist/expression/index.js.map +1 -1
- package/dist/expression.d.ts +1 -1
- package/dist/expression.js +2 -1
- package/dist/expression.js.map +1 -1
- package/dist/generate-types/generate-types.d.ts +9 -0
- package/dist/generate-types/generate-types.js +208 -51
- package/dist/generate-types/generate-types.js.map +1 -1
- package/dist/generate-types/generate-zod-schemas.d.ts +6 -1
- package/dist/generate-types/generate-zod-schemas.js +199 -90
- package/dist/generate-types/generate-zod-schemas.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/prompts/best-practices/guides/chatbot.js +11 -0
- package/dist/prompts/best-practices/guides/chatbot.js.map +1 -1
- package/dist/prompts/best-practices/guides/web-app.d.ts +7 -0
- package/dist/prompts/best-practices/guides/web-app.js +206 -0
- package/dist/prompts/best-practices/guides/web-app.js.map +1 -0
- package/dist/prompts/best-practices/index.d.ts +1 -0
- package/dist/prompts/best-practices/index.js +5 -1
- package/dist/prompts/best-practices/index.js.map +1 -1
- package/dist/prompts/best-practices/types.d.ts +1 -0
- package/dist/prompts/best-practices/types.js +2 -0
- package/dist/prompts/best-practices/types.js.map +1 -1
- package/dist/prompts/node-guidance/parameter-guides/predecessor-output.js +7 -0
- package/dist/prompts/node-guidance/parameter-guides/predecessor-output.js.map +1 -1
- package/dist/prompts/sdk-reference/additional-functions.d.ts +1 -1
- package/dist/prompts/sdk-reference/additional-functions.js +3 -0
- package/dist/prompts/sdk-reference/additional-functions.js.map +1 -1
- package/dist/prompts/sdk-reference/expressions.d.ts +1 -1
- package/dist/prompts/sdk-reference/expressions.js +14 -1
- package/dist/prompts/sdk-reference/expressions.js.map +1 -1
- package/dist/prompts/sdk-reference/workflow-patterns.d.ts +1 -1
- package/dist/prompts/sdk-reference/workflow-patterns.js +102 -34
- package/dist/prompts/sdk-reference/workflow-patterns.js.map +1 -1
- package/dist/prompts/sdk-reference/workflow-rules.d.ts +1 -1
- package/dist/prompts/sdk-reference/workflow-rules.js +14 -7
- package/dist/prompts/sdk-reference/workflow-rules.js.map +1 -1
- package/dist/types/base.d.ts +14 -5
- package/dist/types/base.js +43 -0
- 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 +1 -1
- package/dist/validation/index.js +125 -2
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/resolve-schema.d.ts +14 -0
- package/dist/validation/resolve-schema.js +20 -0
- package/dist/validation/resolve-schema.js.map +1 -1
- package/dist/validation/schema-helpers.d.ts +2 -2
- package/dist/validation/schema-helpers.js +2 -1
- package/dist/validation/schema-helpers.js.map +1 -1
- package/dist/validation/schema-validator.js +1 -0
- package/dist/validation/schema-validator.js.map +1 -1
- package/dist/workflow-builder/connection-utils.js +1 -1
- package/dist/workflow-builder/connection-utils.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 +28 -0
- package/dist/workflow-builder/control-flow-builders/split-in-batches.js.map +1 -1
- package/dist/workflow-builder/control-flow-builders/switch-case.d.ts +1 -2
- package/dist/workflow-builder/node-builders/node-builder.d.ts +1 -0
- package/dist/workflow-builder/node-builders/node-builder.js +41 -2
- package/dist/workflow-builder/node-builders/node-builder.js.map +1 -1
- package/dist/workflow-builder/node-builders/subnode-builders.js +2 -1
- package/dist/workflow-builder/node-builders/subnode-builders.js.map +1 -1
- package/dist/workflow-builder/pin-data-utils.js.map +1 -1
- package/dist/workflow-builder/plugins/defaults.js +1 -0
- package/dist/workflow-builder/plugins/defaults.js.map +1 -1
- package/dist/workflow-builder/plugins/serializers/json-serializer.js +14 -1
- package/dist/workflow-builder/plugins/serializers/json-serializer.js.map +1 -1
- package/dist/workflow-builder/plugins/validators/index.d.ts +1 -0
- package/dist/workflow-builder/plugins/validators/index.js +3 -1
- package/dist/workflow-builder/plugins/validators/index.js.map +1 -1
- package/dist/workflow-builder/plugins/validators/memory-session-key-validator.d.ts +2 -0
- package/dist/workflow-builder/plugins/validators/memory-session-key-validator.js +50 -0
- package/dist/workflow-builder/plugins/validators/memory-session-key-validator.js.map +1 -0
- package/dist/workflow-builder/plugins/validators/set-node-validator.js +110 -4
- package/dist/workflow-builder/plugins/validators/set-node-validator.js.map +1 -1
- package/dist/workflow-builder/validation-helpers.d.ts +1 -0
- package/dist/workflow-builder/validation-helpers.js +7 -0
- package/dist/workflow-builder/validation-helpers.js.map +1 -1
- package/dist/workflow-builder/workflow-import.js +1 -0
- package/dist/workflow-builder/workflow-import.js.map +1 -1
- package/dist/workflow-builder.js +8 -2
- package/dist/workflow-builder.js.map +1 -1
- package/package.json +2 -2
|
@@ -22,7 +22,18 @@ const fetchData = node({
|
|
|
22
22
|
const processData = node({
|
|
23
23
|
type: 'n8n-nodes-base.set',
|
|
24
24
|
version: 3.4,
|
|
25
|
-
config: {
|
|
25
|
+
config: {
|
|
26
|
+
name: 'Process Data',
|
|
27
|
+
parameters: {
|
|
28
|
+
mode: 'manual',
|
|
29
|
+
includeOtherFields: true,
|
|
30
|
+
assignments: {
|
|
31
|
+
assignments: [
|
|
32
|
+
{ id: 'processed-title', name: 'processedTitle', value: expr('{{ $json.title }}'), type: 'string' }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
26
37
|
});
|
|
27
38
|
|
|
28
39
|
// 2. Compose workflow
|
|
@@ -83,49 +94,62 @@ export default workflow('id', 'name')
|
|
|
83
94
|
</independent_sources>
|
|
84
95
|
|
|
85
96
|
<zero_item_safety>
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
When a node returns 0 items, downstream nodes are skipped for that execution. **This is usually the correct behavior** — the scheduler / trigger fires again later, and when there is data, the chain runs normally. Don't paper over an empty result with \`alwaysOutputData: true\` by default.
|
|
98
|
+
|
|
99
|
+
**\`alwaysOutputData: true\` forces a synthetic \`{json: {}}\` item downstream.** This is a footgun: downstream nodes will try to read fields that don't exist, HTTP requests will hit \`GET undefined\`, and loops will run once on a fake item. Use it *only* when the empty case has its own dedicated branch that you want to execute.
|
|
100
|
+
|
|
101
|
+
**Correct pattern — no \`alwaysOutputData\`:**
|
|
102
|
+
\`\`\`javascript
|
|
103
|
+
// Scheduler that processes pending work
|
|
104
|
+
workflow('ingest', 'Ingest Worker')
|
|
105
|
+
.add(scheduleTrigger) // fires every 5 min
|
|
106
|
+
.to(getPending) // returns 0..N rows; no alwaysOutputData
|
|
107
|
+
.to(splitInBatches({version: 3, config: {parameters: {batchSize: 1}}})
|
|
108
|
+
.onEachBatch(fetchUrl.to(embed).to(saveChunk))
|
|
109
|
+
);
|
|
110
|
+
// On runs where getPending returns 0 items, the loop simply doesn't execute.
|
|
111
|
+
// On runs where it returns rows, the loop iterates. No gate, no filter needed.
|
|
112
|
+
\`\`\`
|
|
88
113
|
|
|
114
|
+
**Correct pattern — empty case needs its own branch:**
|
|
89
115
|
\`\`\`javascript
|
|
90
|
-
//
|
|
91
|
-
const
|
|
92
|
-
type: 'n8n-nodes-base.
|
|
93
|
-
version:
|
|
116
|
+
// "No matches found" deserves a notification
|
|
117
|
+
const search = node({
|
|
118
|
+
type: 'n8n-nodes-base.httpRequest',
|
|
119
|
+
version: 4.4,
|
|
94
120
|
config: {
|
|
95
|
-
name: '
|
|
96
|
-
alwaysOutputData: true,
|
|
97
|
-
parameters: {
|
|
121
|
+
name: 'Search',
|
|
122
|
+
alwaysOutputData: true, // empty-case branch below needs to execute
|
|
123
|
+
parameters: { /* ... */ }
|
|
98
124
|
}
|
|
99
125
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const processData = node({
|
|
103
|
-
type: 'n8n-nodes-base.code',
|
|
104
|
-
version: 2,
|
|
126
|
+
const hasResults = ifElse({
|
|
127
|
+
version: 2.2,
|
|
105
128
|
config: {
|
|
106
|
-
name: '
|
|
129
|
+
name: 'Has Results?',
|
|
107
130
|
parameters: {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// ... handle both cases
|
|
114
|
-
\\\`.trim()
|
|
131
|
+
conditions: {
|
|
132
|
+
options: { caseSensitive: true, typeValidation: 'loose' },
|
|
133
|
+
conditions: [{ leftValue: '={{ $json.results }}', operator: { type: 'array', operation: 'notEmpty' } }],
|
|
134
|
+
combinator: 'and'
|
|
135
|
+
}
|
|
115
136
|
}
|
|
116
137
|
}
|
|
117
138
|
});
|
|
139
|
+
workflow('search', 'Search').add(trigger).to(search).to(
|
|
140
|
+
hasResults.onTrue(processResults).onFalse(notifyNoMatches)
|
|
141
|
+
);
|
|
118
142
|
\`\`\`
|
|
119
143
|
|
|
120
|
-
**When to use \`alwaysOutputData: true\`:**
|
|
121
|
-
- Data Table with \`operation: 'get'\` (table may be empty or freshly created)
|
|
122
|
-
- Any lookup/search/filter node whose result feeds into downstream processing
|
|
123
|
-
- HTTP Request that may return an empty array
|
|
144
|
+
**When to use \`alwaysOutputData: true\`:** only when you've paired it with an explicit empty-case branch, AND the downstream branch doesn't blindly read item fields.
|
|
124
145
|
|
|
125
146
|
**When NOT to use it:**
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
147
|
+
- Scheduled/polling triggers where the "no work" case should silently skip
|
|
148
|
+
- Before a \`splitInBatches\` loop — loops already no-op on empty input
|
|
149
|
+
- Before a \`filter\` — the filter already no-ops on empty input
|
|
150
|
+
- When all you'd do on the empty case is "nothing"
|
|
151
|
+
|
|
152
|
+
**Don't gate loops with an \`IF\`.** \`ifElse.onTrue(splitInBatches)\` to check "are there items?" is redundant — the loop already does the right thing with 0 items. Drop the IF.
|
|
129
153
|
|
|
130
154
|
</zero_item_safety>
|
|
131
155
|
|
|
@@ -229,7 +253,18 @@ const fetchRecords = node({
|
|
|
229
253
|
const finalizeResults = node({
|
|
230
254
|
type: 'n8n-nodes-base.set',
|
|
231
255
|
version: 3.4,
|
|
232
|
-
config: {
|
|
256
|
+
config: {
|
|
257
|
+
name: 'Finalize',
|
|
258
|
+
parameters: {
|
|
259
|
+
mode: 'manual',
|
|
260
|
+
includeOtherFields: true,
|
|
261
|
+
assignments: {
|
|
262
|
+
assignments: [
|
|
263
|
+
{ id: 'processed-at', name: 'processedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
233
268
|
});
|
|
234
269
|
|
|
235
270
|
const processRecord = node({
|
|
@@ -262,7 +297,18 @@ const webhookTrigger = trigger({
|
|
|
262
297
|
const processWebhook = node({
|
|
263
298
|
type: 'n8n-nodes-base.set',
|
|
264
299
|
version: 3.4,
|
|
265
|
-
config: {
|
|
300
|
+
config: {
|
|
301
|
+
name: 'Process Webhook',
|
|
302
|
+
parameters: {
|
|
303
|
+
mode: 'manual',
|
|
304
|
+
includeOtherFields: true,
|
|
305
|
+
assignments: {
|
|
306
|
+
assignments: [
|
|
307
|
+
{ id: 'source', name: 'source', value: 'webhook', type: 'string' }
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
266
312
|
});
|
|
267
313
|
|
|
268
314
|
const scheduleTrigger = trigger({
|
|
@@ -274,7 +320,18 @@ const scheduleTrigger = trigger({
|
|
|
274
320
|
const processSchedule = node({
|
|
275
321
|
type: 'n8n-nodes-base.set',
|
|
276
322
|
version: 3.4,
|
|
277
|
-
config: {
|
|
323
|
+
config: {
|
|
324
|
+
name: 'Process Schedule',
|
|
325
|
+
parameters: {
|
|
326
|
+
mode: 'manual',
|
|
327
|
+
includeOtherFields: true,
|
|
328
|
+
assignments: {
|
|
329
|
+
assignments: [
|
|
330
|
+
{ id: 'source', name: 'source', value: 'schedule', type: 'string' }
|
|
331
|
+
]
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
278
335
|
});
|
|
279
336
|
|
|
280
337
|
export default workflow('id', 'name')
|
|
@@ -307,7 +364,18 @@ const scheduleTrigger = trigger({
|
|
|
307
364
|
const processData = node({
|
|
308
365
|
type: 'n8n-nodes-base.set',
|
|
309
366
|
version: 3.4,
|
|
310
|
-
config: {
|
|
367
|
+
config: {
|
|
368
|
+
name: 'Process Data',
|
|
369
|
+
parameters: {
|
|
370
|
+
mode: 'manual',
|
|
371
|
+
includeOtherFields: true,
|
|
372
|
+
assignments: {
|
|
373
|
+
assignments: [
|
|
374
|
+
{ id: 'received-at', name: 'receivedAt', value: expr('{{ $now.toISO() }}'), type: 'string' }
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
311
379
|
});
|
|
312
380
|
|
|
313
381
|
const sendNotification = node({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-patterns.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-patterns.ts"],"names":[],"mappings":";;;AAUA,MAAM,UAAU,GACf,8RAA8R,CAAC;AAMnR,QAAA,qBAAqB,GAAG;;EAEnC,UAAU
|
|
1
|
+
{"version":3,"file":"workflow-patterns.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-patterns.ts"],"names":[],"mappings":";;;AAUA,MAAM,UAAU,GACf,8RAA8R,CAAC;AAMnR,QAAA,qBAAqB,GAAG;;EAEnC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA0hBuB,CAAC;AAEF,oCA9hBrB,6BAAqB,CA8hByB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const WORKFLOW_RULES = "Follow these rules strictly when generating workflows:\n\n1. **Always use newCredential() for authentication**\n - When a node needs credentials, always use `newCredential('Name')` in the credentials config\n - NEVER use placeholder strings, fake API keys, or hardcoded auth values\n - Example: `credentials: { slackApi: newCredential('Slack Bot') }`\n - The credential type must match what the node expects\n\n2. **
|
|
1
|
+
export declare const WORKFLOW_RULES = "Follow these rules strictly when generating workflows:\n\n1. **Always use newCredential() for authentication**\n - When a node needs credentials, always use `newCredential('Name')` in the credentials config\n - NEVER use placeholder strings, fake API keys, or hardcoded auth values\n - Example: `credentials: { slackApi: newCredential('Slack Bot') }`\n - The credential type must match what the node expects\n\n2. **Trust empty item lists \u2014 don't synthesize fake items**\n - When a query returns 0 items, downstream nodes simply don't run for that execution. For scheduled or polling triggers this is the correct \"nothing to do this round\" signal \u2014 the next run will execute normally when data appears.\n - DO NOT add `alwaysOutputData: true` just to \"keep the chain alive.\" Forcing an empty `{}` item downstream is what causes `undefined` reads, failed HTTP calls to `GET undefined`, and Code-node crashes on missing fields.\n - DO NOT add an IF gate before a loop to check \"has items?\" \u2014 loops (`splitInBatches`, per-item nodes, `filter`) already no-op on empty input. The gate is redundant and adds a failure surface.\n - `alwaysOutputData: true` is only correct when you specifically need a downstream branch to run on the \"empty\" case \u2014 e.g. a dedicated \"no matches found\" notification path. In that case, pair it with an `IF` that explicitly checks for the empty case and routes accordingly. Never use it as a default.\n - To drop invalid items mid-pipeline, use a `filter` node. A `filter` that rejects everything emits 0 items and the chain correctly stops \u2014 no `IF` + `splitInBatches` composition needed.\n\n3. **Use `executeOnce: true` for single-execution nodes**\n - When a node receives N items but should only execute once (not N times), set `executeOnce: true`\n - Common cases: sending a summary notification, generating a report, calling an API that doesn't need per-item execution\n - Example: `config: { ..., executeOnce: true }`\n\n4. **Pick the right control-flow primitive**\n - **Per-item loop with side effects (fetch, embed, write)** \u2192 `splitInBatches` with `batchSize: 1` feeding the per-item work, loop back via `nextBatch`. No `IF` gate before it.\n - **Drop items that don't match a predicate** \u2192 `filter`. It emits 0 items when nothing matches, and the chain stops cleanly.\n - **Two mutually exclusive paths that both do real work** \u2192 `IF` (`onTrue` / `onFalse`).\n - **Many mutually exclusive paths keyed off a value** \u2192 `switch` (`onCase`).\n - Nested control flow is supported: `ifNode.onTrue(loopBuilder)`, `switchNode.onCase(0, loopBuilder)`, and `splitInBatches(sib).onEachBatch(ifElseBuilder)` all compile and wire correctly. Use them when the semantics genuinely call for it, not as a workaround for empty-list handling.";
|
|
@@ -9,15 +9,22 @@ exports.WORKFLOW_RULES = `Follow these rules strictly when generating workflows:
|
|
|
9
9
|
- Example: \`credentials: { slackApi: newCredential('Slack Bot') }\`
|
|
10
10
|
- The credential type must match what the node expects
|
|
11
11
|
|
|
12
|
-
2. **
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
12
|
+
2. **Trust empty item lists — don't synthesize fake items**
|
|
13
|
+
- When a query returns 0 items, downstream nodes simply don't run for that execution. For scheduled or polling triggers this is the correct "nothing to do this round" signal — the next run will execute normally when data appears.
|
|
14
|
+
- DO NOT add \`alwaysOutputData: true\` just to "keep the chain alive." Forcing an empty \`{}\` item downstream is what causes \`undefined\` reads, failed HTTP calls to \`GET undefined\`, and Code-node crashes on missing fields.
|
|
15
|
+
- DO NOT add an IF gate before a loop to check "has items?" — loops (\`splitInBatches\`, per-item nodes, \`filter\`) already no-op on empty input. The gate is redundant and adds a failure surface.
|
|
16
|
+
- \`alwaysOutputData: true\` is only correct when you specifically need a downstream branch to run on the "empty" case — e.g. a dedicated "no matches found" notification path. In that case, pair it with an \`IF\` that explicitly checks for the empty case and routes accordingly. Never use it as a default.
|
|
17
|
+
- To drop invalid items mid-pipeline, use a \`filter\` node. A \`filter\` that rejects everything emits 0 items and the chain correctly stops — no \`IF\` + \`splitInBatches\` composition needed.
|
|
18
18
|
|
|
19
19
|
3. **Use \`executeOnce: true\` for single-execution nodes**
|
|
20
20
|
- When a node receives N items but should only execute once (not N times), set \`executeOnce: true\`
|
|
21
21
|
- Common cases: sending a summary notification, generating a report, calling an API that doesn't need per-item execution
|
|
22
|
-
- Example: \`config: { ..., executeOnce: true }
|
|
22
|
+
- Example: \`config: { ..., executeOnce: true }\`
|
|
23
|
+
|
|
24
|
+
4. **Pick the right control-flow primitive**
|
|
25
|
+
- **Per-item loop with side effects (fetch, embed, write)** → \`splitInBatches\` with \`batchSize: 1\` feeding the per-item work, loop back via \`nextBatch\`. No \`IF\` gate before it.
|
|
26
|
+
- **Drop items that don't match a predicate** → \`filter\`. It emits 0 items when nothing matches, and the chain stops cleanly.
|
|
27
|
+
- **Two mutually exclusive paths that both do real work** → \`IF\` (\`onTrue\` / \`onFalse\`).
|
|
28
|
+
- **Many mutually exclusive paths keyed off a value** → \`switch\` (\`onCase\`).
|
|
29
|
+
- Nested control flow is supported: \`ifNode.onTrue(loopBuilder)\`, \`switchNode.onCase(0, loopBuilder)\`, and \`splitInBatches(sib).onEachBatch(ifElseBuilder)\` all compile and wire correctly. Use them when the semantics genuinely call for it, not as a workaround for empty-list handling.`;
|
|
23
30
|
//# sourceMappingURL=workflow-rules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-rules.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-rules.ts"],"names":[],"mappings":";;;AAQa,QAAA,cAAc,GAAG
|
|
1
|
+
{"version":3,"file":"workflow-rules.js","sourceRoot":"","sources":["../../../src/prompts/sdk-reference/workflow-rules.ts"],"names":[],"mappings":";;;AAQa,QAAA,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;qSAyBuQ,CAAC"}
|
package/dist/types/base.d.ts
CHANGED
|
@@ -57,6 +57,13 @@ export interface INodeConnections {
|
|
|
57
57
|
export interface IConnections {
|
|
58
58
|
[key: string]: INodeConnections;
|
|
59
59
|
}
|
|
60
|
+
interface ErrorIndexNodeInfo {
|
|
61
|
+
name?: string;
|
|
62
|
+
type: string;
|
|
63
|
+
parameters?: IDataObject;
|
|
64
|
+
}
|
|
65
|
+
export declare function resolveErrorOutputIndex(type: string, parameters?: IDataObject): number;
|
|
66
|
+
export declare function foldLegacyErrorConnections(connections: IConnections, nodes?: readonly ErrorIndexNodeInfo[]): void;
|
|
60
67
|
export declare function normalizeConnections(connections: IConnections): void;
|
|
61
68
|
export declare function generateUniqueName(baseName: string, exists: (name: string) => boolean): string;
|
|
62
69
|
export interface NodeJSON {
|
|
@@ -129,7 +136,7 @@ export interface WorkflowContext {
|
|
|
129
136
|
}
|
|
130
137
|
export interface NodeConfig<TParams = IDataObject> {
|
|
131
138
|
parameters?: TParams;
|
|
132
|
-
credentials?: Record<string, CredentialReference | NewCredentialValue>;
|
|
139
|
+
credentials?: Record<string, CredentialReference | NewCredentialValue | PlaceholderValue>;
|
|
133
140
|
name?: string;
|
|
134
141
|
position?: [number, number];
|
|
135
142
|
webhookId?: string;
|
|
@@ -229,8 +236,9 @@ export interface SwitchCaseComposite {
|
|
|
229
236
|
readonly switchNode: NodeInstance<'n8n-nodes-base.switch', string, unknown>;
|
|
230
237
|
readonly cases: Array<NodeInstance<string, string, unknown> | Array<NodeInstance<string, string, unknown>> | null>;
|
|
231
238
|
}
|
|
232
|
-
export type IfElseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown>;
|
|
233
|
-
export type SwitchCaseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown>;
|
|
239
|
+
export type IfElseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown> | SplitInBatchesBuilder<unknown>;
|
|
240
|
+
export type SwitchCaseTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown> | SplitInBatchesBuilder<unknown>;
|
|
241
|
+
export type SplitInBatchesTarget = null | NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>> | Array<NodeInstance<string, string, unknown> | NodeChain<NodeInstance<string, string, unknown>, NodeInstance<string, string, unknown>>> | IfElseBuilder<unknown> | SwitchCaseBuilder<unknown> | SplitInBatchesBuilder<unknown>;
|
|
234
242
|
export interface IfElseBuilder<TOutput = unknown> {
|
|
235
243
|
readonly _isIfElseBuilder: true;
|
|
236
244
|
readonly ifNode: NodeInstance<'n8n-nodes-base.if', string, TOutput>;
|
|
@@ -259,8 +267,8 @@ export interface SplitInBatchesConfig extends NodeConfig {
|
|
|
259
267
|
}
|
|
260
268
|
export interface SplitInBatchesBuilder<TOutput = unknown> {
|
|
261
269
|
readonly sibNode: NodeInstance<'n8n-nodes-base.splitInBatches', string, unknown>;
|
|
262
|
-
onEachBatch(target:
|
|
263
|
-
onDone(target:
|
|
270
|
+
onEachBatch(target: SplitInBatchesTarget): SplitInBatchesBuilder<TOutput>;
|
|
271
|
+
onDone(target: SplitInBatchesTarget): SplitInBatchesBuilder<TOutput>;
|
|
264
272
|
}
|
|
265
273
|
export interface GeneratePinDataOptions {
|
|
266
274
|
beforeWorkflow?: WorkflowJSON;
|
|
@@ -372,3 +380,4 @@ export type RunOnceForEachItemFn = <T = unknown>(fn: (ctx: EachItemContext) => {
|
|
|
372
380
|
json: T;
|
|
373
381
|
} | null) => CodeResult<T>;
|
|
374
382
|
export type FromAIArgumentType = 'string' | 'number' | 'boolean' | 'json';
|
|
383
|
+
export {};
|
package/dist/types/base.js
CHANGED
|
@@ -1,9 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveErrorOutputIndex = resolveErrorOutputIndex;
|
|
4
|
+
exports.foldLegacyErrorConnections = foldLegacyErrorConnections;
|
|
3
5
|
exports.normalizeConnections = normalizeConnections;
|
|
4
6
|
exports.generateUniqueName = generateUniqueName;
|
|
5
7
|
exports.isNodeChain = isNodeChain;
|
|
6
8
|
exports.isNodeInstance = isNodeInstance;
|
|
9
|
+
function resolveErrorOutputIndex(type, parameters) {
|
|
10
|
+
if (type === 'n8n-nodes-base.if')
|
|
11
|
+
return 2;
|
|
12
|
+
if (type === 'n8n-nodes-base.switch') {
|
|
13
|
+
const rules = parameters?.rules;
|
|
14
|
+
const rulesArray = rules?.rules ?? rules?.values;
|
|
15
|
+
const numCases = Array.isArray(rulesArray) ? rulesArray.length : 4;
|
|
16
|
+
return numCases + 1;
|
|
17
|
+
}
|
|
18
|
+
if (type === 'n8n-nodes-base.splitInBatches')
|
|
19
|
+
return 2;
|
|
20
|
+
if (type === 'n8n-nodes-base.merge')
|
|
21
|
+
return 1;
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
function foldLegacyErrorConnections(connections, nodes) {
|
|
25
|
+
const nodeLookup = new Map();
|
|
26
|
+
if (nodes) {
|
|
27
|
+
for (const n of nodes) {
|
|
28
|
+
if (n.name)
|
|
29
|
+
nodeLookup.set(n.name, n);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const [nodeName, nodeConns] of Object.entries(connections)) {
|
|
33
|
+
const errorOutputs = nodeConns.error;
|
|
34
|
+
if (!Array.isArray(errorOutputs) || errorOutputs.length === 0)
|
|
35
|
+
continue;
|
|
36
|
+
const errorTargets = errorOutputs[0];
|
|
37
|
+
delete nodeConns.error;
|
|
38
|
+
if (!Array.isArray(errorTargets) || errorTargets.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const node = nodeLookup.get(nodeName);
|
|
41
|
+
const targetIndex = node ? resolveErrorOutputIndex(node.type, node.parameters) : 1;
|
|
42
|
+
const main = Array.isArray(nodeConns.main) ? nodeConns.main : [];
|
|
43
|
+
while (main.length < targetIndex)
|
|
44
|
+
main.push([]);
|
|
45
|
+
const existing = main[targetIndex] ?? [];
|
|
46
|
+
main[targetIndex] = [...existing, ...errorTargets];
|
|
47
|
+
nodeConns.main = main;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
7
50
|
function normalizeConnections(connections) {
|
|
8
51
|
for (const nodeConns of Object.values(connections)) {
|
|
9
52
|
for (const [connType, outputs] of Object.entries(nodeConns)) {
|
package/dist/types/base.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/types/base.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/types/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -80,15 +80,23 @@ function checkConditions(conditions, actualValues) {
|
|
|
80
80
|
return propertyValue >= from && propertyValue <= to;
|
|
81
81
|
}
|
|
82
82
|
if (key === 'includes') {
|
|
83
|
+
if (typeof propertyValue !== 'string')
|
|
84
|
+
return false;
|
|
83
85
|
return propertyValue.includes(targetValue);
|
|
84
86
|
}
|
|
85
87
|
if (key === 'startsWith') {
|
|
88
|
+
if (typeof propertyValue !== 'string')
|
|
89
|
+
return false;
|
|
86
90
|
return propertyValue.startsWith(targetValue);
|
|
87
91
|
}
|
|
88
92
|
if (key === 'endsWith') {
|
|
93
|
+
if (typeof propertyValue !== 'string')
|
|
94
|
+
return false;
|
|
89
95
|
return propertyValue.endsWith(targetValue);
|
|
90
96
|
}
|
|
91
97
|
if (key === 'regex') {
|
|
98
|
+
if (typeof propertyValue !== 'string')
|
|
99
|
+
return false;
|
|
92
100
|
return new RegExp(targetValue).test(propertyValue);
|
|
93
101
|
}
|
|
94
102
|
if (key === 'exists') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-options.js","sourceRoot":"","sources":["../../src/validation/display-options.ts"],"names":[],"mappings":";;;;;AA2JA,
|
|
1
|
+
{"version":3,"file":"display-options.js","sourceRoot":"","sources":["../../src/validation/display-options.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { INodeTypes } from 'n8n-workflow';
|
|
2
2
|
import type { WorkflowBuilder, WorkflowJSON } from '../types/base';
|
|
3
3
|
export { setSchemaBaseDirs } from './schema-validator';
|
|
4
|
-
export type ValidationErrorCode = 'NO_NODES' | 'MISSING_TRIGGER' | 'DISCONNECTED_NODE' | 'MISSING_PARAMETER' | 'INVALID_CONNECTION' | 'CIRCULAR_REFERENCE' | 'INVALID_EXPRESSION' | 'AGENT_STATIC_PROMPT' | 'AGENT_NO_SYSTEM_MESSAGE' | 'HARDCODED_CREDENTIALS' | 'SET_CREDENTIAL_FIELD' | 'MERGE_SINGLE_INPUT' | 'TOOL_NO_PARAMETERS' | 'FROM_AI_IN_NON_TOOL' | 'MISSING_EXPRESSION_PREFIX' | 'INVALID_PARAMETER' | 'INVALID_INPUT_INDEX' | 'SUBNODE_NOT_CONNECTED' | 'SUBNODE_PARAMETER_MISMATCH' | 'UNSUPPORTED_SUBNODE_INPUT' | 'MAX_NODES_EXCEEDED' | 'INVALID_EXPRESSION_PATH' | 'PARTIAL_EXPRESSION_PATH' | 'INVALID_DATE_METHOD';
|
|
4
|
+
export type ValidationErrorCode = 'NO_NODES' | 'MISSING_TRIGGER' | 'DISCONNECTED_NODE' | 'MISSING_PARAMETER' | 'INVALID_CONNECTION' | 'CIRCULAR_REFERENCE' | 'INVALID_EXPRESSION' | 'AGENT_STATIC_PROMPT' | 'AGENT_NO_SYSTEM_MESSAGE' | 'HARDCODED_CREDENTIALS' | 'SET_CREDENTIAL_FIELD' | 'MERGE_SINGLE_INPUT' | 'TOOL_NO_PARAMETERS' | 'FROM_AI_IN_NON_TOOL' | 'MISSING_EXPRESSION_PREFIX' | 'INVALID_PARAMETER' | 'INVALID_INPUT_INDEX' | 'SUBNODE_NOT_CONNECTED' | 'SUBNODE_PARAMETER_MISMATCH' | 'UNSUPPORTED_SUBNODE_INPUT' | 'MISSING_REQUIRED_INPUT' | 'INVALID_OUTPUT_FOR_MODE' | 'MAX_NODES_EXCEEDED' | 'INVALID_EXPRESSION_PATH' | 'PARTIAL_EXPRESSION_PATH' | 'INVALID_DATE_METHOD';
|
|
5
5
|
export declare class ValidationError {
|
|
6
6
|
readonly code: ValidationErrorCode;
|
|
7
7
|
readonly message: string;
|
package/dist/validation/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.ValidationWarning = exports.ValidationError = exports.setSchemaBaseDirs = void 0;
|
|
4
7
|
exports.validateWorkflow = validateWorkflow;
|
|
8
|
+
const get_1 = __importDefault(require("lodash/get"));
|
|
5
9
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
10
|
const display_options_1 = require("./display-options");
|
|
7
11
|
const input_resolver_1 = require("./input-resolver");
|
|
@@ -244,6 +248,8 @@ function validateWorkflow(workflowOrJson, options = {}) {
|
|
|
244
248
|
checkNodeInputIndices(json, options.nodeTypesProvider, warnings);
|
|
245
249
|
validateSubnodeParameters(json, options.nodeTypesProvider, warnings);
|
|
246
250
|
validateParentSupportsInputs(json, options.nodeTypesProvider, warnings);
|
|
251
|
+
validateRequiredInputsConnected(json, options.nodeTypesProvider, errors);
|
|
252
|
+
validateOutputUsage(json, options.nodeTypesProvider, warnings);
|
|
247
253
|
}
|
|
248
254
|
checkMergeNodeInputCount(json, warnings);
|
|
249
255
|
return {
|
|
@@ -341,6 +347,9 @@ function validateSubnodeParameters(json, nodeTypesProvider, warnings) {
|
|
|
341
347
|
continue;
|
|
342
348
|
const { matches, mismatches } = checkDisplayOptionsMatch(subnode.parameters, inputConfig.displayOptions);
|
|
343
349
|
if (!matches) {
|
|
350
|
+
const subnodeOwnsAnyParam = mismatches.some((m) => m.actual !== undefined);
|
|
351
|
+
if (!subnodeOwnsAnyParam)
|
|
352
|
+
continue;
|
|
344
353
|
const sdkFn = AI_CONNECTION_TO_SDK_FUNCTION[connectionType] || connectionType;
|
|
345
354
|
const mismatchDetails = mismatches
|
|
346
355
|
.map((m) => `${m.param}='${String(m.actual)}' (expected: ${m.expected.map((v) => `'${String(v)}'`).join(' or ')})`)
|
|
@@ -359,12 +368,22 @@ function buildConditionSummary(displayOptions, parentParams) {
|
|
|
359
368
|
for (const [paramName, expectedValues] of Object.entries(displayOptions.show)) {
|
|
360
369
|
if (!expectedValues)
|
|
361
370
|
continue;
|
|
362
|
-
const actual = parentParams
|
|
371
|
+
const actual = (0, get_1.default)(parentParams, paramName);
|
|
363
372
|
const expectedStr = expectedValues.map((v) => `'${String(v)}'`).join(' or ');
|
|
364
373
|
parts.push(`${paramName} should be ${expectedStr} (currently '${String(actual)}')`);
|
|
365
374
|
}
|
|
366
375
|
return parts.length > 0 ? `Required: ${parts.join(', ')}.` : '';
|
|
367
376
|
}
|
|
377
|
+
function buildTriggeringConditionSummary(displayOptions, parentParams) {
|
|
378
|
+
if (!displayOptions.show)
|
|
379
|
+
return '';
|
|
380
|
+
const parts = [];
|
|
381
|
+
for (const [paramName, _expectedValues] of Object.entries(displayOptions.show)) {
|
|
382
|
+
const actual = (0, get_1.default)(parentParams, paramName);
|
|
383
|
+
parts.push(`${paramName}='${String(actual)}'`);
|
|
384
|
+
}
|
|
385
|
+
return parts.join(', ');
|
|
386
|
+
}
|
|
368
387
|
function validateParentSupportsInputs(json, nodeTypesProvider, warnings) {
|
|
369
388
|
const nodesByName = new Map();
|
|
370
389
|
for (const node of json.nodes) {
|
|
@@ -406,12 +425,116 @@ function validateParentSupportsInputs(json, nodeTypesProvider, warnings) {
|
|
|
406
425
|
const subnodeName = conn.node;
|
|
407
426
|
const subnodeField = AI_CONNECTION_TO_SUBNODE_FIELD[connectionType] || connectionType;
|
|
408
427
|
const conditionDetails = buildConditionSummary(inputConfig.displayOptions, (parentNode.parameters ?? {}));
|
|
409
|
-
warnings.push(new ValidationWarning('UNSUPPORTED_SUBNODE_INPUT', `'${
|
|
428
|
+
warnings.push(new ValidationWarning('UNSUPPORTED_SUBNODE_INPUT', `'${parentNode.name}' has a ${subnodeField} subnode ('${subnodeName}') connected, but its current configuration does not accept one. ${conditionDetails} These parameters must be set on '${parentNode.name}' itself, NOT on the ${subnodeField} subnode. Alternatively, remove the ${subnodeField} connection if this capability isn't needed.`, parentNode.name, undefined, undefined, 'major'));
|
|
410
429
|
}
|
|
411
430
|
}
|
|
412
431
|
}
|
|
413
432
|
}
|
|
414
433
|
}
|
|
434
|
+
function validateRequiredInputsConnected(json, nodeTypesProvider, errors) {
|
|
435
|
+
const connectionsByDest = (0, n8n_workflow_1.mapConnectionsByDestination)(json.connections);
|
|
436
|
+
for (const parentNode of json.nodes) {
|
|
437
|
+
if (!parentNode.name)
|
|
438
|
+
continue;
|
|
439
|
+
const version = typeof parentNode.typeVersion === 'string'
|
|
440
|
+
? parseFloat(parentNode.typeVersion)
|
|
441
|
+
: (parentNode.typeVersion ?? 1);
|
|
442
|
+
const parentNodeType = nodeTypesProvider.getByNameAndVersion(parentNode.type, version);
|
|
443
|
+
const builderHintInputs = parentNodeType?.description?.builderHint?.inputs;
|
|
444
|
+
if (!builderHintInputs)
|
|
445
|
+
continue;
|
|
446
|
+
const parentContext = {
|
|
447
|
+
parameters: (parentNode.parameters ?? {}),
|
|
448
|
+
nodeVersion: version,
|
|
449
|
+
rootParameters: (parentNode.parameters ?? {}),
|
|
450
|
+
};
|
|
451
|
+
for (const [connectionType, inputConfig] of Object.entries(builderHintInputs)) {
|
|
452
|
+
if (!connectionType.startsWith('ai_'))
|
|
453
|
+
continue;
|
|
454
|
+
if (!inputConfig?.required)
|
|
455
|
+
continue;
|
|
456
|
+
if (inputConfig.displayOptions) {
|
|
457
|
+
const conditionsMet = (0, display_options_1.matchesDisplayOptions)(parentContext, inputConfig.displayOptions);
|
|
458
|
+
if (!conditionsMet)
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const incoming = connectionsByDest[parentNode.name]?.[connectionType];
|
|
462
|
+
const hasConnection = Array.isArray(incoming) && incoming.some((slot) => Array.isArray(slot) && slot.length > 0);
|
|
463
|
+
if (hasConnection)
|
|
464
|
+
continue;
|
|
465
|
+
const subnodeField = AI_CONNECTION_TO_SUBNODE_FIELD[connectionType] || connectionType;
|
|
466
|
+
const triggerDetails = inputConfig.displayOptions
|
|
467
|
+
? ` (triggered by ${buildTriggeringConditionSummary(inputConfig.displayOptions, (parentNode.parameters ?? {}))})`
|
|
468
|
+
: '';
|
|
469
|
+
const alternative = inputConfig.displayOptions
|
|
470
|
+
? ` Either connect a ${subnodeField} subnode, or change those parameters to remove the requirement.`
|
|
471
|
+
: '';
|
|
472
|
+
errors.push(new ValidationError('MISSING_REQUIRED_INPUT', `'${parentNode.name}' requires a ${subnodeField} subnode connected to its ${connectionType} input${triggerDetails}, but none is connected.${alternative}`, parentNode.name, undefined, 'major'));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function describeOutputWiring(connectionType, target = false) {
|
|
477
|
+
if (connectionType === 'main')
|
|
478
|
+
return target ? '.to(...)' : 'wired with .to()';
|
|
479
|
+
const field = AI_CONNECTION_TO_SUBNODE_FIELD[connectionType];
|
|
480
|
+
if (field)
|
|
481
|
+
return target ? `subnodes.${field}` : `attached as subnodes.${field}`;
|
|
482
|
+
return target ? connectionType : `connected via ${connectionType}`;
|
|
483
|
+
}
|
|
484
|
+
function findEnabledAlternativeOutput(outputsHint, ctx, excludeType) {
|
|
485
|
+
for (const [type, cfg] of Object.entries(outputsHint)) {
|
|
486
|
+
if (type === excludeType)
|
|
487
|
+
continue;
|
|
488
|
+
if (!cfg)
|
|
489
|
+
continue;
|
|
490
|
+
if (cfg.displayOptions && !(0, display_options_1.matchesDisplayOptions)(ctx, cfg.displayOptions)) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
return type;
|
|
494
|
+
}
|
|
495
|
+
return undefined;
|
|
496
|
+
}
|
|
497
|
+
function validateOutputUsage(json, nodeTypesProvider, warnings) {
|
|
498
|
+
for (const sourceNode of json.nodes) {
|
|
499
|
+
if (!sourceNode.name)
|
|
500
|
+
continue;
|
|
501
|
+
const outgoing = json.connections[sourceNode.name];
|
|
502
|
+
if (!outgoing)
|
|
503
|
+
continue;
|
|
504
|
+
const version = typeof sourceNode.typeVersion === 'string'
|
|
505
|
+
? parseFloat(sourceNode.typeVersion)
|
|
506
|
+
: (sourceNode.typeVersion ?? 1);
|
|
507
|
+
const nodeType = nodeTypesProvider.getByNameAndVersion(sourceNode.type, version);
|
|
508
|
+
const outputsHint = nodeType?.description?.builderHint?.outputs;
|
|
509
|
+
if (!outputsHint)
|
|
510
|
+
continue;
|
|
511
|
+
const ctx = {
|
|
512
|
+
parameters: (sourceNode.parameters ?? {}),
|
|
513
|
+
nodeVersion: version,
|
|
514
|
+
rootParameters: (sourceNode.parameters ?? {}),
|
|
515
|
+
};
|
|
516
|
+
for (const [connectionType, cfg] of Object.entries(outputsHint)) {
|
|
517
|
+
if (!cfg?.displayOptions)
|
|
518
|
+
continue;
|
|
519
|
+
const edges = outgoing[connectionType];
|
|
520
|
+
if (!edges)
|
|
521
|
+
continue;
|
|
522
|
+
const hasEdges = edges.some((slot) => Array.isArray(slot) && slot.length > 0);
|
|
523
|
+
if (!hasEdges)
|
|
524
|
+
continue;
|
|
525
|
+
const enabled = (0, display_options_1.matchesDisplayOptions)(ctx, cfg.displayOptions);
|
|
526
|
+
if (enabled)
|
|
527
|
+
continue;
|
|
528
|
+
const conditionDetails = buildConditionSummary(cfg.displayOptions, (sourceNode.parameters ?? {}));
|
|
529
|
+
const usedWiring = describeOutputWiring(connectionType);
|
|
530
|
+
const enabledAlt = findEnabledAlternativeOutput(outputsHint, ctx, connectionType);
|
|
531
|
+
const altSuggestion = enabledAlt
|
|
532
|
+
? ` To use this node as-is, attach it as ${describeOutputWiring(enabledAlt, true)} of a parent (it exposes ${enabledAlt} in this configuration).`
|
|
533
|
+
: '';
|
|
534
|
+
warnings.push(new ValidationWarning('INVALID_OUTPUT_FOR_MODE', `'${sourceNode.name}' is ${usedWiring} but its current parameters disable that output. ${conditionDetails}${altSuggestion}`, sourceNode.name, undefined, undefined, 'major'));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
415
538
|
function checkNodeInputIndices(json, nodeTypesProvider, warnings) {
|
|
416
539
|
const nodesByName = new Map();
|
|
417
540
|
for (const node of json.nodes) {
|