@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.
Files changed (34) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -7
  3. package/dist/cli.js +307 -77
  4. package/dist/skills/superglue/SKILL.md +315 -631
  5. package/dist/skills/superglue/references/file-handling.md +180 -96
  6. package/dist/skills/superglue/references/graphql.md +131 -0
  7. package/dist/skills/superglue/references/http.md +251 -0
  8. package/dist/skills/superglue/references/integration.md +4 -2
  9. package/dist/skills/superglue/references/mssql.md +207 -0
  10. package/dist/skills/superglue/references/odbc.md +162 -0
  11. package/dist/skills/superglue/references/postgres.md +154 -0
  12. package/dist/skills/superglue/references/redis.md +68 -118
  13. package/dist/skills/superglue/references/sftp-smb.md +161 -0
  14. package/dist/skills/superglue/references/superglue-info.md +91 -0
  15. package/package.json +1 -1
  16. package/skills/superglue/SKILL.md +315 -631
  17. package/skills/superglue/references/file-handling.md +180 -96
  18. package/skills/superglue/references/graphql.md +131 -0
  19. package/skills/superglue/references/http.md +251 -0
  20. package/skills/superglue/references/integration.md +4 -2
  21. package/skills/superglue/references/mssql.md +207 -0
  22. package/skills/superglue/references/odbc.md +162 -0
  23. package/skills/superglue/references/postgres.md +154 -0
  24. package/skills/superglue/references/redis.md +68 -118
  25. package/skills/superglue/references/sftp-smb.md +161 -0
  26. package/skills/superglue/references/superglue-info.md +91 -0
  27. package/dist/skills/superglue/references/data-handling.md +0 -303
  28. package/dist/skills/superglue/references/databases.md +0 -253
  29. package/dist/skills/superglue/references/file-servers.md +0 -112
  30. package/dist/skills/superglue/references/http-apis.md +0 -248
  31. package/skills/superglue/references/data-handling.md +0 -303
  32. package/skills/superglue/references/databases.md +0 -253
  33. package/skills/superglue/references/file-servers.md +0 -112
  34. package/skills/superglue/references/http-apis.md +0 -248
@@ -1,160 +1,244 @@
1
1
  # File Handling
2
2
 
3
- How the runtime detects, parses, and exposes files across all protocol strategies (HTTP, FTP, SFTP, SMB).
3
+ How the runtime detects, parses, and exposes files across all protocol strategies (HTTP, SFTP/FTP, SMB).
4
4
 
5
- ## When Does a Response Become a File?
5
+ ## Step Configuration
6
6
 
7
- Every response is read as raw bytes. The runtime decides whether it routes to `producedFiles` (i.e. `sourceData.__files__`) or inline `data` using a priority chain — **not** based on size alone:
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
- 1. **Magic-byte detection** (primary) — the raw bytes are inspected for known binary signatures (ZIP `PK\x03\x04`, PDF `%PDF`, GZIP `\x1f\x8b`, plus internal ZIP structure checks for Excel/DOCX). If matched, the response is a file regardless of size or HTTP headers. A 1KB ZIP and a 500MB ZIP both go to `__files__`.
10
- 2. **`Content-Disposition: attachment`** — any response with `attachment` or `filename=` in this header becomes a file, even if byte detection found structured text.
11
- 3. **`application/octet-stream`** — explicit octet-stream MIME becomes a file, catching binary payloads whose bytes don't match a known signature.
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
- Structured text responses (JSON, XML, CSV, YAML, HTML) that pass none of the above checks go to `data` only — no file entry.
13
+ // Reference a user-uploaded file
14
+ { "body": '{"fileBase64": "file::document.base64"}' }
15
15
 
16
- | Classification | Types | Where result lives |
17
- | ------------------- | --------------------------- | -------------------------------------------------------------- |
18
- | **Binary** | PDF, Excel, DOCX, ZIP, GZIP | `producedFiles` + `data` (extracted/parsed content) |
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
- Binary responses expose `stepFileKeys` and populate `sourceData.__files__`. Structured text responses only populate `sourceData.stepId.data`.
20
+ ### file:: Reference Syntax
23
21
 
24
- File server `get` operations always produce files regardless of detected type — content goes into both `data` and `producedFiles`.
22
+ Use `file::<alias>.<suffix>` in step config `body` / `content` fields:
25
23
 
26
- ## Auto-Parsing
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
- | Type | Parsed result |
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
- Detection is heuristic for text formats. YAML requires `---`, `%YAML`, or multiple `key: value` lines to be recognized. Short or ambiguous YAML can fall through to RAW and appear as a plain string instead of a parsed object — both in `.data` and `.extracted`.
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
- ### Excel rules
37
+ ## Authentication
45
38
 
46
- - Excel is NEVER a flat array. It is always `{ "SheetName": [ {row}, ... ] }` — even for single-sheet files.
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
- ## file:: Reference Syntax
41
+ ## File Handling Runtime Details
51
42
 
52
- Use `file::<alias>.<suffix>` in step config `body` / `content` fields:
43
+ ### Detection Priority
53
44
 
54
- | Suffix | Returns | Use case |
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:
46
+
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`)
59
51
 
60
- Rules:
52
+ File server `get` operations always produce files regardless of detected type.
61
53
 
62
- - Bare `file::<key>` without a suffix is **NOT valid** — causes a runtime error
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
54
+ ### Classification
55
+
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 |
67
61
 
68
- ## File Aliasing
62
+ Binary responses expose `stepFileKeys` and populate `sourceData.__files__`. Structured text responses only populate `sourceData.stepId.data`.
69
63
 
70
- When a step produces files, the runtime first chooses a **root alias**:
64
+ ### Auto-Parsing
71
65
 
72
- - non-loop step: `stepId`
73
- - loop iteration: `stepId[N]`
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
- Then it decides whether that root needs a filename suffix:
80
+ **Excel rules:**
76
81
 
77
- - if that step/iteration produced **exactly one file**, the alias is just the root
78
- - if that step/iteration produced **multiple files**, append `["filename.ext"]` to distinguish them
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
- Examples:
86
+ ### File Aliasing
81
87
 
82
- - one direct download: `downloadReport`
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
- This is about **one file vs many files at that step/iteration**, not about whether the runtime knows the filename. File server `get` operations know the filename, but single-file outputs still use the plain step ID (or `stepId[N]`) alias.
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 (those with a `dataSelector` that iterates) 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.
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
- ## Accessing Files in Transforms
101
+ ### stepFileKeys
94
102
 
95
- Inside `transformCode` and `outputTransform`, use `sourceData.__files__` with the **runtime alias**:
103
+ Each step result exposes `stepFileKeys` — an array of runtime aliases for files produced by that step:
96
104
 
97
105
  ```javascript
98
- sourceData.__files__.downloadStep; // correct (step ID for single file)
99
- sourceData.__files__["doubled.csv"]; // wrong (local filename)
106
+ sourceData.downloadStep.stepFileKeys; // ["downloadStep"]
107
+ sourceData.batchDownload[0].stepFileKeys; // ["batchDownload[0]"]
100
108
  ```
101
109
 
102
- For multi-file aliases with bracket notation, prefer discovering aliases from `stepFileKeys` or
103
- `Object.keys(sourceData.__files__)` instead of hardcoding quoted alias strings inside the transform
104
- code string. This avoids JSON/JS escaping mistakes like:
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__['fetchThreeFiles[".bashrc"]']; // brittle inside JSON-encoded transform strings
115
+ sourceData.__files__.downloadStep; // single-file step
116
+ sourceData.__files__["stepId[0]"]; // loop iteration
108
117
  ```
109
118
 
110
- Prefer:
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
- `sourceData.__files__` is **read-only**. To produce new files from a transform, return them via a `__files__` key:
140
+ ### Producing Files From Transforms
141
+
142
+ Return a `__files__` key from a transform to produce new files. The **key name is the filename** — content type is inferred from the extension. Values can be plain strings, bytes, or full file objects:
126
143
 
127
144
  ```javascript
145
+ // Simplest: key = filename, value = string content (auto-encoded to bytes)
128
146
  (sourceData) => ({
129
- summary: "processed",
130
147
  __files__: {
131
- "output.csv": {
132
- filename: "output.csv",
133
- contentType: "text/csv",
134
- raw: new TextEncoder().encode("a,b\n1,2\n"),
135
- },
148
+ "report.csv": Papa.unparse(sourceData.step1.data),
136
149
  },
137
- });
150
+ })
151
+
152
+ // Multiple files with different types
153
+ (sourceData) => ({
154
+ __files__: {
155
+ "data.csv": Papa.unparse(sourceData.step1.data),
156
+ "config.yaml": yaml.dump(sourceData.step1.data),
157
+ },
158
+ summary: { rowCount: sourceData.step1.data.length },
159
+ })
138
160
  ```
139
161
 
140
- Everything except `__files__` becomes the step's `data`. Single-file transforms get the step ID as alias; multi-file transforms use bracket notation.
162
+ Supported value types for `__files__` entries:
141
163
 
142
- ### `raw` rules
164
+ - **String** — auto-encoded to UTF-8 bytes. Filename and content type from the key.
165
+ - **Uint8Array / ArrayBuffer** — used as-is. Filename and content type from the key.
166
+ - **Object with `raw`** — `raw` is normalized (string/bytes/array), `filename` defaults to key if missing, `contentType` inferred from key extension if missing.
167
+ - **Full file object** — `{ filename, contentType, raw }` for explicit control (backwards compatible).
143
168
 
