@superglue/cli 1.1.22 → 1.1.24

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.
@@ -139,23 +139,49 @@ Prefer discovering aliases dynamically via `stepFileKeys`:
139
139
 
140
140
  ### Producing Files From Transforms
141
141
 
142
- Return a `__files__` key from a transform to produce new files:
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:
143
143
 
144
144
  ```javascript
145
+ // Simplest: key = filename, value = string content (auto-encoded to bytes)
145
146
  (sourceData) => ({
146
- summary: "processed",
147
147
  __files__: {
148
- "output.csv": {
149
- filename: "output.csv",
150
- contentType: "text/csv",
151
- raw: new TextEncoder().encode("a,b\n1,2\n"),
152
- },
148
+ "report.csv": Papa.unparse(sourceData.step1.data),
153
149
  },
154
- });
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
+ })
155
160
  ```
156
161
 
162
+ Supported value types for `__files__` entries:
163
+
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).
168
+
157
169
  Everything except `__files__` becomes the step's `data`. Single-file transforms get the step ID as alias; multi-file transforms use bracket notation.
158
170
 
171
+ **Available libraries in transforms:** The following libraries are available for file generation in any transform code (step transforms and output transforms):
172
+
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:
178
+
179
+ ```javascript
180
+ (sourceData) => ({
181
+ __files__: { "report.csv": Papa.unparse(sourceData.step1.data) },
182
+ });
183
+ ```
184
+
159
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`).
160
186
 
161
187
  **`raw` field rules:**
@@ -166,6 +192,36 @@ Everything except `__files__` becomes the step's `data`. Single-file transforms
166
192
  - `null` / `undefined` — produces an empty file (0 bytes)
167
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
168
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
+ }
217
+ ```
218
+
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
+
169
225
  ## Common Pitfalls
170
226
 
171
227
  - Using bare `file::<key>` without `.raw`, `.base64`, or `.extracted` suffix — causes a runtime error
@@ -174,6 +230,8 @@ Everything except `__files__` becomes the step's `data`. Single-file transforms
174
230
  - Hardcoding bracket aliases like `sourceData.__files__["step[\"file.csv\"]"]` — prefer `stepFileKeys` or `Object.keys(sourceData.__files__)` to discover aliases dynamically
175
231
  - Trying to construct binary file internals (xlsx, zip) as JS objects — always pass through original bytes via `.raw`
176
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.
177
235
 
178
236
  ## Error Recovery
179
237
 
