@brimble/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.
Files changed (48) hide show
  1. package/dist/package.json +9 -2
  2. package/package.json +7 -3
  3. package/CODEX.md +0 -188
  4. package/PLAN.md +0 -364
  5. package/src/client.ts +0 -61
  6. package/src/constants.ts +0 -17
  7. package/src/enums/code-language.ts +0 -4
  8. package/src/enums/destroy-reason.ts +0 -8
  9. package/src/enums/destroy-timeout.ts +0 -8
  10. package/src/enums/index.ts +0 -7
  11. package/src/enums/sandbox-status.ts +0 -9
  12. package/src/enums/snapshot-mode.ts +0 -4
  13. package/src/enums/snapshot-status.ts +0 -5
  14. package/src/enums/volume-type.ts +0 -3
  15. package/src/errors/index.ts +0 -2
  16. package/src/errors/sandbox-api-error.ts +0 -54
  17. package/src/index.ts +0 -71
  18. package/src/resources/exec.ts +0 -56
  19. package/src/resources/files.ts +0 -46
  20. package/src/resources/index.ts +0 -8
  21. package/src/resources/path.ts +0 -16
  22. package/src/resources/sandbox-handle.ts +0 -215
  23. package/src/resources/sandboxes.ts +0 -297
  24. package/src/resources/scoped-sandbox.ts +0 -65
  25. package/src/resources/snapshots.ts +0 -104
  26. package/src/resources/stats.ts +0 -30
  27. package/src/resources/volumes.ts +0 -95
  28. package/src/transport/auth.ts +0 -4
  29. package/src/transport/http.ts +0 -501
  30. package/src/transport/pagination.ts +0 -10
  31. package/src/types/exec.ts +0 -42
  32. package/src/types/files.ts +0 -1
  33. package/src/types/index.ts +0 -23
  34. package/src/types/pagination.ts +0 -16
  35. package/src/types/region.ts +0 -19
  36. package/src/types/sandbox.ts +0 -103
  37. package/src/types/snapshot.ts +0 -17
  38. package/src/types/stats.ts +0 -35
  39. package/src/types/template.ts +0 -5
  40. package/src/types/volume.ts +0 -26
  41. package/test/integration/sandbox.integration.test.ts +0 -269
  42. package/test/unit/client.test.ts +0 -87
  43. package/test/unit/sandboxes.test.ts +0 -69
  44. package/test/unit/transport.test.ts +0 -126
  45. package/test/unit/volumes.test.ts +0 -122
  46. package/tsconfig.json +0 -16
  47. package/vitest.config.ts +0 -12
  48. package/vitest.integration.config.ts +0 -15
