@superglue/cli 1.1.20 → 1.1.22
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 +303 -74
- package/dist/skills/superglue/SKILL.md +313 -621
- package/dist/skills/superglue/references/file-handling.md +116 -90
- package/dist/skills/superglue/references/graphql.md +131 -0
- package/dist/skills/superglue/references/http.md +246 -0
- package/dist/skills/superglue/references/integration.md +4 -2
- package/dist/skills/superglue/references/mssql.md +207 -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 +156 -0
- package/dist/skills/superglue/references/superglue-info.md +91 -0
- package/package.json +1 -1
- package/skills/superglue/SKILL.md +313 -621
- package/skills/superglue/references/file-handling.md +116 -90
- package/skills/superglue/references/graphql.md +131 -0
- package/skills/superglue/references/http.md +246 -0
- package/skills/superglue/references/integration.md +4 -2
- package/skills/superglue/references/mssql.md +207 -0
- package/skills/superglue/references/postgres.md +154 -0
- package/skills/superglue/references/redis.md +68 -118
- package/skills/superglue/references/sftp-smb.md +156 -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,128 +1,145 @@
|
|
|
1
1
|
# File Handling
|
|
2
2
|
|
|
3
|
-
How the runtime detects, parses, and exposes files across all protocol strategies (HTTP, FTP,
|
|
3
|
+
How the runtime detects, parses, and exposes files across all protocol strategies (HTTP, SFTP/FTP, SMB).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Step Configuration
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
File handling is not a standalone step type — it is a cross-cutting runtime behavior that applies to any step that produces or consumes files. The `file::<alias>.<suffix>` syntax is used in step config `body` / `content` fields to reference files from previous steps or user uploads.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
4. **25MB size fallback** (last resort) — only applies when byte detection returned `RAW` (unrecognized format), the response is larger than 25MB, **and** the `Content-Type` is not clearly text-like (`text/*`, `application/json`, `*+json`, `application/xml`, etc.). This is the only path where size matters.
|
|
9
|
+
```typescript
|
|
10
|
+
// Reference a file downloaded by a previous step
|
|
11
|
+
{ "body": "file::downloadStep.raw" }
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
// Reference a user-uploaded file
|
|
14
|
+
{ "body": '{"fileBase64": "file::document.base64"}' }
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| **Structured text** | JSON, XML, CSV, YAML, HTML | `data` only (parsed to JS objects) |
|
|
20
|
-
| **RAW** | Unrecognized | File if attachment/octet-stream/25MB fallback, else raw string |
|
|
16
|
+
// Reference a specific file from a multi-file step
|
|
17
|
+
{ "content": 'file::batchDownload["report.csv"].raw' }
|
|
18
|
+
```
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
### file:: Reference Syntax
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
Use `file::<alias>.<suffix>` in step config `body` / `content` fields:
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
| Suffix | Returns | Use case |
|
|
25
|
+
| ------------ | --------------------------- | --------------------------------------- |
|
|
26
|
+
| `.raw` | Original bytes (Uint8Array) | SFTP/FTP put, HTTP raw body upload |
|
|
27
|
+
| `.base64` | Base64-encoded string | APIs expecting base64 inside JSON/XML |
|
|
28
|
+
| `.extracted` | Parsed content | Text fields needing parsed file content |
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
| ----- | ------------------------------------------------------------- |
|
|
30
|
-
| JSON | Native JS object/array |
|
|
31
|
-
| CSV | Array of objects (header row = keys) |
|
|
32
|
-
| XML | JS object tree |
|
|
33
|
-
| HTML | JS object tree |
|
|
34
|
-
| YAML | Native JS object |
|
|
35
|
-
| Excel | Object keyed by sheet name → array of row objects (see below) |
|
|
36
|
-
| PDF | `{ textContent, structuredContent: [{ page, text }] }` |
|
|
37
|
-
| DOCX | Markdown string |
|
|
38
|
-
| ZIP | Object keyed by entry filename, each value recursively parsed |
|
|
39
|
-
| GZIP | Recursively parsed inner content |
|
|
40
|
-
| RAW | UTF-8 string fallback |
|
|
30
|
+
Constraints:
|
|
41
31
|
|
|
42
|
-
|
|
32
|
+
- Bare `file::<key>` without a suffix is invalid and causes a runtime error
|
|
33
|
+
- File references resolve only in body/content fields, not in headers, query params, or URLs
|
|
34
|
+
- Multi-file steps use bracket notation: `file::stepId["report.csv"].raw`
|
|
35
|
+
- Loop iterations use numeric index: `file::stepId[0].raw`
|
|
43
36
|
|
|
44
|
-
|
|
37
|
+
## Authentication
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
- Access rows via `extracted.SheetName` or `extracted["Sheet1"]`, never `extracted[0]`.
|
|
48
|
-
- When producing xlsx, use namespace `http://schemas.openxmlformats.org/spreadsheetml/2006/main` (not the truncated path).
|
|
39
|
+
N/A — file handling is a runtime behavior, not a protocol. Authentication is handled by the protocol skill that produces or consumes the file (http, sftp-smb, etc.).
|
|
49
40
|
|
|
50
|
-
##
|
|
41
|
+
## File Handling Runtime Details
|
|
51
42
|
|
|
52
|
-
|
|
43
|
+
### Detection Priority
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
| ------------ | --------------------------- | --------------------------------------- |
|
|
56
|
-
| `.raw` | Original bytes (Uint8Array) | SFTP/FTP put, HTTP raw body upload |
|
|
57
|
-
| `.base64` | Base64-encoded string | APIs expecting base64 inside JSON/XML |
|
|
58
|
-
| `.extracted` | Parsed content | Text fields needing parsed file content |
|
|
45
|
+
Every response is read as raw bytes. The runtime classifies it using this priority chain:
|
|
59
46
|
|
|
60
|
-
|
|
47
|
+
1. **Magic-byte detection** — known binary signatures (ZIP `PK\x03\x04`, PDF `%PDF`, GZIP `\x1f\x8b`, plus internal ZIP structure checks for Excel/DOCX)
|
|
48
|
+
2. **`Content-Disposition: attachment`** — `attachment` or `filename=` in this header forces file treatment
|
|
49
|
+
3. **`application/octet-stream`** — explicit octet-stream MIME catches unlabeled binary payloads
|
|
50
|
+
4. **25MB size fallback** — only when byte detection returned `RAW`, the response exceeds 25MB, and `Content-Type` is not text-like (`text/*`, `application/json`, `*+json`, `application/xml`)
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
- File references are resolved **only in body/content fields**, not in headers, query params, or URLs
|
|
64
|
-
- For multi-file steps, use bracket notation: `file::stepId["report.csv"].raw`
|
|
65
|
-
- For loop iterations: `file::stepId[0].raw`
|
|
66
|
-
- Important: base64 access in transforms is capped at 500MB by design, so base64 encoded binary in transform steps is only available for files smaller than this
|
|
52
|
+
File server `get` operations always produce files regardless of detected type.
|
|
67
53
|
|
|
68
|
-
|
|
54
|
+
### Classification
|
|
69
55
|
|
|
70
|
-
|
|
56
|
+
| Classification | Types | Destination |
|
|
57
|
+
| ------------------- | --------------------------- | -------------------------------------------------------------- |
|
|
58
|
+
| **Binary** | PDF, Excel, DOCX, ZIP, GZIP | `producedFiles` + `data` (extracted/parsed content) |
|
|
59
|
+
| **Structured text** | JSON, XML, CSV, YAML, HTML | `data` only (parsed to JS objects) |
|
|
60
|
+
| **RAW** | Unrecognized | File if attachment/octet-stream/25MB fallback, else raw string |
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
Binary responses expose `stepFileKeys` and populate `sourceData.__files__`. Structured text responses only populate `sourceData.stepId.data`.
|
|
63
|
+
|
|
64
|
+
### Auto-Parsing
|
|
65
|
+
|
|
66
|
+
| Type | Parsed result |
|
|
67
|
+
| ----- | -------------------------------------------------------------- |
|
|
68
|
+
| JSON | Native JS object/array |
|
|
69
|
+
| CSV | Array of objects (header row = keys) |
|
|
70
|
+
| XML | JS object tree |
|
|
71
|
+
| HTML | JS object tree |
|
|
72
|
+
| YAML | Native JS object |
|
|
73
|
+
| Excel | Object keyed by sheet name, each value is array of row objects |
|
|
74
|
+
| PDF | `{ textContent, structuredContent: [{ page, text }] }` |
|
|
75
|
+
| DOCX | Markdown string |
|
|
76
|
+
| ZIP | Object keyed by entry filename, each value recursively parsed |
|
|
77
|
+
| GZIP | Recursively parsed inner content |
|
|
78
|
+
| RAW | UTF-8 string fallback |
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
**Excel rules:**
|
|
76
81
|
|
|
77
|
-
-
|
|
78
|
-
-
|
|
82
|
+
- Excel is never a flat array. It is always `{ "SheetName": [ {row}, ... ] }` — even for single-sheet files
|
|
83
|
+
- Access rows via `extracted.SheetName` or `extracted["Sheet1"]`, never `extracted[0]`
|
|
84
|
+
- When producing xlsx, use namespace `http://schemas.openxmlformats.org/spreadsheetml/2006/main` (not the truncated path)
|
|
79
85
|
|
|
80
|
-
|
|
86
|
+
### File Aliasing
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
- one loop iteration download: `downloadReport[0]`
|
|
84
|
-
- one batch step returning multiple files: `downloadReport["summary.csv"]`
|
|
85
|
-
- one loop iteration returning multiple files: `downloadReport[0]["summary.csv"]`
|
|
88
|
+
When a step produces files, the runtime assigns an alias:
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
| Scenario | Alias format |
|
|
91
|
+
| --------------------------- | --------------------------- |
|
|
92
|
+
| Single file, non-loop | `stepId` |
|
|
93
|
+
| Single file, loop iteration | `stepId[N]` |
|
|
94
|
+
| Multiple files, non-loop | `stepId["filename.ext"]` |
|
|
95
|
+
| Multiple files, loop | `stepId[N]["filename.ext"]` |
|
|
88
96
|
|
|
89
|
-
Loop steps
|
|
97
|
+
Loop steps always use numeric indices — never filenames — even when each iteration fetches a named file. Filename brackets only appear when a single step or iteration returns multiple files in one response.
|
|
90
98
|
|
|
91
99
|
These aliases are used both in `file::` references and as keys in `sourceData.__files__`.
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
### stepFileKeys
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
Each step result exposes `stepFileKeys` — an array of runtime aliases for files produced by that step:
|
|
96
104
|
|
|
97
105
|
```javascript
|
|
98
|
-
sourceData.
|
|
99
|
-
sourceData.
|
|
106
|
+
sourceData.downloadStep.stepFileKeys; // ["downloadStep"]
|
|
107
|
+
sourceData.batchDownload[0].stepFileKeys; // ["batchDownload[0]"]
|
|
100
108
|
```
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
### Accessing Files in Transforms
|
|
111
|
+
|
|
112
|
+
Inside `transformCode` and `outputTransform`, use `sourceData.__files__` with the runtime alias. Treat `sourceData.__files__` as read-only — the runtime rebuilds it after every step.
|
|
105
113
|
|
|
106
114
|
```javascript
|
|
107
|
-
sourceData.__files__
|
|
115
|
+
sourceData.__files__.downloadStep; // single-file step
|
|
116
|
+
sourceData.__files__["stepId[0]"]; // loop iteration
|
|
108
117
|
```
|
|
109
118
|
|
|
110
|
-
|
|
119
|
+
Available properties on each file object: `filename`, `contentType`, `size`, `raw`, `extracted`, `fileType`, `parseError`.
|
|
120
|
+
|
|
121
|
+
**base64 access:** `base64` is a lazy getter — it is not stored on the file object by default. Access it directly:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
sourceData.__files__.myStep.base64; // computes and returns base64 string on first access
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The base64 value is computed on first access and cached for subsequent reads. It will not appear in `Object.keys()` or `JSON.stringify()` output (non-enumerable). Throws if the file exceeds 500MB — use `.raw` for large files.
|
|
128
|
+
|
|
129
|
+
Prefer discovering aliases dynamically via `stepFileKeys`:
|
|
111
130
|
|
|
112
131
|
```javascript
|
|
113
132
|
(sourceData) => {
|
|
114
133
|
return (sourceData.fetchThreeFiles.stepFileKeys || []).map(function (alias) {
|
|
115
134
|
var f = sourceData.__files__[alias];
|
|
116
|
-
return {
|
|
117
|
-
alias: alias,
|
|
118
|
-
filename: f ? f.filename : null,
|
|
119
|
-
size: f ? f.size : null,
|
|
120
|
-
};
|
|
135
|
+
return { alias: alias, filename: f ? f.filename : null, size: f ? f.size : null };
|
|
121
136
|
});
|
|
122
137
|
};
|
|
123
138
|
```
|
|
124
139
|
|
|
125
|
-
|
|
140
|
+
### Producing Files From Transforms
|
|
141
|
+
|
|
142
|
+
Return a `__files__` key from a transform to produce new files:
|
|
126
143
|
|
|
127
144
|
```javascript
|
|
128
145
|
(sourceData) => ({
|
|
@@ -139,22 +156,31 @@ Prefer:
|
|
|
139
156
|
|
|
140
157
|
Everything except `__files__` becomes the step's `data`. Single-file transforms get the step ID as alias; multi-file transforms use bracket notation.
|
|
141
158
|
|
|
142
|
-
|
|
159
|
+
**Auto-parsing of transform-produced files:** if `extracted` is omitted from the file object, the runtime runs the full detection and parsing pipeline on the raw bytes (same as for HTTP/file server responses). This means a CSV file produced with `raw: "a,b\n1,2"` will automatically get `extracted: [{a: "1", b: "2"}]`. To skip auto-parsing and keep the raw content as-is, set `extracted` explicitly (e.g. `extracted: null`).
|
|
143
160
|
|
|
144
|
-
|
|
145
|
-
- **String** → UTF-8 encoded. Pass a JSON string for structured data: `raw: JSON.stringify(data)`.
|
|
146
|
-
- **Object / array of objects** → automatically JSON-stringified to UTF-8 bytes. Works, but prefer explicit `JSON.stringify` with a `text/csv` or `application/json` contentType for clarity.
|
|
147
|
-
- **`null` / `undefined`** → produces an empty file (0 bytes).
|
|
148
|
-
- **`Uint8Array` / `ArrayBuffer` / `number[]`** → used as-is for binary content.
|
|
149
|
-
- For binary formats (xlsx, zip, pdf): pass through original bytes via `sourceData.__files__.stepId.raw` — never construct the internal file structure as a JS object.
|
|
161
|
+
**`raw` field rules:**
|
|
150
162
|
|
|
151
|
-
|
|
163
|
+
- `Uint8Array` / `ArrayBuffer` / typed arrays — used as-is for binary content
|
|
164
|
+
- String — UTF-8 encoded. Pass a JSON string for structured data: `raw: JSON.stringify(data)`
|
|
165
|
+
- Object / array — auto JSON-stringified to UTF-8 bytes. Prefer explicit `JSON.stringify` with an appropriate `contentType` for clarity
|
|
166
|
+
- `null` / `undefined` — produces an empty file (0 bytes)
|
|
167
|
+
- For binary formats (xlsx, zip, pdf): pass through original bytes via `sourceData.__files__.stepId.raw` — never construct the internal file structure as a JS object
|
|
152
168
|
|
|
153
|
-
|
|
169
|
+
## Common Pitfalls
|
|
154
170
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
- Using bare `file::<key>` without `.raw`, `.base64`, or `.extracted` suffix — causes a runtime error
|
|
172
|
+
- Placing file references in headers or query params instead of body/content fields — they only resolve in body
|
|
173
|
+
- Expecting Excel parsed output to be a flat array — it is always `{ "SheetName": [...] }` even for single-sheet files
|
|
174
|
+
- Hardcoding bracket aliases like `sourceData.__files__["step[\"file.csv\"]"]` — prefer `stepFileKeys` or `Object.keys(sourceData.__files__)` to discover aliases dynamically
|
|
175
|
+
- Trying to construct binary file internals (xlsx, zip) as JS objects — always pass through original bytes via `.raw`
|
|
176
|
+
- Accessing `sourceData.__files__` in step configs — `file::` syntax is for step configs, `sourceData.__files__` is for transform code
|
|
177
|
+
|
|
178
|
+
## Error Recovery
|
|
179
|
+
|
|
180
|
+
When a file-related step fails:
|
|
159
181
|
|
|
160
|
-
|
|
182
|
+
1. **File input failure** - Ask the user whether the file is present in the session state and verify whether you can access the file at all
|
|
183
|
+
2. **Check the file:: reference** — verify the alias matches a step that actually produces files. Use `stepFileKeys` on the producing step's result to see available aliases.
|
|
184
|
+
3. **Verify the suffix** — bare `file::<key>` without `.raw`, `.base64`, or `.extracted` always fails. Choose the suffix based on how the consuming step needs the data.
|
|
185
|
+
4. **Check the producing step** — if `stepFileKeys` is empty or undefined, the producing step may not have returned a file. Verify the response is binary or has `Content-Disposition: attachment` (for HTTP), or that the operation is `get` (for file servers).
|
|
186
|
+
5. **Check aliasing for loops** — loop iterations use numeric indices (`stepId[0]`), not filenames. If the alias doesn't match, run the producing step and inspect the result to see the actual aliases assigned.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# GraphQL
|
|
2
|
+
|
|
3
|
+
GraphQL APIs use HTTP POST under the hood. This skill covers the GraphQL-specific conventions; the http skill covers the underlying HTTP runtime behavior.
|
|
4
|
+
|
|
5
|
+
## Step Configuration
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
{
|
|
9
|
+
type: "request",
|
|
10
|
+
systemId: "my_graphql_api",
|
|
11
|
+
url: "<<my_graphql_api_url>>/graphql",
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: {
|
|
14
|
+
"Authorization": "Bearer <<my_graphql_api_access_token>>",
|
|
15
|
+
"Content-Type": "application/json"
|
|
16
|
+
},
|
|
17
|
+
body: '{"query": "query GetUser($id: ID!) { user(id: $id) { name email } }", "variables": {"id": "<<userId>>"}}'
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Fields
|
|
22
|
+
|
|
23
|
+
| Field | Required | Notes |
|
|
24
|
+
| ---------- | -------- | ------------------------------------------------------ |
|
|
25
|
+
| `url` | yes | GraphQL endpoint — typically `/graphql` |
|
|
26
|
+
| `method` | yes | Always `POST` |
|
|
27
|
+
| `headers` | yes | Must include `Content-Type: application/json` and auth |
|
|
28
|
+
| `body` | yes | JSON string with `query` and optional `variables` |
|
|
29
|
+
| `systemId` | no | Links system credentials and URL |
|
|
30
|
+
|
|
31
|
+
### Body Format
|
|
32
|
+
|
|
33
|
+
The body is always a JSON string with:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"query": "query { ... }",
|
|
38
|
+
"variables": { ... }
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For dynamic variables, use `<<>>` expressions:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"query": "query GetOrders($customerId: ID!, $limit: Int) { orders(customerId: $customerId, first: $limit) { id total } }",
|
|
47
|
+
"variables": {
|
|
48
|
+
"customerId": "<<customerId>>",
|
|
49
|
+
"limit": "<<(sourceData) => sourceData.pageSize || 50>>"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For complex variable objects, use a preceding transform step to build the variables, then reference with `<<(sourceData) => JSON.stringify(sourceData.prepStep.data)>>` as the entire body.
|
|
55
|
+
|
|
56
|
+
## Authentication
|
|
57
|
+
|
|
58
|
+
Same as HTTP — credentials are placed in headers using `<<systemId_credentialKey>>` syntax. Nothing is injected automatically.
|
|
59
|
+
|
|
60
|
+
## GraphQL Runtime Details
|
|
61
|
+
|
|
62
|
+
### Queries vs Mutations
|
|
63
|
+
|
|
64
|
+
- **Queries** (read-only): set `modify: false` on the step (this is the default)
|
|
65
|
+
- **Mutations** (write): set `modify: true`
|
|
66
|
+
|
|
67
|
+
Both use `method: "POST"` — do not rely on HTTP method to distinguish read vs write.
|
|
68
|
+
|
|
69
|
+
### Response Shape
|
|
70
|
+
|
|
71
|
+
GraphQL APIs return `{ "data": { ... }, "errors": [ ... ] }`. The superglue step result wraps this in the standard envelope:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
sourceData.stepId.data; // → { data: { user: { name: "Alice" } }, errors: [...] }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Access the actual data via `sourceData.stepId.data.data.user` (double `.data` — one for the envelope, one for the GraphQL response).
|
|
78
|
+
|
|
79
|
+
### Pagination
|
|
80
|
+
|
|
81
|
+
GraphQL APIs typically use cursor-based pagination (Relay-style). Use superglue's `cursorBased` pagination:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
{
|
|
85
|
+
body: '{"query": "query($cursor: String) { users(first: 50, after: $cursor) { edges { node { id name } } pageInfo { endCursor hasNextPage } } }", "variables": {"cursor": "<<cursor>>"}}',
|
|
86
|
+
pagination: {
|
|
87
|
+
type: "cursorBased",
|
|
88
|
+
pageSize: "50",
|
|
89
|
+
cursorPath: "data.users.pageInfo.endCursor",
|
|
90
|
+
stopCondition: "response.data.data.users.pageInfo.hasNextPage === false"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Note the `cursorPath` and `stopCondition` must account for the GraphQL `data` wrapper.
|
|
96
|
+
|
|
97
|
+
### Schema Discovery
|
|
98
|
+
|
|
99
|
+
Superglue automatically introspects GraphQL schemas during documentation fetching. When a system URL contains "graphql", introspection is the first strategy tried — the full schema is fetched and stored as system documentation.
|
|
100
|
+
|
|
101
|
+
Use `sg system search-docs` to query the stored schema for types, fields, and arguments before building tools. This is more reliable than manual introspection and works even when the API has introspection disabled for external callers.
|
|
102
|
+
|
|
103
|
+
If the schema is not available via `sg system search-docs` (e.g. the system was created without a GraphQL URL, or introspection was blocked), test manually with `sg system call`:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{ "query": "{ __typename }" }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If that succeeds, try a targeted introspection for the types you need:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{ "query": "{ __type(name: \"User\") { fields { name type { name kind } } } }" }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Common Pitfalls
|
|
116
|
+
|
|
117
|
+
- Forgetting `Content-Type: application/json` header — most GraphQL servers reject requests without it
|
|
118
|
+
- Accessing `sourceData.stepId.data.user` instead of `sourceData.stepId.data.data.user` — the GraphQL response has its own `data` wrapper inside the step envelope
|
|
119
|
+
- Building complex variable objects inline with multiple `<<>>` expressions — use a transform step to build the variables object
|
|
120
|
+
- Using `cursorPath` or `stopCondition` that doesn't account for the GraphQL `data` wrapper (e.g. `users.pageInfo` instead of `data.users.pageInfo`)
|
|
121
|
+
- Not setting `modify: true` on mutation steps
|
|
122
|
+
|
|
123
|
+
## Error Recovery
|
|
124
|
+
|
|
125
|
+
When a GraphQL step fails:
|
|
126
|
+
|
|
127
|
+
1. **Check GraphQL-level errors** — even 200 responses can contain `errors` in the response body. Check `sourceData.stepId.data.errors` for field-level or validation errors.
|
|
128
|
+
2. **Verify the endpoint works** — use `sg system call` with `{ "query": "{ __typename }" }` to confirm connectivity and auth before debugging the query itself.
|
|
129
|
+
3. **Check schema compatibility** — fields, types, and arguments change between API versions. Use `sg system search-docs` to check the auto-introspected schema first. If unavailable, use targeted `__type` queries or web search to verify the current schema.
|
|
130
|
+
4. **Verify variables match the query signature** — GraphQL type mismatches (e.g. passing a string where `ID!` is expected) cause validation errors. Check that variable types in the query match what you're passing.
|
|
131
|
+
5. **Check for auth scope issues** — some GraphQL APIs return partial data with `null` fields and errors when the token lacks required scopes for specific fields.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# HTTP
|
|
2
|
+
|
|
3
|
+
## Step Configuration
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
{
|
|
7
|
+
type: "request",
|
|
8
|
+
systemId: "my_api",
|
|
9
|
+
url: "<<my_api_url>>/v1/users",
|
|
10
|
+
method: "GET",
|
|
11
|
+
headers: { "Authorization": "Bearer <<my_api_access_token>>" },
|
|
12
|
+
queryParams: { "limit": "<<limit>>", "status": "active" },
|
|
13
|
+
body: '{"name": "<<(sourceData) => sourceData.currentItem.name>>"}',
|
|
14
|
+
pagination: { ... }
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
All fields support `<<variable>>` and `<<(sourceData) => ...>>` expressions.
|
|
19
|
+
|
|
20
|
+
### Fields
|
|
21
|
+
|
|
22
|
+
| Field | Required | Notes |
|
|
23
|
+
| ------------- | -------- | ------------------------------------------------------- |
|
|
24
|
+
| `url` | yes | Prefer `<<systemId_url>>/path` over hardcoded base URLs |
|
|
25
|
+
| `method` | yes | `GET`, `POST`, `PUT`, `DELETE`, `PATCH` |
|
|
26
|
+
| `headers` | no | Auth headers, content type, custom headers |
|
|
27
|
+
| `queryParams` | no | Key-value pairs appended to URL |
|
|
28
|
+
| `body` | no | Stripped for GET/HEAD/DELETE/OPTIONS |
|
|
29
|
+
| `pagination` | no | See Pagination section |
|
|
30
|
+
| `systemId` | no | Links system credentials and URL. Omit for public APIs |
|
|
31
|
+
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
Credentials are never injected automatically. You must place auth headers explicitly using `<<systemId_credentialKey>>` syntax.
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// Bearer token (including OAuth — token refresh is automatic, header is not)
|
|
38
|
+
{ "Authorization": "Bearer <<systemId_access_token>>" }
|
|
39
|
+
|
|
40
|
+
// API key
|
|
41
|
+
{ "X-API-Key": "<<systemId_api_key>>" }
|
|
42
|
+
|
|
43
|
+
// Basic Auth — auto-encoded to Base64, do NOT manually encode
|
|
44
|
+
{ "Authorization": "Basic <<systemId_username>>:<<systemId_password>>" }
|
|
45
|
+
|
|
46
|
+
// Runtime credentials from payload
|
|
47
|
+
{ "Authorization": "Bearer <<(sourceData) => sourceData.user_access_token>>" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### SOAP Authentication
|
|
51
|
+
|
|
52
|
+
Some APIs (e.g. Salesforce) use SOAP XML login where credentials are embedded in the request body, not in headers. Multiple `<<>>` placeholders can appear adjacent — each is resolved independently and the results are concatenated.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
{
|
|
56
|
+
method: "POST",
|
|
57
|
+
url: "https://test.salesforce.com/services/Soap/u/59.0",
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "text/xml; charset=UTF-8",
|
|
60
|
+
"SOAPAction": "login"
|
|
61
|
+
},
|
|
62
|
+
body: '<?xml version="1.0" encoding="utf-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><n1:login xmlns:n1="urn:partner.soap.sforce.com"><n1:username><<systemId_username>></n1:username><n1:password><<systemId_password>><<systemId_security_token>></n1:password></n1:login></env:Body></env:Envelope>'
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`<<systemId_password>><<systemId_security_token>>` resolves to the password and token concatenated — no separator needed. This works in URL, headers, and body fields.
|
|
67
|
+
|
|
68
|
+
Headers starting with `x-` are treated as custom headers.
|
|
69
|
+
Modern APIs expect auth in headers, not query parameters, unless docs explicitly say otherwise.
|
|
70
|
+
|
|
71
|
+
## HTTP Runtime Details
|
|
72
|
+
|
|
73
|
+
### Request Behavior
|
|
74
|
+
|
|
75
|
+
- **GET, HEAD, DELETE, OPTIONS**: body is always stripped
|
|
76
|
+
- **POST, PUT, PATCH**: body with leading `{` is JSON-parsed; empty body becomes undefined
|
|
77
|
+
- Default headers: `Accept: */*`, Chrome-like `User-Agent`
|
|
78
|
+
- HTTPS: `rejectUnauthorized: false` (accepts self-signed certs)
|
|
79
|
+
|
|
80
|
+
### Body Resolution
|
|
81
|
+
|
|
82
|
+
The runtime processes the body in this order:
|
|
83
|
+
|
|
84
|
+
1. `<<>>` template variables are resolved — object/array returns are `JSON.stringify`'d before splicing
|
|
85
|
+
2. The resulting string is JSON-parsed when possible
|
|
86
|
+
3. `file::` references are resolved on the parsed object
|
|
87
|
+
4. Final send format is determined:
|
|
88
|
+
- raw bytes when the entire body is `file::<key>.raw`
|
|
89
|
+
- base64 string for `file::<key>.base64`
|
|
90
|
+
- `multipart/form-data` when the `Content-Type` header requests it and body resolves to an object
|
|
91
|
+
- `JSON.stringify(...)` for objects
|
|
92
|
+
- plain string for everything else
|
|
93
|
+
|
|
94
|
+
File references resolve only in the body, not in headers, query params, or URLs.
|
|
95
|
+
|
|
96
|
+
**Double-encoding risk:** the runtime stringifies at both step 1 (expression into template) and step 4 (object into 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.
|
|
97
|
+
|
|
98
|
+
### File Uploads
|
|
99
|
+
|
|
100
|
+
**Raw body** — send a file as the entire HTTP body:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
{
|
|
104
|
+
method: "PUT",
|
|
105
|
+
headers: { "Content-Type": "application/pdf" },
|
|
106
|
+
body: "file::document.raw"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Base64-in-JSON** — embed a base64-encoded file inside a structured payload:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
{
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { "Content-Type": "application/json" },
|
|
116
|
+
body: '{"fileBase64":"file::document.base64"}'
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Multipart form** — set the `Content-Type` header and provide a JSON object of form fields:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
{
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "Content-Type": "multipart/form-data" },
|
|
126
|
+
body: '{"file":"file::report.raw","description":"monthly export"}'
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The runtime builds a real `FormData` request: `file::...raw` values become file parts, scalars become text parts, non-file objects/arrays are JSON-stringified into text parts. The user-supplied `Content-Type` header is removed so `fetch` sets the boundary automatically.
|
|
131
|
+
|
|
132
|
+
Multipart restrictions:
|
|
133
|
+
|
|
134
|
+
- Body must resolve to a flat object of form fields
|
|
135
|
+
- Nested file refs inside objects are not supported
|
|
136
|
+
- Do not manually set a multipart boundary
|
|
137
|
+
- Use `.raw` for file parts, `.base64` for base64 text fields, `.extracted` for parsed content fields
|
|
138
|
+
|
|
139
|
+
### Response Handling
|
|
140
|
+
|
|
141
|
+
All HTTP responses are read as raw bytes, then classified by byte-level detection (see `file-handling.md` for the full detection logic).
|
|
142
|
+
|
|
143
|
+
- **Binary types** (PDF, Excel, DOCX, ZIP, GZIP): treated as files, parsed content also populates `data`. Later steps can reference via `file::<stepId>.raw/base64/extracted`.
|
|
144
|
+
- **`application/octet-stream`**: always treated as a file.
|
|
145
|
+
- **Responses over 25 MB**: treated as files as a safety fallback.
|
|
146
|
+
- **RAW + `Content-Disposition: attachment`**: treated as a file.
|
|
147
|
+
- **Structured/text responses** (JSON, XML, CSV, YAML, HTML, text under size limit): parsed inline to `data` only.
|
|
148
|
+
|
|
149
|
+
### Error Detection
|
|
150
|
+
|
|
151
|
+
Non-2xx responses throw with method, URL, response body preview (1000 chars), masked config, and retry count. Any response with status in the 2xx range is treated as success and returned to the step as-is — the runtime does not scan successful response bodies for error fields, so APIs that return HTTP 200 with `{ "error": "..." }` must be handled by your transform or by setting a `stopCondition`/assertion at the step level.
|
|
152
|
+
|
|
153
|
+
Bypass: set `failureBehavior: "continue"` to skip the non-2xx throw and return the response anyway.
|
|
154
|
+
|
|
155
|
+
### Retry Behavior
|
|
156
|
+
|
|
157
|
+
**Connection/server errors:**
|
|
158
|
+
|
|
159
|
+
- Default 5 retries, configurable via `retries` on the step
|
|
160
|
+
- Retries on fetch-level errors (network/DNS/abort except timeout) and on 5xx responses
|
|
161
|
+
- Fixed delay between retries (default 1000ms, configurable via `retryDelay`)
|
|
162
|
+
|
|
163
|
+
**Rate limiting (429):**
|
|
164
|
+
|
|
165
|
+
- Counts against the same retry budget as 5xx/network errors
|
|
166
|
+
- Respects `Retry-After` header (seconds or HTTP-date)
|
|
167
|
+
- Without header: exponential backoff `10^n * 1000ms + jitter` (capped at 1hr per wait)
|
|
168
|
+
- Total wait across all 429 retries capped at `MAX_RATE_LIMIT_WAIT_MS` (1hr)
|
|
169
|
+
|
|
170
|
+
**Timeouts:** Request timeout aborts the fetch and throws immediately — timeouts are not retried.
|
|
171
|
+
|
|
172
|
+
### Pagination
|
|
173
|
+
|
|
174
|
+
Makes multiple requests in a loop, merging results. Only configure after verifying the exact API pagination mechanism from docs.
|
|
175
|
+
|
|
176
|
+
**Configuration:**
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
pagination: {
|
|
180
|
+
type: "offsetBased" | "pageBased" | "cursorBased",
|
|
181
|
+
pageSize: "50",
|
|
182
|
+
cursorPath: "meta.next_cursor", // cursorBased only — dot-path to cursor
|
|
183
|
+
stopCondition: "(response, pageInfo) => !response.data.meta.next_cursor"
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Variables** — the matching variable MUST appear in the request (URL, queryParams, headers, or body). Throws if missing.
|
|
188
|
+
|
|
189
|
+
| Type | Variable | Starts at | Increments by |
|
|
190
|
+
| ----------- | ---------------------------- | --------- | ------------------------ |
|
|
191
|
+
| pageBased | `<<page>>` | 1 | 1 |
|
|
192
|
+
| offsetBased | `<<offset>>` | 0 | pageSize |
|
|
193
|
+
| cursorBased | `<<cursor>>` | null | extracted via cursorPath |
|
|
194
|
+
| all | `<<limit>>` / `<<pageSize>>` | pageSize | — |
|
|
195
|
+
|
|
196
|
+
**Stop conditions** — JS expression evaluated in sandbox. Receives `(response, pageInfo)`:
|
|
197
|
+
|
|
198
|
+
- `response.data` = parsed API response body
|
|
199
|
+
- `response.headers` = response headers
|
|
200
|
+
- `pageInfo = { page, offset, cursor, totalFetched }`
|
|
201
|
+
- Return `true` to STOP
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
"!response.data.meta.next_cursor";
|
|
205
|
+
"response.data.items.length === 0";
|
|
206
|
+
"response.data.hasMore === false";
|
|
207
|
+
"pageInfo.totalFetched >= 1000";
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Cursor extraction:** `cursorPath` is a simple dot-separated property path (e.g. `meta.next_cursor`, `data.0.id`) resolved against the parsed response body. It is not a JSONPath expression — wildcards, filters, and `$.` prefixes are not supported. For property names containing `@` (e.g. OData `@odata.nextLink`), use the name directly as the path. Null/undefined cursor stops pagination.
|
|
211
|
+
|
|
212
|
+
**Safety checks (when stopCondition is set):**
|
|
213
|
+
|
|
214
|
+
1. Identical pages 1 and 2 (both with data): throws — pagination params are not being applied
|
|
215
|
+
2. Both pages empty + stop did not trigger: throws — broken stop condition
|
|
216
|
+
3. Duplicate consecutive response after page 2: auto-stops
|
|
217
|
+
|
|
218
|
+
**Without stopCondition (fallback):**
|
|
219
|
+
|
|
220
|
+
- Array shorter than pageSize: stop
|
|
221
|
+
- Duplicate response hash against any previously seen page: stop
|
|
222
|
+
- Non-array response: stop after first request
|
|
223
|
+
- Max 500 requests (vs 1000 max with a stopCondition)
|
|
224
|
+
|
|
225
|
+
**Paginated step results:** all pages merge into a single result. Access via `sourceData.stepId.data` — it is NOT an array of per-page results. Do not call `.map()` on a paginated step result. Returns single object if one result, array if multiple (unwrapped from single-element array).
|
|
226
|
+
|
|
227
|
+
## Common Pitfalls
|
|
228
|
+
|
|
229
|
+
- Hardcoding base URLs that match the system's base URL instead of using `<<systemId_url>>`
|
|
230
|
+
- Forgetting auth headers entirely — nothing is injected automatically, including for OAuth systems (only token refresh is automatic)
|
|
231
|
+
- Putting pagination variables in the config but not setting a `pagination` block, or vice versa
|
|
232
|
+
- Using `<<currentItem.id>>` — must use arrow function syntax: `<<(sourceData) => sourceData.currentItem.id>>`
|
|
233
|
+
- Mixing multiple `<<>>` expressions in one body string when the API expects nested JSON — use a transform step instead
|
|
234
|
+
- Setting `method: "POST"` for GraphQL queries without `modify: false` — POST that only reads should not be marked as modifying
|
|
235
|
+
|
|
236
|
+
## Error Recovery
|
|
237
|
+
|
|
238
|
+
When an HTTP step fails and the cause is not immediately clear:
|
|
239
|
+
|
|
240
|
+
1. **Isolate endpoint vs system-level issues** — use `sg system call` to test a different endpoint on the same system. If multiple endpoints fail, the issue is likely global authentication or system configuration. If only one fails, the issue is endpoint-specific (wrong URL, missing params, incorrect method or authentication scopes).
|
|
241
|
+
|
|
242
|
+
2. **Check credential scopes and system-side configuration** — a 403 or empty response often means the API key or OAuth token lacks required scopes, or the resource is restricted server-side. Use `sg system search-docs` or a web search to look up required scopes/permissions for the endpoint.
|
|
243
|
+
|
|
244
|
+
3. **Verify request shape against API docs** — use `sg system search-docs` and web search to confirm the endpoint URL, required headers, query parameters, body format, and expected method. APIs change across versions. Search for the error message and the endpoint in docs and online.
|
|
245
|
+
|
|
246
|
+
4. **Check for rate limiting** — 429 errors are retried automatically with backoff, but persistent 429s may mean the API key has hit a quota. Check API dashboard or docs for rate limit details.
|