@liquidmetal-ai/precip 1.0.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/.prettierrc +9 -0
- package/CHANGELOG.md +8 -0
- package/eslint.config.mjs +28 -0
- package/package.json +53 -0
- package/src/engine/agent.ts +478 -0
- package/src/engine/llm-provider.test.ts +275 -0
- package/src/engine/llm-provider.ts +330 -0
- package/src/engine/stream-parser.ts +170 -0
- package/src/index.ts +142 -0
- package/src/mounts/mount-manager.test.ts +516 -0
- package/src/mounts/mount-manager.ts +327 -0
- package/src/mounts/mount-registry.ts +196 -0
- package/src/mounts/zod-to-string.test.ts +154 -0
- package/src/mounts/zod-to-string.ts +213 -0
- package/src/presets/agent-tools.ts +57 -0
- package/src/presets/index.ts +5 -0
- package/src/sandbox/README.md +1321 -0
- package/src/sandbox/bridges/README.md +571 -0
- package/src/sandbox/bridges/actor.test.ts +229 -0
- package/src/sandbox/bridges/actor.ts +195 -0
- package/src/sandbox/bridges/bridge-fixes.test.ts +614 -0
- package/src/sandbox/bridges/bucket.test.ts +300 -0
- package/src/sandbox/bridges/cleanup-reproduction.test.ts +225 -0
- package/src/sandbox/bridges/console-multiple.test.ts +187 -0
- package/src/sandbox/bridges/console.test.ts +157 -0
- package/src/sandbox/bridges/console.ts +122 -0
- package/src/sandbox/bridges/fetch.ts +93 -0
- package/src/sandbox/bridges/index.ts +78 -0
- package/src/sandbox/bridges/readable-stream.ts +323 -0
- package/src/sandbox/bridges/response.test.ts +154 -0
- package/src/sandbox/bridges/response.ts +123 -0
- package/src/sandbox/bridges/review-fixes.test.ts +331 -0
- package/src/sandbox/bridges/search.test.ts +475 -0
- package/src/sandbox/bridges/search.ts +264 -0
- package/src/sandbox/bridges/shared/body-methods.ts +93 -0
- package/src/sandbox/bridges/shared/cleanup.ts +112 -0
- package/src/sandbox/bridges/shared/convert.ts +76 -0
- package/src/sandbox/bridges/shared/headers.ts +181 -0
- package/src/sandbox/bridges/shared/index.ts +36 -0
- package/src/sandbox/bridges/shared/json-helpers.ts +77 -0
- package/src/sandbox/bridges/shared/path-parser.ts +109 -0
- package/src/sandbox/bridges/shared/promise-helper.ts +108 -0
- package/src/sandbox/bridges/shared/registry-setup.ts +84 -0
- package/src/sandbox/bridges/shared/response-object.ts +280 -0
- package/src/sandbox/bridges/shared/result-builder.ts +130 -0
- package/src/sandbox/bridges/shared/scope-helpers.ts +44 -0
- package/src/sandbox/bridges/shared/stream-reader.ts +90 -0
- package/src/sandbox/bridges/storage-bridge.test.ts +893 -0
- package/src/sandbox/bridges/storage.ts +421 -0
- package/src/sandbox/bridges/text-decoder.ts +190 -0
- package/src/sandbox/bridges/text-encoder.ts +102 -0
- package/src/sandbox/bridges/types.ts +39 -0
- package/src/sandbox/bridges/utils.ts +123 -0
- package/src/sandbox/index.ts +6 -0
- package/src/sandbox/quickjs-wasm.d.ts +9 -0
- package/src/sandbox/sandbox.test.ts +191 -0
- package/src/sandbox/sandbox.ts +831 -0
- package/src/sandbox/test-helper.ts +43 -0
- package/src/sandbox/test-mocks.ts +154 -0
- package/src/sandbox/user-stream.test.ts +77 -0
- package/src/skills/frontmatter.test.ts +305 -0
- package/src/skills/frontmatter.ts +200 -0
- package/src/skills/index.ts +9 -0
- package/src/skills/skills-loader.test.ts +237 -0
- package/src/skills/skills-loader.ts +200 -0
- package/src/tools/actor-storage-tools.ts +250 -0
- package/src/tools/code-tools.test.ts +199 -0
- package/src/tools/code-tools.ts +444 -0
- package/src/tools/file-tools.ts +206 -0
- package/src/tools/registry.ts +125 -0
- package/src/tools/script-tools.ts +145 -0
- package/src/tools/smartbucket-tools.ts +203 -0
- package/src/tools/sql-tools.ts +213 -0
- package/src/tools/tool-factory.ts +119 -0
- package/src/types.ts +512 -0
- package/tsconfig.eslint.json +5 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +33 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
# QuickJS Bridge System
|
|
2
|
+
|
|
3
|
+
A modular system for bridging runtime APIs into QuickJS sandbox environments.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The bridge system provides a clean, reusable architecture for exposing host runtime APIs to sandboxed code. Each bridge is self-contained and follows consistent patterns for:
|
|
8
|
+
|
|
9
|
+
- Creating async functions that return promises
|
|
10
|
+
- Managing state across calls
|
|
11
|
+
- Converting between JavaScript and QuickJS types
|
|
12
|
+
- Tracking promises for proper cleanup
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/sandbox/bridges/
|
|
18
|
+
├── types.ts # TypeScript types for bridge system
|
|
19
|
+
├── utils.ts # Reusable bridge utilities
|
|
20
|
+
├── index.ts # Main entry point, exports all bridges
|
|
21
|
+
├── fetch.ts # Fetch API bridge (Response, Headers)
|
|
22
|
+
├── response.ts # Response constructor bridge
|
|
23
|
+
├── console.ts # Console API bridge (console.log, warn, error)
|
|
24
|
+
├── text-encoder.ts # TextEncoder bridge
|
|
25
|
+
├── text-decoder.ts # TextDecoder bridge (with stateful instances)
|
|
26
|
+
└── readable-stream.ts # ReadableStream and Reader bridges
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Core Utilities
|
|
30
|
+
|
|
31
|
+
### BridgeContext
|
|
32
|
+
|
|
33
|
+
All bridges receive a `BridgeContext` object:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
interface BridgeContext {
|
|
37
|
+
context: QuickJSContext; // QuickJS context
|
|
38
|
+
runtime: any; // QuickJS runtime
|
|
39
|
+
tracker: PromiseTracker; // Promise tracker for async ops
|
|
40
|
+
logger?: Logger; // Optional logger
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Utility Functions
|
|
45
|
+
|
|
46
|
+
#### `convertToHandle(context, value)`
|
|
47
|
+
|
|
48
|
+
Converts JavaScript values to QuickJS handles:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const handle = convertToHandle(context, { foo: 'bar' });
|
|
52
|
+
context.setProp(objectHandle, 'data', handle);
|
|
53
|
+
handle.dispose();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### `createAsyncBridge(ctx, name, hostFn)`
|
|
57
|
+
|
|
58
|
+
Creates an async function that bridges to a host function:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const readFileHandle = createAsyncBridge(ctx, 'readFile', async path => {
|
|
62
|
+
return await fs.readFile(path, 'utf-8');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
context.setProp(context.global, 'readFile', readFileHandle);
|
|
66
|
+
readFileHandle.dispose();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### `createSyncBridge(ctx, name, hostFn)`
|
|
70
|
+
|
|
71
|
+
Creates a synchronous function bridge:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const mathAbsHandle = createSyncBridge(ctx, 'abs', x => Math.abs(x));
|
|
75
|
+
context.setProp(mathObj, 'abs', mathAbsHandle);
|
|
76
|
+
mathAbsHandle.dispose();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### `defineClass(ctx, classCode, className)`
|
|
80
|
+
|
|
81
|
+
Defines an ES6 class in the sandbox using `evalCode`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const classHandle = defineClass(
|
|
85
|
+
ctx,
|
|
86
|
+
`
|
|
87
|
+
class MyClass {
|
|
88
|
+
constructor(value) {
|
|
89
|
+
this.value = value;
|
|
90
|
+
}
|
|
91
|
+
getValue() {
|
|
92
|
+
return this.value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
'MyClass'
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (classHandle) {
|
|
100
|
+
context.setProp(context.global, 'MyClass', classHandle);
|
|
101
|
+
classHandle.dispose();
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### `setProperties(context, handle, props)`
|
|
106
|
+
|
|
107
|
+
Sets multiple properties at once:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
setProperties(context, responseHandle, {
|
|
111
|
+
status: 200,
|
|
112
|
+
ok: true,
|
|
113
|
+
statusText: 'OK'
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `addMethod(context, objHandle, methodName, methodHandle)`
|
|
118
|
+
|
|
119
|
+
Adds a method to an object and disposes the method handle:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const getMethod = context.newFunction('get', nameHandle => {
|
|
123
|
+
const name = context.dump(nameHandle);
|
|
124
|
+
return context.newString(getValue(name));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
addMethod(context, headersHandle, 'get', getMethod);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Creating a New Bridge
|
|
131
|
+
|
|
132
|
+
### Example: Date API Bridge
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// src/sandbox/bridges/date.ts
|
|
136
|
+
import type { BridgeContext } from './types.js';
|
|
137
|
+
import { createSyncBridge, createAsyncBridge } from './utils.js';
|
|
138
|
+
|
|
139
|
+
export function installDate(ctx: BridgeContext): void {
|
|
140
|
+
const { context, logger } = ctx;
|
|
141
|
+
|
|
142
|
+
// Simple sync function
|
|
143
|
+
const nowHandle = createSyncBridge(ctx, 'now', () => Date.now());
|
|
144
|
+
context.setProp(context.global, 'dateNow', nowHandle);
|
|
145
|
+
nowHandle.dispose();
|
|
146
|
+
|
|
147
|
+
// Async function with delay
|
|
148
|
+
const delayHandle = createAsyncBridge(ctx, 'delay', async (ms: number) => {
|
|
149
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
150
|
+
return `Delayed ${ms}ms`;
|
|
151
|
+
});
|
|
152
|
+
context.setProp(context.global, 'delay', delayHandle);
|
|
153
|
+
delayHandle.dispose();
|
|
154
|
+
|
|
155
|
+
logger?.info?.('[Bridge] Date API installed');
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Example: ES6 Class Bridge
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// src/sandbox/bridges/timer.ts
|
|
163
|
+
import type { BridgeContext } from './types.js';
|
|
164
|
+
import { defineClass } from './utils.js';
|
|
165
|
+
|
|
166
|
+
export function installTimer(ctx: BridgeContext): void {
|
|
167
|
+
const { context, logger } = ctx;
|
|
168
|
+
|
|
169
|
+
// Track timer instances
|
|
170
|
+
const timers = new Map<number, any>();
|
|
171
|
+
let timerIdCounter = 0;
|
|
172
|
+
|
|
173
|
+
// Host function to start timer
|
|
174
|
+
const startTimerHandle = context.newFunction('__hostStartTimer', msHandle => {
|
|
175
|
+
const ms = context.dump(msHandle);
|
|
176
|
+
const timerId = ++timerIdCounter;
|
|
177
|
+
|
|
178
|
+
const handle = setTimeout(() => {
|
|
179
|
+
timers.delete(timerId);
|
|
180
|
+
}, ms);
|
|
181
|
+
|
|
182
|
+
timers.set(timerId, handle);
|
|
183
|
+
return context.newNumber(timerId);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
context.setProp(context.global, '__hostStartTimer', startTimerHandle);
|
|
187
|
+
startTimerHandle.dispose();
|
|
188
|
+
|
|
189
|
+
// Define Timer class
|
|
190
|
+
const classHandle = defineClass(
|
|
191
|
+
ctx,
|
|
192
|
+
`
|
|
193
|
+
class Timer {
|
|
194
|
+
constructor(ms) {
|
|
195
|
+
this.id = __hostStartTimer(ms);
|
|
196
|
+
this.ms = ms;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getId() {
|
|
200
|
+
return this.id;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
`,
|
|
204
|
+
'Timer'
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (classHandle) {
|
|
208
|
+
context.setProp(context.global, 'Timer', classHandle);
|
|
209
|
+
classHandle.dispose();
|
|
210
|
+
logger?.info?.('[Bridge] Timer installed');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Register in `bridges/index.ts`
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
export { installDate } from './date.js';
|
|
219
|
+
export { installTimer } from './timer.js';
|
|
220
|
+
|
|
221
|
+
// Add to installAllBridges
|
|
222
|
+
export function installAllBridges(ctx: BridgeContext): void {
|
|
223
|
+
installFetch(ctx);
|
|
224
|
+
installResponse(ctx);
|
|
225
|
+
installTextEncoder(ctx);
|
|
226
|
+
installTextDecoder(ctx);
|
|
227
|
+
installDate(ctx); // Add your bridge
|
|
228
|
+
installTimer(ctx); // Add your bridge
|
|
229
|
+
|
|
230
|
+
ctx.logger?.info?.('[Bridge] All bridges installed');
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Existing Bridges
|
|
235
|
+
|
|
236
|
+
### fetch (fetch.ts)
|
|
237
|
+
|
|
238
|
+
Bridges runtime's native `fetch` API with:
|
|
239
|
+
|
|
240
|
+
- Full Response object (status, headers, body)
|
|
241
|
+
- Headers object with `get()`, `has()`, `entries()` methods
|
|
242
|
+
- Body methods: `json()`, `text()`, `arrayBuffer()`
|
|
243
|
+
- ReadableStream support for response.body
|
|
244
|
+
|
|
245
|
+
**Usage in sandbox:**
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
const res = await fetch('https://api.example.com/data');
|
|
249
|
+
const data = await res.json();
|
|
250
|
+
const contentType = res.headers.get('content-type');
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Response (response.ts)
|
|
254
|
+
|
|
255
|
+
Provides `Response` constructor for creating Response objects manually with:
|
|
256
|
+
|
|
257
|
+
- Same interface as browser Response API
|
|
258
|
+
- Accepts body (string, ReadableStream, Uint8Array) and init options
|
|
259
|
+
- Convenience methods for consuming ReadableStreams: `text()`, `json()`, `arrayBuffer()`
|
|
260
|
+
- Useful for wrapping streams, testing, and creating mock responses
|
|
261
|
+
|
|
262
|
+
**Usage in sandbox:**
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
// Wrap a ReadableStream for easy consumption
|
|
266
|
+
const stream = someAsyncFunction(); // Returns a ReadableStream
|
|
267
|
+
const response = new Response(stream);
|
|
268
|
+
const text = await response.text();
|
|
269
|
+
|
|
270
|
+
// Create mock response for testing
|
|
271
|
+
const mockResponse = new Response('Hello World', {
|
|
272
|
+
status: 200,
|
|
273
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
274
|
+
});
|
|
275
|
+
const data = await mockResponse.text();
|
|
276
|
+
|
|
277
|
+
// Create error response
|
|
278
|
+
const errorResponse = new Response('Not found', {
|
|
279
|
+
status: 404,
|
|
280
|
+
statusText: 'Not Found'
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Console (console.ts)
|
|
285
|
+
|
|
286
|
+
Bridges runtime's `console` API with:
|
|
287
|
+
|
|
288
|
+
- `console.log()` - Standard logging (shown in stdout)
|
|
289
|
+
- `console.warn()` - Warning messages (prefixed with [WARN])
|
|
290
|
+
- `console.error()` - Error messages (prefixed with [ERROR])
|
|
291
|
+
- Output is captured and returned with sandbox execution result
|
|
292
|
+
- Multiple arguments are formatted and joined
|
|
293
|
+
|
|
294
|
+
**Usage in sandbox:**
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
console.log('Processing data...');
|
|
298
|
+
const data = await fetch('https://api.example.com/data');
|
|
299
|
+
console.log('Fetched items:', data.length);
|
|
300
|
+
console.warn('High memory usage');
|
|
301
|
+
console.error('Failed to parse:', error);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Output format:**
|
|
305
|
+
When using the `run_code` tool, console output is formatted with return value:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
[stdout]
|
|
309
|
+
Processing data...
|
|
310
|
+
Fetched items: 42
|
|
311
|
+
|
|
312
|
+
[result]
|
|
313
|
+
{ success: true, ... }
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### TextEncoder (text-encoder.ts)
|
|
317
|
+
|
|
318
|
+
Bridges runtime's `TextEncoder` for encoding strings to Uint8Array.
|
|
319
|
+
|
|
320
|
+
**Usage in sandbox:**
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
const encoder = new TextEncoder();
|
|
324
|
+
const bytes = encoder.encode('Hello, World!');
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### TextDecoder (text-decoder.ts)
|
|
328
|
+
|
|
329
|
+
Bridges runtime's `TextDecoder` with stateful instance tracking for streaming decode.
|
|
330
|
+
|
|
331
|
+
**Usage in sandbox:**
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const decoder = new TextDecoder();
|
|
335
|
+
|
|
336
|
+
// Streaming decode
|
|
337
|
+
const text1 = decoder.decode(chunk1, { stream: true });
|
|
338
|
+
const text2 = decoder.decode(chunk2, { stream: true });
|
|
339
|
+
const text3 = decoder.decode(); // Final flush
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### ReadableStream (readable-stream.ts)
|
|
343
|
+
|
|
344
|
+
Bridges runtime's `ReadableStream` for response.body streaming.
|
|
345
|
+
|
|
346
|
+
**Usage in sandbox:**
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
const reader = response.body.getReader();
|
|
350
|
+
while (true) {
|
|
351
|
+
const { done, value } = await reader.read();
|
|
352
|
+
if (done) break;
|
|
353
|
+
// Process chunk (Uint8Array)
|
|
354
|
+
}
|
|
355
|
+
reader.releaseLock();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Benefits
|
|
359
|
+
|
|
360
|
+
### 1. Modularity
|
|
361
|
+
|
|
362
|
+
Each bridge is self-contained in its own file, making it easy to:
|
|
363
|
+
|
|
364
|
+
- Understand what it does
|
|
365
|
+
- Test in isolation
|
|
366
|
+
- Modify without affecting others
|
|
367
|
+
- Remove if not needed
|
|
368
|
+
|
|
369
|
+
### 2. Reusability
|
|
370
|
+
|
|
371
|
+
Common patterns are extracted to `utils.ts`:
|
|
372
|
+
|
|
373
|
+
- `createAsyncBridge` - Standard async function pattern
|
|
374
|
+
- `createSyncBridge` - Standard sync function pattern
|
|
375
|
+
- `defineClass` - ES6 class definition pattern
|
|
376
|
+
- `convertToHandle` - Type conversion pattern
|
|
377
|
+
|
|
378
|
+
### 3. Consistency
|
|
379
|
+
|
|
380
|
+
All bridges follow the same structure:
|
|
381
|
+
|
|
382
|
+
1. Receive `BridgeContext`
|
|
383
|
+
2. Create host functions if needed
|
|
384
|
+
3. Define sandbox API
|
|
385
|
+
4. Log installation
|
|
386
|
+
|
|
387
|
+
### 4. Extensibility
|
|
388
|
+
|
|
389
|
+
Adding new bridges is straightforward:
|
|
390
|
+
|
|
391
|
+
1. Create new file in `bridges/`
|
|
392
|
+
2. Export installation function
|
|
393
|
+
3. Register in `bridges/index.ts`
|
|
394
|
+
4. Document in this file
|
|
395
|
+
|
|
396
|
+
### 5. Maintainability
|
|
397
|
+
|
|
398
|
+
Before: 885 lines in one function
|
|
399
|
+
After: 450 lines in sandbox.ts + focused bridge modules
|
|
400
|
+
|
|
401
|
+
## Common Patterns
|
|
402
|
+
|
|
403
|
+
### Pattern 1: Simple Sync Function
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
const fnHandle = createSyncBridge(ctx, 'funcName', (arg1, arg2) => {
|
|
407
|
+
return result;
|
|
408
|
+
});
|
|
409
|
+
context.setProp(context.global, 'funcName', fnHandle);
|
|
410
|
+
fnHandle.dispose();
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Pattern 2: Async Function with Promise Tracking
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
const fnHandle = createAsyncBridge(ctx, 'asyncFunc', async arg => {
|
|
417
|
+
const result = await someAsyncOperation(arg);
|
|
418
|
+
return result;
|
|
419
|
+
});
|
|
420
|
+
context.setProp(context.global, 'asyncFunc', fnHandle);
|
|
421
|
+
fnHandle.dispose();
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Pattern 3: Stateful Instance Management
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
const instances = new Map<number, InstanceType>();
|
|
428
|
+
let idCounter = 0;
|
|
429
|
+
|
|
430
|
+
const createHandle = context.newFunction('__hostCreate', () => {
|
|
431
|
+
const instance = new InstanceType();
|
|
432
|
+
const id = ++idCounter;
|
|
433
|
+
instances.set(id, instance);
|
|
434
|
+
return context.newNumber(id);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const useHandle = context.newFunction('__hostUse', idHandle => {
|
|
438
|
+
const id = context.dump(idHandle);
|
|
439
|
+
const instance = instances.get(id);
|
|
440
|
+
return instance.doSomething();
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Pattern 4: ES6 Class with Host Methods
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
// Register host methods
|
|
448
|
+
context.setProp(context.global, '__hostCreate', createHandle);
|
|
449
|
+
context.setProp(context.global, '__hostMethod', methodHandle);
|
|
450
|
+
|
|
451
|
+
// Define class that uses host methods
|
|
452
|
+
const classHandle = defineClass(
|
|
453
|
+
ctx,
|
|
454
|
+
`
|
|
455
|
+
class MyClass {
|
|
456
|
+
constructor(arg) {
|
|
457
|
+
this.id = __hostCreate(arg);
|
|
458
|
+
}
|
|
459
|
+
method() {
|
|
460
|
+
return __hostMethod(this.id);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
`,
|
|
464
|
+
'MyClass'
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
context.setProp(context.global, 'MyClass', classHandle);
|
|
468
|
+
classHandle.dispose();
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Promise Tracking
|
|
472
|
+
|
|
473
|
+
All async operations are automatically tracked via `BridgeContext.tracker`:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
interface PromiseTracker {
|
|
477
|
+
pendingPromises: Set<Promise<void>>; // Active promises
|
|
478
|
+
notifyNewPromise: () => void; // Signal new promise
|
|
479
|
+
newPromiseSignal: Promise<void>; // Wait for new promise
|
|
480
|
+
deferredPromises: Set<any>; // Cleanup tracking
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
The `createAsyncBridge` utility handles tracking automatically. Manual tracking:
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
const deferred = context.newPromise();
|
|
488
|
+
tracker.deferredPromises.add(deferred);
|
|
489
|
+
|
|
490
|
+
const hostPromise = someAsyncOp()
|
|
491
|
+
.then(result => {
|
|
492
|
+
deferred.resolve(convertToHandle(context, result));
|
|
493
|
+
runtime.executePendingJobs();
|
|
494
|
+
})
|
|
495
|
+
.finally(() => {
|
|
496
|
+
tracker.pendingPromises.delete(hostPromise);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
tracker.pendingPromises.add(hostPromise);
|
|
500
|
+
tracker.notifyNewPromise();
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Testing Bridges
|
|
504
|
+
|
|
505
|
+
Create focused tests for each bridge:
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
import { describe, it, expect } from 'vitest';
|
|
509
|
+
import { createPromiseTracker, installDate } from '../src/sandbox/bridges/index.js';
|
|
510
|
+
|
|
511
|
+
describe('Date bridge', () => {
|
|
512
|
+
it('should provide dateNow function', async () => {
|
|
513
|
+
// Setup context and tracker
|
|
514
|
+
const tracker = createPromiseTracker();
|
|
515
|
+
const ctx = { context, runtime, tracker };
|
|
516
|
+
|
|
517
|
+
// Install bridge
|
|
518
|
+
installDate(ctx);
|
|
519
|
+
|
|
520
|
+
// Test functionality
|
|
521
|
+
const result = context.evalCode('dateNow()');
|
|
522
|
+
expect(result.value).toBeGreaterThan(0);
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Performance Considerations
|
|
528
|
+
|
|
529
|
+
### Minimize Handle Creation
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
// Bad - creates many handles
|
|
533
|
+
for (const item of items) {
|
|
534
|
+
const handle = context.newString(item);
|
|
535
|
+
// ... use handle
|
|
536
|
+
handle.dispose();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Good - batch operations
|
|
540
|
+
const arrayHandle = context.newArray();
|
|
541
|
+
for (let i = 0; i < items.length; i++) {
|
|
542
|
+
const itemHandle = context.newString(items[i]);
|
|
543
|
+
context.setProp(arrayHandle, i, itemHandle);
|
|
544
|
+
itemHandle.dispose();
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Dispose Immediately
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
// Dispose handles as soon as they're no longer needed
|
|
552
|
+
const handle = context.newString('value');
|
|
553
|
+
context.setProp(obj, 'key', handle);
|
|
554
|
+
handle.dispose(); // Dispose immediately after setting
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Use Batch Utilities
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
// Use setProperties for multiple values
|
|
561
|
+
setProperties(context, handle, {
|
|
562
|
+
prop1: value1,
|
|
563
|
+
prop2: value2,
|
|
564
|
+
prop3: value3
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## See Also
|
|
569
|
+
|
|
570
|
+
- [Fetch API Documentation](../docs/FETCH_API.md)
|
|
571
|
+
- [Sandbox Architecture](../docs/SANDBOX_FETCH_ARCHITECTURE.md)
|