flowscale 1.0.13 → 1.0.15
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/README.md +85 -8
- package/dist/index.d.ts +13 -1
- package/dist/index.js +73 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# Flowscale Node.js SDK
|
|
2
2
|
|
|
3
|
-
A comprehensive
|
|
3
|
+
A comprehensive SDK designed to simplify interaction with the FlowScale ComfyUI API. This library works in both Node.js and browser environments.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⚠️ Important Security Notice for Browser Usage
|
|
8
|
+
|
|
9
|
+
When using this SDK in a browser environment, your API key will be exposed to end users. This is a significant security risk. We strongly recommend:
|
|
10
|
+
|
|
11
|
+
1. Using this SDK in a Node.js backend environment
|
|
12
|
+
2. Creating a proxy API that securely handles the API key server-side
|
|
13
|
+
|
|
14
|
+
If you understand the risks and still need to use the SDK directly in the browser, you must explicitly acknowledge this by setting the `allowDangerouslyExposeApiKey` parameter:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
const flowscale = new FlowscaleAPI(
|
|
18
|
+
apiKey,
|
|
19
|
+
apiUrl,
|
|
20
|
+
true // allowDangerouslyExposeApiKey - Only set to true if you understand the security risks
|
|
21
|
+
);
|
|
22
|
+
```
|
|
4
23
|
|
|
5
24
|
---
|
|
6
25
|
|
|
@@ -29,18 +48,31 @@ To get started, import the Flowscale SDK into your project:
|
|
|
29
48
|
```javascript
|
|
30
49
|
import { FlowscaleAPI } from 'flowscale';
|
|
31
50
|
|
|
32
|
-
//
|
|
51
|
+
// Node.js Environment (Recommended)
|
|
33
52
|
const apiKey = process.env.FLOWSCALE_API_KEY;
|
|
34
53
|
const apiUrl = process.env.FLOWSCALE_API_URL;
|
|
35
|
-
|
|
36
|
-
if (!apiKey || !apiUrl) {
|
|
37
|
-
console.error('FLOWSCALE_API_KEY or FLOWSCALE_API_URL not set in .env');
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
54
|
const flowscale = new FlowscaleAPI(apiKey, apiUrl);
|
|
55
|
+
|
|
56
|
+
// Browser Environment (Not Recommended)
|
|
57
|
+
const flowscale = new FlowscaleAPI(
|
|
58
|
+
'your-api-key', // ⚠️ WARNING: This will be exposed to users
|
|
59
|
+
'https://your-api-url.pod.flowscale.ai',
|
|
60
|
+
true // Explicitly acknowledge the security risk
|
|
61
|
+
);
|
|
42
62
|
```
|
|
43
63
|
|
|
64
|
+
### Environment-Specific Considerations
|
|
65
|
+
|
|
66
|
+
#### Node.js (Recommended)
|
|
67
|
+
- Store API keys in environment variables
|
|
68
|
+
- Use `.env` files for configuration
|
|
69
|
+
- Full access to all SDK features
|
|
70
|
+
|
|
71
|
+
#### Browser
|
|
72
|
+
- API key will be visible in network requests
|
|
73
|
+
- Supports File/Blob uploads directly from browser
|
|
74
|
+
- Consider implementing a backend proxy instead
|
|
75
|
+
|
|
44
76
|
**Environment Variables:** Add the following to your `.env` file:
|
|
45
77
|
|
|
46
78
|
```plaintext
|
|
@@ -165,6 +197,51 @@ console.log('Workflow Result:', result);
|
|
|
165
197
|
|
|
166
198
|
---
|
|
167
199
|
|
|
200
|
+
### 3.1 `executeWorkflowAsync(workflowId, data, groupId?, pollIntervalMs?, timeoutMs?)`
|
|
201
|
+
|
|
202
|
+
**Description:**
|
|
203
|
+
Execute a workflow and automatically wait for the result by polling the output. This is a convenience method that combines `executeWorkflow` and `getOutput` with automatic polling.
|
|
204
|
+
|
|
205
|
+
**Parameters:**
|
|
206
|
+
- `workflowId` *(string)*: The unique ID of the workflow.
|
|
207
|
+
- `data` *(object)*: Input parameters for the workflow.
|
|
208
|
+
- `groupId` *(string, optional)*: A custom identifier for grouping runs.
|
|
209
|
+
- `pollIntervalMs` *(number, optional)*: Polling interval in milliseconds (default: 1000).
|
|
210
|
+
- `timeoutMs` *(number, optional)*: Maximum time to wait for results in milliseconds (default: 300000 - 5 minutes).
|
|
211
|
+
|
|
212
|
+
**Usage:**
|
|
213
|
+
```javascript
|
|
214
|
+
const workflowId = "bncu0a1kipv";
|
|
215
|
+
const inputs = {
|
|
216
|
+
"text_51536": "Prompt test",
|
|
217
|
+
"image_1234": { /* File or Blob of image */ }
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const result = await flowscale.executeWorkflowAsync(workflowId, inputs);
|
|
222
|
+
console.log('Workflow Result:', result);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (error.message.includes('timed out')) {
|
|
225
|
+
console.error('Workflow took too long to complete');
|
|
226
|
+
} else {
|
|
227
|
+
console.error('Workflow error:', error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Response Example:**
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"status": "success",
|
|
236
|
+
"data": {
|
|
237
|
+
"download_url": "https://runs.s3.amazonaws.com/generations/...",
|
|
238
|
+
"generation_status": "success"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
168
245
|
### 4. `getOutput(filename)`
|
|
169
246
|
|
|
170
247
|
**Description:**
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ export declare class FlowscaleAPI {
|
|
|
3
3
|
private apiKey;
|
|
4
4
|
private baseUrl;
|
|
5
5
|
private client;
|
|
6
|
-
|
|
6
|
+
private isNode;
|
|
7
|
+
constructor(apiKey: string, baseUrl: string, allowDangerouslyExposeApiKey?: boolean);
|
|
7
8
|
/**
|
|
8
9
|
* Checks the health status of all ComfyUI instances within the specified cluster.
|
|
9
10
|
*/
|
|
@@ -21,6 +22,17 @@ export declare class FlowscaleAPI {
|
|
|
21
22
|
executeWorkflow(workflowId: string, data: {
|
|
22
23
|
[key: string]: any;
|
|
23
24
|
}, groupId?: string): Promise<ExecuteWorkflowResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Executes a workflow and waits for the output by polling.
|
|
27
|
+
* @param workflowId - The ID of the workflow to execute.
|
|
28
|
+
* @param data - Form data including text fields and file uploads.
|
|
29
|
+
* @param groupId - Optional group ID.
|
|
30
|
+
* @param pollIntervalMs - Optional polling interval in milliseconds (default: 1000)
|
|
31
|
+
* @param timeoutMs - Optional timeout in milliseconds (default: 300000 - 5 minutes)
|
|
32
|
+
*/
|
|
33
|
+
executeWorkflowAsync(workflowId: string, data: {
|
|
34
|
+
[key: string]: any;
|
|
35
|
+
}, groupId?: string, pollIntervalMs?: number, timeoutMs?: number): Promise<GetOutputResponse>;
|
|
24
36
|
/**
|
|
25
37
|
* Retrieves the output of a specific run by providing the filename.
|
|
26
38
|
* @param filename - The filename of the output to retrieve.
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __assign = (this && this.__assign) || function () {
|
|
3
|
-
__assign = Object.assign || function(t) {
|
|
4
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
-
s = arguments[i];
|
|
6
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
-
t[p] = s[p];
|
|
8
|
-
}
|
|
9
|
-
return t;
|
|
10
|
-
};
|
|
11
|
-
return __assign.apply(this, arguments);
|
|
12
|
-
};
|
|
13
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -52,12 +41,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
52
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
42
|
exports.FlowscaleAPI = void 0;
|
|
54
43
|
var axios_1 = __importDefault(require("axios"));
|
|
55
|
-
var form_data_1 = __importDefault(require("form-data"));
|
|
56
|
-
var fs_1 = __importDefault(require("fs"));
|
|
57
|
-
var path_1 = __importDefault(require("path"));
|
|
58
44
|
var FlowscaleAPI = /** @class */ (function () {
|
|
59
|
-
function FlowscaleAPI(apiKey, baseUrl) {
|
|
45
|
+
function FlowscaleAPI(apiKey, baseUrl, allowDangerouslyExposeApiKey) {
|
|
46
|
+
if (allowDangerouslyExposeApiKey === void 0) { allowDangerouslyExposeApiKey = false; }
|
|
60
47
|
var _this = this;
|
|
48
|
+
// Check if we're in a Node.js environment
|
|
49
|
+
this.isNode = typeof process !== 'undefined' &&
|
|
50
|
+
process.versions != null &&
|
|
51
|
+
process.versions.node != null;
|
|
52
|
+
// Warn about API key exposure in browser environment
|
|
53
|
+
if (!this.isNode && !allowDangerouslyExposeApiKey) {
|
|
54
|
+
throw new Error('WARNING: Using FlowscaleAPI in a browser environment will expose your API key to end users. ' +
|
|
55
|
+
'This is a security risk. If you understand the risks and still want to proceed, ' +
|
|
56
|
+
'set allowDangerouslyExposeApiKey to true. ' +
|
|
57
|
+
'We strongly recommend using a backend proxy instead.');
|
|
58
|
+
}
|
|
61
59
|
this.apiKey = apiKey;
|
|
62
60
|
this.baseUrl = baseUrl;
|
|
63
61
|
this.client = axios_1.default.create({
|
|
@@ -132,7 +130,7 @@ var FlowscaleAPI = /** @class */ (function () {
|
|
|
132
130
|
switch (_a.label) {
|
|
133
131
|
case 0:
|
|
134
132
|
_a.trys.push([0, 2, , 3]);
|
|
135
|
-
formData_1 = new
|
|
133
|
+
formData_1 = new FormData();
|
|
136
134
|
_loop_1 = function (key) {
|
|
137
135
|
if (data.hasOwnProperty(key)) {
|
|
138
136
|
var value = data[key];
|
|
@@ -152,7 +150,10 @@ var FlowscaleAPI = /** @class */ (function () {
|
|
|
152
150
|
for (key in data) {
|
|
153
151
|
_loop_1(key);
|
|
154
152
|
}
|
|
155
|
-
headers =
|
|
153
|
+
headers = {
|
|
154
|
+
'Content-Type': 'multipart/form-data',
|
|
155
|
+
'X-API-KEY': this.apiKey,
|
|
156
|
+
};
|
|
156
157
|
url = "/api/v1/runs?workflow_id=".concat(encodeURIComponent(workflowId)).concat(groupId ? "&group_id=".concat(encodeURIComponent(groupId)) : '');
|
|
157
158
|
return [4 /*yield*/, this.client.post(url, formData_1, { headers: headers })];
|
|
158
159
|
case 1:
|
|
@@ -167,6 +168,55 @@ var FlowscaleAPI = /** @class */ (function () {
|
|
|
167
168
|
});
|
|
168
169
|
});
|
|
169
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* Executes a workflow and waits for the output by polling.
|
|
173
|
+
* @param workflowId - The ID of the workflow to execute.
|
|
174
|
+
* @param data - Form data including text fields and file uploads.
|
|
175
|
+
* @param groupId - Optional group ID.
|
|
176
|
+
* @param pollIntervalMs - Optional polling interval in milliseconds (default: 1000)
|
|
177
|
+
* @param timeoutMs - Optional timeout in milliseconds (default: 300000 - 5 minutes)
|
|
178
|
+
*/
|
|
179
|
+
FlowscaleAPI.prototype.executeWorkflowAsync = function (workflowId_1, data_1, groupId_1) {
|
|
180
|
+
return __awaiter(this, arguments, void 0, function (workflowId, data, groupId, pollIntervalMs, timeoutMs) {
|
|
181
|
+
var startTime, executeResponse, outputName, output;
|
|
182
|
+
var _a, _b;
|
|
183
|
+
if (pollIntervalMs === void 0) { pollIntervalMs = 1000; }
|
|
184
|
+
if (timeoutMs === void 0) { timeoutMs = 300000; }
|
|
185
|
+
return __generator(this, function (_c) {
|
|
186
|
+
switch (_c.label) {
|
|
187
|
+
case 0:
|
|
188
|
+
startTime = Date.now();
|
|
189
|
+
return [4 /*yield*/, this.executeWorkflow(workflowId, data, groupId)];
|
|
190
|
+
case 1:
|
|
191
|
+
executeResponse = _c.sent();
|
|
192
|
+
// If there are no output names, throw an error
|
|
193
|
+
if (!((_b = (_a = executeResponse === null || executeResponse === void 0 ? void 0 : executeResponse.data) === null || _a === void 0 ? void 0 : _a.output_names) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
194
|
+
throw new Error('No output names returned from workflow execution');
|
|
195
|
+
}
|
|
196
|
+
outputName = executeResponse.data.output_names[0];
|
|
197
|
+
_c.label = 2;
|
|
198
|
+
case 2:
|
|
199
|
+
if (!true) return [3 /*break*/, 5];
|
|
200
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
201
|
+
throw new Error("Workflow execution timed out after ".concat(timeoutMs, "ms"));
|
|
202
|
+
}
|
|
203
|
+
return [4 /*yield*/, this.getOutput(outputName)];
|
|
204
|
+
case 3:
|
|
205
|
+
output = _c.sent();
|
|
206
|
+
if (output !== null) {
|
|
207
|
+
return [2 /*return*/, output];
|
|
208
|
+
}
|
|
209
|
+
// Wait for the polling interval
|
|
210
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, pollIntervalMs); })];
|
|
211
|
+
case 4:
|
|
212
|
+
// Wait for the polling interval
|
|
213
|
+
_c.sent();
|
|
214
|
+
return [3 /*break*/, 2];
|
|
215
|
+
case 5: return [2 /*return*/];
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
};
|
|
170
220
|
/**
|
|
171
221
|
* Retrieves the output of a specific run by providing the filename.
|
|
172
222
|
* @param filename - The filename of the output to retrieve.
|
|
@@ -284,21 +334,16 @@ var FlowscaleAPI = /** @class */ (function () {
|
|
|
284
334
|
* Helper method to append data to FormData.
|
|
285
335
|
*/
|
|
286
336
|
FlowscaleAPI.prototype.appendFormData = function (formData, key, value) {
|
|
287
|
-
if (value instanceof
|
|
288
|
-
//
|
|
337
|
+
if (value instanceof Blob || value instanceof File) {
|
|
338
|
+
// Handle File and Blob objects directly
|
|
289
339
|
formData.append(key, value);
|
|
290
340
|
}
|
|
291
|
-
else if (
|
|
292
|
-
//
|
|
293
|
-
formData.append(key,
|
|
294
|
-
}
|
|
295
|
-
else if (typeof value === 'string' && fs_1.default.existsSync(value)) {
|
|
296
|
-
// If the value is a file path
|
|
297
|
-
var fileName = path_1.default.basename(value);
|
|
298
|
-
formData.append(key, fs_1.default.createReadStream(value), fileName);
|
|
341
|
+
else if (typeof value === 'object' && value !== null) {
|
|
342
|
+
// Handle plain objects by stringifying
|
|
343
|
+
formData.append(key, JSON.stringify(value));
|
|
299
344
|
}
|
|
300
345
|
else {
|
|
301
|
-
//
|
|
346
|
+
// Handle primitive values
|
|
302
347
|
formData.append(key, value);
|
|
303
348
|
}
|
|
304
349
|
};
|