@ricsam/isolate-runtime 0.1.13 → 0.1.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.
package/README.md CHANGED
@@ -115,20 +115,16 @@ interface RuntimeOptions {
115
115
  cwd?: string;
116
116
  /** Enable test environment (describe, it, expect) */
117
117
  testEnvironment?: boolean | TestEnvironmentOptions;
118
- /** Playwright options - user provides page object */
118
+ /** Playwright options (handler-first public API) */
119
119
  playwright?: PlaywrightOptions;
120
120
  }
121
121
 
122
122
  interface PlaywrightOptions {
123
- page: import("playwright").Page;
123
+ handler: (op: PlaywrightOperation) => Promise<PlaywrightResult>;
124
124
  timeout?: number;
125
- baseUrl?: string;
126
125
  /** Print browser console logs to stdout */
127
126
  console?: boolean;
128
- /** Browser console log callback (from the page, not sandbox) */
129
- onBrowserConsoleLog?: (entry: { level: string; stdout: string; timestamp: number }) => void;
130
- onNetworkRequest?: (info: { url: string; method: string; headers: Record<string, string>; timestamp: number }) => void;
131
- onNetworkResponse?: (info: { url: string; status: number; headers: Record<string, string>; timestamp: number }) => void;
127
+ onEvent?: (event: PlaywrightEvent) => void;
132
128
  }
133
129
  ```
134
130
 
@@ -310,21 +306,27 @@ type TestEvent =
310
306
 
311
307
  ## Playwright Integration
312
308
 
313
- Run browser automation with untrusted code. **You provide the Playwright page object**:
309
+ Run browser automation with untrusted code. Public API is handler-first:
310
+
311
+ ```typescript
312
+ import { defaultPlaywrightHandler } from "@ricsam/isolate-playwright/client";
313
+
314
+ playwright: { handler: defaultPlaywrightHandler(page) }
315
+ ```
314
316
 
315
317
  ### Script Mode (No Tests)
316
318
 
317
319
  ```typescript
318
320
  import { createRuntime } from "@ricsam/isolate-runtime";
319
321
  import { chromium } from "playwright";
322
+ import { defaultPlaywrightHandler } from "@ricsam/isolate-playwright/client";
320
323
 
321
324
  const browser = await chromium.launch({ headless: true });
322
325
  const page = await browser.newPage();
323
326
 
324
327
  const runtime = await createRuntime({
325
328
  playwright: {
326
- page,
327
- baseUrl: "https://example.com",
329
+ handler: defaultPlaywrightHandler(page),
328
330
  console: true, // Print browser console to stdout
329
331
  },
330
332
  });
@@ -351,6 +353,7 @@ Combine `testEnvironment` and `playwright` for browser testing. Playwright exten
351
353
  ```typescript
352
354
  import { createRuntime } from "@ricsam/isolate-runtime";
353
355
  import { chromium } from "playwright";
356
+ import { defaultPlaywrightHandler } from "@ricsam/isolate-playwright/client";
354
357
 
355
358
  const browser = await chromium.launch({ headless: true });
356
359
  const page = await browser.newPage();
@@ -358,9 +361,12 @@ const page = await browser.newPage();
358
361
  const runtime = await createRuntime({
359
362
  testEnvironment: true, // Provides describe, it, expect
360
363
  playwright: {
361
- page,
362
- baseUrl: "https://example.com",
363
- onBrowserConsoleLog: (entry) => console.log("[browser]", entry.stdout),
364
+ handler: defaultPlaywrightHandler(page),
365
+ onEvent: (event) => {
366
+ if (event.type === "browserConsoleLog") {
367
+ console.log("[browser]", event.stdout);
368
+ }
369
+ },
364
370
  },
365
371
  });
366
372
 
@@ -386,6 +392,74 @@ await runtime.dispose();
386
392
  await browser.close();
387
393
  ```
388
394
 
395
+ ### Multi-Page Testing
396
+
397
+ For tests that need multiple pages or browser contexts, provide `createPage` and/or `createContext` callbacks:
398
+
399
+ ```typescript
400
+ import { createRuntime } from "@ricsam/isolate-runtime";
401
+ import { chromium } from "playwright";
402
+ import { defaultPlaywrightHandler } from "@ricsam/isolate-playwright/client";
403
+
404
+ const browser = await chromium.launch({ headless: true });
405
+ const browserContext = await browser.newContext();
406
+ const page = await browserContext.newPage();
407
+
408
+ const runtime = await createRuntime({
409
+ testEnvironment: true,
410
+ playwright: {
411
+ handler: defaultPlaywrightHandler(page, {
412
+ // Called when isolate code calls context.newPage()
413
+ createPage: async (context) => context.newPage(),
414
+ // Called when isolate code calls browser.newContext()
415
+ createContext: async (options) => browser.newContext(options),
416
+ }),
417
+ },
418
+ });
419
+
420
+ await runtime.eval(`
421
+ test('multi-page test', async () => {
422
+ // Create additional pages
423
+ const page2 = await context.newPage();
424
+
425
+ // Navigate independently
426
+ await page.goto('https://example.com/page1');
427
+ await page2.goto('https://example.com/page2');
428
+
429
+ // Work with multiple pages
430
+ await page.locator('#button').click();
431
+ await page2.locator('#input').fill('text');
432
+
433
+ await page2.close();
434
+ });
435
+
436
+ test('multi-context test', async () => {
437
+ // Create isolated context (separate cookies, storage)
438
+ const ctx2 = await browser.newContext();
439
+ const page2 = await ctx2.newPage();
440
+
441
+ // Cookies are isolated between contexts
442
+ await context.addCookies([{ name: 'test', value: '1', domain: 'example.com', path: '/' }]);
443
+ const ctx1Cookies = await context.cookies();
444
+ const ctx2Cookies = await ctx2.cookies();
445
+
446
+ expect(ctx1Cookies.some(c => c.name === 'test')).toBe(true);
447
+ expect(ctx2Cookies.some(c => c.name === 'test')).toBe(false);
448
+
449
+ await ctx2.close();
450
+ });
451
+ `);
452
+
453
+ const results = await runtime.testEnvironment.runTests();
454
+ await runtime.dispose();
455
+ await browser.close();
456
+ ```
457
+
458
+ **Behavior without lifecycle callbacks:**
459
+ - `context.newPage()` without `createPage`: Throws error
460
+ - `browser.newContext()` without `createContext`: Throws error
461
+ - `context.cookies()`, `context.addCookies()`, `context.clearCookies()`: Work without callbacks
462
+
389
463
  ## Included APIs
390
464
 
391
465
  - Core (Blob, File, streams, URL, TextEncoder/Decoder)
@@ -397,20 +471,7 @@ await browser.close();
397
471
  - Fetch API
398
472
  - File System (if handler provided)
399
473
  - Test Environment (if enabled)
400
- - Playwright (if page provided)
401
-
402
- ## Legacy API
403
-
404
- For backwards compatibility with code that needs direct isolate/context access:
405
-
406
- ```typescript
407
- import { createLegacyRuntime } from "@ricsam/isolate-runtime";
408
-
409
- const runtime = await createLegacyRuntime();
410
- // runtime.isolate and runtime.context are available
411
- await runtime.context.eval(`console.log("Hello")`);
412
- runtime.dispose(); // sync
413
- ```
474
+ - Playwright (if handler provided)
414
475
 
415
476
  ## License
416
477
 
