@cloudflare/sandbox 0.2.0 → 0.2.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/Dockerfile +31 -7
  3. package/README.md +226 -2
  4. package/container_src/bun.lock +122 -0
  5. package/container_src/circuit-breaker.ts +121 -0
  6. package/container_src/index.ts +305 -10
  7. package/container_src/jupyter-server.ts +579 -0
  8. package/container_src/jupyter-service.ts +448 -0
  9. package/container_src/mime-processor.ts +255 -0
  10. package/container_src/package.json +9 -0
  11. package/container_src/startup.sh +83 -0
  12. package/dist/{chunk-YVZ3K26G.js → chunk-CUHYLCMT.js} +9 -21
  13. package/dist/chunk-CUHYLCMT.js.map +1 -0
  14. package/dist/chunk-EGC5IYXA.js +108 -0
  15. package/dist/chunk-EGC5IYXA.js.map +1 -0
  16. package/dist/chunk-FKBV7CZS.js +113 -0
  17. package/dist/chunk-FKBV7CZS.js.map +1 -0
  18. package/dist/chunk-LALY4SFU.js +129 -0
  19. package/dist/chunk-LALY4SFU.js.map +1 -0
  20. package/dist/{chunk-6THNBO4S.js → chunk-S5FFBU4Y.js} +1 -1
  21. package/dist/{chunk-6THNBO4S.js.map → chunk-S5FFBU4Y.js.map} +1 -1
  22. package/dist/chunk-VTKZL632.js +237 -0
  23. package/dist/chunk-VTKZL632.js.map +1 -0
  24. package/dist/{chunk-ZJN2PQOS.js → chunk-ZMPO44U4.js} +171 -72
  25. package/dist/chunk-ZMPO44U4.js.map +1 -0
  26. package/dist/{client-BXYlxy-j.d.ts → client-bzEV222a.d.ts} +52 -4
  27. package/dist/client.d.ts +2 -1
  28. package/dist/client.js +1 -1
  29. package/dist/errors.d.ts +95 -0
  30. package/dist/errors.js +27 -0
  31. package/dist/errors.js.map +1 -0
  32. package/dist/index.d.ts +3 -1
  33. package/dist/index.js +33 -3
  34. package/dist/interpreter-types.d.ts +259 -0
  35. package/dist/interpreter-types.js +9 -0
  36. package/dist/interpreter-types.js.map +1 -0
  37. package/dist/interpreter.d.ts +33 -0
  38. package/dist/interpreter.js +8 -0
  39. package/dist/interpreter.js.map +1 -0
  40. package/dist/jupyter-client.d.ts +4 -0
  41. package/dist/jupyter-client.js +9 -0
  42. package/dist/jupyter-client.js.map +1 -0
  43. package/dist/request-handler.d.ts +2 -1
  44. package/dist/request-handler.js +8 -3
  45. package/dist/sandbox.d.ts +2 -1
  46. package/dist/sandbox.js +8 -3
  47. package/dist/types.d.ts +8 -0
  48. package/dist/types.js +1 -1
  49. package/package.json +1 -1
  50. package/src/client.ts +37 -54
  51. package/src/errors.ts +218 -0
  52. package/src/index.ts +44 -10
  53. package/src/interpreter-types.ts +383 -0
  54. package/src/interpreter.ts +150 -0
  55. package/src/jupyter-client.ts +349 -0
  56. package/src/sandbox.ts +281 -153
  57. package/src/types.ts +15 -0
  58. package/dist/chunk-YVZ3K26G.js.map +0 -1
  59. package/dist/chunk-ZJN2PQOS.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @cloudflare/sandbox
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#51](https://github.com/cloudflare/sandbox-sdk/pull/51) [`4aceb32`](https://github.com/cloudflare/sandbox-sdk/commit/4aceb3215c836f59afcb88b2b325016b3f623f46) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Handle intermittent interpreter failures and decouple jupyter startup
8
+
9
+ ## 0.2.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#49](https://github.com/cloudflare/sandbox-sdk/pull/49) [`d81d2a5`](https://github.com/cloudflare/sandbox-sdk/commit/d81d2a563c9af8947d5444019ed4d6156db563e3) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Implement code interpreter API
14
+
3
15
  ## 0.2.0
4
16
 
5
17
  ### Minor Changes
