@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 +21 -0
- package/README.md +180 -0
- package/dist/index.cjs +144 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|