@@ -4,23 +4,6 @@ var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __reExport = (target, mod, secondTarget) => {
8
- for (let key of __getOwnPropNames(mod))
9
- if (!__hasOwnProp.call(target, key) && key !== "default")
10
- __defProp(target, key, {
11
- get: () => mod[key],
12
- enumerable: true
13
- });
14
- if (secondTarget) {
15
- for (let key of __getOwnPropNames(mod))
16
- if (!__hasOwnProp.call(secondTarget, key) && key !== "default")
17
- __defProp(secondTarget, key, {
18
- get: () => mod[key],
19
- enumerable: true
20
- });
21
- return secondTarget;
22
- }
23
- };
24
7
  var __toESM = (mod, isNodeMode, target) => {
25
8
  target = mod != null ? __create(__getProtoOf(mod)) : {};
26
9
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
@@ -74,6 +57,8 @@ __export(exports_src, {
74
57
  normalizeEntryFilename: () => import_isolate_protocol2.normalizeEntryFilename,
75
58
  hasTests: () => import_isolate_test_environment2.hasTests,
76
59
  getTestCount: () => import_isolate_test_environment2.getTestCount,
60
+ getDefaultPlaywrightHandlerMetadata: () => import_isolate_playwright2.getDefaultPlaywrightHandlerMetadata,
61
+ defaultPlaywrightHandler: () => import_isolate_playwright2.defaultPlaywrightHandler,
77
62
  createRuntime: () => createRuntime,
78
63
  createPlaywrightHandler: () => import_isolate_playwright2.createPlaywrightHandler,
79
64
  createNodeFileSystemHandler: () => import_isolate_fs2.createNodeFileSystemHandler
@@ -106,7 +91,6 @@ var import_isolate_path2 = require("@ricsam/isolate-path");
106
91
  var import_isolate_timers2 = require("@ricsam/isolate-timers");
107
92
  var import_isolate_test_environment2 = require("@ricsam/isolate-test-environment");
108
93
  var import_isolate_playwright2 = require("@ricsam/isolate-playwright");
109
- __reExport(exports_src, require("./internal.cjs"), module.exports);
110
94
  var iteratorSessions = new Map;
111
95
  var nextIteratorId = 1;
112
96
  var ISOLATE_MARSHAL_CODE = `
@@ -232,6 +216,68 @@ var ISOLATE_MARSHAL_CODE = `
232
216
  }
233
217
  return fd;
234
218
  }
219
+ case 'CallbackRef': {
220
+ // Create a proxy function that invokes the callback
221
+ const callbackId = value.callbackId;
222
+ return function(...args) {
223
+ const argsJson = JSON.stringify(marshalForHost(args));
224
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [callbackId, argsJson]);
225
+ const result = JSON.parse(resultJson);
226
+ if (result.ok) {
227
+ return unmarshalFromHost(result.value);
228
+ } else {
229
+ const error = new Error(result.error.message);
230
+ error.name = result.error.name;
231
+ throw error;
232
+ }
233
+ };
234
+ }
235
+ case 'PromiseRef': {
236
+ // Create a proxy Promise that resolves via callback
237
+ const promiseId = value.promiseId;
238
+ return new Promise((resolve, reject) => {
239
+ try {
240
+ const argsJson = JSON.stringify([promiseId]);
241
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [value.__resolveCallbackId, argsJson]);
242
+ const result = JSON.parse(resultJson);
243
+ if (result.ok) {
244
+ resolve(unmarshalFromHost(result.value));
245
+ } else {
246
+ reject(new Error(result.error.message));
247
+ }
248
+ } catch (e) {
249
+ reject(e);
250
+ }
251
+ });
252
+ }
253
+ case 'AsyncIteratorRef': {
254
+ const iteratorId = value.iteratorId;
255
+ const nextCallbackId = value.__nextCallbackId;
256
+ const returnCallbackId = value.__returnCallbackId;
257
+ return {
258
+ [Symbol.asyncIterator]() { return this; },
259
+ async next() {
260
+ const argsJson = JSON.stringify([iteratorId]);
261
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [nextCallbackId, argsJson]);
262
+ const result = JSON.parse(resultJson);
263
+ if (!result.ok) {
264
+ const error = new Error(result.error.message);
265
+ error.name = result.error.name;
266
+ throw error;
267
+ }
268
+ return {
269
+ done: result.value.done,
270
+ value: unmarshalFromHost(result.value.value)
271
+ };
272
+ },
273
+ async return(v) {
274
+ const argsJson = JSON.stringify([iteratorId, marshalForHost(v)]);
275
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [returnCallbackId, argsJson]);
276
+ const result = JSON.parse(resultJson);
277
+ return { done: true, value: result.ok ? unmarshalFromHost(result.value) : undefined };
278
+ }
279
+ };
280
+ }
235
281
  default:
