@cloudflare/sandbox 0.4.12 → 0.4.15

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 (79) hide show
  1. package/.turbo/turbo-build.log +13 -47
  2. package/CHANGELOG.md +46 -16
  3. package/Dockerfile +78 -31
  4. package/README.md +9 -2
  5. package/dist/index.d.ts +1889 -9
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3144 -65
  8. package/dist/index.js.map +1 -1
  9. package/package.json +5 -5
  10. package/src/clients/base-client.ts +39 -24
  11. package/src/clients/command-client.ts +8 -8
  12. package/src/clients/file-client.ts +31 -26
  13. package/src/clients/git-client.ts +3 -4
  14. package/src/clients/index.ts +12 -16
  15. package/src/clients/interpreter-client.ts +51 -47
  16. package/src/clients/port-client.ts +10 -10
  17. package/src/clients/process-client.ts +11 -8
  18. package/src/clients/sandbox-client.ts +2 -4
  19. package/src/clients/types.ts +6 -2
  20. package/src/clients/utility-client.ts +10 -6
  21. package/src/errors/adapter.ts +90 -32
  22. package/src/errors/classes.ts +189 -64
  23. package/src/errors/index.ts +9 -5
  24. package/src/file-stream.ts +11 -6
  25. package/src/index.ts +22 -15
  26. package/src/interpreter.ts +50 -41
  27. package/src/request-handler.ts +24 -21
  28. package/src/sandbox.ts +339 -149
  29. package/src/security.ts +21 -6
  30. package/src/sse-parser.ts +4 -3
  31. package/src/version.ts +1 -1
  32. package/tests/base-client.test.ts +116 -80
  33. package/tests/command-client.test.ts +149 -112
  34. package/tests/file-client.test.ts +309 -197
  35. package/tests/file-stream.test.ts +24 -20
  36. package/tests/get-sandbox.test.ts +10 -10
  37. package/tests/git-client.test.ts +188 -101
  38. package/tests/port-client.test.ts +100 -108
  39. package/tests/process-client.test.ts +204 -179
  40. package/tests/request-handler.test.ts +117 -65
  41. package/tests/sandbox.test.ts +219 -67
  42. package/tests/sse-parser.test.ts +17 -16
  43. package/tests/utility-client.test.ts +79 -72
  44. package/tsdown.config.ts +12 -0
  45. package/vitest.config.ts +6 -6
  46. package/dist/chunk-BFVUNTP4.js +0 -104
  47. package/dist/chunk-BFVUNTP4.js.map +0 -1
  48. package/dist/chunk-EKSWCBCA.js +0 -86
  49. package/dist/chunk-EKSWCBCA.js.map +0 -1
  50. package/dist/chunk-JXZMAU2C.js +0 -559
  51. package/dist/chunk-JXZMAU2C.js.map +0 -1
  52. package/dist/chunk-UJ3TV4M6.js +0 -7
  53. package/dist/chunk-UJ3TV4M6.js.map +0 -1
  54. package/dist/chunk-YE265ASX.js +0 -2484
  55. package/dist/chunk-YE265ASX.js.map +0 -1
  56. package/dist/chunk-Z532A7QC.js +0 -78
  57. package/dist/chunk-Z532A7QC.js.map +0 -1
  58. package/dist/file-stream.d.ts +0 -43
  59. package/dist/file-stream.js +0 -9
  60. package/dist/file-stream.js.map +0 -1
  61. package/dist/interpreter.d.ts +0 -33
  62. package/dist/interpreter.js +0 -8
  63. package/dist/interpreter.js.map +0 -1
  64. package/dist/request-handler.d.ts +0 -18
  65. package/dist/request-handler.js +0 -13
  66. package/dist/request-handler.js.map +0 -1
  67. package/dist/sandbox-CLZWpfGc.d.ts +0 -613
  68. package/dist/sandbox.d.ts +0 -4
  69. package/dist/sandbox.js +0 -13
  70. package/dist/sandbox.js.map +0 -1
  71. package/dist/security.d.ts +0 -31
  72. package/dist/security.js +0 -13
  73. package/dist/security.js.map +0 -1
  74. package/dist/sse-parser.d.ts +0 -28
  75. package/dist/sse-parser.js +0 -11
  76. package/dist/sse-parser.js.map +0 -1
  77. package/dist/version.d.ts +0 -8
  78. package/dist/version.js +0 -7
  79. package/dist/version.js.map +0 -1
@@ -1,17 +1,14 @@
1
- import type { Logger } from "@repo/shared";
2
- import { createNoOpLogger } from "@repo/shared";
1
+ import type { Logger } from '@repo/shared';
2
+ import { createNoOpLogger } from '@repo/shared';
3
3
  import { getHttpStatus } from '@repo/shared/errors';