@@ -224,10 +224,15 @@ pagination: {
224
224
 
225
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
226
 
227
+ ### File Output
228
+
229
+ When an HTTP step downloads a file (PDF, CSV, binary response), set `outputFile: true` on the step to make it downloadable by the user. Without the flag, downloaded files stay internal (only accessible to subsequent steps via `file::` references).
230
+
227
231
  ## Common Pitfalls
228
232
 
229
233
  - Hardcoding base URLs that match the system's base URL instead of using `<<systemId_url>>`
230
234
  - Forgetting auth headers entirely — nothing is injected automatically, including for OAuth systems (only token refresh is automatic)
235
+ - Forgetting `outputFile: true` on steps that download files the user wants
231
236
  - Putting pagination variables in the config but not setting a `pagination` block, or vice versa
232
237
  - Using `<<currentItem.id>>` — must use arrow function syntax: `<<(sourceData) => sourceData.currentItem.id>>`
233
238
  - Mixing multiple `<<>>` expressions in one body string when the API expects nested JSON — use a transform step instead
@@ -0,0 +1,162 @@
1
+ # ODBC
2
+
3
+ ## Step Configuration
4
+
5
+ ```typescript
6
+ {
7
+ type: "request",
8
+ systemId: "my_sybase_db",
9
+ url: "odbc://<<my_sybase_db_username>>:<<my_sybase_db_password>>@<<my_sybase_db_host>>:<<my_sybase_db_port>>/<<my_sybase_db_database>>?DRIVER=<<my_sybase_db_driver>>",
10
+ body: '{"query": "SELECT * FROM users WHERE id = ?", "params": [<<userId>>]}'
11
+ }
12
+ ```
13
+
14
+ Configure the connection URL based on which credentials are stored in the system — use `sg system find` to check `storedCredentials` and reference them accordingly.
15
+
16
+ `method`, `headers`, `queryParams`, and `pagination` are HTTP-only fields — omit them for database steps.
17
+
18
+ ### Fields
19
+
20
+ | Field | Required | Notes |
21
+ | ---------- | -------- | -------------------------------------------------------------- |
22
+ | `url` | yes | Connection string with `<<credential>>` placeholders |
23
+ | `body` | yes | JSON string with `query` and optional `params` |
24
+ | `systemId` | no | Links system credentials for `<<systemId_credKey>>` resolution |
25
+
26
+ ### URL Format
27
+
28
+ ```
29
+ odbc://user:password@host:port/database?DRIVER=DriverName
30
+ odbc://user:password@host:port/database?DRIVER=FreeTDS&TDS_Version=5.0
31
+ ```
32
+
33
+ The path contains the **database name**. The ODBC driver is specified via the `DRIVER` query parameter (defaults to `FreeTDS` if omitted). This format is consistent with other superglue database strategies (postgres, mssql) where the path is always the database.
34
+
35
+ **Supported drivers:**
36
+
37
+ | Driver | Database | Default Port |
38
+ | --------- | ---------------------------- | ------------ |
39
+ | `FreeTDS` | SAP ASE (Sybase), SQL Server | 5000 |
40
+
41
+ Additional query parameters (beyond `DRIVER`) are passed through to the ODBC connection string as key=value pairs.
42
+
43
+ ### Body Format
44
+
45
+ JSON string with `query` and optional `params` (or `values`):
46
+
47
+ ```json
48
+ {
49
+ "query": "SELECT * FROM users WHERE age > ? AND status = ?",
50
+ "params": [25, "active"]
51
+ }
52
+ ```
53
+
54
+ ## Authentication
55
+
56
+ Credentials are embedded directly in the connection URL using `<<systemId_credentialKey>>` placeholders. Nothing is injected automatically.
57
+
58
+ ```
59
+ odbc://<<my_db_username>>:<<my_db_password>>@<<my_db_host>>:<<my_db_port>>/<<my_db_database>>?DRIVER=<<my_db_driver>>
60
+ ```
61
+
62
+ Credential variables are resolved at runtime before connecting.
63
+
64
+ ## ODBC Runtime Details
65
+
66
+ ### Schema Discovery
67
+
68
+ superglue does not auto-introspect ODBC database schemas. Before building tools, always query the schema manually with `sg system call` to discover tables and columns.
69
+
70
+ **SAP ASE (Sybase):**
71
+
72
+ ```json
73
+ {
74
+ "query": "SELECT o.name AS table_name, c.name AS column_name, t.name AS data_type, CASE WHEN c.status & 8 = 8 THEN 'YES' ELSE 'NO' END AS is_nullable FROM sysobjects o JOIN syscolumns c ON o.id = c.id JOIN systypes t ON c.usertype = t.usertype WHERE o.type = 'U' ORDER BY o.name, c.colid"
75
+ }
76
+ ```
77
+
78
+ Always verify table and column names against the actual schema before building tools.
79
+
80
+ ### Parameterized Queries
81
+
82
+ Always use parameterized queries with positional `?` placeholders — prevents SQL injection. Parameters are bound positionally from the `params` array.
83
+
84
+ For numeric or boolean parameters in query params arrays, use unquoted placeholders: `[<<numericParam>>]` not `["<<numericParam>>"]`. The quotes force string resolution.
85
+
86
+ ```json
87
+ { "query": "SELECT * FROM orders WHERE name = ? AND customer_id = ?", "params": ["<<customerName>>", <<customerId>>] }
88
+ ```
89
+
90
+ With expressions:
91
+
92
+ ```json
93
+ {
94
+ "query": "INSERT INTO logs (message, level) VALUES (?, ?)",
95
+ "params": ["<<(sourceData) => sourceData.message>>", "error"]
96
+ }
97
+ ```
98
+
99
+ ### Return Value
100
+
101
+ Returns an array of row objects with column names as keys.
102
+
103
+ - `SELECT`: array of matching rows
104
+ - `INSERT/UPDATE/DELETE`: empty array (SAP ASE does not support `RETURNING` — query the data separately if needed)
105
+
106
+ ### Batch Operations
107
+
108
+ Use a data selector returning an array to execute a query per item:
109
+
110
+ ```javascript
111
+ // dataSelector: (sourceData) => sourceData.newUsers
112
+ // body: {"query": "INSERT INTO users (name) VALUES (?)", "params": ["<<(sourceData) => sourceData.currentItem.name>>"]}
113
+ ```
114
+
115
+ ### Retry Behavior
116
+
117
+ Default retries from server config. On final failure, error includes a truncated query preview.
118
+
119
+ ## SAP ASE (Sybase) Specifics
120
+
121
+ ### Differences from SQL Server
122
+
123
+ SAP ASE uses Transact-SQL like SQL Server, but with key differences:
124
+
125
+ - **No `OUTPUT` clause** — use `SELECT @@identity` after INSERT to get the last inserted identity value
126
+ - **No `TOP` with `ORDER BY` guarantee** — use `SET ROWCOUNT n` before the query instead
127
+ - **`CONVERT` syntax differs** — `CONVERT(target_type, expression)` not `CONVERT(expression, style)`
128
+ - **String concatenation** — use `+` operator, not `CONCAT()` (unless ASE 16.0+)
129
+ - **Date functions** — use `GETDATE()` not `CURRENT_TIMESTAMP`, `DATEADD`/`DATEDIFF` syntax matches SQL Server
130
+
131
+ ### TDS Protocol Version
132
+
133
+ FreeTDS defaults to TDS 5.0 for Sybase connections. If you encounter protocol errors, you can specify the version via query parameter:
134
+
135
+ ```
136
+ odbc://user:pass@host:5000/mydb?DRIVER=FreeTDS&TDS_Version=5.0
137
+ ```
138
+
139
+ | TDS Version | Use Case |
140
+ | ----------- | -------------------------- |
141
+ | `5.0` | SAP ASE / Sybase (default) |
142
+ | `7.0` | SQL Server 7.0+ |
143
+ | `7.4` | SQL Server 2012+ |
144
+
145
+ ## Common Pitfalls
146
+
147
+ - Using `@param1`, `@param2` (MSSQL syntax) or `$1`, `$2` (PostgreSQL syntax) instead of positional `?` placeholders — ODBC uses `?` for all parameter binding
148
+ - Forgetting that SAP ASE has no `OUTPUT`/`RETURNING` clause — mutations return empty arrays; query the data separately
149
+ - Using `CONCAT()` on older ASE versions — use `+` for string concatenation instead
150
+ - Driver name mismatch — the `DRIVER` query parameter must exactly match the section name in `odbcinst.ini` (case-sensitive). Use `DRIVER=FreeTDS`, not `DRIVER=freetds` or `DRIVER=FREETDS`
151
+ - Accessing step result directly instead of via `.data` — database results follow the same envelope as all steps: `sourceData.stepId.data`
152
+
153
+ ## Error Recovery
154
+
155
+ When an ODBC step fails and the cause is not obvious:
156
+
157
+ 1. **Read the error message** — superglue includes the a truncated query preview in the error. Check for syntax errors, missing columns, or type mismatches.
158
+ 2. **Verify connection credentials** — use `sg system find` to confirm `storedCredentials` keys match the placeholders in your URL. Test connectivity with a simple `SELECT 1` via `sg system call`.
159
+ 3. **Check database-side access** — connection refused or permission denied errors typically mean the database user lacks access. Verify with the user that the credentials have the required grants.
160
+ 4. **Check driver availability** — if the error mentions "driver not found", the `DRIVER` query parameter does not match any registered driver in `/etc/odbcinst.ini`. Verify the driver name is correct and the driver is installed in the Docker image.
161
+ 5. **Test the query directly** — use `sg system call` with a simplified version of the query to isolate whether the issue is the query itself, the parameters, or the connection.
162
+ 6. **Check TDS version** — protocol errors may indicate a TDS version mismatch. Try adding `?TDS_Version=5.0` to the query parameters for SAP ASE connections.
@@ -134,9 +134,14 @@ Single operation returns the result directly. Array of operations returns an arr
134
134
 
135
135
  Default retries from server config. On final failure, error includes the full operations list. Each retry creates a fresh connection.
136
136
 
137
+ ### File Output
138
+
139
+ When the user wants to **download** a file fetched via SFTP/SMB/FTP, set `outputFile: true` on the step. This stores the file in S3 and returns a download link in the API response. Without the flag, fetched files stay internal (only accessible to subsequent steps via `file::` references).
140
+
137
141
  ## Common Pitfalls
138
142
 
139
143
  - Using absolute filesystem paths instead of paths relative to the URL base path
144
+ - Forgetting `outputFile: true` on steps that download files the user wants — without it, the file is only available internally between steps
140
145
  - Forgetting that SMB requires a share name as the first URL path segment
141
146
  - Using `file::<key>` without a suffix in `put` content. Use `file::<key>.raw` to preserve original bytes
142
147
  - Missing the double-data access pattern for `get` results: `sourceData.stepId.data` gives you the parsed file content (through the step envelope)