@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.
@@ -23,7 +23,9 @@ import {
23
23
  hasTests as hasTestsInContext,
24
24
  getTestCount as getTestCountInContext
25
25
  } from "@ricsam/isolate-test-environment";
26
- import { setupPlaywright } from "@ricsam/isolate-playwright";
26
+ import {
27
+ setupPlaywright
28
+ } from "@ricsam/isolate-playwright";
27
29
  import {
28
30
  marshalValue,
29
31
  unmarshalValue
@@ -47,10 +49,10 @@ import {
47
49
  } from "@ricsam/isolate-test-environment";
48
50
  import {
49
51
  setupPlaywright as setupPlaywright2,
50
- createPlaywrightHandler
52
+ createPlaywrightHandler,
53
+ defaultPlaywrightHandler,
54
+ getDefaultPlaywrightHandlerMetadata
51
55
  } from "@ricsam/isolate-playwright";
52
-
53
- export * from "./internal.mjs";
54
56
  var iteratorSessions = new Map;
55
57
  var nextIteratorId = 1;
56
58
  var ISOLATE_MARSHAL_CODE = `
@@ -176,6 +178,68 @@ var ISOLATE_MARSHAL_CODE = `
176
178
  }
177
179
  return fd;
178
180
  }
181
+ case 'CallbackRef': {
182
+ // Create a proxy function that invokes the callback
183
+ const callbackId = value.callbackId;
184
+ return function(...args) {
185
+ const argsJson = JSON.stringify(marshalForHost(args));
186
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [callbackId, argsJson]);
187
+ const result = JSON.parse(resultJson);
188
+ if (result.ok) {
189
+ return unmarshalFromHost(result.value);
190
+ } else {
191
+ const error = new Error(result.error.message);
192
+ error.name = result.error.name;
193
+ throw error;
194
+ }
195
+ };
196
+ }
197
+ case 'PromiseRef': {
198
+ // Create a proxy Promise that resolves via callback
199
+ const promiseId = value.promiseId;
200
+ return new Promise((resolve, reject) => {
201
+ try {
202
+ const argsJson = JSON.stringify([promiseId]);
203
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [value.__resolveCallbackId, argsJson]);
204
+ const result = JSON.parse(resultJson);
205
+ if (result.ok) {
206
+ resolve(unmarshalFromHost(result.value));
207
+ } else {
208
+ reject(new Error(result.error.message));
209
+ }
210
+ } catch (e) {
211
+ reject(e);
212
+ }
213
+ });
214
+ }
215
+ case 'AsyncIteratorRef': {
216
+ const iteratorId = value.iteratorId;
217
+ const nextCallbackId = value.__nextCallbackId;
218
+ const returnCallbackId = value.__returnCallbackId;
219
+ return {
220
+ [Symbol.asyncIterator]() { return this; },
221
+ async next() {
222
+ const argsJson = JSON.stringify([iteratorId]);
223
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [nextCallbackId, argsJson]);
224
+ const result = JSON.parse(resultJson);
225
+ if (!result.ok) {
226
+ const error = new Error(result.error.message);
227
+ error.name = result.error.name;
228
+ throw error;
229
+ }
230
+ return {
231
+ done: result.value.done,
232
+ value: unmarshalFromHost(result.value.value)
233
+ };
234
+ },
235
+ async return(v) {
236
+ const argsJson = JSON.stringify([iteratorId, marshalForHost(v)]);
237
+ const resultJson = __customFn_invoke.applySyncPromise(undefined, [returnCallbackId, argsJson]);
238
+ const result = JSON.parse(resultJson);
239
+ return { done: true, value: result.ok ? unmarshalFromHost(result.value) : undefined };
240
+ }
241
+ };
242
+ }
179
243
  default:
180
244
  // Unknown ref type, return as-is
181
245
  break;
@@ -199,9 +263,27 @@ var ISOLATE_MARSHAL_CODE = `
199
263
  globalThis.__unmarshalFromHost = unmarshalFromHost;
200
264
  })();
201
265
  `;