4
4
  import type { ErrorResponse as NewErrorResponse } from '../errors';
5
5
  import { createErrorFromResponse, ErrorCode } from '../errors';
6
6
  import type { SandboxError } from '../errors/classes';
7
- import type {
8
- HttpClientOptions,
9
- ResponseHandler
10
- } from './types';
7
+ import type { HttpClientOptions, ResponseHandler } from './types';
11
8
 
12
9
  // Container provisioning retry configuration
13
- const TIMEOUT_MS = 60_000; // 60 seconds total timeout budget
14
- const MIN_TIME_FOR_RETRY_MS = 10_000; // Need at least 10s remaining to retry (8s Container + 2s delay)
10
+ const TIMEOUT_MS = 60_000; // 60 seconds total timeout budget
11
+ const MIN_TIME_FOR_RETRY_MS = 10_000; // Need at least 10s remaining to retry (8s Container + 2s delay)
15
12
 
16
13
  /**
17
14
  * Abstract base class providing common HTTP functionality for all domain clients
@@ -42,7 +39,8 @@ export abstract class BaseHttpClient {
42
39
 
43
40
  // Only retry container provisioning 503s, not user app 503s
44
41
  if (response.status === 503) {
45
- const isContainerProvisioning = await this.isContainerProvisioningError(response);
42
+ const isContainerProvisioning =
43
+ await this.isContainerProvisioningError(response);
46
44
 
47
45
  if (isContainerProvisioning) {
48
46
  const elapsed = Date.now() - startTime;
@@ -60,13 +58,16 @@ export abstract class BaseHttpClient {
60
58
  remainingSec: Math.floor(remaining / 1000)
61
59
  });
62
60
 
63
- await new Promise(resolve => setTimeout(resolve, delay));
61
+ await new Promise((resolve) => setTimeout(resolve, delay));
64
62
  attempt++;
65
63
  continue;
66
64
  } else {
67
65
  // Exhausted retries - log error and return response
68
66
  // Let existing error handling convert to proper error
69
- this.logger.error('Container failed to provision after multiple attempts', new Error(`Failed after ${attempt + 1} attempts over 60s`));
67
+ this.logger.error(
68
+ 'Container failed to provision after multiple attempts',
69
+ new Error(`Failed after ${attempt + 1} attempts over 60s`)
70
+ );
70
71
  return response;
71
72
  }
72
73
  }
@@ -87,9 +88,9 @@ export abstract class BaseHttpClient {
87
88
  const response = await this.doFetch(endpoint, {
88
89
  method: 'POST',
89
90
  headers: {
90
- 'Content-Type': 'application/json',
91
+ 'Content-Type': 'application/json'
91
92
  },
92
- body: JSON.stringify(data),
93
+ body: JSON.stringify(data)
93
94
  });
94
95
 
95
96
  return this.handleResponse(response, responseHandler);
@@ -103,7 +104,7 @@ export abstract class BaseHttpClient {
103
104
  responseHandler?: ResponseHandler<T>
104
105
  ): Promise<T> {
105
106
  const response = await this.doFetch(endpoint, {
106
- method: 'GET',
107
+ method: 'GET'
107
108
  });
108
109
 
109
110
  return this.handleResponse(response, responseHandler);
@@ -117,13 +118,12 @@ export abstract class BaseHttpClient {
117
118
  responseHandler?: ResponseHandler<T>
118
119
  ): Promise<T> {
119
120
  const response = await this.doFetch(endpoint, {
120
- method: 'DELETE',
121
+ method: 'DELETE'
121
122
  });
122
123
 
123
124
  return this.handleResponse(response, responseHandler);
124
125
  }
125
126
 
126
-
127
127
  /**
128
128
  * Handle HTTP response with error checking and parsing
129
129
  */