@@ -1,4 +0,0 @@
1
- export enum CodeLanguage {
2
- Python = 'python',
3
- Node = 'node',
4
- }
@@ -1,8 +0,0 @@
1
- export enum DestroyReason {
2
- User = 'user',
3
- IdleTtl = 'idle_ttl',
4
- MaxLifetime = 'max_lifetime',
5
- OneShotStopped = 'one_shot_stopped',
6
- Failed = 'failed',
7
- PausedTooLong = 'paused_too_long',
8
- }
@@ -1,8 +0,0 @@
1
- export enum DestroyTimeout {
2
- ThirtyMinutes = '30m',
3
- OneHour = '1h',
4
- ThreeHours = '3h',
5
- SixHours = '6h',
6
- TwelveHours = '12h',
7
- EighteenHours = '18h',
8
- }
@@ -1,7 +0,0 @@
1
- export { CodeLanguage } from './code-language';
2
- export { DestroyReason } from './destroy-reason';
3
- export { DestroyTimeout } from './destroy-timeout';
4
- export { SandboxStatus } from './sandbox-status';
5
- export { SnapshotMode } from './snapshot-mode';
6
- export { SnapshotStatus } from './snapshot-status';
7
- export { VolumeType } from './volume-type';
@@ -1,9 +0,0 @@
1
- export enum SandboxStatus {
2
- Starting = 'starting',
3
- Ready = 'ready',
4
- Pausing = 'pausing',
5
- Paused = 'paused',
6
- Resuming = 'resuming',
7
- Failed = 'failed',
8
- Destroyed = 'destroyed',
9
- }
@@ -1,4 +0,0 @@
1
- export enum SnapshotMode {
2
- Manual = 'manual',
3
- Automatic = 'automatic',
4
- }
@@ -1,5 +0,0 @@
1
- export enum SnapshotStatus {
2
- Creating = 'creating',
3
- Ready = 'ready',
4
- Failed = 'failed',
5
- }
@@ -1,3 +0,0 @@
1
- export enum VolumeType {
2
- Sandbox = 'sandbox',
3
- }
@@ -1,2 +0,0 @@
1
- export { AuthError, NotFoundError, RateLimitError, SandboxApiError, ValidationError } from './sandbox-api-error';
2
- export type { SandboxApiErrorArgs } from './sandbox-api-error';
@@ -1,54 +0,0 @@
1
- export type SandboxApiErrorArgs = {
2
- status: number;
3
- message: string;
4
- endpoint: string;
5
- responseBody: unknown;
6
- requestId?: string | null;
7
- };
8
-
9
- export class SandboxApiError extends Error {
10
- public readonly status: number;
11
- public readonly endpoint: string;
12
- public readonly responseBody: unknown;
13
- public readonly requestId: string | null;
14
-
15
- public constructor(args: SandboxApiErrorArgs) {
16
- super(args.message);
17
- this.name = 'SandboxApiError';
18
- this.status = args.status;
19
- this.endpoint = args.endpoint;
20
- this.responseBody = args.responseBody;
21
- this.requestId = args.requestId ?? null;
22
- }
23
- }
24
-
25
- export class AuthError extends SandboxApiError {
26
- public constructor(args: SandboxApiErrorArgs) {
27
- super(args);
28
- this.name = 'AuthError';
29
- }
30
- }
31
-
32
- export class ValidationError extends SandboxApiError {
33
- public constructor(args: SandboxApiErrorArgs) {
34
- super(args);
35
- this.name = 'ValidationError';
36
- }
37
- }
38
-
39
- export class NotFoundError extends SandboxApiError {
40
- public constructor(args: SandboxApiErrorArgs) {
41
- super(args);
42
- this.name = 'NotFoundError';
43
- }
44
- }
45
-
46
- export class RateLimitError extends SandboxApiError {
47
- public readonly retryAfterSeconds: number | null;
48
-
49
- public constructor(args: SandboxApiErrorArgs & { retryAfterSeconds?: number | null }) {
50
- super(args);
51
- this.name = 'RateLimitError';
52
- this.retryAfterSeconds = args.retryAfterSeconds ?? null;
53
- }
54
- }
package/src/index.ts DELETED
@@ -1,71 +0,0 @@
1
- export {
2
- DEFAULT_BASE_URL,
3
- DEFAULT_PAGE,
4
- DEFAULT_PAGE_LIMIT,
5
- DEFAULT_RETRY_BASE_DELAY_MS,
6
- DEFAULT_RETRY_MAX_ATTEMPTS,
7
- DEFAULT_RETRY_MAX_DELAY_MS,
8
- DEFAULT_RETRY_STATUSES,
9
- DEFAULT_TIMEOUT_MS,
10
- MAX_PAGE_LIMIT,
11
- SANDBOX_API_KEY_ENV_NAME,
12
- } from './constants';
13
- export { SandboxClient } from './client';
14
- export type { SandboxClientOptions } from './client';
15
-
16
- export { AuthError, NotFoundError, RateLimitError, SandboxApiError, ValidationError } from './errors';
17
- export type { SandboxApiErrorArgs } from './errors';
18
-
19
- export {
20
- CodeLanguage,
21
- DestroyReason,
22
- DestroyTimeout,
23
- SandboxStatus,
24
- SnapshotMode,
25
- SnapshotStatus,
26
- VolumeType,
27
- } from './enums';
28
-
29
- export {
30
- ExecResource,
31
- FilesResource,
32
- SandboxHandle,
33
- SandboxesResource,
34
- ScopedSandboxResource,
35
- SnapshotScopeResource,
36
- SnapshotsResource,
37
- StatsResource,
38
- VolumesResource,
39
- } from './resources';
40
-
41
- export type {
42
- AckMessage,
43
- CodeInput,
44
- CreateSandboxInput,
45
- CreateSandboxResult,
46
- CreateSnapshotInput,
47
- CreateVolumeInput,
48
- ExecInput,
49
- ExecResult,
50
- ExecStreamFrame,
51
- FileUploadBody,
52
- Paginated,
53
- Pagination,
54
- RegionSummary,
55
- SandboxRegion,
56
- SandboxRegionsResult,
57
- Sandbox,
58
- SandboxSpecs,
59
- Snapshot,
60
- Stats,
61
- StatsAverageNetwork,
62
- StatsAverageNumeric,
63
- StatsQuery,
64
- StatsTimelinePoint,
65
- TeamScopedPagination,
66
- WaitUntilReadyOptions,
67
- Volume,
68
- } from './types';
69
-
70
- export type { RequestOptions } from './transport/http';
71
- export type { RetryOptions } from './transport/http';
@@ -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
- }