@superglue/cli 1.1.21 → 1.1.23
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/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -7
- package/dist/cli.js +307 -77
- package/dist/skills/superglue/SKILL.md +315 -631
- package/dist/skills/superglue/references/file-handling.md +180 -96
- package/dist/skills/superglue/references/graphql.md +131 -0
- package/dist/skills/superglue/references/http.md +251 -0
- package/dist/skills/superglue/references/integration.md +4 -2
- package/dist/skills/superglue/references/mssql.md +207 -0
- package/dist/skills/superglue/references/odbc.md +162 -0
- package/dist/skills/superglue/references/postgres.md +154 -0
- package/dist/skills/superglue/references/redis.md +68 -118
- package/dist/skills/superglue/references/sftp-smb.md +161 -0
- package/dist/skills/superglue/references/superglue-info.md +91 -0
- package/package.json +1 -1
- package/skills/superglue/SKILL.md +315 -631
- package/skills/superglue/references/file-handling.md +180 -96
- package/skills/superglue/references/graphql.md +131 -0
- package/skills/superglue/references/http.md +251 -0
- package/skills/superglue/references/integration.md +4 -2
- package/skills/superglue/references/mssql.md +207 -0
- package/skills/superglue/references/odbc.md +162 -0
- package/skills/superglue/references/postgres.md +154 -0
- package/skills/superglue/references/redis.md +68 -118
- package/skills/superglue/references/sftp-smb.md +161 -0
- package/skills/superglue/references/superglue-info.md +91 -0
- package/dist/skills/superglue/references/data-handling.md +0 -303
- package/dist/skills/superglue/references/databases.md +0 -253
- package/dist/skills/superglue/references/file-servers.md +0 -112
- package/dist/skills/superglue/references/http-apis.md +0 -248
- package/skills/superglue/references/data-handling.md +0 -303
- package/skills/superglue/references/databases.md +0 -253
- package/skills/superglue/references/file-servers.md +0 -112
- package/skills/superglue/references/http-apis.md +0 -248
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
# HTTP APIs
|
|
2
|
-
|
|
3
|
-
Covers REST APIs, GraphQL, webhooks, and any HTTP-based service.
|
|
4
|
-
|
|
5
|
-
## Step Configuration
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
{
|
|
9
|
-
type: "request",
|
|
10
|
-
systemId: "my_api",
|
|
11
|
-
url: "<<my_api_url>>/v1/users", // RECOMMENDED: use system URL variable for base URL
|
|
12
|
-
method: "GET", // GET, POST, PUT, DELETE, PATCH
|
|
13
|
-
headers: { "Authorization": "Bearer <<my_api_access_token>>" }, // supports <<variables>> and JS expressions
|
|
14
|
-
queryParams: { "limit": "<<limit>>", "status": "active" }, // supports <<variables>> and JS expressions
|
|
15
|
-
body: '{"name": "<<(sourceData) => sourceData.currentItem.name>>"}', // supports <<variables>> and JS expressions
|
|
16
|
-
pagination: { ... } // optional
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### URL Best Practices
|
|
21
|
-
|
|
22
|
-
**Prefer system URL variables over hardcoded URLs:**
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
// RECOMMENDED: Environment-agnostic (works with dev/prod switching)
|
|
26
|
-
{ "url": "<<salesforce_url>>/services/data/v58.0/sobjects/Account" }
|
|
27
|
-
|
|
28
|
-
// ACCEPTABLE: When URL differs significantly from system base URL
|
|
29
|
-
{ "url": "https://api.stripe.com/v1/customers" }
|
|
30
|
-
|
|
31
|
-
// AVOID: Hardcoding URLs that match the system's base URL
|
|
32
|
-
{ "url": "https://mycompany.salesforce.com/services/data/v58.0/sobjects/Account" }
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Using `<<systemId_url>>` enables the same tool to work across different environments (dev/prod) without modification. The URL resolves dynamically based on execution mode.
|
|
36
|
-
|
|
37
|
-
## Authentication Patterns
|
|
38
|
-
|
|
39
|
-
```javascript
|
|
40
|
-
// Bearer token
|
|
41
|
-
{ "Authorization": "Bearer <<systemId_access_token>>" }
|
|
42
|
-
|
|
43
|
-
// API key in header
|
|
44
|
-
{ "X-API-Key": "<<systemId_api_key>>" }
|
|
45
|
-
|
|
46
|
-
// Basic Auth — auto-encoded to Base64, do NOT manually encode
|
|
47
|
-
{ "Authorization": "Basic <<systemId_username>>:<<systemId_password>>" }
|
|
48
|
-
|
|
49
|
-
// Runtime credentials from payload
|
|
50
|
-
{ "Authorization": "Bearer <<(sourceData) => sourceData.user_access_token>>" }
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Headers starting with `x-` are treated as custom headers.
|
|
54
|
-
Modern APIs expect auth in headers, NOT query parameters, unless docs explicitly say otherwise.
|
|
55
|
-
|
|
56
|
-
## Request Behavior
|
|
57
|
-
|
|
58
|
-
- **GET, HEAD, DELETE, OPTIONS**: Body is always stripped
|
|
59
|
-
- **POST, PUT, PATCH**: Body with leading `{` is JSON-parsed; empty body becomes undefined
|
|
60
|
-
- Default headers: `Accept: */*`, Chrome-like `User-Agent`
|
|
61
|
-
- HTTPS: `rejectUnauthorized: false` (accepts self-signed certs)
|
|
62
|
-
|
|
63
|
-
### Response Handling
|
|
64
|
-
|
|
65
|
-
All HTTP responses are read as raw bytes, then classified by `detectFileType` (byte-level detection). See the file-handling skill for the full detection logic and file type reference.
|
|
66
|
-
|
|
67
|
-
- **Binary types** (PDF, Excel, DOCX, ZIP, GZIP): treated as files. Parsed content also becomes `data`. Later steps can reference the file via `file::<stepId>.raw/base64/extracted`.
|
|
68
|
-
- **`application/octet-stream`**: always treated as a file, even if byte-level detection falls back to `RAW`.
|
|
69
|
-
- **Large responses over 25 MB**: treated as files as a safety fallback so the runtime does not try to parse a huge unknown blob as RAW text/JSON.
|
|
70
|
-
- **RAW + Content-Disposition attachment**: treated as a file.
|
|
71
|
-
- **Smaller structured/text responses** (JSON, XML, CSV, YAML, HTML, plain text under the size limit): parsed inline to `data` only.
|
|
72
|
-
|
|
73
|
-
This means an HTTP step downloading a PDF or large `.bin` file will expose `stepFileKeys` and populate `sourceData.__files__`, while an ordinary JSON API response will usually just populate `sourceData.stepId.data`.
|
|
74
|
-
|
|
75
|
-
### Body Resolution Order
|
|
76
|
-
|
|
77
|
-
For HTTP request steps, the runtime processes the body in this order:
|
|
78
|
-
|
|
79
|
-
1. `<<>>` template variables are resolved via `replaceVariables`
|
|
80
|
-
2. The resulting body string is JSON-parsed when possible
|
|
81
|
-
3. `file::...` references are resolved on the parsed body object
|
|
82
|
-
4. The HTTP strategy decides how to send the final body:
|
|
83
|
-
- raw bytes for `file::<key>.raw` when the **entire body** is a file ref
|
|
84
|
-
- base64 string content for `file::<key>.base64` when the API expects base64 inside JSON/XML
|
|
85
|
-
- `multipart/form-data` when the `Content-Type` header requests it and the body resolves to an object
|
|
86
|
-
- plain string body for strings
|
|
87
|
-
- `JSON.stringify(...)` for ordinary objects
|
|
88
|
-
|
|
89
|
-
File references are resolved **only in the body**, not in headers, query params, or URLs.
|
|
90
|
-
|
|
91
|
-
**Avoiding double-encoding:** The runtime stringifies objects at both step 1 (expression → template) and step 4 (object → fetch body). When the body contains nested JSON (e.g. LLM APIs), have `<<>>` expressions return a string via `JSON.stringify(...)`, or use a preceding transform step.
|
|
92
|
-
|
|
93
|
-
### File Uploads
|
|
94
|
-
|
|
95
|
-
#### Raw Body Uploads
|
|
96
|
-
|
|
97
|
-
If the endpoint expects the file as the entire HTTP body, use:
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
{
|
|
101
|
-
method: "PUT",
|
|
102
|
-
headers: { "Content-Type": "application/pdf" },
|
|
103
|
-
body: "file::document.raw"
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
This sends the exact original file bytes as the request body.
|
|
108
|
-
|
|
109
|
-
#### Base64-In-JSON/XML Uploads
|
|
110
|
-
|
|
111
|
-
If the endpoint expects the file encoded as base64 inside a structured payload, use:
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
{
|
|
115
|
-
method: "POST",
|
|
116
|
-
headers: { "Content-Type": "application/json" },
|
|
117
|
-
body: '{"fileBase64":"file::document.base64"}'
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
This resolves the file bytes to a base64 string before the final `JSON.stringify(...)`.
|
|
122
|
-
|
|
123
|
-
#### Automatic Multipart Uploads
|
|
124
|
-
|
|
125
|
-
If the endpoint expects `multipart/form-data`, set the header and make the body a JSON object of form fields:
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
{
|
|
129
|
-
method: "POST",
|
|
130
|
-
headers: { "Content-Type": "multipart/form-data" },
|
|
131
|
-
body: '{"file":"file::report.raw","description":"monthly export"}'
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
The runtime will:
|
|
136
|
-
|
|
137
|
-
- build a real `FormData` request
|
|
138
|
-
- turn `file::...raw` values into file parts using the stored filename and content type
|
|
139
|
-
- turn scalar values into text parts
|
|
140
|
-
- JSON-stringify non-file objects/arrays into text parts
|
|
141
|
-
- remove the user-supplied `Content-Type` header and let `fetch` set the multipart boundary automatically
|
|
142
|
-
|
|
143
|
-
Restrictions:
|
|
144
|
-
|
|
145
|
-
- the multipart body must resolve to an object of top-level form fields
|
|
146
|
-
- nested file refs inside objects are not supported
|
|
147
|
-
- do **not** manually include a multipart boundary in the header
|
|
148
|
-
- use `.raw` for file parts, `.base64` for base64 text fields, and `.extracted` only for text fields that should contain parsed content
|
|
149
|
-
|
|
150
|
-
## Error Detection
|
|
151
|
-
|
|
152
|
-
### HTTP Status Errors
|
|
153
|
-
|
|
154
|
-
Non-2xx responses throw with: method, URL, response body preview (1000 chars), masked config, retry count.
|
|
155
|
-
|
|
156
|
-
### Smart Error Detection in 2xx Responses
|
|
157
|
-
|
|
158
|
-
Even successful responses are scanned for error indicators:
|
|
159
|
-
|
|
160
|
-
- `response.code` or `response.status` is 400-599 → throws
|
|
161
|
-
- Keys matching `error`, `errors`, `error_message`, `failure_reason`, `failure`, `failed` (up to depth 2) with non-empty values → throws
|
|
162
|
-
- Error message includes: _"To prevent this from happening, enable 'Continue on failure' in the step's Advanced Settings."_
|
|
163
|
-
|
|
164
|
-
Bypass: `failureBehavior: "continue"` skips all error checking. Can be set in the tool playground.
|
|
165
|
-
|
|
166
|
-
## Retry Logic
|
|
167
|
-
|
|
168
|
-
### Connection/Server Errors
|
|
169
|
-
|
|
170
|
-
- Default 1 retry (3 if keep-alive disabled), capped at server max
|
|
171
|
-
- Only retries if response was fast (under quick-retry threshold)
|
|
172
|
-
- Configurable delay between retries
|
|
173
|
-
|
|
174
|
-
### Rate Limiting (429)
|
|
175
|
-
|
|
176
|
-
- Respects `Retry-After` header (seconds or date)
|
|
177
|
-
- Without header: exponential backoff `10^n * 1000ms + jitter` (max 1hr per wait)
|
|
178
|
-
- Total wait capped at `MAX_RATE_LIMIT_WAIT_MS`
|
|
179
|
-
- Separate from general retry count
|
|
180
|
-
|
|
181
|
-
## Pagination
|
|
182
|
-
|
|
183
|
-
Makes multiple requests in a loop, merging results. Only configure if you've verified the exact API pagination mechanism from docs.
|
|
184
|
-
|
|
185
|
-
### Configuration
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
pagination: {
|
|
189
|
-
type: "offsetBased" | "pageBased" | "cursorBased",
|
|
190
|
-
pageSize: "50",
|
|
191
|
-
cursorPath: "meta.next_cursor", // cursorBased only — JSONPath to cursor
|
|
192
|
-
stopCondition: "(response, pageInfo) => !response.data.meta.next_cursor"
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### Variables (auto-injected)
|
|
197
|
-
|
|
198
|
-
| Type | Variable | Starts at | Increments by |
|
|
199
|
-
| ----------- | ---------------------------- | --------- | ------------------------ |
|
|
200
|
-
| pageBased | `<<page>>` | 1 | 1 |
|
|
201
|
-
| offsetBased | `<<offset>>` | 0 | pageSize |
|
|
202
|
-
| cursorBased | `<<cursor>>` | null | extracted via cursorPath |
|
|
203
|
-
| all | `<<limit>>` / `<<pageSize>>` | pageSize | — |
|
|
204
|
-
|
|
205
|
-
**CRITICAL**: The matching pagination variable MUST appear in the request (URL, queryParams, headers, or body). Throws if missing.
|
|
206
|
-
|
|
207
|
-
### Stop Conditions
|
|
208
|
-
|
|
209
|
-
JS expression evaluated in sandbox. Receives `(response, pageInfo)`:
|
|
210
|
-
|
|
211
|
-
- `response.data` = parsed API response body
|
|
212
|
-
- `response.headers` = response headers
|
|
213
|
-
- `pageInfo = { page, offset, cursor, totalFetched }`
|
|
214
|
-
- Return `true` to **STOP**
|
|
215
|
-
|
|
216
|
-
Examples:
|
|
217
|
-
|
|
218
|
-
```javascript
|
|
219
|
-
"!response.data.meta.next_cursor"; // no next cursor
|
|
220
|
-
"response.data.items.length === 0"; // empty page
|
|
221
|
-
"response.data.hasMore === false"; // explicit flag
|
|
222
|
-
"pageInfo.totalFetched >= 1000"; // item cap
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Safety Checks (only when stopCondition is set)
|
|
226
|
-
|
|
227
|
-
1. **Identical pages 1 & 2** (both with data): Throws — pagination params aren't being applied
|
|
228
|
-
2. **Both pages empty + stop didn't trigger**: Throws — broken stop condition
|
|
229
|
-
3. **Duplicate consecutive response (after page 2)**: Auto-stops
|
|
230
|
-
|
|
231
|
-
### Without stopCondition (fallback)
|
|
232
|
-
|
|
233
|
-
- Array shorter than pageSize → stop
|
|
234
|
-
- Duplicate response hash (against any previously seen page) → stop
|
|
235
|
-
- Non-array response → stop after first request
|
|
236
|
-
- Max 500 requests (vs 1000 max with a stopCondition)
|
|
237
|
-
|
|
238
|
-
### Cursor Extraction
|
|
239
|
-
|
|
240
|
-
`cursorPath` is a JSONPath expression (auto-prefixed with `$.` if needed). Extracted via `jsonpath-plus`. Null cursor → stop.
|
|
241
|
-
|
|
242
|
-
**Special characters in cursorPath:** For property names containing `@` (e.g. OData's `@odata.nextLink`), use the property name directly as the cursorPath: `cursorPath: "@odata.nextLink"`. The engine handles `@`-prefixed paths automatically.
|
|
243
|
-
|
|
244
|
-
## Output
|
|
245
|
-
|
|
246
|
-
Paginated steps merge all pages into a **single** result object. The step result is always accessed via `sourceData.stepId.data` — it is NOT an array of per-page results. Do NOT call `.map()` on a paginated step result.
|
|
247
|
-
|
|
248
|
-
Returns single object if one result, array if multiple (unwrapped from single-element array).
|
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
# Data Handling: Variables, Selectors & Transforms
|
|
2
|
-
|
|
3
|
-
Most tool building and editing failures trace back to incorrect variable syntax, wrong data selector returns, or misunderstanding of the tool result envelope.
|
|
4
|
-
|
|
5
|
-
## Variable Replacement (`<<>>` Syntax)
|
|
6
|
-
|
|
7
|
-
The `<<expression>>` syntax injects dynamic values into step config fields (URL, headers, body, queryParams).
|
|
8
|
-
|
|
9
|
-
### Simple Variables (top-level keys only)
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
<<userId>> payload keys
|
|
13
|
-
<<currentItem>> whole current loop item
|
|
14
|
-
<<page>> pagination variable
|
|
15
|
-
<<systemId_api_key>> system credential
|
|
16
|
-
<<systemId_url>> system base URL
|
|
17
|
-
<<sg_auth_email>> email of the authenticated user
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**CRITICAL**: Simple `<<varName>>` only works for top-level keys. NO dots, NO nesting.
|
|
21
|
-
|
|
22
|
-
- VALID: `<<userId>>`, `<<currentItem>>`, `<<page>>`, `<<stripe_api_key>>`, `<<stripe_url>>`, `<<sg_auth_email>>`
|
|
23
|
-
- INVALID: `<<currentItem.id>>`, `<<sourceData.userId>>`, `<<user.name>>` — these FAIL at runtime
|
|
24
|
-
|
|
25
|
-
### Arrow Function Expressions (for everything else)
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
<<(sourceData) => sourceData.currentItem.player_id>>
|
|
29
|
-
<<(sourceData) => sourceData.getUsers.data.map(u => u.id)>>
|
|
30
|
-
<<(sourceData) => JSON.stringify({ ids: sourceData.fetchUsers.data.map(u => u.id) })>>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Resolution Order
|
|
34
|
-
|
|
35
|
-
1. Direct lookup: check if expression is a top-level key in the merged variable object
|
|
36
|
-
2. Arrow function: execute in Deno subprocess sandbox with full data as `sourceData`
|
|
37
|
-
3. Failure: throws error listing available keys
|
|
38
|
-
|
|
39
|
-
### Variable Sources
|
|
40
|
-
|
|
41
|
-
All variables come from a single merged object:
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
{
|
|
45
|
-
...originalPayload, // user-provided input
|
|
46
|
-
...previousStepResults, // keyed by stepId
|
|
47
|
-
...systemCredentials, // namespaced: systemId_credKey
|
|
48
|
-
...systemUrls, // namespaced: systemId_url
|
|
49
|
-
sg_auth_email, // email of authenticated user (context variable)
|
|
50
|
-
currentItem, // current loop item (if in a loop)
|
|
51
|
-
page, offset, cursor, // pagination variables
|
|
52
|
-
limit, pageSize // pagination size
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Context Variables
|
|
57
|
-
|
|
58
|
-
Context variables provide information about the execution environment:
|
|
59
|
-
|
|
60
|
-
- `<<sg_auth_email>>` — Email address of the authenticated user who triggered the tool execution
|
|
61
|
-
|
|
62
|
-
**Use case**: Personalize tool outputs based on who is running the tool. For example:
|
|
63
|
-
|
|
64
|
-
- Different permissions in Tableau based on user
|
|
65
|
-
- User-specific account lookups
|
|
66
|
-
- Audit logging with user identity
|
|
67
|
-
|
|
68
|
-
**Important**: `sg_auth_email` is NOT available in scheduled executions (cron jobs). If a tool references `<<sg_auth_email>>` and runs via scheduler, it will fail with a clear error message.
|
|
69
|
-
|
|
70
|
-
### Return Type Handling
|
|
71
|
-
|
|
72
|
-
- Objects/arrays returned from expressions → JSON.stringified automatically
|
|
73
|
-
- Resolved values of `"undefined"` or `"null"` → stripped from headers and queryParams
|
|
74
|
-
|
|
75
|
-
### Special: Basic Auth
|
|
76
|
-
|
|
77
|
-
`"Authorization": "Basic <<user>>:<<pass>>"` is auto-converted to Base64. Do NOT manually encode.
|
|
78
|
-
|
|
79
|
-
## Credential Resolution
|
|
80
|
-
|
|
81
|
-
Credentials are available via `<<>>` syntax. Two sources, payload takes precedence:
|
|
82
|
-
|
|
83
|
-
1. **System credentials**: Stored on System, namespaced as `<<systemId_credKey>>`
|
|
84
|
-
|
|
85
|
-
```javascript
|
|
86
|
-
// System id="stripe", credentials={ api_key: "sk_..." }
|
|
87
|
-
// Available as: <<stripe_api_key>>
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
2. **Payload credentials**: Passed at runtime, accessed directly
|
|
91
|
-
```javascript
|
|
92
|
-
// Payload: { user_access_token: "abc" }
|
|
93
|
-
// Available as: <<user_access_token>>
|
|
94
|
-
// Or: <<(sourceData) => sourceData.user_access_token>>
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
For OAuth systems, use `<<systemId_access_token>>` — the token value is auto-refreshed during tool execution, but the header itself must be present in the step config.
|
|
98
|
-
|
|
99
|
-
## System URL Resolution
|
|
100
|
-
|
|
101
|
-
System URLs are available via `<<systemId_url>>` syntax. This enables environment-agnostic tools:
|
|
102
|
-
|
|
103
|
-
```javascript
|
|
104
|
-
// System id="salesforce", url="https://mycompany.salesforce.com"
|
|
105
|
-
// Available as: <<salesforce_url>>
|
|
106
|
-
|
|
107
|
-
// Step config:
|
|
108
|
-
{
|
|
109
|
-
"url": "<<salesforce_url>>/services/data/v58.0/sobjects/Account",
|
|
110
|
-
"headers": { "Authorization": "Bearer <<salesforce_access_token>>" }
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**Why use system URL variables?**
|
|
115
|
-
|
|
116
|
-
- Same tool works across dev/prod environments without modification
|
|
117
|
-
- When switching execution mode, the system URL resolves to the appropriate environment
|
|
118
|
-
- Avoids hardcoding base URLs that differ between environments
|
|
119
|
-
|
|
120
|
-
## Data Selectors
|
|
121
|
-
|
|
122
|
-
Every step has a `dataSelector` — a JS function that controls execution mode and what `currentItem` is.
|
|
123
|
-
|
|
124
|
-
### Returns OBJECT → Single Execution
|
|
125
|
-
|
|
126
|
-
Step runs once. The object becomes `currentItem`.
|
|
127
|
-
|
|
128
|
-
```javascript
|
|
129
|
-
(sourceData) => ({ userId: sourceData.userId })
|
|
130
|
-
(sourceData) => ({}) // run once with no specific input
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Result stored as:
|
|
134
|
-
|
|
135
|
-
```javascript
|
|
136
|
-
sourceData.stepId = { currentItem: <object>, data: <response>, success: true }
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Returns ARRAY → Loop Execution
|
|
140
|
-
|
|
141
|
-
Step runs once per array element. Each element becomes `currentItem`.
|
|
142
|
-
|
|
143
|
-
```javascript
|
|
144
|
-
(sourceData) => sourceData.getContacts.data.filter(c => c.active)
|
|
145
|
-
(sourceData) => sourceData.userIds // if userIds is an array from payload
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Result stored as:
|
|
149
|
-
|
|
150
|
-
```javascript
|
|
151
|
-
sourceData.stepId = [
|
|
152
|
-
{ currentItem: <item1>, data: <response1>, success: true },
|
|
153
|
-
{ currentItem: <item2>, data: <response2>, success: true },
|
|
154
|
-
...
|
|
155
|
-
]
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
Empty arrays are valid — step just skips execution.
|
|
159
|
-
|
|
160
|
-
### Accessing currentItem in Step Config
|
|
161
|
-
|
|
162
|
-
```
|
|
163
|
-
<<currentItem>> // whole value
|
|
164
|
-
<<(sourceData) => sourceData.currentItem.id>> // property
|
|
165
|
-
<<(sourceData) => sourceData.currentItem.name.toUpperCase()>> // with transform
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Result Envelope
|
|
169
|
-
|
|
170
|
-
**Every** step result (request or transform) is wrapped identically:
|
|
171
|
-
|
|
172
|
-
### From Object Selector
|
|
173
|
-
|
|
174
|
-
```javascript
|
|
175
|
-
sourceData.stepId = {
|
|
176
|
-
currentItem: <selectorOutput>,
|
|
177
|
-
data: <apiResponse or transformResult>,
|
|
178
|
-
success: true
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Access:
|
|
182
|
-
sourceData.stepId.data // the response
|
|
183
|
-
sourceData.stepId.currentItem // what was passed in
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### From Array Selector
|
|
187
|
-
|
|
188
|
-
```javascript
|
|
189
|
-
sourceData.stepId = [
|
|
190
|
-
{ currentItem: <item1>, data: <response1>, success: true },
|
|
191
|
-
{ currentItem: <item2>, data: <response2>, success: false, error: "..." },
|
|
192
|
-
...
|
|
193
|
-
]
|
|
194
|
-
|
|
195
|
-
// Access:
|
|
196
|
-
sourceData.stepId.map(item => item.data) // all responses
|
|
197
|
-
sourceData.stepId.flatMap(item => item.data.results) // nested arrays
|
|
198
|
-
sourceData.stepId.filter(item => item.success).map(i => i.data) // only successes
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### From Paginated Step
|
|
202
|
-
|
|
203
|
-
Paginated steps merge all pages server-side into a **single** result. The result is always a single envelope, NOT an array — even though multiple pages were fetched.
|
|
204
|
-
|
|
205
|
-
```javascript
|
|
206
|
-
sourceData.stepId = {
|
|
207
|
-
data: <merged response from all pages>,
|
|
208
|
-
success: true
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Access:
|
|
212
|
-
sourceData.stepId.data // the merged response
|
|
213
|
-
sourceData.stepId.data.items // if the API wraps results in .items
|
|
214
|
-
|
|
215
|
-
// WRONG — do NOT treat paginated results as an array of envelopes:
|
|
216
|
-
sourceData.stepId.map(...) // FAILS — stepId is an object, not an array
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## sourceData Structure
|
|
220
|
-
|
|
221
|
-
`sourceData` is the cumulative state available to data selectors, variable expressions, and transforms:
|
|
222
|
-
|
|
223
|
-
```javascript
|
|
224
|
-
sourceData = {
|
|
225
|
-
// Original payload fields at ROOT level (NOT under .payload)
|
|
226
|
-
userId: "abc",
|
|
227
|
-
date: "2024-01-15",
|
|
228
|
-
companies: ["acme", "globex"],
|
|
229
|
-
|
|
230
|
-
// Previous step results, keyed by step ID
|
|
231
|
-
getUsers: { currentItem: {}, data: { users: [...] }, success: true },
|
|
232
|
-
fetchDetails: [
|
|
233
|
-
{ currentItem: { id: 1 }, data: { name: "Alice" }, success: true },
|
|
234
|
-
{ currentItem: { id: 2 }, data: { name: "Bob" }, success: true },
|
|
235
|
-
],
|
|
236
|
-
|
|
237
|
-
// Current item (only within a loop step's config)
|
|
238
|
-
currentItem: { id: 1 },
|
|
239
|
-
|
|
240
|
-
// Runtime file map — see file-handling skill for full reference
|
|
241
|
-
__files__: { ... }
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Treat `sourceData.__files__` as read-only. See the file-handling skill for file detection, the `RuntimeExecutionFile` shape, aliasing rules, and how to access files in transforms.
|
|
246
|
-
|
|
247
|
-
## Three Transformation Points
|
|
248
|
-
|
|
249
|
-
| Point | When it runs | Purpose |
|
|
250
|
-
| -------------------------------- | ------------------------ | ------------------------------------ |
|
|
251
|
-
| `dataSelector` | Before step executes | Controls input + single vs loop mode |
|
|
252
|
-
| Transform step (`transformCode`) | As the step itself | Reshape data between request steps |
|
|
253
|
-
| `outputTransform` | After all steps complete | Shape final tool output |
|
|
254
|
-
|
|
255
|
-
## Transform Steps
|
|
256
|
-
|
|
257
|
-
Transform steps use `type: "transform"` with a `transformCode` function. They do NOT have: systemId, url, method, headers, body, queryParams, or pagination.
|
|
258
|
-
|
|
259
|
-
Results are wrapped in the **same envelope** as request steps:
|
|
260
|
-
|
|
261
|
-
```javascript
|
|
262
|
-
sourceData.formatForInsert = { currentItem: <dataSelector output>, data: <transformCode result>, success: true }
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
If the step has a dataSelector returning an array, the transform runs once per item. See tool-building guidelines for when to use transform steps vs dataSelectors vs outputTransform.
|
|
266
|
-
|
|
267
|
-
### Returning Files From a Transform
|
|
268
|
-
|
|
269
|
-
Transform steps can produce files by returning a reserved `__files__` key in the result. See the file-handling skill for the full shape, aliasing rules, and access patterns.
|
|
270
|
-
|
|
271
|
-
## Output Transform
|
|
272
|
-
|
|
273
|
-
Final transformation shaping the tool's output. Runs after all steps complete. Can access cumulative step result data and payloads.
|
|
274
|
-
|
|
275
|
-
### Requirements
|
|
276
|
-
|
|
277
|
-
- Function signature: `(sourceData) => { ... }`
|
|
278
|
-
- **Must have a return statement**
|
|
279
|
-
- Async/await IS supported (same Deno sandbox as transform steps)
|
|
280
|
-
- NEVER include newlines or tabs in the code string
|
|
281
|
-
|
|
282
|
-
## JS Code Constraints (all transform points)
|
|
283
|
-
|
|
284
|
-
Runs in a Deno subprocess sandbox:
|
|
285
|
-
|
|
286
|
-
- 8192 MB memory limit, 10 min timeout (transforms/selectors), 3s timeout (pagination stops)
|
|
287
|
-
- Async/await IS supported in transforms
|
|
288
|
-
- No filesystem access — `--deny-read`, `--deny-write`
|
|
289
|
-
- No subprocess spawning — `--deny-run`
|
|
290
|
-
- Network access IS available (`--allow-net`) — transforms can make HTTP requests
|
|
291
|
-
- Environment variables are sandboxed — only safe vars (PATH, HOME, DENO_DIR) are visible, no server secrets
|
|
292
|
-
- JSON-serializable I/O only
|
|
293
|
-
- Must return a value (undefined returns null)
|
|
294
|
-
|
|
295
|
-
### Serialization Safety
|
|
296
|
-
|
|
297
|
-
All JS code is stored as a JSON string, then MessagePack-serialized to the Deno subprocess, then `eval()`'d. Patterns that break across this pipeline:
|
|
298
|
-
|
|
299
|
-
- **Regex literals with `/`** — `/https?:\/\/([^/]+)/` corrupts. Use `new URL(str).hostname` or `str.split("//")[1].split("/")[0]` instead.
|
|
300
|
-
- **Backslash-heavy patterns** — `\n`, `\t`, `\\` in regex/strings get misinterpreted. Prefer string methods (`.split()`, `.indexOf()`, `.includes()`) over regex.
|
|
301
|
-
- **Literal newlines** — code string must be single-line. Use `;` between statements.
|
|
302
|
-
|
|
303
|
-
**Rule of thumb**: prefer string methods over regex. When regex is unavoidable, use `new RegExp("pattern")` constructor over `/pattern/` literals.
|