@withone/cli 1.40.0 → 1.41.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.
|
@@ -811,7 +811,29 @@ async function executeActionStep(step, context, api, permissions, allowedActionI
|
|
|
811
811
|
const action = step.action;
|
|
812
812
|
const platform = resolveValue(action.platform, context);
|
|
813
813
|
const actionId = resolveValue(action.actionId, context);
|
|
814
|
-
const
|
|
814
|
+
const hasKey = action.connectionKey !== void 0 && action.connectionKey !== null && action.connectionKey !== "";
|
|
815
|
+
const hasRef = !!action.connection?.platform;
|
|
816
|
+
if (hasKey && hasRef) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
`Action step "${step.id}" has both "connectionKey" and "connection" \u2014 set exactly one. Prefer "connection: { platform, tag? }" so re-auth doesn't break the flow.`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
if (!hasKey && !hasRef) {
|
|
822
|
+
throw new Error(
|
|
823
|
+
`Action step "${step.id}" must set "connection: { platform: <name> }" (or legacy "connectionKey: <key>").`
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
let connectionKey;
|
|
827
|
+
if (hasKey) {
|
|
828
|
+
connectionKey = resolveValue(action.connectionKey, context);
|
|
829
|
+
} else {
|
|
830
|
+
const ref = resolveValue(action.connection, context);
|
|
831
|
+
if (!context._connections) {
|
|
832
|
+
context._connections = await api.listConnections();
|
|
833
|
+
}
|
|
834
|
+
const conn = await api.resolveConnection(ref, context._connections);
|
|
835
|
+
connectionKey = conn.key;
|
|
836
|
+
}
|
|
815
837
|
const data = action.data ? resolveValue(action.data, context) : void 0;
|
|
816
838
|
const pathVars = action.pathVars ? resolveValue(action.pathVars, context) : void 0;
|
|
817
839
|
const queryParams = action.queryParams ? resolveValue(action.queryParams, context) : void 0;
|
|
@@ -1115,7 +1137,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
1115
1137
|
if (flowStack.includes(resolvedKey)) {
|
|
1116
1138
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
1117
1139
|
}
|
|
1118
|
-
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-
|
|
1140
|
+
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-26LNPU4F.js");
|
|
1119
1141
|
const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
|
|
1120
1142
|
const subContext = await executeFlow(
|
|
1121
1143
|
subFlow,
|
|
@@ -1661,7 +1683,8 @@ var FLOW_SCHEMA = {
|
|
|
1661
1683
|
fields: {
|
|
1662
1684
|
platform: { type: "string", required: true, description: "Platform name (kebab-case)" },
|
|
1663
1685
|
actionId: { type: "string", required: true, description: "Action ID from `actions search`" },
|
|
1664
|
-
|
|
1686
|
+
connection: { type: "object", required: false, description: "Late-bound connection ref { platform, tag? } \u2014 survives re-auth. Exactly one of `connection` or `connectionKey` must be set." },
|
|
1687
|
+
connectionKey: { type: "string", required: false, description: "Literal connection key (or $.input selector). Legacy form \u2014 prefer `connection: { platform, tag? }`. Exactly one of `connection` or `connectionKey` must be set." },
|
|
1665
1688
|
data: { type: "object", required: false, description: "Request body (POST/PUT/PATCH)" },
|
|
1666
1689
|
pathVars: { type: "object", required: false, description: "URL path variables" },
|
|
1667
1690
|
queryParams: { type: "object", required: false, description: "Query parameters" },
|
|
@@ -1674,7 +1697,7 @@ var FLOW_SCHEMA = {
|
|
|
1674
1697
|
action: {
|
|
1675
1698
|
platform: "stripe",
|
|
1676
1699
|
actionId: "conn_mod_def::xxx::yyy",
|
|
1677
|
-
|
|
1700
|
+
connection: { platform: "stripe" },
|
|
1678
1701
|
data: { query: "email:'{{$.input.customerEmail}}'" }
|
|
1679
1702
|
}
|
|
1680
1703
|
}
|
|
@@ -1723,7 +1746,7 @@ var FLOW_SCHEMA = {
|
|
|
1723
1746
|
type: "condition",
|
|
1724
1747
|
condition: {
|
|
1725
1748
|
expression: "$.steps.search.response.data.length > 0",
|
|
1726
|
-
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...",
|
|
1749
|
+
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...", connection: { platform: "slack" }, data: { text: "Found!" } } }],
|
|
1727
1750
|
else: [{ id: "logMiss", name: "Log not found", type: "transform", transform: { expression: "'Not found'" } }]
|
|
1728
1751
|
}
|
|
1729
1752
|
}
|
|
@@ -1747,7 +1770,7 @@ var FLOW_SCHEMA = {
|
|
|
1747
1770
|
loop: {
|
|
1748
1771
|
over: "$.steps.listOrders.response.data",
|
|
1749
1772
|
as: "order",
|
|
1750
|
-
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...",
|
|
1773
|
+
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...", connection: { platform: "stripe" }, data: { amount: "$.loop.order.total" } } }]
|
|
1751
1774
|
}
|
|
1752
1775
|
}
|
|
1753
1776
|
},
|
|
@@ -1765,8 +1788,8 @@ var FLOW_SCHEMA = {
|
|
|
1765
1788
|
type: "parallel",
|
|
1766
1789
|
parallel: {
|
|
1767
1790
|
steps: [
|
|
1768
|
-
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...",
|
|
1769
|
-
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...",
|
|
1791
|
+
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...", connection: { platform: "stripe" } } },
|
|
1792
|
+
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...", connection: { platform: "slack" } } }
|
|
1770
1793
|
]
|
|
1771
1794
|
}
|
|
1772
1795
|
}
|
|
@@ -1818,7 +1841,7 @@ var FLOW_SCHEMA = {
|
|
|
1818
1841
|
while: {
|
|
1819
1842
|
condition: "$.steps.paginate.output.lastResult.nextPageToken != null",
|
|
1820
1843
|
maxIterations: 50,
|
|
1821
|
-
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...",
|
|
1844
|
+
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...", connection: { platform: "gmail" } } }]
|
|
1822
1845
|
}
|
|
1823
1846
|
}
|
|
1824
1847
|
},
|
|
@@ -1842,7 +1865,7 @@ var FLOW_SCHEMA = {
|
|
|
1842
1865
|
configKey: "paginate",
|
|
1843
1866
|
description: "Auto-paginate API results into a single array",
|
|
1844
1867
|
fields: {
|
|
1845
|
-
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, connectionKey)" },
|
|
1868
|
+
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, plus exactly one of `connection` or `connectionKey`)" },
|
|
1846
1869
|
pageTokenField: { type: "string", required: true, description: "Dot-path in response to next page token" },
|
|
1847
1870
|
resultsField: { type: "string", required: true, description: "Dot-path in response to results array" },
|
|
1848
1871
|
inputTokenParam: { type: "string", required: true, description: "Dot-path in action config where page token is injected" },
|
|
@@ -1853,7 +1876,7 @@ var FLOW_SCHEMA = {
|
|
|
1853
1876
|
name: "Fetch all Gmail messages",
|
|
1854
1877
|
type: "paginate",
|
|
1855
1878
|
paginate: {
|
|
1856
|
-
action: { platform: "gmail", actionId: "...",
|
|
1879
|
+
action: { platform: "gmail", actionId: "...", connection: { platform: "gmail" }, queryParams: { maxResults: 100 } },
|
|
1857
1880
|
pageTokenField: "nextPageToken",
|
|
1858
1881
|
resultsField: "messages",
|
|
1859
1882
|
inputTokenParam: "queryParams.pageToken",
|
|
@@ -2051,12 +2074,6 @@ The only differences: (1) prepend the stdin-read line, (2) replace \`return X\`
|
|
|
2051
2074
|
"description": "What this flow does",
|
|
2052
2075
|
"version": "1",
|
|
2053
2076
|
"inputs": {
|
|
2054
|
-
"connectionKey": {
|
|
2055
|
-
"type": "string",
|
|
2056
|
-
"required": true,
|
|
2057
|
-
"description": "Platform connection key",
|
|
2058
|
-
"connection": { "platform": "stripe" }
|
|
2059
|
-
},
|
|
2060
2077
|
"param": {
|
|
2061
2078
|
"type": "string",
|
|
2062
2079
|
"required": true,
|
|
@@ -2071,7 +2088,7 @@ The only differences: (1) prepend the stdin-read line, (2) replace \`return X\`
|
|
|
2071
2088
|
"action": {
|
|
2072
2089
|
"platform": "stripe",
|
|
2073
2090
|
"actionId": "conn_mod_def::xxx::yyy",
|
|
2074
|
-
"
|
|
2091
|
+
"connection": { "platform": "stripe" },
|
|
2075
2092
|
"data": { "query": "{{$.input.param}}" }
|
|
2076
2093
|
}
|
|
2077
2094
|
}
|
|
@@ -2165,13 +2182,13 @@ Pipes can be applied to any value (objects/arrays are JSON-stringified first for
|
|
|
2165
2182
|
|
|
2166
2183
|
### When to use bare selectors vs \`{{...}}\` interpolation
|
|
2167
2184
|
|
|
2168
|
-
- **Bare selectors** (\`$.input.x\`): Use for fields the engine resolves directly \u2014 \`connectionKey\`, \`over\`, \`path\`, \`expression\`, \`condition\`, and any field where the entire value is a single selector. The resolved value keeps its original type (object, array, number).
|
|
2185
|
+
- **Bare selectors** (\`$.input.x\`): Use for fields the engine resolves directly \u2014 \`connectionKey\`, the \`tag\` (or \`platform\`) inside \`connection\`, \`over\`, \`path\`, \`expression\`, \`condition\`, and any field where the entire value is a single selector. The resolved value keeps its original type (object, array, number).
|
|
2169
2186
|
- **Interpolation** (\`{{$.input.x}}\`): Use inside string values where the selector is embedded in text \u2014 e.g., \`"Hello {{$.steps.getUser.response.name}}"\`. The resolved value is always stringified. Use this in \`data\`, \`pathVars\`, and \`queryParams\` when mixing selectors with literal text.
|
|
2170
2187
|
- **Rule of thumb**: If the value is purely a selector, use bare. If it's a string containing a selector, use \`{{...}}\`.
|
|
2171
2188
|
|
|
2172
2189
|
### Selectors vs expressions
|
|
2173
2190
|
|
|
2174
|
-
Selectors in data fields (\`data\`, \`queryParams\`, \`pathVars\`, \`connectionKey\`) are **dot-path lookups only** \u2014 they do not support JavaScript operators like \`||\` or \`&&\`. For default values, use the \`default\` field on the input definition:
|
|
2191
|
+
Selectors in data fields (\`data\`, \`queryParams\`, \`pathVars\`, \`connectionKey\`, \`connection.tag\`) are **dot-path lookups only** \u2014 they do not support JavaScript operators like \`||\` or \`&&\`. For default values, use the \`default\` field on the input definition:
|
|
2175
2192
|
|
|
2176
2193
|
\`\`\`json
|
|
2177
2194
|
{ "inputs": { "maxResults": { "type": "number", "default": 10 } } }
|
|
@@ -2291,9 +2308,30 @@ A \`flow\` step's \`flow.key\` accepts selectors and Handlebars interpolations,
|
|
|
2291
2308
|
|
|
2292
2309
|
Conditional execution: \`"if": "$.steps.prev.response.data.length > 0"\`
|
|
2293
2310
|
|
|
2294
|
-
##
|
|
2311
|
+
## Connection Resolution \u2014 late-bound by default
|
|
2312
|
+
|
|
2313
|
+
Action steps reference a platform connection in one of two forms:
|
|
2314
|
+
|
|
2315
|
+
\`\`\`json
|
|
2316
|
+
// preferred \u2014 late-bound, survives re-auth
|
|
2317
|
+
"connection": { "platform": "gmail" }
|
|
2318
|
+
|
|
2319
|
+
// multi-account: disambiguate with the connection's tag
|
|
2320
|
+
"connection": { "platform": "gmail", "tag": "work@example.com" }
|
|
2321
|
+
|
|
2322
|
+
// legacy \u2014 works for backwards compat, breaks on re-auth
|
|
2323
|
+
"connectionKey": "live::gmail::default::abc123..."
|
|
2324
|
+
\`\`\`
|
|
2325
|
+
|
|
2326
|
+
The engine resolves the \`connection\` ref once per flow run (cached for the run's lifetime) by calling \`listConnections\` and matching on platform + optional tag. Resolution errors fail the step with a clear message \u2014 \`No connection found for platform "X"\`, \`Multiple "X" connections found (tags: ...). Add a "tag" field\`, or \`No "X" connection has tag "Y"\`.
|
|
2327
|
+
|
|
2328
|
+
Both \`platform\` and \`tag\` accept \`$.input.x\` selectors so a flow can be parameterised per-execution (e.g. multi-tenant orchestrators that pass the user's email as the tag).
|
|
2329
|
+
|
|
2330
|
+
The validator rejects any action that sets both forms or neither, at \`flow validate\` and \`flow execute\` time.
|
|
2331
|
+
|
|
2332
|
+
### Optional input metadata: connection-key auto-resolve (legacy)
|
|
2295
2333
|
|
|
2296
|
-
|
|
2334
|
+
For flows that still use literal \`connectionKey\` strings via inputs, an input declaration can carry a \`"connection": { "platform": "..." }\` hint so \`flow execute\` auto-fills a single matching connection's key. New flows don't need this \u2014 switch the action's connection form to \`{ platform, tag? }\` and skip the input entirely.
|
|
2297
2335
|
|
|
2298
2336
|
## Complete Example: Fetch Data, Transform, Notify
|
|
2299
2337
|
|
|
@@ -2304,18 +2342,6 @@ When an input has \`"connection": { "platform": "stripe" }\`, the flow engine ca
|
|
|
2304
2342
|
"description": "Fetch recent contacts from CRM, build a summary, post to Slack",
|
|
2305
2343
|
"version": "1",
|
|
2306
2344
|
"inputs": {
|
|
2307
|
-
"crmConnectionKey": {
|
|
2308
|
-
"type": "string",
|
|
2309
|
-
"required": true,
|
|
2310
|
-
"description": "CRM platform connection key",
|
|
2311
|
-
"connection": { "platform": "attio" }
|
|
2312
|
-
},
|
|
2313
|
-
"slackConnectionKey": {
|
|
2314
|
-
"type": "string",
|
|
2315
|
-
"required": true,
|
|
2316
|
-
"description": "Slack connection key",
|
|
2317
|
-
"connection": { "platform": "slack" }
|
|
2318
|
-
},
|
|
2319
2345
|
"slackChannel": {
|
|
2320
2346
|
"type": "string",
|
|
2321
2347
|
"required": true,
|
|
@@ -2330,7 +2356,7 @@ When an input has \`"connection": { "platform": "stripe" }\`, the flow engine ca
|
|
|
2330
2356
|
"action": {
|
|
2331
2357
|
"platform": "attio",
|
|
2332
2358
|
"actionId": "ATTIO_LIST_PEOPLE_ACTION_ID",
|
|
2333
|
-
"
|
|
2359
|
+
"connection": { "platform": "attio" },
|
|
2334
2360
|
"queryParams": { "limit": "10" }
|
|
2335
2361
|
}
|
|
2336
2362
|
},
|
|
@@ -2349,7 +2375,7 @@ When an input has \`"connection": { "platform": "stripe" }\`, the flow engine ca
|
|
|
2349
2375
|
"action": {
|
|
2350
2376
|
"platform": "slack",
|
|
2351
2377
|
"actionId": "SLACK_SEND_MESSAGE_ACTION_ID",
|
|
2352
|
-
"
|
|
2378
|
+
"connection": { "platform": "slack" },
|
|
2353
2379
|
"data": {
|
|
2354
2380
|
"channel": "$.input.slackChannel",
|
|
2355
2381
|
"text": "{{$.steps.buildSummary.output.summary}}"
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveFlowPath,
|
|
20
20
|
saveFlow,
|
|
21
21
|
validateActionInput
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-A5AFLKCQ.js";
|
|
23
23
|
|
|
24
24
|
// src/index.ts
|
|
25
25
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -3269,10 +3269,13 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
3269
3269
|
const a = value;
|
|
3270
3270
|
if (!a.platform) errors.push({ path: `${fieldPath}.platform`, message: 'Action must have "platform"' });
|
|
3271
3271
|
if (!a.actionId) errors.push({ path: `${fieldPath}.actionId`, message: 'Action must have "actionId"' });
|
|
3272
|
-
|
|
3272
|
+
validateConnectionForm(a, fieldPath, errors);
|
|
3273
3273
|
}
|
|
3274
3274
|
}
|
|
3275
3275
|
}
|
|
3276
|
+
if (descriptor.type === "action") {
|
|
3277
|
+
validateConnectionForm(config2, `${path17}.${configKey}`, errors);
|
|
3278
|
+
}
|
|
3276
3279
|
if (descriptor.type === "code") {
|
|
3277
3280
|
const hasSource = typeof config2.source === "string" && config2.source.length > 0;
|
|
3278
3281
|
const hasModule = typeof config2.module === "string" && config2.module.length > 0;
|
|
@@ -3300,6 +3303,43 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
3300
3303
|
}
|
|
3301
3304
|
}
|
|
3302
3305
|
}
|
|
3306
|
+
function validateConnectionForm(config2, pathPrefix, errors) {
|
|
3307
|
+
const hasKey = config2.connectionKey !== void 0 && config2.connectionKey !== null && config2.connectionKey !== "";
|
|
3308
|
+
const conn = config2.connection;
|
|
3309
|
+
const hasRef = !!conn && typeof conn === "object" && !Array.isArray(conn) && typeof conn.platform === "string" && conn.platform.length > 0;
|
|
3310
|
+
if (hasKey && hasRef) {
|
|
3311
|
+
errors.push({
|
|
3312
|
+
path: pathPrefix,
|
|
3313
|
+
message: `Action has both "connectionKey" and "connection" \u2014 set exactly one. Prefer "connection: { platform, tag? }" so re-auth doesn't break the flow.`
|
|
3314
|
+
});
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
if (!hasKey && !hasRef) {
|
|
3318
|
+
errors.push({
|
|
3319
|
+
path: pathPrefix,
|
|
3320
|
+
message: 'Action must set "connection: { platform: <name> }" (or legacy "connectionKey: <key>").'
|
|
3321
|
+
});
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
if (hasRef) {
|
|
3325
|
+
const c = conn;
|
|
3326
|
+
if (c.tag !== void 0 && typeof c.tag !== "string") {
|
|
3327
|
+
errors.push({
|
|
3328
|
+
path: `${pathPrefix}.connection.tag`,
|
|
3329
|
+
message: '"connection.tag" must be a string when set'
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
const allowed = /* @__PURE__ */ new Set(["platform", "tag"]);
|
|
3333
|
+
for (const k of Object.keys(c)) {
|
|
3334
|
+
if (!allowed.has(k)) {
|
|
3335
|
+
errors.push({
|
|
3336
|
+
path: `${pathPrefix}.connection.${k}`,
|
|
3337
|
+
message: `Unknown field "${k}" in connection ref. Allowed: platform, tag.`
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3303
3343
|
function detectFlatConfigHint(step, descriptor) {
|
|
3304
3344
|
const requiredFields = Object.entries(descriptor.fields).filter(([, fd]) => fd.required).map(([name]) => name);
|
|
3305
3345
|
const flatFields = requiredFields.filter((f) => f in step);
|
|
@@ -4107,14 +4147,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4107
4147
|
name: "My Workflow",
|
|
4108
4148
|
description: "A basic workflow with a single action step",
|
|
4109
4149
|
version: "1",
|
|
4110
|
-
inputs: {
|
|
4111
|
-
connectionKey: {
|
|
4112
|
-
type: "string",
|
|
4113
|
-
required: true,
|
|
4114
|
-
description: "Connection key for the platform",
|
|
4115
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4116
|
-
}
|
|
4117
|
-
},
|
|
4150
|
+
inputs: {},
|
|
4118
4151
|
steps: [
|
|
4119
4152
|
{
|
|
4120
4153
|
id: "step1",
|
|
@@ -4123,7 +4156,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4123
4156
|
action: {
|
|
4124
4157
|
platform: "PLATFORM_NAME",
|
|
4125
4158
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4126
|
-
|
|
4159
|
+
connection: { platform: "PLATFORM_NAME" },
|
|
4127
4160
|
data: {}
|
|
4128
4161
|
}
|
|
4129
4162
|
}
|
|
@@ -4134,14 +4167,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4134
4167
|
name: "Conditional Workflow",
|
|
4135
4168
|
description: "Fetch data, then branch based on results",
|
|
4136
4169
|
version: "1",
|
|
4137
|
-
inputs: {
|
|
4138
|
-
connectionKey: {
|
|
4139
|
-
type: "string",
|
|
4140
|
-
required: true,
|
|
4141
|
-
description: "Connection key",
|
|
4142
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4143
|
-
}
|
|
4144
|
-
},
|
|
4170
|
+
inputs: {},
|
|
4145
4171
|
steps: [
|
|
4146
4172
|
{
|
|
4147
4173
|
id: "fetch",
|
|
@@ -4150,7 +4176,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4150
4176
|
action: {
|
|
4151
4177
|
platform: "PLATFORM_NAME",
|
|
4152
4178
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4153
|
-
|
|
4179
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
4154
4180
|
}
|
|
4155
4181
|
},
|
|
4156
4182
|
{
|
|
@@ -4184,14 +4210,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4184
4210
|
name: "Loop Workflow",
|
|
4185
4211
|
description: "Fetch a list, then process each item",
|
|
4186
4212
|
version: "1",
|
|
4187
|
-
inputs: {
|
|
4188
|
-
connectionKey: {
|
|
4189
|
-
type: "string",
|
|
4190
|
-
required: true,
|
|
4191
|
-
description: "Connection key",
|
|
4192
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4193
|
-
}
|
|
4194
|
-
},
|
|
4213
|
+
inputs: {},
|
|
4195
4214
|
steps: [
|
|
4196
4215
|
{
|
|
4197
4216
|
id: "fetchList",
|
|
@@ -4200,7 +4219,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4200
4219
|
action: {
|
|
4201
4220
|
platform: "PLATFORM_NAME",
|
|
4202
4221
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4203
|
-
|
|
4222
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
4204
4223
|
}
|
|
4205
4224
|
},
|
|
4206
4225
|
{
|
|
@@ -4233,14 +4252,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4233
4252
|
name: "AI Analysis Workflow",
|
|
4234
4253
|
description: "Fetch data, analyze with Claude, and send results",
|
|
4235
4254
|
version: "1",
|
|
4236
|
-
inputs: {
|
|
4237
|
-
connectionKey: {
|
|
4238
|
-
type: "string",
|
|
4239
|
-
required: true,
|
|
4240
|
-
description: "Connection key for data source",
|
|
4241
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4242
|
-
}
|
|
4243
|
-
},
|
|
4255
|
+
inputs: {},
|
|
4244
4256
|
steps: [
|
|
4245
4257
|
{
|
|
4246
4258
|
id: "fetchData",
|
|
@@ -4249,7 +4261,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4249
4261
|
action: {
|
|
4250
4262
|
platform: "PLATFORM_NAME",
|
|
4251
4263
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4252
|
-
|
|
4264
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
4253
4265
|
}
|
|
4254
4266
|
},
|
|
4255
4267
|
{
|
package/package.json
CHANGED
|
@@ -282,12 +282,19 @@ The `if`, `unless`, `condition.expression`, `while.condition`, `transform.expres
|
|
|
282
282
|
"action": {
|
|
283
283
|
"platform": "stripe",
|
|
284
284
|
"actionId": "conn_mod_def::xxx::yyy",
|
|
285
|
-
"
|
|
285
|
+
"connection": { "platform": "stripe" },
|
|
286
286
|
"data": { "query": "email:'{{$.input.customerEmail}}'" }
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
+
**Connection forms.** Each action step sets exactly one of:
|
|
292
|
+
|
|
293
|
+
- **`connection: { platform: "<name>", "tag"?: "<tag>" }`** (preferred) — late-bound, resolved at flow-execute time. Survives re-auth (which always mints a new key). Use `tag` to disambiguate when a platform has multiple connections (e.g. multi-account Gmail). Both `platform` and `tag` accept `$.input.x` selectors so flows can be parameterised per-execution.
|
|
294
|
+
- **`connectionKey: "<literal-or-selector>"`** (legacy) — passes the key string straight through. Still supported for backwards compat, but breaks on re-auth and forces manual edits across every flow that references the stale key. Migrate to `connection` when convenient.
|
|
295
|
+
|
|
296
|
+
The validator rejects an action that sets both forms (or neither) at `flow validate` and `flow execute` time.
|
|
297
|
+
|
|
291
298
|
### `transform` — JS expression (implicit return)
|
|
292
299
|
|
|
293
300
|
```json
|