144
- - `raw` accepts: `Uint8Array | ArrayBuffer | number[] | string | object | null`.
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.
169
+ Everything except `__files__` becomes the step's `data`. Single-file transforms get the step ID as alias; multi-file transforms use bracket notation.
150
170
 
151
- ## stepFileKeys
171
+ **Available libraries in transforms:** The following libraries are available for file generation in any transform code (step transforms and output transforms):
152
172
 
153
- Each step result exposes `stepFileKeys` — an array of runtime aliases for files produced by that step:
173
+ - `Papa` — [papaparse](https://www.papaparse.com/). Use `Papa.unparse(rows)` to generate CSV from an array of objects. Handles quoting, escaping, and special characters automatically.
174
+ - `XLSX` — [SheetJS](https://sheetjs.com/). Use `XLSX.utils.json_to_sheet(data)` + `XLSX.write(wb, { type: "array", bookType: "xlsx" })` to generate Excel files.
175
+ - `yaml` — [js-yaml](https://github.com/nodeca/js-yaml). Use `yaml.dump(obj)` to generate YAML.
176
+
177
+ Example — CSV with Papa:
154
178
 
155
179
  ```javascript
156
- sourceData.downloadStep.stepFileKeys; // ["downloadStep"]
157
- sourceData.batchDownload[0].stepFileKeys; // ["batchDownload[0]"]
180
+ (sourceData) => ({
181
+ __files__: { "report.csv": Papa.unparse(sourceData.step1.data) },
182
+ });
183
+ ```
184
+
185
+ **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`).
186
+
187
+ **`raw` field rules:**
188
+
189
+ - `Uint8Array` / `ArrayBuffer` / typed arrays — used as-is for binary content
190
+ - String — UTF-8 encoded. Pass a JSON string for structured data: `raw: JSON.stringify(data)`
191
+ - Object / array — auto JSON-stringified to UTF-8 bytes. Prefer explicit `JSON.stringify` with an appropriate `contentType` for clarity
192
+ - `null` / `undefined` — produces an empty file (0 bytes)
193
+ - 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
194
+
195
+ ### Returning Files as Tool Output
196
+
197
+ To make files downloadable from the tool's API response, set `outputFile: true` on the step that produces the file. The output transform is only for shaping JSON data — it has nothing to do with files.
198
+
199
+ ```json
200
+ {
201
+ "steps": [
202
+ {
203
+ "id": "fetchData",
204
+ "config": { "type": "request", "url": "https://api.example.com/report" }
205
+ },
206
+ {
207
+ "id": "generateReport",
208
+ "config": {
209
+ "type": "transform",
210
+ "transformCode": "(sourceData) => ({ __files__: { \"report.csv\": Papa.unparse(sourceData.fetchData.data) } })"
211
+ },
212
+ "outputFile": true
213
+ }
214
+ ],
215
+ "outputTransform": "(sourceData) => ({ rowCount: sourceData.fetchData.data.length })"
216
+ }
158
217
  ```
159
218
 
160
- Use `stepFileKeys` to dynamically discover which file aliases are available from a step.
219
+ Only steps with `outputFile: true` have their files stored to S3 and returned as downloadable artifacts. Steps without the flag keep their files internal (for inter-step passing only).
220
+
221
+ This works on any step type — transform steps, HTTP steps, SFTP/FTP steps. Just set `outputFile: true`.
222
+
223
+ Use `sg run download <runId>` to download file artifacts from the CLI.
224
+
225
+ ## Common Pitfalls
226
+
227
+ - Using bare `file::<key>` without `.raw`, `.base64`, or `.extracted` suffix — causes a runtime error
228
+ - Placing file references in headers or query params instead of body/content fields — they only resolve in body
229
+ - Expecting Excel parsed output to be a flat array — it is always `{ "SheetName": [...] }` even for single-sheet files
230
+ - Hardcoding bracket aliases like `sourceData.__files__["step[\"file.csv\"]"]` — prefer `stepFileKeys` or `Object.keys(sourceData.__files__)` to discover aliases dynamically
231
+ - Trying to construct binary file internals (xlsx, zip) as JS objects — always pass through original bytes via `.raw`
232
+ - Accessing `sourceData.__files__` in step configs — `file::` syntax is for step configs, `sourceData.__files__` is for transform code
233
+ - Manually base64-encoding file content with `btoa()` — never do this. Return raw bytes (Uint8Array) or strings directly. The runtime handles encoding automatically. Use `XLSX.write(wb, { type: "array" })` not `type: "buffer"` followed by manual btoa.
234
+ - Putting file logic in the output transform — the output transform is for JSON data only. Use `outputFile: true` on the step instead.
235
+
236
+ ## Error Recovery
237
+
238
+ When a file-related step fails:
239
+
240
+ 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
241
+ 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.
242
+ 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.
243
+ 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).
244
+ 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.