@brimble/sandbox 0.1.0 → 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.
Files changed (61) hide show
  1. package/README.md +9 -5
  2. package/dist/package.json +10 -3
  3. package/dist/src/client.d.ts +3 -3
  4. package/dist/src/client.js +3 -3
  5. package/dist/src/index.d.ts +3 -3
  6. package/dist/src/index.js +2 -2
  7. package/dist/src/resources/files.d.ts +6 -1
  8. package/dist/src/resources/files.js +36 -0
  9. package/dist/src/resources/sandbox-handle.d.ts +6 -1
  10. package/dist/src/resources/sandbox-handle.js +8 -0
  11. package/dist/src/resources/scoped-sandbox.d.ts +3 -1
  12. package/dist/src/resources/scoped-sandbox.js +4 -0
  13. package/dist/src/types/files.d.ts +16 -0
  14. package/dist/src/types/index.d.ts +1 -1
  15. package/package.json +7 -3
  16. package/CODEX.md +0 -188
  17. package/PLAN.md +0 -364
  18. package/src/client.ts +0 -61
  19. package/src/constants.ts +0 -17
  20. package/src/enums/code-language.ts +0 -4
  21. package/src/enums/destroy-reason.ts +0 -8
  22. package/src/enums/destroy-timeout.ts +0 -8
  23. package/src/enums/index.ts +0 -7
  24. package/src/enums/sandbox-status.ts +0 -9
  25. package/src/enums/snapshot-mode.ts +0 -4
  26. package/src/enums/snapshot-status.ts +0 -5
  27. package/src/enums/volume-type.ts +0 -3
  28. package/src/errors/index.ts +0 -2
  29. package/src/errors/sandbox-api-error.ts +0 -54
  30. package/src/index.ts +0 -71
  31. package/src/resources/exec.ts +0 -56
  32. package/src/resources/files.ts +0 -46
  33. package/src/resources/index.ts +0 -8
  34. package/src/resources/path.ts +0 -16
  35. package/src/resources/sandbox-handle.ts +0 -215
  36. package/src/resources/sandboxes.ts +0 -297
  37. package/src/resources/scoped-sandbox.ts +0 -65
  38. package/src/resources/snapshots.ts +0 -104
  39. package/src/resources/stats.ts +0 -30
  40. package/src/resources/volumes.ts +0 -95
  41. package/src/transport/auth.ts +0 -4
  42. package/src/transport/http.ts +0 -501
  43. package/src/transport/pagination.ts +0 -10
  44. package/src/types/exec.ts +0 -42
  45. package/src/types/files.ts +0 -1
  46. package/src/types/index.ts +0 -23
  47. package/src/types/pagination.ts +0 -16
  48. package/src/types/region.ts +0 -19
  49. package/src/types/sandbox.ts +0 -103
  50. package/src/types/snapshot.ts +0 -17
  51. package/src/types/stats.ts +0 -35
  52. package/src/types/template.ts +0 -5
  53. package/src/types/volume.ts +0 -26
  54. package/test/integration/sandbox.integration.test.ts +0 -269
  55. package/test/unit/client.test.ts +0 -87
  56. package/test/unit/sandboxes.test.ts +0 -69
  57. package/test/unit/transport.test.ts +0 -126
  58. package/test/unit/volumes.test.ts +0 -122
  59. package/tsconfig.json +0 -16
  60. package/vitest.config.ts +0 -12
  61. package/vitest.integration.config.ts +0 -15
