@brimble/sandbox 0.1.0
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/CODEX.md +188 -0
- package/PLAN.md +364 -0
- package/README.md +147 -0
- package/dist/package.json +23 -0
- package/dist/src/client.d.ts +23 -0
- package/dist/src/client.js +46 -0
- package/dist/src/constants.d.ts +14 -0
- package/dist/src/constants.js +21 -0
- package/dist/src/enums/code-language.d.ts +4 -0
- package/dist/src/enums/code-language.js +8 -0
- package/dist/src/enums/destroy-reason.d.ts +8 -0
- package/dist/src/enums/destroy-reason.js +12 -0
- package/dist/src/enums/destroy-timeout.d.ts +8 -0
- package/dist/src/enums/destroy-timeout.js +12 -0
- package/dist/src/enums/index.d.ts +7 -0
- package/dist/src/enums/index.js +17 -0
- package/dist/src/enums/sandbox-status.d.ts +9 -0
- package/dist/src/enums/sandbox-status.js +13 -0
- package/dist/src/enums/snapshot-mode.d.ts +4 -0
- package/dist/src/enums/snapshot-mode.js +8 -0
- package/dist/src/enums/snapshot-status.d.ts +5 -0
- package/dist/src/enums/snapshot-status.js +9 -0
- package/dist/src/enums/volume-type.d.ts +3 -0
- package/dist/src/enums/volume-type.js +7 -0
- package/dist/src/errors/index.d.ts +2 -0
- package/dist/src/errors/index.js +9 -0
- package/dist/src/errors/sandbox-api-error.d.ts +29 -0
- package/dist/src/errors/sandbox-api-error.js +48 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +40 -0
- package/dist/src/resources/exec.d.ts +19 -0
- package/dist/src/resources/exec.js +45 -0
- package/dist/src/resources/files.d.ts +16 -0
- package/dist/src/resources/files.js +41 -0
- package/dist/src/resources/index.d.ts +8 -0
- package/dist/src/resources/index.js +20 -0
- package/dist/src/resources/path.d.ts +7 -0
- package/dist/src/resources/path.js +19 -0
- package/dist/src/resources/sandbox-handle.d.ts +78 -0
- package/dist/src/resources/sandbox-handle.js +151 -0
- package/dist/src/resources/sandboxes.d.ts +64 -0
- package/dist/src/resources/sandboxes.js +224 -0
- package/dist/src/resources/scoped-sandbox.d.ts +39 -0
- package/dist/src/resources/scoped-sandbox.js +51 -0
- package/dist/src/resources/snapshots.d.ts +26 -0
- package/dist/src/resources/snapshots.js +88 -0
- package/dist/src/resources/stats.d.ts +11 -0
- package/dist/src/resources/stats.js +26 -0
- package/dist/src/resources/volumes.d.ts +21 -0
- package/dist/src/resources/volumes.js +80 -0
- package/dist/src/transport/auth.d.ts +2 -0
- package/dist/src/transport/auth.js +7 -0
- package/dist/src/transport/http.d.ts +73 -0
- package/dist/src/transport/http.js +354 -0
- package/dist/src/transport/pagination.d.ts +3 -0
- package/dist/src/transport/pagination.js +11 -0
- package/dist/src/types/exec.d.ts +34 -0
- package/dist/src/types/exec.js +2 -0
- package/dist/src/types/files.d.ts +1 -0
- package/dist/src/types/files.js +2 -0
- package/dist/src/types/index.d.ts +9 -0
- package/dist/src/types/index.js +2 -0
- package/dist/src/types/pagination.d.ts +14 -0
- package/dist/src/types/pagination.js +2 -0
- package/dist/src/types/region.d.ts +17 -0
- package/dist/src/types/region.js +2 -0
- package/dist/src/types/sandbox.d.ts +90 -0
- package/dist/src/types/sandbox.js +2 -0
- package/dist/src/types/snapshot.d.ts +15 -0
- package/dist/src/types/snapshot.js +2 -0
- package/dist/src/types/stats.d.ts +31 -0
- package/dist/src/types/stats.js +2 -0
- package/dist/src/types/template.d.ts +5 -0
- package/dist/src/types/template.js +2 -0
- package/dist/src/types/volume.d.ts +24 -0
- package/dist/src/types/volume.js +2 -0
- package/package.json +26 -0
- package/src/client.ts +61 -0
- package/src/constants.ts +17 -0
- package/src/enums/code-language.ts +4 -0
- package/src/enums/destroy-reason.ts +8 -0
- package/src/enums/destroy-timeout.ts +8 -0
- package/src/enums/index.ts +7 -0
- package/src/enums/sandbox-status.ts +9 -0
- package/src/enums/snapshot-mode.ts +4 -0
- package/src/enums/snapshot-status.ts +5 -0
- package/src/enums/volume-type.ts +3 -0
- package/src/errors/index.ts +2 -0
- package/src/errors/sandbox-api-error.ts +54 -0
- package/src/index.ts +71 -0
- package/src/resources/exec.ts +56 -0
- package/src/resources/files.ts +46 -0
- package/src/resources/index.ts +8 -0
- package/src/resources/path.ts +16 -0
- package/src/resources/sandbox-handle.ts +215 -0
- package/src/resources/sandboxes.ts +297 -0
- package/src/resources/scoped-sandbox.ts +65 -0
- package/src/resources/snapshots.ts +104 -0
- package/src/resources/stats.ts +30 -0
- package/src/resources/volumes.ts +95 -0
- package/src/transport/auth.ts +4 -0
- package/src/transport/http.ts +501 -0
- package/src/transport/pagination.ts +10 -0
- package/src/types/exec.ts +42 -0
- package/src/types/files.ts +1 -0
- package/src/types/index.ts +23 -0
- package/src/types/pagination.ts +16 -0
- package/src/types/region.ts +19 -0
- package/src/types/sandbox.ts +103 -0
- package/src/types/snapshot.ts +17 -0
- package/src/types/stats.ts +35 -0
- package/src/types/template.ts +5 -0
- package/src/types/volume.ts +26 -0
- package/test/integration/sandbox.integration.test.ts +269 -0
- package/test/unit/client.test.ts +87 -0
- package/test/unit/sandboxes.test.ts +69 -0
- package/test/unit/transport.test.ts +126 -0
- package/test/unit/volumes.test.ts +122 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +12 -0
- package/vitest.integration.config.ts +15 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpTransport = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
const auth_1 = require("./auth");
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null;
|
|
9
|
+
}
|
|
10
|
+
function hasMessage(value) {
|
|
11
|
+
return isRecord(value) && typeof value.message === 'string';
|
|
12
|
+
}
|
|
13
|
+
function isEnvelope(value) {
|
|
14
|
+
return hasMessage(value);
|
|
15
|
+
}
|
|
16
|
+
function maybeJoinSignal(signal, timeoutMs) {
|
|
17
|
+
if (!signal) {
|
|
18
|
+
return { signal: AbortSignal.timeout(timeoutMs), cleanup: () => { } };
|
|
19
|
+
}
|
|
20
|
+
const timeoutController = new AbortController();
|
|
21
|
+
const timeoutId = setTimeout(() => {
|
|
22
|
+
timeoutController.abort(new DOMException('The operation timed out.', 'AbortError'));
|
|
23
|
+
}, timeoutMs);
|
|
24
|
+
const mergedController = new AbortController();
|
|
25
|
+
const onCallerAbort = () => {
|
|
26
|
+
mergedController.abort(signal.reason);
|
|
27
|
+
};
|
|
28
|
+
const onTimeoutAbort = () => {
|
|
29
|
+
mergedController.abort(timeoutController.signal.reason);
|
|
30
|
+
};
|
|
31
|
+
if (signal.aborted) {
|
|
32
|
+
mergedController.abort(signal.reason);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
signal.addEventListener('abort', onCallerAbort, { once: true });
|
|
36
|
+
}
|
|
37
|
+
timeoutController.signal.addEventListener('abort', onTimeoutAbort, { once: true });
|
|
38
|
+
const cleanup = () => {
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
signal.removeEventListener('abort', onCallerAbort);
|
|
41
|
+
timeoutController.signal.removeEventListener('abort', onTimeoutAbort);
|
|
42
|
+
};
|
|
43
|
+
return { signal: mergedController.signal, cleanup };
|
|
44
|
+
}
|
|
45
|
+
async function parseResponseBody(response) {
|
|
46
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
47
|
+
if (contentType.includes('application/json')) {
|
|
48
|
+
return response.json();
|
|
49
|
+
}
|
|
50
|
+
const text = await response.text();
|
|
51
|
+
return text.length > 0 ? text : null;
|
|
52
|
+
}
|
|
53
|
+
function buildUrl(baseUrl, endpoint, query) {
|
|
54
|
+
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
55
|
+
const normalizedPath = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
56
|
+
const url = new URL(`${normalizedBaseUrl}${normalizedPath}`);
|
|
57
|
+
if (query) {
|
|
58
|
+
url.search = query.toString();
|
|
59
|
+
}
|
|
60
|
+
return url.toString();
|
|
61
|
+
}
|
|
62
|
+
function normalizeRetryConfig(retry) {
|
|
63
|
+
return {
|
|
64
|
+
maxAttempts: Math.max(1, retry?.maxAttempts ?? constants_1.DEFAULT_RETRY_MAX_ATTEMPTS),
|
|
65
|
+
baseDelayMs: Math.max(0, retry?.baseDelayMs ?? constants_1.DEFAULT_RETRY_BASE_DELAY_MS),
|
|
66
|
+
maxDelayMs: Math.max(0, retry?.maxDelayMs ?? constants_1.DEFAULT_RETRY_MAX_DELAY_MS),
|
|
67
|
+
retryStatuses: retry?.retryStatuses ?? [...constants_1.DEFAULT_RETRY_STATUSES],
|
|
68
|
+
retryMethods: retry?.retryMethods ?? ['GET', 'DELETE', 'PUT'],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function mergeRetryConfig(base, override) {
|
|
72
|
+
if (override === false) {
|
|
73
|
+
return {
|
|
74
|
+
...base,
|
|
75
|
+
maxAttempts: 1,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (!override) {
|
|
79
|
+
return base;
|
|
80
|
+
}
|
|
81
|
+
return normalizeRetryConfig({
|
|
82
|
+
maxAttempts: override.maxAttempts ?? base.maxAttempts,
|
|
83
|
+
baseDelayMs: override.baseDelayMs ?? base.baseDelayMs,
|
|
84
|
+
maxDelayMs: override.maxDelayMs ?? base.maxDelayMs,
|
|
85
|
+
retryStatuses: override.retryStatuses ?? base.retryStatuses,
|
|
86
|
+
retryMethods: override.retryMethods ?? base.retryMethods,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function isAbortError(error) {
|
|
90
|
+
return error instanceof DOMException && error.name === 'AbortError';
|
|
91
|
+
}
|
|
92
|
+
function canRetryByMethod(method, retryMethods, idempotencyKey) {
|
|
93
|
+
if (retryMethods.includes(method)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (method === 'POST' && idempotencyKey) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
function canRetryBody(body) {
|
|
102
|
+
if (!body) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (typeof body === 'string') {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function computeRetryDelayMs(attempt, baseDelayMs, maxDelayMs) {
|
|
114
|
+
const exponent = Math.max(0, attempt - 1);
|
|
115
|
+
const delayMs = baseDelayMs * (2 ** exponent);
|
|
116
|
+
return Math.min(maxDelayMs, delayMs);
|
|
117
|
+
}
|
|
118
|
+
async function sleep(ms) {
|
|
119
|
+
if (ms <= 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await new Promise((resolve) => {
|
|
123
|
+
setTimeout(resolve, ms);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function parseRetryAfterSeconds(value) {
|
|
127
|
+
if (!value) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const asNumber = Number(value);
|
|
131
|
+
if (Number.isFinite(asNumber)) {
|
|
132
|
+
return asNumber;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function getRequestId(response) {
|
|
137
|
+
return response.headers.get('x-request-id') ?? response.headers.get('x-correlation-id') ?? null;
|
|
138
|
+
}
|
|
139
|
+
class HttpTransport {
|
|
140
|
+
baseUrl;
|
|
141
|
+
apiKey;
|
|
142
|
+
timeoutMs;
|
|
143
|
+
fetchImpl;
|
|
144
|
+
retry;
|
|
145
|
+
/** Create a transport used by all SDK resources. */
|
|
146
|
+
constructor(config) {
|
|
147
|
+
this.baseUrl = config.baseUrl;
|
|
148
|
+
this.apiKey = config.apiKey;
|
|
149
|
+
this.timeoutMs = config.timeoutMs ?? constants_1.DEFAULT_TIMEOUT_MS;
|
|
150
|
+
this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
151
|
+
this.retry = normalizeRetryConfig(config.retry);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Send a JSON request and return the unwrapped `data` payload.
|
|
155
|
+
* If the endpoint returns 204, this resolves to `undefined`.
|
|
156
|
+
*/
|
|
157
|
+
async requestJson(args) {
|
|
158
|
+
const headers = args.body !== undefined ? { 'content-type': 'application/json', ...(args.headers ?? {}) } : args.headers;
|
|
159
|
+
const response = await this.executeRequest({
|
|
160
|
+
endpoint: args.endpoint,
|
|
161
|
+
method: args.method,
|
|
162
|
+
query: args.query,
|
|
163
|
+
body: args.body !== undefined ? JSON.stringify(args.body) : undefined,
|
|
164
|
+
headers,
|
|
165
|
+
signal: args.signal,
|
|
166
|
+
timeoutMs: args.timeoutMs,
|
|
167
|
+
idempotencyKey: args.idempotencyKey,
|
|
168
|
+
retry: args.retry,
|
|
169
|
+
});
|
|
170
|
+
if (response.status === 204) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const payload = await parseResponseBody(response);
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
176
|
+
}
|
|
177
|
+
if (isEnvelope(payload)) {
|
|
178
|
+
if ('data' in payload) {
|
|
179
|
+
return payload.data;
|
|
180
|
+
}
|
|
181
|
+
return payload;
|
|
182
|
+
}
|
|
183
|
+
return payload;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Send a binary upload request (for file writes).
|
|
187
|
+
* Returns when the API acknowledges success.
|
|
188
|
+
*/
|
|
189
|
+
async requestBinary(args) {
|
|
190
|
+
const response = await this.executeRequest({
|
|
191
|
+
endpoint: args.endpoint,
|
|
192
|
+
method: args.method,
|
|
193
|
+
query: args.query,
|
|
194
|
+
body: args.body,
|
|
195
|
+
headers: args.headers,
|
|
196
|
+
signal: args.signal,
|
|
197
|
+
timeoutMs: args.timeoutMs,
|
|
198
|
+
idempotencyKey: args.idempotencyKey,
|
|
199
|
+
retry: args.retry,
|
|
200
|
+
});
|
|
201
|
+
if (response.ok || response.status === 204) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const payload = await parseResponseBody(response);
|
|
205
|
+
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Send a JSON request and return the raw response stream.
|
|
209
|
+
* Used for endpoints that stream SSE frames (`text/event-stream`).
|
|
210
|
+
*/
|
|
211
|
+
async requestJsonStream(args) {
|
|
212
|
+
const headers = args.body !== undefined ? { 'content-type': 'application/json', ...(args.headers ?? {}) } : args.headers;
|
|
213
|
+
const response = await this.executeRequest({
|
|
214
|
+
endpoint: args.endpoint,
|
|
215
|
+
method: args.method,
|
|
216
|
+
query: args.query,
|
|
217
|
+
body: args.body !== undefined ? JSON.stringify(args.body) : undefined,
|
|
218
|
+
headers,
|
|
219
|
+
signal: args.signal,
|
|
220
|
+
timeoutMs: args.timeoutMs,
|
|
221
|
+
idempotencyKey: args.idempotencyKey,
|
|
222
|
+
retry: args.retry,
|
|
223
|
+
});
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
const payload = await parseResponseBody(response);
|
|
226
|
+
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
227
|
+
}
|
|
228
|
+
if (!response.body) {
|
|
229
|
+
throw new errors_1.SandboxApiError({
|
|
230
|
+
status: response.status,
|
|
231
|
+
message: 'Expected a response stream but received an empty body',
|
|
232
|
+
endpoint: `${args.method} ${args.endpoint}`,
|
|
233
|
+
responseBody: null,
|
|
234
|
+
requestId: getRequestId(response),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return response.body;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Request a binary response stream (for file downloads).
|
|
241
|
+
* Throws if the API returns an error status.
|
|
242
|
+
*/
|
|
243
|
+
async requestStream(args) {
|
|
244
|
+
const response = await this.executeRequest({
|
|
245
|
+
endpoint: args.endpoint,
|
|
246
|
+
method: args.method,
|
|
247
|
+
query: args.query,
|
|
248
|
+
signal: args.signal,
|
|
249
|
+
timeoutMs: args.timeoutMs,
|
|
250
|
+
idempotencyKey: args.idempotencyKey,
|
|
251
|
+
retry: args.retry,
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
const payload = await parseResponseBody(response);
|
|
255
|
+
throw this.toApiError(response, args.method, args.endpoint, payload);
|
|
256
|
+
}
|
|
257
|
+
if (!response.body) {
|
|
258
|
+
throw new errors_1.SandboxApiError({
|
|
259
|
+
status: response.status,
|
|
260
|
+
message: 'Expected a response stream but received an empty body',
|
|
261
|
+
endpoint: `${args.method} ${args.endpoint}`,
|
|
262
|
+
responseBody: null,
|
|
263
|
+
requestId: getRequestId(response),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return response.body;
|
|
267
|
+
}
|
|
268
|
+
async executeRequest(args) {
|
|
269
|
+
const timeoutMs = args.timeoutMs ?? this.timeoutMs;
|
|
270
|
+
const retryConfig = mergeRetryConfig(this.retry, args.retry);
|
|
271
|
+
const canRetryMethod = canRetryByMethod(args.method, retryConfig.retryMethods, args.idempotencyKey);
|
|
272
|
+
const replayableBody = canRetryBody(args.body);
|
|
273
|
+
let lastError;
|
|
274
|
+
for (let attempt = 1; attempt <= retryConfig.maxAttempts; attempt += 1) {
|
|
275
|
+
const { signal, cleanup } = maybeJoinSignal(args.signal, timeoutMs);
|
|
276
|
+
try {
|
|
277
|
+
const response = await this.fetchImpl(buildUrl(this.baseUrl, args.endpoint, args.query), {
|
|
278
|
+
method: args.method,
|
|
279
|
+
headers: {
|
|
280
|
+
'x-brimble-key': (0, auth_1.buildApiKeyHeader)(this.apiKey),
|
|
281
|
+
source: 'sdk-package',
|
|
282
|
+
'source-version': constants_1.SDK_PACKAGE_VERSION,
|
|
283
|
+
...(args.idempotencyKey ? { 'idempotency-key': args.idempotencyKey } : {}),
|
|
284
|
+
...(args.headers ?? {}),
|
|
285
|
+
},
|
|
286
|
+
body: args.body,
|
|
287
|
+
signal,
|
|
288
|
+
});
|
|
289
|
+
const shouldRetry = attempt < retryConfig.maxAttempts &&
|
|
290
|
+
canRetryMethod &&
|
|
291
|
+
replayableBody &&
|
|
292
|
+
retryConfig.retryStatuses.includes(response.status) &&
|
|
293
|
+
!signal.aborted;
|
|
294
|
+
if (shouldRetry) {
|
|
295
|
+
await sleep(computeRetryDelayMs(attempt, retryConfig.baseDelayMs, retryConfig.maxDelayMs));
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
return response;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
lastError = error;
|
|
302
|
+
const shouldRetry = attempt < retryConfig.maxAttempts &&
|
|
303
|
+
canRetryMethod &&
|
|
304
|
+
replayableBody &&
|
|
305
|
+
!isAbortError(error) &&
|
|
306
|
+
!(args.signal?.aborted ?? false);
|
|
307
|
+
if (!shouldRetry) {
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
await sleep(computeRetryDelayMs(attempt, retryConfig.baseDelayMs, retryConfig.maxDelayMs));
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
cleanup();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (lastError) {
|
|
317
|
+
throw lastError;
|
|
318
|
+
}
|
|
319
|
+
throw new Error('HTTP request failed before receiving a response.');
|
|
320
|
+
}
|
|
321
|
+
toApiError(response, method, endpoint, responseBody) {
|
|
322
|
+
let message = response.statusText || 'Sandbox API request failed';
|
|
323
|
+
if (hasMessage(responseBody)) {
|
|
324
|
+
message = responseBody.message;
|
|
325
|
+
}
|
|
326
|
+
else if (typeof responseBody === 'string' && responseBody.trim().length > 0) {
|
|
327
|
+
message = responseBody;
|
|
328
|
+
}
|
|
329
|
+
const baseArgs = {
|
|
330
|
+
status: response.status,
|
|
331
|
+
message,
|
|
332
|
+
endpoint: `${method} ${endpoint}`,
|
|
333
|
+
responseBody,
|
|
334
|
+
requestId: getRequestId(response),
|
|
335
|
+
};
|
|
336
|
+
if (response.status === 401 || response.status === 403) {
|
|
337
|
+
return new errors_1.AuthError(baseArgs);
|
|
338
|
+
}
|
|
339
|
+
if (response.status === 400 || response.status === 422) {
|
|
340
|
+
return new errors_1.ValidationError(baseArgs);
|
|
341
|
+
}
|
|
342
|
+
if (response.status === 404) {
|
|
343
|
+
return new errors_1.NotFoundError(baseArgs);
|
|
344
|
+
}
|
|
345
|
+
if (response.status === 429) {
|
|
346
|
+
return new errors_1.RateLimitError({
|
|
347
|
+
...baseArgs,
|
|
348
|
+
retryAfterSeconds: parseRetryAfterSeconds(response.headers.get('retry-after')),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return new errors_1.SandboxApiError(baseArgs);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
exports.HttpTransport = HttpTransport;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toPaginationQuery = toPaginationQuery;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
/** Convert pagination inputs into query params with sensible defaults. */
|
|
6
|
+
function toPaginationQuery(pagination = {}) {
|
|
7
|
+
const params = new URLSearchParams();
|
|
8
|
+
params.set('page', String(pagination.page ?? constants_1.DEFAULT_PAGE));
|
|
9
|
+
params.set('limit', String(pagination.limit ?? constants_1.DEFAULT_PAGE_LIMIT));
|
|
10
|
+
return params;
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CodeLanguage } from '../enums';
|
|
2
|
+
export type ExecInput = {
|
|
3
|
+
cmd: string;
|
|
4
|
+
timeout_seconds?: number;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
stream?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type CodeInput = {
|
|
9
|
+
language: CodeLanguage;
|
|
10
|
+
code: string;
|
|
11
|
+
timeout_seconds?: number;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
stream?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type ExecResult = {
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
exit_code: number;
|
|
19
|
+
duration_ms: number;
|
|
20
|
+
};
|
|
21
|
+
export type ExecStreamFrame = {
|
|
22
|
+
type: 'stdout';
|
|
23
|
+
data: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'stderr';
|
|
26
|
+
data: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: 'done';
|
|
29
|
+
exit_code: number;
|
|
30
|
+
duration_ms: number;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'error';
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type FileUploadBody = ReadableStream<Uint8Array> | Buffer | Uint8Array;
|
|
@@ -0,0 +1,9 @@
|
|
|
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 { AckMessage, CreateSandboxInput, CreateSandboxRequest, CreateSandboxResult, CreateSandboxWithVolumeInput, CreateSandboxWithVolumeResult, Sandbox, SandboxReadyRequestOptions, SandboxRegionInput, SandboxRuntimeOptions, SandboxSpecs, WaitPreference, WaitUntilReadyOptions, } from './sandbox';
|
|
6
|
+
export type { CreateSnapshotInput, Snapshot } from './snapshot';
|
|
7
|
+
export type { Stats, StatsAverageNetwork, StatsAverageNumeric, StatsQuery, StatsTimelinePoint } from './stats';
|
|
8
|
+
export type { SandboxTemplate } from './template';
|
|
9
|
+
export type { CreateVolumeInput, Volume } from './volume';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Pagination = {
|
|
2
|
+
page?: number;
|
|
3
|
+
limit?: number;
|
|
4
|
+
};
|
|
5
|
+
export type TeamScopedPagination = Pagination & {
|
|
6
|
+
teamId?: string;
|
|
7
|
+
};
|
|
8
|
+
export type Paginated<T> = {
|
|
9
|
+
data: T[];
|
|
10
|
+
totalCount: number;
|
|
11
|
+
currentPage: number;
|
|
12
|
+
totalPages: number;
|
|
13
|
+
limit: number;
|
|
14
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
export type SandboxRegion = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
country: string;
|
|
13
|
+
continent: string | null;
|
|
14
|
+
};
|
|
15
|
+
export type SandboxRegionsResult = {
|
|
16
|
+
regions: SandboxRegion[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { DestroyReason, DestroyTimeout, SandboxStatus, SnapshotMode } from '../enums';
|
|
2
|
+
import type { RequestOptions } from '../transport/http';
|
|
3
|
+
import type { RegionSummary } from './region';
|
|
4
|
+
import type { CreateVolumeInput, Volume } from './volume';
|
|
5
|
+
export type SandboxSpecs = {
|
|
6
|
+
cpu?: number;
|
|
7
|
+
memory?: number;
|
|
8
|
+
disk?: number;
|
|
9
|
+
};
|
|
10
|
+
export type SandboxRegionInput = string | 'auto';
|
|
11
|
+
export type CreateSandboxInput = {
|
|
12
|
+
name?: string;
|
|
13
|
+
template?: string;
|
|
14
|
+
teamId?: string;
|
|
15
|
+
environmentId?: string;
|
|
16
|
+
region?: string;
|
|
17
|
+
specs?: SandboxSpecs;
|
|
18
|
+
autoDestroy?: boolean;
|
|
19
|
+
destroyTimeout?: DestroyTimeout;
|
|
20
|
+
oneShot?: boolean;
|
|
21
|
+
blockOutbound?: boolean;
|
|
22
|
+
persistent?: boolean;
|
|
23
|
+
persistentDiskGB?: number;
|
|
24
|
+
volumeId?: string;
|
|
25
|
+
fromSnapshot?: string;
|
|
26
|
+
snapshotMode?: SnapshotMode;
|
|
27
|
+
snapshotFrequency?: string;
|
|
28
|
+
};
|
|
29
|
+
export type CreateSandboxRequest = Omit<CreateSandboxInput, 'region'> & {
|
|
30
|
+
region?: SandboxRegionInput;
|
|
31
|
+
};
|
|
32
|
+
export type CreateSandboxResult = {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
template: string;
|
|
36
|
+
status: SandboxStatus;
|
|
37
|
+
created_at: string;
|
|
38
|
+
expires_at: string;
|
|
39
|
+
};
|
|
40
|
+
export type Sandbox = {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
template: string;
|
|
44
|
+
status: SandboxStatus;
|
|
45
|
+
region: RegionSummary | string;
|
|
46
|
+
specs: SandboxSpecs;
|
|
47
|
+
team: string | null;
|
|
48
|
+
project_environment: string | null;
|
|
49
|
+
auto_destroy: boolean;
|
|
50
|
+
destroy_timeout: DestroyTimeout | null;
|
|
51
|
+
one_shot: boolean;
|
|
52
|
+
block_outbound: boolean;
|
|
53
|
+
persistent: boolean;
|
|
54
|
+
persistent_disk_gb: number | null;
|
|
55
|
+
paused_at: string | null;
|
|
56
|
+
from_snapshot: string | null;
|
|
57
|
+
snapshot_mode: SnapshotMode;
|
|
58
|
+
snapshot_frequency: string | null;
|
|
59
|
+
created_at: string;
|
|
60
|
+
last_activity_at: string;
|
|
61
|
+
expires_at: string;
|
|
62
|
+
destroyed_at: string | null;
|
|
63
|
+
destroy_reason: DestroyReason | null;
|
|
64
|
+
};
|
|
65
|
+
export type AckMessage = {
|
|
66
|
+
message: string;
|
|
67
|
+
};
|
|
68
|
+
export type WaitUntilReadyOptions = {
|
|
69
|
+
timeoutMs?: number;
|
|
70
|
+
pollIntervalMs?: number;
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
};
|
|
73
|
+
export type WaitPreference = boolean | WaitUntilReadyOptions;
|
|
74
|
+
export type SandboxRuntimeOptions = RequestOptions & {
|
|
75
|
+
waitUntilReady?: WaitPreference;
|
|
76
|
+
};
|
|
77
|
+
export type SandboxReadyRequestOptions = {
|
|
78
|
+
request?: RequestOptions;
|
|
79
|
+
wait?: WaitUntilReadyOptions;
|
|
80
|
+
};
|
|
81
|
+
export type CreateSandboxWithVolumeInput = {
|
|
82
|
+
sandbox: Omit<CreateSandboxRequest, 'volumeId' | 'persistent' | 'persistentDiskGB'>;
|
|
83
|
+
volume: Omit<CreateVolumeInput, 'region'> & {
|
|
84
|
+
region?: string;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
export type CreateSandboxWithVolumeResult = {
|
|
88
|
+
sandbox: CreateSandboxResult;
|
|
89
|
+
volume: Volume;
|
|
90
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SnapshotStatus } from '../enums';
|
|
2
|
+
export type CreateSnapshotInput = {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export type Snapshot = {
|
|
6
|
+
id: string;
|
|
7
|
+
sandbox_id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
image_tag: string;
|
|
10
|
+
source_template: string;
|
|
11
|
+
status: SnapshotStatus;
|
|
12
|
+
failure_reason: string | null;
|
|
13
|
+
size_bytes: number | null;
|
|
14
|
+
created_at: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type StatsAverageNumeric = {
|
|
2
|
+
totalInPercentage: number;
|
|
3
|
+
size: number;
|
|
4
|
+
};
|
|
5
|
+
export type StatsAverageNetwork = {
|
|
6
|
+
value?: number | null;
|
|
7
|
+
total?: number | null;
|
|
8
|
+
totalInPercentage?: number | null;
|
|
9
|
+
bytesPerSecond?: number | null;
|
|
10
|
+
};
|
|
11
|
+
export type StatsTimelinePoint = {
|
|
12
|
+
date: string;
|
|
13
|
+
memory: number;
|
|
14
|
+
cpu: number;
|
|
15
|
+
network: {
|
|
16
|
+
bytesPerSecond: number | null;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type Stats = {
|
|
20
|
+
average: {
|
|
21
|
+
memory: StatsAverageNumeric;
|
|
22
|
+
cpu: StatsAverageNumeric;
|
|
23
|
+
network: StatsAverageNetwork;
|
|
24
|
+
};
|
|
25
|
+
replicaCount: number;
|
|
26
|
+
results: StatsTimelinePoint[];
|
|
27
|
+
responseTime: unknown;
|
|
28
|
+
};
|
|
29
|
+
export type StatsQuery = {
|
|
30
|
+
hoursAgo?: number;
|
|
31
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { VolumeType } from '../enums';
|
|
2
|
+
import type { RegionSummary } from './region';
|
|
3
|
+
export type CreateVolumeInput = {
|
|
4
|
+
name: string;
|
|
5
|
+
sizeGB: number;
|
|
6
|
+
region: string;
|
|
7
|
+
type?: VolumeType;
|
|
8
|
+
teamId?: string;
|
|
9
|
+
};
|
|
10
|
+
export type Volume = {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
type: VolumeType;
|
|
14
|
+
team: string | null;
|
|
15
|
+
csi_volume_id: string | null;
|
|
16
|
+
size: number;
|
|
17
|
+
region: RegionSummary | null;
|
|
18
|
+
mount_path: string | null;
|
|
19
|
+
attached_sandbox_id: string | null;
|
|
20
|
+
attached_project_id: string | null;
|
|
21
|
+
last_attached_at: string | null;
|
|
22
|
+
created_at: string | null;
|
|
23
|
+
updated_at: string | null;
|
|
24
|
+
};
|