202
- async function setupCustomFunctions(context, customFunctions) {
266
+ async function setupCustomFunctions(context, customFunctions, marshalOptions) {
203
267
  const global = context.global;
204
- const invokeCallbackRef = new ivm.Reference(async (name, argsJson) => {
268
+ const invokeCallbackRef = new ivm.Reference(async (nameOrId, argsJson) => {
269
+ if (typeof nameOrId === "number" && marshalOptions) {
270
+ const rawArgs2 = JSON.parse(argsJson);
271
+ const args2 = unmarshalValue(rawArgs2);
272
+ try {
273
+ const result = await marshalOptions.invokeCallback(nameOrId, args2);
274
+ const ctx = marshalOptions.createMarshalContext();
275
+ const marshalledResult = await marshalValue(result, ctx);
276
+ const processedResult = marshalOptions.addCallbackIdsToRefs(marshalledResult);
277
+ return JSON.stringify({ ok: true, value: processedResult });
278
+ } catch (error) {
279
+ const err = error;
280
+ return JSON.stringify({
281
+ ok: false,
282
+ error: { message: err.message, name: err.name }
283
+ });
284
+ }
285
+ }
286
+ const name = String(nameOrId);
205
287
  const def = customFunctions[name];
206
288
  if (!def) {
207
289
  return JSON.stringify({
@@ -215,7 +297,13 @@ async function setupCustomFunctions(context, customFunctions) {
215
297
  const rawArgs = JSON.parse(argsJson);
216
298
  const args = unmarshalValue(rawArgs);
217
299
  try {
218
- const result = def.type === "async" ? await def.fn(...args) : def.fn(...args);
300
+ const result = await def.fn(...args);
301
+ if (marshalOptions) {
302
+ const ctx = marshalOptions.createMarshalContext();
303
+ const marshalledResult2 = await marshalValue(result, ctx);
304
+ const processedResult = marshalOptions.addCallbackIdsToRefs(marshalledResult2);
305
+ return JSON.stringify({ ok: true, value: processedResult });
306
+ }
219
307
  const marshalledResult = await marshalValue(result);
220
308
  return JSON.stringify({ ok: true, value: marshalledResult });
221
309
  } catch (error) {
@@ -271,7 +359,14 @@ async function setupCustomFunctions(context, customFunctions) {
271
359
  if (result.done) {
272
360
  iteratorSessions.delete(iteratorId);
273
361
  }
274
- const marshalledValue = await marshalValue(result.value);
362
+ let marshalledValue;
363
+ if (marshalOptions) {
364
+ const ctx = marshalOptions.createMarshalContext();
365
+ marshalledValue = await marshalValue(result.value, ctx);
366
+ marshalledValue = marshalOptions.addCallbackIdsToRefs(marshalledValue);
367
+ } else {
368
+ marshalledValue = await marshalValue(result.value);
369
+ }
275
370
  return JSON.stringify({
276
371
  ok: true,
277
372
  done: result.done,
@@ -297,7 +392,14 @@ async function setupCustomFunctions(context, customFunctions) {
297
392
  const value = unmarshalValue(rawValue);
298
393
  const result = await session.iterator.return?.(value);
299
394
  iteratorSessions.delete(iteratorId);
300
- const marshalledValue = await marshalValue(result?.value);
395
+ let marshalledValue;
396
+ if (marshalOptions) {
397
+ const ctx = marshalOptions.createMarshalContext();
398
+ marshalledValue = await marshalValue(result?.value, ctx);
399
+ marshalledValue = marshalOptions.addCallbackIdsToRefs(marshalledValue);
400
+ } else {
401
+ marshalledValue = await marshalValue(result?.value);
402
+ }
301
403
  return JSON.stringify({ ok: true, done: true, value: marshalledValue });
302
404
  } catch (error) {
303
405
  const err = error;
@@ -327,7 +429,14 @@ async function setupCustomFunctions(context, customFunctions) {
327
429
  });
328
430
  const result = await session.iterator.throw?.(error);
329
431
  iteratorSessions.delete(iteratorId);
330
- const marshalledValue = await marshalValue(result?.value);
432
+ let marshalledValue;
433
+ if (marshalOptions) {
434
+ const ctx = marshalOptions.createMarshalContext();
435
+ marshalledValue = await marshalValue(result?.value, ctx);
436
+ marshalledValue = marshalOptions.addCallbackIdsToRefs(marshalledValue);
437
+ } else {
438
+ marshalledValue = await marshalValue(result?.value);
439
+ }
331
440
  return JSON.stringify({
332
441
  ok: true,
333
442
  done: result?.done ?? true,
@@ -418,8 +527,114 @@ async function setupCustomFunctions(context, customFunctions) {
418
527
  }
419
528
  return invokeCallbackRef;
420
529
  }
530
+ function createLocalCustomFunctionsMarshalOptions() {
531
+ const returnedCallbacks = new Map;
532
+ const returnedPromises = new Map;
533
+ const returnedIterators = new Map;
534
+ let nextLocalCallbackId = 1e6;
535
+ const createMarshalContext = () => ({
536
+ registerCallback: (fn) => {
537
+ const callbackId = nextLocalCallbackId++;
538
+ returnedCallbacks.set(callbackId, fn);
539
+ return callbackId;
540
+ },
541
+ registerPromise: (promise) => {
542
+ const promiseId = nextLocalCallbackId++;
543
+ returnedPromises.set(promiseId, promise);
544
+ return promiseId;
545
+ },
546
+ registerIterator: (iterator) => {
547
+ const iteratorId = nextLocalCallbackId++;
548
+ returnedIterators.set(iteratorId, iterator);
549
+ return iteratorId;
550
+ }
551
+ });
552
+ const isPromiseRef = (value) => typeof value === "object" && value !== null && value.__type === "PromiseRef";
553
+ const isAsyncIteratorRef = (value) => typeof value === "object" && value !== null && value.__type === "AsyncIteratorRef";
554
+ const addCallbackIdsToRefs = (value) => {
555
+ if (value === null || typeof value !== "object")
556
+ return value;
557
+ if (isPromiseRef(value)) {
558
+ if ("__resolveCallbackId" in value)
559
+ return value;
560
+ const resolveCallbackId = nextLocalCallbackId++;
561
+ returnedCallbacks.set(resolveCallbackId, async (promiseId) => {
562
+ const promise = returnedPromises.get(promiseId);
563
+ if (!promise) {
564
+ throw new Error(`Promise ${promiseId} not found`);
565
+ }
566
+ const result2 = await promise;
567
+ returnedPromises.delete(promiseId);
568
+ const ctx = createMarshalContext();
569
+ const marshalled = await marshalValue(result2, ctx);
570
+ return addCallbackIdsToRefs(marshalled);
571
+ });
572
+ return { ...value, __resolveCallbackId: resolveCallbackId };
573
+ }
574
+ if (isAsyncIteratorRef(value)) {
575
+ if ("__nextCallbackId" in value)
576
+ return value;
577
+ const nextCallbackId = nextLocalCallbackId++;
578
+ returnedCallbacks.set(nextCallbackId, async (iteratorId) => {
579
+ const iterator = returnedIterators.get(iteratorId);
580
+ if (!iterator) {
581
+ throw new Error(`Iterator ${iteratorId} not found`);
582
+ }
583
+ const result2 = await iterator.next();
584
+ if (result2.done) {
585
+ returnedIterators.delete(iteratorId);
586
+ }
587
+ const ctx = createMarshalContext();
588
+ const marshalledValue = await marshalValue(result2.value, ctx);
589
+ return {
590
+ done: result2.done,
591
+ value: addCallbackIdsToRefs(marshalledValue)
592
+ };
593
+ });
594
+ const returnCallbackId = nextLocalCallbackId++;
595
+ returnedCallbacks.set(returnCallbackId, async (iteratorId, returnValue) => {
596
+ const iterator = returnedIterators.get(iteratorId);
597
+ returnedIterators.delete(iteratorId);
598
+ if (!iterator || !iterator.return) {
599
+ return { done: true, value: undefined };
600
+ }
601
+ const result2 = await iterator.return(returnValue);
602
+ const ctx = createMarshalContext();
603
+ const marshalledValue = await marshalValue(result2.value, ctx);
604
+ return {
605
+ done: true,
606
+ value: addCallbackIdsToRefs(marshalledValue)
607
+ };
608
+ });
609
+ return {
610
+ ...value,
611
+ __nextCallbackId: nextCallbackId,
612
+ __returnCallbackId: returnCallbackId
613
+ };
614
+ }
615
+ if (Array.isArray(value)) {
616
+ return value.map((item) => addCallbackIdsToRefs(item));
617
+ }
618
+ const result = {};
619
+ for (const key of Object.keys(value)) {
620
+ result[key] = addCallbackIdsToRefs(value[key]);
621
+ }
622
+ return result;
623
+ };
624
+ const invokeCallback = async (callbackId, args) => {
625
+ const callback = returnedCallbacks.get(callbackId);
626
+ if (!callback) {
627
+ throw new Error(`Local callback ${callbackId} not found`);
628
+ }
629
+ return await callback(...args);
630
+ };
631
+ return { createMarshalContext, addCallbackIdsToRefs, invokeCallback };
632
+ }
421
633
  function createModuleResolver(state) {
422
634
  return async (specifier, referrer) => {
635
+ const staticCached = state.staticModuleCache.get(specifier);
636
+ if (staticCached)
637
+ return staticCached;
423
638
  const cached = state.moduleCache.get(specifier);
424
639
  if (cached)
425
640
  return cached;
@@ -428,16 +643,21 @@ function createModuleResolver(state) {
428
643
  }
429
644
  const importerPath = state.moduleToFilename.get(referrer) ?? "<unknown>";
430
645
  const importerResolveDir = path.posix.dirname(importerPath);
431
- const { code, resolveDir } = await state.moduleLoader(specifier, {
646
+ const result = await state.moduleLoader(specifier, {
432
647
  path: importerPath,
433
648
  resolveDir: importerResolveDir
434
649
  });
650
+ const { code, resolveDir } = result;
435
651
  const hash = contentHash(code);
436
652
  const cacheKey = `${specifier}:${hash}`;
437
653
  const hashCached = state.moduleCache.get(cacheKey);
438
654
  if (hashCached)
439
655
  return hashCached;
440
- const transformed = await transformModuleCode(code, specifier);
656
+ let transformed = state.transformCache.get(hash);
657
+ if (!transformed) {
658
+ transformed = await transformModuleCode(code, specifier);
659
+ state.transformCache.set(hash, transformed);
660
+ }
441
661
  if (transformed.sourceMap) {
442
662
  state.sourceMaps.set(specifier, transformed.sourceMap);
443
663
  }
@@ -446,8 +666,12 @@ function createModuleResolver(state) {
446
666
  });
447
667
  const resolvedPath = path.posix.join(resolveDir, path.posix.basename(specifier));
448
668
  state.moduleToFilename.set(mod, resolvedPath);
449
- state.moduleCache.set(specifier, mod);
450
- state.moduleCache.set(cacheKey, mod);
669
+ if (result.static) {
670
+ state.staticModuleCache.set(specifier, mod);
671
+ } else {
672
+ state.moduleCache.set(specifier, mod);
673
+ state.moduleCache.set(cacheKey, mod);
674
+ }
451
675
  const resolver = createModuleResolver(state);
452
676
  await mod.instantiate(state.context, resolver);
453
677
  return mod;
@@ -458,8 +682,8 @@ function convertFetchCallback(callback) {
458
682
  return {};
459
683
  }
460
684
  return {
461
- onFetch: async (request) => {
462
- return Promise.resolve(callback(request));
685
+ onFetch: async (url, init) => {
686
+ return Promise.resolve(callback(url, init));
463
687
  }
464
688
  };
465
689
  }
@@ -475,8 +699,11 @@ async function createRuntime(options) {
475
699
  context,
476
700
  handles: {},
477
701
  moduleCache: new Map,
702
+ staticModuleCache: new Map,
703
+ transformCache: new Map,
478
704
  moduleToFilename: new Map,
479
705
  sourceMaps: new Map,
706
+ pendingCallbacks: [],
480
707
  moduleLoader: opts.moduleLoader,
481
708
  customFunctions: opts.customFunctions
482
709
  };
@@ -491,13 +718,17 @@ async function createRuntime(options) {
491
718
  state.handles.fs = await setupFs(context, opts.fs);
492
719
  }
493
720
  if (opts.customFunctions) {
494
- state.customFnInvokeRef = await setupCustomFunctions(context, opts.customFunctions);
721
+ const customMarshalOptions = opts.customFunctionsMarshalOptions ?? createLocalCustomFunctionsMarshalOptions();
722
+ state.customFnInvokeRef = await setupCustomFunctions(context, opts.customFunctions, customMarshalOptions);
495
723
  }
496
724
  if (opts.testEnvironment) {
497
725
  const testEnvOptions = typeof opts.testEnvironment === "object" ? opts.testEnvironment : undefined;
498
726
  state.handles.testEnvironment = await setupTestEnvironment(context, testEnvOptions);
499
727
  }
500
728
  if (opts.playwright) {
729
+ if (!opts.playwright.handler) {
730
+ throw new Error("Playwright configured without handler. Provide playwright.handler in createRuntime options.");
731
+ }
501
732
  let eventCallback = opts.playwright.onEvent;
502
733
  if (opts.playwright.console && opts.console?.onEntry) {
503
734
  const originalCallback = eventCallback;
@@ -516,13 +747,13 @@ async function createRuntime(options) {
516
747
  }
517
748
  };
518
749
  }
519
- state.handles.playwright = await setupPlaywright(context, {
520
- page: opts.playwright.page,
750
+ const playwrightSetupOptions = {
751
+ handler: opts.playwright.handler,
521
752
  timeout: opts.playwright.timeout,
522
- baseUrl: opts.playwright.baseUrl,
523
753
  console: opts.playwright.console && !opts.console?.onEntry,
524
754
  onEvent: eventCallback
525
- });
755
+ };
756
+ state.handles.playwright = await setupPlaywright(context, playwrightSetupOptions);
526
757
  }
527
758
  const fetchHandle = {
528
759
  async dispatchRequest(request, options2) {
@@ -578,6 +809,36 @@ async function createRuntime(options) {
578
809
  throw new Error("Fetch handle not available");
579
810
  }
580
811
  return state.handles.fetch.hasActiveConnections();
812
+ },
813
+ dispatchClientWebSocketOpen(socketId, protocol, extensions) {
814
+ if (!state.handles.fetch) {
815
+ throw new Error("Fetch handle not available");
816
+ }
817
+ state.handles.fetch.dispatchClientWebSocketOpen(socketId, protocol, extensions);
818
+ },
819
+ dispatchClientWebSocketMessage(socketId, data) {
820
+ if (!state.handles.fetch) {
821
+ throw new Error("Fetch handle not available");
822
+ }
823
+ state.handles.fetch.dispatchClientWebSocketMessage(socketId, data);
824
+ },
825
+ dispatchClientWebSocketClose(socketId, code, reason, wasClean) {
826
+ if (!state.handles.fetch) {
827
+ throw new Error("Fetch handle not available");
828
+ }
829
+ state.handles.fetch.dispatchClientWebSocketClose(socketId, code, reason, wasClean);
830
+ },
831
+ dispatchClientWebSocketError(socketId) {
832
+ if (!state.handles.fetch) {
833
+ throw new Error("Fetch handle not available");
834
+ }
835
+ state.handles.fetch.dispatchClientWebSocketError(socketId);
836
+ },
837
+ onClientWebSocketCommand(callback) {
838
+ if (!state.handles.fetch) {
839
+ throw new Error("Fetch handle not available");
840
+ }
841
+ return state.handles.fetch.onClientWebSocketCommand(callback);
581
842
  }
582
843
  };
583
844
  const timersHandle = {
@@ -600,11 +861,27 @@ async function createRuntime(options) {
600
861
  }
601
862
  };
602
863
  const testEnvironmentHandle = {
603
- async runTests(_timeout) {
864
+ async runTests(timeout) {
604
865
  if (!state.handles.testEnvironment) {
605
866
  throw new Error("Test environment not enabled. Set testEnvironment: true in createRuntime options.");
606
867
  }
607
- return runTestsInContext(state.context);
868
+ if (timeout === undefined) {
869
+ return runTestsInContext(state.context);
870
+ }
871
+ let timeoutId;
872
+ const timeoutPromise = new Promise((_, reject) => {
873
+ timeoutId = setTimeout(() => reject(new Error("Test timeout")), timeout);
874
+ });
875
+ try {
876
+ return await Promise.race([
877
+ runTestsInContext(state.context),
878
+ timeoutPromise
879
+ ]);
880
+ } finally {
881
+ if (timeoutId) {
882
+ clearTimeout(timeoutId);
883
+ }
884
+ }
608
885
  },
609
886
  hasTests() {
610
887
  if (!state.handles.testEnvironment) {
@@ -625,7 +902,7 @@ async function createRuntime(options) {
625
902
  const playwrightHandle = {
626
903
  getCollectedData() {
627
904
  if (!state.handles.playwright) {
628
- throw new Error("Playwright not configured. Provide playwright.page in createRuntime options.");
905
+ throw new Error("Playwright not configured. Provide playwright.handler in createRuntime options.");
629
906
  }
630
907
  return {
631
908
  browserConsoleLogs: state.handles.playwright.getBrowserConsoleLogs(),
@@ -639,6 +916,7 @@ async function createRuntime(options) {
639
916
  };
640
917
  return {
641
918
  id,
919
+ pendingCallbacks: state.pendingCallbacks,
642
920
  fetch: fetchHandle,
643
921
  timers: timersHandle,
644
922
  console: consoleHandle,
@@ -669,6 +947,10 @@ async function createRuntime(options) {
669
947
  } finally {
670
948
  runRef.release();
671
949
  }
950
+ if (state.pendingCallbacks.length > 0) {
951
+ await Promise.all(state.pendingCallbacks);
952
+ state.pendingCallbacks.length = 0;
953
+ }
672
954
  } catch (err) {
673
955
  const error = err;
674
956
  if (error.stack && state.sourceMaps.size > 0) {
@@ -677,6 +959,11 @@ async function createRuntime(options) {
677
959
  throw error;
678
960
  }
679
961
  },
962
+ clearModuleCache() {
963
+ state.moduleCache.clear();
964
+ state.moduleToFilename.clear();
965
+ state.sourceMaps.clear();
966
+ },
680
967
  async dispose() {
681
968
  if (state.customFnInvokeRef) {
682
969
  state.customFnInvokeRef.release();
@@ -713,9 +1000,11 @@ export {
713
1000
  normalizeEntryFilename2 as normalizeEntryFilename,
714
1001
  hasTests,
715
1002
  getTestCount,
1003
+ getDefaultPlaywrightHandlerMetadata,
1004
+ defaultPlaywrightHandler,
716
1005
  createRuntime,
717
1006
  createPlaywrightHandler,
718
1007
  createNodeFileSystemHandler
719
1008
  };
720
1009
 
721
- //# debugId=F9EB26C7D1E4C4FF64756E2164756E21
1010
+ //# debugId=D363252DB857D59164756E2164756E21