@hatchet-dev/sandbox 0.1.0 → 0.1.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/CHANGELOG.md +11 -0
- package/README.md +5 -1
- package/dist/files.d.ts +5 -4
- package/dist/files.js +14 -5
- package/dist/sandbox.d.ts +8 -0
- package/dist/sandbox.js +18 -0
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Eac
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.1] - 2026-10-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `create({ from, inheritParentMetadata: true })` copies the parent sandbox's metadata onto the fork; tags given in `metadata` take precedence.
|
|
14
|
+
- `hatchet.sandbox.delete({ sandboxId?, metadata? })` and `sandbox.delete()`: remove sandboxes together with every snapshot and file stored for them. `kill()` keeps snapshots as before.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `files.read` of a staged file replayed in a durable task after its signed URL expired now asks the provider to sign the same file again instead of failing with `staging_failed`.
|
|
19
|
+
|
|
9
20
|
## [0.1.0] - 2026-10-27
|
|
10
21
|
|
|
11
22
|
### Added
|
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ Your app only talks to Hatchet. A brickie provider, connected to the same tenant
|
|
|
65
65
|
| `create(opts?)` | `Sandbox` | Starts a sandbox. |
|
|
66
66
|
| `connect(id, { template? })` | `Sandbox` | Picks up a sandbox from its latest snapshot. Starts it again if it was stopped. Throws if the ID does not exist. |
|
|
67
67
|
| `list({ sandboxId?, metadata? })` | `SandboxInfo[]` | Finds sandboxes by ID or by metadata. All given pairs must match. |
|
|
68
|
+
| `delete({ sandboxId?, metadata? })` | `string[]` | Deletes every matching sandbox with all of its snapshots and files, and returns their IDs. One filter is required. |
|
|
68
69
|
|
|
69
70
|
Options for `create`:
|
|
70
71
|
|
|
@@ -74,6 +75,7 @@ Options for `create`:
|
|
|
74
75
|
| `template` | The container image. Defaults to the provider's image. |
|
|
75
76
|
| `from` | Start from this snapshot. Create two sandboxes from the same snapshot and you have a fork. |
|
|
76
77
|
| `metadata` | Tags you can search for with `list`. |
|
|
78
|
+
| `inheritParentMetadata` | With `from`, copy the tags of the sandbox that made the snapshot onto the new one. Tags given in `metadata` take precedence. |
|
|
77
79
|
| `idleTimeoutMs` | Remove the sandbox after this long with no calls. The provider sets the maximum. |
|
|
78
80
|
|
|
79
81
|
### `Sandbox`
|
|
@@ -81,7 +83,8 @@ Options for `create`:
|
|
|
81
83
|
| Call | Returns | What it does |
|
|
82
84
|
|---|---|---|
|
|
83
85
|
| `exec(cmd, { timeoutMs? })` | `{ stdout, stderr, exitCode, snapshotId?, timings? }` | Runs `sh -c cmd` in the workspace and takes a snapshot. A non-zero exit code is a normal result, not an error. On timeout the exit code is 124. |
|
|
84
|
-
| `kill()` | `void` |
|
|
86
|
+
| `kill()` | `void` | Stops the sandbox. The snapshots stay, so `connect` still works. |
|
|
87
|
+
| `delete()` | `void` | Stops the sandbox and deletes every snapshot and file stored for it. `connect` fails afterwards. |
|
|
85
88
|
| `sandboxId` | `string` | The ID to use with `connect` later. |
|
|
86
89
|
| `files` | `Files` | The file API below. |
|
|
87
90
|
|
|
@@ -110,6 +113,7 @@ The sandbox itself does not need network access for any of this.
|
|
|
110
113
|
- `FilesystemError` has a `code`. One of: `not_found`, `invalid_path`, `invalid_url`, `invalid_content`, `invalid`, `is_a_directory`, `not_a_directory`, `too_large`, `staging_failed`, `transfer_failed`.
|
|
111
114
|
- A command that fails does not throw. Look at `exitCode`.
|
|
112
115
|
- `connect` with an unknown ID throws. `create` with an `id` that already exists throws too. Use `connect` for that one, or pick a new ID.
|
|
116
|
+
- `hatchet.sandbox.delete` without a `sandboxId` or a non-empty `metadata` filter throws before touching anything. Metadata matching is a subset match, the same as `list`: a filter of `{ a, b }` matches a sandbox tagged `{ a, b, c }`.
|
|
113
117
|
|
|
114
118
|
## Telemetry
|
|
115
119
|
|
package/dist/files.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonObject } from '@hatchet-dev/typescript-sdk/v1';
|
|
2
|
-
export type FileType = 'file' | 'dir'
|
|
2
|
+
export type FileType = 'file' | 'dir';
|
|
3
3
|
export interface EntryInfo {
|
|
4
4
|
name: string;
|
|
5
5
|
path: string;
|
|
@@ -8,7 +8,6 @@ export interface EntryInfo {
|
|
|
8
8
|
mode: number;
|
|
9
9
|
permissions: string;
|
|
10
10
|
modifiedTime?: Date;
|
|
11
|
-
symlinkTarget?: string;
|
|
12
11
|
}
|
|
13
12
|
export type FileData = string | ArrayBuffer | Uint8Array | Blob;
|
|
14
13
|
export interface WriteEntry {
|
|
@@ -56,7 +55,7 @@ export declare class Files {
|
|
|
56
55
|
/** @internal */
|
|
57
56
|
constructor(turn: FsTurn);
|
|
58
57
|
private call;
|
|
59
|
-
/** Reads a file as text (default), bytes, or a Blob. Files over the provider's `--max-file-bytes` (2 MiB) come through the provider's
|
|
58
|
+
/** Reads a file as text (default), bytes, or a Blob. Files over the provider's `--max-file-bytes` (2 MiB) come through the provider's `--store` instead of Hatchet. */
|
|
60
59
|
read(path: string, opts?: ReadOpts & {
|
|
61
60
|
format?: 'text';
|
|
62
61
|
}): Promise<string>;
|
|
@@ -66,7 +65,9 @@ export declare class Files {
|
|
|
66
65
|
read(path: string, opts: ReadOpts & {
|
|
67
66
|
format: 'blob';
|
|
68
67
|
}): Promise<Blob>;
|
|
69
|
-
/**
|
|
68
|
+
/** A replayed durable read may hold a URL that has expired; the provider signs the same staged file again. */
|
|
69
|
+
private readStaged;
|
|
70
|
+
/** Writes one file or several. Parent directories are created. Bytes are stored exactly. Files over 2 MiB go through the provider's `--store` instead of Hatchet. */
|
|
70
71
|
write(path: string, data: FileData): Promise<EntryInfo>;
|
|
71
72
|
write(files: WriteEntry[]): Promise<EntryInfo[]>;
|
|
72
73
|
/** Writes several files in one task. */
|
package/dist/files.js
CHANGED
|
@@ -70,10 +70,7 @@ async function putStaged(url, bytes) {
|
|
|
70
70
|
}
|
|
71
71
|
async function getStaged(url) {
|
|
72
72
|
if (url.startsWith('file://')) {
|
|
73
|
-
|
|
74
|
-
const bytes = await fs.readFile(path);
|
|
75
|
-
await fs.rm(path, { force: true });
|
|
76
|
-
return bytes;
|
|
73
|
+
return fs.readFile(decodeURIComponent(url.slice('file://'.length)));
|
|
77
74
|
}
|
|
78
75
|
const res = await fetch(url);
|
|
79
76
|
if (!res.ok)
|
|
@@ -95,13 +92,25 @@ class Files {
|
|
|
95
92
|
}
|
|
96
93
|
async read(path, opts = {}) {
|
|
97
94
|
const out = await this.call('read', { path });
|
|
98
|
-
const bytes = out.url ? await
|
|
95
|
+
const bytes = out.url ? await this.readStaged(out.url, out.stagedKey) : Buffer.from(out.content ?? '', 'base64');
|
|
99
96
|
if (opts.format === 'bytes')
|
|
100
97
|
return new Uint8Array(bytes);
|
|
101
98
|
if (opts.format === 'blob')
|
|
102
99
|
return new Blob([new Uint8Array(bytes)]);
|
|
103
100
|
return bytes.toString('utf8');
|
|
104
101
|
}
|
|
102
|
+
/** A replayed durable read may hold a URL that has expired; the provider signs the same staged file again. */
|
|
103
|
+
async readStaged(url, stagedKey) {
|
|
104
|
+
try {
|
|
105
|
+
return await getStaged(url);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
if (url.startsWith('file://') || !(e instanceof FilesystemError))
|
|
109
|
+
throw e;
|
|
110
|
+
const { getUrl } = await this.call('presign', { stagedKey });
|
|
111
|
+
return getStaged(getUrl);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
105
114
|
async write(pathOrFiles, data) {
|
|
106
115
|
if (typeof pathOrFiles !== 'string')
|
|
107
116
|
return this.writeFiles(pathOrFiles);
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface SandboxOpts {
|
|
|
23
23
|
metadata?: Record<string, string>;
|
|
24
24
|
/** Milliseconds without a turn before the provider removes the sandbox. Capped by the provider. */
|
|
25
25
|
idleTimeoutMs?: number;
|
|
26
|
+
/** With `from`, start from the metadata of the sandbox that made that snapshot. Pairs given in `metadata` take precedence over inherited ones. */
|
|
27
|
+
inheritParentMetadata?: boolean;
|
|
26
28
|
}
|
|
27
29
|
export interface ExecOpts {
|
|
28
30
|
/** Kill the command after this long. The result then has `exitCode` 124. */
|
|
@@ -61,10 +63,14 @@ export declare class Sandbox {
|
|
|
61
63
|
static connect(client: HatchetClient, sandboxId: string, opts?: ConnectOpts): Promise<Sandbox>;
|
|
62
64
|
/** Lists sandboxes on the provider. Prefer `hatchet.sandbox.list`. */
|
|
63
65
|
static list(client: HatchetClient, opts?: SandboxListOpts): Promise<SandboxInfo[]>;
|
|
66
|
+
/** Deletes every sandbox matching the filter, with all their snapshots and files. Prefer `hatchet.sandbox.delete`. */
|
|
67
|
+
static delete(client: HatchetClient, opts: SandboxListOpts): Promise<string[]>;
|
|
64
68
|
/** Runs a shell command with `sh -c` in the workspace and makes a snapshot. A timeout gives `exitCode` 124, not an error. */
|
|
65
69
|
exec(cmd: string, opts?: ExecOpts): Promise<CommandResult>;
|
|
66
70
|
/** Removes the sandbox. Its snapshots stay; `connect` can bring it back. */
|
|
67
71
|
kill(): Promise<void>;
|
|
72
|
+
/** Removes the sandbox and deletes every snapshot and file the provider stored for it. `connect` fails afterwards. */
|
|
73
|
+
delete(): Promise<void>;
|
|
68
74
|
private turn;
|
|
69
75
|
}
|
|
70
76
|
/** `hatchet.sandbox`, added to `HatchetClient` by importing `@hatchet-dev/sandbox`. */
|
|
@@ -75,6 +81,8 @@ export interface SandboxNamespace {
|
|
|
75
81
|
connect(sandboxId: string, opts?: ConnectOpts): Promise<Sandbox>;
|
|
76
82
|
/** Lists sandboxes the provider knows, filtered by ID or metadata. */
|
|
77
83
|
list(opts?: SandboxListOpts): Promise<SandboxInfo[]>;
|
|
84
|
+
/** Deletes the sandboxes matching the filter, with every snapshot and file stored for them. Returns their IDs. */
|
|
85
|
+
delete(opts: SandboxListOpts): Promise<string[]>;
|
|
78
86
|
}
|
|
79
87
|
declare module '@hatchet-dev/typescript-sdk/v1' {
|
|
80
88
|
interface HatchetClient {
|
package/dist/sandbox.js
CHANGED
|
@@ -44,6 +44,7 @@ class Sandbox {
|
|
|
44
44
|
...(opts.from ? { snapshotId: opts.from } : {}),
|
|
45
45
|
...(opts.metadata ? { metadata: opts.metadata } : {}),
|
|
46
46
|
...(opts.idleTimeoutMs ? { idleTtlMs: opts.idleTimeoutMs } : {}),
|
|
47
|
+
...(opts.inheritParentMetadata ? { inheritMetadata: true } : {}),
|
|
47
48
|
});
|
|
48
49
|
return new Sandbox(run, created.sandboxId, opts.template);
|
|
49
50
|
}
|
|
@@ -63,6 +64,18 @@ class Sandbox {
|
|
|
63
64
|
const raw = (result.output.sandboxes ?? []);
|
|
64
65
|
return raw.map((s) => ({ ...s, createdAt: new Date(s.createdAt), lastUsed: s.lastUsed ? new Date(s.lastUsed) : undefined }));
|
|
65
66
|
}
|
|
67
|
+
/** Deletes every sandbox matching the filter, with all their snapshots and files. Prefer `hatchet.sandbox.delete`. */
|
|
68
|
+
static async delete(client, opts) {
|
|
69
|
+
if (!opts.sandboxId && Object.keys(opts.metadata ?? {}).length === 0)
|
|
70
|
+
throw new Error('delete needs a sandboxId or a metadata filter');
|
|
71
|
+
const run = turnRunner(client);
|
|
72
|
+
const ids = [];
|
|
73
|
+
for (const { sandboxId } of await Sandbox.list(client, opts)) {
|
|
74
|
+
await run({ fn: '$delete', input: {}, sandboxId });
|
|
75
|
+
ids.push(sandboxId);
|
|
76
|
+
}
|
|
77
|
+
return ids;
|
|
78
|
+
}
|
|
66
79
|
/** Runs a shell command with `sh -c` in the workspace and makes a snapshot. A timeout gives `exitCode` 124, not an error. */
|
|
67
80
|
async exec(cmd, opts = {}) {
|
|
68
81
|
return this.turn('$command', { cmd, ...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}) });
|
|
@@ -71,6 +84,10 @@ class Sandbox {
|
|
|
71
84
|
async kill() {
|
|
72
85
|
await this.turn('$destroy', {});
|
|
73
86
|
}
|
|
87
|
+
/** Removes the sandbox and deletes every snapshot and file the provider stored for it. `connect` fails afterwards. */
|
|
88
|
+
async delete() {
|
|
89
|
+
await this.turn('$delete', {});
|
|
90
|
+
}
|
|
74
91
|
async turn(fn, input) {
|
|
75
92
|
const result = await this.run({
|
|
76
93
|
sandboxId: this.sandboxId,
|
|
@@ -89,6 +106,7 @@ Object.defineProperty(v1_1.HatchetClient.prototype, 'sandbox', {
|
|
|
89
106
|
create: (opts) => Sandbox.create(this, opts),
|
|
90
107
|
connect: (sandboxId, opts) => Sandbox.connect(this, sandboxId, opts),
|
|
91
108
|
list: (opts) => Sandbox.list(this, opts),
|
|
109
|
+
delete: (opts) => Sandbox.delete(this, opts),
|
|
92
110
|
};
|
|
93
111
|
},
|
|
94
112
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hatchet-dev/sandbox",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Hatchet Sandbox TypeScript SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@hatchet-dev/typescript-sdk": "^1.33.1",
|
|
36
36
|
"@types/node": "^24.0.0",
|
|
37
|
+
"tsx": "^4.23.15",
|
|
37
38
|
"typedoc": "^0.28.0",
|
|
38
39
|
"typescript": "^5.9.0"
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
41
42
|
"build": "tsc -p tsconfig.build.json",
|
|
42
43
|
"typecheck": "tsc",
|
|
43
|
-
"docs": "typedoc"
|
|
44
|
+
"docs": "typedoc",
|
|
45
|
+
"test": "node --import tsx --test --test-force-exit --test-timeout=180000 test/e2e.test.ts"
|
|
44
46
|
}
|
|
45
47
|
}
|