Package: @saintno/comfyui-sdk — TypeScript SDK for ComfyUI server communication.
Runtime: Bun (test runner, bundler, package manager). No Node.js or npm.
Output: Dual bundle — build/index.esm.js (browser) + build/index.cjs (Node), with build/index.d.ts.
bun run build # Build dist bundles + types
bun test # Unit tests only (integration auto-skipped)
bun run test:integration # Full suite against http://192.168.14.93:8188
COMFYUI_HOST=http://host:8188 bun test # Run with custom serversrc/
├── client.ts # ComfyApi — main entry point, extends EventTarget
├── socket.ts # WebSocketClient — auto-picks native ws vs Node ws
├── call-wrapper.ts # CallWrapper — runs workflows, emits event callbacks
├── prompt-builder.ts # PromptBuilder — fluent API to mutate workflow JSON
├── pool.ts # ComfyPool — multi-client job distribution
├── tools.ts # Pure utility functions (seed, randomInt, delay, encodePath)
├── contansts.ts # Constants (polling interval, timeouts)
├── features/
│ ├── abstract.ts # AbstractFeature base class
│ ├── manager.ts # ManagerFeature — ComfyUI-Manager extension (may not be installed)
│ └── monitoring.ts # MonitoringFeature — Crystools extension (may not be installed)
└── types/
├── api.ts # Core types (QueueResponse, SystemStats, ServerFeatures, etc.)
├── event.ts # Event maps (TComfyAPIEventMap, TComfyPoolEventMap)
├── error.ts # Error types
├── manager.ts # Manager-specific types
├── sampler.ts # Sampler/scheduler name union types
└── tool.ts # PromptBuilder config types
All client methods use a this.log(fnName, message, data) pattern that dispatches a "log" event instead of console.log. Errors are logged then re-thrown.
fetchApi(route, options) is the internal HTTP wrapper. It auto-injects credentials (basic/bearer/custom headers) and handles host concatenation. Never call fetch directly.
Extensions live under api.ext.manager and api.ext.monitor. Each extends AbstractFeature and must implement checkSupported() and destroy(). Features may not be installed on every server — always check isSupported before calling methods.
Uses immutable-style chaining. Methods like bypass(), input(), setInputNode() return a modified clone. The original instance is not mutated (except inputRaw()).
If WebSocket connection fails, createSocket() falls back to HTTP polling via setupPollingFallback().
ping, pollStatus, getSystemStats, getExtensions, getEmbeddings, getNodeDefs, getCheckpoints, getLoras, getSamplerInfo, getQueue, queuePrompt, getHistory, getHistories, getSettings, getSetting, storeSetting, storeSettings, getUserData, storeUserData, deleteUserData, moveUserData, interrupt, freeMemory, getImage, getPathImage, uploadImage, uploadMask, getTerminalLogs, setTerminalSubscription, getUserConfig, createUser, getFeatures, getModelTypes, getModels, getWorkflowTemplates, getViewMetadata, clearHistory, manageQueue, listUserDataV2
| Method | Replacement |
|---|---|
listUserData() |
listUserDataV2() — returns structured entries with type/size/modified |
getModelFolders() |
getModelTypes() — stable /models endpoint |
getModelFiles(folder) |
getModels(folder) — stable /models/{folder} endpoint |
getModelPreview(), getModelPreviewUrl() — /experiment/models/preview/* was removed upstream.
setTerminalSubscription — /internal/logs/subscribe may return 500 on some deployments.
- Framework:
bun:test(no jest/vitest) - Unit tests:
test/*.spec.ts— no network, always run - Integration tests:
test/*.integration.spec.ts— require live ComfyUI server
Integration tests use describe.skipIf(!process.env.COMFYUI_HOST()) at the top level. Without COMFYUI_HOST, they are skipped, not failed. Never set describe.skipIf per-test; only at the describe block level.
import { getTestHost, createTestClient, waitForClient } from "./fixtures";getTestHost()— returnsprocess.env.COMFYUI_HOST || ""createTestClient(clientId?)— createsnew ComfyApi(getTestHost(), clientId)waitForClient(client)— callsclient.init(5, 2000).waitForReady()
- Default: bun:test default (5s)
- Integration suites: no describe-level timeout (set per-test via 3rd arg)
- Workflow execution tests:
it("name", fn, 120_000) - Pool tests:
it("name", fn, 30_000)
describe.skipIf(!getTestHost())("ComfyApi Integration", () => {
let api: ComfyApi;
beforeAll(async () => {
api = createTestClient();
await waitForClient(api);
});
afterAll(() => {
api.destroy();
});
});One client per suite. init() in beforeAll, destroy() in afterAll.
- Never use
describe.skipIfresult asit—describe.skipIfreturns a describe function, not anitfunction. Using it asskip("test name", fn)creates nested describe blocks, not tests. - WebSocket events are fire-and-forget — Don't rely on
on("log")catching events from synchronous methods. UseaddEventListenerdirectly for event tests. - History polling —
getHistory(promptId)may return nothing immediately. Poll with retries and timeouts. - Pool batch —
pool.batch()is designed for workflow jobs, not arbitrary API calls. Use simplepool.run()for non-workflow operations. describe.skipIfcauses LSP dead-code warnings — The type checker treats skipped blocks as unreachable. These are false positives; ignore them.
- No comments — Code should be self-documenting
- No emojis — Unless explicitly requested
- Imports — Use
import { X } from "src/module"(path alias), not relative../src/module - Type imports — Use
import("path").Typefor inline type-only imports to avoid circular deps - Error throwing — Throw
Errorobjects, not strings - Naming — Classes are PascalCase, methods are camelCase, constants are UPPER_SNAKE_CASE
- Add types to
src/types/api.ts - Add method to
src/client.tsfollowing existing patterns (fetchApi+this.log) - Mark experimental methods with
@experimentalJSDoc - Add integration test gated by
describe.skipIf(!getTestHost()) - Run
bun testto verify unit tests pass - Run
COMFYUI_HOST=http://192.168.14.93:8188 bun testto verify integration - Verify endpoint exists on the server before adding the test