flowscale 1.0.3

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,52 @@
1
+ import { HealthCheckResponse, QueueResponse, ExecuteWorkflowResponse, GetOutputResponse, RunDetailResponse, RunListResponse, CancelRunResponse } from './types';
2
+ export declare class FlowscaleAPI {
3
+ private apiKey;
4
+ private baseUrl;
5
+ private client;
6
+ constructor(apiKey: string, baseUrl: string);
7
+ /**
8
+ * Checks the health status of all ComfyUI instances within the specified cluster.
9
+ */
10
+ checkHealth(): Promise<HealthCheckResponse>;
11
+ /**
12
+ * Retrieves the queue data for all ComfyUI instances in the cluster.
13
+ */
14
+ getQueue(): Promise<QueueResponse>;
15
+ /**
16
+ * Executes a specified workflow by processing dynamic form data.
17
+ * @param workflowId - The ID of the workflow to execute.
18
+ * @param data - Form data including text fields and file uploads.
19
+ * @param groupId - Optional group ID.
20
+ */
21
+ executeWorkflow(workflowId: string, data: {
22
+ [key: string]: any;
23
+ }, groupId?: string): Promise<ExecuteWorkflowResponse>;
24
+ /**
25
+ * Retrieves the output of a specific run by providing the filename.
26
+ * @param filename - The filename of the output to retrieve.
27
+ */
28
+ getOutput(filename: string): Promise<GetOutputResponse | null>;
29
+ /**
30
+ * Cancels a specific run using its unique run ID.
31
+ * @param runId - The ID of the run to cancel.
32
+ */
33
+ cancelRun(runId: string): Promise<CancelRunResponse>;
34
+ /**
35
+ * Retrieves detailed information about a specific run using its unique run ID.
36
+ * @param runId - The ID of the run to retrieve.
37
+ */
38
+ getRun(runId: string): Promise<RunDetailResponse>;
39
+ /**
40
+ * Retrieves a list of all runs associated with a specific group ID.
41
+ * @param groupId - The group ID to filter runs.
42
+ */
43
+ getRuns(groupId: string): Promise<RunListResponse>;
44
+ /**
45
+ * Helper method to append data to FormData.
46
+ */
47
+ private appendFormData;
48
+ /**
49
+ * Error handling helper.
50
+ */
51
+ private handleError;
52
+ }
package/dist/index.js ADDED
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FlowscaleAPI = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const form_data_1 = __importDefault(require("form-data"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ class FlowscaleAPI {
12
+ constructor(apiKey, baseUrl) {
13
+ this.apiKey = apiKey;
14
+ this.baseUrl = baseUrl;
15
+ this.client = axios_1.default.create({
16
+ baseURL: this.baseUrl,
17
+ });
18
+ // Add the API key to all requests
19
+ this.client.interceptors.request.use((config) => {
20
+ if (!config.headers) {
21
+ config.headers = {};
22
+ }
23
+ config.headers['X-API-KEY'] = this.apiKey;
24
+ return config;
25
+ });
26
+ }
27
+ /**
28
+ * Checks the health status of all ComfyUI instances within the specified cluster.
29
+ */
30
+ async checkHealth() {
31
+ try {
32
+ const response = await this.client.get('/api/v1/comfy/health');
33
+ return response.data;
34
+ }
35
+ catch (error) {
36
+ this.handleError(error);
37
+ }
38
+ }
39
+ /**
40
+ * Retrieves the queue data for all ComfyUI instances in the cluster.
41
+ */
42
+ async getQueue() {
43
+ try {
44
+ const response = await this.client.get('/api/v1/comfy/queue');
45
+ return response.data;
46
+ }
47
+ catch (error) {
48
+ this.handleError(error);
49
+ }
50
+ }
51
+ /**
52
+ * Executes a specified workflow by processing dynamic form data.
53
+ * @param workflowId - The ID of the workflow to execute.
54
+ * @param data - Form data including text fields and file uploads.
55
+ * @param groupId - Optional group ID.
56
+ */
57
+ async executeWorkflow(workflowId, data, groupId) {
58
+ try {
59
+ const formData = new form_data_1.default();
60
+ // Append data to formData
61
+ for (const key in data) {
62
+ if (data.hasOwnProperty(key)) {
63
+ const value = data[key];
64
+ if (Array.isArray(value)) {
65
+ // Handle arrays (for multiple files)
66
+ value.forEach((item) => {
67
+ this.appendFormData(formData, key, item);
68
+ });
69
+ }
70
+ else {
71
+ this.appendFormData(formData, key, value);
72
+ }
73
+ }
74
+ }
75
+ const headers = {
76
+ ...formData.getHeaders(),
77
+ 'X-API-KEY': this.apiKey,
78
+ };
79
+ const url = `/api/v1/runs?workflow_id=${encodeURIComponent(workflowId)}${groupId ? `&group_id=${encodeURIComponent(groupId)}` : ''}`;
80
+ const response = await this.client.post(url, formData, { headers });
81
+ return response.data;
82
+ }
83
+ catch (error) {
84
+ this.handleError(error);
85
+ }
86
+ }
87
+ /**
88
+ * Retrieves the output of a specific run by providing the filename.
89
+ * @param filename - The filename of the output to retrieve.
90
+ */
91
+ async getOutput(filename) {
92
+ try {
93
+ const response = await this.client.get('/api/v1/runs/output', {
94
+ params: { filename },
95
+ });
96
+ return response.data;
97
+ }
98
+ catch (error) {
99
+ const axiosError = error;
100
+ if (axiosError.response && axiosError.response.status === 204) {
101
+ return null; // No output found
102
+ }
103
+ else if (axiosError.response &&
104
+ axiosError.response.status === 408) {
105
+ throw new Error('Run Timeout');
106
+ }
107
+ else {
108
+ this.handleError(error);
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Cancels a specific run using its unique run ID.
114
+ * @param runId - The ID of the run to cancel.
115
+ */
116
+ async cancelRun(runId) {
117
+ try {
118
+ const response = await this.client.post(`/api/v1/runs/${encodeURIComponent(runId)}/cancel`);
119
+ return response.data;
120
+ }
121
+ catch (error) {
122
+ this.handleError(error);
123
+ }
124
+ }
125
+ /**
126
+ * Retrieves detailed information about a specific run using its unique run ID.
127
+ * @param runId - The ID of the run to retrieve.
128
+ */
129
+ async getRun(runId) {
130
+ try {
131
+ const response = await this.client.get(`/api/v1/runs/${encodeURIComponent(runId)}`);
132
+ return response.data;
133
+ }
134
+ catch (error) {
135
+ this.handleError(error);
136
+ }
137
+ }
138
+ /**
139
+ * Retrieves a list of all runs associated with a specific group ID.
140
+ * @param groupId - The group ID to filter runs.
141
+ */
142
+ async getRuns(groupId) {
143
+ try {
144
+ const response = await this.client.get('/api/v1/runs', {
145
+ params: { group_id: groupId },
146
+ });
147
+ return response.data;
148
+ }
149
+ catch (error) {
150
+ this.handleError(error);
151
+ }
152
+ }
153
+ /**
154
+ * Helper method to append data to FormData.
155
+ */
156
+ appendFormData(formData, key, value) {
157
+ if (value instanceof fs_1.default.ReadStream) {
158
+ // It's a file stream
159
+ formData.append(key, value);
160
+ }
161
+ else if (Buffer.isBuffer(value)) {
162
+ // It's a Buffer
163
+ formData.append(key, value, { filename: `${key}.dat` });
164
+ }
165
+ else if (typeof value === 'string' && fs_1.default.existsSync(value)) {
166
+ // If the value is a file path
167
+ const fileName = path_1.default.basename(value);
168
+ formData.append(key, fs_1.default.createReadStream(value), fileName);
169
+ }
170
+ else {
171
+ // Assume it's a string or other value
172
+ formData.append(key, value);
173
+ }
174
+ }
175
+ /**
176
+ * Error handling helper.
177
+ */
178
+ handleError(error) {
179
+ if (axios_1.default.isAxiosError(error)) {
180
+ if (error.response) {
181
+ // Server responded with a status code outside 2xx
182
+ throw new Error(`API Error: ${error.response.status} ${error.response.statusText} - ${JSON.stringify(error.response.data)}`);
183
+ }
184
+ else if (error.request) {
185
+ // Request was made but no response
186
+ throw new Error('No response received from API server');
187
+ }
188
+ }
189
+ // Something else happened
190
+ throw new Error(`Error: ${error.message}`);
191
+ }
192
+ }
193
+ exports.FlowscaleAPI = FlowscaleAPI;
@@ -0,0 +1,72 @@
1
+ export interface HealthCheckResponse {
2
+ status: string;
3
+ data: Array<{
4
+ container: string;
5
+ status: string;
6
+ }>;
7
+ }
8
+ export interface QueueResponse {
9
+ status: string;
10
+ data: Array<{
11
+ container: string;
12
+ queue: {
13
+ queue_running: any[];
14
+ queue_pending: any[];
15
+ };
16
+ }>;
17
+ }
18
+ export interface ExecuteWorkflowResponse {
19
+ status: string;
20
+ data: {
21
+ output_names: string[];
22
+ run_id: string;
23
+ };
24
+ }
25
+ export interface GetOutputResponse {
26
+ status: string;
27
+ data: {
28
+ download_url: string;
29
+ generation_status: string;
30
+ };
31
+ }
32
+ export interface RunDetail {
33
+ _id: string;
34
+ team_id: string;
35
+ workflow_id: string;
36
+ group_id?: string;
37
+ status: string;
38
+ inputs: Array<{
39
+ path: string;
40
+ value: string;
41
+ s3_key?: string;
42
+ url?: string;
43
+ }>;
44
+ outputs: Array<{
45
+ filename: string;
46
+ s3_key?: string;
47
+ url?: string;
48
+ }>;
49
+ created_at: string;
50
+ started_at?: string;
51
+ completed_at?: string;
52
+ }
53
+ export interface RunDetailResponse {
54
+ status: string;
55
+ data: RunDetail;
56
+ }
57
+ export interface RunListResponse {
58
+ status: string;
59
+ data: {
60
+ group_id: string;
61
+ count: number;
62
+ runs: RunDetail[];
63
+ };
64
+ }
65
+ export interface CancelRunResponse {
66
+ status: string;
67
+ data: string;
68
+ }
69
+ export interface ErrorResponse {
70
+ status: string;
71
+ errors: string;
72
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // src/types.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "flowscale",
3
+ "version": "1.0.3",
4
+ "description": "An NPM library for communicating with the Flowscale APIs",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "echo \"No tests yet\""
10
+ },
11
+ "author": "Your Name",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "axios": "^1.7.7",
15
+ "form-data": "^4.0.1",
16
+ "flowscale-test": "^1.0.1"
17
+ },
18
+ "devDependencies": {
19
+ "@types/form-data": "^2.5.2",
20
+ "@types/node": "^22.9.1",
21
+ "typescript": "^5.6.3"
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,230 @@
1
+ import axios, { AxiosInstance, AxiosError, AxiosRequestHeaders } from 'axios';
2
+ import FormData from 'form-data';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import {
6
+ HealthCheckResponse,
7
+ QueueResponse,
8
+ ExecuteWorkflowResponse,
9
+ GetOutputResponse,
10
+ RunDetailResponse,
11
+ RunListResponse,
12
+ CancelRunResponse,
13
+ ErrorResponse,
14
+ } from './types';
15
+
16
+ export class FlowscaleAPI {
17
+ private apiKey: string;
18
+ private baseUrl: string;
19
+ private client: AxiosInstance;
20
+
21
+ constructor(apiKey: string, baseUrl: string) {
22
+ this.apiKey = apiKey;
23
+ this.baseUrl = baseUrl;
24
+
25
+ this.client = axios.create({
26
+ baseURL: this.baseUrl,
27
+ });
28
+
29
+ // Add the API key to all requests
30
+ this.client.interceptors.request.use((config) => {
31
+ if (!config.headers) {
32
+ config.headers = {} as AxiosRequestHeaders
33
+ }
34
+ config.headers['X-API-KEY'] = this.apiKey;
35
+ return config;
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Checks the health status of all ComfyUI instances within the specified cluster.
41
+ */
42
+ public async checkHealth(): Promise<HealthCheckResponse> {
43
+ try {
44
+ const response = await this.client.get<HealthCheckResponse>(
45
+ '/api/v1/comfy/health'
46
+ );
47
+ return response.data;
48
+ } catch (error) {
49
+ this.handleError(error);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Retrieves the queue data for all ComfyUI instances in the cluster.
55
+ */
56
+ public async getQueue(): Promise<QueueResponse> {
57
+ try {
58
+ const response = await this.client.get<QueueResponse>(
59
+ '/api/v1/comfy/queue'
60
+ );
61
+ return response.data;
62
+ } catch (error) {
63
+ this.handleError(error);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Executes a specified workflow by processing dynamic form data.
69
+ * @param workflowId - The ID of the workflow to execute.
70
+ * @param data - Form data including text fields and file uploads.
71
+ * @param groupId - Optional group ID.
72
+ */
73
+ public async executeWorkflow(
74
+ workflowId: string,
75
+ data: { [key: string]: any },
76
+ groupId?: string
77
+ ): Promise<ExecuteWorkflowResponse> {
78
+ try {
79
+ const formData = new FormData();
80
+
81
+ // Append data to formData
82
+ for (const key in data) {
83
+ if (data.hasOwnProperty(key)) {
84
+ const value = data[key];
85
+ if (Array.isArray(value)) {
86
+ // Handle arrays (for multiple files)
87
+ value.forEach((item: any) => {
88
+ this.appendFormData(formData, key, item);
89
+ });
90
+ } else {
91
+ this.appendFormData(formData, key, value);
92
+ }
93
+ }
94
+ }
95
+
96
+ const headers = {
97
+ ...formData.getHeaders(),
98
+ 'X-API-KEY': this.apiKey,
99
+ };
100
+
101
+ const url = `/api/v1/runs?workflow_id=${encodeURIComponent(workflowId)}${
102
+ groupId ? `&group_id=${encodeURIComponent(groupId)}` : ''
103
+ }`;
104
+
105
+ const response = await this.client.post<ExecuteWorkflowResponse>(
106
+ url,
107
+ formData,
108
+ { headers }
109
+ );
110
+ return response.data;
111
+ } catch (error) {
112
+ this.handleError(error);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Retrieves the output of a specific run by providing the filename.
118
+ * @param filename - The filename of the output to retrieve.
119
+ */
120
+ public async getOutput(filename: string): Promise<GetOutputResponse | null> {
121
+ try {
122
+ const response = await this.client.get<GetOutputResponse>(
123
+ '/api/v1/runs/output',
124
+ {
125
+ params: { filename },
126
+ }
127
+ );
128
+ return response.data;
129
+ } catch (error) {
130
+ const axiosError = error as AxiosError;
131
+ if (axiosError.response && axiosError.response.status === 204) {
132
+ return null; // No output found
133
+ } else if (
134
+ axiosError.response &&
135
+ axiosError.response.status === 408
136
+ ) {
137
+ throw new Error('Run Timeout');
138
+ } else {
139
+ this.handleError(error);
140
+ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Cancels a specific run using its unique run ID.
146
+ * @param runId - The ID of the run to cancel.
147
+ */
148
+ public async cancelRun(runId: string): Promise<CancelRunResponse> {
149
+ try {
150
+ const response = await this.client.post<CancelRunResponse>(
151
+ `/api/v1/runs/${encodeURIComponent(runId)}/cancel`
152
+ );
153
+ return response.data;
154
+ } catch (error) {
155
+ this.handleError(error);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Retrieves detailed information about a specific run using its unique run ID.
161
+ * @param runId - The ID of the run to retrieve.
162
+ */
163
+ public async getRun(runId: string): Promise<RunDetailResponse> {
164
+ try {
165
+ const response = await this.client.get<RunDetailResponse>(
166
+ `/api/v1/runs/${encodeURIComponent(runId)}`
167
+ );
168
+ return response.data;
169
+ } catch (error) {
170
+ this.handleError(error);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Retrieves a list of all runs associated with a specific group ID.
176
+ * @param groupId - The group ID to filter runs.
177
+ */
178
+ public async getRuns(groupId: string): Promise<RunListResponse> {
179
+ try {
180
+ const response = await this.client.get<RunListResponse>('/api/v1/runs', {
181
+ params: { group_id: groupId },
182
+ });
183
+ return response.data;
184
+ } catch (error) {
185
+ this.handleError(error);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Helper method to append data to FormData.
191
+ */
192
+ private appendFormData(formData: FormData, key: string, value: any) {
193
+ if (value instanceof fs.ReadStream) {
194
+ // It's a file stream
195
+ formData.append(key, value);
196
+ } else if (Buffer.isBuffer(value)) {
197
+ // It's a Buffer
198
+ formData.append(key, value, { filename: `${key}.dat` });
199
+ } else if (typeof value === 'string' && fs.existsSync(value)) {
200
+ // If the value is a file path
201
+ const fileName = path.basename(value);
202
+ formData.append(key, fs.createReadStream(value), fileName);
203
+ } else {
204
+ // Assume it's a string or other value
205
+ formData.append(key, value);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Error handling helper.
211
+ */
212
+ private handleError(error: any): never {
213
+ if (axios.isAxiosError(error)) {
214
+ if (error.response) {
215
+ // Server responded with a status code outside 2xx
216
+ throw new Error(
217
+ `API Error: ${error.response.status} ${error.response.statusText} - ${JSON.stringify(
218
+ error.response.data
219
+ )}`
220
+ );
221
+ } else if (error.request) {
222
+ // Request was made but no response
223
+ throw new Error('No response received from API server');
224
+ }
225
+ }
226
+ console.error(error);
227
+ // Something else happened
228
+ throw new Error(`Error: ${error.message}`);
229
+ }
230
+ }
package/src/types.ts ADDED
@@ -0,0 +1,82 @@
1
+ // src/types.ts
2
+
3
+ export interface HealthCheckResponse {
4
+ status: string;
5
+ data: Array<{
6
+ container: string;
7
+ status: string;
8
+ }>;
9
+ }
10
+
11
+ export interface QueueResponse {
12
+ status: string;
13
+ data: Array<{
14
+ container: string;
15
+ queue: {
16
+ queue_running: any[];
17
+ queue_pending: any[];
18
+ };
19
+ }>;
20
+ }
21
+
22
+ export interface ExecuteWorkflowResponse {
23
+ status: string;
24
+ data: {
25
+ output_names: string[];
26
+ run_id: string;
27
+ };
28
+ }
29
+
30
+ export interface GetOutputResponse {
31
+ status: string;
32
+ data: {
33
+ download_url: string;
34
+ generation_status: string;
35
+ };
36
+ }
37
+
38
+ export interface RunDetail {
39
+ _id: string;
40
+ team_id: string;
41
+ workflow_id: string;
42
+ group_id?: string;
43
+ status: string;
44
+ inputs: Array<{
45
+ path: string;
46
+ value: string;
47
+ s3_key?: string;
48
+ url?: string;
49
+ }>;
50
+ outputs: Array<{
51
+ filename: string;
52
+ s3_key?: string;
53
+ url?: string;
54
+ }>;
55
+ created_at: string;
56
+ started_at?: string;
57
+ completed_at?: string;
58
+ }
59
+
60
+ export interface RunDetailResponse {
61
+ status: string;
62
+ data: RunDetail;
63
+ }
64
+
65
+ export interface RunListResponse {
66
+ status: string;
67
+ data: {
68
+ group_id: string;
69
+ count: number;
70
+ runs: RunDetail[];
71
+ };
72
+ }
73
+
74
+ export interface CancelRunResponse {
75
+ status: string;
76
+ data: string;
77
+ }
78
+
79
+ export interface ErrorResponse {
80
+ status: string;
81
+ errors: string;
82
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2019",
4
+ "module": "CommonJS",
5
+ "declaration": true,
6
+ "outDir": "./dist",
7
+ "strict": true,
8
+ "esModuleInterop": true
9
+ },
10
+ "include": ["src/**/*"]
11
+ }