236
282
  // Unknown ref type, return as-is
237
283
  break;
@@ -255,9 +301,27 @@ var ISOLATE_MARSHAL_CODE = `
255
301
  globalThis.__unmarshalFromHost = unmarshalFromHost;
256
302
  })();
257
303
  `;
258
- async function setupCustomFunctions(context, customFunctions) {
304
+ async function setupCustomFunctions(context, customFunctions, marshalOptions) {
259
305
  const global = context.global;
260
- const invokeCallbackRef = new import_isolated_vm.default.Reference(async (name, argsJson) => {
306
+ const invokeCallbackRef = new import_isolated_vm.default.Reference(async (nameOrId, argsJson) => {
307
+ if (typeof nameOrId === "number" && marshalOptions) {
308
+ const rawArgs2 = JSON.parse(argsJson);
309
+ const args2 = import_isolate_protocol3.unmarshalValue(rawArgs2);
310
+ try {
311
+ const result = await marshalOptions.invokeCallback(nameOrId, args2);
312
+ const ctx = marshalOptions.createMarshalContext();
313
+ const marshalledResult = await import_isolate_protocol3.marshalValue(result, ctx);
314
+ const processedResult = marshalOptions.addCallbackIdsToRefs(marshalledResult);
315
+ return JSON.stringify({ ok: true, value: processedResult });
316
+ } catch (error) {
317
+ const err = error;
318
+ return JSON.stringify({
319
+ ok: false,
320
+ error: { message: err.message, name: err.name }
321
+ });
322
+ }
323
+ }
324
+ const name = String(nameOrId);
261
325
  const def = customFunctions[name];
262
326
  if (!def) {
263
327
  return JSON.stringify({
@@ -271,7 +335,13 @@ async function setupCustomFunctions(context, customFunctions) {
271
335
  const rawArgs = JSON.parse(argsJson);
272
336
  const args = import_isolate_protocol3.unmarshalValue(rawArgs);
273
337
  try {
274
- const result = def.type === "async" ? await def.fn(...args) : def.fn(...args);
338
+ const result = await def.fn(...args);
339
+ if (marshalOptions) {
340
+ const ctx = marshalOptions.createMarshalContext();
341
+ const marshalledResult2 = await import_isolate_protocol3.marshalValue(result, ctx);
342
+ const processedResult = marshalOptions.addCallbackIdsToRefs(marshalledResult2);
343
+ return JSON.stringify({ ok: true, value: processedResult });
344
+ }
275
345
  const marshalledResult = await import_isolate_protocol3.marshalValue(result);
276
346
  return JSON.stringify({ ok: true, value: marshalledResult });
277
347
  } catch (error) {
@@ -327,7 +397,14 @@ async function setupCustomFunctions(context, customFunctions) {
327
397
  if (result.done) {
328
398
  iteratorSessions.delete(iteratorId);
329
399
  }
330
- const marshalledValue = await import_isolate_protocol3.marshalValue(result.value);
400
+ let marshalledValue;
401
+ if (marshalOptions) {
402
+ const ctx = marshalOptions.createMarshalContext();
403
+ marshalledValue = await import_isolate_protocol3.marshalValue(result.value, ctx);
404
+ marshalledValue = marshalOptions.addCallbackIdsToRefs(marshalledValue);
405
+ } else {
406
+ marshalledValue = await import_isolate_protocol3.marshalValue(result.value);
407
+ }
331
408
  return JSON.stringify({
332
409
  ok: true,
333
410
  done: result.done,
@@ -353,7 +430,14 @@ async function setupCustomFunctions(context, customFunctions) {
353
430
  const value = import_isolate_protocol3.unmarshalValue(rawValue);
354
431
  const result = await session.iterator.return?.(value);
355
432
  iteratorSessions.delete(iteratorId);
356
- const marshalledValue = await import_isolate_protocol3.marshalValue(result?.value);
433
+ let marshalledValue;
434
+ if (marshalOptions) {
435
+ const ctx = marshalOptions.createMarshalContext();
436
+ marshalledValue = await import_isolate_protocol3.marshalValue(result?.value, ctx);
437
+ marshalledValue = marshalOptions.addCallbackIdsToRefs(marshalledValue);
438
+ } else {
439
+ marshalledValue = await import_isolate_protocol3.marshalValue(result?.value);
440
+ }
357
441
  return JSON.stringify({ ok: true, done: true, value: marshalledValue });
358
442
  } catch (error) {
359
443
  const err = error;
@@ -383,7 +467,14 @@ async function setupCustomFunctions(context, customFunctions) {
383
467
  });
384
468
  const result = await session.iterator.throw?.(error);
385
469
  iteratorSessions.delete(iteratorId);
386
- const marshalledValue = await import_isolate_protocol3.marshalValue(result?.value);
470
+ let marshalledValue;
471
+ if (marshalOptions) {
472
+ const ctx = marshalOptions.createMarshalContext();
473
+ marshalledValue = await import_isolate_protocol3.marshalValue(result?.value, ctx);
474
+ marshalledValue = marshalOptions.addCallbackIdsToRefs(marshalledValue);
475
+ } else {
476
+ marshalledValue = await import_isolate_protocol3.marshalValue(result?.value);
477
+ }
387
478
  return JSON.stringify({
388
479
  ok: true,
389
480
  done: result?.done ?? true,
@@ -474,8 +565,114 @@ async function setupCustomFunctions(context, customFunctions) {
474
565
  }
475
566
  return invokeCallbackRef;
476
567
  }
568
+ function createLocalCustomFunctionsMarshalOptions() {
569
+ const returnedCallbacks = new Map;
570
+ const returnedPromises = new Map;
571
+ const returnedIterators = new Map;
572
+ let nextLocalCallbackId = 1e6;
573
+ const createMarshalContext = () => ({
574
+ registerCallback: (fn) => {
575
+ const callbackId = nextLocalCallbackId++;
576
+ returnedCallbacks.set(callbackId, fn);
577
+ return callbackId;
578
+ },
579
+ registerPromise: (promise) => {
580
+ const promiseId = nextLocalCallbackId++;
581
+ returnedPromises.set(promiseId, promise);
582
+ return promiseId;
583
+ },
584
+ registerIterator: (iterator) => {
585
+ const iteratorId = nextLocalCallbackId++;
586
+ returnedIterators.set(iteratorId, iterator);
587
+ return iteratorId;
588
+ }
589
+ });
590
+ const isPromiseRef = (value) => typeof value === "object" && value !== null && value.__type === "PromiseRef";
591
+ const isAsyncIteratorRef = (value) => typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
592
+ const addCallbackIdsToRefs = (value) => {
593
+ if (value === null || typeof value !== "object")
594
+ return value;
595
+ if (isPromiseRef(value)) {
596
+ if ("__resolveCallbackId" in value)
597
+ return value;
598
+ const resolveCallbackId = nextLocalCallbackId++;
599
+ returnedCallbacks.set(resolveCallbackId, async (promiseId) => {
600
+ const promise = returnedPromises.get(promiseId);
601
+ if (!promise) {
602
+ throw new Error(`Promise ${promiseId} not found`);
603
+ }
604
+ const result2 = await promise;
605
+ returnedPromises.delete(promiseId);
606
+ const ctx = createMarshalContext();
607
+ const marshalled = await import_isolate_protocol3.marshalValue(result2, ctx);
608
+ return addCallbackIdsToRefs(marshalled);
609
+ });
610
+ return { ...value, __resolveCallbackId: resolveCallbackId };
611
+ }
612
+ if (isAsyncIteratorRef(value)) {
613
+ if ("__nextCallbackId" in value)
614
+ return value;
615
+ const nextCallbackId = nextLocalCallbackId++;
616
+ returnedCallbacks.set(nextCallbackId, async (iteratorId) => {
617
+ const iterator = returnedIterators.get(iteratorId);
618
+ if (!iterator) {
619
+ throw new Error(`Iterator ${iteratorId} not found`);
620
+ }
621
+ const result2 = await iterator.next();
622
+ if (result2.done) {
623
+ returnedIterators.delete(iteratorId);
624
+ }
625
+ const ctx = createMarshalContext();
626
+ const marshalledValue = await import_isolate_protocol3.marshalValue(result2.value, ctx);
627
+ return {
628
+ done: result2.done,
629
+ value: addCallbackIdsToRefs(marshalledValue)
630
+ };
631
+ });
632
+ const returnCallbackId = nextLocalCallbackId++;
633
+ returnedCallbacks.set(returnCallbackId, async (iteratorId, returnValue) => {
634
+ const iterator = returnedIterators.get(iteratorId);
635
+ returnedIterators.delete(iteratorId);
636
+ if (!iterator || !iterator.return) {
637
+ return { done: true, value: undefined };
638
+ }
639
+ const result2 = await iterator.return(returnValue);
640
+ const ctx = createMarshalContext();
641
+ const marshalledValue = await import_isolate_protocol3.marshalValue(result2.value, ctx);
642
+ return {
643
+ done: true,
644
+ value: addCallbackIdsToRefs(marshalledValue)
645
+ };
646
+ });
647
+ return {
648
+ ...value,
649
+ __nextCallbackId: nextCallbackId,
650
+ __returnCallbackId: returnCallbackId
651
+ };
652
+ }
653
+ if (Array.isArray(value)) {
654
+ return value.map((item) => addCallbackIdsToRefs(item));
655
+ }
656
+ const result = {};
657
+ for (const key of Object.keys(value)) {
658
+ result[key] = addCallbackIdsToRefs(value[key]);
659
+ }
660
+ return result;
661
+ };
662
+ const invokeCallback = async (callbackId, args) => {
663
+ const callback = returnedCallbacks.get(callbackId);
664
+ if (!callback) {
665
+ throw new Error(`Local callback ${callbackId} not found`);
666
+ }
667
+ return await callback(...args);
668
+ };
669
+ return { createMarshalContext, addCallbackIdsToRefs, invokeCallback };
670
+ }
477
671
  function createModuleResolver(state) {
478
672
  return async (specifier, referrer) => {
673
+ const staticCached = state.staticModuleCache.get(specifier);
674
+ if (staticCached)
675
+ return staticCached;
479
676
  const cached = state.moduleCache.get(specifier);
480
677
  if (cached)
481
678
  return cached;
@@ -484,16 +681,21 @@ function createModuleResolver(state) {
484
681
  }
485
682
  const importerPath = state.moduleToFilename.get(referrer) ?? "<unknown>";
486
683
  const importerResolveDir = import_node_path.default.posix.dirname(importerPath);
487
- const { code, resolveDir } = await state.moduleLoader(specifier, {
684
+ const result = await state.moduleLoader(specifier, {
488
685
  path: importerPath,
489
686
  resolveDir: importerResolveDir
490
687
  });
688
+ const { code, resolveDir } = result;
491
689
  const hash = import_isolate_transform.contentHash(code);
492
690
  const cacheKey = `${specifier}:${hash}`;
493
691
  const hashCached = state.moduleCache.get(cacheKey);
494
692
  if (hashCached)
495
693
  return hashCached;
496
- const transformed = await import_isolate_transform.transformModuleCode(code, specifier);
694
+ let transformed = state.transformCache.get(hash);
695
+ if (!transformed) {
696
+ transformed = await import_isolate_transform.transformModuleCode(code, specifier);
697
+ state.transformCache.set(hash, transformed);
698
+ }
497
699
  if (transformed.sourceMap) {
498
700
  state.sourceMaps.set(specifier, transformed.sourceMap);
499
701
  }
@@ -502,8 +704,12 @@ function createModuleResolver(state) {
502
704
  });
503
705
  const resolvedPath = import_node_path.default.posix.join(resolveDir, import_node_path.default.posix.basename(specifier));
504
706
  state.moduleToFilename.set(mod, resolvedPath);
505
- state.moduleCache.set(specifier, mod);
506
- state.moduleCache.set(cacheKey, mod);
707
+ if (result.static) {
708
+ state.staticModuleCache.set(specifier, mod);
709
+ } else {
710
+ state.moduleCache.set(specifier, mod);
711
+ state.moduleCache.set(cacheKey, mod);
712
+ }
507
713
  const resolver = createModuleResolver(state);
508
714
  await mod.instantiate(state.context, resolver);
509
715
  return mod;
@@ -514,8 +720,8 @@ function convertFetchCallback(callback) {
514
720
  return {};
515
721
  }
516
722
  return {
517
- onFetch: async (request) => {
518
- return Promise.resolve(callback(request));
723
+ onFetch: async (url, init) => {
724
+ return Promise.resolve(callback(url, init));
519
725
  }
520
726
  };
521
727
  }
@@ -531,8 +737,11 @@ async function createRuntime(options) {
531
737
  context,
532
738
  handles: {},
533
739
  moduleCache: new Map,
740
+ staticModuleCache: new Map,
741
+ transformCache: new Map,
534
742
  moduleToFilename: new Map,
535
743
  sourceMaps: new Map,
744
+ pendingCallbacks: [],
536
745
  moduleLoader: opts.moduleLoader,
537
746
  customFunctions: opts.customFunctions
538
747
  };
@@ -547,13 +756,17 @@ async function createRuntime(options) {
547
756
  state.handles.fs = await import_isolate_fs.setupFs(context, opts.fs);
548
757
  }
549
758
  if (opts.customFunctions) {
550
- state.customFnInvokeRef = await setupCustomFunctions(context, opts.customFunctions);
759
+ const customMarshalOptions = opts.customFunctionsMarshalOptions ?? createLocalCustomFunctionsMarshalOptions();
760
+ state.customFnInvokeRef = await setupCustomFunctions(context, opts.customFunctions, customMarshalOptions);
551
761
  }
552
762
  if (opts.testEnvironment) {
553
763
  const testEnvOptions = typeof opts.testEnvironment === "object" ? opts.testEnvironment : undefined;
554
764
  state.handles.testEnvironment = await import_isolate_test_environment.setupTestEnvironment(context, testEnvOptions);
555
765
  }
556
766
  if (opts.playwright) {
767
+ if (!opts.playwright.handler) {
768
+ throw new Error("Playwright configured without handler. Provide playwright.handler in createRuntime options.");
769
+ }
557
770
  let eventCallback = opts.playwright.onEvent;
558
771
  if (opts.playwright.console && opts.console?.onEntry) {
559
772
  const originalCallback = eventCallback;
@@ -572,13 +785,13 @@ async function createRuntime(options) {
572
785
  }
573
786
  };
574
787
  }
575
- state.handles.playwright = await import_isolate_playwright.setupPlaywright(context, {
576
- page: opts.playwright.page,
788
+ const playwrightSetupOptions = {
789
+ handler: opts.playwright.handler,
577
790
  timeout: opts.playwright.timeout,
578
- baseUrl: opts.playwright.baseUrl,
579
791
  console: opts.playwright.console && !opts.console?.onEntry,
580
792
  onEvent: eventCallback
581
- });
793
+ };
794
+ state.handles.playwright = await import_isolate_playwright.setupPlaywright(context, playwrightSetupOptions);
582
795
  }
583
796
  const fetchHandle = {
584
797
  async dispatchRequest(request, options2) {
@@ -634,6 +847,36 @@ async function createRuntime(options) {
634
847
  throw new Error("Fetch handle not available");
635
848
  }
636
849
  return state.handles.fetch.hasActiveConnections();
850
+ },
851
+ dispatchClientWebSocketOpen(socketId, protocol, extensions) {
852
+ if (!state.handles.fetch) {
853
+ throw new Error("Fetch handle not available");
854
+ }
855
+ state.handles.fetch.dispatchClientWebSocketOpen(socketId, protocol, extensions);
856
+ },
857
+ dispatchClientWebSocketMessage(socketId, data) {
858
+ if (!state.handles.fetch) {
859
+ throw new Error("Fetch handle not available");
860
+ }
861
+ state.handles.fetch.dispatchClientWebSocketMessage(socketId, data);
862
+ },
863
+ dispatchClientWebSocketClose(socketId, code, reason, wasClean) {
864
+ if (!state.handles.fetch) {
865
+ throw new Error("Fetch handle not available");
866
+ }
867
+ state.handles.fetch.dispatchClientWebSocketClose(socketId, code, reason, wasClean);
868
+ },
869
+ dispatchClientWebSocketError(socketId) {
870
+ if (!state.handles.fetch) {
871
+ throw new Error("Fetch handle not available");
872
+ }
873
+ state.handles.fetch.dispatchClientWebSocketError(socketId);
874
+ },
875
+ onClientWebSocketCommand(callback) {
876
+ if (!state.handles.fetch) {
877
+ throw new Error("Fetch handle not available");
878
+ }
879
+ return state.handles.fetch.onClientWebSocketCommand(callback);
637
880
  }
638
881
  };
639
882
  const timersHandle = {
@@ -656,11 +899,27 @@ async function createRuntime(options) {
656
899
  }
657
900
  };
658
901
  const testEnvironmentHandle = {
659
- async runTests(_timeout) {
902
+ async runTests(timeout) {
660
903
  if (!state.handles.testEnvironment) {
661
904
  throw new Error("Test environment not enabled. Set testEnvironment: true in createRuntime options.");
662
905
  }
663
- return import_isolate_test_environment.runTests(state.context);
906
+ if (timeout === undefined) {
907
+ return import_isolate_test_environment.runTests(state.context);
908
+ }
909
+ let timeoutId;
910
+ const timeoutPromise = new Promise((_, reject) => {
911
+ timeoutId = setTimeout(() => reject(new Error("Test timeout")), timeout);
912
+ });
913
+ try {
914
+ return await Promise.race([
915
+ import_isolate_test_environment.runTests(state.context),
916
+ timeoutPromise
917
+ ]);
918
+ } finally {
919
+ if (timeoutId) {
920
+ clearTimeout(timeoutId);
921
+ }
922
+ }
664
923
  },
665
924
  hasTests() {
666
925
  if (!state.handles.testEnvironment) {
@@ -681,7 +940,7 @@ async function createRuntime(options) {
681
940
  const playwrightHandle = {
682
941
  getCollectedData() {
683
942
  if (!state.handles.playwright) {
684
- throw new Error("Playwright not configured. Provide playwright.page in createRuntime options.");
943
+ throw new Error("Playwright not configured. Provide playwright.handler in createRuntime options.");
685
944
  }
686
945
  return {
687
946
  browserConsoleLogs: state.handles.playwright.getBrowserConsoleLogs(),
@@ -695,6 +954,7 @@ async function createRuntime(options) {
695
954
  };
696
955
  return {
697
956
  id,
957
+ pendingCallbacks: state.pendingCallbacks,
698
958
  fetch: fetchHandle,
699
959
  timers: timersHandle,
700
960
  console: consoleHandle,
@@ -725,6 +985,10 @@ async function createRuntime(options) {
725
985
  } finally {
726
986
  runRef.release();
727
987
  }
988
+ if (state.pendingCallbacks.length > 0) {
989
+ await Promise.all(state.pendingCallbacks);
990
+ state.pendingCallbacks.length = 0;
991
+ }
728
992
  } catch (err) {
729
993
  const error = err;
730
994
  if (error.stack && state.sourceMaps.size > 0) {
@@ -733,6 +997,11 @@ async function createRuntime(options) {
733
997
  throw error;
734
998
  }
735
999
  },
1000
+ clearModuleCache() {
1001
+ state.moduleCache.clear();
1002
+ state.moduleToFilename.clear();
1003
+ state.sourceMaps.clear();
1004
+ },
736
1005
  async dispose() {
737
1006
  if (state.customFnInvokeRef) {
738
1007
  state.customFnInvokeRef.release();
@@ -754,4 +1023,4 @@ async function createRuntime(options) {
754
1023
  };
755
1024
  }
756
1025
 
757
- //# debugId=9F9E12C10DE91D2864756E2164756E21
1026
+ //# debugId=6EFB8447C7EE485F64756E2164756E21