package/Dockerfile CHANGED
@@ -31,6 +31,7 @@ RUN apt-get update && apt-get install -y \
31
31
  python3.11 \
32
32
  python3.11-dev \
33
33
  python3-pip \
34
+ python3.11-venv \
34
35
  # Other useful tools
35
36
  sudo \
36
37
  ca-certificates \
@@ -41,12 +42,12 @@ RUN apt-get update && apt-get install -y \
41
42
  # Set Python 3.11 as default python3
42
43
  RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1
43
44
 
44
- # Install Node.js 22 LTS
45
+ # Install Node.js 20 LTS
45
46
  # Using the official NodeSource repository setup script
46
47
  RUN apt-get update && apt-get install -y ca-certificates curl gnupg \
47
48
  && mkdir -p /etc/apt/keyrings \
48
49
  && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
49
- && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
50
+ && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
50
51
  && apt-get update \
51
52
  && apt-get install -y nodejs \
52
53
  && rm -rf /var/lib/apt/lists/*
@@ -55,6 +56,21 @@ RUN apt-get update && apt-get install -y ca-certificates curl gnupg \
55
56
  COPY --from=bun-source /usr/local/bin/bun /usr/local/bin/bun
56
57
  COPY --from=bun-source /usr/local/bin/bunx /usr/local/bin/bunx
57
58
 
59
+ # Install Jupyter and kernels
60
+ RUN pip3 install --no-cache-dir \
61
+ jupyter \
62
+ jupyterlab \
63
+ ipykernel \
64
+ notebook \
65
+ matplotlib \
66
+ numpy \
67
+ pandas \
68
+ seaborn \
69
+ && python3 -m ipykernel install --user --name python3
70
+
71
+ # Install JavaScript kernel (ijavascript) - using E2B's fork
72
+ RUN npm install -g --unsafe-perm git+https://github.com/e2b-dev/ijavascript.git \
73
+ && ijsinstall --install=global
58
74
 
59
75
  # Set up container server directory
60
76
  WORKDIR /container-server
@@ -63,16 +79,24 @@ WORKDIR /container-server
63
79
  RUN python3 --version && \
64
80
  node --version && \
65
81
  npm --version && \
66
- bun --version
82
+ bun --version && \
83
+ jupyter --version && \
84
+ jupyter kernelspec list
67
85
 
68
86
  # Copy container source files to server directory
87
+ COPY container_src/package.json ./
88
+ RUN bun install
89
+
69
90
  COPY container_src/ ./
70
91
 
71
92
  # Create clean workspace directory for users
72
93
  RUN mkdir -p /workspace
73
94
 
74
- # Expose the application port
75
- EXPOSE 3000
95
+ # Expose the application port (3000 for control, 8888 for Jupyter)
96
+ EXPOSE 3000 8888
97
+
98
+ # Make startup script executable
99
+ RUN chmod +x startup.sh
76
100
 
77
- # Run the application from container server directory
78
- CMD ["bun", "index.ts"]
101
+ # Use startup script
102
+ CMD ["./startup.sh"]
package/README.md CHANGED
@@ -17,6 +17,10 @@
17
17
  - [Basic Setup](#basic-setup)
18
18
  - [📚 API Reference](#api-reference)
19
19
  - [Core Methods](#core-methods)
20
+ - [🧪 Code Interpreter](#code-interpreter)
21
+ - [Code Execution](#code-execution)
22
+ - [Rich Outputs](#rich-outputs)
23
+ - [Output Formats](#output-formats)
20
24
  - [🌐 Port Forwarding](#port-forwarding)
21
25
  - [Utility Methods](#utility-methods)
22
26
  - [💡 Examples](#examples)
@@ -24,6 +28,7 @@
24
28
  - [Build and Test Code](#build-and-test-code)
25
29
  - [Interactive Development Environment](#interactive-development-environment)
26
30
  - [Expose Services with Preview URLs](#expose-services-with-preview-urls)
31
+ - [Data Analysis with Code Interpreter](#data-analysis-with-code-interpreter)
27
32
  - [🏗️ Architecture](#architecture)
28
33
  - [🛠️ Advanced Usage](#advanced-usage)
29
34
  - [AsyncIterable Streaming Support](#asynciterable-streaming-support)
@@ -50,6 +55,9 @@ The Cloudflare Sandbox SDK enables you to run isolated code environments directl
50
55
  - **🔄 Git Integration**: Clone repositories directly into sandboxes
51
56
  - **🚀 Streaming Support**: Real-time output streaming for long-running commands
52
57
  - **🎮 Session Management**: Maintain state across multiple operations
58
+ - **🧪 Code Interpreter**: Execute Python and JavaScript with rich outputs (charts, tables, formatted data)
59
+ - **📊 Multi-Language Support**: Persistent execution contexts for Python and JavaScript/TypeScript
60
+ - **🎨 Rich MIME Types**: Automatic processing of images, HTML, charts, and structured data
53
61
 
54
62
  <h2 id="quick-start">🚀 Quick Start</h2>
55
63
 
@@ -64,7 +72,7 @@ npm install @cloudflare/sandbox
64
72
  1. **Create a Dockerfile** (temporary requirement, will be removed in future releases):
65
73
 
66
74
  ```dockerfile
67
- FROM docker.io/cloudflare/sandbox:0.2.0
75
+ FROM docker.io/cloudflare/sandbox:0.2.2
68
76
 
69
77
  # Expose the ports you want to expose
70
78
  EXPOSE 3000
@@ -246,6 +254,123 @@ console.log(result.stdout); // "production"
246
254
  - `unexposePort(port)` - Remove port exposure
247
255
  - `getExposedPorts()` - List all exposed ports with their URLs
248
256
 
257
+ <h2 id="code-interpreter">🧪 Code Interpreter</h2>
258
+
259
+ The Sandbox SDK includes powerful code interpreter capabilities, allowing you to execute Python and JavaScript code with rich outputs including charts, tables, and formatted data.
260
+
261
+ ### Code Execution
262
+
263
+ #### `createCodeContext(options?)`
264
+
265
+ Creates a new code execution context with persistent state.
266
+
267
+ ```typescript
268
+ // Create a Python context
269
+ const pythonCtx = await sandbox.createCodeContext({ language: 'python' });
270
+
271
+ // Create a JavaScript context
272
+ const jsCtx = await sandbox.createCodeContext({ language: 'javascript' });
273
+ ```
274
+
275
+ **Options:**
276
+ - `language`: Programming language (`'python'` | `'javascript'` | `'typescript'`)
277
+ - `cwd`: Working directory (default: `/workspace`)
278
+ - `envVars`: Environment variables for the context
279
+
280
+ #### `runCode(code, options?)`
281
+
282
+ Executes code with optional streaming callbacks.
283
+
284
+ ```typescript
285
+ // Simple execution
286
+ const execution = await sandbox.runCode('print("Hello World")', {
287
+ context: pythonCtx
288
+ });
289
+
290
+ // With streaming callbacks
291
+ await sandbox.runCode(`
292
+ for i in range(5):
293
+ print(f"Step {i}")
294
+ time.sleep(1)
295
+ `, {
296
+ context: pythonCtx,
297
+ onStdout: (output) => console.log('Real-time:', output.text),
298
+ onResult: (result) => console.log('Result:', result)
299
+ });
300
+ ```
301
+
302
+ **Options:**
303
+ - `context`: Context to run the code in
304
+ - `language`: Language if no context provided
305
+ - `onStdout`: Callback for stdout output
306
+ - `onStderr`: Callback for stderr output
307
+ - `onResult`: Callback for execution results
308
+ - `onError`: Callback for errors
309
+
310
+ #### `runCodeStream(code, options?)`
311
+
312
+ Returns a streaming response for real-time processing.
313
+
314
+ ```typescript
315
+ const stream = await sandbox.runCodeStream('import time; [print(i) for i in range(10)]');
316
+ // Process the stream as needed
317
+ ```
318
+
319
+ ### Rich Outputs
320
+
321
+ The code interpreter automatically detects and processes various output types:
322
+
323
+ ```typescript
324
+ // Data visualization
325
+ const execution = await sandbox.runCode(`
326
+ import matplotlib.pyplot as plt
327
+ import numpy as np
328
+
329
+ x = np.linspace(0, 10, 100)
330
+ y = np.sin(x)
331
+ plt.plot(x, y)
332
+ plt.title('Sine Wave')
333
+ plt.show()
334
+ `, {
335
+ context: pythonCtx,
336
+ onResult: (result) => {
337
+ if (result.png) {
338
+ // Base64 encoded PNG image
339
+ console.log('Chart generated!');
340
+ }
341
+ }
342
+ });
343
+
344
+ // HTML tables with pandas
345
+ const tableExecution = await sandbox.runCode(`
346
+ import pandas as pd
347
+ df = pd.DataFrame({
348
+ 'name': ['Alice', 'Bob', 'Charlie'],
349
+ 'score': [92, 88, 95]
350
+ })
351
+ df
352
+ `, { context: pythonCtx });
353
+
354
+ // Access HTML table in execution.results[0].html
355
+ ```
356
+
357
+ ### Output Formats
358
+
359
+ Results can include multiple formats:
360
+ - `text`: Plain text representation
361
+ - `html`: HTML (often pandas DataFrames)
362
+ - `png`/`jpeg`: Base64 encoded images
363
+ - `svg`: Vector graphics
364
+ - `json`: Structured data
365
+ - `chart`: Parsed chart information
366
+
367
+ Check available formats with `result.formats()`.
368
+
369
+ #### Additional Code Interpreter Methods
370
+
371
+ - `listCodeContexts()` - List all active code contexts
372
+ - `deleteCodeContext(contextId)` - Delete a specific context
373
+
249
374
  <h2 id="port-forwarding">🌐 Port Forwarding</h2>
250
375
 
251
376
  The SDK automatically handles preview URL routing for exposed ports. Just add one line to your worker:
@@ -404,14 +529,100 @@ console.log(`Service available at: ${preview.url}`);
404
529
  // See the example in examples/basic/src/index.ts for the routing implementation.
405
530
  ```
406
531
 
532
+ ### Data Analysis with Code Interpreter
533
+
534
+ ```typescript
535
+ const sandbox = getSandbox(env.Sandbox, "analysis");
536
+
537
+ // Create a Python context for data analysis
538
+ const pythonCtx = await sandbox.createCodeContext({ language: 'python' });
539
+
540
+ // Load and analyze data
541
+ const analysis = await sandbox.runCode(`
542
+ import pandas as pd
543
+ import matplotlib.pyplot as plt
544
+
545
+ # Create sample data
546
+ data = {
547
+ 'Month': ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
548
+ 'Sales': [10000, 12000, 15000, 14000, 18000],
549
+ 'Profit': [2000, 2500, 3200, 2800, 4000]
550
+ }
551
+ df = pd.DataFrame(data)
552
+
553
+ # Display summary statistics
554
+ print("Sales Summary:")
555
+ print(df.describe())
556
+
557
+ # Create visualization
558
+ plt.figure(figsize=(10, 6))
559
+ plt.subplot(1, 2, 1)
560
+ plt.bar(df['Month'], df['Sales'])
561
+ plt.title('Monthly Sales')
562
+ plt.xlabel('Month')
563
+ plt.ylabel('Sales ($)')
564
+
565
+ plt.subplot(1, 2, 2)
566
+ plt.plot(df['Month'], df['Profit'], marker='o', color='green')
567
+ plt.title('Monthly Profit')
568
+ plt.xlabel('Month')
569
+ plt.ylabel('Profit ($)')
570
+
571
+ plt.tight_layout()
572
+ plt.show()
573
+
574
+ # Return the data as JSON
575
+ df.to_dict('records')
576
+ `, {
577
+ context: pythonCtx,
578
+ onResult: (result) => {
579
+ if (result.png) {
580
+ // Handle the chart image
581
+ console.log('Chart generated:', result.png.substring(0, 50) + '...');
582
+ }
583
+ if (result.json) {
584
+ // Handle the structured data
585
+ console.log('Data:', result.json);
586
+ }
587
+ }
588
+ });
589
+
590
+ // Multi-language workflow: Process in Python, analyze in JavaScript
591
+ await sandbox.runCode(`
592
+ # Save processed data
593
+ df.to_json('/tmp/sales_data.json', orient='records')
594
+ `, { context: pythonCtx });
595
+
596
+ const jsCtx = await sandbox.createCodeContext({ language: 'javascript' });
597
+ const jsAnalysis = await sandbox.runCode(`
598
+ const fs = require('fs');
599
+ const data = JSON.parse(fs.readFileSync('/tmp/sales_data.json', 'utf8'));
600
+
601
+ // Calculate growth rate
602
+ const growth = data.map((curr, idx) => {
603
+ if (idx === 0) return { ...curr, growth: 0 };
604
+ const prev = data[idx - 1];
605
+ return {
606
+ ...curr,
607
+ growth: ((curr.Sales - prev.Sales) / prev.Sales * 100).toFixed(2) + '%'
608
+ };
609
+ });
610
+
611
+ console.log('Growth Analysis:', growth);
612
+ growth;
613
+ `, { context: jsCtx });
614
+ ```
615
+
407
616
  <h2 id="architecture">🏗️ Architecture</h2>
408
617
 
409
618
  The SDK leverages Cloudflare's infrastructure:
410
619
 
411
620
  - **Durable Objects**: Manages sandbox lifecycle and state
412
- - **Containers**: Provides isolated execution environments
621
+ - **Containers**: Provides isolated execution environments with Jupyter kernels
413
622
  - **Workers**: Handles HTTP routing and API interface
414
623
  - **Edge Network**: Enables global distribution and low latency
624
+ - **Jupyter Integration**: Python (IPython) and JavaScript (TSLab) kernels for code execution
625
+ - **MIME Processing**: Automatic detection and handling of rich output formats
415
626
 
416
627
  <h2 id="advanced-usage">🛠️ Advanced Usage</h2>
417
628
 
@@ -521,6 +732,9 @@ sandbox.client.onCommandComplete = (success, code) =>
521
732
  - Maximum container runtime is limited by Durable Object constraints
522
733
  - WebSocket support for preview URLs coming soon
523
734
  - Some system calls may be restricted in the container environment
735
+ - Code interpreter has no internet access (sandbox restriction)
736
+ - Some Python/JavaScript packages may not be pre-installed
737
+ - Resource limits apply to code execution (CPU, memory)
524
738
 
525
739
  <h2 id="contributing">🤝 Contributing</h2>
526
740
 
@@ -534,11 +748,21 @@ cd sandbox-sdk
534
748
  # Install dependencies
535
749
  npm install
536
750
 
751
+ # Install Bun (if not already installed)
752
+ # Visit https://bun.sh for installation instructions
753
+ curl -fsSL https://bun.sh/install | bash
754
+
755
+ # Install container dependencies (required for TypeScript checking)
756
+ cd packages/sandbox/container_src && bun install && cd -
757
+
537
758
  # Run tests
538
759
  npm test
539
760
 
540
761
  # Build the project
541
762
  npm run build
763
+
764
+ # Run type checking and linting
765
+ npm run check
542
766
  ```
543
767
 
544
768
  <h2 id="license">📄 License</h2>
@@ -0,0 +1,122 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "workspaces": {
4
+ "": {
5
+ "name": "sandbox-server",
6
+ "dependencies": {
7
+ "@jupyterlab/services": "^7.0.0",
8
+ "uuid": "^9.0.1",
9
+ "ws": "^8.16.0",
10
+ },
11
+ "devDependencies": {
12
+ "@types/uuid": "^9.0.7",
13
+ "@types/ws": "^8.5.10",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@jupyter/ydoc": ["@jupyter/ydoc@3.1.0", "", { "dependencies": { "@jupyterlab/nbformat": "^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0", "@lumino/coreutils": "^1.11.0 || ^2.0.0", "@lumino/disposable": "^1.10.0 || ^2.0.0", "@lumino/signaling": "^1.10.0 || ^2.0.0", "y-protocols": "^1.0.5", "yjs": "^13.5.40" } }, "sha512-+hLNUBZr8Zz8NiuaoYKINDURDJHbjFGxg8EcSU52y+rBe412TsoFxfPSng4eP7w3cZFrVlm7D+8K0nAMHxj0ZQ=="],
19
+
20
+ "@jupyterlab/coreutils": ["@jupyterlab/coreutils@6.4.5", "", { "dependencies": { "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/signaling": "^2.1.4", "minimist": "~1.2.0", "path-browserify": "^1.0.0", "url-parse": "~1.5.4" } }, "sha512-fNEefnqTNP/3alrmGYDb0Mu1heS5Cu39FIDiDDoZuuWv4bsdE2xf7VaStuwjljsQm7VZwi5aeholi1+ciBkZKg=="],
21
+
22
+ "@jupyterlab/nbformat": ["@jupyterlab/nbformat@4.4.5", "", { "dependencies": { "@lumino/coreutils": "^2.2.1" } }, "sha512-VOlm1klsb1LUNp47AL8iDZPP71pXu9a+WqukJFuuWVxg//+iR6wdqTYcunE9Lx/7SZDqMsXGD+doBZC36Lzenw=="],
23
+
24
+ "@jupyterlab/services": ["@jupyterlab/services@7.4.5", "", { "dependencies": { "@jupyter/ydoc": "^3.0.4", "@jupyterlab/coreutils": "^6.4.5", "@jupyterlab/nbformat": "^4.4.5", "@jupyterlab/settingregistry": "^4.4.5", "@jupyterlab/statedb": "^4.4.5", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/polling": "^2.1.4", "@lumino/properties": "^2.0.3", "@lumino/signaling": "^2.1.4", "ws": "^8.11.0" } }, "sha512-JEr8+4VS2MqFyYHh4AnpK/g5V0qRBpmnfXmU0p9YFjcIgGR365D8JF0880IJ0e8+jaW88gSAvZFB8x3o9xRoAQ=="],
25
+
26
+ "@jupyterlab/settingregistry": ["@jupyterlab/settingregistry@4.4.5", "", { "dependencies": { "@jupyterlab/nbformat": "^4.4.5", "@jupyterlab/statedb": "^4.4.5", "@lumino/commands": "^2.3.2", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/signaling": "^2.1.4", "@rjsf/utils": "^5.13.4", "ajv": "^8.12.0", "json5": "^2.2.3" }, "peerDependencies": { "react": ">=16" } }, "sha512-6hEBq3qI99VZwO97W0AM0mP1is57bWC8Vk2xudic1a90wA9G6ovUBzayqSMm/6QhTuIbCY+vruznwhhsLrVMeg=="],
27
+
28
+ "@jupyterlab/statedb": ["@jupyterlab/statedb@4.4.5", "", { "dependencies": { "@lumino/commands": "^2.3.2", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/properties": "^2.0.3", "@lumino/signaling": "^2.1.4" } }, "sha512-UpqhOujKwoWoxtNBL2Qk0nrw+ORLJ3ckwiJg2eA0CI+n5kO3IBYAnPzak0tSS0mQNspayXr3KAm4GQ80op5yuA=="],
29
+
30
+ "@lumino/algorithm": ["@lumino/algorithm@2.0.3", "", {}, "sha512-DIcF7cIrGEC1Wh8DNjdwaL7IdcNs4Jj1VjO/90oHefeQPszKgc6DSfCxvbQiRanKR6tl/JL7tq4ZRPZES2oVAA=="],
31
+
32
+ "@lumino/commands": ["@lumino/commands@2.3.2", "", { "dependencies": { "@lumino/algorithm": "^2.0.3", "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/domutils": "^2.0.3", "@lumino/keyboard": "^2.0.3", "@lumino/signaling": "^2.1.4", "@lumino/virtualdom": "^2.0.3" } }, "sha512-aAFEiUpp2hrkQU82Z85w1L80g0iDzsQRncLBa+pqVR/k0k1lz6H9F6xZ1ff+lBumZKKtsxBxNomvd0hfxLLqGw=="],
33
+
34
+ "@lumino/coreutils": ["@lumino/coreutils@2.2.1", "", { "dependencies": { "@lumino/algorithm": "^2.0.3" } }, "sha512-yij4TnxDIum7xfFUsVvZB0oLv4shs2mNbn3juwtEIsruvVBPmurNzKX0Y8z2QetbP2AZ6MSFtBzEKsihf0H0VA=="],
35
+
36
+ "@lumino/disposable": ["@lumino/disposable@2.1.4", "", { "dependencies": { "@lumino/signaling": "^2.1.4" } }, "sha512-qTJiDbglPE2QnG4x4gtBcRbcfKQibxyyinNGKcNDrcK2TGTbbhK5PpMQ8d70l2V2Xw2pb/LfksBAg5pxkJ/G4A=="],
37
+
38
+ "@lumino/domutils": ["@lumino/domutils@2.0.3", "", {}, "sha512-bXAbZg3mf2ZDNdBBpCGDike3U+osRGHePTh8H2Ud2KwaN4g/5IryFJm/TiO4K5IYs91bWF2Zqhf3FsdbZKHlGw=="],
39
+
40
+ "@lumino/keyboard": ["@lumino/keyboard@2.0.3", "", {}, "sha512-bU2OxAR8a9eNBdV0YFjU6/lVVpbOw1gM7yHOuDGDdNu4J0UpKapFoR9gopNGSaHTmTwDtx9RHdFfIAgHwjZ+VQ=="],
41
+
42
+ "@lumino/polling": ["@lumino/polling@2.1.4", "", { "dependencies": { "@lumino/coreutils": "^2.2.1", "@lumino/disposable": "^2.1.4", "@lumino/signaling": "^2.1.4" } }, "sha512-gSkxlIJ/4/esY2G7bsRrY9A4KimDMHTo0shaD+MCbhd09fZMCWJoDMcA447/dykB1rM5NXgugNLjpdGGL/e8cw=="],
43
+
44
+ "@lumino/properties": ["@lumino/properties@2.0.3", "", {}, "sha512-zkXIU5uYz/ScHCHGl5Bt4gMYsfPxZEduZd80zqDslBWvTIMro3NnzLe66NMnecbdr5N3hDJagYyA8//Qy3XjiA=="],
45
+
46
+ "@lumino/signaling": ["@lumino/signaling@2.1.4", "", { "dependencies": { "@lumino/algorithm": "^2.0.3", "@lumino/coreutils": "^2.2.1" } }, "sha512-nC5Z6d9om369Jkh1Vp3b7C89hV4cjr1fQDVcxhemyKXwc9r6VW7FpKixC+jElcAknP5KLj1FAa8Np+K06mMkEA=="],
47
+
48
+ "@lumino/virtualdom": ["@lumino/virtualdom@2.0.3", "", { "dependencies": { "@lumino/algorithm": "^2.0.3" } }, "sha512-q2C8eBxPvvOOQjN3KuxZ+vJi082JH/GF7KwMdaWsy5g+7wjKdnXPuLQFTBLOrVqIzmbxBDlLeFr93CEhdQXcyQ=="],
49
+
50
+ "@rjsf/utils": ["@rjsf/utils@5.24.12", "", { "dependencies": { "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "react-is": "^18.2.0" }, "peerDependencies": { "react": "^16.14.0 || >=17" } }, "sha512-fDwQB0XkjZjpdFUz5UAnuZj8nnbxDbX5tp+jTOjjJKw2TMQ9gFFYCQ12lSpdhezA2YgEGZfxyYTGW0DKDL5Drg=="],
51
+
52
+ "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
53
+
54
+ "@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
55
+
56
+ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
57
+
58
+ "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
59
+
60
+ "compute-gcd": ["compute-gcd@1.2.1", "", { "dependencies": { "validate.io-array": "^1.0.3", "validate.io-function": "^1.0.2", "validate.io-integer-array": "^1.0.0" } }, "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg=="],
61
+
62
+ "compute-lcm": ["compute-lcm@1.1.2", "", { "dependencies": { "compute-gcd": "^1.2.1", "validate.io-array": "^1.0.3", "validate.io-function": "^1.0.2", "validate.io-integer-array": "^1.0.0" } }, "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ=="],
63
+
64
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
65
+
66
+ "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="],
67
+
68
+ "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="],
69
+
70
+ "json-schema-compare": ["json-schema-compare@0.2.2", "", { "dependencies": { "lodash": "^4.17.4" } }, "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ=="],
71
+
72
+ "json-schema-merge-allof": ["json-schema-merge-allof@0.8.1", "", { "dependencies": { "compute-lcm": "^1.1.2", "json-schema-compare": "^0.2.2", "lodash": "^4.17.20" } }, "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w=="],
73
+
74
+ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
75
+
76
+ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
77
+
78
+ "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
79
+
80
+ "lib0": ["lib0@0.2.114", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0gentesthtml": "bin/gentesthtml.js", "0serve": "bin/0serve.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ=="],
81
+
82
+ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
83
+
84
+ "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
85
+
86
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
87
+
88
+ "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
89
+
90
+ "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="],
91
+
92
+ "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
93
+
94
+ "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
95
+
96
+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
97
+
98
+ "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
99
+
100
+ "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
101
+
102
+ "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
103
+
104
+ "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
105
+
106
+ "validate.io-array": ["validate.io-array@1.0.6", "", {}, "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg=="],
107
+
108
+ "validate.io-function": ["validate.io-function@1.0.2", "", {}, "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ=="],
109
+
110
+ "validate.io-integer": ["validate.io-integer@1.0.5", "", { "dependencies": { "validate.io-number": "^1.0.3" } }, "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ=="],
111
+
112
+ "validate.io-integer-array": ["validate.io-integer-array@1.0.0", "", { "dependencies": { "validate.io-array": "^1.0.3", "validate.io-integer": "^1.0.4" } }, "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA=="],
113
+
114
+ "validate.io-number": ["validate.io-number@1.0.3", "", {}, "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg=="],
115
+
116
+ "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
117
+
118
+ "y-protocols": ["y-protocols@1.0.6", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q=="],
119
+
120
+ "yjs": ["yjs@13.6.27", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw=="],
121
+ }
122
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Circuit Breaker implementation to prevent cascading failures
3
+ */
4
+ export class CircuitBreaker {
5
+ private failures = 0;
6
+ private lastFailure: number = 0;
7
+ private successCount = 0;
8
+ private state: "closed" | "open" | "half-open" = "closed";
9
+
10
+ // Configuration
11
+ private readonly threshold: number;
12
+ private readonly timeout: number;
13
+ private readonly halfOpenSuccessThreshold: number;
14
+ private readonly name: string;
15
+
16
+ constructor(options: {
17
+ name: string;
18
+ threshold?: number;
19
+ timeout?: number;
20
+ halfOpenSuccessThreshold?: number;
21
+ }) {
22
+ this.name = options.name;
23
+ this.threshold = options.threshold || 5;
24
+ this.timeout = options.timeout || 30000; // 30 seconds
25
+ this.halfOpenSuccessThreshold = options.halfOpenSuccessThreshold || 3;
26
+ }
27
+
28
+ /**
29
+ * Execute an operation with circuit breaker protection
30
+ */
31
+ async execute<T>(operation: () => Promise<T>): Promise<T> {
32
+ // Check circuit state
33
+ if (this.state === "open") {
34
+ if (Date.now() - this.lastFailure > this.timeout) {
35
+ console.log(
36
+ `[CircuitBreaker ${this.name}] Transitioning from open to half-open`
37
+ );
38
+ this.state = "half-open";
39
+ this.successCount = 0;
40
+ } else {
41
+ throw new Error(
42
+ `Circuit breaker is open for ${this.name}. Retry after ${
43
+ this.timeout - (Date.now() - this.lastFailure)
44
+ }ms`
45
+ );
46
+ }
47
+ }
48
+
49
+ try {
50
+ const result = await operation();
51
+
52
+ // Record success
53
+ if (this.state === "half-open") {
54
+ this.successCount++;
55
+ if (this.successCount >= this.halfOpenSuccessThreshold) {
56
+ console.log(
57
+ `[CircuitBreaker ${this.name}] Transitioning from half-open to closed`
58
+ );
59
+ this.state = "closed";
60
+ this.failures = 0;
61
+ }
62
+ } else if (this.state === "closed") {
63
+ // Reset failure count on success
64
+ this.failures = 0;
65
+ }
66
+
67
+ return result;
68
+ } catch (error) {
69
+ this.recordFailure();
70
+ throw error;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Record a failure and update circuit state
76
+ */
77
+ private recordFailure() {
78
+ this.failures++;
79
+ this.lastFailure = Date.now();
80
+
81
+ if (this.state === "half-open") {
82
+ console.log(
83
+ `[CircuitBreaker ${this.name}] Failure in half-open state, transitioning to open`
84
+ );
85
+ this.state = "open";
86
+ } else if (this.failures >= this.threshold) {
87
+ console.log(
88
+ `[CircuitBreaker ${this.name}] Threshold reached (${this.failures}/${this.threshold}), transitioning to open`
89
+ );
90
+ this.state = "open";
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Get current circuit breaker state
96
+ */
97
+ getState(): {
98
+ state: string;
99
+ failures: number;
100
+ lastFailure: number;
101
+ isOpen: boolean;
102
+ } {
103
+ return {
104
+ state: this.state,
105
+ failures: this.failures,
106
+ lastFailure: this.lastFailure,
107
+ isOpen: this.state === "open",
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Reset the circuit breaker
113
+ */
114
+ reset() {
115
+ this.state = "closed";
116
+ this.failures = 0;
117
+ this.successCount = 0;
118
+ this.lastFailure = 0;
119
+ console.log(`[CircuitBreaker ${this.name}] Reset to closed state`);
120
+ }
121
+ }