@hatchet-dev/sandbox 0.1.4 → 0.1.5
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/CHANGELOG.md +10 -0
- package/dist/index.cjs +202 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -25
- package/dist/index.d.ts +8 -25
- package/dist/index.js +202 -118
- package/dist/index.js.map +1 -1
- package/dist/wire.cjs +51 -0
- package/dist/wire.cjs.map +1 -0
- package/dist/wire.d.cts +52 -0
- package/dist/wire.d.ts +52 -0
- package/dist/wire.js +44 -0
- package/dist/wire.js.map +1 -0
- package/package.json +12 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Eac
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.5] - 2026-11-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `@hatchet-dev/sandbox/wire` exports the Zod schemas and types of the sandbox task protocol, `turnInput`, `turnResult`, `commandOutput`, `sandboxInfo`, `resources` and `metadata`. It imports nothing but `zod`, so code that spawns the sandbox task itself, such as a serverless durable task on an edge runtime where the main entry cannot load, can share the wire types instead of writing its own.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Every option the SDK accepts is now checked with Zod before a task is sent. Unknown option keys, an empty sandbox ID, path, or command, a non-positive `idleTimeoutMs`, `timeoutMs`, or `depth`, a sandbox `id` outside lowercase letters, digits, and hyphens, `inheritParentMetadata` without `from`, and a `download` or `upload` URL that is not https are rejected with a `ZodError`. The `resources` and `delete` checks that already existed throw a `ZodError` instead of a plain `Error`. Provider responses are checked the same way, so a malformed result fails the call instead of surfacing later as a missing field. `zod` 4 is a dependency of the package.
|
|
18
|
+
|
|
9
19
|
## [0.1.4] - 2026-11-09
|
|
10
20
|
|
|
11
21
|
### Fixed
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var v1 = require('@hatchet-dev/typescript-sdk/v1');
|
|
4
|
+
var zod = require('zod');
|
|
4
5
|
var fs = require('fs/promises');
|
|
5
6
|
|
|
6
7
|
function _interopNamespace(e) {
|
|
@@ -32,10 +33,40 @@ var FilesystemError = class extends Error {
|
|
|
32
33
|
}
|
|
33
34
|
code;
|
|
34
35
|
};
|
|
35
|
-
var
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
var path = zod.z.string().min(1);
|
|
37
|
+
var positiveInt = zod.z.int().positive();
|
|
38
|
+
var httpsUrl = zod.z.url({ protocol: /^https$/ });
|
|
39
|
+
var fileData = zod.z.union([
|
|
40
|
+
zod.z.string(),
|
|
41
|
+
zod.z.instanceof(Uint8Array),
|
|
42
|
+
zod.z.instanceof(ArrayBuffer),
|
|
43
|
+
zod.z.instanceof(Blob)
|
|
44
|
+
]);
|
|
45
|
+
var writeEntries = zod.z.strictObject({ path, data: fileData }).array();
|
|
46
|
+
var readOpts = zod.z.strictObject({ format: zod.z.enum(["text", "bytes", "blob"]).optional() });
|
|
47
|
+
var listOpts = zod.z.strictObject({ depth: positiveInt.optional() });
|
|
48
|
+
var transferOpts = zod.z.strictObject({ timeoutMs: positiveInt.optional() });
|
|
49
|
+
var entry = zod.z.looseObject({
|
|
50
|
+
name: zod.z.string(),
|
|
51
|
+
path: zod.z.string(),
|
|
52
|
+
type: zod.z.enum(["file", "dir"]),
|
|
53
|
+
size: zod.z.number(),
|
|
54
|
+
mode: zod.z.number(),
|
|
55
|
+
permissions: zod.z.string(),
|
|
56
|
+
modifiedTime: zod.z.coerce.date().optional()
|
|
38
57
|
});
|
|
58
|
+
var fsOutput = zod.z.looseObject({
|
|
59
|
+
content: zod.z.string().optional(),
|
|
60
|
+
stagedKey: zod.z.string().optional(),
|
|
61
|
+
url: zod.z.string().optional(),
|
|
62
|
+
putUrl: zod.z.string().optional(),
|
|
63
|
+
getUrl: zod.z.string().optional(),
|
|
64
|
+
entry: entry.nullish(),
|
|
65
|
+
entries: entry.array().optional(),
|
|
66
|
+
created: zod.z.boolean().optional(),
|
|
67
|
+
error: zod.z.object({ code: zod.z.string(), message: zod.z.string() }).optional()
|
|
68
|
+
});
|
|
69
|
+
var fsOutputWithEntry = fsOutput.extend({ entry });
|
|
39
70
|
var STAGE_OVER_BYTES = 2 << 20;
|
|
40
71
|
async function toBytes(data) {
|
|
41
72
|
if (typeof data === "string") return Buffer.from(data, "utf8");
|
|
@@ -71,16 +102,18 @@ var Files = class {
|
|
|
71
102
|
this.turn = turn;
|
|
72
103
|
}
|
|
73
104
|
turn;
|
|
74
|
-
async call(fn, input, fresh = false) {
|
|
75
|
-
const out = await this.turn(`$fs.${fn}`, input, fresh);
|
|
76
|
-
if (out.error)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
async call(fn, input, fresh = false, schema = fsOutput) {
|
|
106
|
+
const out = fsOutput.parse(await this.turn(`$fs.${fn}`, input, fresh));
|
|
107
|
+
if (out.error)
|
|
108
|
+
throw new FilesystemError(out.error.code, out.error.message);
|
|
109
|
+
return schema.parse(out);
|
|
110
|
+
}
|
|
111
|
+
async read(p, opts = {}) {
|
|
112
|
+
const { format } = readOpts.parse(opts);
|
|
113
|
+
const out = await this.call("read", { path: path.parse(p) });
|
|
81
114
|
const bytes = out.url ? await this.readStaged(out.url, out.stagedKey) : Buffer.from(out.content ?? "", "base64");
|
|
82
|
-
if (
|
|
83
|
-
if (
|
|
115
|
+
if (format === "bytes") return new Uint8Array(bytes);
|
|
116
|
+
if (format === "blob") return new Blob([new Uint8Array(bytes)]);
|
|
84
117
|
return bytes.toString("utf8");
|
|
85
118
|
}
|
|
86
119
|
/** A replayed durable read may hold a URL that has expired; the provider signs the same staged file again. The renewal is a plain run, not a durable child, so the replayed child sequence stays aligned. */
|
|
@@ -95,11 +128,12 @@ var Files = class {
|
|
|
95
128
|
}
|
|
96
129
|
async write(pathOrFiles, data) {
|
|
97
130
|
if (typeof pathOrFiles !== "string") return this.writeFiles(pathOrFiles);
|
|
98
|
-
const [
|
|
99
|
-
return
|
|
131
|
+
const [entry2] = await this.writeFiles([{ path: pathOrFiles, data: data ?? "" }]);
|
|
132
|
+
return entry2;
|
|
100
133
|
}
|
|
101
134
|
/** Writes several files in one task. */
|
|
102
135
|
async writeFiles(files) {
|
|
136
|
+
writeEntries.parse(files);
|
|
103
137
|
const bytes = await Promise.all(files.map((f) => toBytes(f.data)));
|
|
104
138
|
const encoded = [];
|
|
105
139
|
for (const [i, f] of files.entries()) {
|
|
@@ -119,58 +153,70 @@ var Files = class {
|
|
|
119
153
|
}
|
|
120
154
|
}
|
|
121
155
|
const { entries = [] } = await this.call("write", { files: encoded });
|
|
122
|
-
return entries
|
|
156
|
+
return entries;
|
|
123
157
|
}
|
|
124
158
|
/** Lists a directory. `depth` 1 (default) is the directory itself; higher values recurse. No snapshot is made. */
|
|
125
|
-
async list(
|
|
126
|
-
const {
|
|
127
|
-
|
|
159
|
+
async list(p = ".", opts = {}) {
|
|
160
|
+
const { depth = 1 } = listOpts.parse(opts);
|
|
161
|
+
const { entries = [] } = await this.call("list", { path: path.parse(p), depth });
|
|
162
|
+
return entries;
|
|
128
163
|
}
|
|
129
164
|
/** True if the path exists. No snapshot is made. */
|
|
130
|
-
async exists(
|
|
131
|
-
const { entry } = await this.call("stat", { path });
|
|
132
|
-
return
|
|
165
|
+
async exists(p) {
|
|
166
|
+
const { entry: entry2 } = await this.call("stat", { path: path.parse(p) });
|
|
167
|
+
return entry2 != null;
|
|
133
168
|
}
|
|
134
169
|
/** Metadata of a file or directory. Throws `FilesystemError` with code `not_found` if absent. */
|
|
135
|
-
async getInfo(
|
|
136
|
-
const { entry } = await this.call("stat", { path });
|
|
137
|
-
if (!
|
|
138
|
-
return
|
|
170
|
+
async getInfo(p) {
|
|
171
|
+
const { entry: entry2 } = await this.call("stat", { path: path.parse(p) });
|
|
172
|
+
if (!entry2) throw new FilesystemError("not_found", `${p} does not exist`);
|
|
173
|
+
return entry2;
|
|
139
174
|
}
|
|
140
175
|
/** Creates a directory and its parents. Returns false if it already existed. */
|
|
141
|
-
async makeDir(
|
|
142
|
-
const { created } = await this.call("mkdir", { path });
|
|
176
|
+
async makeDir(p) {
|
|
177
|
+
const { created } = await this.call("mkdir", { path: path.parse(p) });
|
|
143
178
|
return created === true;
|
|
144
179
|
}
|
|
145
180
|
/** Renames or moves a file or directory. */
|
|
146
181
|
async rename(oldPath, newPath) {
|
|
147
|
-
const { entry } = await this.call(
|
|
148
|
-
|
|
182
|
+
const { entry: entry2 } = await this.call(
|
|
183
|
+
"rename",
|
|
184
|
+
{ path: path.parse(oldPath), newPath: path.parse(newPath) },
|
|
185
|
+
false,
|
|
186
|
+
fsOutputWithEntry
|
|
187
|
+
);
|
|
188
|
+
return entry2;
|
|
149
189
|
}
|
|
150
190
|
/** Copies a file or directory tree. */
|
|
151
191
|
async copy(srcPath, dstPath) {
|
|
152
|
-
const { entry } = await this.call(
|
|
153
|
-
|
|
192
|
+
const { entry: entry2 } = await this.call(
|
|
193
|
+
"copy",
|
|
194
|
+
{ path: path.parse(srcPath), newPath: path.parse(dstPath) },
|
|
195
|
+
false,
|
|
196
|
+
fsOutputWithEntry
|
|
197
|
+
);
|
|
198
|
+
return entry2;
|
|
154
199
|
}
|
|
155
200
|
/** Deletes a file or directory tree. */
|
|
156
|
-
async remove(
|
|
157
|
-
await this.call("remove", { path });
|
|
201
|
+
async remove(p) {
|
|
202
|
+
await this.call("remove", { path: path.parse(p) });
|
|
158
203
|
}
|
|
159
204
|
/** The provider fetches an https URL into the workspace. The sandbox needs no network. Private and loopback addresses are refused. */
|
|
160
|
-
async download(url,
|
|
161
|
-
const { entry } = await this.call(
|
|
162
|
-
|
|
163
|
-
path,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
205
|
+
async download(url, p, opts = {}) {
|
|
206
|
+
const { entry: entry2 } = await this.call(
|
|
207
|
+
"download",
|
|
208
|
+
{ url: httpsUrl.parse(url), path: path.parse(p), ...transferOpts.parse(opts) },
|
|
209
|
+
false,
|
|
210
|
+
fsOutputWithEntry
|
|
211
|
+
);
|
|
212
|
+
return entry2;
|
|
167
213
|
}
|
|
168
214
|
/** The provider sends a workspace file to an https URL with HTTP PUT, for example a presigned URL. */
|
|
169
|
-
async upload(
|
|
215
|
+
async upload(p, url, opts = {}) {
|
|
170
216
|
await this.call("upload", {
|
|
171
|
-
path,
|
|
172
|
-
url,
|
|
173
|
-
...
|
|
217
|
+
path: path.parse(p),
|
|
218
|
+
url: httpsUrl.parse(url),
|
|
219
|
+
...transferOpts.parse(opts)
|
|
174
220
|
});
|
|
175
221
|
}
|
|
176
222
|
};
|
|
@@ -192,6 +238,43 @@ async function setupTelemetry() {
|
|
|
192
238
|
console.error(`sandbox: opentelemetry disabled: ${err.message}`);
|
|
193
239
|
}
|
|
194
240
|
}
|
|
241
|
+
var positiveInt2 = zod.z.int().positive();
|
|
242
|
+
var metadata = zod.z.record(zod.z.string(), zod.z.string());
|
|
243
|
+
var resources = zod.z.strictObject({
|
|
244
|
+
vcpu: positiveInt2.optional(),
|
|
245
|
+
memoryMib: positiveInt2.optional(),
|
|
246
|
+
diskMib: positiveInt2.optional()
|
|
247
|
+
});
|
|
248
|
+
var turnInput = zod.z.strictObject({
|
|
249
|
+
fn: zod.z.string().min(1),
|
|
250
|
+
input: zod.z.record(zod.z.string(), zod.z.unknown()),
|
|
251
|
+
sandboxId: zod.z.string().min(1).optional(),
|
|
252
|
+
image: zod.z.string().min(1).optional(),
|
|
253
|
+
snapshotId: zod.z.string().min(1).optional(),
|
|
254
|
+
metadata: metadata.optional(),
|
|
255
|
+
idleTtlMs: positiveInt2.optional(),
|
|
256
|
+
inheritMetadata: zod.z.boolean().optional(),
|
|
257
|
+
resources: resources.optional()
|
|
258
|
+
});
|
|
259
|
+
var turnResult = zod.z.looseObject({
|
|
260
|
+
output: zod.z.looseObject({}),
|
|
261
|
+
sandboxId: zod.z.string(),
|
|
262
|
+
snapshotId: zod.z.string().optional(),
|
|
263
|
+
timings: zod.z.record(zod.z.string(), zod.z.number()).optional()
|
|
264
|
+
});
|
|
265
|
+
var commandOutput = zod.z.looseObject({
|
|
266
|
+
stdout: zod.z.string(),
|
|
267
|
+
stderr: zod.z.string(),
|
|
268
|
+
exitCode: zod.z.int()
|
|
269
|
+
});
|
|
270
|
+
var sandboxInfo = zod.z.looseObject({
|
|
271
|
+
sandboxId: zod.z.string(),
|
|
272
|
+
metadata: metadata.optional(),
|
|
273
|
+
live: zod.z.boolean(),
|
|
274
|
+
latestSnapshot: zod.z.string().optional(),
|
|
275
|
+
createdAt: zod.z.coerce.date(),
|
|
276
|
+
lastUsed: zod.z.coerce.date().optional()
|
|
277
|
+
});
|
|
195
278
|
|
|
196
279
|
// src/sandbox.ts
|
|
197
280
|
var TASK_NAMES = ["hatchet-sandbox", "brickie-sandbox"];
|
|
@@ -229,6 +312,11 @@ function resolveTaskName(client) {
|
|
|
229
312
|
}
|
|
230
313
|
return pending;
|
|
231
314
|
}
|
|
315
|
+
var positiveInt3 = zod.z.int().positive();
|
|
316
|
+
var nonEmpty = zod.z.string().min(1);
|
|
317
|
+
var commandResult = commandOutput.extend(
|
|
318
|
+
turnResult.pick({ snapshotId: true, timings: true }).shape
|
|
319
|
+
);
|
|
232
320
|
var UNITS = {
|
|
233
321
|
K: 1e3,
|
|
234
322
|
M: 1e6,
|
|
@@ -239,38 +327,55 @@ var UNITS = {
|
|
|
239
327
|
Gi: 2 ** 30,
|
|
240
328
|
Ti: 2 ** 40
|
|
241
329
|
};
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
330
|
+
var QUANTITY = /^(\d+(?:\.\d+)?)\s*([KMGT]i?)B?$/;
|
|
331
|
+
var mib = zod.z.union(
|
|
332
|
+
[
|
|
333
|
+
positiveInt3,
|
|
334
|
+
zod.z.string().trim().regex(QUANTITY).transform((s) => {
|
|
335
|
+
const [, n, unit] = QUANTITY.exec(s);
|
|
336
|
+
return Math.ceil(Number(n) * UNITS[unit] / UNITS.Mi);
|
|
337
|
+
}).pipe(zod.z.int().min(1))
|
|
338
|
+
],
|
|
339
|
+
{
|
|
340
|
+
error: "expected a positive whole number of MiB or a quantity with a unit like '512Mi', '4Gi' or '8G'"
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
var resources2 = zod.z.strictObject({
|
|
344
|
+
vcpu: positiveInt3.optional(),
|
|
345
|
+
memory: mib.optional(),
|
|
346
|
+
disk: mib.optional()
|
|
347
|
+
}).refine(
|
|
348
|
+
(r) => r.vcpu !== void 0 || r.memory !== void 0 || r.disk !== void 0,
|
|
349
|
+
"set at least one of vcpu, memory, disk"
|
|
350
|
+
).transform(({ vcpu, memory, disk }) => ({ vcpu, memoryMib: memory, diskMib: disk }));
|
|
351
|
+
var sandboxOpts = zod.z.strictObject({
|
|
352
|
+
id: zod.z.string().regex(/^[a-z0-9-]+$/, "lowercase letters, digits, and hyphens only").optional(),
|
|
353
|
+
template: nonEmpty.optional(),
|
|
354
|
+
from: nonEmpty.optional(),
|
|
355
|
+
metadata: metadata.optional(),
|
|
356
|
+
idleTimeoutMs: positiveInt3.optional(),
|
|
357
|
+
inheritParentMetadata: zod.z.boolean().optional(),
|
|
358
|
+
provider: nonEmpty.optional(),
|
|
359
|
+
resources: resources2.optional()
|
|
360
|
+
}).refine((o) => !o.inheritParentMetadata || o.from !== void 0, {
|
|
361
|
+
error: "inheritParentMetadata needs from",
|
|
362
|
+
path: ["inheritParentMetadata"]
|
|
363
|
+
});
|
|
364
|
+
var execOpts = zod.z.strictObject({ timeoutMs: positiveInt3.optional() });
|
|
365
|
+
var connectOpts = zod.z.strictObject({ template: nonEmpty.optional() });
|
|
366
|
+
var listOpts2 = zod.z.strictObject({
|
|
367
|
+
sandboxId: nonEmpty.optional(),
|
|
368
|
+
metadata: metadata.optional()
|
|
369
|
+
});
|
|
370
|
+
var deleteOpts = listOpts2.refine(
|
|
371
|
+
(o) => o.sandboxId !== void 0 || Object.keys(o.metadata ?? {}).length > 0,
|
|
372
|
+
"delete needs a sandboxId or a metadata filter"
|
|
373
|
+
);
|
|
271
374
|
function turnRunner(client) {
|
|
272
375
|
const resolved = resolveTaskName(client);
|
|
273
|
-
return async (
|
|
376
|
+
return async (raw, opts) => {
|
|
377
|
+
const input = turnInput.parse(raw);
|
|
378
|
+
const payload = input;
|
|
274
379
|
const name = await resolved;
|
|
275
380
|
const additionalMetadata = {
|
|
276
381
|
"hatchet.sandbox.fn": input.fn,
|
|
@@ -278,10 +383,10 @@ function turnRunner(client) {
|
|
|
278
383
|
...input.image ? { "hatchet.sandbox.image": input.image } : {},
|
|
279
384
|
...input.snapshotId ? { "hatchet.sandbox.from": input.snapshotId } : {}
|
|
280
385
|
};
|
|
281
|
-
const result = opts?.fresh ? await (await client.admin.runWorkflow(name,
|
|
386
|
+
const result = opts?.fresh ? await (await client.admin.runWorkflow(name, payload, {
|
|
282
387
|
additionalMetadata
|
|
283
|
-
})).result() : await client.workflow({ name }).run(
|
|
284
|
-
return result[name] ?? result;
|
|
388
|
+
})).result() : await client.workflow({ name }).run(payload, { additionalMetadata });
|
|
389
|
+
return turnResult.parse(result[name] ?? result);
|
|
285
390
|
};
|
|
286
391
|
}
|
|
287
392
|
var Sandbox = class _Sandbox {
|
|
@@ -298,54 +403,40 @@ var Sandbox = class _Sandbox {
|
|
|
298
403
|
files;
|
|
299
404
|
/** Starts a new sandbox. Prefer `hatchet.sandbox.create`. */
|
|
300
405
|
static async create(client, opts = {}) {
|
|
406
|
+
const o = sandboxOpts.parse(opts);
|
|
301
407
|
await setupTelemetry();
|
|
302
408
|
const run = turnRunner(client);
|
|
303
|
-
const metadata = opts.provider ? { ...opts.metadata, provider: opts.provider } : opts.metadata;
|
|
304
409
|
const created = await run({
|
|
305
410
|
fn: "$create",
|
|
306
411
|
input: {},
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
412
|
+
sandboxId: o.id,
|
|
413
|
+
image: o.template,
|
|
414
|
+
snapshotId: o.from,
|
|
415
|
+
metadata: o.provider ? { ...o.metadata, provider: o.provider } : o.metadata,
|
|
416
|
+
idleTtlMs: o.idleTimeoutMs,
|
|
417
|
+
inheritMetadata: o.inheritParentMetadata,
|
|
418
|
+
resources: o.resources
|
|
314
419
|
});
|
|
315
|
-
return new _Sandbox(run, created.sandboxId,
|
|
420
|
+
return new _Sandbox(run, created.sandboxId, o.template);
|
|
316
421
|
}
|
|
317
422
|
/** Attaches to a sandbox by ID. Prefer `hatchet.sandbox.connect`. */
|
|
318
423
|
static async connect(client, sandboxId, opts = {}) {
|
|
424
|
+
const id = nonEmpty.parse(sandboxId);
|
|
425
|
+
const o = connectOpts.parse(opts);
|
|
319
426
|
await setupTelemetry();
|
|
320
427
|
const run = turnRunner(client);
|
|
321
|
-
await run({
|
|
322
|
-
|
|
323
|
-
input: {},
|
|
324
|
-
sandboxId,
|
|
325
|
-
...opts.template ? { image: opts.template } : {}
|
|
326
|
-
});
|
|
327
|
-
return new _Sandbox(run, sandboxId, opts.template);
|
|
428
|
+
await run({ fn: "$connect", input: {}, sandboxId: id, image: o.template });
|
|
429
|
+
return new _Sandbox(run, id, o.template);
|
|
328
430
|
}
|
|
329
431
|
/** Lists sandboxes on the provider. Prefer `hatchet.sandbox.list`. */
|
|
330
432
|
static async list(client, opts = {}) {
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
...opts.sandboxId ? { sandboxId: opts.sandboxId } : {},
|
|
335
|
-
...opts.metadata ? { metadata: opts.metadata } : {}
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
const raw = result.output.sandboxes ?? [];
|
|
339
|
-
return raw.map((s) => ({
|
|
340
|
-
...s,
|
|
341
|
-
createdAt: new Date(s.createdAt),
|
|
342
|
-
lastUsed: s.lastUsed ? new Date(s.lastUsed) : void 0
|
|
343
|
-
}));
|
|
433
|
+
const o = listOpts2.parse(opts);
|
|
434
|
+
const result = await turnRunner(client)({ fn: "$list", input: o });
|
|
435
|
+
return sandboxInfo.array().parse(result.output.sandboxes ?? []);
|
|
344
436
|
}
|
|
345
437
|
/** Deletes every sandbox matching the filter, with all their snapshots and files. Prefer `hatchet.sandbox.delete`. */
|
|
346
438
|
static async delete(client, opts) {
|
|
347
|
-
|
|
348
|
-
throw new Error("delete needs a sandboxId or a metadata filter");
|
|
439
|
+
deleteOpts.parse(opts);
|
|
349
440
|
const run = turnRunner(client);
|
|
350
441
|
const ids = [];
|
|
351
442
|
for (const { sandboxId } of await _Sandbox.list(client, opts)) {
|
|
@@ -356,10 +447,8 @@ var Sandbox = class _Sandbox {
|
|
|
356
447
|
}
|
|
357
448
|
/** Runs a shell command with `sh -c` in the workspace and makes a snapshot. A timeout gives `exitCode` 124, not an error. */
|
|
358
449
|
async exec(cmd, opts = {}) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}
|
|
362
|
-
});
|
|
450
|
+
const input = { cmd: nonEmpty.parse(cmd), ...execOpts.parse(opts) };
|
|
451
|
+
return commandResult.parse(await this.turn("$command", input));
|
|
363
452
|
}
|
|
364
453
|
/** Removes the sandbox. Its snapshots stay; `connect` can bring it back. */
|
|
365
454
|
async kill() {
|
|
@@ -371,12 +460,7 @@ var Sandbox = class _Sandbox {
|
|
|
371
460
|
}
|
|
372
461
|
async turn(fn, input, fresh = false) {
|
|
373
462
|
const result = await this.run(
|
|
374
|
-
{
|
|
375
|
-
sandboxId: this.sandboxId,
|
|
376
|
-
...this.template ? { image: this.template } : {},
|
|
377
|
-
fn,
|
|
378
|
-
input
|
|
379
|
-
},
|
|
463
|
+
{ sandboxId: this.sandboxId, image: this.template, fn, input },
|
|
380
464
|
{ fresh }
|
|
381
465
|
);
|
|
382
466
|
return {
|