@useknockout/node 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 useknockout
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,180 @@
1
+ # @useknockout/node
2
+
3
+ > Official TypeScript / Node.js client for [useknockout](https://github.com/useknockout/api) — state-of-the-art background removal API.
4
+
5
+ - **Zero runtime dependencies** — uses the native `fetch` built into Node 18+
6
+ - **First-class TypeScript** — full types, no `any`s in the public API
7
+ - **Works anywhere `fetch` works** — Node, Bun, Deno, Vercel/Cloudflare Workers, serverless
8
+ - **MIT licensed**
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @useknockout/node
16
+ # or
17
+ pnpm add @useknockout/node
18
+ # or
19
+ yarn add @useknockout/node
20
+ ```
21
+
22
+ Requires **Node 18+** (for global `fetch` and `FormData`).
23
+
24
+ ## Quick start
25
+
26
+ ```ts
27
+ import { writeFile } from "node:fs/promises";
28
+ import { Knockout } from "@useknockout/node";
29
+
30
+ const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
31
+
32
+ // 1. From a local file path
33
+ const png = await client.remove({ file: "./input.jpg" });
34
+ await writeFile("./output.png", png);
35
+
36
+ // 2. From a Buffer (e.g. uploaded via multer)
37
+ const buf = await fs.readFile("./input.jpg");
38
+ const out = await client.remove({ file: buf, filename: "input.jpg" });
39
+
40
+ // 3. From a remote URL
41
+ const url = await client.removeUrl({ url: "https://example.com/cat.jpg" });
42
+ ```
43
+
44
+ ## API
45
+
46
+ ### `new Knockout(options?)`
47
+
48
+ | Option | Type | Default | Description |
49
+ |---|---|---|---|
50
+ | `token` | `string` | — | Bearer token. Required unless self-hosting without auth. |
51
+ | `baseUrl` | `string` | `https://useknockout--api.modal.run` | Override the API endpoint. |
52
+ | `timeoutMs` | `number` | `60_000` | Per-request timeout. |
53
+ | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch (for edge runtimes or polyfills). |
54
+
55
+ ### `client.remove(input)`
56
+
57
+ Remove the background from a local file or in-memory buffer.
58
+
59
+ | Field | Type | Description |
60
+ |---|---|---|
61
+ | `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | File path or raw image bytes. |
62
+ | `filename` | `string?` | Optional override — auto-inferred from path if omitted. |
63
+ | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
64
+
65
+ Returns: `Buffer` of the processed image (PNG or WebP with transparent alpha).
66
+
67
+ ### `client.removeUrl(input)`
68
+
69
+ Remove the background from a remote image URL.
70
+
71
+ | Field | Type | Description |
72
+ |---|---|---|
73
+ | `url` | `string` | Remote image URL. |
74
+ | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
75
+
76
+ Returns: `Buffer` of the processed image.
77
+
78
+ ### `client.health()`
79
+
80
+ Returns: `Promise<{ status: string; model: string }>`. No auth required.
81
+
82
+ ### `KnockoutError`
83
+
84
+ Thrown on any non-2xx response. Fields:
85
+
86
+ - `status` — HTTP status code
87
+ - `code` — `"auth" | "rate_limit" | "bad_request" | "payload_too_large" | "server" | "unknown"`
88
+ - `body` — raw response body string
89
+
90
+ ```ts
91
+ import { KnockoutError } from "@useknockout/node";
92
+
93
+ try {
94
+ await client.remove({ file: "./huge.jpg" });
95
+ } catch (err) {
96
+ if (err instanceof KnockoutError && err.code === "payload_too_large") {
97
+ // retry with a resized image
98
+ }
99
+ throw err;
100
+ }
101
+ ```
102
+
103
+ ## Framework examples
104
+
105
+ ### Next.js App Router
106
+
107
+ ```ts
108
+ // app/api/remove/route.ts
109
+ import { Knockout } from "@useknockout/node";
110
+
111
+ const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
112
+
113
+ export async function POST(req: Request) {
114
+ const form = await req.formData();
115
+ const file = form.get("file") as File;
116
+ const buf = Buffer.from(await file.arrayBuffer());
117
+
118
+ const png = await client.remove({ file: buf, filename: file.name });
119
+
120
+ return new Response(new Uint8Array(png), {
121
+ headers: { "Content-Type": "image/png" },
122
+ });
123
+ }
124
+ ```
125
+
126
+ ### Express
127
+
128
+ ```ts
129
+ import express from "express";
130
+ import multer from "multer";
131
+ import { Knockout } from "@useknockout/node";
132
+
133
+ const app = express();
134
+ const upload = multer();
135
+ const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
136
+
137
+ app.post("/remove", upload.single("file"), async (req, res) => {
138
+ const png = await client.remove({
139
+ file: req.file!.buffer,
140
+ filename: req.file!.originalname,
141
+ });
142
+ res.type("image/png").send(png);
143
+ });
144
+ ```
145
+
146
+ ### Cloudflare Workers / Vercel Edge
147
+
148
+ ```ts
149
+ import { Knockout } from "@useknockout/node";
150
+
151
+ const client = new Knockout({ token: env.KNOCKOUT_TOKEN });
152
+
153
+ export default {
154
+ async fetch(req: Request) {
155
+ const { searchParams } = new URL(req.url);
156
+ const imageUrl = searchParams.get("url")!;
157
+ const png = await client.removeUrl({ url: imageUrl });
158
+ return new Response(new Uint8Array(png), {
159
+ headers: { "Content-Type": "image/png" },
160
+ });
161
+ },
162
+ };
163
+ ```
164
+
165
+ ## Self-hosting
166
+
167
+ Point the SDK at your own Modal deployment:
168
+
169
+ ```ts
170
+ const client = new Knockout({
171
+ token: "your-self-hosted-token",
172
+ baseUrl: "https://YOUR_WORKSPACE--api.modal.run",
173
+ });
174
+ ```
175
+
176
+ See [useknockout/api](https://github.com/useknockout/api) for the Modal deployment.
177
+
178
+ ## License
179
+
180
+ MIT — see [LICENSE](./LICENSE).
package/dist/index.cjs ADDED
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var promises = require('fs/promises');
6
+ var path = require('path');
7
+
8
+ // src/index.ts
9
+ var DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
10
+ var SDK_VERSION = "0.0.1";
11
+ var KnockoutError = class _KnockoutError extends Error {
12
+ status;
13
+ code;
14
+ body;
15
+ constructor(status, body) {
16
+ const code = _KnockoutError.classify(status);
17
+ super(`Knockout API error ${status} (${code}): ${body || "no body"}`);
18
+ this.name = "KnockoutError";
19
+ this.status = status;
20
+ this.code = code;
21
+ this.body = body;
22
+ }
23
+ static classify(status) {
24
+ if (status === 401 || status === 403) return "auth";
25
+ if (status === 429) return "rate_limit";
26
+ if (status === 413) return "payload_too_large";
27
+ if (status >= 400 && status < 500) return "bad_request";
28
+ if (status >= 500) return "server";
29
+ return "unknown";
30
+ }
31
+ };
32
+ var Knockout = class {
33
+ baseUrl;
34
+ token;
35
+ timeoutMs;
36
+ fetchImpl;
37
+ constructor(options = {}) {
38
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
39
+ this.token = options.token;
40
+ this.timeoutMs = options.timeoutMs ?? 6e4;
41
+ const fetchRef = options.fetch ?? globalThis.fetch;
42
+ if (!fetchRef) {
43
+ throw new Error(
44
+ "Global fetch is unavailable. Provide `options.fetch` or use Node 18+."
45
+ );
46
+ }
47
+ this.fetchImpl = fetchRef.bind(globalThis);
48
+ }
49
+ /** Hit GET /health — no auth required. */
50
+ async health() {
51
+ const res = await this.request("GET", "/health");
52
+ const body = await res.text();
53
+ if (!res.ok) throw new KnockoutError(res.status, body);
54
+ return JSON.parse(body);
55
+ }
56
+ /**
57
+ * Remove the background from an image, returning the cleaned PNG/WebP bytes.
58
+ *
59
+ * @example
60
+ * const png = await client.remove({ file: "./input.jpg" });
61
+ */
62
+ async remove(input) {
63
+ const format = input.format ?? "png";
64
+ const { blob, filename } = await toBlob(input);
65
+ const form = new FormData();
66
+ form.append("file", blob, filename);
67
+ const res = await this.request("POST", `/remove?format=${format}`, {
68
+ body: form
69
+ });
70
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
71
+ return Buffer.from(await res.arrayBuffer());
72
+ }
73
+ /**
74
+ * Remove the background from a remote URL, returning the cleaned PNG/WebP bytes.
75
+ *
76
+ * @example
77
+ * const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
78
+ */
79
+ async removeUrl(input) {
80
+ const format = input.format ?? "png";
81
+ const res = await this.request("POST", "/remove-url", {
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ url: input.url, format })
84
+ });
85
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
86
+ return Buffer.from(await res.arrayBuffer());
87
+ }
88
+ async request(method, path, init = {}) {
89
+ const url = `${this.baseUrl}${path}`;
90
+ const headers = {
91
+ "User-Agent": `useknockout-node/${SDK_VERSION}`,
92
+ ...init.headers ?? {}
93
+ };
94
+ if (this.token) headers["Authorization"] = `Bearer ${this.token}`;
95
+ const controller = new AbortController();
96
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
97
+ try {
98
+ return await this.fetchImpl(url, {
99
+ method,
100
+ headers,
101
+ body: init.body,
102
+ signal: controller.signal
103
+ });
104
+ } finally {
105
+ clearTimeout(timer);
106
+ }
107
+ }
108
+ };
109
+ async function toBlob(input) {
110
+ const { file } = input;
111
+ const filename = input.filename ?? inferFilename(file);
112
+ if (typeof file === "string") {
113
+ const data = await promises.readFile(file);
114
+ return { blob: new Blob([new Uint8Array(data)]), filename };
115
+ }
116
+ if (file instanceof Blob) {
117
+ return { blob: file, filename };
118
+ }
119
+ if (file instanceof ArrayBuffer) {
120
+ return { blob: new Blob([new Uint8Array(file)]), filename };
121
+ }
122
+ if (file instanceof Uint8Array) {
123
+ return { blob: new Blob([new Uint8Array(file)]), filename };
124
+ }
125
+ if (Buffer.isBuffer(file)) {
126
+ return {
127
+ blob: new Blob([new Uint8Array(file)]),
128
+ filename
129
+ };
130
+ }
131
+ throw new TypeError("Unsupported `file` input. Provide a path, Buffer, Blob, ArrayBuffer, or Uint8Array.");
132
+ }
133
+ function inferFilename(file) {
134
+ if (typeof file === "string") return path.basename(file) || "image";
135
+ return "image";
136
+ }
137
+ var index_default = Knockout;
138
+
139
+ exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
140
+ exports.Knockout = Knockout;
141
+ exports.KnockoutError = KnockoutError;
142
+ exports.default = index_default;
143
+ //# sourceMappingURL=index.cjs.map
144
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["readFile","basename"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @useknockout/node — official TypeScript / Node.js client for the useknockout API.\n *\n * Quick start:\n *\n * import { Knockout } from \"@useknockout/node\";\n *\n * const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });\n * const png = await client.remove({ file: \"./input.jpg\" }); // Buffer of PNG bytes\n * await writeFile(\"out.png\", png);\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { basename } from \"node:path\";\n\nexport const DEFAULT_BASE_URL = \"https://useknockout--api.modal.run\";\nconst SDK_VERSION = \"0.0.1\";\n\nexport type OutputFormat = \"png\" | \"webp\";\n\nexport interface KnockoutOptions {\n /** API bearer token. Required unless your self-hosted instance has no auth. */\n token?: string;\n /** Override the API base URL. Defaults to the hosted endpoint. */\n baseUrl?: string;\n /** Per-request timeout in milliseconds. Default 60_000. */\n timeoutMs?: number;\n /** Custom fetch (useful for edge runtimes / polyfills). Defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface RemoveInput {\n /** Local file path, Buffer, Blob, or ArrayBuffer of the image. */\n file: string | Buffer | Blob | ArrayBuffer | Uint8Array;\n /** Optional filename — inferred from path when `file` is a string. */\n filename?: string;\n /** Output format. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface RemoveUrlInput {\n /** Remote URL of the image to process. */\n url: string;\n /** Output format. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface HealthResponse {\n status: string;\n model: string;\n}\n\n/**\n * Error thrown when the API returns a non-2xx response.\n */\nexport class KnockoutError extends Error {\n public readonly status: number;\n public readonly code: \"auth\" | \"rate_limit\" | \"bad_request\" | \"payload_too_large\" | \"server\" | \"unknown\";\n public readonly body: string;\n\n constructor(status: number, body: string) {\n const code = KnockoutError.classify(status);\n super(`Knockout API error ${status} (${code}): ${body || \"no body\"}`);\n this.name = \"KnockoutError\";\n this.status = status;\n this.code = code;\n this.body = body;\n }\n\n private static classify(status: number): KnockoutError[\"code\"] {\n if (status === 401 || status === 403) return \"auth\";\n if (status === 429) return \"rate_limit\";\n if (status === 413) return \"payload_too_large\";\n if (status >= 400 && status < 500) return \"bad_request\";\n if (status >= 500) return \"server\";\n return \"unknown\";\n }\n}\n\n/**\n * useknockout API client.\n *\n * All methods return a `Buffer` (Node) of the processed image bytes.\n * Use `.toString(\"base64\")` or `writeFile(path, buf)` to persist.\n */\nexport class Knockout {\n private readonly baseUrl: string;\n private readonly token: string | undefined;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: KnockoutOptions = {}) {\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.token = options.token;\n this.timeoutMs = options.timeoutMs ?? 60_000;\n const fetchRef = options.fetch ?? globalThis.fetch;\n if (!fetchRef) {\n throw new Error(\n \"Global fetch is unavailable. Provide `options.fetch` or use Node 18+.\"\n );\n }\n this.fetchImpl = fetchRef.bind(globalThis);\n }\n\n /** Hit GET /health — no auth required. */\n async health(): Promise<HealthResponse> {\n const res = await this.request(\"GET\", \"/health\");\n const body = await res.text();\n if (!res.ok) throw new KnockoutError(res.status, body);\n return JSON.parse(body) as HealthResponse;\n }\n\n /**\n * Remove the background from an image, returning the cleaned PNG/WebP bytes.\n *\n * @example\n * const png = await client.remove({ file: \"./input.jpg\" });\n */\n async remove(input: RemoveInput): Promise<Buffer> {\n const format: OutputFormat = input.format ?? \"png\";\n const { blob, filename } = await toBlob(input);\n\n const form = new FormData();\n form.append(\"file\", blob, filename);\n\n const res = await this.request(\"POST\", `/remove?format=${format}`, {\n body: form,\n });\n\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return Buffer.from(await res.arrayBuffer());\n }\n\n /**\n * Remove the background from a remote URL, returning the cleaned PNG/WebP bytes.\n *\n * @example\n * const png = await client.removeUrl({ url: \"https://example.com/cat.jpg\" });\n */\n async removeUrl(input: RemoveUrlInput): Promise<Buffer> {\n const format: OutputFormat = input.format ?? \"png\";\n const res = await this.request(\"POST\", \"/remove-url\", {\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ url: input.url, format }),\n });\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return Buffer.from(await res.arrayBuffer());\n }\n\n private async request(\n method: \"GET\" | \"POST\",\n path: string,\n init: { headers?: Record<string, string>; body?: BodyInit } = {}\n ): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = {\n \"User-Agent\": `useknockout-node/${SDK_VERSION}`,\n ...(init.headers ?? {}),\n };\n if (this.token) headers[\"Authorization\"] = `Bearer ${this.token}`;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await this.fetchImpl(url, {\n method,\n headers,\n body: init.body,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\nasync function toBlob(\n input: RemoveInput\n): Promise<{ blob: Blob; filename: string }> {\n const { file } = input;\n const filename = input.filename ?? inferFilename(file);\n\n if (typeof file === \"string\") {\n const data = await readFile(file);\n return { blob: new Blob([new Uint8Array(data)]), filename };\n }\n if (file instanceof Blob) {\n return { blob: file, filename };\n }\n if (file instanceof ArrayBuffer) {\n return { blob: new Blob([new Uint8Array(file)]), filename };\n }\n if (file instanceof Uint8Array) {\n return { blob: new Blob([new Uint8Array(file)]), filename };\n }\n if (Buffer.isBuffer(file)) {\n return {\n blob: new Blob([new Uint8Array(file)]),\n filename,\n };\n }\n throw new TypeError(\"Unsupported `file` input. Provide a path, Buffer, Blob, ArrayBuffer, or Uint8Array.\");\n}\n\nfunction inferFilename(file: RemoveInput[\"file\"]): string {\n if (typeof file === \"string\") return basename(file) || \"image\";\n return \"image\";\n}\n\nexport default Knockout;\n"]}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @useknockout/node — official TypeScript / Node.js client for the useknockout API.
3
+ *
4
+ * Quick start:
5
+ *
6
+ * import { Knockout } from "@useknockout/node";
7
+ *
8
+ * const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
9
+ * const png = await client.remove({ file: "./input.jpg" }); // Buffer of PNG bytes
10
+ * await writeFile("out.png", png);
11
+ */
12
+ declare const DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
13
+ type OutputFormat = "png" | "webp";
14
+ interface KnockoutOptions {
15
+ /** API bearer token. Required unless your self-hosted instance has no auth. */
16
+ token?: string;
17
+ /** Override the API base URL. Defaults to the hosted endpoint. */
18
+ baseUrl?: string;
19
+ /** Per-request timeout in milliseconds. Default 60_000. */
20
+ timeoutMs?: number;
21
+ /** Custom fetch (useful for edge runtimes / polyfills). Defaults to global fetch. */
22
+ fetch?: typeof fetch;
23
+ }
24
+ interface RemoveInput {
25
+ /** Local file path, Buffer, Blob, or ArrayBuffer of the image. */
26
+ file: string | Buffer | Blob | ArrayBuffer | Uint8Array;
27
+ /** Optional filename — inferred from path when `file` is a string. */
28
+ filename?: string;
29
+ /** Output format. Default "png". */
30
+ format?: OutputFormat;
31
+ }
32
+ interface RemoveUrlInput {
33
+ /** Remote URL of the image to process. */
34
+ url: string;
35
+ /** Output format. Default "png". */
36
+ format?: OutputFormat;
37
+ }
38
+ interface HealthResponse {
39
+ status: string;
40
+ model: string;
41
+ }
42
+ /**
43
+ * Error thrown when the API returns a non-2xx response.
44
+ */
45
+ declare class KnockoutError extends Error {
46
+ readonly status: number;
47
+ readonly code: "auth" | "rate_limit" | "bad_request" | "payload_too_large" | "server" | "unknown";
48
+ readonly body: string;
49
+ constructor(status: number, body: string);
50
+ private static classify;
51
+ }
52
+ /**
53
+ * useknockout API client.
54
+ *
55
+ * All methods return a `Buffer` (Node) of the processed image bytes.
56
+ * Use `.toString("base64")` or `writeFile(path, buf)` to persist.
57
+ */
58
+ declare class Knockout {
59
+ private readonly baseUrl;
60
+ private readonly token;
61
+ private readonly timeoutMs;
62
+ private readonly fetchImpl;
63
+ constructor(options?: KnockoutOptions);
64
+ /** Hit GET /health — no auth required. */
65
+ health(): Promise<HealthResponse>;
66
+ /**
67
+ * Remove the background from an image, returning the cleaned PNG/WebP bytes.
68
+ *
69
+ * @example
70
+ * const png = await client.remove({ file: "./input.jpg" });
71
+ */
72
+ remove(input: RemoveInput): Promise<Buffer>;
73
+ /**
74
+ * Remove the background from a remote URL, returning the cleaned PNG/WebP bytes.
75
+ *
76
+ * @example
77
+ * const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
78
+ */
79
+ removeUrl(input: RemoveUrlInput): Promise<Buffer>;
80
+ private request;
81
+ }
82
+
83
+ export { DEFAULT_BASE_URL, type HealthResponse, Knockout, KnockoutError, type KnockoutOptions, type OutputFormat, type RemoveInput, type RemoveUrlInput, Knockout as default };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @useknockout/node — official TypeScript / Node.js client for the useknockout API.
3
+ *
4
+ * Quick start:
5
+ *
6
+ * import { Knockout } from "@useknockout/node";
7
+ *
8
+ * const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
9
+ * const png = await client.remove({ file: "./input.jpg" }); // Buffer of PNG bytes
10
+ * await writeFile("out.png", png);
11
+ */
12
+ declare const DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
13
+ type OutputFormat = "png" | "webp";
14
+ interface KnockoutOptions {
15
+ /** API bearer token. Required unless your self-hosted instance has no auth. */
16
+ token?: string;
17
+ /** Override the API base URL. Defaults to the hosted endpoint. */
18
+ baseUrl?: string;
19
+ /** Per-request timeout in milliseconds. Default 60_000. */
20
+ timeoutMs?: number;
21
+ /** Custom fetch (useful for edge runtimes / polyfills). Defaults to global fetch. */
22
+ fetch?: typeof fetch;
23
+ }
24
+ interface RemoveInput {
25
+ /** Local file path, Buffer, Blob, or ArrayBuffer of the image. */
26
+ file: string | Buffer | Blob | ArrayBuffer | Uint8Array;
27
+ /** Optional filename — inferred from path when `file` is a string. */
28
+ filename?: string;
29
+ /** Output format. Default "png". */
30
+ format?: OutputFormat;
31
+ }
32
+ interface RemoveUrlInput {
33
+ /** Remote URL of the image to process. */
34
+ url: string;
35
+ /** Output format. Default "png". */
36
+ format?: OutputFormat;
37
+ }
38
+ interface HealthResponse {
39
+ status: string;
40
+ model: string;
41
+ }
42
+ /**
43
+ * Error thrown when the API returns a non-2xx response.
44
+ */
45
+ declare class KnockoutError extends Error {
46
+ readonly status: number;
47
+ readonly code: "auth" | "rate_limit" | "bad_request" | "payload_too_large" | "server" | "unknown";
48
+ readonly body: string;
49
+ constructor(status: number, body: string);
50
+ private static classify;
51
+ }
52
+ /**
53
+ * useknockout API client.
54
+ *
55
+ * All methods return a `Buffer` (Node) of the processed image bytes.
56
+ * Use `.toString("base64")` or `writeFile(path, buf)` to persist.
57
+ */
58
+ declare class Knockout {
59
+ private readonly baseUrl;
60
+ private readonly token;
61
+ private readonly timeoutMs;
62
+ private readonly fetchImpl;
63
+ constructor(options?: KnockoutOptions);
64
+ /** Hit GET /health — no auth required. */
65
+ health(): Promise<HealthResponse>;
66
+ /**
67
+ * Remove the background from an image, returning the cleaned PNG/WebP bytes.
68
+ *
69
+ * @example
70
+ * const png = await client.remove({ file: "./input.jpg" });
71
+ */
72
+ remove(input: RemoveInput): Promise<Buffer>;
73
+ /**
74
+ * Remove the background from a remote URL, returning the cleaned PNG/WebP bytes.
75
+ *
76
+ * @example
77
+ * const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
78
+ */
79
+ removeUrl(input: RemoveUrlInput): Promise<Buffer>;
80
+ private request;
81
+ }
82
+
83
+ export { DEFAULT_BASE_URL, type HealthResponse, Knockout, KnockoutError, type KnockoutOptions, type OutputFormat, type RemoveInput, type RemoveUrlInput, Knockout as default };
package/dist/index.js ADDED
@@ -0,0 +1,137 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { basename } from 'path';
3
+
4
+ // src/index.ts
5
+ var DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
6
+ var SDK_VERSION = "0.0.1";
7
+ var KnockoutError = class _KnockoutError extends Error {
8
+ status;
9
+ code;
10
+ body;
11
+ constructor(status, body) {
12
+ const code = _KnockoutError.classify(status);
13
+ super(`Knockout API error ${status} (${code}): ${body || "no body"}`);
14
+ this.name = "KnockoutError";
15
+ this.status = status;
16
+ this.code = code;
17
+ this.body = body;
18
+ }
19
+ static classify(status) {
20
+ if (status === 401 || status === 403) return "auth";
21
+ if (status === 429) return "rate_limit";
22
+ if (status === 413) return "payload_too_large";
23
+ if (status >= 400 && status < 500) return "bad_request";
24
+ if (status >= 500) return "server";
25
+ return "unknown";
26
+ }
27
+ };
28
+ var Knockout = class {
29
+ baseUrl;
30
+ token;
31
+ timeoutMs;
32
+ fetchImpl;
33
+ constructor(options = {}) {
34
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
35
+ this.token = options.token;
36
+ this.timeoutMs = options.timeoutMs ?? 6e4;
37
+ const fetchRef = options.fetch ?? globalThis.fetch;
38
+ if (!fetchRef) {
39
+ throw new Error(
40
+ "Global fetch is unavailable. Provide `options.fetch` or use Node 18+."
41
+ );
42
+ }
43
+ this.fetchImpl = fetchRef.bind(globalThis);
44
+ }
45
+ /** Hit GET /health — no auth required. */
46
+ async health() {
47
+ const res = await this.request("GET", "/health");
48
+ const body = await res.text();
49
+ if (!res.ok) throw new KnockoutError(res.status, body);
50
+ return JSON.parse(body);
51
+ }
52
+ /**
53
+ * Remove the background from an image, returning the cleaned PNG/WebP bytes.
54
+ *
55
+ * @example
56
+ * const png = await client.remove({ file: "./input.jpg" });
57
+ */
58
+ async remove(input) {
59
+ const format = input.format ?? "png";
60
+ const { blob, filename } = await toBlob(input);
61
+ const form = new FormData();
62
+ form.append("file", blob, filename);
63
+ const res = await this.request("POST", `/remove?format=${format}`, {
64
+ body: form
65
+ });
66
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
67
+ return Buffer.from(await res.arrayBuffer());
68
+ }
69
+ /**
70
+ * Remove the background from a remote URL, returning the cleaned PNG/WebP bytes.
71
+ *
72
+ * @example
73
+ * const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
74
+ */
75
+ async removeUrl(input) {
76
+ const format = input.format ?? "png";
77
+ const res = await this.request("POST", "/remove-url", {
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({ url: input.url, format })
80
+ });
81
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
82
+ return Buffer.from(await res.arrayBuffer());
83
+ }
84
+ async request(method, path, init = {}) {
85
+ const url = `${this.baseUrl}${path}`;
86
+ const headers = {
87
+ "User-Agent": `useknockout-node/${SDK_VERSION}`,
88
+ ...init.headers ?? {}
89
+ };
90
+ if (this.token) headers["Authorization"] = `Bearer ${this.token}`;
91
+ const controller = new AbortController();
92
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
93
+ try {
94
+ return await this.fetchImpl(url, {
95
+ method,
96
+ headers,
97
+ body: init.body,
98
+ signal: controller.signal
99
+ });
100
+ } finally {
101
+ clearTimeout(timer);
102
+ }
103
+ }
104
+ };
105
+ async function toBlob(input) {
106
+ const { file } = input;
107
+ const filename = input.filename ?? inferFilename(file);
108
+ if (typeof file === "string") {
109
+ const data = await readFile(file);
110
+ return { blob: new Blob([new Uint8Array(data)]), filename };
111
+ }
112
+ if (file instanceof Blob) {
113
+ return { blob: file, filename };
114
+ }
115
+ if (file instanceof ArrayBuffer) {
116
+ return { blob: new Blob([new Uint8Array(file)]), filename };
117
+ }
118
+ if (file instanceof Uint8Array) {
119
+ return { blob: new Blob([new Uint8Array(file)]), filename };
120
+ }
121
+ if (Buffer.isBuffer(file)) {
122
+ return {
123
+ blob: new Blob([new Uint8Array(file)]),
124
+ filename
125
+ };
126
+ }
127
+ throw new TypeError("Unsupported `file` input. Provide a path, Buffer, Blob, ArrayBuffer, or Uint8Array.");
128
+ }
129
+ function inferFilename(file) {
130
+ if (typeof file === "string") return basename(file) || "image";
131
+ return "image";
132
+ }
133
+ var index_default = Knockout;
134
+
135
+ export { DEFAULT_BASE_URL, Knockout, KnockoutError, index_default as default };
136
+ //# sourceMappingURL=index.js.map
137
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @useknockout/node — official TypeScript / Node.js client for the useknockout API.\n *\n * Quick start:\n *\n * import { Knockout } from \"@useknockout/node\";\n *\n * const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });\n * const png = await client.remove({ file: \"./input.jpg\" }); // Buffer of PNG bytes\n * await writeFile(\"out.png\", png);\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { basename } from \"node:path\";\n\nexport const DEFAULT_BASE_URL = \"https://useknockout--api.modal.run\";\nconst SDK_VERSION = \"0.0.1\";\n\nexport type OutputFormat = \"png\" | \"webp\";\n\nexport interface KnockoutOptions {\n /** API bearer token. Required unless your self-hosted instance has no auth. */\n token?: string;\n /** Override the API base URL. Defaults to the hosted endpoint. */\n baseUrl?: string;\n /** Per-request timeout in milliseconds. Default 60_000. */\n timeoutMs?: number;\n /** Custom fetch (useful for edge runtimes / polyfills). Defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface RemoveInput {\n /** Local file path, Buffer, Blob, or ArrayBuffer of the image. */\n file: string | Buffer | Blob | ArrayBuffer | Uint8Array;\n /** Optional filename — inferred from path when `file` is a string. */\n filename?: string;\n /** Output format. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface RemoveUrlInput {\n /** Remote URL of the image to process. */\n url: string;\n /** Output format. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface HealthResponse {\n status: string;\n model: string;\n}\n\n/**\n * Error thrown when the API returns a non-2xx response.\n */\nexport class KnockoutError extends Error {\n public readonly status: number;\n public readonly code: \"auth\" | \"rate_limit\" | \"bad_request\" | \"payload_too_large\" | \"server\" | \"unknown\";\n public readonly body: string;\n\n constructor(status: number, body: string) {\n const code = KnockoutError.classify(status);\n super(`Knockout API error ${status} (${code}): ${body || \"no body\"}`);\n this.name = \"KnockoutError\";\n this.status = status;\n this.code = code;\n this.body = body;\n }\n\n private static classify(status: number): KnockoutError[\"code\"] {\n if (status === 401 || status === 403) return \"auth\";\n if (status === 429) return \"rate_limit\";\n if (status === 413) return \"payload_too_large\";\n if (status >= 400 && status < 500) return \"bad_request\";\n if (status >= 500) return \"server\";\n return \"unknown\";\n }\n}\n\n/**\n * useknockout API client.\n *\n * All methods return a `Buffer` (Node) of the processed image bytes.\n * Use `.toString(\"base64\")` or `writeFile(path, buf)` to persist.\n */\nexport class Knockout {\n private readonly baseUrl: string;\n private readonly token: string | undefined;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: KnockoutOptions = {}) {\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.token = options.token;\n this.timeoutMs = options.timeoutMs ?? 60_000;\n const fetchRef = options.fetch ?? globalThis.fetch;\n if (!fetchRef) {\n throw new Error(\n \"Global fetch is unavailable. Provide `options.fetch` or use Node 18+.\"\n );\n }\n this.fetchImpl = fetchRef.bind(globalThis);\n }\n\n /** Hit GET /health — no auth required. */\n async health(): Promise<HealthResponse> {\n const res = await this.request(\"GET\", \"/health\");\n const body = await res.text();\n if (!res.ok) throw new KnockoutError(res.status, body);\n return JSON.parse(body) as HealthResponse;\n }\n\n /**\n * Remove the background from an image, returning the cleaned PNG/WebP bytes.\n *\n * @example\n * const png = await client.remove({ file: \"./input.jpg\" });\n */\n async remove(input: RemoveInput): Promise<Buffer> {\n const format: OutputFormat = input.format ?? \"png\";\n const { blob, filename } = await toBlob(input);\n\n const form = new FormData();\n form.append(\"file\", blob, filename);\n\n const res = await this.request(\"POST\", `/remove?format=${format}`, {\n body: form,\n });\n\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return Buffer.from(await res.arrayBuffer());\n }\n\n /**\n * Remove the background from a remote URL, returning the cleaned PNG/WebP bytes.\n *\n * @example\n * const png = await client.removeUrl({ url: \"https://example.com/cat.jpg\" });\n */\n async removeUrl(input: RemoveUrlInput): Promise<Buffer> {\n const format: OutputFormat = input.format ?? \"png\";\n const res = await this.request(\"POST\", \"/remove-url\", {\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ url: input.url, format }),\n });\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return Buffer.from(await res.arrayBuffer());\n }\n\n private async request(\n method: \"GET\" | \"POST\",\n path: string,\n init: { headers?: Record<string, string>; body?: BodyInit } = {}\n ): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = {\n \"User-Agent\": `useknockout-node/${SDK_VERSION}`,\n ...(init.headers ?? {}),\n };\n if (this.token) headers[\"Authorization\"] = `Bearer ${this.token}`;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await this.fetchImpl(url, {\n method,\n headers,\n body: init.body,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\nasync function toBlob(\n input: RemoveInput\n): Promise<{ blob: Blob; filename: string }> {\n const { file } = input;\n const filename = input.filename ?? inferFilename(file);\n\n if (typeof file === \"string\") {\n const data = await readFile(file);\n return { blob: new Blob([new Uint8Array(data)]), filename };\n }\n if (file instanceof Blob) {\n return { blob: file, filename };\n }\n if (file instanceof ArrayBuffer) {\n return { blob: new Blob([new Uint8Array(file)]), filename };\n }\n if (file instanceof Uint8Array) {\n return { blob: new Blob([new Uint8Array(file)]), filename };\n }\n if (Buffer.isBuffer(file)) {\n return {\n blob: new Blob([new Uint8Array(file)]),\n filename,\n };\n }\n throw new TypeError(\"Unsupported `file` input. Provide a path, Buffer, Blob, ArrayBuffer, or Uint8Array.\");\n}\n\nfunction inferFilename(file: RemoveInput[\"file\"]): string {\n if (typeof file === \"string\") return basename(file) || \"image\";\n return \"image\";\n}\n\nexport default Knockout;\n"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@useknockout/node",
3
+ "version": "0.0.1",
4
+ "description": "Official Node.js client for useknockout — state-of-the-art background removal API.",
5
+ "keywords": [
6
+ "background-removal",
7
+ "remove-background",
8
+ "birefnet",
9
+ "image-processing",
10
+ "computer-vision",
11
+ "ai",
12
+ "segmentation",
13
+ "useknockout"
14
+ ],
15
+ "homepage": "https://github.com/useknockout/api",
16
+ "bugs": {
17
+ "url": "https://github.com/useknockout/node/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/useknockout/node.git"
22
+ },
23
+ "license": "MIT",
24
+ "author": "useknockout",
25
+ "type": "module",
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "require": "./dist/index.cjs"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "prepublishOnly": "npm run build",
47
+ "typecheck": "tsc --noEmit"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.7.0",
51
+ "tsup": "^8.3.0",
52
+ "typescript": "^5.6.0"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }