@useknockout/node 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,11 +23,24 @@ Requires **Node 18+** (for global `fetch` and `FormData`).
23
23
 
24
24
  ## Quick start
25
25
 
26
+ ### Public beta token
27
+
28
+ During the public beta, anyone can use this shared bearer token:
29
+
30
+ ```
31
+ kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f
32
+ ```
33
+
34
+ No signup — copy, paste, call the API. Need your own key / production limits? DM [@useknockout](https://x.com/useknockout) on X.
35
+
36
+ ### One-minute example
37
+
26
38
  ```ts
27
39
  import { writeFile } from "node:fs/promises";
28
40
  import { Knockout } from "@useknockout/node";
29
41
 
30
- const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
42
+ // Public beta for production use your own key
43
+ const client = new Knockout({ token: "kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f" });
31
44
 
32
45
  // 1. From a local file path
33
46
  const png = await client.remove({ file: "./input.jpg" });
package/dist/index.cjs CHANGED
@@ -7,7 +7,7 @@ var path = require('path');
7
7
 
8
8
  // src/index.ts
9
9
  var DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
10
- var SDK_VERSION = "0.0.1";
10
+ var SDK_VERSION = "0.0.3";
11
11
  var KnockoutError = class _KnockoutError extends Error {
12
12
  status;
13
13
  code;
@@ -85,6 +85,80 @@ var Knockout = class {
85
85
  if (!res.ok) throw new KnockoutError(res.status, await res.text());
86
86
  return Buffer.from(await res.arrayBuffer());
87
87
  }
88
+ /**
89
+ * Replace the background with a solid color or a remote image.
90
+ *
91
+ * @example Solid color
92
+ * const jpg = await client.replaceBackground({ file: "./cat.jpg", bgColor: "#FF5733", format: "jpg" });
93
+ *
94
+ * @example Remote image as new background
95
+ * const png = await client.replaceBackground({
96
+ * file: "./cat.jpg",
97
+ * bgUrl: "https://example.com/beach.jpg",
98
+ * });
99
+ */
100
+ async replaceBackground(input) {
101
+ const format = input.format ?? "png";
102
+ const { blob, filename } = await toBlob({ file: input.file, filename: input.filename });
103
+ const form = new FormData();
104
+ form.append("file", blob, filename);
105
+ const params = new URLSearchParams({ format });
106
+ if (input.bgUrl) params.set("bg_url", input.bgUrl);
107
+ if (input.bgColor) params.set("bg_color", input.bgColor);
108
+ const res = await this.request("POST", `/replace-bg?${params.toString()}`, {
109
+ body: form
110
+ });
111
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
112
+ return Buffer.from(await res.arrayBuffer());
113
+ }
114
+ /**
115
+ * Remove the background from up to 10 images in a single call.
116
+ * Returns a JSON object with base64-encoded result bytes per image.
117
+ *
118
+ * @example
119
+ * const batch = await client.removeBatch({
120
+ * files: ["./a.jpg", "./b.jpg", "./c.jpg"],
121
+ * format: "png",
122
+ * });
123
+ * for (const r of batch.results) {
124
+ * if (r.success) await writeFile(r.filename!, Buffer.from(r.data_base64!, "base64"));
125
+ * }
126
+ */
127
+ async removeBatch(input) {
128
+ const format = input.format ?? "png";
129
+ if (input.files.length === 0) throw new Error("At least one file required");
130
+ if (input.files.length > 10) throw new Error("Max 10 files per batch");
131
+ const form = new FormData();
132
+ for (let i = 0; i < input.files.length; i++) {
133
+ const name = input.filenames?.[i];
134
+ const { blob, filename } = await toBlob({ file: input.files[i], filename: name });
135
+ form.append("files", blob, filename);
136
+ }
137
+ const res = await this.request("POST", `/remove-batch?format=${format}`, {
138
+ body: form
139
+ });
140
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
141
+ return await res.json();
142
+ }
143
+ /**
144
+ * Remove the background from up to 10 remote image URLs in a single call.
145
+ *
146
+ * @example
147
+ * const batch = await client.removeBatchUrl({
148
+ * urls: ["https://a.jpg", "https://b.jpg"],
149
+ * });
150
+ */
151
+ async removeBatchUrl(input) {
152
+ const format = input.format ?? "png";
153
+ if (input.urls.length === 0) throw new Error("At least one URL required");
154
+ if (input.urls.length > 10) throw new Error("Max 10 URLs per batch");
155
+ const res = await this.request("POST", "/remove-batch-url", {
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({ urls: input.urls, format })
158
+ });
159
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
160
+ return await res.json();
161
+ }
88
162
  async request(method, path, init = {}) {
89
163
  const url = `${this.baseUrl}${path}`;
90
164
  const headers = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["readFile","basename"],"mappings":";;;;;;;;AAeO,IAAM,gBAAA,GAAmB;AAChC,IAAM,WAAA,GAAc,OAAA;AAuCb,IAAM,aAAA,GAAN,MAAM,cAAA,SAAsB,KAAA,CAAM;AAAA,EACvB,MAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EAEhB,WAAA,CAAY,QAAgB,IAAA,EAAc;AACxC,IAAA,MAAM,IAAA,GAAO,cAAA,CAAc,QAAA,CAAS,MAAM,CAAA;AAC1C,IAAA,KAAA,CAAM,sBAAsB,MAAM,CAAA,EAAA,EAAK,IAAI,CAAA,GAAA,EAAM,IAAA,IAAQ,SAAS,CAAA,CAAE,CAAA;AACpE,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,OAAe,SAAS,MAAA,EAAuC;AAC7D,IAAA,IAAI,MAAA,KAAW,GAAA,IAAO,MAAA,KAAW,GAAA,EAAK,OAAO,MAAA;AAC7C,IAAA,IAAI,MAAA,KAAW,KAAK,OAAO,YAAA;AAC3B,IAAA,IAAI,MAAA,KAAW,KAAK,OAAO,mBAAA;AAC3B,IAAA,IAAI,MAAA,IAAU,GAAA,IAAO,MAAA,GAAS,GAAA,EAAK,OAAO,aAAA;AAC1C,IAAA,IAAI,MAAA,IAAU,KAAK,OAAO,QAAA;AAC1B,IAAA,OAAO,SAAA;AAAA,EACT;AACF;AAQO,IAAM,WAAN,MAAe;AAAA,EACH,OAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EAEjB,WAAA,CAAY,OAAA,GAA2B,EAAC,EAAG;AACzC,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,OAAA,IAAW,gBAAA,EAAkB,OAAA,CAAQ,QAAQ,EAAE,CAAA;AACvE,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,IAAA,CAAK,SAAA,GAAY,QAAQ,SAAA,IAAa,GAAA;AACtC,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,KAAA,IAAS,UAAA,CAAW,KAAA;AAC7C,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA,CAAS,IAAA,CAAK,UAAU,CAAA;AAAA,EAC3C;AAAA;AAAA,EAGA,MAAM,MAAA,GAAkC;AACtC,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,SAAS,CAAA;AAC/C,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,aAAA,CAAc,GAAA,CAAI,QAAQ,IAAI,CAAA;AACrD,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,KAAA,EAAqC;AAChD,IAAA,MAAM,MAAA,GAAuB,MAAM,MAAA,IAAU,KAAA;AAC7C,IAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAS,GAAI,MAAM,OAAO,KAAK,CAAA;AAE7C,IAAA,MAAM,IAAA,GAAO,IAAI,QAAA,EAAS;AAC1B,IAAA,IAAA,CAAK,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAM,QAAQ,CAAA;AAElC,IAAA,MAAM,MAAM,MAAM,IAAA,CAAK,QAAQ,MAAA,EAAQ,CAAA,eAAA,EAAkB,MAAM,CAAA,CAAA,EAAI;AAAA,MACjE,IAAA,EAAM;AAAA,KACP,CAAA;AAED,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,aAAA,CAAc,GAAA,CAAI,MAAA,EAAQ,MAAM,GAAA,CAAI,IAAA,EAAM,CAAA;AACjE,IAAA,OAAO,MAAA,CAAO,IAAA,CAAK,MAAM,GAAA,CAAI,aAAa,CAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,KAAA,EAAwC;AACtD,IAAA,MAAM,MAAA,GAAuB,MAAM,MAAA,IAAU,KAAA;AAC7C,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,OAAA,CAAQ,QAAQ,aAAA,EAAe;AAAA,MACpD,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,KAAK,SAAA,CAAU,EAAE,KAAK,KAAA,CAAM,GAAA,EAAK,QAAQ;AAAA,KAChD,CAAA;AACD,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,aAAA,CAAc,GAAA,CAAI,MAAA,EAAQ,MAAM,GAAA,CAAI,IAAA,EAAM,CAAA;AACjE,IAAA,OAAO,MAAA,CAAO,IAAA,CAAK,MAAM,GAAA,CAAI,aAAa,CAAA;AAAA,EAC5C;AAAA,EAEA,MAAc,OAAA,CACZ,MAAA,EACA,IAAA,EACA,IAAA,GAA8D,EAAC,EAC5C;AACnB,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,GAAG,IAAI,CAAA,CAAA;AAClC,IAAA,MAAM,OAAA,GAAkC;AAAA,MACtC,YAAA,EAAc,oBAAoB,WAAW,CAAA,CAAA;AAAA,MAC7C,GAAI,IAAA,CAAK,OAAA,IAAW;AAAC,KACvB;AACA,IAAA,IAAI,KAAK,KAAA,EAAO,OAAA,CAAQ,eAAe,CAAA,GAAI,CAAA,OAAA,EAAU,KAAK,KAAK,CAAA,CAAA;AAE/D,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,SAAS,CAAA;AACjE,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,GAAA,EAAK;AAAA,QAC/B,MAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,QAAQ,UAAA,CAAW;AAAA,OACpB,CAAA;AAAA,IACH,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF;AACF;AAEA,eAAe,OACb,KAAA,EAC2C;AAC3C,EAAA,MAAM,EAAE,MAAK,GAAI,KAAA;AACjB,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,QAAA,IAAY,aAAA,CAAc,IAAI,CAAA;AAErD,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,IAAA,MAAM,IAAA,GAAO,MAAMA,iBAAA,CAAS,IAAI,CAAA;AAChC,IAAA,OAAO,EAAE,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,IAAI,UAAA,CAAW,IAAI,CAAC,CAAC,CAAA,EAAG,QAAA,EAAS;AAAA,EAC5D;AACA,EAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,IAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,QAAA,EAAS;AAAA,EAChC;AACA,EAAA,IAAI,gBAAgB,WAAA,EAAa;AAC/B,IAAA,OAAO,EAAE,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,IAAI,UAAA,CAAW,IAAI,CAAC,CAAC,CAAA,EAAG,QAAA,EAAS;AAAA,EAC5D;AACA,EAAA,IAAI,gBAAgB,UAAA,EAAY;AAC9B,IAAA,OAAO,EAAE,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,IAAI,UAAA,CAAW,IAAI,CAAC,CAAC,CAAA,EAAG,QAAA,EAAS;AAAA,EAC5D;AACA,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG;AACzB,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,IAAI,UAAA,CAAW,IAAI,CAAC,CAAC,CAAA;AAAA,MACrC;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,IAAI,UAAU,qFAAqF,CAAA;AAC3G;AAEA,SAAS,cAAc,IAAA,EAAmC;AACxD,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,OAAOC,aAAA,CAAS,IAAI,CAAA,IAAK,OAAA;AACvD,EAAA,OAAO,OAAA;AACT;AAEA,IAAO,aAAA,GAAQ","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"]}
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.3\";\n\nexport type OutputFormat = \"png\" | \"webp\";\nexport type OpaqueFormat = \"png\" | \"webp\" | \"jpg\";\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 ReplaceBgInput {\n /** Local file path, Buffer, Blob, or ArrayBuffer of the foreground image. */\n file: string | Buffer | Blob | ArrayBuffer | Uint8Array;\n /** Optional filename — inferred from path when `file` is a string. */\n filename?: string;\n /** Hex color for the new background. Default \"#FFFFFF\". Ignored if `bgUrl` is set. */\n bgColor?: string;\n /** Remote URL of an image to use as the new background. Takes precedence over `bgColor`. */\n bgUrl?: string;\n /** Output format. \"jpg\" is smallest. Default \"png\". */\n format?: OpaqueFormat;\n}\n\nexport interface BatchInput {\n /** Array of local paths / buffers / blobs. Up to 10. */\n files: Array<string | Buffer | Blob | ArrayBuffer | Uint8Array>;\n /** Optional filenames aligned to `files`. */\n filenames?: string[];\n /** Output format for each image. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface BatchUrlInput {\n /** Remote URLs to process. Up to 10. */\n urls: string[];\n /** Output format for each image. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface BatchResultItem {\n filename?: string;\n url?: string;\n success: boolean;\n format?: OutputFormat;\n size_bytes?: number;\n data_base64?: string;\n error?: string;\n}\n\nexport interface BatchResponse {\n count: number;\n format: OutputFormat;\n results: BatchResultItem[];\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 /**\n * Replace the background with a solid color or a remote image.\n *\n * @example Solid color\n * const jpg = await client.replaceBackground({ file: \"./cat.jpg\", bgColor: \"#FF5733\", format: \"jpg\" });\n *\n * @example Remote image as new background\n * const png = await client.replaceBackground({\n * file: \"./cat.jpg\",\n * bgUrl: \"https://example.com/beach.jpg\",\n * });\n */\n async replaceBackground(input: ReplaceBgInput): Promise<Buffer> {\n const format: OpaqueFormat = input.format ?? \"png\";\n const { blob, filename } = await toBlob({ file: input.file, filename: input.filename });\n\n const form = new FormData();\n form.append(\"file\", blob, filename);\n\n const params = new URLSearchParams({ format });\n if (input.bgUrl) params.set(\"bg_url\", input.bgUrl);\n if (input.bgColor) params.set(\"bg_color\", input.bgColor);\n\n const res = await this.request(\"POST\", `/replace-bg?${params.toString()}`, {\n body: form,\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 up to 10 images in a single call.\n * Returns a JSON object with base64-encoded result bytes per image.\n *\n * @example\n * const batch = await client.removeBatch({\n * files: [\"./a.jpg\", \"./b.jpg\", \"./c.jpg\"],\n * format: \"png\",\n * });\n * for (const r of batch.results) {\n * if (r.success) await writeFile(r.filename!, Buffer.from(r.data_base64!, \"base64\"));\n * }\n */\n async removeBatch(input: BatchInput): Promise<BatchResponse> {\n const format: OutputFormat = input.format ?? \"png\";\n if (input.files.length === 0) throw new Error(\"At least one file required\");\n if (input.files.length > 10) throw new Error(\"Max 10 files per batch\");\n\n const form = new FormData();\n for (let i = 0; i < input.files.length; i++) {\n const name = input.filenames?.[i];\n const { blob, filename } = await toBlob({ file: input.files[i]!, filename: name });\n form.append(\"files\", blob, filename);\n }\n\n const res = await this.request(\"POST\", `/remove-batch?format=${format}`, {\n body: form,\n });\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return (await res.json()) as BatchResponse;\n }\n\n /**\n * Remove the background from up to 10 remote image URLs in a single call.\n *\n * @example\n * const batch = await client.removeBatchUrl({\n * urls: [\"https://a.jpg\", \"https://b.jpg\"],\n * });\n */\n async removeBatchUrl(input: BatchUrlInput): Promise<BatchResponse> {\n const format: OutputFormat = input.format ?? \"png\";\n if (input.urls.length === 0) throw new Error(\"At least one URL required\");\n if (input.urls.length > 10) throw new Error(\"Max 10 URLs per batch\");\n\n const res = await this.request(\"POST\", \"/remove-batch-url\", {\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ urls: input.urls, format }),\n });\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return (await res.json()) as BatchResponse;\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: { file: string | Buffer | Blob | ArrayBuffer | Uint8Array; filename?: string }\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: string | Buffer | Blob | ArrayBuffer | Uint8Array): string {\n if (typeof file === \"string\") return basename(file) || \"image\";\n return \"image\";\n}\n\nexport default Knockout;\n"]}
package/dist/index.d.cts CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
  declare const DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
13
13
  type OutputFormat = "png" | "webp";
14
+ type OpaqueFormat = "png" | "webp" | "jpg";
14
15
  interface KnockoutOptions {
15
16
  /** API bearer token. Required unless your self-hosted instance has no auth. */
16
17
  token?: string;
@@ -35,6 +36,46 @@ interface RemoveUrlInput {
35
36
  /** Output format. Default "png". */
36
37
  format?: OutputFormat;
37
38
  }
39
+ interface ReplaceBgInput {
40
+ /** Local file path, Buffer, Blob, or ArrayBuffer of the foreground image. */
41
+ file: string | Buffer | Blob | ArrayBuffer | Uint8Array;
42
+ /** Optional filename — inferred from path when `file` is a string. */
43
+ filename?: string;
44
+ /** Hex color for the new background. Default "#FFFFFF". Ignored if `bgUrl` is set. */
45
+ bgColor?: string;
46
+ /** Remote URL of an image to use as the new background. Takes precedence over `bgColor`. */
47
+ bgUrl?: string;
48
+ /** Output format. "jpg" is smallest. Default "png". */
49
+ format?: OpaqueFormat;
50
+ }
51
+ interface BatchInput {
52
+ /** Array of local paths / buffers / blobs. Up to 10. */
53
+ files: Array<string | Buffer | Blob | ArrayBuffer | Uint8Array>;
54
+ /** Optional filenames aligned to `files`. */
55
+ filenames?: string[];
56
+ /** Output format for each image. Default "png". */
57
+ format?: OutputFormat;
58
+ }
59
+ interface BatchUrlInput {
60
+ /** Remote URLs to process. Up to 10. */
61
+ urls: string[];
62
+ /** Output format for each image. Default "png". */
63
+ format?: OutputFormat;
64
+ }
65
+ interface BatchResultItem {
66
+ filename?: string;
67
+ url?: string;
68
+ success: boolean;
69
+ format?: OutputFormat;
70
+ size_bytes?: number;
71
+ data_base64?: string;
72
+ error?: string;
73
+ }
74
+ interface BatchResponse {
75
+ count: number;
76
+ format: OutputFormat;
77
+ results: BatchResultItem[];
78
+ }
38
79
  interface HealthResponse {
39
80
  status: string;
40
81
  model: string;
@@ -77,7 +118,43 @@ declare class Knockout {
77
118
  * const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
78
119
  */
79
120
  removeUrl(input: RemoveUrlInput): Promise<Buffer>;
121
+ /**
122
+ * Replace the background with a solid color or a remote image.
123
+ *
124
+ * @example Solid color
125
+ * const jpg = await client.replaceBackground({ file: "./cat.jpg", bgColor: "#FF5733", format: "jpg" });
126
+ *
127
+ * @example Remote image as new background
128
+ * const png = await client.replaceBackground({
129
+ * file: "./cat.jpg",
130
+ * bgUrl: "https://example.com/beach.jpg",
131
+ * });
132
+ */
133
+ replaceBackground(input: ReplaceBgInput): Promise<Buffer>;
134
+ /**
135
+ * Remove the background from up to 10 images in a single call.
136
+ * Returns a JSON object with base64-encoded result bytes per image.
137
+ *
138
+ * @example
139
+ * const batch = await client.removeBatch({
140
+ * files: ["./a.jpg", "./b.jpg", "./c.jpg"],
141
+ * format: "png",
142
+ * });
143
+ * for (const r of batch.results) {
144
+ * if (r.success) await writeFile(r.filename!, Buffer.from(r.data_base64!, "base64"));
145
+ * }
146
+ */
147
+ removeBatch(input: BatchInput): Promise<BatchResponse>;
148
+ /**
149
+ * Remove the background from up to 10 remote image URLs in a single call.
150
+ *
151
+ * @example
152
+ * const batch = await client.removeBatchUrl({
153
+ * urls: ["https://a.jpg", "https://b.jpg"],
154
+ * });
155
+ */
156
+ removeBatchUrl(input: BatchUrlInput): Promise<BatchResponse>;
80
157
  private request;
81
158
  }
82
159
 
83
- export { DEFAULT_BASE_URL, type HealthResponse, Knockout, KnockoutError, type KnockoutOptions, type OutputFormat, type RemoveInput, type RemoveUrlInput, Knockout as default };
160
+ export { type BatchInput, type BatchResponse, type BatchResultItem, type BatchUrlInput, DEFAULT_BASE_URL, type HealthResponse, Knockout, KnockoutError, type KnockoutOptions, type OpaqueFormat, type OutputFormat, type RemoveInput, type RemoveUrlInput, type ReplaceBgInput, Knockout as default };
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
  declare const DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
13
13
  type OutputFormat = "png" | "webp";
14
+ type OpaqueFormat = "png" | "webp" | "jpg";
14
15
  interface KnockoutOptions {
15
16
  /** API bearer token. Required unless your self-hosted instance has no auth. */
16
17
  token?: string;
@@ -35,6 +36,46 @@ interface RemoveUrlInput {
35
36
  /** Output format. Default "png". */
36
37
  format?: OutputFormat;
37
38
  }
39
+ interface ReplaceBgInput {
40
+ /** Local file path, Buffer, Blob, or ArrayBuffer of the foreground image. */
41
+ file: string | Buffer | Blob | ArrayBuffer | Uint8Array;
42
+ /** Optional filename — inferred from path when `file` is a string. */
43
+ filename?: string;
44
+ /** Hex color for the new background. Default "#FFFFFF". Ignored if `bgUrl` is set. */
45
+ bgColor?: string;
46
+ /** Remote URL of an image to use as the new background. Takes precedence over `bgColor`. */
47
+ bgUrl?: string;
48
+ /** Output format. "jpg" is smallest. Default "png". */
49
+ format?: OpaqueFormat;
50
+ }
51
+ interface BatchInput {
52
+ /** Array of local paths / buffers / blobs. Up to 10. */
53
+ files: Array<string | Buffer | Blob | ArrayBuffer | Uint8Array>;
54
+ /** Optional filenames aligned to `files`. */
55
+ filenames?: string[];
56
+ /** Output format for each image. Default "png". */
57
+ format?: OutputFormat;
58
+ }
59
+ interface BatchUrlInput {
60
+ /** Remote URLs to process. Up to 10. */
61
+ urls: string[];
62
+ /** Output format for each image. Default "png". */
63
+ format?: OutputFormat;
64
+ }
65
+ interface BatchResultItem {
66
+ filename?: string;
67
+ url?: string;
68
+ success: boolean;
69
+ format?: OutputFormat;
70
+ size_bytes?: number;
71
+ data_base64?: string;
72
+ error?: string;
73
+ }
74
+ interface BatchResponse {
75
+ count: number;
76
+ format: OutputFormat;
77
+ results: BatchResultItem[];
78
+ }
38
79
  interface HealthResponse {
39
80
  status: string;
40
81
  model: string;
@@ -77,7 +118,43 @@ declare class Knockout {
77
118
  * const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
78
119
  */
79
120
  removeUrl(input: RemoveUrlInput): Promise<Buffer>;
121
+ /**
122
+ * Replace the background with a solid color or a remote image.
123
+ *
124
+ * @example Solid color
125
+ * const jpg = await client.replaceBackground({ file: "./cat.jpg", bgColor: "#FF5733", format: "jpg" });
126
+ *
127
+ * @example Remote image as new background
128
+ * const png = await client.replaceBackground({
129
+ * file: "./cat.jpg",
130
+ * bgUrl: "https://example.com/beach.jpg",
131
+ * });
132
+ */
133
+ replaceBackground(input: ReplaceBgInput): Promise<Buffer>;
134
+ /**
135
+ * Remove the background from up to 10 images in a single call.
136
+ * Returns a JSON object with base64-encoded result bytes per image.
137
+ *
138
+ * @example
139
+ * const batch = await client.removeBatch({
140
+ * files: ["./a.jpg", "./b.jpg", "./c.jpg"],
141
+ * format: "png",
142
+ * });
143
+ * for (const r of batch.results) {
144
+ * if (r.success) await writeFile(r.filename!, Buffer.from(r.data_base64!, "base64"));
145
+ * }
146
+ */
147
+ removeBatch(input: BatchInput): Promise<BatchResponse>;
148
+ /**
149
+ * Remove the background from up to 10 remote image URLs in a single call.
150
+ *
151
+ * @example
152
+ * const batch = await client.removeBatchUrl({
153
+ * urls: ["https://a.jpg", "https://b.jpg"],
154
+ * });
155
+ */
156
+ removeBatchUrl(input: BatchUrlInput): Promise<BatchResponse>;
80
157
  private request;
81
158
  }
82
159
 
83
- export { DEFAULT_BASE_URL, type HealthResponse, Knockout, KnockoutError, type KnockoutOptions, type OutputFormat, type RemoveInput, type RemoveUrlInput, Knockout as default };
160
+ export { type BatchInput, type BatchResponse, type BatchResultItem, type BatchUrlInput, DEFAULT_BASE_URL, type HealthResponse, Knockout, KnockoutError, type KnockoutOptions, type OpaqueFormat, type OutputFormat, type RemoveInput, type RemoveUrlInput, type ReplaceBgInput, Knockout as default };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { basename } from 'path';
3
3
 
4
4
  // src/index.ts
5
5
  var DEFAULT_BASE_URL = "https://useknockout--api.modal.run";
6
- var SDK_VERSION = "0.0.1";
6
+ var SDK_VERSION = "0.0.3";
7
7
  var KnockoutError = class _KnockoutError extends Error {
8
8
  status;
9
9
  code;
@@ -81,6 +81,80 @@ var Knockout = class {
81
81
  if (!res.ok) throw new KnockoutError(res.status, await res.text());
82
82
  return Buffer.from(await res.arrayBuffer());
83
83
  }
84
+ /**
85
+ * Replace the background with a solid color or a remote image.
86
+ *
87
+ * @example Solid color
88
+ * const jpg = await client.replaceBackground({ file: "./cat.jpg", bgColor: "#FF5733", format: "jpg" });
89
+ *
90
+ * @example Remote image as new background
91
+ * const png = await client.replaceBackground({
92
+ * file: "./cat.jpg",
93
+ * bgUrl: "https://example.com/beach.jpg",
94
+ * });
95
+ */
96
+ async replaceBackground(input) {
97
+ const format = input.format ?? "png";
98
+ const { blob, filename } = await toBlob({ file: input.file, filename: input.filename });
99
+ const form = new FormData();
100
+ form.append("file", blob, filename);
101
+ const params = new URLSearchParams({ format });
102
+ if (input.bgUrl) params.set("bg_url", input.bgUrl);
103
+ if (input.bgColor) params.set("bg_color", input.bgColor);
104
+ const res = await this.request("POST", `/replace-bg?${params.toString()}`, {
105
+ body: form
106
+ });
107
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
108
+ return Buffer.from(await res.arrayBuffer());
109
+ }
110
+ /**
111
+ * Remove the background from up to 10 images in a single call.
112
+ * Returns a JSON object with base64-encoded result bytes per image.
113
+ *
114
+ * @example
115
+ * const batch = await client.removeBatch({
116
+ * files: ["./a.jpg", "./b.jpg", "./c.jpg"],
117
+ * format: "png",
118
+ * });
119
+ * for (const r of batch.results) {
120
+ * if (r.success) await writeFile(r.filename!, Buffer.from(r.data_base64!, "base64"));
121
+ * }
122
+ */
123
+ async removeBatch(input) {
124
+ const format = input.format ?? "png";
125
+ if (input.files.length === 0) throw new Error("At least one file required");
126
+ if (input.files.length > 10) throw new Error("Max 10 files per batch");
127
+ const form = new FormData();
128
+ for (let i = 0; i < input.files.length; i++) {
129
+ const name = input.filenames?.[i];
130
+ const { blob, filename } = await toBlob({ file: input.files[i], filename: name });
131
+ form.append("files", blob, filename);
132
+ }
133
+ const res = await this.request("POST", `/remove-batch?format=${format}`, {
134
+ body: form
135
+ });
136
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
137
+ return await res.json();
138
+ }
139
+ /**
140
+ * Remove the background from up to 10 remote image URLs in a single call.
141
+ *
142
+ * @example
143
+ * const batch = await client.removeBatchUrl({
144
+ * urls: ["https://a.jpg", "https://b.jpg"],
145
+ * });
146
+ */
147
+ async removeBatchUrl(input) {
148
+ const format = input.format ?? "png";
149
+ if (input.urls.length === 0) throw new Error("At least one URL required");
150
+ if (input.urls.length > 10) throw new Error("Max 10 URLs per batch");
151
+ const res = await this.request("POST", "/remove-batch-url", {
152
+ headers: { "Content-Type": "application/json" },
153
+ body: JSON.stringify({ urls: input.urls, format })
154
+ });
155
+ if (!res.ok) throw new KnockoutError(res.status, await res.text());
156
+ return await res.json();
157
+ }
84
158
  async request(method, path, init = {}) {
85
159
  const url = `${this.baseUrl}${path}`;
86
160
  const headers = {
package/dist/index.js.map CHANGED
@@ -1 +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"]}
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.3\";\n\nexport type OutputFormat = \"png\" | \"webp\";\nexport type OpaqueFormat = \"png\" | \"webp\" | \"jpg\";\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 ReplaceBgInput {\n /** Local file path, Buffer, Blob, or ArrayBuffer of the foreground image. */\n file: string | Buffer | Blob | ArrayBuffer | Uint8Array;\n /** Optional filename — inferred from path when `file` is a string. */\n filename?: string;\n /** Hex color for the new background. Default \"#FFFFFF\". Ignored if `bgUrl` is set. */\n bgColor?: string;\n /** Remote URL of an image to use as the new background. Takes precedence over `bgColor`. */\n bgUrl?: string;\n /** Output format. \"jpg\" is smallest. Default \"png\". */\n format?: OpaqueFormat;\n}\n\nexport interface BatchInput {\n /** Array of local paths / buffers / blobs. Up to 10. */\n files: Array<string | Buffer | Blob | ArrayBuffer | Uint8Array>;\n /** Optional filenames aligned to `files`. */\n filenames?: string[];\n /** Output format for each image. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface BatchUrlInput {\n /** Remote URLs to process. Up to 10. */\n urls: string[];\n /** Output format for each image. Default \"png\". */\n format?: OutputFormat;\n}\n\nexport interface BatchResultItem {\n filename?: string;\n url?: string;\n success: boolean;\n format?: OutputFormat;\n size_bytes?: number;\n data_base64?: string;\n error?: string;\n}\n\nexport interface BatchResponse {\n count: number;\n format: OutputFormat;\n results: BatchResultItem[];\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 /**\n * Replace the background with a solid color or a remote image.\n *\n * @example Solid color\n * const jpg = await client.replaceBackground({ file: \"./cat.jpg\", bgColor: \"#FF5733\", format: \"jpg\" });\n *\n * @example Remote image as new background\n * const png = await client.replaceBackground({\n * file: \"./cat.jpg\",\n * bgUrl: \"https://example.com/beach.jpg\",\n * });\n */\n async replaceBackground(input: ReplaceBgInput): Promise<Buffer> {\n const format: OpaqueFormat = input.format ?? \"png\";\n const { blob, filename } = await toBlob({ file: input.file, filename: input.filename });\n\n const form = new FormData();\n form.append(\"file\", blob, filename);\n\n const params = new URLSearchParams({ format });\n if (input.bgUrl) params.set(\"bg_url\", input.bgUrl);\n if (input.bgColor) params.set(\"bg_color\", input.bgColor);\n\n const res = await this.request(\"POST\", `/replace-bg?${params.toString()}`, {\n body: form,\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 up to 10 images in a single call.\n * Returns a JSON object with base64-encoded result bytes per image.\n *\n * @example\n * const batch = await client.removeBatch({\n * files: [\"./a.jpg\", \"./b.jpg\", \"./c.jpg\"],\n * format: \"png\",\n * });\n * for (const r of batch.results) {\n * if (r.success) await writeFile(r.filename!, Buffer.from(r.data_base64!, \"base64\"));\n * }\n */\n async removeBatch(input: BatchInput): Promise<BatchResponse> {\n const format: OutputFormat = input.format ?? \"png\";\n if (input.files.length === 0) throw new Error(\"At least one file required\");\n if (input.files.length > 10) throw new Error(\"Max 10 files per batch\");\n\n const form = new FormData();\n for (let i = 0; i < input.files.length; i++) {\n const name = input.filenames?.[i];\n const { blob, filename } = await toBlob({ file: input.files[i]!, filename: name });\n form.append(\"files\", blob, filename);\n }\n\n const res = await this.request(\"POST\", `/remove-batch?format=${format}`, {\n body: form,\n });\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return (await res.json()) as BatchResponse;\n }\n\n /**\n * Remove the background from up to 10 remote image URLs in a single call.\n *\n * @example\n * const batch = await client.removeBatchUrl({\n * urls: [\"https://a.jpg\", \"https://b.jpg\"],\n * });\n */\n async removeBatchUrl(input: BatchUrlInput): Promise<BatchResponse> {\n const format: OutputFormat = input.format ?? \"png\";\n if (input.urls.length === 0) throw new Error(\"At least one URL required\");\n if (input.urls.length > 10) throw new Error(\"Max 10 URLs per batch\");\n\n const res = await this.request(\"POST\", \"/remove-batch-url\", {\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ urls: input.urls, format }),\n });\n if (!res.ok) throw new KnockoutError(res.status, await res.text());\n return (await res.json()) as BatchResponse;\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: { file: string | Buffer | Blob | ArrayBuffer | Uint8Array; filename?: string }\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: string | Buffer | Blob | ArrayBuffer | Uint8Array): string {\n if (typeof file === \"string\") return basename(file) || \"image\";\n return \"image\";\n}\n\nexport default Knockout;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useknockout/node",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Official Node.js client for useknockout — state-of-the-art background removal API.",
5
5
  "keywords": [
6
6
  "background-removal",