@@ -1,56 +0,0 @@
1
- import type { CodeInput, ExecInput, ExecResult } from '../types';
2
- import type { RequestOptions } from '../transport/http';
3
- import { HttpTransport } from '../transport/http';
4
-
5
- export class ExecResource {
6
- private readonly transport: HttpTransport;
7
- private readonly sandboxId: string;
8
-
9
- /** @internal Create the exec/code runner wrapper for one sandbox. */
10
- public constructor(transport: HttpTransport, sandboxId: string) {
11
- this.transport = transport;
12
- this.sandboxId = sandboxId;
13
- }
14
-
15
- /** Run a shell command in the sandbox. */
16
- public exec(input: ExecInput & { stream: true }, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
17
- public exec(input: ExecInput, options?: RequestOptions): Promise<ExecResult>;
18
- public exec(input: ExecInput, options?: RequestOptions): Promise<ExecResult | ReadableStream<Uint8Array>> {
19
- if (input.stream === true) {
20
- return this.transport.requestJsonStream({
21
- endpoint: `/sandboxes/${this.sandboxId}/exec`,
22
- method: 'POST',
23
- body: input,
24
- ...options,
25
- });
26
- }
27
-
28
- return this.transport.requestJson<ExecResult>({
29
- endpoint: `/sandboxes/${this.sandboxId}/exec`,
30
- method: 'POST',
31
- body: input,
32
- ...options,
33
- }) as Promise<ExecResult>;
34
- }
35
-
36
- /** Run a code snippet in the sandbox. */
37
- public runCode(input: CodeInput & { stream: true }, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
38
- public runCode(input: CodeInput, options?: RequestOptions): Promise<ExecResult>;
39
- public runCode(input: CodeInput, options?: RequestOptions): Promise<ExecResult | ReadableStream<Uint8Array>> {
40
- if (input.stream === true) {
41
- return this.transport.requestJsonStream({
42
- endpoint: `/sandboxes/${this.sandboxId}/code`,
43
- method: 'POST',
44
- body: input,
45
- ...options,
46
- });
47
- }
48
-
49
- return this.transport.requestJson<ExecResult>({
50
- endpoint: `/sandboxes/${this.sandboxId}/code`,
51
- method: 'POST',
52
- body: input,
53
- ...options,
54
- }) as Promise<ExecResult>;
55
- }
56
- }
@@ -1,46 +0,0 @@
1
- import type { FileUploadBody } from '../types';
2
- import type { RequestOptions } from '../transport/http';
3
- import { HttpTransport } from '../transport/http';
4
- import { encodeFilePath } from './path';
5
-
6
- export class FilesResource {
7
- private readonly transport: HttpTransport;
8
- private readonly sandboxId: string;
9
-
10
- /** @internal Create the files wrapper for one sandbox. */
11
- public constructor(transport: HttpTransport, sandboxId: string) {
12
- this.transport = transport;
13
- this.sandboxId = sandboxId;
14
- }
15
-
16
- /**
17
- * Upload file bytes to a path inside the sandbox.
18
- * Tip: pass a Buffer/Uint8Array when you can so Content-Length is set automatically.
19
- */
20
- public async put(path: string, body: FileUploadBody, options?: RequestOptions): Promise<void> {
21
- const headers: Record<string, string> = {
22
- 'content-type': 'application/octet-stream',
23
- };
24
-
25
- if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
26
- headers['content-length'] = String(body.byteLength);
27
- }
28
-
29
- await this.transport.requestBinary({
30
- endpoint: `/sandboxes/${this.sandboxId}/files/${encodeFilePath(path)}`,
31
- method: 'PUT',
32
- body,
33
- headers,
34
- ...options,
35
- });
36
- }
37
-
38
- /** Download a file from the sandbox as a stream. */
39
- public get(path: string, options?: RequestOptions): Promise<ReadableStream<Uint8Array>> {
40
- return this.transport.requestStream({
41
- endpoint: `/sandboxes/${this.sandboxId}/files/${encodeFilePath(path)}`,
42
- method: 'GET',
43
- ...options,
44
- });
45
- }
46
- }
@@ -1,8 +0,0 @@
1
- export { ExecResource } from './exec';
2
- export { FilesResource } from './files';
3
- export { SandboxHandle } from './sandbox-handle';
4
- export { SandboxesResource } from './sandboxes';
5
- export { ScopedSandboxResource } from './scoped-sandbox';
6
- export { SnapshotScopeResource, SnapshotsResource } from './snapshots';
7
- export { StatsResource } from './stats';
8
- export { VolumesResource } from './volumes';
@@ -1,16 +0,0 @@
1
- /** Encode a single path segment safely for URL usage. */
2
- export function encodePathSegment(value: string): string {
3
- return encodeURIComponent(value);
4
- }
5
-
6
- /**
7
- * Encode a sandbox file path without encoding forward slashes.
8
- * Example: `tmp/my file.txt` -> `tmp/my%20file.txt`
9
- */
10
- export function encodeFilePath(path: string): string {
11
- return path
12
- .split('/')
13
- .filter((segment) => segment.length > 0)
14
- .map(encodePathSegment)
15
- .join('/');
16
- }
@@ -1,215 +0,0 @@
1
- import {
2
- DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS,
3
- DEFAULT_SANDBOX_READY_TIMEOUT_MS,
4
- } from '../constants';
5
- import { SandboxStatus } from '../enums';
6
- import type { RequestOptions } from '../transport/http';
7
- import type {
8
- AckMessage,
9
- CodeInput,
10
- CreateSandboxResult,
11
- CreateSnapshotInput,
12
- ExecInput,
13
- ExecResult,
14
- FileUploadBody,
15
- Paginated,
16
- Pagination,
17
- Sandbox,
18
- SandboxRuntimeOptions,
19
- Snapshot,
20
- Stats,
21
- StatsQuery,
22
- WaitPreference,
23
- WaitUntilReadyOptions,
24
- } from '../types';
25
- import { ScopedSandboxResource } from './scoped-sandbox';
26
- import type { SandboxesResource } from './sandboxes';
27
-
28
- function delay(ms: number): Promise<void> {
29
- return new Promise((resolve) => {
30
- setTimeout(resolve, ms);
31
- });
32
- }
33
-
34
- export class SandboxHandle {
35
- private readonly sandboxes: SandboxesResource;
36
- private readonly scope: ScopedSandboxResource;
37
- private sandboxState: Sandbox | CreateSandboxResult;
38
-
39
- /** Snapshot operations grouped under a dedicated namespace. */
40
- public readonly snapshots: {
41
- create: (input: CreateSnapshotInput, options?: RequestOptions) => Promise<Snapshot>;
42
- list: (query?: Pagination, options?: RequestOptions) => Promise<Paginated<Snapshot>>;
43
- };
44
-
45
- /** @internal Create a sandbox handle from create/get responses. */
46
- public constructor(sandboxes: SandboxesResource, state: Sandbox | CreateSandboxResult) {
47
- this.sandboxes = sandboxes;
48
- this.sandboxState = state;
49
- this.scope = this.sandboxes.use(state.id);
50
-
51
- this.snapshots = {
52
- create: (input, options) => this.createSnapshot(input, options),
53
- list: (query = {}, options) => this.listSnapshots(query, options),
54
- };
55
- }
56
-
57
- /** Current sandbox id. */
58
- public get id(): string {
59
- return this.sandboxState.id;
60
- }
61
-
62
- /** Current cached sandbox status. */
63
- public get status(): SandboxStatus {
64
- return this.sandboxState.status;
65
- }
66
-
67
- /** Current cached sandbox payload. */
68
- public get data(): Sandbox | CreateSandboxResult {
69
- return this.sandboxState;
70
- }
71
-
72
- /** Refresh sandbox details from the API and update local state. */
73
- public async refresh(options?: RequestOptions): Promise<Sandbox> {
74
- const sandbox = await this.sandboxes.getData(this.id, options);
75
- this.sandboxState = sandbox;
76
- return sandbox;
77
- }
78
-
79
- /** Destroy this sandbox (idempotent). */
80
- public async destroy(options?: RequestOptions): Promise<void> {
81
- await this.sandboxes.destroy(this.id, options);
82
- }
83
-
84
- /** Request pause for this sandbox and refresh cached state. */
85
- public async pause(options?: RequestOptions): Promise<AckMessage | undefined> {
86
- const response = await this.sandboxes.pause(this.id, options);
87
- await this.refresh(options);
88
- return response;
89
- }
90
-
91
- /** Request resume for this sandbox and refresh cached state. */
92
- public async resume(options?: RequestOptions): Promise<AckMessage | undefined> {
93
- const response = await this.sandboxes.resume(this.id, options);
94
- await this.refresh(options);
95
- return response;
96
- }
97
-
98
- /**
99
- * Poll sandbox status until it becomes `ready`.
100
- * Throws on timeout or when `signal` is aborted.
101
- */
102
- public async waitUntilReady(options: WaitUntilReadyOptions = {}): Promise<Sandbox> {
103
- const timeoutMs = options.timeoutMs ?? DEFAULT_SANDBOX_READY_TIMEOUT_MS;
104
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_SANDBOX_READY_POLL_INTERVAL_MS;
105
- const deadline = Date.now() + timeoutMs;
106
-
107
- while (true) {
108
- if (options.signal?.aborted) {
109
- throw new Error('waitUntilReady aborted by signal');
110
- }
111
-
112
- const sandbox = await this.refresh({ signal: options.signal });
113
- if (sandbox.status === SandboxStatus.Ready) {
114
- return sandbox;
115
- }
116
-
117
- if (Date.now() >= deadline) {
118
- throw new Error(`Sandbox ${this.id} did not become ready within ${timeoutMs}ms`);
119
- }
120
-
121
- await delay(pollIntervalMs);
122
- }
123
- }
124
-
125
- /**
126
- * Run a shell command in this sandbox.
127
- * By default this throws when not ready; set `waitUntilReady` to auto-wait.
128
- */
129
- public exec(input: ExecInput & { stream: true }, options?: SandboxRuntimeOptions): Promise<ReadableStream<Uint8Array>>;
130
- public exec(input: ExecInput, options?: SandboxRuntimeOptions): Promise<ExecResult>;
131
- public async exec(input: ExecInput, options: SandboxRuntimeOptions = {}): Promise<ExecResult | ReadableStream<Uint8Array>> {
132
- await this.ensureReady(options.waitUntilReady);
133
- return this.scope.exec(input, options);
134
- }
135
-
136
- /**
137
- * Run a code snippet in this sandbox.
138
- * By default this throws when not ready; set `waitUntilReady` to auto-wait.
139
- */
140
- public runCode(input: CodeInput & { stream: true }, options?: SandboxRuntimeOptions): Promise<ReadableStream<Uint8Array>>;
141
- public runCode(input: CodeInput, options?: SandboxRuntimeOptions): Promise<ExecResult>;
142
- public async runCode(input: CodeInput, options: SandboxRuntimeOptions = {}): Promise<ExecResult | ReadableStream<Uint8Array>> {
143
- await this.ensureReady(options.waitUntilReady);
144
- return this.scope.runCode(input, options);
145
- }
146
-
147
- /**
148
- * Upload a file into this sandbox.
149
- * By default this throws when not ready; set `waitUntilReady` to auto-wait.
150
- */
151
- public async putFile(path: string, body: FileUploadBody, options: SandboxRuntimeOptions = {}): Promise<void> {
152
- await this.ensureReady(options.waitUntilReady);
153
- await this.scope.putFile(path, body, options);
154
- }
155
-
156
- /**
157
- * Download a file from this sandbox.
158
- * By default this throws when not ready; set `waitUntilReady` to auto-wait.
159
- */
160
- public async getFile(path: string, options: SandboxRuntimeOptions = {}): Promise<ReadableStream<Uint8Array>> {
161
- await this.ensureReady(options.waitUntilReady);
162
- return this.scope.getFile(path, options);
163
- }
164
-
165
- /**
166
- * Fetch usage stats for this sandbox.
167
- * By default this throws when not ready; set `waitUntilReady` to auto-wait.
168
- */
169
- public async stats(query: StatsQuery = {}, options: SandboxRuntimeOptions = {}): Promise<Stats> {
170
- await this.ensureReady(options.waitUntilReady);
171
- return this.scope.stats(query, options);
172
- }
173
-
174
- /**
175
- * Create a snapshot for this sandbox.
176
- * By default this throws when not ready; set `waitUntilReady` to auto-wait.
177
- */
178
- public async createSnapshot(input: CreateSnapshotInput, options: SandboxRuntimeOptions = {}): Promise<Snapshot> {
179
- await this.ensureReady(options.waitUntilReady);
180
- return this.scope.createSnapshot(input, options);
181
- }
182
-
183
- /**
184
- * List snapshots for this sandbox.
185
- * By default this throws when not ready; set `waitUntilReady` to auto-wait.
186
- */
187
- public async listSnapshots(query: Pagination = {}, options: SandboxRuntimeOptions = {}): Promise<Paginated<Snapshot>> {
188
- await this.ensureReady(options.waitUntilReady);
189
- return this.scope.listSnapshots(query, options);
190
- }
191
-
192
- private async ensureReady(waitUntilReady: WaitPreference | undefined): Promise<void> {
193
- if (this.status === SandboxStatus.Ready) {
194
- return;
195
- }
196
-
197
- if (waitUntilReady) {
198
- if (typeof waitUntilReady === 'object') {
199
- await this.waitUntilReady(waitUntilReady);
200
- return;
201
- }
202
-
203
- await this.waitUntilReady();
204
- return;
205
- }
206
-
207
- this.assertReady();
208
- }
209
-
210
- private assertReady(): void {
211
- if (this.status !== SandboxStatus.Ready) {
212
- throw new Error(`Sandbox ${this.id} is ${this.status}. Call waitUntilReady() or refresh() before runtime operations.`);
213
- }
214
- }
215
- }
@@ -1,297 +0,0 @@
1
- import { DEFAULT_PAGE, DEFAULT_PAGE_LIMIT } from '../constants';
2
- import { VolumeType } from '../enums';
3
- import type { RequestOptions } from '../transport/http';
4
- import { HttpTransport } from '../transport/http';
5
- import { toPaginationQuery } from '../transport/pagination';
6
- import type {
7
- AckMessage,
8
- CreateSandboxRequest,
9
- CreateSandboxResult,
10
- CreateSandboxWithVolumeInput,
11
- CreateVolumeInput,
12
- Paginated,
13
- Sandbox,
14
- SandboxReadyRequestOptions,
15
- SandboxRegionInput,
16
- SandboxRegionsResult,
17
- SandboxTemplate,
18
- TeamScopedPagination,
19
- Volume,
20
- WaitPreference,
21
- } from '../types';
22
- import { SandboxHandle } from './sandbox-handle';
23
- import { ScopedSandboxResource } from './scoped-sandbox';
24
- import { VolumesResource } from './volumes';
25
-
26
- export type QuickstartSandboxInput = Omit<CreateSandboxRequest, 'template' | 'persistent' | 'persistentDiskGB'> & {
27
- template?: string;
28
- persistentDiskGB?: number;
29
- waitUntilReady?: WaitPreference;
30
- };
31
-
32
- export class SandboxesResource {
33
- private readonly transport: HttpTransport;
34
- private readonly volumes: VolumesResource;
35
-
36
- /** @internal Create the sandboxes resource wrapper. */
37
- public constructor(transport: HttpTransport) {
38
- this.transport = transport;
39
- this.volumes = new VolumesResource(transport);
40
- }
41
-
42
- /**
43
- * Create a new sandbox.
44
- * Region is optional; when omitted this SDK picks the first available region.
45
- * The sandbox starts asynchronously, so fetch it until `status` is `ready`
46
- * before running commands or file operations.
47
- */
48
- public async create(input: CreateSandboxRequest, options?: RequestOptions): Promise<SandboxHandle> {
49
- const region = await this.resolveRegionId(input.region, options);
50
- const body: CreateSandboxRequest = {
51
- ...input,
52
- region,
53
- };
54
-
55
- const result = (await this.transport.requestJson<CreateSandboxResult>({
56
- endpoint: '/sandboxes',
57
- method: 'POST',
58
- body,
59
- ...options,
60
- })) as CreateSandboxResult;
61
-
62
- return new SandboxHandle(this, result);
63
- }
64
-
65
- /** Create a sandbox and wait until it is `ready` before returning it. */
66
- public async createReady(input: CreateSandboxRequest, options: SandboxReadyRequestOptions = {}): Promise<SandboxHandle> {
67
- const sandbox = await this.create(input, options.request);
68
- await sandbox.waitUntilReady(options.wait);
69
- return sandbox;
70
- }
71
-
72
- /**
73
- * Create a volume and then create a sandbox attached to that volume.
74
- * This is the one-call helper for persistent sandbox workflows.
75
- */
76
- public async withVolume(input: CreateSandboxWithVolumeInput, options?: RequestOptions): Promise<SandboxHandle> {
77
- const region = await this.resolveRegionId(input.sandbox.region ?? input.volume.region, options);
78
-
79
- const volume = await this.volumes.create(
80
- {
81
- ...input.volume,
82
- region,
83
- type: VolumeType.Sandbox,
84
- },
85
- options,
86
- );
87
-
88
- return this.create(
89
- {
90
- ...input.sandbox,
91
- region,
92
- volumeId: volume.id,
93
- },
94
- options,
95
- );
96
- }
97
-
98
- /** Create a sandbox-scoped volume with package-level defaults and validation. */
99
- public createVolume(input: CreateVolumeInput, options?: RequestOptions): Promise<Volume> {
100
- return this.volumes.create(input, options);
101
- }
102
-
103
- /** List your sandboxes with pagination. */
104
- public async list(query: TeamScopedPagination = {}, options?: RequestOptions): Promise<Paginated<SandboxHandle>> {
105
- const page = await this.listData(query, options);
106
-
107
- return {
108
- ...page,
109
- data: page.data.map((sandbox) => new SandboxHandle(this, sandbox)),
110
- };
111
- }
112
-
113
- /** Iterate over all sandbox handles across paginated results. */
114
- public async *iterate(query: TeamScopedPagination = {}, options?: RequestOptions): AsyncGenerator<SandboxHandle> {
115
- const limit = query.limit ?? DEFAULT_PAGE_LIMIT;
116
- let page = query.page ?? DEFAULT_PAGE;
117
-
118
- while (true) {
119
- const paginated = await this.list({ ...query, page, limit }, options);
120
-
121
- for (const sandbox of paginated.data) {
122
- yield sandbox;
123
- }
124
-
125
- if (page >= paginated.totalPages || paginated.data.length === 0) {
126
- return;
127
- }
128
-
129
- page += 1;
130
- }
131
- }
132
-
133
- /** Fetch one sandbox handle by id. */
134
- public async get(sandboxId: string, options?: RequestOptions): Promise<SandboxHandle> {
135
- const sandbox = await this.getData(sandboxId, options);
136
- return new SandboxHandle(this, sandbox);
137
- }
138
-
139
- /** Fetch one sandbox and wait for `ready` before returning the handle. */
140
- public async getReady(sandboxId: string, options: SandboxReadyRequestOptions = {}): Promise<SandboxHandle> {
141
- const sandbox = await this.get(sandboxId, options.request);
142
- await sandbox.waitUntilReady(options.wait);
143
- return sandbox;
144
- }
145
-
146
- /** @internal Fetch raw sandbox payload by id. */
147
- public getData(sandboxId: string, options?: RequestOptions): Promise<Sandbox> {
148
- return this.transport.requestJson<Sandbox>({
149
- endpoint: `/sandboxes/${sandboxId}`,
150
- method: 'GET',
151
- ...options,
152
- }) as Promise<Sandbox>;
153
- }
154
-
155
- /** @internal Fetch raw paginated sandbox payload. */
156
- public listData(query: TeamScopedPagination = {}, options?: RequestOptions): Promise<Paginated<Sandbox>> {
157
- const params = toPaginationQuery(query);
158
-
159
- if (query.teamId) {
160
- params.set('teamId', query.teamId);
161
- }
162
-
163
- return this.transport.requestJson<Paginated<Sandbox>>({
164
- endpoint: '/sandboxes',
165
- method: 'GET',
166
- query: params,
167
- ...options,
168
- }) as Promise<Paginated<Sandbox>>;
169
- }
170
-
171
- /** List regions where sandboxes can be provisioned. */
172
- public listRegions(options?: RequestOptions): Promise<SandboxRegionsResult> {
173
- return this.transport.requestJson<SandboxRegionsResult>({
174
- endpoint: '/sandboxes/regions',
175
- method: 'GET',
176
- ...options,
177
- }) as Promise<SandboxRegionsResult>;
178
- }
179
-
180
- /** List sandbox templates available for create operations. */
181
- public async listTemplates(options?: RequestOptions): Promise<SandboxTemplate[]> {
182
- const payload = await this.transport.requestJson<unknown>({
183
- endpoint: '/sandbox/templates',
184
- method: 'GET',
185
- ...options,
186
- });
187
-
188
- if (Array.isArray(payload)) {
189
- return payload as SandboxTemplate[];
190
- }
191
-
192
- if (payload && typeof payload === 'object' && 'templates' in payload) {
193
- const templates = (payload as { templates?: unknown }).templates;
194
- if (Array.isArray(templates)) {
195
- return templates as SandboxTemplate[];
196
- }
197
- }
198
-
199
- return [];
200
- }
201
-
202
- /** Fetch one template by name from the template catalog. */
203
- public async getTemplate(templateName: string, options?: RequestOptions): Promise<SandboxTemplate | undefined> {
204
- const templates = await this.listTemplates(options);
205
- return templates.find((template) => template.name === templateName);
206
- }
207
-
208
- /** Destroy a sandbox (idempotent). */
209
- public async destroy(sandboxId: string, options?: RequestOptions): Promise<void> {
210
- await this.transport.requestJson({
211
- endpoint: `/sandboxes/${sandboxId}`,
212
- method: 'DELETE',
213
- ...options,
214
- });
215
- }
216
-
217
- /** Request sandbox pause. */
218
- public pause(sandboxId: string, options?: RequestOptions): Promise<AckMessage | undefined> {
219
- return this.transport.requestJson<AckMessage>({
220
- endpoint: `/sandboxes/${sandboxId}/pause`,
221
- method: 'POST',
222
- ...options,
223
- });
224
- }
225
-
226
- /** Request sandbox resume. */
227
- public resume(sandboxId: string, options?: RequestOptions): Promise<AckMessage | undefined> {
228
- return this.transport.requestJson<AckMessage>({
229
- endpoint: `/sandboxes/${sandboxId}/resume`,
230
- method: 'POST',
231
- ...options,
232
- });
233
- }
234
-
235
- /** Use runtime operations for a specific sandbox id. */
236
- public use(sandboxId: string): ScopedSandboxResource {
237
- return new ScopedSandboxResource(this.transport, sandboxId);
238
- }
239
-
240
- /** Opinionated Node.js quickstart (persistent sandbox + optional wait). */
241
- public async quickstartNode(input: QuickstartSandboxInput = {}, options?: RequestOptions): Promise<SandboxHandle> {
242
- return this.quickstart({
243
- ...input,
244
- template: input.template ?? 'node-22',
245
- persistentDiskGB: input.persistentDiskGB ?? 20,
246
- }, options);
247
- }
248
-
249
- /** Opinionated Python quickstart (persistent sandbox + optional wait). */
250
- public async quickstartPython(input: QuickstartSandboxInput = {}, options?: RequestOptions): Promise<SandboxHandle> {
251
- return this.quickstart({
252
- ...input,
253
- template: input.template ?? 'python-3.12',
254
- persistentDiskGB: input.persistentDiskGB ?? 20,
255
- }, options);
256
- }
257
-
258
- private async quickstart(input: QuickstartSandboxInput, options?: RequestOptions): Promise<SandboxHandle> {
259
- const { waitUntilReady, persistentDiskGB, ...createInput } = input;
260
-
261
- const sandbox = await this.create(
262
- {
263
- ...createInput,
264
- persistent: true,
265
- persistentDiskGB,
266
- },
267
- options,
268
- );
269
-
270
- if (waitUntilReady === false) {
271
- return sandbox;
272
- }
273
-
274
- if (typeof waitUntilReady === 'object') {
275
- await sandbox.waitUntilReady(waitUntilReady);
276
- return sandbox;
277
- }
278
-
279
- await sandbox.waitUntilReady();
280
- return sandbox;
281
- }
282
-
283
- private async resolveRegionId(region: SandboxRegionInput | undefined, options?: RequestOptions): Promise<string> {
284
- if (region && region !== 'auto') {
285
- return region;
286
- }
287
-
288
- const { regions } = await this.listRegions(options);
289
- const regionId = regions[0]?.id;
290
-
291
- if (!regionId) {
292
- throw new Error('No sandbox regions available for this account.');
293
- }
294
-
295
- return regionId;
296
- }
297
- }