@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.
- package/README.md +9 -5
- package/dist/package.json +10 -3
- package/dist/src/client.d.ts +3 -3
- package/dist/src/client.js +3 -3
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +2 -2
- package/dist/src/resources/files.d.ts +6 -1
- package/dist/src/resources/files.js +36 -0
- package/dist/src/resources/sandbox-handle.d.ts +6 -1
- package/dist/src/resources/sandbox-handle.js +8 -0
- package/dist/src/resources/scoped-sandbox.d.ts +3 -1
- package/dist/src/resources/scoped-sandbox.js +4 -0
- package/dist/src/types/files.d.ts +16 -0
- package/dist/src/types/index.d.ts +1 -1
- package/package.json +7 -3
- package/CODEX.md +0 -188
- package/PLAN.md +0 -364
- package/src/client.ts +0 -61
- package/src/constants.ts +0 -17
- package/src/enums/code-language.ts +0 -4
- package/src/enums/destroy-reason.ts +0 -8
- package/src/enums/destroy-timeout.ts +0 -8
- package/src/enums/index.ts +0 -7
- package/src/enums/sandbox-status.ts +0 -9
- package/src/enums/snapshot-mode.ts +0 -4
- package/src/enums/snapshot-status.ts +0 -5
- package/src/enums/volume-type.ts +0 -3
- package/src/errors/index.ts +0 -2
- package/src/errors/sandbox-api-error.ts +0 -54
- package/src/index.ts +0 -71
- package/src/resources/exec.ts +0 -56
- package/src/resources/files.ts +0 -46
- package/src/resources/index.ts +0 -8
- package/src/resources/path.ts +0 -16
- package/src/resources/sandbox-handle.ts +0 -215
- package/src/resources/sandboxes.ts +0 -297
- package/src/resources/scoped-sandbox.ts +0 -65
- package/src/resources/snapshots.ts +0 -104
- package/src/resources/stats.ts +0 -30
- package/src/resources/volumes.ts +0 -95
- package/src/transport/auth.ts +0 -4
- package/src/transport/http.ts +0 -501
- package/src/transport/pagination.ts +0 -10
- package/src/types/exec.ts +0 -42
- package/src/types/files.ts +0 -1
- package/src/types/index.ts +0 -23
- package/src/types/pagination.ts +0 -16
- package/src/types/region.ts +0 -19
- package/src/types/sandbox.ts +0 -103
- package/src/types/snapshot.ts +0 -17
- package/src/types/stats.ts +0 -35
- package/src/types/template.ts +0 -5
- package/src/types/volume.ts +0 -26
- package/test/integration/sandbox.integration.test.ts +0 -269
- package/test/unit/client.test.ts +0 -87
- package/test/unit/sandboxes.test.ts +0 -69
- package/test/unit/transport.test.ts +0 -126
- package/test/unit/volumes.test.ts +0 -122
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -12
- package/vitest.integration.config.ts +0 -15
package/src/transport/http.ts
DELETED
|
@@ -1,501 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_RETRY_BASE_DELAY_MS,
|
|
3
|
-
DEFAULT_RETRY_MAX_ATTEMPTS,
|
|
4
|
-
DEFAULT_RETRY_MAX_DELAY_MS,
|
|
5
|
-
DEFAULT_RETRY_STATUSES,
|
|
6
|
-
SDK_PACKAGE_VERSION,
|
|
7
|
-
DEFAULT_TIMEOUT_MS,
|
|
8
|
-
} from '../constants';
|
|
9
|
-
import { AuthError, NotFoundError, RateLimitError, SandboxApiError, ValidationError } from '../errors';
|
|
10
|
-
import { buildApiKeyHeader } from './auth';
|
|
11
|
-
|
|
12
|
-
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
13
|
-
|
|
14
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
15
|
-
|
|
16
|
-
export type RetryOptions = {
|
|
17
|
-
maxAttempts?: number;
|
|
18
|
-
baseDelayMs?: number;
|
|
19
|
-
maxDelayMs?: number;
|
|
20
|
-
retryStatuses?: number[];
|
|
21
|
-
retryMethods?: HttpMethod[];
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type RequestOptions = {
|
|
25
|
-
signal?: AbortSignal;
|
|
26
|
-
timeoutMs?: number;
|
|
27
|
-
idempotencyKey?: string;
|
|
28
|
-
retry?: RetryOptions | false;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type JsonRequestArgs = RequestOptions & {
|
|
32
|
-
endpoint: string;
|
|
33
|
-
method: HttpMethod;
|
|
34
|
-
query?: URLSearchParams;
|
|
35
|
-
body?: JsonValue | undefined;
|
|
36
|
-
headers?: Record<string, string>;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type BinaryRequestArgs = RequestOptions & {
|
|
40
|
-
endpoint: string;
|
|
41
|
-
method: HttpMethod;
|
|
42
|
-
query?: URLSearchParams;
|
|
43
|
-
body?: ReadableStream<Uint8Array> | Buffer | Uint8Array;
|
|
44
|
-
headers?: Record<string, string>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type HttpTransportConfig = {
|
|
48
|
-
baseUrl: string;
|
|
49
|
-
apiKey: string;
|
|
50
|
-
timeoutMs?: number;
|
|
51
|
-
fetchImpl?: typeof fetch;
|
|
52
|
-
retry?: RetryOptions;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
type ResponseEnvelope<T> = {
|
|
56
|
-
message: string;
|
|
57
|
-
data?: T;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
type NormalizedRetryConfig = {
|
|
61
|
-
maxAttempts: number;
|
|
62
|
-
baseDelayMs: number;
|
|
63
|
-
maxDelayMs: number;
|
|
64
|
-
retryStatuses: number[];
|
|
65
|
-
retryMethods: HttpMethod[];
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
69
|
-
return typeof value === 'object' && value !== null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function hasMessage(value: unknown): value is { message: string } {
|
|
73
|
-
return isRecord(value) && typeof value.message === 'string';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isEnvelope<T>(value: unknown): value is ResponseEnvelope<T> {
|
|
77
|
-
return hasMessage(value);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function maybeJoinSignal(signal: AbortSignal | undefined, timeoutMs: number): { signal: AbortSignal; cleanup: () => void } {
|
|
81
|
-
if (!signal) {
|
|
82
|
-
return { signal: AbortSignal.timeout(timeoutMs), cleanup: () => {} };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const timeoutController = new AbortController();
|
|
86
|
-
const timeoutId = setTimeout(() => {
|
|
87
|
-
timeoutController.abort(new DOMException('The operation timed out.', 'AbortError'));
|
|
88
|
-
}, timeoutMs);
|
|
89
|
-
|
|
90
|
-
const mergedController = new AbortController();
|
|
91
|
-
|
|
92
|
-
const onCallerAbort = () => {
|
|
93
|
-
mergedController.abort(signal.reason);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const onTimeoutAbort = () => {
|
|
97
|
-
mergedController.abort(timeoutController.signal.reason);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (signal.aborted) {
|
|
101
|
-
mergedController.abort(signal.reason);
|
|
102
|
-
} else {
|
|
103
|
-
signal.addEventListener('abort', onCallerAbort, { once: true });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
timeoutController.signal.addEventListener('abort', onTimeoutAbort, { once: true });
|
|
107
|
-
|
|
108
|
-
const cleanup = () => {
|
|
109
|
-
clearTimeout(timeoutId);
|
|
110
|
-
signal.removeEventListener('abort', onCallerAbort);
|
|
111
|
-
timeoutController.signal.removeEventListener('abort', onTimeoutAbort);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
return { signal: mergedController.signal, cleanup };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function parseResponseBody(response: Response): Promise<unknown> {
|
|
118
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
119
|
-
|
|
120
|
-
if (contentType.includes('application/json')) {
|
|
121
|
-
return response.json() as Promise<unknown>;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const text = await response.text();
|
|
125
|
-
return text.length > 0 ? text : null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function buildUrl(baseUrl: string, endpoint: string, query?: URLSearchParams): string {
|
|
129
|
-
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
130
|
-
const normalizedPath = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
131
|
-
const url = new URL(`${normalizedBaseUrl}${normalizedPath}`);
|
|
132
|
-
|
|
133
|
-
if (query) {
|
|
134
|
-
url.search = query.toString();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return url.toString();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function normalizeRetryConfig(retry?: RetryOptions): NormalizedRetryConfig {
|
|
141
|
-
return {
|
|
142
|
-
maxAttempts: Math.max(1, retry?.maxAttempts ?? DEFAULT_RETRY_MAX_ATTEMPTS),
|
|
143
|
-
baseDelayMs: Math.max(0, retry?.baseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS),
|
|
144
|
-
maxDelayMs: Math.max(0, retry?.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS),
|
|
145
|
-
retryStatuses: retry?.retryStatuses ?? [...DEFAULT_RETRY_STATUSES],
|
|
146
|
-
retryMethods: retry?.retryMethods ?? ['GET', 'DELETE', 'PUT'],
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function mergeRetryConfig(base: NormalizedRetryConfig, override?: RetryOptions | false): NormalizedRetryConfig {
|
|
151
|
-
if (override === false) {
|
|
152
|
-
return {
|
|
153
|
-
...base,
|
|
154
|
-
maxAttempts: 1,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!override) {
|
|
159
|
-
return base;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return normalizeRetryConfig({
|
|
163
|
-
maxAttempts: override.maxAttempts ?? base.maxAttempts,
|
|
164
|
-
baseDelayMs: override.baseDelayMs ?? base.baseDelayMs,
|
|
165
|
-
maxDelayMs: override.maxDelayMs ?? base.maxDelayMs,
|
|
166
|
-
retryStatuses: override.retryStatuses ?? base.retryStatuses,
|
|
167
|
-
retryMethods: override.retryMethods ?? base.retryMethods,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function isAbortError(error: unknown): boolean {
|
|
172
|
-
return error instanceof DOMException && error.name === 'AbortError';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function canRetryByMethod(method: HttpMethod, retryMethods: HttpMethod[], idempotencyKey?: string): boolean {
|
|
176
|
-
if (retryMethods.includes(method)) {
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (method === 'POST' && idempotencyKey) {
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function canRetryBody(body: string | ReadableStream<Uint8Array> | Buffer | Uint8Array | undefined): boolean {
|
|
188
|
-
if (!body) {
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (typeof body === 'string') {
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function computeRetryDelayMs(attempt: number, baseDelayMs: number, maxDelayMs: number): number {
|
|
204
|
-
const exponent = Math.max(0, attempt - 1);
|
|
205
|
-
const delayMs = baseDelayMs * (2 ** exponent);
|
|
206
|
-
return Math.min(maxDelayMs, delayMs);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function sleep(ms: number): Promise<void> {
|
|
210
|
-
if (ms <= 0) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
await new Promise((resolve) => {
|
|
215
|
-
setTimeout(resolve, ms);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function parseRetryAfterSeconds(value: string | null): number | null {
|
|
220
|
-
if (!value) {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const asNumber = Number(value);
|
|
225
|
-
|
|
226
|
-
if (Number.isFinite(asNumber)) {
|
|
227
|
-
return asNumber;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function getRequestId(response: Response): string | null {
|
|
234
|
-
return response.headers.get('x-request-id') ?? response.headers.get('x-correlation-id') ?? null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export class HttpTransport {
|
|
238
|
-
private readonly baseUrl: string;
|
|
239
|
-
private readonly apiKey: string;
|
|
240
|
-
private readonly timeoutMs: number;
|
|
241
|
-
private readonly fetchImpl: typeof fetch;
|
|
242
|
-
private readonly retry: NormalizedRetryConfig;
|
|
243
|
-
|
|
244
|
-
/** Create a transport used by all SDK resources. */
|
|
245
|
-
public constructor(config: HttpTransportConfig) {
|
|
246
|
-
this.baseUrl = config.baseUrl;
|
|
247
|
-
this.apiKey = config.apiKey;
|
|
248
|
-
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
249
|
-
this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
250
|
-
this.retry = normalizeRetryConfig(config.retry);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Send a JSON request and return the unwrapped `data` payload.
|
|
255
|
-
* If the endpoint returns 204, this resolves to `undefined`.
|
|
256
|
-
*/
|
|
257
|
-
public async requestJson<T>(args: JsonRequestArgs): Promise<T | undefined> {
|
|
258
|
-
const headers = args.body !== undefined ? { 'content-type': 'application/json', ...(args.headers ?? {}) } : args.headers;
|
|
259
|
-
|
|
260
|
-
const response = await this.executeRequest({
|
|
261
|
-
endpoint: args.endpoint,
|
|
262
|
-
method: args.method,
|
|
263
|
-
query: args.query,
|
|
264
|
-
body: args.body !== undefined ? JSON.stringify(args.body) : undefined,
|
|
265
|
-
headers,
|
|
266
|
-
signal: args.signal,
|
|
267
|
-
timeoutMs: args.timeoutMs,
|
|
268
|
-
idempotencyKey: args.idempotencyKey,
|
|
269
|
-
retry: args.retry,
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
if (response.status === 204) {
|
|
273
|
-
return undefined;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const payload = await parseResponseBody(response);
|
|
277
|
-
|
|
278
|
-
if (!response.ok) {
|
|
279
|
-
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (isEnvelope<T>(payload)) {
|
|
283
|
-
if ('data' in payload) {
|
|
284
|
-
return payload.data;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return payload as T;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return payload as T;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Send a binary upload request (for file writes).
|
|
295
|
-
* Returns when the API acknowledges success.
|
|
296
|
-
*/
|
|
297
|
-
public async requestBinary(args: BinaryRequestArgs): Promise<void> {
|
|
298
|
-
const response = await this.executeRequest({
|
|
299
|
-
endpoint: args.endpoint,
|
|
300
|
-
method: args.method,
|
|
301
|
-
query: args.query,
|
|
302
|
-
body: args.body,
|
|
303
|
-
headers: args.headers,
|
|
304
|
-
signal: args.signal,
|
|
305
|
-
timeoutMs: args.timeoutMs,
|
|
306
|
-
idempotencyKey: args.idempotencyKey,
|
|
307
|
-
retry: args.retry,
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
if (response.ok || response.status === 204) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const payload = await parseResponseBody(response);
|
|
315
|
-
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Send a JSON request and return the raw response stream.
|
|
320
|
-
* Used for endpoints that stream SSE frames (`text/event-stream`).
|
|
321
|
-
*/
|
|
322
|
-
public async requestJsonStream(args: JsonRequestArgs): Promise<ReadableStream<Uint8Array>> {
|
|
323
|
-
const headers = args.body !== undefined ? { 'content-type': 'application/json', ...(args.headers ?? {}) } : args.headers;
|
|
324
|
-
|
|
325
|
-
const response = await this.executeRequest({
|
|
326
|
-
endpoint: args.endpoint,
|
|
327
|
-
method: args.method,
|
|
328
|
-
query: args.query,
|
|
329
|
-
body: args.body !== undefined ? JSON.stringify(args.body) : undefined,
|
|
330
|
-
headers,
|
|
331
|
-
signal: args.signal,
|
|
332
|
-
timeoutMs: args.timeoutMs,
|
|
333
|
-
idempotencyKey: args.idempotencyKey,
|
|
334
|
-
retry: args.retry,
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
if (!response.ok) {
|
|
338
|
-
const payload = await parseResponseBody(response);
|
|
339
|
-
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (!response.body) {
|
|
343
|
-
throw new SandboxApiError({
|
|
344
|
-
status: response.status,
|
|
345
|
-
message: 'Expected a response stream but received an empty body',
|
|
346
|
-
endpoint: `${args.method} ${args.endpoint}`,
|
|
347
|
-
responseBody: null,
|
|
348
|
-
requestId: getRequestId(response),
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return response.body;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Request a binary response stream (for file downloads).
|
|
357
|
-
* Throws if the API returns an error status.
|
|
358
|
-
*/
|
|
359
|
-
public async requestStream(args: RequestOptions & { endpoint: string; method: HttpMethod; query?: URLSearchParams }): Promise<ReadableStream<Uint8Array>> {
|
|
360
|
-
const response = await this.executeRequest({
|
|
361
|
-
endpoint: args.endpoint,
|
|
362
|
-
method: args.method,
|
|
363
|
-
query: args.query,
|
|
364
|
-
signal: args.signal,
|
|
365
|
-
timeoutMs: args.timeoutMs,
|
|
366
|
-
idempotencyKey: args.idempotencyKey,
|
|
367
|
-
retry: args.retry,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
if (!response.ok) {
|
|
371
|
-
const payload = await parseResponseBody(response);
|
|
372
|
-
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (!response.body) {
|
|
376
|
-
throw new SandboxApiError({
|
|
377
|
-
status: response.status,
|
|
378
|
-
message: 'Expected a response stream but received an empty body',
|
|
379
|
-
endpoint: `${args.method} ${args.endpoint}`,
|
|
380
|
-
responseBody: null,
|
|
381
|
-
requestId: getRequestId(response),
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return response.body;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
private async executeRequest(args: {
|
|
389
|
-
endpoint: string;
|
|
390
|
-
method: HttpMethod;
|
|
391
|
-
query?: URLSearchParams;
|
|
392
|
-
body?: string | ReadableStream<Uint8Array> | Buffer | Uint8Array;
|
|
393
|
-
headers?: Record<string, string>;
|
|
394
|
-
signal?: AbortSignal;
|
|
395
|
-
timeoutMs?: number;
|
|
396
|
-
idempotencyKey?: string;
|
|
397
|
-
retry?: RetryOptions | false;
|
|
398
|
-
}): Promise<Response> {
|
|
399
|
-
const timeoutMs = args.timeoutMs ?? this.timeoutMs;
|
|
400
|
-
const retryConfig = mergeRetryConfig(this.retry, args.retry);
|
|
401
|
-
const canRetryMethod = canRetryByMethod(args.method, retryConfig.retryMethods, args.idempotencyKey);
|
|
402
|
-
const replayableBody = canRetryBody(args.body);
|
|
403
|
-
|
|
404
|
-
let lastError: unknown;
|
|
405
|
-
|
|
406
|
-
for (let attempt = 1; attempt <= retryConfig.maxAttempts; attempt += 1) {
|
|
407
|
-
const { signal, cleanup } = maybeJoinSignal(args.signal, timeoutMs);
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
const response = await this.fetchImpl(buildUrl(this.baseUrl, args.endpoint, args.query), {
|
|
411
|
-
method: args.method,
|
|
412
|
-
headers: {
|
|
413
|
-
'x-brimble-key': buildApiKeyHeader(this.apiKey),
|
|
414
|
-
source: 'sdk-package',
|
|
415
|
-
'source-version': SDK_PACKAGE_VERSION,
|
|
416
|
-
...(args.idempotencyKey ? { 'idempotency-key': args.idempotencyKey } : {}),
|
|
417
|
-
...(args.headers ?? {}),
|
|
418
|
-
},
|
|
419
|
-
body: args.body as BodyInit | undefined,
|
|
420
|
-
signal,
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
const shouldRetry =
|
|
424
|
-
attempt < retryConfig.maxAttempts &&
|
|
425
|
-
canRetryMethod &&
|
|
426
|
-
replayableBody &&
|
|
427
|
-
retryConfig.retryStatuses.includes(response.status) &&
|
|
428
|
-
!signal.aborted;
|
|
429
|
-
|
|
430
|
-
if (shouldRetry) {
|
|
431
|
-
await sleep(computeRetryDelayMs(attempt, retryConfig.baseDelayMs, retryConfig.maxDelayMs));
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
return response;
|
|
436
|
-
} catch (error) {
|
|
437
|
-
lastError = error;
|
|
438
|
-
|
|
439
|
-
const shouldRetry =
|
|
440
|
-
attempt < retryConfig.maxAttempts &&
|
|
441
|
-
canRetryMethod &&
|
|
442
|
-
replayableBody &&
|
|
443
|
-
!isAbortError(error) &&
|
|
444
|
-
!(args.signal?.aborted ?? false);
|
|
445
|
-
|
|
446
|
-
if (!shouldRetry) {
|
|
447
|
-
throw error;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
await sleep(computeRetryDelayMs(attempt, retryConfig.baseDelayMs, retryConfig.maxDelayMs));
|
|
451
|
-
} finally {
|
|
452
|
-
cleanup();
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (lastError) {
|
|
457
|
-
throw lastError;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
throw new Error('HTTP request failed before receiving a response.');
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
private toApiError(response: Response, method: HttpMethod, endpoint: string, responseBody: unknown): SandboxApiError {
|
|
464
|
-
let message = response.statusText || 'Sandbox API request failed';
|
|
465
|
-
|
|
466
|
-
if (hasMessage(responseBody)) {
|
|
467
|
-
message = responseBody.message;
|
|
468
|
-
} else if (typeof responseBody === 'string' && responseBody.trim().length > 0) {
|
|
469
|
-
message = responseBody;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const baseArgs = {
|
|
473
|
-
status: response.status,
|
|
474
|
-
message,
|
|
475
|
-
endpoint: `${method} ${endpoint}`,
|
|
476
|
-
responseBody,
|
|
477
|
-
requestId: getRequestId(response),
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
if (response.status === 401 || response.status === 403) {
|
|
481
|
-
return new AuthError(baseArgs);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (response.status === 400 || response.status === 422) {
|
|
485
|
-
return new ValidationError(baseArgs);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (response.status === 404) {
|
|
489
|
-
return new NotFoundError(baseArgs);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
if (response.status === 429) {
|
|
493
|
-
return new RateLimitError({
|
|
494
|
-
...baseArgs,
|
|
495
|
-
retryAfterSeconds: parseRetryAfterSeconds(response.headers.get('retry-after')),
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
return new SandboxApiError(baseArgs);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_PAGE, DEFAULT_PAGE_LIMIT } from '../constants';
|
|
2
|
-
import type { Pagination } from '../types';
|
|
3
|
-
|
|
4
|
-
/** Convert pagination inputs into query params with sensible defaults. */
|
|
5
|
-
export function toPaginationQuery(pagination: Pagination = {}): URLSearchParams {
|
|
6
|
-
const params = new URLSearchParams();
|
|
7
|
-
params.set('page', String(pagination.page ?? DEFAULT_PAGE));
|
|
8
|
-
params.set('limit', String(pagination.limit ?? DEFAULT_PAGE_LIMIT));
|
|
9
|
-
return params;
|
|
10
|
-
}
|
package/src/types/exec.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { CodeLanguage } from '../enums';
|
|
2
|
-
|
|
3
|
-
export type ExecInput = {
|
|
4
|
-
cmd: string;
|
|
5
|
-
timeout_seconds?: number;
|
|
6
|
-
cwd?: string;
|
|
7
|
-
stream?: boolean;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type CodeInput = {
|
|
11
|
-
language: CodeLanguage;
|
|
12
|
-
code: string;
|
|
13
|
-
timeout_seconds?: number;
|
|
14
|
-
cwd?: string;
|
|
15
|
-
stream?: boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type ExecResult = {
|
|
19
|
-
stdout: string;
|
|
20
|
-
stderr: string;
|
|
21
|
-
exit_code: number;
|
|
22
|
-
duration_ms: number;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type ExecStreamFrame =
|
|
26
|
-
| {
|
|
27
|
-
type: 'stdout';
|
|
28
|
-
data: string;
|
|
29
|
-
}
|
|
30
|
-
| {
|
|
31
|
-
type: 'stderr';
|
|
32
|
-
data: string;
|
|
33
|
-
}
|
|
34
|
-
| {
|
|
35
|
-
type: 'done';
|
|
36
|
-
exit_code: number;
|
|
37
|
-
duration_ms: number;
|
|
38
|
-
}
|
|
39
|
-
| {
|
|
40
|
-
type: 'error';
|
|
41
|
-
message: string;
|
|
42
|
-
};
|
package/src/types/files.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type FileUploadBody = ReadableStream<Uint8Array> | Buffer | Uint8Array;
|
package/src/types/index.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export type { CodeInput, ExecInput, ExecResult, ExecStreamFrame } from './exec';
|
|
2
|
-
export type { FileUploadBody } from './files';
|
|
3
|
-
export type { Paginated, Pagination, TeamScopedPagination } from './pagination';
|
|
4
|
-
export type { RegionSummary, SandboxRegion, SandboxRegionsResult } from './region';
|
|
5
|
-
export type {
|
|
6
|
-
AckMessage,
|
|
7
|
-
CreateSandboxInput,
|
|
8
|
-
CreateSandboxRequest,
|
|
9
|
-
CreateSandboxResult,
|
|
10
|
-
CreateSandboxWithVolumeInput,
|
|
11
|
-
CreateSandboxWithVolumeResult,
|
|
12
|
-
Sandbox,
|
|
13
|
-
SandboxReadyRequestOptions,
|
|
14
|
-
SandboxRegionInput,
|
|
15
|
-
SandboxRuntimeOptions,
|
|
16
|
-
SandboxSpecs,
|
|
17
|
-
WaitPreference,
|
|
18
|
-
WaitUntilReadyOptions,
|
|
19
|
-
} from './sandbox';
|
|
20
|
-
export type { CreateSnapshotInput, Snapshot } from './snapshot';
|
|
21
|
-
export type { Stats, StatsAverageNetwork, StatsAverageNumeric, StatsQuery, StatsTimelinePoint } from './stats';
|
|
22
|
-
export type { SandboxTemplate } from './template';
|
|
23
|
-
export type { CreateVolumeInput, Volume } from './volume';
|
package/src/types/pagination.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type Pagination = {
|
|
2
|
-
page?: number;
|
|
3
|
-
limit?: number;
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
export type TeamScopedPagination = Pagination & {
|
|
7
|
-
teamId?: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type Paginated<T> = {
|
|
11
|
-
data: T[];
|
|
12
|
-
totalCount: number;
|
|
13
|
-
currentPage: number;
|
|
14
|
-
totalPages: number;
|
|
15
|
-
limit: number;
|
|
16
|
-
};
|
package/src/types/region.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export type RegionSummary = {
|
|
2
|
-
id: string;
|
|
3
|
-
name: string;
|
|
4
|
-
country: string;
|
|
5
|
-
continent: string | null;
|
|
6
|
-
provider: string;
|
|
7
|
-
is_paid: boolean;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type SandboxRegion = {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
country: string;
|
|
14
|
-
continent: string | null;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type SandboxRegionsResult = {
|
|
18
|
-
regions: SandboxRegion[];
|
|
19
|
-
};
|