@tscomfy/client 0.0.0-stage → 0.2.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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -2
  3. package/dist/channel.d.ts +54 -0
  4. package/dist/channel.d.ts.map +1 -0
  5. package/dist/channel.js +307 -0
  6. package/dist/channel.js.map +1 -0
  7. package/dist/client-error.d.ts +15 -0
  8. package/dist/client-error.d.ts.map +1 -0
  9. package/dist/client-error.js +16 -0
  10. package/dist/client-error.js.map +1 -0
  11. package/dist/connection.d.ts +33 -0
  12. package/dist/connection.d.ts.map +1 -0
  13. package/dist/connection.js +35 -0
  14. package/dist/connection.js.map +1 -0
  15. package/dist/event-queue.d.ts +16 -0
  16. package/dist/event-queue.d.ts.map +1 -0
  17. package/dist/event-queue.js +66 -0
  18. package/dist/event-queue.js.map +1 -0
  19. package/dist/events.d.ts +60 -0
  20. package/dist/events.d.ts.map +1 -0
  21. package/dist/events.js +2 -0
  22. package/dist/events.js.map +1 -0
  23. package/dist/files.d.ts +55 -0
  24. package/dist/files.d.ts.map +1 -0
  25. package/dist/files.js +69 -0
  26. package/dist/files.js.map +1 -0
  27. package/dist/frames.d.ts +24 -0
  28. package/dist/frames.d.ts.map +1 -0
  29. package/dist/frames.js +71 -0
  30. package/dist/frames.js.map +1 -0
  31. package/dist/http.d.ts +28 -0
  32. package/dist/http.d.ts.map +1 -0
  33. package/dist/http.js +96 -0
  34. package/dist/http.js.map +1 -0
  35. package/dist/index.d.ts +17 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +14 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/job.d.ts +59 -0
  40. package/dist/job.d.ts.map +1 -0
  41. package/dist/job.js +481 -0
  42. package/dist/job.js.map +1 -0
  43. package/dist/outcome.d.ts +47 -0
  44. package/dist/outcome.d.ts.map +1 -0
  45. package/dist/outcome.js +2 -0
  46. package/dist/outcome.js.map +1 -0
  47. package/dist/outputs.d.ts +53 -0
  48. package/dist/outputs.d.ts.map +1 -0
  49. package/dist/outputs.js +21 -0
  50. package/dist/outputs.js.map +1 -0
  51. package/dist/read-outputs.d.ts +11 -0
  52. package/dist/read-outputs.d.ts.map +1 -0
  53. package/dist/read-outputs.js +142 -0
  54. package/dist/read-outputs.js.map +1 -0
  55. package/dist/server-errors.d.ts +7 -0
  56. package/dist/server-errors.d.ts.map +1 -0
  57. package/dist/server-errors.js +46 -0
  58. package/dist/server-errors.js.map +1 -0
  59. package/dist/server.d.ts +30 -0
  60. package/dist/server.d.ts.map +1 -0
  61. package/dist/server.js +2 -0
  62. package/dist/server.js.map +1 -0
  63. package/dist/timers.d.ts +11 -0
  64. package/dist/timers.d.ts.map +1 -0
  65. package/dist/timers.js +38 -0
  66. package/dist/timers.js.map +1 -0
  67. package/dist/url.d.ts +14 -0
  68. package/dist/url.d.ts.map +1 -0
  69. package/dist/url.js +39 -0
  70. package/dist/url.js.map +1 -0
  71. package/dist/uuid.d.ts +7 -0
  72. package/dist/uuid.d.ts.map +1 -0
  73. package/dist/uuid.js +13 -0
  74. package/dist/uuid.js.map +1 -0
  75. package/package.json +46 -4
  76. package/src/channel.ts +334 -0
  77. package/src/client-error.ts +19 -0
  78. package/src/connection.ts +59 -0
  79. package/src/event-queue.ts +70 -0
  80. package/src/events.ts +76 -0
  81. package/src/files.ts +119 -0
  82. package/src/frames.ts +87 -0
  83. package/src/http.ts +113 -0
  84. package/src/index.ts +53 -0
  85. package/src/job.ts +562 -0
  86. package/src/outcome.ts +52 -0
  87. package/src/outputs.ts +69 -0
  88. package/src/read-outputs.ts +171 -0
  89. package/src/server-errors.ts +61 -0
  90. package/src/server.ts +29 -0
  91. package/src/timers.ts +35 -0
  92. package/src/url.ts +41 -0
  93. package/src/uuid.ts +12 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the tscomfy authors
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 CHANGED
@@ -1,3 +1,77 @@
1
- # Temporary Holding Version
1
+ # @tscomfy/client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@tscomfy/client)](https://www.npmjs.com/package/@tscomfy/client)
4
+
5
+ Runs tscomfy workflows on a ComfyUI server. It queues a workflow, streams progress, live
6
+ previews and outputs, and tells you how the job ended. It uses only `fetch` and `WebSocket`, with
7
+ no runtime dependencies, and runs on Node 24+, Bun 1.4+, Deno and browsers.
8
+
9
+ ```sh
10
+ npm install @tscomfy/client @tscomfy/core
11
+ ```
12
+
13
+ ```ts
14
+ import { workflow } from "@tscomfy/core";
15
+ import { connect, fetchFile, outputFiles, submit } from "@tscomfy/client";
16
+ import { local } from "./comfy/local/index.ts"; // generated by `tscomfy generate`
17
+
18
+ const wf = workflow(local, ({ n }) => {
19
+ const [model, clip, vae] = n.CheckpointLoaderSimple({ ckpt_name: "sdxl.safetensors" });
20
+ const latent = n.KSampler({
21
+ model,
22
+ positive: n.CLIPTextEncode({ clip, text: "a lighthouse at dusk" }),
23
+ negative: n.CLIPTextEncode({ clip, text: "" }),
24
+ latent_image: n.EmptyLatentImage({ width: 1024, height: 1024 }),
25
+ seed: 42n,
26
+ sampler_name: "euler",
27
+ scheduler: "normal",
28
+ });
29
+ const save = n.SaveImage({ images: n.VAEDecode({ samples: latent, vae }) });
30
+ return { save }; // kept as wf.handles
31
+ });
32
+
33
+ const comfy = await connect({ url: "http://127.0.0.1:8188" });
34
+ const job = submit(comfy, wf, { previews: "taesd" });
35
+ for await (const event of job.events) {
36
+ if (event.type === "progress") console.log(`${event.node}: ${event.value}/${event.max}`);
37
+ if (event.type === "preview") showPreview(event.mime, event.bytes);
38
+ }
39
+ const outcome = await job.outcome;
40
+ if (outcome.status === "success") {
41
+ for (const file of outputFiles(outcome, wf.handles.save)) {
42
+ const { bytes } = await fetchFile(comfy, file);
43
+ // write bytes to disk, show them, upload them…
44
+ }
45
+ }
46
+ await comfy.close();
47
+ ```
48
+
49
+ **Reliable by design.**
50
+ - **No missed events.** The client chooses the prompt id and subscribes before it queues.
51
+ - **The server's record decides.** The outcome comes from the server's record of the job, so a
52
+ dropped websocket costs a moment of progress, never a wrong or missing result.
53
+ - **Outcomes are data.** The outcome is `success`, `rejected` (the server's validation errors as
54
+ diagnostics), `failed` (the node's error) or `cancelled`.
55
+ - **What rejects.** The outcome promise rejects only when you abort (`signal`) or the server
56
+ can't be reached (`ClientError`).
57
+ - **Exact seeds.** Seeds beyond 2⁵³ arrive exactly as written.
58
+
59
+ **Also:** `job.cancel()`, `uploadFile` for loader inputs, `fileUrl` for `<img src>`, and
60
+ `outputOf(outcome, node)` for a node's text output.
61
+
62
+ ## Requirements
63
+
64
+ - **Servers:** ComfyUI 0.26 or later.
65
+ - **TypeScript:** your project needs a runtime's web types (`@types/node`, Bun's types, Deno, or
66
+ the DOM lib). The package's declarations name `fetch`, `WebSocket` and `AbortSignal`.
67
+ - **Browsers:** a page needs the server's permission. Start ComfyUI with
68
+ `--enable-cors-header <origin>`, or serve the page from the same origin.
69
+
70
+ ## Documentation
71
+
72
+ [Running workflows](https://github.com/TheeKia/tscomfy/blob/main/docs/guides/running-workflows.md)
73
+ and [the API reference](https://github.com/TheeKia/tscomfy/blob/main/docs/api/client.md).
74
+
75
+ ## License
76
+
77
+ MIT
@@ -0,0 +1,54 @@
1
+ import { type JsonObject } from "@tscomfy/core";
2
+ import { ClientError } from "./client-error.ts";
3
+ import type { Connection } from "./connection.ts";
4
+ import { type PreviewFrame, type TextFrame } from "./frames.ts";
5
+ import type { Server } from "./server.ts";
6
+ /** Ties a connection to its channel. */
7
+ export declare function attach(connection: Connection, channel: Channel): void;
8
+ /** The channel behind a connection. Throws a `TypeError` for anything `connect` didn't return. */
9
+ export declare function channelOf(connection: Connection): Channel;
10
+ /** What a job hears from the channel. */
11
+ export interface JobListener {
12
+ readonly id: string;
13
+ /** A JSON event carrying this job's prompt id. */
14
+ message(type: string, data: JsonObject): void;
15
+ /** A preview or progress text of this job. */
16
+ frame(frame: PreviewFrame | TextFrame): void;
17
+ /** The server's queue changed (a `status` broadcast). */
18
+ queueChanged(): void;
19
+ /** The websocket reopened after a drop: events may have been lost. */
20
+ reconnected(): void;
21
+ /** The channel is gone for good: closed, or the server stayed unreachable. */
22
+ fail(error: ClientError): void;
23
+ }
24
+ /**
25
+ * One websocket and the jobs listening on it. Opens one socket at a time, shared by every
26
+ * caller; reconnects only while jobs are open, after the old socket has closed (two live sockets
27
+ * with one client id lose events on the server), and re-sends the feature flags first. `close()`
28
+ * stops everything it started: sockets, handshakes, backoff waits.
29
+ */
30
+ export declare class Channel {
31
+ #private;
32
+ readonly server: Server;
33
+ readonly clientId: string;
34
+ features: JsonObject;
35
+ constructor(server: Server, clientId: string);
36
+ /** Whether `close()` was called. */
37
+ get closed(): boolean;
38
+ /**
39
+ * Waits until the websocket is open, opening it (once, for every caller) if it isn't. Rejects
40
+ * with the signal's reason when `signal` aborts, and with a {@link ClientError} when the
41
+ * channel is closed or the socket can't be opened.
42
+ */
43
+ ready(signal?: AbortSignal): Promise<void>;
44
+ /**
45
+ * Checks that the server is one the client supports: ComfyUI 0.26 or later by its version
46
+ * (when it reports one it can parse), and with the job API. Rejects with a {@link ClientError}.
47
+ */
48
+ checkServer(signal?: AbortSignal): Promise<void>;
49
+ register(job: JobListener): void;
50
+ unregister(job: JobListener): void;
51
+ /** Closes every socket and fails every open job; waits until the sockets have closed. */
52
+ close(): Promise<void>;
53
+ }
54
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAO1C,wCAAwC;AACxC,wBAAgB,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAErE;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAMzD;AAED,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9C,8CAA8C;IAC9C,KAAK,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7C,yDAAyD;IACzD,YAAY,IAAI,IAAI,CAAC;IACrB,sEAAsE;IACtE,WAAW,IAAI,IAAI,CAAC;IACpB,8EAA8E;IAC9E,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;CAChC;AAsBD;;;;;GAKG;AACH,qBAAa,OAAO;;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,UAAU,CAAM;IAa1B,YAAY,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAG3C;IAED,oCAAoC;IACpC,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;;OAIG;IACG,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/C;IAED;;;OAGG;IACG,WAAW,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BrD;IAED,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAG/B;IAED,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAGjC;IAED,yFAAyF;IACzF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrB;CAyIF"}
@@ -0,0 +1,307 @@
1
+ import { parse, stringify } from "@tscomfy/core";
2
+ import { ClientError } from "./client-error.js";
3
+ import { decodeFrame } from "./frames.js";
4
+ import { isObject, readObject, send } from "./http.js";
5
+ import { abortable, sleep } from "./timers.js";
6
+ import { baseUrl, endpoint, shownUrl } from "./url.js";
7
+ /** Connections and the channels behind them. */
8
+ const channels = new WeakMap();
9
+ /** Ties a connection to its channel. */
10
+ export function attach(connection, channel) {
11
+ channels.set(connection, channel);
12
+ }
13
+ /** The channel behind a connection. Throws a `TypeError` for anything `connect` didn't return. */
14
+ export function channelOf(connection) {
15
+ const channel = channels.get(connection);
16
+ if (channel === undefined) {
17
+ throw new TypeError("Expected a connection returned by connect()");
18
+ }
19
+ return channel;
20
+ }
21
+ /** How long the server gets to answer the feature flags. */
22
+ const HANDSHAKE_MS = 10_000;
23
+ /** Delays between reconnect attempts, before jitter: about two minutes in all. */
24
+ const BACKOFF_MS = [250, 500, 1000, 2000, 4000, 8000, 15_000, 30_000, 30_000, 30_000];
25
+ /** The lowest server version the client supports: the per-job cancel came in 0.26.0. */
26
+ const MIN_VERSION = [0, 26, 0];
27
+ /**
28
+ * The flags the client announces as its first message: previews and progress text that say
29
+ * which prompt they belong to, as the frontend announces them.
30
+ */
31
+ const CLIENT_FLAGS = { supports_preview_metadata: true, supports_progress_text_metadata: true };
32
+ /** A websocket's `readyState` once closed; the global `WebSocket` may not exist. */
33
+ const CLOSED = 3;
34
+ const closedError = () => new ClientError("The connection is closed");
35
+ /**
36
+ * One websocket and the jobs listening on it. Opens one socket at a time, shared by every
37
+ * caller; reconnects only while jobs are open, after the old socket has closed (two live sockets
38
+ * with one client id lose events on the server), and re-sends the feature flags first. `close()`
39
+ * stops everything it started: sockets, handshakes, backoff waits.
40
+ */
41
+ export class Channel {
42
+ server;
43
+ clientId;
44
+ features = {};
45
+ #jobs = new Map();
46
+ /** Every socket this channel opened and that hasn't closed yet. */
47
+ #sockets = new Set();
48
+ /** Aborted by `close()`: ends handshakes and backoff waits. */
49
+ #life = new AbortController();
50
+ #socket;
51
+ #opening;
52
+ #reconnecting;
53
+ #closing;
54
+ /** The job whose prompt the server is running, for progress text without a prompt id. */
55
+ #running;
56
+ constructor(server, clientId) {
57
+ this.server = server;
58
+ this.clientId = clientId;
59
+ }
60
+ /** Whether `close()` was called. */
61
+ get closed() {
62
+ return this.#life.signal.aborted;
63
+ }
64
+ /**
65
+ * Waits until the websocket is open, opening it (once, for every caller) if it isn't. Rejects
66
+ * with the signal's reason when `signal` aborts, and with a {@link ClientError} when the
67
+ * channel is closed or the socket can't be opened.
68
+ */
69
+ async ready(signal) {
70
+ for (;;) {
71
+ if (this.closed)
72
+ throw closedError();
73
+ if (this.#reconnecting !== undefined) {
74
+ // oxlint-disable-next-line no-await-in-loop -- the state changes while it waits
75
+ await abortable(this.#reconnecting, signal);
76
+ continue;
77
+ }
78
+ if (this.#socket !== undefined)
79
+ return;
80
+ this.#opening ??= this.#open().finally(() => {
81
+ this.#opening = undefined;
82
+ });
83
+ // oxlint-disable-next-line no-await-in-loop -- the state changes while it waits
84
+ await abortable(this.#opening, signal);
85
+ }
86
+ }
87
+ /**
88
+ * Checks that the server is one the client supports: ComfyUI 0.26 or later by its version
89
+ * (when it reports one it can parse), and with the job API. Rejects with a {@link ClientError}.
90
+ */
91
+ async checkServer(signal) {
92
+ const stats = await send(this.server, { method: "GET", path: "system_stats", signal });
93
+ if (stats.ok) {
94
+ const body = await readObject(stats).catch(() => undefined);
95
+ const system = body?.["system"];
96
+ const version = isObject(system) ? system["comfyui_version"] : undefined;
97
+ if (typeof version === "string" && isOlder(version, MIN_VERSION)) {
98
+ throw new ClientError(`The server runs ComfyUI ${version}; @tscomfy/client needs ComfyUI ${MIN_VERSION.join(".")} or later`);
99
+ }
100
+ }
101
+ else {
102
+ await stats.body?.cancel();
103
+ }
104
+ const jobs = await send(this.server, {
105
+ method: "GET",
106
+ path: "api/jobs",
107
+ query: { limit: "1" },
108
+ signal,
109
+ });
110
+ await jobs.body?.cancel();
111
+ if (!jobs.ok) {
112
+ throw new ClientError(`The server has no job API (GET /api/jobs answered ${String(jobs.status)}); @tscomfy/client needs ComfyUI ${MIN_VERSION.join(".")} or later`, { status: jobs.status });
113
+ }
114
+ }
115
+ register(job) {
116
+ if (this.closed)
117
+ throw closedError();
118
+ this.#jobs.set(job.id, job);
119
+ }
120
+ unregister(job) {
121
+ if (this.#jobs.get(job.id) === job)
122
+ this.#jobs.delete(job.id);
123
+ if (this.#running === job)
124
+ this.#running = undefined;
125
+ }
126
+ /** Closes every socket and fails every open job; waits until the sockets have closed. */
127
+ close() {
128
+ this.#closing ??= this.#close();
129
+ return this.#closing;
130
+ }
131
+ async #close() {
132
+ this.#life.abort(closedError());
133
+ this.#socket = undefined;
134
+ const jobs = [...this.#jobs.values()];
135
+ this.#jobs.clear();
136
+ for (const job of jobs)
137
+ job.fail(new ClientError("The connection was closed"));
138
+ await Promise.all([...this.#sockets].map((socket) => closeSocket(socket)));
139
+ }
140
+ /** Opens a socket and negotiates the feature flags; the socket becomes the channel's. */
141
+ async #open() {
142
+ const url = endpoint(baseUrl(this.server), "ws", { clientId: this.clientId });
143
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
144
+ const shown = shownUrl(url);
145
+ const life = this.#life.signal;
146
+ const socket = this.server.webSocket?.(url.href) ?? new WebSocket(url.href);
147
+ socket.binaryType = "arraybuffer";
148
+ this.#sockets.add(socket);
149
+ socket.addEventListener("close", () => {
150
+ this.#sockets.delete(socket);
151
+ this.#dropped(socket);
152
+ });
153
+ await new Promise((resolve, reject) => {
154
+ const timer = setTimeout(() => {
155
+ fail(new ClientError(`${shown} didn't answer the websocket handshake in time`));
156
+ }, HANDSHAKE_MS);
157
+ const done = () => {
158
+ clearTimeout(timer);
159
+ life.removeEventListener("abort", aborted);
160
+ socket.removeEventListener("close", closed);
161
+ };
162
+ const fail = (error) => {
163
+ done();
164
+ socket.close();
165
+ reject(error);
166
+ };
167
+ const aborted = () => {
168
+ fail(closedError());
169
+ };
170
+ const closed = () => {
171
+ fail(new ClientError(`Could not open a websocket to ${shown}`));
172
+ };
173
+ if (life.aborted) {
174
+ aborted();
175
+ return;
176
+ }
177
+ life.addEventListener("abort", aborted, { once: true });
178
+ socket.addEventListener("close", closed);
179
+ socket.addEventListener("open", () => {
180
+ socket.send(stringify({ type: "feature_flags", data: CLIENT_FLAGS }));
181
+ });
182
+ let negotiated = false;
183
+ socket.addEventListener("message", (event) => {
184
+ if (!negotiated && typeof event.data === "string") {
185
+ const message = readMessage(event.data);
186
+ if (message?.type === "feature_flags") {
187
+ negotiated = true;
188
+ done();
189
+ this.features = message.data;
190
+ this.#socket = socket;
191
+ resolve();
192
+ return;
193
+ }
194
+ }
195
+ this.#receive(event.data);
196
+ });
197
+ });
198
+ }
199
+ #receive(data) {
200
+ if (typeof data === "string") {
201
+ const message = readMessage(data);
202
+ if (message !== undefined)
203
+ this.#dispatch(message.type, message.data);
204
+ return;
205
+ }
206
+ if (!(data instanceof ArrayBuffer))
207
+ return;
208
+ const frame = decodeFrame(data, this.features["supports_progress_text_metadata"] === true);
209
+ if (frame === undefined)
210
+ return;
211
+ const job = frame.promptId === undefined ? this.#running : this.#jobs.get(frame.promptId);
212
+ job?.frame(frame);
213
+ }
214
+ #dispatch(type, data) {
215
+ if (type === "status") {
216
+ for (const job of this.#jobs.values())
217
+ job.queueChanged();
218
+ return;
219
+ }
220
+ const promptId = data["prompt_id"];
221
+ if (typeof promptId !== "string")
222
+ return;
223
+ const job = this.#jobs.get(promptId);
224
+ if (job === undefined) {
225
+ // Another client's prompt, or one of ours that settled: it isn't running ours.
226
+ if (type === "execution_start")
227
+ this.#running = undefined;
228
+ return;
229
+ }
230
+ if (type === "execution_start")
231
+ this.#running = job;
232
+ if (type === "executing" && data["node"] === null && this.#running === job) {
233
+ this.#running = undefined;
234
+ }
235
+ job.message(type, data);
236
+ }
237
+ #dropped(socket) {
238
+ if (this.#socket !== socket)
239
+ return;
240
+ this.#socket = undefined;
241
+ this.#running = undefined;
242
+ // With no job open, the next submit reopens it.
243
+ if (this.closed || this.#jobs.size === 0)
244
+ return;
245
+ this.#reconnecting = this.#reconnect().finally(() => {
246
+ this.#reconnecting = undefined;
247
+ });
248
+ }
249
+ async #reconnect() {
250
+ const life = this.#life.signal;
251
+ for (const delay of BACKOFF_MS) {
252
+ // oxlint-disable-next-line no-await-in-loop -- attempts are sequential, with growing waits
253
+ await sleep(delay * (0.5 + Math.random() / 2), life);
254
+ if (life.aborted)
255
+ return;
256
+ try {
257
+ // oxlint-disable-next-line no-await-in-loop -- attempts are sequential, with growing waits
258
+ await this.#open();
259
+ for (const job of this.#jobs.values())
260
+ job.reconnected();
261
+ return;
262
+ }
263
+ catch {
264
+ // The next attempt, after a longer wait; none after close().
265
+ }
266
+ }
267
+ const error = new ClientError(`Lost the websocket to ${shownUrl(baseUrl(this.server))} and couldn't reopen it`);
268
+ const jobs = [...this.#jobs.values()];
269
+ this.#jobs.clear();
270
+ for (const job of jobs)
271
+ job.fail(error);
272
+ }
273
+ }
274
+ /** Closes a socket and waits for its close, a second at most. */
275
+ function closeSocket(socket) {
276
+ if (socket.readyState === CLOSED)
277
+ return Promise.resolve();
278
+ return new Promise((resolve) => {
279
+ const timer = setTimeout(resolve, 1000);
280
+ socket.addEventListener("close", () => {
281
+ clearTimeout(timer);
282
+ resolve();
283
+ });
284
+ socket.close();
285
+ });
286
+ }
287
+ /** Whether a `major.minor.patch` version (anything after it ignored) is below `minimum`. */
288
+ function isOlder(version, minimum) {
289
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version);
290
+ if (match === null)
291
+ return false;
292
+ const parts = [Number(match[1]), Number(match[2]), Number(match[3])];
293
+ for (const [index, part] of parts.entries()) {
294
+ const least = minimum[index] ?? 0;
295
+ if (part !== least)
296
+ return part < least;
297
+ }
298
+ return false;
299
+ }
300
+ /** A JSON message `{ type, data }`, read losslessly; anything else is ignored. */
301
+ function readMessage(text) {
302
+ const value = parse(text).value;
303
+ if (!isObject(value) || typeof value["type"] !== "string")
304
+ return undefined;
305
+ return { type: value["type"], data: isObject(value["data"]) ? value["data"] : {} };
306
+ }
307
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The server couldn't be reached, or answered in a way the protocol doesn't allow. Thrown by the
3
+ * HTTP functions and used to reject a job's outcome; what the server decides about a job (a
4
+ * rejection, a failure) is data instead, in the {@link Outcome}. The underlying error, if any, is
5
+ * the `cause`.
6
+ */
7
+ export declare class ClientError extends Error {
8
+ /** The HTTP status of the answer that caused it, when there was one. */
9
+ readonly status: number | undefined;
10
+ constructor(message: string, options?: {
11
+ readonly cause?: unknown;
12
+ readonly status?: number;
13
+ });
14
+ }
15
+ //# sourceMappingURL=client-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-error.d.ts","sourceRoot":"","sources":["../src/client-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAEpC,YACE,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,EAKrE;CACF"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The server couldn't be reached, or answered in a way the protocol doesn't allow. Thrown by the
3
+ * HTTP functions and used to reject a job's outcome; what the server decides about a job (a
4
+ * rejection, a failure) is data instead, in the {@link Outcome}. The underlying error, if any, is
5
+ * the `cause`.
6
+ */
7
+ export class ClientError extends Error {
8
+ /** The HTTP status of the answer that caused it, when there was one. */
9
+ status;
10
+ constructor(message, options = {}) {
11
+ super(message, { cause: options.cause });
12
+ this.name = "ClientError";
13
+ this.status = options.status;
14
+ }
15
+ }
16
+ //# sourceMappingURL=client-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-error.js","sourceRoot":"","sources":["../src/client-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,wEAAwE;IAC/D,MAAM,CAAqB;IAEpC,YACE,OAAe,EACf,OAAO,GAA2D,EAAE;QAEpE,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,33 @@
1
+ import type { JsonObject } from "@tscomfy/core";
2
+ import type { Server } from "./server.ts";
3
+ /**
4
+ * An open connection to a server: a {@link Server} with a websocket that delivers the events of
5
+ * the jobs submitted through it. Several jobs share it. Close it when done: an open websocket
6
+ * keeps a Node or Bun process running.
7
+ */
8
+ export interface Connection extends Server {
9
+ /** The id the server sends this connection's events to; fresh for every connection. */
10
+ readonly clientId: string;
11
+ /** The server's feature flags, as it answered them when the connection opened. */
12
+ readonly features: JsonObject;
13
+ /**
14
+ * Closes the websocket. Jobs still open reject their outcome with a {@link ClientError}; they
15
+ * keep running on the server.
16
+ */
17
+ close(): Promise<void>;
18
+ }
19
+ /** Options for {@link connect}. */
20
+ export interface ConnectOptions {
21
+ /** Aborts opening the connection; rejects with the signal's reason. */
22
+ readonly signal?: AbortSignal;
23
+ }
24
+ /**
25
+ * Opens a connection: a websocket with a fresh client id, which announces
26
+ * `supports_preview_metadata` (so previews say which job and node they belong to) and waits for
27
+ * the server's feature flags. Also checks that the server is ComfyUI 0.26 or later with the job
28
+ * API. Rejects with a {@link ClientError} when the server can't be reached or is too old, and
29
+ * with the signal's reason when `signal` aborts; either way nothing is left open. Throws a
30
+ * `TypeError` for an invalid `server.url`.
31
+ */
32
+ export declare function connect(server: Server, options?: ConnectOptions): Promise<Connection>;
33
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C;;;;GAIG;AACH,MAAM,WAAW,UAAW,SAAQ,MAAM;IACxC,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAqB/F"}
@@ -0,0 +1,35 @@
1
+ import { attach, Channel } from "./channel.js";
2
+ import { baseUrl } from "./url.js";
3
+ import { randomUuid } from "./uuid.js";
4
+ /**
5
+ * Opens a connection: a websocket with a fresh client id, which announces
6
+ * `supports_preview_metadata` (so previews say which job and node they belong to) and waits for
7
+ * the server's feature flags. Also checks that the server is ComfyUI 0.26 or later with the job
8
+ * API. Rejects with a {@link ClientError} when the server can't be reached or is too old, and
9
+ * with the signal's reason when `signal` aborts; either way nothing is left open. Throws a
10
+ * `TypeError` for an invalid `server.url`.
11
+ */
12
+ export async function connect(server, options = {}) {
13
+ baseUrl(server);
14
+ const channel = new Channel(server, randomUuid());
15
+ try {
16
+ await channel.ready(options.signal);
17
+ await channel.checkServer(options.signal);
18
+ }
19
+ catch (error) {
20
+ await channel.close();
21
+ throw error;
22
+ }
23
+ const connection = Object.freeze({
24
+ url: server.url,
25
+ ...(server.headers === undefined ? {} : { headers: server.headers }),
26
+ ...(server.fetch === undefined ? {} : { fetch: server.fetch }),
27
+ ...(server.webSocket === undefined ? {} : { webSocket: server.webSocket }),
28
+ clientId: channel.clientId,
29
+ features: channel.features,
30
+ close: () => channel.close(),
31
+ });
32
+ attach(connection, channel);
33
+ return connection;
34
+ }
35
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAyBvC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,MAAc,EAAE,OAAO,GAAmB,EAAE;IACxE,OAAO,CAAC,MAAM,CAAC,CAAC;IAChB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,UAAU,GAAe,MAAM,CAAC,MAAM,CAAC;QAC3C,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;QACpE,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAC9D,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;QAC1E,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;KAC7B,CAAC,CAAC;IACH,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC5B,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { JobEvent } from "./events.ts";
2
+ /**
3
+ * A job's events, for one consumer. Buffered from the start, so none is missed before the
4
+ * consumer starts reading; while it lags, a newer progress or preview of a node replaces the
5
+ * unread one where it stands, so the buffer stays bounded by the number of nodes and keeps its
6
+ * order.
7
+ */
8
+ export declare class EventQueue implements AsyncIterable<JobEvent> {
9
+ #private;
10
+ /** Adds an event, unless the queue has ended or its consumer stopped. */
11
+ push(event: JobEvent): void;
12
+ /** Ends the queue: the consumer reads what is buffered, then stops. */
13
+ end(): void;
14
+ [Symbol.asyncIterator](): AsyncIterator<JobEvent, undefined>;
15
+ }
16
+ //# sourceMappingURL=event-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-queue.d.ts","sourceRoot":"","sources":["../src/event-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAU5C;;;;;GAKG;AACH,qBAAa,UAAW,YAAW,aAAa,CAAC,QAAQ,CAAC;;IAOxD,yEAAyE;IACzE,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAiB1B;IAED,uEAAuE;IACvE,GAAG,IAAI,IAAI,CAGV;IAED,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAmB3D;CACF"}