@superglue/cli 1.1.10 → 1.1.12

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.
@@ -0,0 +1,88 @@
1
+ # Database Steps (PostgreSQL)
2
+
3
+ ## Step Configuration
4
+
5
+ ```typescript
6
+ {
7
+ type: "request",
8
+ systemId: "my_postgres_db",
9
+ url: "postgres://<<my_postgres_db_user>>:<<my_postgres_db_password>>@<<my_postgres_db_host>>:<<my_postgres_db_port>>/<<my_postgres_db_database>>",
10
+ method: "POST", // ignored for Postgres but required by schema
11
+ body: '{"query": "SELECT * FROM users WHERE id = $1", "params": [<<userId>>]}'
12
+ }
13
+ ```
14
+
15
+ Configure the connection url depending on which credentials are saved in the stored db system - reference them accordingly.
16
+
17
+ ## URL Format
18
+
19
+ ```
20
+ postgres://user:password@host:port/database
21
+ postgresql://user:password@host:port/database
22
+ ```
23
+
24
+ All credential variables are resolved before connecting. Trailing slashes and extra slashes before query strings are cleaned.
25
+
26
+ ## Body Format
27
+
28
+ JSON string with `query` and optional `params` (or `values`):
29
+
30
+ ```json
31
+ { "query": "SELECT * FROM users WHERE age > $1 AND status = $2", "params": [25, "active"] }
32
+ ```
33
+
34
+ ### Parameterized Queries
35
+
36
+ **Always use parameterized queries** with `$1`, `$2`, etc. — prevents SQL injection.
37
+
38
+ ```json
39
+ { "query": "SELECT * FROM orders WHERE customer_id = $1", "params": ["<<customerId>>"] }
40
+ ```
41
+
42
+ With expressions:
43
+
44
+ ```json
45
+ {
46
+ "query": "INSERT INTO logs (message, level) VALUES ($1, $2)",
47
+ "params": ["<<(sourceData) => sourceData.message>>", "error"]
48
+ }
49
+ ```
50
+
51
+ With RETURNING:
52
+
53
+ ```json
54
+ {
55
+ "query": "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
56
+ "params": [
57
+ "<<(sourceData) => sourceData.currentItem.name>>",
58
+ "<<(sourceData) => sourceData.currentItem.email>>"
59
+ ]
60
+ }
61
+ ```
62
+
63
+ ## Connection Pooling
64
+
65
+ - Pools cached by connection string, max 10 clients, 5s connection timeout
66
+ - Idle pools cleaned up automatically
67
+ - SSL auto-configured: enabled (with `rejectUnauthorized: false`) unless connecting to localhost without explicit sslmode
68
+
69
+ ## Retry Logic
70
+
71
+ Default retries from server config. On final failure, error includes query text and params.
72
+
73
+ ## Return Value
74
+
75
+ Returns `result.rows` — array of row objects with column names as keys.
76
+
77
+ - `SELECT`: array of matching rows
78
+ - `INSERT/UPDATE/DELETE` with `RETURNING`: array of returned rows
79
+ - Mutations without `RETURNING`: empty array
80
+
81
+ ## Batch Operations via Loop
82
+
83
+ Use a data selector returning an array to execute a query per item:
84
+
85
+ ```javascript
86
+ // dataSelector: (sourceData) => sourceData.newUsers
87
+ // body: {"query": "INSERT INTO users (name) VALUES ($1)", "params": ["<<(sourceData) => sourceData.currentItem.name>>"]}
88
+ ```
@@ -0,0 +1,136 @@
1
+ # File Server Steps (FTP / SFTP / SMB)
2
+
3
+ FTP, SFTP, and SMB share an identical operation interface. Only the URL protocol and connection details differ.
4
+
5
+ ## URL Formats
6
+
7
+ ```
8
+ ftp://user:password@host:port/basePath (port 21)
9
+ ftps://user:password@host:port/basePath (port 21, TLS)
10
+ sftp://user:password@host:port/basePath (port 22)
11
+ smb://user:password@host/sharename/basePath (port 445)
12
+ smb://domain\user:password@host/sharename/ (domain auth)
13
+ ```
14
+
15
+ All support `<<variable>>` syntax in the URL.
16
+
17
+ SMB requires a share name as the first path segment. Additional segments become a base path prepended to all operation paths.
18
+
19
+ ## Path Handling
20
+
21
+ **All operation paths are relative to the base path in the URL.** Do NOT use absolute filesystem paths.
22
+
23
+ If the system URL is `sftp://user:pass@host/home/sftptest/uploads`:
24
+
25
+ - `report.csv` → `/home/sftptest/uploads/report.csv`
26
+ - `subdir/data.csv` → `/home/sftptest/uploads/subdir/data.csv`
27
+
28
+ Do NOT use `/home/sftptest/uploads/report.csv` — the base path is already set in the URL.
29
+
30
+ If the URL has no base path (e.g., `sftp://user:pass@host`), operation paths are used as-is from the server root.
31
+
32
+ ## Body Format
33
+
34
+ JSON string — single operation or array of operations (batch):
35
+
36
+ ### Single
37
+
38
+ ```json
39
+ { "operation": "get", "path": "data/report.csv" }
40
+ ```
41
+
42
+ ### Batch (sequential, same connection)
43
+
44
+ ```json
45
+ [
46
+ { "operation": "mkdir", "path": "backup" },
47
+ { "operation": "get", "path": "data/report.csv" },
48
+ { "operation": "put", "path": "backup/report.csv", "content": "data here" }
49
+ ]
50
+ ```
51
+
52
+ ## Supported Operations
53
+
54
+ | Operation | Required fields | Returns |
55
+ | --------- | ----------------- | ----------------------------------------------------------------------- |
56
+ | `list` | `path` | Array of `{ name, path, size, type, modifyTime, ... }` |
57
+ | `get` | `path` | Auto-parsed file content (CSV→objects, JSON→parsed, etc.) or raw string |
58
+ | `put` | `path`, `content` | `{ success, message, size }` |
59
+ | `delete` | `path` | `{ success, message }` |
60
+ | `rename` | `path`, `newPath` | `{ success, message }` |
61
+ | `mkdir` | `path` | `{ success, message }` |
62
+ | `rmdir` | `path` | `{ success, message }` |
63
+ | `exists` | `path` | `{ exists: boolean, path }` |
64
+ | `stat` | `path` | File metadata or `{ exists: false }` |
65
+
66
+ ### `get` — Auto-Parsing
67
+
68
+ Downloaded files are all parsed automatically:
69
+
70
+ - CSV, JSON, XML, Excel, PDF, etc. → parsed to JS objects
71
+ - Falls back to UTF-8 string
72
+ - SMB additionally detects binary files (returns `{ _binary: true, encoding: "base64", data: "..." }`)
73
+
74
+ ### `put` — Content Handling
75
+
76
+ - String → written as-is
77
+ - Buffer → written directly
78
+ - Object/Array → `JSON.stringify` with 2-space indent
79
+
80
+ ## Connection Details
81
+
82
+ ### SFTP
83
+
84
+ Uses `ssh2-sftp-client`. Supports password + private key auth (`credentials.privateKey`, `credentials.passphrase`).
85
+
86
+ ### FTP/FTPS
87
+
88
+ Uses `basic-ftp`. FTPS uses TLS with `rejectUnauthorized: false`.
89
+
90
+ ### SMB
91
+
92
+ Uses `@awo00/smb2`. Connection lifecycle: create client → authenticate (domain optional) → connect tree (share) → execute → disconnect/logoff/close. Entire operation wrapped in timeout race.
93
+
94
+ All paths use forward slashes (`/`) — SMB library handles Windows conversion internally.
95
+
96
+ Credentials must be injected into the URL using placeholders:
97
+
98
+ ```
99
+ smb://<<systemId_username>>:<<systemId_password>>@fileserver.example.com/ShareName
100
+ ```
101
+
102
+ For private systems via Secure Gateway, the host is `tunnelId.tunnel` (e.g., `my_tunnel.tunnel`).
103
+
104
+ ## Return Value
105
+
106
+ Single operation → result directly. Multiple operations → array of results.
107
+
108
+ ## Common Patterns
109
+
110
+ ### Download and process
111
+
112
+ ```json
113
+ { "operation": "get", "path": "incoming/<<(sourceData) => sourceData.currentItem.filename>>" }
114
+ ```
115
+
116
+ ### Upload results
117
+
118
+ ```json
119
+ {
120
+ "operation": "put",
121
+ "path": "output/results.json",
122
+ "content": "<<(sourceData) => JSON.stringify(sourceData.processData.data)>>"
123
+ }
124
+ ```
125
+
126
+ ### List then loop
127
+
128
+ Step 1 lists files, step 2 uses data selector to loop over them:
129
+
130
+ ```javascript
131
+ // Step 1: list files
132
+ // body: {"operation": "list", "path": "incoming"}
133
+
134
+ // Step 2 dataSelector: (sourceData) => sourceData.listFiles.data.filter(f => f.type === "file")
135
+ // body: {"operation": "get", "path": "<<(sourceData) => sourceData.currentItem.path>>"}
136
+ ```
@@ -0,0 +1,279 @@
1
+ # Integrating superglue into Your Codebase
2
+
3
+ After building and saving tools with the CLI, integrate them into your application.
4
+
5
+ ## Quick Reference: REST API (curl)
6
+
7
+ For any language, use the REST API directly:
8
+
9
+ ```bash
10
+ # Set your API key and endpoint
11
+ export SUPERGLUE_API_KEY="your-api-key"
12
+ export SUPERGLUE_API_ENDPOINT="https://api.superglue.cloud"
13
+
14
+ # Run a tool (sync - waits for completion)
15
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id/run" \
16
+ -H "Authorization: Bearer $SUPERGLUE_API_KEY" \
17
+ -H "Content-Type: application/json" \
18
+ -d '{"inputs": {"userId": "123"}}'
19
+
20
+ # Run a tool (async - returns immediately)
21
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id/run" \
22
+ -H "Authorization: Bearer $SUPERGLUE_API_KEY" \
23
+ -H "Content-Type: application/json" \
24
+ -d '{"inputs": {"userId": "123"}, "options": {"async": true}}'
25
+
26
+ # Get run status/result
27
+ curl "$SUPERGLUE_API_ENDPOINT/v1/runs/{runId}" \
28
+ -H "Authorization: Bearer $SUPERGLUE_API_KEY"
29
+
30
+ # List tools
31
+ curl "$SUPERGLUE_API_ENDPOINT/v1/tools?page=1&limit=50" \
32
+ -H "Authorization: Bearer $SUPERGLUE_API_KEY"
33
+
34
+ # Get tool details
35
+ curl "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id" \
36
+ -H "Authorization: Bearer $SUPERGLUE_API_KEY"
37
+
38
+ # Cancel a running tool
39
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/runs/{runId}/cancel" \
40
+ -H "Authorization: Bearer $SUPERGLUE_API_KEY"
41
+ ```
42
+
43
+ ### Response Format
44
+
45
+ **Sync execution (200 OK):**
46
+
47
+ ```json
48
+ {
49
+ "runId": "7f3e9c1a-2b4d-4e8f-9a3b-1c5d7e9f2a4b",
50
+ "toolId": "my-tool-id",
51
+ "status": "success",
52
+ "data": { "result": "..." },
53
+ "stepResults": [{ "stepId": "step-1", "success": true, "data": {} }],
54
+ "metadata": {
55
+ "startedAt": "2024-01-15T10:00:00Z",
56
+ "completedAt": "2024-01-15T10:00:05Z",
57
+ "durationMs": 5234
58
+ }
59
+ }
60
+ ```
61
+
62
+ **Async execution (202 Accepted):**
63
+
64
+ ```json
65
+ {
66
+ "runId": "7f3e9c1a-2b4d-4e8f-9a3b-1c5d7e9f2a4b",
67
+ "toolId": "my-tool-id",
68
+ "status": "running"
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## SDK Installation
75
+
76
+ ```bash
77
+ # TypeScript/JavaScript
78
+ npm install @superglue/client
79
+
80
+ # Python
81
+ pip install superglue-client
82
+ ```
83
+
84
+ Both SDKs are auto-generated from the OpenAPI spec (JS via orval, Python via openapi-python-client).
85
+
86
+ ## TypeScript/JavaScript Usage
87
+
88
+ The JS SDK exports: `configure`, `listTools`, `getTool`, `runTool`, `getRun`, `cancelRun`, `triggerWebhook`, `listRuns`.
89
+
90
+ ```typescript
91
+ import { configure, runTool, getRun, listTools, getTool } from "@superglue/client";
92
+
93
+ configure({
94
+ apiKey: process.env.SUPERGLUE_API_KEY!,
95
+ baseUrl: "https://api.superglue.cloud/v1",
96
+ });
97
+ ```
98
+
99
+ ### Run a Tool (Sync)
100
+
101
+ ```typescript
102
+ const response = await runTool("my-tool-id", {
103
+ inputs: { userId: "123", date: "2024-01-15" },
104
+ });
105
+
106
+ if (response.status === 200) {
107
+ const run = response.data;
108
+ console.log(run.status); // "success" | "failed"
109
+ console.log(run.data); // tool output
110
+ } else {
111
+ console.error("Error:", response.data);
112
+ }
113
+ ```
114
+
115
+ The response is `{ data: Run, status: number, headers: Headers }`. The `Run` object has: `runId`, `toolId`, `status`, `data`, `error`, `stepResults`, `metadata`.
116
+
117
+ ### Async Execution (Fire-and-Forget)
118
+
119
+ ```typescript
120
+ const response = await runTool("my-tool-id", {
121
+ inputs: { userId: "123" },
122
+ options: { async: true },
123
+ });
124
+
125
+ if (response.status === 202) {
126
+ const run = response.data;
127
+ console.log(`Run started: ${run.runId}`);
128
+
129
+ let latest = run;
130
+ while (latest.status === "running") {
131
+ await new Promise((r) => setTimeout(r, 1000));
132
+ const poll = await getRun(latest.runId);
133
+ latest = poll.data;
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### List and Discover Tools
139
+
140
+ ```typescript
141
+ const { data: tools } = await listTools({ page: 1, limit: 50 });
142
+ console.log(tools.data);
143
+
144
+ const { data: tool } = await getTool("my-tool-id");
145
+ console.log(tool.inputSchema);
146
+ ```
147
+
148
+ ## Python Usage
149
+
150
+ The Python SDK uses `AuthenticatedClient` with httpx. Functions are in `superglue_client.api.tools` and `superglue_client.api.runs`.
151
+
152
+ ```python
153
+ import os
154
+ from superglue_client import AuthenticatedClient
155
+ from superglue_client.api.tools import run_tool, list_tools, get_tool
156
+ from superglue_client.api.runs import get_run
157
+ from superglue_client.models.run_request import RunRequest
158
+ from superglue_client.models.run_request_inputs import RunRequestInputs
159
+
160
+ client = AuthenticatedClient(
161
+ base_url="https://api.superglue.cloud/v1",
162
+ token=os.environ["SUPERGLUE_API_KEY"],
163
+ )
164
+ ```
165
+
166
+ ### Run a Tool (Sync)
167
+
168
+ ```python
169
+ with client as c:
170
+ result = run_tool.sync(
171
+ "my-tool-id",
172
+ client=c,
173
+ body=RunRequest(
174
+ inputs=RunRequestInputs.from_dict({"userId": "123"}),
175
+ ),
176
+ )
177
+
178
+ if hasattr(result, "status") and result.status == "success":
179
+ print(result.data)
180
+ elif hasattr(result, "error"):
181
+ print(result.error)
182
+ ```
183
+
184
+ `sync()` returns `Run | Error | None`. Use `sync_detailed()` for the full `Response` wrapper with status code and headers.
185
+
186
+ ### Async Python
187
+
188
+ ```python
189
+ async with client as c:
190
+ result = await run_tool.asyncio(
191
+ "my-tool-id",
192
+ client=c,
193
+ body=RunRequest(
194
+ inputs=RunRequestInputs.from_dict({"userId": "123"}),
195
+ ),
196
+ )
197
+ ```
198
+
199
+ ## Webhook Triggers
200
+
201
+ Trigger tools via HTTP POST without SDK. Webhooks execute asynchronously.
202
+
203
+ ```bash
204
+ curl -X POST "https://api.superglue.cloud/v1/hooks/{toolId}?token={apiKey}" \
205
+ -H "Content-Type: application/json" \
206
+ -d '{"userId": "123"}'
207
+ ```
208
+
209
+ ### Webhook URL Pattern
210
+
211
+ ```
212
+ POST {endpoint}/v1/hooks/{toolId}?token={apiKey}
213
+ ```
214
+
215
+ - `endpoint` — Your superglue API endpoint
216
+ - `toolId` — The saved tool ID (from `sg tool save`)
217
+ - `apiKey` — Your superglue API key (passed as `token` query param)
218
+
219
+ ### Webhook Response
220
+
221
+ Webhooks return `202 Accepted` immediately:
222
+
223
+ ```json
224
+ {
225
+ "runId": "uuid-of-execution",
226
+ "status": "accepted",
227
+ "toolId": "my-tool-id"
228
+ }
229
+ ```
230
+
231
+ To get the result, poll the run status:
232
+
233
+ ```bash
234
+ curl "https://api.superglue.cloud/v1/runs/{runId}" \
235
+ -H "Authorization: Bearer {apiKey}"
236
+ ```
237
+
238
+ ## Error Handling
239
+
240
+ ### Retry Pattern (TypeScript)
241
+
242
+ ```typescript
243
+ async function runToolWithRetry(toolId: string, inputs: Record<string, any>, maxRetries = 3) {
244
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
245
+ const response = await runTool(toolId, { inputs });
246
+
247
+ if (response.status === 200 && response.data.status === "success") {
248
+ return response.data;
249
+ }
250
+
251
+ if (response.data.error?.includes("validation")) {
252
+ throw new Error(response.data.error);
253
+ }
254
+
255
+ if (attempt < maxRetries) {
256
+ await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
257
+ }
258
+ }
259
+ throw new Error(`Tool ${toolId} failed after ${maxRetries} attempts`);
260
+ }
261
+ ```
262
+
263
+ ## Environment Configuration
264
+
265
+ ```bash
266
+ # Required
267
+ SUPERGLUE_API_KEY=your-api-key
268
+
269
+ # Optional (defaults to hosted)
270
+ SUPERGLUE_API_ENDPOINT=https://api.superglue.cloud
271
+ ```
272
+
273
+ ## Best Practices
274
+
275
+ 1. **Store API keys in secrets** — Never commit to version control
276
+ 2. **Validate payloads client-side** — Check against `inputSchema` before calling
277
+ 3. **Handle errors gracefully** — Tools can fail due to external API issues
278
+ 4. **Monitor run history** — Use `sg run list` or the web UI to track executions
279
+ 5. **Use async mode for long-running tools** — Avoid timeouts on complex workflows
@@ -0,0 +1,190 @@
1
+ # Redis Steps
2
+
3
+ ## Step Configuration
4
+
5
+ ```typescript
6
+ {
7
+ type: "request",
8
+ systemId: "my_redis",
9
+ url: "redis://<<my_redis_username>>:<<my_redis_password>>@<<my_redis_host>>:<<my_redis_port>>/<<my_redis_database>>",
10
+ method: "POST", // ignored for Redis but required by schema
11
+ body: '{"command": "GET", "args": ["<<keyName>>"]}'
12
+ }
13
+ ```
14
+
15
+ Configure the connection URL depending on which credentials are saved in the stored Redis system - reference them accordingly.
16
+
17
+ ## URL Format
18
+
19
+ ```
20
+ redis://user:password@host:port/database
21
+ rediss://user:password@host:port/database (TLS)
22
+ ```
23
+
24
+ - `redis://` for standard connections
25
+ - `rediss://` for TLS-encrypted connections (e.g. Redis Cloud, AWS ElastiCache with encryption)
26
+ - `/database` is the database number (0-15), defaults to 0 if omitted
27
+
28
+ All credential variables are resolved before connecting. Trailing slashes are cleaned.
29
+
30
+ ## Body Format
31
+
32
+ JSON string with `command` (required) and optional `args` array. Body can be a single command object or an array of commands (pipelined in a single round-trip):
33
+
34
+ ```json
35
+ { "command": "GET", "args": ["mykey"] }
36
+ ```
37
+
38
+ Multiple commands:
39
+
40
+ ```json
41
+ [
42
+ { "command": "GET", "args": ["user:1:name"] },
43
+ { "command": "HGETALL", "args": ["user:1"] }
44
+ ]
45
+ ```
46
+
47
+ ### Common Commands
48
+
49
+ **String operations:**
50
+
51
+ ```json
52
+ { "command": "GET", "args": ["user:123:name"] }
53
+ { "command": "SET", "args": ["user:123:name", "Alice"] }
54
+ { "command": "MGET", "args": ["key1", "key2", "key3"] }
55
+ { "command": "INCR", "args": ["counter"] }
56
+ ```
57
+
58
+ **Hash operations:**
59
+
60
+ ```json
61
+ { "command": "HGETALL", "args": ["user:123"] }
62
+ { "command": "HGET", "args": ["user:123", "email"] }
63
+ { "command": "HSET", "args": ["user:123", "email", "alice@example.com"] }
64
+ { "command": "HMGET", "args": ["user:123", "name", "email"] }
65
+ ```
66
+
67
+ **List operations:**
68
+
69
+ ```json
70
+ { "command": "LRANGE", "args": ["queue:tasks", "0", "-1"] }
71
+ { "command": "LPUSH", "args": ["queue:tasks", "task1"] }
72
+ { "command": "RPOP", "args": ["queue:tasks"] }
73
+ { "command": "LLEN", "args": ["queue:tasks"] }
74
+ ```
75
+
76
+ **Set operations:**
77
+
78
+ ```json
79
+ { "command": "SMEMBERS", "args": ["tags:post:1"] }
80
+ { "command": "SADD", "args": ["tags:post:1", "redis", "database"] }
81
+ { "command": "SISMEMBER", "args": ["tags:post:1", "redis"] }
82
+ ```
83
+
84
+ **Sorted set operations:**
85
+
86
+ ```json
87
+ { "command": "ZRANGE", "args": ["leaderboard", "0", "-1", "WITHSCORES"] }
88
+ { "command": "ZADD", "args": ["leaderboard", "100", "player1"] }
89
+ { "command": "ZRANK", "args": ["leaderboard", "player1"] }
90
+ ```
91
+
92
+ **Key operations:**
93
+
94
+ ```json
95
+ { "command": "KEYS", "args": ["user:*"] }
96
+ { "command": "EXISTS", "args": ["mykey"] }
97
+ { "command": "TTL", "args": ["session:abc"] }
98
+ { "command": "EXPIRE", "args": ["session:abc", "3600"] }
99
+ { "command": "DEL", "args": ["mykey"] }
100
+ { "command": "TYPE", "args": ["mykey"] }
101
+ ```
102
+
103
+ **Scan (safe iteration):**
104
+
105
+ ```json
106
+ { "command": "SCAN", "args": ["0", "MATCH", "user:*", "COUNT", "100"] }
107
+ { "command": "HSCAN", "args": ["myhash", "0", "MATCH", "field*"] }
108
+ ```
109
+
110
+ ### Dynamic Arguments with Variables
111
+
112
+ ```json
113
+ { "command": "GET", "args": ["<<(sourceData) => `user:${sourceData.userId}:profile`>>"] }
114
+ ```
115
+
116
+ ```json
117
+ { "command": "HSET", "args": ["<<userKey>>", "<<fieldName>>", "<<fieldValue>>"] }
118
+ ```
119
+
120
+ ## Connection Management
121
+
122
+ - A fresh connection is created per execution and closed after completion
123
+ - TLS auto-configured for `rediss://` URLs (with `rejectUnauthorized: false`)
124
+ - 5-second connection timeout, 30-second command timeout
125
+ - Array commands (pipeline) run on a single connection for efficiency
126
+
127
+ ## Retry Logic
128
+
129
+ Default retries from server config. On final failure, error includes command and args.
130
+
131
+ ## Return Value
132
+
133
+ Returns the raw Redis response:
134
+
135
+ - `GET` / `HGET`: string or null
136
+ - `HGETALL`: object with field-value pairs (flattened array from Redis, parsed by ioredis)
137
+ - `LRANGE` / `SMEMBERS` / `KEYS`: array of strings
138
+ - `SET` / `DEL` / `EXPIRE`: "OK" or integer
139
+ - `INCR` / `LLEN` / `SADD`: integer
140
+ - `MGET` / `HMGET`: array (nulls for missing keys)
141
+ - `SCAN` / `HSCAN`: array of [cursor, results]
142
+
143
+ ## Multiple Commands (Pipeline)
144
+
145
+ Pass an array of commands to execute them in a single round-trip (like FTP/SFTP batch operations):
146
+
147
+ ```json
148
+ [
149
+ { "command": "GET", "args": ["user:1:name"] },
150
+ { "command": "HGETALL", "args": ["user:1"] },
151
+ { "command": "LRANGE", "args": ["tasks", "0", "-1"] }
152
+ ]
153
+ ```
154
+
155
+ Returns an array of results, one per command:
156
+
157
+ ```json
158
+ [
159
+ { "command": "GET", "result": "Alice" },
160
+ { "command": "HGETALL", "result": ["name", "Alice", "email", "alice@example.com"] },
161
+ { "command": "LRANGE", "result": ["task3", "task2", "task1"] }
162
+ ]
163
+ ```
164
+
165
+ If an individual command fails, that entry has `error` instead of `result`:
166
+
167
+ ```json
168
+ [
169
+ { "command": "GET", "result": "Alice" },
170
+ { "command": "WRONGCMD", "error": "ERR unknown command 'WRONGCMD'" }
171
+ ]
172
+ ```
173
+
174
+ A single command returns a single result. Multiple commands return an array — same pattern as FTP/SFTP operations.
175
+
176
+ ## Batch Operations via Loop
177
+
178
+ Use a data selector returning an array to execute a command per item:
179
+
180
+ ```javascript
181
+ // dataSelector: (sourceData) => sourceData.userIds
182
+ // body: {"command": "HGETALL", "args": ["<<(sourceData) => `user:${sourceData.currentItem}`>>"]}
183
+ ```
184
+
185
+ ## Important Notes
186
+
187
+ - All args must be strings (Redis protocol is text-based) - numbers are auto-coerced
188
+ - Use `SCAN` instead of `KEYS` in production for large keyspaces
189
+ - `HGETALL` returns an empty object `{}` for non-existent keys
190
+ - Use array body for multi-command pipelines instead of separate steps