@@ -145,7 +145,9 @@ export abstract class BaseHttpClient {
145
145
  // Handle malformed JSON responses gracefully
146
146
  const errorResponse: NewErrorResponse = {
147
147
  code: ErrorCode.INVALID_JSON_RESPONSE,
148
- message: `Invalid JSON response: ${error instanceof Error ? error.message : 'Unknown parsing error'}`,
148
+ message: `Invalid JSON response: ${
149
+ error instanceof Error ? error.message : 'Unknown parsing error'
150
+ }`,
149
151
  context: {},
150
152
  httpStatus: response.status,
151
153
  timestamp: new Date().toISOString()
@@ -182,8 +184,6 @@ export abstract class BaseHttpClient {
182
184
  throw error;
183
185
  }
184
186
 
185
-
186
-
187
187
  /**
188
188
  * Create a streaming response handler for Server-Sent Events
189
189
  */
@@ -205,7 +205,10 @@ export abstract class BaseHttpClient {
205
205
  * Utility method to log successful operations
206
206
  */
207
207
  protected logSuccess(operation: string, details?: string): void {
208
- this.logger.info(`${operation} completed successfully`, details ? { details } : undefined);
208
+ this.logger.info(
209
+ `${operation} completed successfully`,
210
+ details ? { details } : undefined
211
+ );
209
212
  }
210
213
 
211
214
  /**
@@ -242,7 +245,9 @@ export abstract class BaseHttpClient {
242
245
  * Check if 503 response is from container provisioning (retryable)
243
246
  * vs user application (not retryable)
244
247
  */
245
- private async isContainerProvisioningError(response: Response): Promise<boolean> {
248
+ private async isContainerProvisioningError(
249
+ response: Response
250
+ ): Promise<boolean> {
246
251
  try {
247
252
  // Clone response so we don't consume the original body
248
253
  const cloned = response.clone();
@@ -251,13 +256,19 @@ export abstract class BaseHttpClient {
251
256
  // Container package returns specific message for provisioning errors
252
257
  return text.includes('There is no Container instance available');
253
258
  } catch (error) {
254
- this.logger.error('Error checking response body', error instanceof Error ? error : new Error(String(error)));
259
+ this.logger.error(
260
+ 'Error checking response body',
261
+ error instanceof Error ? error : new Error(String(error))
262
+ );
255
263
  // If we can't read the body, don't retry to be safe
256
264
  return false;
257
265
  }
258
266
  }
259
267
 
260
- private async executeFetch(path: string, options?: RequestInit): Promise<Response> {
268
+ private async executeFetch(
269
+ path: string,
270
+ options?: RequestInit
271
+ ): Promise<Response> {
261
272
  const url = this.options.stub
262
273
  ? `http://localhost:${this.options.port}${path}`
263
274
  : `${this.baseUrl}${path}`;
@@ -273,7 +284,11 @@ export abstract class BaseHttpClient {
273
284
  return await fetch(url, options);
274
285
  }
275
286
  } catch (error) {
276
- this.logger.error('HTTP request error', error instanceof Error ? error : new Error(String(error)), { method: options?.method || 'GET', url });
287
+ this.logger.error(
288
+ 'HTTP request error',
289
+ error instanceof Error ? error : new Error(String(error)),
290
+ { method: options?.method || 'GET', url }
291
+ );
277
292
  throw error;
278
293
  }
279
294
  }
@@ -1,5 +1,9 @@
1
1
  import { BaseHttpClient } from './base-client';
2
- import type { BaseApiResponse, HttpClientOptions, SessionRequest } from './types';
2
+ import type {
3
+ BaseApiResponse,
4
+ HttpClientOptions,
5
+ SessionRequest
6
+ } from './types';
3
7
 
4
8
  /**
5
9
  * Request interface for command execution
@@ -23,7 +27,6 @@ export interface ExecuteResponse extends BaseApiResponse {
23
27
  * Client for command execution operations
24
28
  */
25
29
  export class CommandClient extends BaseHttpClient {
26
-
27
30
  /**
28
31
  * Execute a command and return the complete result
29
32
  * @param command - The command to execute
@@ -42,10 +45,7 @@ export class CommandClient extends BaseHttpClient {
42
45
  ...(timeoutMs !== undefined && { timeoutMs })
43
46
  };
44
47
 
45
- const response = await this.post<ExecuteResponse>(
46
- '/api/execute',
47
- data
48
- );
48
+ const response = await this.post<ExecuteResponse>('/api/execute', data);
49
49
 
50
50
  this.logSuccess(
51
51
  'Command executed',
@@ -90,9 +90,9 @@ export class CommandClient extends BaseHttpClient {
90
90
  const response = await this.doFetch('/api/execute/stream', {
91
91
  method: 'POST',
92
92
  headers: {
93
- 'Content-Type': 'application/json',
93
+ 'Content-Type': 'application/json'
94
94
  },
95
- body: JSON.stringify(data),
95
+ body: JSON.stringify(data)
96
96
  });
97
97
 
98
98
  const stream = await this.handleStreamResponse(response);
@@ -49,7 +49,6 @@ export interface FileOperationRequest extends SessionRequest {
49
49
  * Client for file system operations
50
50
  */
51
51
  export class FileClient extends BaseHttpClient {
52
-
53
52
  /**
54
53
  * Create a directory
55
54
  * @param path - Directory path to create
@@ -65,12 +64,15 @@ export class FileClient extends BaseHttpClient {
65
64
  const data = {
66
65
  path,
67
66
  sessionId,
68
- recursive: options?.recursive ?? false,
67
+ recursive: options?.recursive ?? false
69
68
  };
70
69
 
71
70
  const response = await this.post<MkdirResult>('/api/mkdir', data);
72
-
73
- this.logSuccess('Directory created', `${path} (recursive: ${data.recursive})`);
71
+
72
+ this.logSuccess(
73
+ 'Directory created',
74
+ `${path} (recursive: ${data.recursive})`
75
+ );
74
76
  return response;
75
77
  } catch (error) {
76
78
  this.logError('mkdir', error);
@@ -96,11 +98,11 @@ export class FileClient extends BaseHttpClient {
96
98
  path,
97
99
  content,
98
100
  sessionId,
99
- encoding: options?.encoding ?? 'utf8',
101
+ encoding: options?.encoding ?? 'utf8'
100
102
  };
101
103
 
102
104
  const response = await this.post<WriteFileResult>('/api/write', data);
103
-
105
+
104
106
  this.logSuccess('File written', `${path} (${content.length} chars)`);
105
107
  return response;
106
108
  } catch (error) {
@@ -124,12 +126,15 @@ export class FileClient extends BaseHttpClient {
124
126
  const data = {
125
127
  path,
126
128
  sessionId,
127
- encoding: options?.encoding ?? 'utf8',
129
+ encoding: options?.encoding ?? 'utf8'
128
130
  };
129
131
 
130
132
  const response = await this.post<ReadFileResult>('/api/read', data);
131
133
 
132
- this.logSuccess('File read', `${path} (${response.content.length} chars)`);
134
+ this.logSuccess(
135
+ 'File read',
136
+ `${path} (${response.content.length} chars)`
137
+ );
133
138
  return response;
134
139
  } catch (error) {
135
140
  this.logError('readFile', error);
@@ -150,15 +155,15 @@ export class FileClient extends BaseHttpClient {
150
155
  try {
151
156
  const data = {
152
157
  path,
153
- sessionId,
158
+ sessionId
154
159
  };
155
160
 
156
161
  const response = await this.doFetch('/api/read/stream', {
157
162
  method: 'POST',
158
163
  headers: {
159
- 'Content-Type': 'application/json',
164
+ 'Content-Type': 'application/json'
160
165
  },
161
- body: JSON.stringify(data),
166
+ body: JSON.stringify(data)
162
167
  });
163
168
 
164
169
  const stream = await this.handleStreamResponse(response);
@@ -175,15 +180,12 @@ export class FileClient extends BaseHttpClient {
175
180
  * @param path - File path to delete
176
181
  * @param sessionId - The session ID for this operation
177
182
  */
178
- async deleteFile(
179
- path: string,
180
- sessionId: string
181
- ): Promise<DeleteFileResult> {
183
+ async deleteFile(path: string, sessionId: string): Promise<DeleteFileResult> {
182
184
  try {
183
185
  const data = { path, sessionId };
184
186
 
185
187
  const response = await this.post<DeleteFileResult>('/api/delete', data);
186
-
188
+
187
189
  this.logSuccess('File deleted', path);
188
190
  return response;
189
191
  } catch (error) {
@@ -207,7 +209,7 @@ export class FileClient extends BaseHttpClient {
207
209
  const data = { oldPath: path, newPath, sessionId };
208
210
 
209
211
  const response = await this.post<RenameFileResult>('/api/rename', data);
210
-
212
+
211
213
  this.logSuccess('File renamed', `${path} -> ${newPath}`);
212
214
  return response;
213
215
  } catch (error) {
@@ -255,10 +257,13 @@ export class FileClient extends BaseHttpClient {
255
257
  const data = {
256
258
  path,
257
259
  sessionId,
258
- options: options || {},
260
+ options: options || {}
259
261
  };
260
262
 
261
- const response = await this.post<ListFilesResult>('/api/list-files', data);
263
+ const response = await this.post<ListFilesResult>(
264
+ '/api/list-files',
265
+ data
266
+ );
262
267
 
263
268
  this.logSuccess('Files listed', `${path} (${response.count} files)`);
264
269
  return response;
@@ -273,23 +278,23 @@ export class FileClient extends BaseHttpClient {
273
278
  * @param path - Path to check
274
279
  * @param sessionId - The session ID for this operation
275
280
  */
276
- async exists(
277
- path: string,
278
- sessionId: string
279
- ): Promise<FileExistsResult> {
281
+ async exists(path: string, sessionId: string): Promise<FileExistsResult> {
280
282
  try {
281
283
  const data = {
282
284
  path,
283
- sessionId,
285
+ sessionId
284
286
  };
285
287
 
286
288
  const response = await this.post<FileExistsResult>('/api/exists', data);
287
289
 
288
- this.logSuccess('Path existence checked', `${path} (exists: ${response.exists})`);
290
+ this.logSuccess(
291
+ 'Path existence checked',
292
+ `${path} (exists: ${response.exists})`
293
+ );
289
294
  return response;
290
295
  } catch (error) {
291
296
  this.logError('exists', error);
292
297
  throw error;
293
298
  }
294
299
  }
295
- }
300
+ }
@@ -18,7 +18,6 @@ export interface GitCheckoutRequest extends SessionRequest {
18
18
  * Client for Git repository operations
19
19
  */
20
20
  export class GitClient extends BaseHttpClient {
21
-
22
21
  /**
23
22
  * Clone a Git repository
24
23
  * @param repoUrl - URL of the Git repository to clone
@@ -45,7 +44,7 @@ export class GitClient extends BaseHttpClient {
45
44
  const data: GitCheckoutRequest = {
46
45
  repoUrl,
47
46
  sessionId,
48
- targetDir,
47
+ targetDir
49
48
  };
50
49
 
51
50
  // Only include branch if explicitly specified
@@ -79,7 +78,7 @@ export class GitClient extends BaseHttpClient {
79
78
  const url = new URL(repoUrl);
80
79
  const pathParts = url.pathname.split('/');
81
80
  const repoName = pathParts[pathParts.length - 1];
82
-
81
+
83
82
  // Remove .git extension if present
84
83
  return repoName.replace(/\.git$/, '');
85
84
  } catch {
@@ -89,4 +88,4 @@ export class GitClient extends BaseHttpClient {
89
88
  return repoName.replace(/\.git$/, '') || 'repo';
90
89
  }
91
90
  }
92
- }
91
+ }
@@ -1,11 +1,7 @@
1
1
  // Main client exports
2
2
 
3
-
4
3
  // Command client types
5
- export type {
6
- ExecuteRequest,
7
- ExecuteResponse,
8
- } from './command-client';
4
+ export type { ExecuteRequest, ExecuteResponse } from './command-client';
9
5
 
10
6
  // Domain-specific clients
11
7
  export { CommandClient } from './command-client';
@@ -14,23 +10,23 @@ export type {
14
10
  FileOperationRequest,
15
11
  MkdirRequest,
16
12
  ReadFileRequest,
17
- WriteFileRequest,
13
+ WriteFileRequest
18
14
  } from './file-client';
19
15
  export { FileClient } from './file-client';
20
16
  // Git client types
21
- export type {
22
- GitCheckoutRequest,
23
- GitCheckoutResult,
24
- } from './git-client';
17
+ export type { GitCheckoutRequest, GitCheckoutResult } from './git-client';
25
18
  export { GitClient } from './git-client';
26
- export { type ExecutionCallbacks, InterpreterClient } from './interpreter-client';
19
+ export {
20
+ type ExecutionCallbacks,
21
+ InterpreterClient
22
+ } from './interpreter-client';
27
23
  // Port client types
28
24
  export type {
29
25
  ExposePortRequest,
30
26
  PortCloseResult,
31
27
  PortExposeResult,
32
28
  PortListResult,
33
- UnexposePortRequest,
29
+ UnexposePortRequest
34
30
  } from './port-client';
35
31
  export { PortClient } from './port-client';
36
32
  // Process client types
@@ -41,7 +37,7 @@ export type {
41
37
  ProcessListResult,
42
38
  ProcessLogsResult,
43
39
  ProcessStartResult,
44
- StartProcessRequest,
40
+ StartProcessRequest
45
41
  } from './process-client';
46
42
  export { ProcessClient } from './process-client';
47
43
  export { SandboxClient } from './sandbox-client';
@@ -53,12 +49,12 @@ export type {
53
49
  HttpClientOptions,
54
50
  RequestConfig,
55
51
  ResponseHandler,
56
- SessionRequest,
52
+ SessionRequest
57
53
  } from './types';
58
54
  // Utility client types
59
55
  export type {
60
56
  CommandsResponse,
61
57
  PingResponse,
62
- VersionResponse,
58
+ VersionResponse
63
59
  } from './utility-client';
64
- export { UtilityClient } from './utility-client';
60
+ export { UtilityClient } from './utility-client';