@hatchet-dev/sandbox 0.1.1 → 0.1.2

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/dist/files.js DELETED
@@ -1,179 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.Files = exports.FilesystemError = void 0;
37
- const fs = __importStar(require("node:fs/promises"));
38
- /** Thrown for caller mistakes such as a missing file or a path outside the workspace. */
39
- class FilesystemError extends Error {
40
- code;
41
- constructor(code, message) {
42
- super(message);
43
- this.code = code;
44
- this.name = 'FilesystemError';
45
- }
46
- }
47
- exports.FilesystemError = FilesystemError;
48
- const toEntry = (e) => ({
49
- ...e,
50
- modifiedTime: e.modifiedTime ? new Date(e.modifiedTime) : undefined,
51
- });
52
- const STAGE_OVER_BYTES = 2 << 20;
53
- async function toBytes(data) {
54
- if (typeof data === 'string')
55
- return Buffer.from(data, 'utf8');
56
- if (data instanceof Blob)
57
- return Buffer.from(await data.arrayBuffer());
58
- if (data instanceof Uint8Array)
59
- return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
60
- return Buffer.from(data);
61
- }
62
- async function putStaged(url, bytes) {
63
- if (url.startsWith('file://')) {
64
- await fs.writeFile(decodeURIComponent(url.slice('file://'.length)), bytes);
65
- return;
66
- }
67
- const res = await fetch(url, { method: 'PUT', body: new Uint8Array(bytes), headers: { 'Content-Type': 'application/octet-stream' } });
68
- if (!res.ok)
69
- throw new FilesystemError('staging_failed', `stage upload failed: HTTP ${res.status}`);
70
- }
71
- async function getStaged(url) {
72
- if (url.startsWith('file://')) {
73
- return fs.readFile(decodeURIComponent(url.slice('file://'.length)));
74
- }
75
- const res = await fetch(url);
76
- if (!res.ok)
77
- throw new FilesystemError('staging_failed', `stage download failed: HTTP ${res.status}`);
78
- return Buffer.from(await res.arrayBuffer());
79
- }
80
- /** File operations on a sandbox workspace. Paths are relative to the workspace. Each call is one Hatchet task. */
81
- class Files {
82
- turn;
83
- /** @internal */
84
- constructor(turn) {
85
- this.turn = turn;
86
- }
87
- async call(fn, input) {
88
- const out = await this.turn(`$fs.${fn}`, input);
89
- if (out.error)
90
- throw new FilesystemError(out.error.code, out.error.message);
91
- return out;
92
- }
93
- async read(path, opts = {}) {
94
- const out = await this.call('read', { path });
95
- const bytes = out.url ? await this.readStaged(out.url, out.stagedKey) : Buffer.from(out.content ?? '', 'base64');
96
- if (opts.format === 'bytes')
97
- return new Uint8Array(bytes);
98
- if (opts.format === 'blob')
99
- return new Blob([new Uint8Array(bytes)]);
100
- return bytes.toString('utf8');
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
- }
114
- async write(pathOrFiles, data) {
115
- if (typeof pathOrFiles !== 'string')
116
- return this.writeFiles(pathOrFiles);
117
- const [entry] = await this.writeFiles([{ path: pathOrFiles, data: data ?? '' }]);
118
- return entry;
119
- }
120
- /** Writes several files in one task. */
121
- async writeFiles(files) {
122
- const encoded = await Promise.all(files.map(async (f) => {
123
- const bytes = await toBytes(f.data);
124
- if (bytes.length <= STAGE_OVER_BYTES)
125
- return { path: f.path, content: bytes.toString('base64') };
126
- const { stagedKey, putUrl } = await this.call('presign', {});
127
- await putStaged(putUrl, bytes);
128
- return { path: f.path, stagedKey: stagedKey };
129
- }));
130
- const { entries = [] } = await this.call('write', { files: encoded });
131
- return entries.map(toEntry);
132
- }
133
- /** Lists a directory. `depth` 1 (default) is the directory itself; higher values recurse. No snapshot is made. */
134
- async list(path = '.', opts = {}) {
135
- const { entries = [] } = await this.call('list', { path, depth: opts.depth ?? 1 });
136
- return entries.map(toEntry);
137
- }
138
- /** True if the path exists. No snapshot is made. */
139
- async exists(path) {
140
- const { entry } = await this.call('stat', { path });
141
- return entry != null;
142
- }
143
- /** Metadata of a file or directory. Throws `FilesystemError` with code `not_found` if absent. */
144
- async getInfo(path) {
145
- const { entry } = await this.call('stat', { path });
146
- if (!entry)
147
- throw new FilesystemError('not_found', `${path} does not exist`);
148
- return toEntry(entry);
149
- }
150
- /** Creates a directory and its parents. Returns false if it already existed. */
151
- async makeDir(path) {
152
- const { created } = await this.call('mkdir', { path });
153
- return created === true;
154
- }
155
- /** Renames or moves a file or directory. */
156
- async rename(oldPath, newPath) {
157
- const { entry } = await this.call('rename', { path: oldPath, newPath });
158
- return toEntry(entry);
159
- }
160
- /** Copies a file or directory tree. */
161
- async copy(srcPath, dstPath) {
162
- const { entry } = await this.call('copy', { path: srcPath, newPath: dstPath });
163
- return toEntry(entry);
164
- }
165
- /** Deletes a file or directory tree. */
166
- async remove(path) {
167
- await this.call('remove', { path });
168
- }
169
- /** The provider fetches an https URL into the workspace. The sandbox needs no network. Private and loopback addresses are refused. */
170
- async download(url, path, opts = {}) {
171
- const { entry } = await this.call('download', { url, path, ...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}) });
172
- return toEntry(entry);
173
- }
174
- /** The provider sends a workspace file to an https URL with HTTP PUT, for example a presigned URL. */
175
- async upload(path, url, opts = {}) {
176
- await this.call('upload', { path, url, ...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}) });
177
- }
178
- }
179
- exports.Files = Files;
package/dist/otel.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function setupTelemetry(): Promise<void>;
package/dist/otel.js DELETED
@@ -1,20 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setupTelemetry = setupTelemetry;
4
- let telemetryStarted = false;
5
- async function setupTelemetry() {
6
- if (telemetryStarted || process.env.BRICKIE_OTEL === 'false') {
7
- return;
8
- }
9
- telemetryStarted = true;
10
- try {
11
- const [{ registerInstrumentations }, { HatchetInstrumentor }] = await Promise.all([
12
- import('@opentelemetry/instrumentation'),
13
- import('@hatchet-dev/typescript-sdk/opentelemetry/index.js'),
14
- ]);
15
- registerInstrumentations({ instrumentations: [new HatchetInstrumentor()] });
16
- }
17
- catch (err) {
18
- console.error(`brickie: opentelemetry disabled: ${err.message}`);
19
- }
20
- }
package/dist/sandbox.d.ts DELETED
@@ -1,91 +0,0 @@
1
- import { HatchetClient } from '@hatchet-dev/typescript-sdk/v1';
2
- import { Files } from './files.js';
3
- export interface Timings {
4
- /** Snapshot made after this turn. Absent for turns that do not change the workspace. */
5
- snapshotId?: string;
6
- /** Milliseconds per phase: `claim`, `restore`, `exec`, `checkpoint`. */
7
- timings?: Record<string, number>;
8
- }
9
- export interface CommandResult extends Timings {
10
- stdout: string;
11
- stderr: string;
12
- /** A non-zero code is a normal result, not an error. */
13
- exitCode: number;
14
- }
15
- export interface SandboxOpts {
16
- /** Sandbox ID to use instead of a generated one. Lowercase letters, digits, and hyphens; must be new on the provider. */
17
- id?: string;
18
- /** Container image. Defaults to the provider's image. */
19
- template?: string;
20
- /** Snapshot to start from. Two sandboxes with the same `from` are a fork. */
21
- from?: string;
22
- /** Key-value tags, used to find the sandbox again with {@link SandboxNamespace.list}. */
23
- metadata?: Record<string, string>;
24
- /** Milliseconds without a turn before the provider removes the sandbox. Capped by the provider. */
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;
28
- }
29
- export interface ExecOpts {
30
- /** Kill the command after this long. The result then has `exitCode` 124. */
31
- timeoutMs?: number;
32
- }
33
- export interface ConnectOpts {
34
- /** Image to use if the sandbox has to be started again. Defaults to the provider's image. */
35
- template?: string;
36
- }
37
- export interface SandboxListOpts {
38
- /** Only the sandbox with this ID. */
39
- sandboxId?: string;
40
- /** Only sandboxes whose metadata contains every one of these pairs. */
41
- metadata?: Record<string, string>;
42
- }
43
- export interface SandboxInfo {
44
- sandboxId: string;
45
- metadata?: Record<string, string>;
46
- /** True while the sandbox is running. A sandbox that is not live is started again by `connect`. */
47
- live: boolean;
48
- latestSnapshot?: string;
49
- createdAt: Date;
50
- lastUsed?: Date;
51
- }
52
- /** One sandbox. Every method is one `brickie-sandbox` Hatchet task. */
53
- export declare class Sandbox {
54
- private readonly run;
55
- readonly sandboxId: string;
56
- private readonly template?;
57
- /** File operations on the workspace. */
58
- readonly files: Files;
59
- private constructor();
60
- /** Starts a new sandbox. Prefer `hatchet.sandbox.create`. */
61
- static create(client: HatchetClient, opts?: SandboxOpts): Promise<Sandbox>;
62
- /** Attaches to a sandbox by ID. Prefer `hatchet.sandbox.connect`. */
63
- static connect(client: HatchetClient, sandboxId: string, opts?: ConnectOpts): Promise<Sandbox>;
64
- /** Lists sandboxes on the provider. Prefer `hatchet.sandbox.list`. */
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[]>;
68
- /** Runs a shell command with `sh -c` in the workspace and makes a snapshot. A timeout gives `exitCode` 124, not an error. */
69
- exec(cmd: string, opts?: ExecOpts): Promise<CommandResult>;
70
- /** Removes the sandbox. Its snapshots stay; `connect` can bring it back. */
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>;
74
- private turn;
75
- }
76
- /** `hatchet.sandbox`, added to `HatchetClient` by importing `@hatchet-dev/sandbox`. */
77
- export interface SandboxNamespace {
78
- /** Starts a new sandbox. Inside a durable task this is a durable step. */
79
- create(opts?: SandboxOpts): Promise<Sandbox>;
80
- /** Attaches to a sandbox by ID. If it is not running, the provider starts it from its latest snapshot. */
81
- connect(sandboxId: string, opts?: ConnectOpts): Promise<Sandbox>;
82
- /** Lists sandboxes the provider knows, filtered by ID or metadata. */
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[]>;
86
- }
87
- declare module '@hatchet-dev/typescript-sdk/v1' {
88
- interface HatchetClient {
89
- readonly sandbox: SandboxNamespace;
90
- }
91
- }
package/dist/sandbox.js DELETED
@@ -1,112 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Sandbox = void 0;
4
- const v1_1 = require("@hatchet-dev/typescript-sdk/v1");
5
- const files_js_1 = require("./files.js");
6
- const otel_js_1 = require("./otel.js");
7
- const SANDBOX_TASK_NAME = 'brickie-sandbox';
8
- function turnRunner(client) {
9
- const task = client.workflow({ name: SANDBOX_TASK_NAME });
10
- return async (input) => {
11
- const result = await task.run(input, {
12
- additionalMetadata: {
13
- 'brickie.fn': input.fn,
14
- ...(input.sandboxId ? { 'brickie.sandbox': input.sandboxId } : {}),
15
- ...(input.image ? { 'brickie.image': input.image } : {}),
16
- ...(input.snapshotId ? { 'brickie.from': input.snapshotId } : {}),
17
- },
18
- });
19
- return result[SANDBOX_TASK_NAME] ?? result;
20
- };
21
- }
22
- /** One sandbox. Every method is one `brickie-sandbox` Hatchet task. */
23
- class Sandbox {
24
- run;
25
- sandboxId;
26
- template;
27
- /** File operations on the workspace. */
28
- files;
29
- constructor(run, sandboxId, template) {
30
- this.run = run;
31
- this.sandboxId = sandboxId;
32
- this.template = template;
33
- this.files = new files_js_1.Files((fn, input) => this.turn(fn, input));
34
- }
35
- /** Starts a new sandbox. Prefer `hatchet.sandbox.create`. */
36
- static async create(client, opts = {}) {
37
- void (0, otel_js_1.setupTelemetry)();
38
- const run = turnRunner(client);
39
- const created = await run({
40
- fn: '$create',
41
- input: {},
42
- ...(opts.id ? { sandboxId: opts.id } : {}),
43
- ...(opts.template ? { image: opts.template } : {}),
44
- ...(opts.from ? { snapshotId: opts.from } : {}),
45
- ...(opts.metadata ? { metadata: opts.metadata } : {}),
46
- ...(opts.idleTimeoutMs ? { idleTtlMs: opts.idleTimeoutMs } : {}),
47
- ...(opts.inheritParentMetadata ? { inheritMetadata: true } : {}),
48
- });
49
- return new Sandbox(run, created.sandboxId, opts.template);
50
- }
51
- /** Attaches to a sandbox by ID. Prefer `hatchet.sandbox.connect`. */
52
- static async connect(client, sandboxId, opts = {}) {
53
- void (0, otel_js_1.setupTelemetry)();
54
- const run = turnRunner(client);
55
- await run({ fn: '$connect', input: {}, sandboxId, ...(opts.template ? { image: opts.template } : {}) });
56
- return new Sandbox(run, sandboxId, opts.template);
57
- }
58
- /** Lists sandboxes on the provider. Prefer `hatchet.sandbox.list`. */
59
- static async list(client, opts = {}) {
60
- const result = await turnRunner(client)({
61
- fn: '$list',
62
- input: { ...(opts.sandboxId ? { sandboxId: opts.sandboxId } : {}), ...(opts.metadata ? { metadata: opts.metadata } : {}) },
63
- });
64
- const raw = (result.output.sandboxes ?? []);
65
- return raw.map((s) => ({ ...s, createdAt: new Date(s.createdAt), lastUsed: s.lastUsed ? new Date(s.lastUsed) : undefined }));
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
- }
79
- /** Runs a shell command with `sh -c` in the workspace and makes a snapshot. A timeout gives `exitCode` 124, not an error. */
80
- async exec(cmd, opts = {}) {
81
- return this.turn('$command', { cmd, ...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}) });
82
- }
83
- /** Removes the sandbox. Its snapshots stay; `connect` can bring it back. */
84
- async kill() {
85
- await this.turn('$destroy', {});
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
- }
91
- async turn(fn, input) {
92
- const result = await this.run({
93
- sandboxId: this.sandboxId,
94
- ...(this.template ? { image: this.template } : {}),
95
- fn,
96
- input,
97
- });
98
- return { ...result.output, snapshotId: result.snapshotId, timings: result.timings };
99
- }
100
- }
101
- exports.Sandbox = Sandbox;
102
- Object.defineProperty(v1_1.HatchetClient.prototype, 'sandbox', {
103
- configurable: true,
104
- get() {
105
- return {
106
- create: (opts) => Sandbox.create(this, opts),
107
- connect: (sandboxId, opts) => Sandbox.connect(this, sandboxId, opts),
108
- list: (opts) => Sandbox.list(this, opts),
109
- delete: (opts) => Sandbox.delete(this, opts),
110
- };
111
- },
112
- });