comfyui-node 1.0.0

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 (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +601 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/call-wrapper.d.ts +115 -0
  5. package/dist/call-wrapper.d.ts.map +1 -0
  6. package/dist/call-wrapper.js +432 -0
  7. package/dist/call-wrapper.js.map +1 -0
  8. package/dist/client.d.ts +233 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +719 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/constants.d.ts +4 -0
  13. package/dist/constants.d.ts.map +1 -0
  14. package/dist/constants.js +4 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/features/abstract.d.ts +15 -0
  17. package/dist/features/abstract.d.ts.map +1 -0
  18. package/dist/features/abstract.js +19 -0
  19. package/dist/features/abstract.js.map +1 -0
  20. package/dist/features/base.d.ts +9 -0
  21. package/dist/features/base.d.ts.map +1 -0
  22. package/dist/features/base.js +15 -0
  23. package/dist/features/base.js.map +1 -0
  24. package/dist/features/feature-flags.d.ts +14 -0
  25. package/dist/features/feature-flags.d.ts.map +1 -0
  26. package/dist/features/feature-flags.js +27 -0
  27. package/dist/features/feature-flags.js.map +1 -0
  28. package/dist/features/file.d.ts +86 -0
  29. package/dist/features/file.d.ts.map +1 -0
  30. package/dist/features/file.js +160 -0
  31. package/dist/features/file.js.map +1 -0
  32. package/dist/features/history.d.ts +16 -0
  33. package/dist/features/history.d.ts.map +1 -0
  34. package/dist/features/history.js +23 -0
  35. package/dist/features/history.js.map +1 -0
  36. package/dist/features/index.d.ts +14 -0
  37. package/dist/features/index.d.ts.map +1 -0
  38. package/dist/features/index.js +14 -0
  39. package/dist/features/index.js.map +1 -0
  40. package/dist/features/manager.d.ts +154 -0
  41. package/dist/features/manager.d.ts.map +1 -0
  42. package/dist/features/manager.js +309 -0
  43. package/dist/features/manager.js.map +1 -0
  44. package/dist/features/misc.d.ts +17 -0
  45. package/dist/features/misc.d.ts.map +1 -0
  46. package/dist/features/misc.js +52 -0
  47. package/dist/features/misc.js.map +1 -0
  48. package/dist/features/model.d.ts +39 -0
  49. package/dist/features/model.d.ts.map +1 -0
  50. package/dist/features/model.js +79 -0
  51. package/dist/features/model.js.map +1 -0
  52. package/dist/features/monitoring.d.ts +90 -0
  53. package/dist/features/monitoring.d.ts.map +1 -0
  54. package/dist/features/monitoring.js +129 -0
  55. package/dist/features/monitoring.js.map +1 -0
  56. package/dist/features/node.d.ts +42 -0
  57. package/dist/features/node.d.ts.map +1 -0
  58. package/dist/features/node.js +68 -0
  59. package/dist/features/node.js.map +1 -0
  60. package/dist/features/queue.d.ts +23 -0
  61. package/dist/features/queue.d.ts.map +1 -0
  62. package/dist/features/queue.js +68 -0
  63. package/dist/features/queue.js.map +1 -0
  64. package/dist/features/system.d.ts +21 -0
  65. package/dist/features/system.d.ts.map +1 -0
  66. package/dist/features/system.js +45 -0
  67. package/dist/features/system.js.map +1 -0
  68. package/dist/features/terminal.d.ts +25 -0
  69. package/dist/features/terminal.d.ts.map +1 -0
  70. package/dist/features/terminal.js +32 -0
  71. package/dist/features/terminal.js.map +1 -0
  72. package/dist/features/user.d.ts +42 -0
  73. package/dist/features/user.d.ts.map +1 -0
  74. package/dist/features/user.js +76 -0
  75. package/dist/features/user.js.map +1 -0
  76. package/dist/index.d.ts +8 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +6 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/pool.d.ts +171 -0
  81. package/dist/pool.d.ts.map +1 -0
  82. package/dist/pool.js +467 -0
  83. package/dist/pool.js.map +1 -0
  84. package/dist/prompt-builder.d.ts +131 -0
  85. package/dist/prompt-builder.d.ts.map +1 -0
  86. package/dist/prompt-builder.js +266 -0
  87. package/dist/prompt-builder.js.map +1 -0
  88. package/dist/tools.d.ts +10 -0
  89. package/dist/tools.d.ts.map +1 -0
  90. package/dist/tools.js +16 -0
  91. package/dist/tools.js.map +1 -0
  92. package/dist/typed-event-target.d.ts +7 -0
  93. package/dist/typed-event-target.d.ts.map +1 -0
  94. package/dist/typed-event-target.js +19 -0
  95. package/dist/typed-event-target.js.map +1 -0
  96. package/dist/types/api.d.ts +212 -0
  97. package/dist/types/api.d.ts.map +1 -0
  98. package/dist/types/api.js +16 -0
  99. package/dist/types/api.js.map +1 -0
  100. package/dist/types/error.d.ts +72 -0
  101. package/dist/types/error.d.ts.map +1 -0
  102. package/dist/types/error.js +75 -0
  103. package/dist/types/error.js.map +1 -0
  104. package/dist/types/event.d.ts +164 -0
  105. package/dist/types/event.d.ts.map +1 -0
  106. package/dist/types/event.js +2 -0
  107. package/dist/types/event.js.map +1 -0
  108. package/dist/types/manager.d.ts +157 -0
  109. package/dist/types/manager.d.ts.map +1 -0
  110. package/dist/types/manager.js +41 -0
  111. package/dist/types/manager.js.map +1 -0
  112. package/dist/types/sampler.d.ts +3 -0
  113. package/dist/types/sampler.d.ts.map +1 -0
  114. package/dist/types/sampler.js +2 -0
  115. package/dist/types/sampler.js.map +1 -0
  116. package/dist/types/tool.d.ts +10 -0
  117. package/dist/types/tool.d.ts.map +1 -0
  118. package/dist/types/tool.js +2 -0
  119. package/dist/types/tool.js.map +1 -0
  120. package/dist/utils/response-error.d.ts +4 -0
  121. package/dist/utils/response-error.d.ts.map +1 -0
  122. package/dist/utils/response-error.js +62 -0
  123. package/dist/utils/response-error.js.map +1 -0
  124. package/dist/utils/ws-reconnect.d.ts +29 -0
  125. package/dist/utils/ws-reconnect.d.ts.map +1 -0
  126. package/dist/utils/ws-reconnect.js +91 -0
  127. package/dist/utils/ws-reconnect.js.map +1 -0
  128. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Igor Lins e Silva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,601 @@
1
+ # ComfyUI SDK
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/comfyui-node?style=flat-square)](https://www.npmjs.com/package/comfyui-node)
4
+ [![License](https://img.shields.io/npm/l/comfyui-node?style=flat-square)](https://github.com/igorls/comfyui-node/blob/main/LICENSE)
5
+ ![CI](https://github.com/igorls/comfyui-node/actions/workflows/release.yml/badge.svg)
6
+ ![Type Coverage](https://img.shields.io/badge/type--coverage-95%25-brightgreen?style=flat-square)
7
+ ![Node Version](https://img.shields.io/badge/node-%3E%3D22-brightgreen?style=flat-square)
8
+
9
+ TypeScript SDK for interacting with the [ComfyUI](https://github.com/comfyanonymous/ComfyUI) API – focused on workflow construction, prompt execution orchestration, multi‑instance scheduling and extension integration.
10
+
11
+ > 1.0 is a complete redesign around modular feature namespaces (`api.ext.*`) and stronger typing. All legacy instance methods have been removed – see Migration section if upgrading.
12
+
13
+ ## Contents
14
+
15
+ - [Features](#features)
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Multi-Instance Pool](#multi-instance-pool)
19
+ - [Authentication](#authentication)
20
+ - [Custom WebSocket](#custom-websocket)
21
+ - [Modular Features (`api.ext`)](#modular-features-apiext)
22
+ - [Events](#events)
23
+ - [1.0 Migration](#10-migration)
24
+ - [Reference Overview](#reference-overview)
25
+ - [Examples](#examples)
26
+ - [Errors & Diagnostics](#errors--diagnostics)
27
+ - [Contributing](#contributing)
28
+ - [License](#license)
29
+
30
+ ## Features
31
+
32
+ - Fully typed TypeScript surface
33
+ - Fluent `PromptBuilder` for graph mutation & input/output mapping
34
+ - Fluent `PromptBuilder` for graph mutation & input/output mapping (with validation & JSON (de)serialization helpers)
35
+ - WebSocket events (progress, preview, output, completion) with reconnection & HTTP polling fallback
36
+ - Weighted multi‑instance job distribution (`ComfyPool`)
37
+ - Extension integration (Manager, Crystools monitor, feature flags)
38
+ - Modular feature namespaces (`api.ext.queue`, `api.ext.node`, etc.)
39
+ - Upload helpers (images, masks) & user data file operations
40
+ - Authentication strategies (basic, bearer token, custom headers)
41
+ - Structured errors & narrow fetch helper
42
+ - Validation utilities for prompt graphs (missing mappings, immediate cycles)
43
+ - JSON round‑trip support for builder state persistence
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ bun add comfyui-node
49
+ # or
50
+ npm install comfyui-node
51
+ ```
52
+
53
+ Requires Node.js 22+ (Active / Current). Earlier Node versions are not supported in 1.0+.
54
+
55
+ ## Quick Start
56
+
57
+ ```ts
58
+ import { ComfyApi, CallWrapper, PromptBuilder, seed, TSamplerName, TSchedulerName } from "comfyui-node";
59
+ import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
60
+
61
+ const api = new ComfyApi("http://localhost:8189").init();
62
+ const workflow = new PromptBuilder(
63
+ ExampleTxt2ImgWorkflow,
64
+ ["positive","negative","checkpoint","seed","batch","step","cfg","sampler","sheduler","width","height"],
65
+ ["images"]
66
+ )
67
+ .setInputNode("checkpoint","4.inputs.ckpt_name")
68
+ .setInputNode("seed","3.inputs.seed")
69
+ .setInputNode("batch","5.inputs.batch_size")
70
+ .setInputNode("negative","7.inputs.text")
71
+ .setInputNode("positive","6.inputs.text")
72
+ .setInputNode("cfg","3.inputs.cfg")
73
+ .setInputNode("sampler","3.inputs.sampler_name")
74
+ .setInputNode("sheduler","3.inputs.scheduler")
75
+ .setInputNode("step","3.inputs.steps")
76
+ .setInputNode("width","5.inputs.width")
77
+ .setInputNode("height","5.inputs.height")
78
+ .setOutputNode("images","9")
79
+ .input("checkpoint","SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
80
+ .input("seed", seed())
81
+ .input("step", 6)
82
+ .input("cfg", 1)
83
+ .input<TSamplerName>("sampler","dpmpp_2m_sde_gpu")
84
+ .input<TSchedulerName>("sheduler","sgm_uniform")
85
+ .input("width",1024)
86
+ .input("height",1024)
87
+ .input("batch",1)
88
+ .input("positive","A picture of a dog on the street");
89
+
90
+ new CallWrapper(api, workflow)
91
+ .onFinished(d => console.log(d.images?.images.map((img: any) => api.ext.file.getPathImage(img))))
92
+ .run();
93
+ ```
94
+
95
+ ### PromptBuilder Validation & Serialization
96
+
97
+ `PromptBuilder` now includes optional robustness helpers you can invoke before submission:
98
+
99
+ ```ts
100
+ builder
101
+ .validateOutputMappings() // Ensures every declared output key maps to an existing node id
102
+ .validateNoImmediateCycles(); // Guards against a node directly referencing itself in its input tuple
103
+
104
+ // Serialize to persist / send over IPC
105
+ const saved = builder.toJSON();
106
+ // Later restore (types must line up with original generic parameters when casting)
107
+ const restored = PromptBuilder.fromJSON(saved);
108
+ ```
109
+
110
+ If validation fails an `Error` is thrown with a concise list of offending mappings, e.g.
111
+
112
+ ```text
113
+ Error: Unmapped or missing output nodes: images:UNMAPPED
114
+ ```
115
+
116
+ Cycle detection currently targets immediate self‑cycles (a node whose input tuple references itself). Broader multi‑hop cycle detection can be layered later without breaking this API.
117
+
118
+ ### Common Validation Patterns
119
+
120
+ ```ts
121
+ function safeBuild(wf: any) {
122
+ return new PromptBuilder(wf,["positive","seed"],["images"])
123
+ .setInputNode("positive","6.inputs.text")
124
+ .setInputNode("seed","3.inputs.seed")
125
+ .setOutputNode("images","9")
126
+ .input("positive","Hello world")
127
+ .input("seed", seed())
128
+ .validateOutputMappings()
129
+ .validateNoImmediateCycles();
130
+ }
131
+ ```
132
+
133
+ ## Multi-Instance Pool
134
+
135
+ `ComfyPool` provides weighted job scheduling & automatic client selection across multiple ComfyUI instances. It is transport‑agnostic and only relies on the standard `ComfyApi` event surface.
136
+
137
+ ### Modes
138
+
139
+ | Mode | Enum | Behavior | When to use |
140
+ | ---- | ---- | -------- | ----------- |
141
+ | Pick zero queue | `EQueueMode.PICK_ZERO` (default) | Choose any online client whose reported `queue_remaining` is 0 (prefers idle machines). Locks a client until it emits an execution event. | Co‑existence with the ComfyUI web UI where queue spikes are common. |
142
+ | Lowest queue | `EQueueMode.PICK_LOWEST` | Choose the online client with the smallest `queue_remaining` (may still be busy). | High throughput batch ingestion; keeps all nodes saturated. |
143
+ | Round‑robin | `EQueueMode.PICK_ROUTINE` | Simple rotation through available online clients irrespective of queue depth. | Latency balancing; predictable distribution. |
144
+
145
+ ### Basic Example
146
+
147
+ ```ts
148
+ import { ComfyApi, ComfyPool, EQueueMode, CallWrapper, PromptBuilder, seed } from "comfyui-node";
149
+ import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
150
+
151
+ // Create two API clients (auth / headers etc still work as normal)
152
+ const pool = new ComfyPool([
153
+ new ComfyApi("http://localhost:8188"),
154
+ new ComfyApi("http://localhost:8189")
155
+ ]); // defaults to PICK_ZERO
156
+
157
+ // A single generation task (returns file paths for convenience)
158
+ const generate = async (api: ComfyApi) => {
159
+ const wf = new PromptBuilder(ExampleTxt2ImgWorkflow,["positive","checkpoint","seed"],["images"])
160
+ .setInputNode("checkpoint","4.inputs.ckpt_name")
161
+ .setInputNode("seed","3.inputs.seed")
162
+ .setInputNode("positive","6.inputs.text")
163
+ .setOutputNode("images","9")
164
+ .input("checkpoint","SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
165
+ .input("seed", seed())
166
+ .input("positive","A close up picture of a cat");
167
+ return await new Promise<string[]>(resolve => {
168
+ new CallWrapper(api, wf)
169
+ .onFinished(data => resolve(data.images?.images.map((img: any) => api.ext.file.getPathImage(img)) || []))
170
+ .run();
171
+ });
172
+ };
173
+
174
+ // Dispatch 3 parallel generations across the pool
175
+ const results = await pool.batch(Array(3).fill(generate));
176
+ console.log(results.flat());
177
+ ```
178
+
179
+ ### Job Weighting
180
+
181
+ Jobs are inserted into an internal priority queue ordered by ascending weight. Lower weight runs earlier. By default the weight is set to the queue length at insertion (FIFO). You can override:
182
+
183
+ ```ts
184
+ await Promise.all([
185
+ pool.run(doSomethingHeavy, 10), // runs later
186
+ pool.run(doSomethingQuick, 1), // runs first
187
+ pool.run(anotherTask, 5)
188
+ ]);
189
+ ```
190
+
191
+ ### Include / Exclude Filters
192
+
193
+ Target or avoid specific client IDs:
194
+
195
+ ```ts
196
+ await pool.run(taskA, undefined, { includeIds: ["gpu-a"] }); // only gpu-a
197
+ await pool.run(taskB, undefined, { excludeIds: ["gpu-b"] }); // any except gpu-b
198
+ ```
199
+
200
+ ### Failover & Retries
201
+
202
+ `run()` attempts transparent failover when a job throws. It excludes the failing client and retries another (up to `maxRetries`).
203
+
204
+ ```ts
205
+ await pool.run(doGenerate, undefined, undefined, { maxRetries: 3, retryDelay: 1500 });
206
+ ```
207
+
208
+ Disable failover:
209
+
210
+ ```ts
211
+ await pool.run(doGenerate, undefined, undefined, { enableFailover: false });
212
+ ```
213
+
214
+ ### Pool Events
215
+
216
+ `ComfyPool` is an `EventTarget` emitting high‑level orchestration signals:
217
+
218
+ | Event | Detail Payload | When |
219
+ | ----- | -------------- | ---- |
220
+ | `init` | – | All clients added & initial processing pass done |
221
+ | `added` / `removed` | `{ client, clientIdx }` | Client lifecycle changes |
222
+ | `ready` | `{ client, clientIdx }` | Individual client fully initialized |
223
+ | `executing` / `executed` | `{ client, clientIdx }` | A job starts / finishes on a client |
224
+ | `execution_error` | `{ client, clientIdx, error, willRetry, attempt, maxRetries }` | A job threw; may retry |
225
+ | `execution_interrupted` | `{ client, clientIdx }` | Underlying API emitted interruption |
226
+ | `connected` / `disconnected` / `reconnected` | `{ client, clientIdx }` | WebSocket state relayed from `ComfyApi` |
227
+ | `terminal` | `{ clientIdx, line }` | Terminal log pass‑through |
228
+ | `system_monitor` | `{ clientIdx, data }` | Crystools monitor snapshot (when supported) |
229
+ | `add_job` | `{ jobIdx, weight }` | Job inserted into internal queue |
230
+ | `change_mode` | `{ mode }` | Queue selection mode altered |
231
+ | `have_job` | `{ client, remain }` | A client reports pending queue > 0 |
232
+ | `idle` | `{ client }` | A previously busy client reports queue 0 |
233
+
234
+ ### Cleaning Up
235
+
236
+ Always invoke `destroy()` when finished to clear intervals, event listeners & underlying client connections:
237
+
238
+ ```ts
239
+ pool.destroy();
240
+ ```
241
+
242
+ ### Combined Orchestration Example (Auth + Pool + Validation + Retry)
243
+
244
+ ```ts
245
+ import { ComfyApi, ComfyPool, EQueueMode, PromptBuilder, CallWrapper, seed } from "comfyui-node";
246
+
247
+ const pool = new ComfyPool([
248
+ new ComfyApi(process.env.C1!,"c1", { credentials: { type: "bearer_token", token: process.env.C1_TOKEN! } }),
249
+ new ComfyApi(process.env.C2!,"c2")
250
+ ], { mode: EQueueMode.PICK_LOWEST });
251
+
252
+ async function generate(api: ComfyApi, text: string) {
253
+ const wf = /* load / clone a base workflow JSON */ {} as any;
254
+ const builder = new PromptBuilder(wf,["positive","seed"],["images"])
255
+ .setInputNode("positive","6.inputs.text")
256
+ .setInputNode("seed","3.inputs.seed")
257
+ .setOutputNode("images","9")
258
+ .input("positive", text)
259
+ .input("seed", seed())
260
+ .validateOutputMappings();
261
+
262
+ return await new Promise<string[]>((resolve, reject) => {
263
+ new CallWrapper(api, builder)
264
+ .onFinished(d => resolve((d.images?.images||[]).map((img:any)=> api.ext.file.getPathImage(img))))
265
+ .onFailed(err => reject(err))
266
+ .run();
267
+ });
268
+ }
269
+
270
+ // Weighted submission with retry semantics
271
+ const tasks = ["cat portrait","cyberpunk city","forest at dawn"].map(txt => (api: ComfyApi) => generate(api, txt));
272
+ const results = await Promise.all(tasks.map((fn,i)=> pool.run(fn, i))); // lower weight = earlier
273
+ console.log(results.flat());
274
+ pool.destroy();
275
+ ```
276
+
277
+ ### Choosing a Mode
278
+
279
+ | Goal | Suggested Mode |
280
+ | ---- | -------------- |
281
+ | Minimize latency spikes | `PICK_ZERO` |
282
+ | Maximize throughput | `PICK_LOWEST` |
283
+ | Deterministic striping | `PICK_ROUTINE` |
284
+
285
+ You can change dynamically:
286
+
287
+ ```ts
288
+ pool.changeMode(EQueueMode.PICK_LOWEST);
289
+ ```
290
+
291
+ ### Observability Tips
292
+
293
+ Listen for `execution_error` with `willRetry=true` to surface transient node failures; attach Prometheus / metrics counters externally from these events if desired.
294
+
295
+ ### Relation to `CallWrapper`
296
+
297
+ `ComfyPool` does not abstract prompt construction or execution detail; each job decides how to use `CallWrapper`, direct `api.ext.queue.*` calls or even file operations before enqueueing.
298
+
299
+ ### Future Ideas (Contributions Welcome)
300
+
301
+ - Global circuit breaker (temporarily exclude flapping client)
302
+ - Adaptive weight assignment based on rolling execution duration
303
+ - Pluggable selection strategies via user callback
304
+
305
+ If you build one, open a PR – keep the core minimal & dependency‑free.
306
+
307
+ ## Authentication
308
+
309
+ ```ts
310
+ import { ComfyApi, BasicCredentials, BearerTokenCredentials, CustomCredentials } from "comfyui-node";
311
+
312
+ const basic = new ComfyApi("http://localhost:8189","id1", { credentials: { type: "basic", username: "u", password: "p" } as BasicCredentials }).init();
313
+ const bearer = new ComfyApi("http://localhost:8189","id2", { credentials: { type: "bearer_token", token: "token" } as BearerTokenCredentials }).init();
314
+ const custom = new ComfyApi("http://localhost:8189","id3", { credentials: { type: "custom", headers: { "X-Api-Key": "abc" } } as CustomCredentials }).init();
315
+ ```
316
+
317
+ ## Custom WebSocket
318
+
319
+ ```ts
320
+ import { ComfyApi, WebSocketInterface } from "comfyui-node";
321
+ import CustomWebSocket from "your-custom-ws";
322
+
323
+ const api = new ComfyApi("http://localhost:8189", "node-id", { customWebSocketImpl: CustomWebSocket as WebSocketInterface }).init();
324
+ ```
325
+
326
+ ## Modular Features (`api.ext`)
327
+
328
+ ```ts
329
+ await api.waitForReady();
330
+ await api.ext.queue.queuePrompt(null, workflow);
331
+ const stats = await api.ext.system.getSystemStats();
332
+ const checkpoints = await api.ext.node.getCheckpoints();
333
+ const embeddings = await api.ext.misc.getEmbeddings();
334
+ const flags = await api.ext.featureFlags.getServerFeatures();
335
+ ```
336
+
337
+ | Namespace | Responsibility |
338
+ | --------- | -------------- |
339
+ | `queue` | Prompt submission, append & interrupt |
340
+ | `history` | Execution history retrieval |
341
+ | `system` | System stats & memory free |
342
+ | `node` | Node defs + sampler / checkpoint / lora helpers |
343
+ | `user` | User & settings CRUD |
344
+ | `file` | Uploads, image helpers, user data file ops |
345
+ | `model` | Experimental model browsing & previews |
346
+ | `terminal` | Terminal logs & subscription toggle |
347
+ | `misc` | Extensions list, embeddings (new + fallback) |
348
+ | `manager` | ComfyUI Manager extension integration |
349
+ | `monitor` | Crystools monitor events & snapshot |
350
+ | `featureFlags` | Server capabilities (`/features`) |
351
+
352
+ ## Events
353
+
354
+ Both `ComfyApi` and `ComfyPool` expose strongly typed event maps. Import the key unions or event maps for generic helpers:
355
+
356
+ ```ts
357
+ import { ComfyApi, ComfyApiEventKey, TComfyAPIEventMap } from 'comfyui-node';
358
+
359
+ const api = new ComfyApi('http://localhost:8188');
360
+ api.on('progress', (ev) => {
361
+ console.log(ev.detail.value, '/', ev.detail.max);
362
+ });
363
+
364
+ function handleApiEvent<K extends ComfyApiEventKey>(k: K, e: TComfyAPIEventMap[K]) {
365
+ if (k === 'executed') {
366
+ console.log('Node executed:', e.detail.node);
367
+ }
368
+ }
369
+ ```
370
+
371
+ Pool usage:
372
+
373
+ ```ts
374
+ import { ComfyPool, ComfyPoolEventKey } from 'comfyui-node';
375
+
376
+ pool.on('execution_error', (ev) => {
377
+ if (ev.detail.willRetry) console.warn('Transient failure, retrying...');
378
+ });
379
+ ```
380
+
381
+ ---
382
+
383
+ ## 1.0 Migration
384
+
385
+ All legacy `ComfyApi` instance methods listed below were **removed in 1.0.0** after a deprecation window in 0.2.x. Migrate to the `api.ext.*` namespaces. If you're upgrading from <1.0, replace calls as shown. No runtime warnings remain (they were stripped with the removals).
386
+
387
+ | Deprecated | Replacement |
388
+ | ---------- | ----------- |
389
+ | `queuePrompt(...)` | `api.ext.queue.queuePrompt(...)` |
390
+ | `appendPrompt(...)` | `api.ext.queue.appendPrompt(...)` |
391
+ | `getHistories(...)` | `api.ext.history.getHistories(...)` |
392
+ | `getHistory(id)` | `api.ext.history.getHistory(id)` |
393
+ | `getSystemStats()` | `api.ext.system.getSystemStats()` |
394
+ | `getCheckpoints()` | `api.ext.node.getCheckpoints()` |
395
+ | `getLoras()` | `api.ext.node.getLoras()` |
396
+ | `getSamplerInfo()` | `api.ext.node.getSamplerInfo()` |
397
+ | `getNodeDefs(name?)` | `api.ext.node.getNodeDefs(name?)` |
398
+ | `getExtensions()` | `api.ext.misc.getExtensions()` |
399
+ | `getEmbeddings()` | `api.ext.misc.getEmbeddings()` |
400
+ | `uploadImage(...)` | `api.ext.file.uploadImage(...)` |
401
+ | `uploadMask(...)` | `api.ext.file.uploadMask(...)` |
402
+ | `getPathImage(info)` | `api.ext.file.getPathImage(info)` |
403
+ | `getImage(info)` | `api.ext.file.getImage(info)` |
404
+ | `getUserData(file)` | `api.ext.file.getUserData(file)` |
405
+ | `storeUserData(...)` | `api.ext.file.storeUserData(...)` |
406
+ | `deleteUserData(file)` | `api.ext.file.deleteUserData(file)` |
407
+ | `moveUserData(...)` | `api.ext.file.moveUserData(...)` |
408
+ | `listUserData(...)` | `api.ext.file.listUserData(...)` |
409
+ | `getUserConfig()` | `api.ext.user.getUserConfig()` |
410
+ | `createUser(name)` | `api.ext.user.createUser(name)` |
411
+ | `getSettings()` | `api.ext.user.getSettings()` |
412
+ | `getSetting(id)` | `api.ext.user.getSetting(id)` |
413
+ | `storeSettings(map)` | `api.ext.user.storeSettings(map)` |
414
+ | `storeSetting(id,val)` | `api.ext.user.storeSetting(id,val)` |
415
+ | `getTerminalLogs()` | `api.ext.terminal.getTerminalLogs()` |
416
+ | `setTerminalSubscription()` | `api.ext.terminal.setTerminalSubscription()` |
417
+ | `interrupt()` | `api.ext.queue.interrupt()` |
418
+
419
+ Quick grep-based migration (bash):
420
+
421
+ ```bash
422
+ grep -R "api\.getSystemStats" -n src | cut -d: -f1 | xargs sed -i '' 's/api\.getSystemStats()/api.ext.system.getSystemStats()/g'
423
+ ```
424
+
425
+ PowerShell example:
426
+
427
+ ```powershell
428
+ Get-ChildItem -Recurse -Include *.ts | ForEach-Object {
429
+ (Get-Content $_.FullName) -replace 'api.getSystemStats\(\)', 'api.ext.system.getSystemStats()' | Set-Content $_.FullName
430
+ }
431
+ ```
432
+
433
+ (Adjust the pattern per method; or use a codemod tool if you have many occurrences.)
434
+
435
+ Diff example:
436
+
437
+ Example migration:
438
+
439
+ ```diff
440
+ - const stats = await api.getSystemStats();
441
+ + const stats = await api.ext.system.getSystemStats();
442
+ - await api.uploadImage(buf, 'a.png');
443
+ + await api.ext.file.uploadImage(buf, 'a.png');
444
+ ```
445
+
446
+ ## Reference Overview
447
+
448
+ Core (non‑deprecated) `ComfyApi` methods: `init`, `waitForReady`, event registration (`on`/`off`/`removeAllListeners`), `fetchApi`, `pollStatus`, `ping`, `reconnectWs`, `destroy`, and modular surface via `ext`.
449
+
450
+ Supporting classes:
451
+
452
+ - `PromptBuilder` – graph construction & value injection
453
+ - `CallWrapper` – prompt execution lifecycle helpers
454
+ - `ComfyPool` – multi‑instance scheduler
455
+
456
+ Enums & Types: `EQueueMode`, sampler / scheduler unions, `OSType`, plus exported response types found under `types/*`.
457
+
458
+ ## Examples
459
+
460
+ See the `examples` directory for text‑to‑image, image‑to‑image, upscaling and pool orchestration patterns.
461
+
462
+ ## Errors & Diagnostics
463
+
464
+ The SDK raises specialized subclasses of `Error` to improve debuggability during workflow submission and execution:
465
+
466
+ | Error | When | Key Extras |
467
+ | ----- | ---- | ---------- |
468
+ | `EnqueueFailedError` | HTTP `/prompt` (append/queue) failed | `status`, `statusText`, `url`, `method`, `bodyJSON`, `bodyTextSnippet`, `reason` |
469
+ | `ExecutionFailedError` | Execution finished but not all mapped outputs arrived | missing outputs context |
470
+ | `ExecutionInterruptedError` | Server emitted an interruption mid run | cause carries interruption detail |
471
+ | `MissingNodeError` | A declared bypass or output node is absent | `cause` (optional) |
472
+ | `WentMissingError` | Job disappeared from queue and no cached output | – |
473
+ | `FailedCacheError` | Cached output retrieval failed | – |
474
+ | `CustomEventError` | Server emitted execution error event | event payload in `cause` |
475
+ | `DisconnectedError` | WebSocket disconnected mid‑execution | – |
476
+
477
+ ### Error Codes
478
+
479
+ Every custom error exposes a stable `code` (enum) to enable branch logic without string matching message text:
480
+
481
+ ```ts
482
+ import { ErrorCode, EnqueueFailedError } from "comfyui-node";
483
+
484
+ try { /* run call wrapper */ } catch (e) {
485
+ if ((e as any).code === ErrorCode.ENQUEUE_FAILED) {
486
+ // inspect structured diagnostics
487
+ }
488
+ }
489
+ ```
490
+
491
+ ### EnqueueFailedError Details
492
+
493
+ When the server rejects a workflow submission the SDK now attempts to surface the underlying cause:
494
+
495
+ ```ts
496
+ try {
497
+ await new CallWrapper(api, workflow).run();
498
+ } catch (e) {
499
+ if (e instanceof EnqueueFailedError) {
500
+ console.error('Status:', e.status, e.statusText);
501
+ console.error('Reason:', e.reason);
502
+ console.error('Body JSON:', e.bodyJSON);
503
+ console.error('Snippet:', e.bodyTextSnippet);
504
+ }
505
+ }
506
+ ```
507
+
508
+ `reason` is resolved using (in order): `bodyJSON.error`, `bodyJSON.message`, falling back to a truncated textual body (first 500 chars). Raw JSON (if parseable) and a short text snippet are both retained to help rapidly identify mis‑shaped prompts, missing extensions, permission issues or model path problems.
509
+
510
+ If the response body is not JSON, `bodyTextSnippet` contains the first 500 characters of the returned text, which is also copied into `reason`.
511
+
512
+ These enriched diagnostics are only attached for the enqueue phase; downstream execution issues still rely on event‑level errors.
513
+
514
+ ### Execution Failure vs Interruption
515
+
516
+ - `ExecutionFailedError`: The workflow ran but one or more declared output nodes never produced data (often due to an upstream node error not surfaced as a global event). Revisit your output mappings or inspect per‑node errors.
517
+ - `ExecutionInterruptedError`: The server (or user action) actively interrupted execution; retrying may succeed if the interruption cause was transient.
518
+
519
+ ### Persisting & Replaying Builder State
520
+
521
+ You can store builder state in a database / job queue:
522
+
523
+ ```ts
524
+ const snapshot = builder.toJSON();
525
+ // later
526
+ const restored = PromptBuilder.fromJSON(snapshot)
527
+ .validateOutputMappings();
528
+ ```
529
+
530
+ This is useful for deferred execution, cross‑process scheduling, or audit logging of the exact prompt graph sent to the server.
531
+
532
+ ## Contributing
533
+
534
+ Issues and PRs welcome. Please include focused changes and tests where sensible. Adhere to existing coding style and keep feature surfaces minimal & cohesive.
535
+
536
+ ## Testing & Coverage
537
+
538
+ This repository uses Bun's built-in test runner. Common scripts:
539
+
540
+ ```bash
541
+ bun test # unit + lightweight integration tests
542
+ bun run test:real # real server tests (COMFY_REAL=1)
543
+ bun run test:full # comprehensive real server tests (COMFY_REAL=1 COMFY_FULL=1)
544
+ bun run coverage # text coverage summary (lines/functions per file)
545
+ bun run coverage:lcov # generate coverage/lcov.info (for badges or external services)
546
+ bun run coverage:enforce # generate LCOV then enforce thresholds
547
+ ```
548
+
549
+ Environment flags:
550
+
551
+ - `COMFY_REAL=1` enables `test/real.integration.spec.ts` (expects a running ComfyUI at `http://localhost:8188` unless overridden via `COMFY_HOST`).
552
+ - `COMFY_FULL=1` additionally enables the extended `test/real.full.integration.spec.ts` suite.
553
+ - `COMFY_HOST=http://host:port` to point at a non-default instance.
554
+
555
+ Coverage thresholds are enforced by `scripts/coverage-check.ts` (baseline intentionally modest to allow incremental improvement):
556
+
557
+ Default thresholds:
558
+
559
+ - Lines: `>= 25%`
560
+ - Functions: `>= 60%`
561
+
562
+ Override thresholds ad hoc (CI example):
563
+
564
+ ```bash
565
+ COVERAGE_MIN_LINES=30 COVERAGE_MIN_FUNCTIONS=65 bun run coverage:enforce
566
+ ```
567
+
568
+ or in PowerShell:
569
+
570
+ ```powershell
571
+ $env:COVERAGE_MIN_LINES=30; $env:COVERAGE_MIN_FUNCTIONS=65; bun run coverage:enforce
572
+ ```
573
+
574
+ ### Improving Coverage
575
+
576
+ Current low-coverage areas (see `bun test --coverage` output):
577
+
578
+ - `src/client.ts` – large surface; break out helpers & add unit tests for fetch error branches and WebSocket reconnect logic.
579
+ - `src/call-wrapper.ts` – test error paths (enqueue failure, execution interruption, missing outputs) with mocked `fetch` & event streams.
580
+ - Feature modules with toleration logic (`monitoring`, `manager`, `terminal`) – add mocks to simulate absent endpoints & successful responses.
581
+
582
+ Incremental strategy:
583
+
584
+ 1. Extract pure helper functions from monolithic classes (e.g., parsing, polling backoff) into modules you can unit test in isolation.
585
+ 2. Add fine-grained tests for error branches (simulate non-200 responses & malformed JSON bodies) to raise line coverage quickly.
586
+ 3. Introduce deterministic mock WebSocket that replays scripted events (connection drop, progress, output) to cover reconnect & event translation.
587
+ 4. Gradually raise `COVERAGE_MIN_LINES` by 5% after each meaningful set of additions.
588
+
589
+ Skipping heavy real-image generation: full suite internally tolerates missing models & will skip or soften assertions rather than fail—use it sparingly in CI (nightly job) if runtime is a concern.
590
+
591
+ If contributing, please run at least:
592
+
593
+ ```bash
594
+ bun test && bun run coverage
595
+ ```
596
+
597
+ before opening a PR, and prefer adding tests alongside new feature code.
598
+
599
+ ## License
600
+
601
+ MIT – see `LICENSE`.