@cloudflare/sandbox 0.4.11 → 0.4.14

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 +44 -16
  3. package/Dockerfile +15 -9
  4. package/README.md +0 -1
  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 +370 -148
  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 +45 -6
  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 +220 -68
  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-FE4PJSRB.js +0 -7
  51. package/dist/chunk-FE4PJSRB.js.map +0 -1
  52. package/dist/chunk-JXZMAU2C.js +0 -559
  53. package/dist/chunk-JXZMAU2C.js.map +0 -1
  54. package/dist/chunk-SVWLTRHD.js +0 -2456
  55. package/dist/chunk-SVWLTRHD.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-DWQVgVTY.d.ts +0 -603
  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
package/src/security.ts CHANGED
@@ -10,7 +10,10 @@
10
10
  */
11
11
 
12
12
  export class SecurityError extends Error {
13
- constructor(message: string, public readonly code?: string) {
13
+ constructor(
14
+ message: string,
15
+ public readonly code?: string
16
+ ) {
14
17
  super(message);
15
18
  this.name = 'SecurityError';
16
19
  }
@@ -34,7 +37,7 @@ export function validatePort(port: number): boolean {
34
37
  // Exclude ports reserved by our system
35
38
  const reservedPorts = [
36
39
  3000, // Control plane port
37
- 8787, // Common wrangler dev port
40
+ 8787 // Common wrangler dev port
38
41
  ];
39
42
 
40
43
  if (reservedPorts.includes(port)) {
@@ -67,8 +70,13 @@ export function sanitizeSandboxId(id: string): string {
67
70
 
68
71
  // Prevent reserved names that cause technical conflicts
69
72
  const reservedNames = [
70
- 'www', 'api', 'admin', 'root', 'system',
71
- 'cloudflare', 'workers'
73
+ 'www',
74
+ 'api',
75
+ 'admin',
76
+ 'root',
77
+ 'system',
78
+ 'cloudflare',
79
+ 'workers'
72
80
  ];
73
81
 
74
82
  const lowerCaseId = id.toLowerCase();
@@ -82,7 +90,6 @@ export function sanitizeSandboxId(id: string): string {
82
90
  return id;
83
91
  }
84
92
 
85
-
86
93
  /**
87
94
  * Validates language for code interpreter
88
95
  * Only allows supported languages
@@ -92,7 +99,15 @@ export function validateLanguage(language: string | undefined): void {
92
99
  return; // undefined is valid, will default to python
93
100
  }
94
101
 
95
- const supportedLanguages = ['python', 'python3', 'javascript', 'js', 'node', 'typescript', 'ts'];
102
+ const supportedLanguages = [
103
+ 'python',
104
+ 'python3',
105
+ 'javascript',
106
+ 'js',
107
+ 'node',
108
+ 'typescript',
109
+ 'ts'
110
+ ];
96
111
  const normalized = language.toLowerCase();
97
112
 
98
113
  if (!supportedLanguages.includes(normalized)) {
package/src/sse-parser.ts CHANGED
@@ -76,7 +76,6 @@ export async function* parseSSEStream<T>(
76
76
  }
77
77
  }
78
78
 
79
-
80
79
  /**
81
80
  * Helper to convert a Response with SSE stream directly to AsyncIterable
82
81
  * @param response - Response object with SSE stream
@@ -87,7 +86,9 @@ export async function* responseToAsyncIterable<T>(
87
86
  signal?: AbortSignal
88
87
  ): AsyncIterable<T> {
89
88
  if (!response.ok) {
90
- throw new Error(`Response not ok: ${response.status} ${response.statusText}`);
89
+ throw new Error(
90
+ `Response not ok: ${response.status} ${response.statusText}`
91
+ );
91
92
  }
92
93
 
93
94
  if (!response.body) {
@@ -140,4 +141,4 @@ export function asyncIterableToSSEStream<T>(
140
141
  // Handle stream cancellation
141
142
  }
142
143
  });
143
- }
144
+ }
package/src/version.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  * This file is auto-updated by .github/changeset-version.ts during releases
4
4
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
5
5
  */
6
- export const SDK_VERSION = '0.4.11';
6
+ export const SDK_VERSION = '0.4.14';
@@ -31,11 +31,14 @@ class TestHttpClient extends BaseHttpClient {
31
31
  super({
32
32
  baseUrl: 'http://test.com',
33
33
  port: 3000,
34
- ...options,
34
+ ...options
35
35
  });
36
36
  }
37
37
 
38
- public async testRequest<T = BaseApiResponse>(endpoint: string, data?: Record<string, unknown>): Promise<T> {
38
+ public async testRequest<T = BaseApiResponse>(
39
+ endpoint: string,
40
+ data?: Record<string, unknown>
41
+ ): Promise<T> {
39
42
  if (data) {
40
43
  return this.post<T>(endpoint, data);
41
44
  }
@@ -48,10 +51,9 @@ class TestHttpClient extends BaseHttpClient {
48
51
  }
49
52
 
50
53
  public async testErrorHandling(errorResponse: ErrorResponse) {
51
- const response = new Response(
52
- JSON.stringify(errorResponse),
53
- { status: errorResponse.httpStatus || 400 }
54
- );
54
+ const response = new Response(JSON.stringify(errorResponse), {
55
+ status: errorResponse.httpStatus || 400
56
+ });
55
57
  return this.handleErrorResponse(response);
56
58
  }
57
59
  }
@@ -63,15 +65,15 @@ describe('BaseHttpClient', () => {
63
65
 
64
66
  beforeEach(() => {
65
67
  vi.clearAllMocks();
66
-
68
+
67
69
  mockFetch = vi.fn();
68
70
  global.fetch = mockFetch as unknown as typeof fetch;
69
71
  onError = vi.fn();
70
-
72
+
71
73
  client = new TestHttpClient({
72
74
  baseUrl: 'http://test.com',
73
75
  port: 3000,
74
- onError,
76
+ onError
75
77
  });
76
78
  });
77
79
 
@@ -81,10 +83,12 @@ describe('BaseHttpClient', () => {
81
83
 
82
84
  describe('core request functionality', () => {
83
85
  it('should handle successful API requests', async () => {
84
- mockFetch.mockResolvedValue(new Response(
85
- JSON.stringify({ success: true, data: 'operation completed' }),
86
- { status: 200 }
87
- ));
86
+ mockFetch.mockResolvedValue(
87
+ new Response(
88
+ JSON.stringify({ success: true, data: 'operation completed' }),
89
+ { status: 200 }
90
+ )
91
+ );
88
92
 
89
93
  const result = await client.testRequest<TestDataResponse>('/api/test');
90
94
 
@@ -94,12 +98,16 @@ describe('BaseHttpClient', () => {
94
98
 
95
99
  it('should handle POST requests with data', async () => {
96
100
  const requestData = { action: 'create', name: 'test-resource' };
97
- mockFetch.mockResolvedValue(new Response(
98
- JSON.stringify({ success: true, id: 'resource-123' }),
99
- { status: 201 }
100
- ));
101
+ mockFetch.mockResolvedValue(
102
+ new Response(JSON.stringify({ success: true, id: 'resource-123' }), {
103
+ status: 201
104
+ })
105
+ );
101
106
 
102
- const result = await client.testRequest<TestResourceResponse>('/api/create', requestData);
107
+ const result = await client.testRequest<TestResourceResponse>(
108
+ '/api/create',
109
+ requestData
110
+ );
103
111
 
104
112
  expect(result.success).toBe(true);
105
113
  expect(result.id).toBe('resource-123');
@@ -123,7 +131,7 @@ describe('BaseHttpClient', () => {
123
131
  httpStatus: 404,
124
132
  timestamp: new Date().toISOString()
125
133
  },
126
- expectedError: FileNotFoundError,
134
+ expectedError: FileNotFoundError
127
135
  },
128
136
  {
129
137
  containerError: {
@@ -133,7 +141,7 @@ describe('BaseHttpClient', () => {
133
141
  httpStatus: 403,
134
142
  timestamp: new Date().toISOString()
135
143
  },
136
- expectedError: PermissionDeniedError,
144
+ expectedError: PermissionDeniedError
137
145
  },
138
146
  {
139
147
  containerError: {
@@ -143,7 +151,7 @@ describe('BaseHttpClient', () => {
143
151
  httpStatus: 400,
144
152
  timestamp: new Date().toISOString()
145
153
  },
146
- expectedError: CommandError,
154
+ expectedError: CommandError
147
155
  },
148
156
  {
149
157
  containerError: {
@@ -153,7 +161,7 @@ describe('BaseHttpClient', () => {
153
161
  httpStatus: 500,
154
162
  timestamp: new Date().toISOString()
155
163
  },
156
- expectedError: FileSystemError,
164
+ expectedError: FileSystemError
157
165
  },
158
166
  {
159
167
  containerError: {
@@ -163,49 +171,56 @@ describe('BaseHttpClient', () => {
163
171
  httpStatus: 500,
164
172
  timestamp: new Date().toISOString()
165
173
  },
166
- expectedError: SandboxError,
174
+ expectedError: SandboxError
167
175
  }
168
176
  ];
169
177
 
170
178
  for (const test of errorMappingTests) {
171
- await expect(client.testErrorHandling(test.containerError as ErrorResponse))
172
- .rejects.toThrow(test.expectedError);
173
-
174
- expect(onError).toHaveBeenCalledWith(test.containerError.message, undefined);
179
+ await expect(
180
+ client.testErrorHandling(test.containerError as ErrorResponse)
181
+ ).rejects.toThrow(test.expectedError);
182
+
183
+ expect(onError).toHaveBeenCalledWith(
184
+ test.containerError.message,
185
+ undefined
186
+ );
175
187
  }
176
188
  });
177
189
 
178
190
  it('should handle malformed error responses', async () => {
179
- mockFetch.mockResolvedValue(new Response(
180
- 'invalid json {',
181
- { status: 500 }
182
- ));
191
+ mockFetch.mockResolvedValue(
192
+ new Response('invalid json {', { status: 500 })
193
+ );
183
194
 
184
- await expect(client.testRequest('/api/test'))
185
- .rejects.toThrow(SandboxError);
195
+ await expect(client.testRequest('/api/test')).rejects.toThrow(
196
+ SandboxError
197
+ );
186
198
  });
187
199
 
188
200
  it('should handle network failures', async () => {
189
201
  mockFetch.mockRejectedValue(new Error('Network connection timeout'));
190
202
 
191
- await expect(client.testRequest('/api/test'))
192
- .rejects.toThrow('Network connection timeout');
203
+ await expect(client.testRequest('/api/test')).rejects.toThrow(
204
+ 'Network connection timeout'
205
+ );
193
206
  });
194
207
 
195
208
  it('should handle server unavailable scenarios', async () => {
196
- mockFetch.mockResolvedValue(new Response(
197
- 'Service Unavailable',
198
- { status: 503 }
199
- ));
209
+ mockFetch.mockResolvedValue(
210
+ new Response('Service Unavailable', { status: 503 })
211
+ );
200
212
 
201
- await expect(client.testRequest('/api/test'))
202
- .rejects.toThrow(SandboxError);
213
+ await expect(client.testRequest('/api/test')).rejects.toThrow(
214
+ SandboxError
215
+ );
203
216
 
204
- expect(onError).toHaveBeenCalledWith('HTTP error! status: 503', undefined);
217
+ expect(onError).toHaveBeenCalledWith(
218
+ 'HTTP error! status: 503',
219
+ undefined
220
+ );
205
221
  });
206
222
  });
207
223
 
208
-
209
224
  describe('streaming functionality', () => {
210
225
  it('should handle streaming responses', async () => {
211
226
  const streamData = 'data: {"type":"output","content":"stream data"}\n\n';
@@ -216,10 +231,12 @@ describe('BaseHttpClient', () => {
216
231
  }
217
232
  });
218
233
 
219
- mockFetch.mockResolvedValue(new Response(mockStream, {
220
- status: 200,
221
- headers: { 'Content-Type': 'text/event-stream' }
222
- }));
234
+ mockFetch.mockResolvedValue(
235
+ new Response(mockStream, {
236
+ status: 200,
237
+ headers: { 'Content-Type': 'text/event-stream' }
238
+ })
239
+ );
223
240
 
224
241
  const stream = await client.testStreamRequest('/api/stream');
225
242
 
@@ -236,41 +253,52 @@ describe('BaseHttpClient', () => {
236
253
  });
237
254
 
238
255
  it('should handle streaming errors', async () => {
239
- mockFetch.mockResolvedValue(new Response(
240
- JSON.stringify({ error: 'Stream initialization failed', code: 'STREAM_ERROR' }),
241
- { status: 400 }
242
- ));
256
+ mockFetch.mockResolvedValue(
257
+ new Response(
258
+ JSON.stringify({
259
+ error: 'Stream initialization failed',
260
+ code: 'STREAM_ERROR'
261
+ }),
262
+ { status: 400 }
263
+ )
264
+ );
243
265
 
244
- await expect(client.testStreamRequest('/api/bad-stream'))
245
- .rejects.toThrow(SandboxError);
266
+ await expect(client.testStreamRequest('/api/bad-stream')).rejects.toThrow(
267
+ SandboxError
268
+ );
246
269
  });
247
270
 
248
271
  it('should handle missing stream body', async () => {
249
- mockFetch.mockResolvedValue(new Response(null, {
250
- status: 200,
251
- headers: { 'Content-Type': 'text/event-stream' }
252
- }));
272
+ mockFetch.mockResolvedValue(
273
+ new Response(null, {
274
+ status: 200,
275
+ headers: { 'Content-Type': 'text/event-stream' }
276
+ })
277
+ );
253
278
 
254
- await expect(client.testStreamRequest('/api/empty-stream'))
255
- .rejects.toThrow('No response body for streaming');
279
+ await expect(
280
+ client.testStreamRequest('/api/empty-stream')
281
+ ).rejects.toThrow('No response body for streaming');
256
282
  });
257
283
  });
258
284
 
259
285
  describe('stub integration', () => {
260
286
  it('should use stub when provided instead of fetch', async () => {
261
- const stubFetch = vi.fn().mockResolvedValue(new Response(
262
- JSON.stringify({ success: true, source: 'stub' }),
263
- { status: 200 }
264
- ));
287
+ const stubFetch = vi.fn().mockResolvedValue(
288
+ new Response(JSON.stringify({ success: true, source: 'stub' }), {
289
+ status: 200
290
+ })
291
+ );
265
292
 
266
293
  const stub = { containerFetch: stubFetch };
267
294
  const stubClient = new TestHttpClient({
268
295
  baseUrl: 'http://test.com',
269
296
  port: 3000,
270
- stub,
297
+ stub
271
298
  });
272
299
 
273
- const result = await stubClient.testRequest<TestSourceResponse>('/api/stub-test');
300
+ const result =
301
+ await stubClient.testRequest<TestSourceResponse>('/api/stub-test');
274
302
 
275
303
  expect(result.success).toBe(true);
276
304
  expect(result.source).toBe('stub');
@@ -283,16 +311,19 @@ describe('BaseHttpClient', () => {
283
311
  });
284
312
 
285
313
  it('should handle stub errors', async () => {
286
- const stubFetch = vi.fn().mockRejectedValue(new Error('Stub connection failed'));
314
+ const stubFetch = vi
315
+ .fn()
316
+ .mockRejectedValue(new Error('Stub connection failed'));
287
317
  const stub = { containerFetch: stubFetch };
288
318
  const stubClient = new TestHttpClient({
289
319
  baseUrl: 'http://test.com',
290
320
  port: 3000,
291
- stub,
321
+ stub
292
322
  });
293
323
 
294
- await expect(stubClient.testRequest('/api/stub-error'))
295
- .rejects.toThrow('Stub connection failed');
324
+ await expect(stubClient.testRequest('/api/stub-error')).rejects.toThrow(
325
+ 'Stub connection failed'
326
+ );
296
327
  });
297
328
  });
298
329
 
@@ -303,26 +334,31 @@ describe('BaseHttpClient', () => {
303
334
  { status: 202, shouldSucceed: true },
304
335
  { status: 409, shouldSucceed: false },
305
336
  { status: 422, shouldSucceed: false },
306
- { status: 429, shouldSucceed: false },
337
+ { status: 429, shouldSucceed: false }
307
338
  ];
308
339
 
309
340
  for (const test of unusualStatusTests) {
310
- mockFetch.mockResolvedValueOnce(new Response(
311
- test.shouldSucceed
312
- ? JSON.stringify({ success: true, status: test.status })
313
- : JSON.stringify({ error: `Status ${test.status}` }),
314
- { status: test.status }
315
- ));
341
+ mockFetch.mockResolvedValueOnce(
342
+ new Response(
343
+ test.shouldSucceed
344
+ ? JSON.stringify({ success: true, status: test.status })
345
+ : JSON.stringify({ error: `Status ${test.status}` }),
346
+ { status: test.status }
347
+ )
348
+ );
316
349
 
317
350
  if (test.shouldSucceed) {
318
- const result = await client.testRequest<TestStatusResponse>('/api/unusual-status');
351
+ const result = await client.testRequest<TestStatusResponse>(
352
+ '/api/unusual-status'
353
+ );
319
354
  expect(result.success).toBe(true);
320
355
  expect(result.status).toBe(test.status);
321
356
  } else {
322
- await expect(client.testRequest('/api/unusual-status'))
323
- .rejects.toThrow();
357
+ await expect(
358
+ client.testRequest('/api/unusual-status')
359
+ ).rejects.toThrow();
324
360
  }
325
361
  }
326
362
  });
327
363
  });
328
- });
364
+ });