boxer-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # boxer-sdk
2
+
3
+ TypeScript client SDK for [Boxer](https://github.com/theonekeyg/boxer) - a sandboxed container execution service backed by gVisor.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install boxer-sdk
9
+ ```
10
+
11
+ Requires Node.js 18+ (or any runtime with native `fetch`) and a running Boxer server.
12
+
13
+ ## Quick start
14
+
15
+ ```typescript
16
+ import { BoxerClient } from "boxer-sdk";
17
+
18
+ const client = new BoxerClient({ baseUrl: "http://localhost:8080" });
19
+
20
+ const result = await client.run(
21
+ "python:3.12-slim",
22
+ ["python3", "-c", "print('hello world')"],
23
+ );
24
+ console.log(result.stdout); // hello world
25
+ console.log(result.exitCode); // 0
26
+ console.log(result.wall_ms); // e.g. 312
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Hello world in Python, Node.js, and Perl
32
+
33
+ ### Python
34
+
35
+ ```typescript
36
+ const result = await client.run(
37
+ "python:3.12-slim",
38
+ ["python3", "-c", "print('hello world')"],
39
+ );
40
+ console.log(result.stdout); // hello world
41
+ ```
42
+
43
+ ### Node.js
44
+
45
+ ```typescript
46
+ const result = await client.run(
47
+ "node:20-slim",
48
+ ["node", "-e", "console.log('hello world')"],
49
+ );
50
+ console.log(result.stdout); // hello world
51
+ ```
52
+
53
+ ### Perl
54
+
55
+ ```typescript
56
+ const result = await client.run(
57
+ "perl:5.38-slim",
58
+ ["perl", "-e", "print 'hello world\n'"],
59
+ );
60
+ console.log(result.stdout); // hello world
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Working with files
66
+
67
+ ### Upload a script and run it
68
+
69
+ Upload a file to the Boxer file store, then reference it by path in `run`.
70
+ The file is bind-mounted read-only at `/<remotePath>` inside the container.
71
+
72
+ ```typescript
73
+ import { readFile } from "node:fs/promises";
74
+
75
+ // Python
76
+ const pyScript = await readFile("script.py");
77
+ await client.uploadFile("script.py", pyScript);
78
+
79
+ const result = await client.run(
80
+ "python:3.12-slim",
81
+ ["python3", "/script.py"],
82
+ { files: ["script.py"] },
83
+ );
84
+
85
+ // Node.js
86
+ const jsScript = await readFile("app.js");
87
+ await client.uploadFile("app.js", jsScript);
88
+
89
+ const result = await client.run(
90
+ "node:20-slim",
91
+ ["node", "/app.js"],
92
+ { files: ["app.js"] },
93
+ );
94
+
95
+ // Perl
96
+ const plScript = await readFile("hello.pl");
97
+ await client.uploadFile("hello.pl", plScript);
98
+
99
+ const result = await client.run(
100
+ "perl:5.38-slim",
101
+ ["perl", "/hello.pl"],
102
+ { files: ["hello.pl"] },
103
+ );
104
+ ```
105
+
106
+ ### Upload a local file or directory
107
+
108
+ `uploadPath` (available from `boxer-sdk/node`) recursively uploads a local file
109
+ or directory, preserving the directory structure.
110
+
111
+ ```typescript
112
+ import { uploadPath } from "boxer-sdk/node";
113
+
114
+ // Upload a single file
115
+ const paths = await uploadPath(client, "./script.py");
116
+ // => ["script.py"]
117
+
118
+ // Upload an entire directory
119
+ const paths = await uploadPath(client, "./myproject", "myproject");
120
+ // => ["myproject/main.py", "myproject/utils.py", ...]
121
+
122
+ const result = await client.run(
123
+ "python:3.12-slim",
124
+ ["python3", "/myproject/main.py"],
125
+ { files: paths },
126
+ );
127
+ ```
128
+
129
+ ### Download output files from the container
130
+
131
+ Any file the container writes to `/output/` is automatically captured and
132
+ retrievable via `downloadFile` after the run completes.
133
+
134
+ ```typescript
135
+ const code = `
136
+ import os, json
137
+ os.makedirs('/output', exist_ok=True)
138
+ with open('/output/result.json', 'w') as f:
139
+ json.dump({'message': 'hello world', 'value': 42}, f)
140
+ `;
141
+
142
+ const script = new TextEncoder().encode(code);
143
+ await client.uploadFile("compute.py", script);
144
+
145
+ const result = await client.run(
146
+ "python:3.12-slim",
147
+ ["python3", "/compute.py"],
148
+ { files: ["compute.py"] },
149
+ );
150
+
151
+ // Download the file the container wrote
152
+ const data = await client.downloadFile(`output/${result.exec_id}/result.json`);
153
+ console.log(new TextDecoder().decode(data)); // {"message": "hello world", "value": 42}
154
+ ```
155
+
156
+ The output path pattern is always `output/<exec_id>/<filename>`.
157
+
158
+ ---
159
+
160
+ ## Resource limits
161
+
162
+ ```typescript
163
+ import type { ResourceLimits } from "boxer-sdk";
164
+
165
+ const limits: ResourceLimits = {
166
+ cpu_cores: 0.5,
167
+ memory_mb: 128,
168
+ wall_clock_secs: 10,
169
+ };
170
+
171
+ const result = await client.run(
172
+ "python:3.12-slim",
173
+ ["python3", "-c", "print('done')"],
174
+ { limits },
175
+ );
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Error handling
181
+
182
+ ```typescript
183
+ import { BoxerAPIError, BoxerTimeoutError, BoxerOutputLimitError } from "boxer-sdk";
184
+
185
+ try {
186
+ const result = await client.run(
187
+ "python:3.12-slim",
188
+ ["python3", "-c", "while True: pass"],
189
+ { limits: { wall_clock_secs: 5 } },
190
+ );
191
+ } catch (err) {
192
+ if (err instanceof BoxerTimeoutError) {
193
+ console.error("execution timed out");
194
+ } else if (err instanceof BoxerOutputLimitError) {
195
+ console.error("output exceeded size limit");
196
+ } else if (err instanceof BoxerAPIError) {
197
+ console.error(`API error ${err.statusCode}: ${err.message}`);
198
+ }
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Runtime compatibility
205
+
206
+ The core `boxer-sdk` package uses only web-standard APIs (`fetch`, `FormData`,
207
+ `Blob`) and has zero dependencies, making it compatible with:
208
+
209
+ - Node.js 18+
210
+ - Bun
211
+ - Deno
212
+ - Browsers
213
+
214
+ The `boxer-sdk/node` subpath export (`uploadPath`) requires a Node.js-compatible
215
+ `fs` module and is available in Node, Bun, and Deno.
216
+
217
+ ---
218
+
219
+ ## Running tests
220
+
221
+ Tests require a live Boxer server:
222
+
223
+ ```bash
224
+ pnpm install
225
+ BOXER_URL=http://localhost:8080 pnpm test
226
+ ```
227
+
228
+ Without `BOXER_URL` all tests are skipped automatically.
@@ -0,0 +1,39 @@
1
+ interface ResourceLimits {
2
+ cpu_cores?: number;
3
+ memory_mb?: number;
4
+ pids_limit?: number;
5
+ wall_clock_secs?: number;
6
+ nofile?: number;
7
+ }
8
+ interface RunResult {
9
+ exec_id: string;
10
+ exit_code: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ wall_ms: number;
14
+ }
15
+ interface RunOptions {
16
+ env?: string[];
17
+ cwd?: string;
18
+ limits?: ResourceLimits;
19
+ files?: string[];
20
+ persist?: boolean;
21
+ network?: string;
22
+ }
23
+
24
+ interface BoxerClientOptions {
25
+ baseUrl?: string;
26
+ timeout?: number;
27
+ }
28
+ declare class BoxerClient {
29
+ private readonly baseUrl;
30
+ private readonly timeout;
31
+ constructor(options?: BoxerClientOptions);
32
+ private fetch;
33
+ health(): Promise<boolean>;
34
+ run(image: string, cmd: string[], options?: RunOptions): Promise<RunResult>;
35
+ uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void>;
36
+ downloadFile(path: string): Promise<Uint8Array>;
37
+ }
38
+
39
+ export { BoxerClient as B, type ResourceLimits as R, type BoxerClientOptions as a, type RunOptions as b, type RunResult as c };
@@ -0,0 +1,39 @@
1
+ interface ResourceLimits {
2
+ cpu_cores?: number;
3
+ memory_mb?: number;
4
+ pids_limit?: number;
5
+ wall_clock_secs?: number;
6
+ nofile?: number;
7
+ }
8
+ interface RunResult {
9
+ exec_id: string;
10
+ exit_code: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ wall_ms: number;
14
+ }
15
+ interface RunOptions {
16
+ env?: string[];
17
+ cwd?: string;
18
+ limits?: ResourceLimits;
19
+ files?: string[];
20
+ persist?: boolean;
21
+ network?: string;
22
+ }
23
+
24
+ interface BoxerClientOptions {
25
+ baseUrl?: string;
26
+ timeout?: number;
27
+ }
28
+ declare class BoxerClient {
29
+ private readonly baseUrl;
30
+ private readonly timeout;
31
+ constructor(options?: BoxerClientOptions);
32
+ private fetch;
33
+ health(): Promise<boolean>;
34
+ run(image: string, cmd: string[], options?: RunOptions): Promise<RunResult>;
35
+ uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void>;
36
+ downloadFile(path: string): Promise<Uint8Array>;
37
+ }
38
+
39
+ export { BoxerClient as B, type ResourceLimits as R, type BoxerClientOptions as a, type RunOptions as b, type RunResult as c };
package/dist/index.cjs ADDED
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ BoxerAPIError: () => BoxerAPIError,
24
+ BoxerClient: () => BoxerClient,
25
+ BoxerError: () => BoxerError,
26
+ BoxerOutputLimitError: () => BoxerOutputLimitError,
27
+ BoxerTimeoutError: () => BoxerTimeoutError
28
+ });
29
+ module.exports = __toCommonJS(src_exports);
30
+
31
+ // src/errors.ts
32
+ var BoxerError = class extends Error {
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "BoxerError";
36
+ Object.setPrototypeOf(this, new.target.prototype);
37
+ }
38
+ };
39
+ var BoxerAPIError = class extends BoxerError {
40
+ constructor(message, statusCode) {
41
+ super(message);
42
+ this.statusCode = statusCode;
43
+ this.name = "BoxerAPIError";
44
+ Object.setPrototypeOf(this, new.target.prototype);
45
+ }
46
+ };
47
+ var BoxerTimeoutError = class extends BoxerAPIError {
48
+ constructor(message, statusCode) {
49
+ super(message, statusCode);
50
+ this.name = "BoxerTimeoutError";
51
+ Object.setPrototypeOf(this, new.target.prototype);
52
+ }
53
+ };
54
+ var BoxerOutputLimitError = class extends BoxerAPIError {
55
+ constructor(message, statusCode) {
56
+ super(message, statusCode);
57
+ this.name = "BoxerOutputLimitError";
58
+ Object.setPrototypeOf(this, new.target.prototype);
59
+ }
60
+ };
61
+
62
+ // src/client.ts
63
+ async function raiseForStatus(res) {
64
+ if (res.ok) return;
65
+ let detail;
66
+ try {
67
+ const body = await res.json();
68
+ detail = typeof body.error === "string" ? body.error : res.statusText;
69
+ } catch {
70
+ detail = res.statusText;
71
+ }
72
+ const code = res.status;
73
+ if (code === 408) throw new BoxerTimeoutError(detail, code);
74
+ if (code === 507) throw new BoxerOutputLimitError(detail, code);
75
+ throw new BoxerAPIError(detail, code);
76
+ }
77
+ function buildRunBody(image, cmd, options) {
78
+ const body = { image, cmd };
79
+ if (options.env?.length) body.env = options.env;
80
+ if (options.cwd && options.cwd !== "/") body.cwd = options.cwd;
81
+ if (options.limits) body.limits = limitsToObject(options.limits);
82
+ if (options.files?.length) body.files = options.files;
83
+ if (options.persist) body.persist = options.persist;
84
+ if (options.network != null) body.network = options.network;
85
+ return body;
86
+ }
87
+ function limitsToObject(limits) {
88
+ const out = {};
89
+ if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;
90
+ if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;
91
+ if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;
92
+ if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;
93
+ if (limits.nofile != null) out.nofile = limits.nofile;
94
+ return out;
95
+ }
96
+ function parseRunResult(data) {
97
+ if (data == null || typeof data !== "object" || !("exec_id" in data) || !("exit_code" in data) || !("stdout" in data) || !("stderr" in data) || !("wall_ms" in data)) {
98
+ throw new Error("Unexpected response from Boxer API: missing required fields");
99
+ }
100
+ const d = data;
101
+ return {
102
+ exec_id: String(d.exec_id),
103
+ exit_code: Number(d.exit_code),
104
+ stdout: String(d.stdout),
105
+ stderr: String(d.stderr),
106
+ wall_ms: Number(d.wall_ms)
107
+ };
108
+ }
109
+ var BoxerClient = class {
110
+ constructor(options = {}) {
111
+ this.baseUrl = (options.baseUrl ?? "http://localhost:8080").replace(/\/$/, "");
112
+ this.timeout = options.timeout ?? 12e4;
113
+ }
114
+ async fetch(path, init = {}) {
115
+ const controller = new AbortController();
116
+ const timer = setTimeout(() => controller.abort(), this.timeout);
117
+ try {
118
+ return await fetch(`${this.baseUrl}${path}`, {
119
+ ...init,
120
+ signal: controller.signal
121
+ });
122
+ } finally {
123
+ clearTimeout(timer);
124
+ }
125
+ }
126
+ async health() {
127
+ const res = await this.fetch("/healthz");
128
+ return res.ok;
129
+ }
130
+ async run(image, cmd, options = {}) {
131
+ const body = buildRunBody(image, cmd, options);
132
+ const res = await this.fetch("/run", {
133
+ method: "POST",
134
+ headers: { "Content-Type": "application/json" },
135
+ body: JSON.stringify(body)
136
+ });
137
+ await raiseForStatus(res);
138
+ return parseRunResult(await res.json());
139
+ }
140
+ async uploadFile(remotePath, content) {
141
+ const form = new FormData();
142
+ form.append("path", remotePath);
143
+ let blob;
144
+ if (content instanceof Blob) {
145
+ blob = content;
146
+ } else if (content instanceof Uint8Array) {
147
+ blob = new Blob([new Uint8Array(content)], { type: "application/octet-stream" });
148
+ } else {
149
+ blob = new Blob([content], { type: "application/octet-stream" });
150
+ }
151
+ form.append("file", blob, remotePath.split("/").pop() ?? "file");
152
+ const res = await this.fetch("/files", { method: "POST", body: form });
153
+ await raiseForStatus(res);
154
+ }
155
+ async downloadFile(path) {
156
+ const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);
157
+ await raiseForStatus(res);
158
+ return new Uint8Array(await res.arrayBuffer());
159
+ }
160
+ };
161
+ // Annotate the CommonJS export names for ESM import in node:
162
+ 0 && (module.exports = {
163
+ BoxerAPIError,
164
+ BoxerClient,
165
+ BoxerError,
166
+ BoxerOutputLimitError,
167
+ BoxerTimeoutError
168
+ });
169
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { BoxerClient } from \"./client.js\";\nexport type { BoxerClientOptions } from \"./client.js\";\nexport { BoxerAPIError, BoxerError, BoxerOutputLimitError, BoxerTimeoutError } from \"./errors.js\";\nexport type { ResourceLimits, RunOptions, RunResult } from \"./types.js\";\n","export class BoxerError extends Error {\n override name = \"BoxerError\";\n\n constructor(message: string) {\n super(message);\n // Restore prototype chain for instanceof checks across transpile boundaries\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerAPIError extends BoxerError {\n override name = \"BoxerAPIError\";\n\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerTimeoutError extends BoxerAPIError {\n override name = \"BoxerTimeoutError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerOutputLimitError extends BoxerAPIError {\n override name = \"BoxerOutputLimitError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { BoxerAPIError, BoxerOutputLimitError, BoxerTimeoutError } from \"./errors.js\";\nimport type { ResourceLimits, RunOptions, RunResult } from \"./types.js\";\n\nexport interface BoxerClientOptions {\n baseUrl?: string;\n timeout?: number;\n}\n\nasync function raiseForStatus(res: Response): Promise<void> {\n if (res.ok) return;\n\n let detail: string;\n try {\n const body = (await res.json()) as Record<string, unknown>;\n detail = typeof body.error === \"string\" ? body.error : res.statusText;\n } catch {\n detail = res.statusText;\n }\n\n const code = res.status;\n if (code === 408) throw new BoxerTimeoutError(detail, code);\n if (code === 507) throw new BoxerOutputLimitError(detail, code);\n throw new BoxerAPIError(detail, code);\n}\n\nfunction buildRunBody(image: string, cmd: string[], options: RunOptions): Record<string, unknown> {\n const body: Record<string, unknown> = { image, cmd };\n if (options.env?.length) body.env = options.env;\n if (options.cwd && options.cwd !== \"/\") body.cwd = options.cwd;\n if (options.limits) body.limits = limitsToObject(options.limits);\n if (options.files?.length) body.files = options.files;\n if (options.persist) body.persist = options.persist;\n if (options.network != null) body.network = options.network;\n return body;\n}\n\nfunction limitsToObject(limits: ResourceLimits): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;\n if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;\n if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;\n if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;\n if (limits.nofile != null) out.nofile = limits.nofile;\n return out;\n}\n\nfunction parseRunResult(data: unknown): RunResult {\n if (\n data == null ||\n typeof data !== \"object\" ||\n !(\"exec_id\" in data) ||\n !(\"exit_code\" in data) ||\n !(\"stdout\" in data) ||\n !(\"stderr\" in data) ||\n !(\"wall_ms\" in data)\n ) {\n throw new Error(\"Unexpected response from Boxer API: missing required fields\");\n }\n\n const d = data as Record<string, unknown>;\n return {\n exec_id: String(d.exec_id),\n exit_code: Number(d.exit_code),\n stdout: String(d.stdout),\n stderr: String(d.stderr),\n wall_ms: Number(d.wall_ms),\n };\n}\n\nexport class BoxerClient {\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: BoxerClientOptions = {}) {\n this.baseUrl = (options.baseUrl ?? \"http://localhost:8080\").replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? 120_000;\n }\n\n private async fetch(path: string, init: RequestInit = {}): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n return await fetch(`${this.baseUrl}${path}`, {\n ...init,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n async health(): Promise<boolean> {\n const res = await this.fetch(\"/healthz\");\n return res.ok;\n }\n\n async run(image: string, cmd: string[], options: RunOptions = {}): Promise<RunResult> {\n const body = buildRunBody(image, cmd, options);\n const res = await this.fetch(\"/run\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n await raiseForStatus(res);\n return parseRunResult(await res.json());\n }\n\n async uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void> {\n const form = new FormData();\n form.append(\"path\", remotePath);\n let blob: Blob;\n if (content instanceof Blob) {\n blob = content;\n } else if (content instanceof Uint8Array) {\n // Copy via ArrayLike<number> to ensure ArrayBuffer (not SharedArrayBuffer)\n blob = new Blob([new Uint8Array(content)], { type: \"application/octet-stream\" });\n } else {\n blob = new Blob([content], { type: \"application/octet-stream\" });\n }\n form.append(\"file\", blob, remotePath.split(\"/\").pop() ?? \"file\");\n\n const res = await this.fetch(\"/files\", { method: \"POST\", body: form });\n await raiseForStatus(res);\n }\n\n async downloadFile(path: string): Promise<Uint8Array> {\n const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);\n await raiseForStatus(res);\n return new Uint8Array(await res.arrayBuffer());\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,21 @@
1
+ export { B as BoxerClient, a as BoxerClientOptions, R as ResourceLimits, b as RunOptions, c as RunResult } from './client-DpDp9i-R.cjs';
2
+
3
+ declare class BoxerError extends Error {
4
+ name: string;
5
+ constructor(message: string);
6
+ }
7
+ declare class BoxerAPIError extends BoxerError {
8
+ readonly statusCode: number;
9
+ name: string;
10
+ constructor(message: string, statusCode: number);
11
+ }
12
+ declare class BoxerTimeoutError extends BoxerAPIError {
13
+ name: string;
14
+ constructor(message: string, statusCode: number);
15
+ }
16
+ declare class BoxerOutputLimitError extends BoxerAPIError {
17
+ name: string;
18
+ constructor(message: string, statusCode: number);
19
+ }
20
+
21
+ export { BoxerAPIError, BoxerError, BoxerOutputLimitError, BoxerTimeoutError };
@@ -0,0 +1,21 @@
1
+ export { B as BoxerClient, a as BoxerClientOptions, R as ResourceLimits, b as RunOptions, c as RunResult } from './client-DpDp9i-R.js';
2
+
3
+ declare class BoxerError extends Error {
4
+ name: string;
5
+ constructor(message: string);
6
+ }
7
+ declare class BoxerAPIError extends BoxerError {
8
+ readonly statusCode: number;
9
+ name: string;
10
+ constructor(message: string, statusCode: number);
11
+ }
12
+ declare class BoxerTimeoutError extends BoxerAPIError {
13
+ name: string;
14
+ constructor(message: string, statusCode: number);
15
+ }
16
+ declare class BoxerOutputLimitError extends BoxerAPIError {
17
+ name: string;
18
+ constructor(message: string, statusCode: number);
19
+ }
20
+
21
+ export { BoxerAPIError, BoxerError, BoxerOutputLimitError, BoxerTimeoutError };
package/dist/index.js ADDED
@@ -0,0 +1,138 @@
1
+ // src/errors.ts
2
+ var BoxerError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "BoxerError";
6
+ Object.setPrototypeOf(this, new.target.prototype);
7
+ }
8
+ };
9
+ var BoxerAPIError = class extends BoxerError {
10
+ constructor(message, statusCode) {
11
+ super(message);
12
+ this.statusCode = statusCode;
13
+ this.name = "BoxerAPIError";
14
+ Object.setPrototypeOf(this, new.target.prototype);
15
+ }
16
+ };
17
+ var BoxerTimeoutError = class extends BoxerAPIError {
18
+ constructor(message, statusCode) {
19
+ super(message, statusCode);
20
+ this.name = "BoxerTimeoutError";
21
+ Object.setPrototypeOf(this, new.target.prototype);
22
+ }
23
+ };
24
+ var BoxerOutputLimitError = class extends BoxerAPIError {
25
+ constructor(message, statusCode) {
26
+ super(message, statusCode);
27
+ this.name = "BoxerOutputLimitError";
28
+ Object.setPrototypeOf(this, new.target.prototype);
29
+ }
30
+ };
31
+
32
+ // src/client.ts
33
+ async function raiseForStatus(res) {
34
+ if (res.ok) return;
35
+ let detail;
36
+ try {
37
+ const body = await res.json();
38
+ detail = typeof body.error === "string" ? body.error : res.statusText;
39
+ } catch {
40
+ detail = res.statusText;
41
+ }
42
+ const code = res.status;
43
+ if (code === 408) throw new BoxerTimeoutError(detail, code);
44
+ if (code === 507) throw new BoxerOutputLimitError(detail, code);
45
+ throw new BoxerAPIError(detail, code);
46
+ }
47
+ function buildRunBody(image, cmd, options) {
48
+ const body = { image, cmd };
49
+ if (options.env?.length) body.env = options.env;
50
+ if (options.cwd && options.cwd !== "/") body.cwd = options.cwd;
51
+ if (options.limits) body.limits = limitsToObject(options.limits);
52
+ if (options.files?.length) body.files = options.files;
53
+ if (options.persist) body.persist = options.persist;
54
+ if (options.network != null) body.network = options.network;
55
+ return body;
56
+ }
57
+ function limitsToObject(limits) {
58
+ const out = {};
59
+ if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;
60
+ if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;
61
+ if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;
62
+ if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;
63
+ if (limits.nofile != null) out.nofile = limits.nofile;
64
+ return out;
65
+ }
66
+ function parseRunResult(data) {
67
+ if (data == null || typeof data !== "object" || !("exec_id" in data) || !("exit_code" in data) || !("stdout" in data) || !("stderr" in data) || !("wall_ms" in data)) {
68
+ throw new Error("Unexpected response from Boxer API: missing required fields");
69
+ }
70
+ const d = data;
71
+ return {
72
+ exec_id: String(d.exec_id),
73
+ exit_code: Number(d.exit_code),
74
+ stdout: String(d.stdout),
75
+ stderr: String(d.stderr),
76
+ wall_ms: Number(d.wall_ms)
77
+ };
78
+ }
79
+ var BoxerClient = class {
80
+ constructor(options = {}) {
81
+ this.baseUrl = (options.baseUrl ?? "http://localhost:8080").replace(/\/$/, "");
82
+ this.timeout = options.timeout ?? 12e4;
83
+ }
84
+ async fetch(path, init = {}) {
85
+ const controller = new AbortController();
86
+ const timer = setTimeout(() => controller.abort(), this.timeout);
87
+ try {
88
+ return await fetch(`${this.baseUrl}${path}`, {
89
+ ...init,
90
+ signal: controller.signal
91
+ });
92
+ } finally {
93
+ clearTimeout(timer);
94
+ }
95
+ }
96
+ async health() {
97
+ const res = await this.fetch("/healthz");
98
+ return res.ok;
99
+ }
100
+ async run(image, cmd, options = {}) {
101
+ const body = buildRunBody(image, cmd, options);
102
+ const res = await this.fetch("/run", {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify(body)
106
+ });
107
+ await raiseForStatus(res);
108
+ return parseRunResult(await res.json());
109
+ }
110
+ async uploadFile(remotePath, content) {
111
+ const form = new FormData();
112
+ form.append("path", remotePath);
113
+ let blob;
114
+ if (content instanceof Blob) {
115
+ blob = content;
116
+ } else if (content instanceof Uint8Array) {
117
+ blob = new Blob([new Uint8Array(content)], { type: "application/octet-stream" });
118
+ } else {
119
+ blob = new Blob([content], { type: "application/octet-stream" });
120
+ }
121
+ form.append("file", blob, remotePath.split("/").pop() ?? "file");
122
+ const res = await this.fetch("/files", { method: "POST", body: form });
123
+ await raiseForStatus(res);
124
+ }
125
+ async downloadFile(path) {
126
+ const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);
127
+ await raiseForStatus(res);
128
+ return new Uint8Array(await res.arrayBuffer());
129
+ }
130
+ };
131
+ export {
132
+ BoxerAPIError,
133
+ BoxerClient,
134
+ BoxerError,
135
+ BoxerOutputLimitError,
136
+ BoxerTimeoutError
137
+ };
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export class BoxerError extends Error {\n override name = \"BoxerError\";\n\n constructor(message: string) {\n super(message);\n // Restore prototype chain for instanceof checks across transpile boundaries\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerAPIError extends BoxerError {\n override name = \"BoxerAPIError\";\n\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerTimeoutError extends BoxerAPIError {\n override name = \"BoxerTimeoutError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerOutputLimitError extends BoxerAPIError {\n override name = \"BoxerOutputLimitError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { BoxerAPIError, BoxerOutputLimitError, BoxerTimeoutError } from \"./errors.js\";\nimport type { ResourceLimits, RunOptions, RunResult } from \"./types.js\";\n\nexport interface BoxerClientOptions {\n baseUrl?: string;\n timeout?: number;\n}\n\nasync function raiseForStatus(res: Response): Promise<void> {\n if (res.ok) return;\n\n let detail: string;\n try {\n const body = (await res.json()) as Record<string, unknown>;\n detail = typeof body.error === \"string\" ? body.error : res.statusText;\n } catch {\n detail = res.statusText;\n }\n\n const code = res.status;\n if (code === 408) throw new BoxerTimeoutError(detail, code);\n if (code === 507) throw new BoxerOutputLimitError(detail, code);\n throw new BoxerAPIError(detail, code);\n}\n\nfunction buildRunBody(image: string, cmd: string[], options: RunOptions): Record<string, unknown> {\n const body: Record<string, unknown> = { image, cmd };\n if (options.env?.length) body.env = options.env;\n if (options.cwd && options.cwd !== \"/\") body.cwd = options.cwd;\n if (options.limits) body.limits = limitsToObject(options.limits);\n if (options.files?.length) body.files = options.files;\n if (options.persist) body.persist = options.persist;\n if (options.network != null) body.network = options.network;\n return body;\n}\n\nfunction limitsToObject(limits: ResourceLimits): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;\n if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;\n if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;\n if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;\n if (limits.nofile != null) out.nofile = limits.nofile;\n return out;\n}\n\nfunction parseRunResult(data: unknown): RunResult {\n if (\n data == null ||\n typeof data !== \"object\" ||\n !(\"exec_id\" in data) ||\n !(\"exit_code\" in data) ||\n !(\"stdout\" in data) ||\n !(\"stderr\" in data) ||\n !(\"wall_ms\" in data)\n ) {\n throw new Error(\"Unexpected response from Boxer API: missing required fields\");\n }\n\n const d = data as Record<string, unknown>;\n return {\n exec_id: String(d.exec_id),\n exit_code: Number(d.exit_code),\n stdout: String(d.stdout),\n stderr: String(d.stderr),\n wall_ms: Number(d.wall_ms),\n };\n}\n\nexport class BoxerClient {\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: BoxerClientOptions = {}) {\n this.baseUrl = (options.baseUrl ?? \"http://localhost:8080\").replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? 120_000;\n }\n\n private async fetch(path: string, init: RequestInit = {}): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n return await fetch(`${this.baseUrl}${path}`, {\n ...init,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n async health(): Promise<boolean> {\n const res = await this.fetch(\"/healthz\");\n return res.ok;\n }\n\n async run(image: string, cmd: string[], options: RunOptions = {}): Promise<RunResult> {\n const body = buildRunBody(image, cmd, options);\n const res = await this.fetch(\"/run\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n await raiseForStatus(res);\n return parseRunResult(await res.json());\n }\n\n async uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void> {\n const form = new FormData();\n form.append(\"path\", remotePath);\n let blob: Blob;\n if (content instanceof Blob) {\n blob = content;\n } else if (content instanceof Uint8Array) {\n // Copy via ArrayLike<number> to ensure ArrayBuffer (not SharedArrayBuffer)\n blob = new Blob([new Uint8Array(content)], { type: \"application/octet-stream\" });\n } else {\n blob = new Blob([content], { type: \"application/octet-stream\" });\n }\n form.append(\"file\", blob, remotePath.split(\"/\").pop() ?? \"file\");\n\n const res = await this.fetch(\"/files\", { method: \"POST\", body: form });\n await raiseForStatus(res);\n }\n\n async downloadFile(path: string): Promise<Uint8Array> {\n const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);\n await raiseForStatus(res);\n return new Uint8Array(await res.arrayBuffer());\n }\n}\n"],"mappings":"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","names":[]}
package/dist/node.cjs ADDED
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/node.ts
21
+ var node_exports = {};
22
+ __export(node_exports, {
23
+ uploadPath: () => uploadPath
24
+ });
25
+ module.exports = __toCommonJS(node_exports);
26
+ var import_promises = require("fs/promises");
27
+ var import_node_path = require("path");
28
+ async function uploadPath(client, localPath, remotePath) {
29
+ const info = await (0, import_promises.stat)(localPath);
30
+ if (info.isDirectory()) {
31
+ const prefix = remotePath ?? (0, import_node_path.basename)(localPath);
32
+ const uploaded = [];
33
+ await uploadDir(client, localPath, localPath, prefix, uploaded);
34
+ return uploaded.sort();
35
+ }
36
+ const dest = remotePath ?? (0, import_node_path.basename)(localPath);
37
+ const content = await (0, import_promises.readFile)(localPath);
38
+ await client.uploadFile(dest, content);
39
+ return [dest];
40
+ }
41
+ async function uploadDir(client, rootDir, currentDir, prefix, uploaded) {
42
+ const entries = await (0, import_promises.readdir)(currentDir, { withFileTypes: true });
43
+ await Promise.all(
44
+ entries.map(async (entry) => {
45
+ const fullPath = (0, import_node_path.join)(currentDir, entry.name);
46
+ if (entry.isDirectory()) {
47
+ await uploadDir(client, rootDir, fullPath, prefix, uploaded);
48
+ } else if (entry.isFile()) {
49
+ const rel = (0, import_node_path.relative)(rootDir, fullPath);
50
+ const dest = `${prefix}/${rel.split("\\").join("/")}`;
51
+ const content = await (0, import_promises.readFile)(fullPath);
52
+ await client.uploadFile(dest, content);
53
+ uploaded.push(dest);
54
+ }
55
+ })
56
+ );
57
+ }
58
+ // Annotate the CommonJS export names for ESM import in node:
59
+ 0 && (module.exports = {
60
+ uploadPath
61
+ });
62
+ //# sourceMappingURL=node.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node.ts"],"sourcesContent":["import { readFile, readdir, stat } from \"node:fs/promises\";\nimport { basename, join, relative } from \"node:path\";\nimport type { BoxerClient } from \"./client.js\";\n\n/**\n * Upload a local file or directory to the Boxer file store.\n *\n * If `localPath` is a directory, all files inside it are uploaded recursively,\n * preserving the directory structure under `remotePath` (defaults to the\n * directory name).\n *\n * Returns the list of remote paths that were uploaded.\n *\n * Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).\n */\nexport async function uploadPath(\n client: BoxerClient,\n localPath: string,\n remotePath?: string,\n): Promise<string[]> {\n const info = await stat(localPath);\n\n if (info.isDirectory()) {\n const prefix = remotePath ?? basename(localPath);\n const uploaded: string[] = [];\n await uploadDir(client, localPath, localPath, prefix, uploaded);\n return uploaded.sort();\n }\n\n const dest = remotePath ?? basename(localPath);\n const content = await readFile(localPath);\n await client.uploadFile(dest, content);\n return [dest];\n}\n\nasync function uploadDir(\n client: BoxerClient,\n rootDir: string,\n currentDir: string,\n prefix: string,\n uploaded: string[],\n): Promise<void> {\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n await Promise.all(\n entries.map(async (entry) => {\n const fullPath = join(currentDir, entry.name);\n if (entry.isDirectory()) {\n await uploadDir(client, rootDir, fullPath, prefix, uploaded);\n } else if (entry.isFile()) {\n const rel = relative(rootDir, fullPath);\n const dest = `${prefix}/${rel.split(\"\\\\\").join(\"/\")}`;\n const content = await readFile(fullPath);\n await client.uploadFile(dest, content);\n uploaded.push(dest);\n }\n }),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAwC;AACxC,uBAAyC;AAczC,eAAsB,WACpB,QACA,WACA,YACmB;AACnB,QAAM,OAAO,UAAM,sBAAK,SAAS;AAEjC,MAAI,KAAK,YAAY,GAAG;AACtB,UAAM,SAAS,kBAAc,2BAAS,SAAS;AAC/C,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAU,QAAQ,WAAW,WAAW,QAAQ,QAAQ;AAC9D,WAAO,SAAS,KAAK;AAAA,EACvB;AAEA,QAAM,OAAO,kBAAc,2BAAS,SAAS;AAC7C,QAAM,UAAU,UAAM,0BAAS,SAAS;AACxC,QAAM,OAAO,WAAW,MAAM,OAAO;AACrC,SAAO,CAAC,IAAI;AACd;AAEA,eAAe,UACb,QACA,SACA,YACA,QACA,UACe;AACf,QAAM,UAAU,UAAM,yBAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AAEjE,QAAM,QAAQ;AAAA,IACZ,QAAQ,IAAI,OAAO,UAAU;AAC3B,YAAM,eAAW,uBAAK,YAAY,MAAM,IAAI;AAC5C,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,UAAU,QAAQ,SAAS,UAAU,QAAQ,QAAQ;AAAA,MAC7D,WAAW,MAAM,OAAO,GAAG;AACzB,cAAM,UAAM,2BAAS,SAAS,QAAQ;AACtC,cAAM,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC;AACnD,cAAM,UAAU,UAAM,0BAAS,QAAQ;AACvC,cAAM,OAAO,WAAW,MAAM,OAAO;AACrC,iBAAS,KAAK,IAAI;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
@@ -0,0 +1,16 @@
1
+ import { B as BoxerClient } from './client-DpDp9i-R.cjs';
2
+
3
+ /**
4
+ * Upload a local file or directory to the Boxer file store.
5
+ *
6
+ * If `localPath` is a directory, all files inside it are uploaded recursively,
7
+ * preserving the directory structure under `remotePath` (defaults to the
8
+ * directory name).
9
+ *
10
+ * Returns the list of remote paths that were uploaded.
11
+ *
12
+ * Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).
13
+ */
14
+ declare function uploadPath(client: BoxerClient, localPath: string, remotePath?: string): Promise<string[]>;
15
+
16
+ export { uploadPath };
package/dist/node.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { B as BoxerClient } from './client-DpDp9i-R.js';
2
+
3
+ /**
4
+ * Upload a local file or directory to the Boxer file store.
5
+ *
6
+ * If `localPath` is a directory, all files inside it are uploaded recursively,
7
+ * preserving the directory structure under `remotePath` (defaults to the
8
+ * directory name).
9
+ *
10
+ * Returns the list of remote paths that were uploaded.
11
+ *
12
+ * Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).
13
+ */
14
+ declare function uploadPath(client: BoxerClient, localPath: string, remotePath?: string): Promise<string[]>;
15
+
16
+ export { uploadPath };
package/dist/node.js ADDED
@@ -0,0 +1,37 @@
1
+ // src/node.ts
2
+ import { readFile, readdir, stat } from "fs/promises";
3
+ import { basename, join, relative } from "path";
4
+ async function uploadPath(client, localPath, remotePath) {
5
+ const info = await stat(localPath);
6
+ if (info.isDirectory()) {
7
+ const prefix = remotePath ?? basename(localPath);
8
+ const uploaded = [];
9
+ await uploadDir(client, localPath, localPath, prefix, uploaded);
10
+ return uploaded.sort();
11
+ }
12
+ const dest = remotePath ?? basename(localPath);
13
+ const content = await readFile(localPath);
14
+ await client.uploadFile(dest, content);
15
+ return [dest];
16
+ }
17
+ async function uploadDir(client, rootDir, currentDir, prefix, uploaded) {
18
+ const entries = await readdir(currentDir, { withFileTypes: true });
19
+ await Promise.all(
20
+ entries.map(async (entry) => {
21
+ const fullPath = join(currentDir, entry.name);
22
+ if (entry.isDirectory()) {
23
+ await uploadDir(client, rootDir, fullPath, prefix, uploaded);
24
+ } else if (entry.isFile()) {
25
+ const rel = relative(rootDir, fullPath);
26
+ const dest = `${prefix}/${rel.split("\\").join("/")}`;
27
+ const content = await readFile(fullPath);
28
+ await client.uploadFile(dest, content);
29
+ uploaded.push(dest);
30
+ }
31
+ })
32
+ );
33
+ }
34
+ export {
35
+ uploadPath
36
+ };
37
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/node.ts"],"sourcesContent":["import { readFile, readdir, stat } from \"node:fs/promises\";\nimport { basename, join, relative } from \"node:path\";\nimport type { BoxerClient } from \"./client.js\";\n\n/**\n * Upload a local file or directory to the Boxer file store.\n *\n * If `localPath` is a directory, all files inside it are uploaded recursively,\n * preserving the directory structure under `remotePath` (defaults to the\n * directory name).\n *\n * Returns the list of remote paths that were uploaded.\n *\n * Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).\n */\nexport async function uploadPath(\n client: BoxerClient,\n localPath: string,\n remotePath?: string,\n): Promise<string[]> {\n const info = await stat(localPath);\n\n if (info.isDirectory()) {\n const prefix = remotePath ?? basename(localPath);\n const uploaded: string[] = [];\n await uploadDir(client, localPath, localPath, prefix, uploaded);\n return uploaded.sort();\n }\n\n const dest = remotePath ?? basename(localPath);\n const content = await readFile(localPath);\n await client.uploadFile(dest, content);\n return [dest];\n}\n\nasync function uploadDir(\n client: BoxerClient,\n rootDir: string,\n currentDir: string,\n prefix: string,\n uploaded: string[],\n): Promise<void> {\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n await Promise.all(\n entries.map(async (entry) => {\n const fullPath = join(currentDir, entry.name);\n if (entry.isDirectory()) {\n await uploadDir(client, rootDir, fullPath, prefix, uploaded);\n } else if (entry.isFile()) {\n const rel = relative(rootDir, fullPath);\n const dest = `${prefix}/${rel.split(\"\\\\\").join(\"/\")}`;\n const content = await readFile(fullPath);\n await client.uploadFile(dest, content);\n uploaded.push(dest);\n }\n }),\n );\n}\n"],"mappings":";AAAA,SAAS,UAAU,SAAS,YAAY;AACxC,SAAS,UAAU,MAAM,gBAAgB;AAczC,eAAsB,WACpB,QACA,WACA,YACmB;AACnB,QAAM,OAAO,MAAM,KAAK,SAAS;AAEjC,MAAI,KAAK,YAAY,GAAG;AACtB,UAAM,SAAS,cAAc,SAAS,SAAS;AAC/C,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAU,QAAQ,WAAW,WAAW,QAAQ,QAAQ;AAC9D,WAAO,SAAS,KAAK;AAAA,EACvB;AAEA,QAAM,OAAO,cAAc,SAAS,SAAS;AAC7C,QAAM,UAAU,MAAM,SAAS,SAAS;AACxC,QAAM,OAAO,WAAW,MAAM,OAAO;AACrC,SAAO,CAAC,IAAI;AACd;AAEA,eAAe,UACb,QACA,SACA,YACA,QACA,UACe;AACf,QAAM,UAAU,MAAM,QAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AAEjE,QAAM,QAAQ;AAAA,IACZ,QAAQ,IAAI,OAAO,UAAU;AAC3B,YAAM,WAAW,KAAK,YAAY,MAAM,IAAI;AAC5C,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,UAAU,QAAQ,SAAS,UAAU,QAAQ,QAAQ;AAAA,MAC7D,WAAW,MAAM,OAAO,GAAG;AACzB,cAAM,MAAM,SAAS,SAAS,QAAQ;AACtC,cAAM,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC;AACnD,cAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,cAAM,OAAO,WAAW,MAAM,OAAO;AACrC,iBAAS,KAAK,IAAI;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "boxer-sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for the Boxer sandboxed container execution service",
5
+ "license": "Apache-2.0",
6
+ "author": "Alexander Kolupaev <theonekeyg@gmail.com>",
7
+ "homepage": "https://github.com/theonekeyg/boxer",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./node": {
16
+ "types": "./dist/node.d.ts",
17
+ "import": "./dist/node.mjs",
18
+ "require": "./dist/node.cjs"
19
+ }
20
+ },
21
+ "main": "./dist/index.cjs",
22
+ "module": "./dist/index.mjs",
23
+ "types": "./dist/index.d.ts",
24
+ "files": ["dist"],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "lint": "biome check .",
28
+ "lint:fix": "biome check --write .",
29
+ "format": "biome format --write .",
30
+ "test": "vitest run",
31
+ "typecheck": "tsc --noEmit"
32
+ },
33
+ "pnpm": {
34
+ "onlyBuiltDependencies": ["@biomejs/biome", "esbuild"]
35
+ },
36
+ "devDependencies": {
37
+ "@biomejs/biome": "^1.9.4",
38
+ "@types/node": "^22.0.0",
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.6.0",
41
+ "vitest": "^2.0.0"
42
+ }
43
+ }