@ricsam/isolate-runtime 0.1.14 → 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,12 +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
753
  console: opts.playwright.console && !opts.console?.onEntry,
523
754
  onEvent: eventCallback
524
- });
755
+ };
756
+ state.handles.playwright = await setupPlaywright(context, playwrightSetupOptions);
525
757
  }
526
758
  const fetchHandle = {
527
759
  async dispatchRequest(request, options2) {
@@ -577,6 +809,36 @@ async function createRuntime(options) {
577
809
  throw new Error("Fetch handle not available");
578
810
  }
579
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);
580
842
  }
581
843
  };
582
844
  const timersHandle = {
@@ -599,11 +861,27 @@ async function createRuntime(options) {
599
861
  }
600
862
  };
601
863
  const testEnvironmentHandle = {
602
- async runTests(_timeout) {
864
+ async runTests(timeout) {
603
865
  if (!state.handles.testEnvironment) {
604
866
  throw new Error("Test environment not enabled. Set testEnvironment: true in createRuntime options.");
605
867
  }
606
- 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
+ }
607
885
  },
608
886
  hasTests() {
609
887
  if (!state.handles.testEnvironment) {
@@ -624,7 +902,7 @@ async function createRuntime(options) {
624
902
  const playwrightHandle = {
625
903
  getCollectedData() {
626
904
  if (!state.handles.playwright) {
627
- 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.");
628
906
  }
629
907
  return {
630
908
  browserConsoleLogs: state.handles.playwright.getBrowserConsoleLogs(),
@@ -638,6 +916,7 @@ async function createRuntime(options) {
638
916
  };
639
917
  return {
640
918
  id,
919
+ pendingCallbacks: state.pendingCallbacks,
641
920
  fetch: fetchHandle,
642
921
  timers: timersHandle,
643
922
  console: consoleHandle,
@@ -668,6 +947,10 @@ async function createRuntime(options) {
668
947
  } finally {
669
948
  runRef.release();
670
949
  }
950
+ if (state.pendingCallbacks.length > 0) {
951
+ await Promise.all(state.pendingCallbacks);
952
+ state.pendingCallbacks.length = 0;
953
+ }
671
954
  } catch (err) {
672
955
  const error = err;
673
956
  if (error.stack && state.sourceMaps.size > 0) {
@@ -676,6 +959,11 @@ async function createRuntime(options) {
676
959
  throw error;
677
960
  }
678
961
  },
962
+ clearModuleCache() {
963
+ state.moduleCache.clear();
964
+ state.moduleToFilename.clear();
965
+ state.sourceMaps.clear();
966
+ },
679
967
  async dispose() {
680
968
  if (state.customFnInvokeRef) {
681
969
  state.customFnInvokeRef.release();
@@ -712,9 +1000,11 @@ export {
712
1000
  normalizeEntryFilename2 as normalizeEntryFilename,
713
1001
  hasTests,
714
1002
  getTestCount,
1003
+ getDefaultPlaywrightHandlerMetadata,
1004
+ defaultPlaywrightHandler,
715
1005
  createRuntime,
716
1006
  createPlaywrightHandler,
717
1007
  createNodeFileSystemHandler
718
1008
  };
719
1009
 
720
- //# debugId=5B1C4DA72893C8FA64756E2164756E21
1010
+ //# debugId=D363252DB857D59164756E2164756E21