@ricsam/isolate-runtime 0.1.15 → 0.1.17

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.
@@ -632,49 +632,102 @@ function createLocalCustomFunctionsMarshalOptions() {
632
632
  }
633
633
  function createModuleResolver(state) {
634
634
  return async (specifier, referrer) => {
635
- const staticCached = state.staticModuleCache.get(specifier);
635
+ const importerPath = state.moduleToFilename.get(referrer) ?? "<unknown>";
636
+ const importerResolveDir = path.posix.dirname(importerPath);
637
+ const importerStack = state.moduleImportChain.get(importerPath) ?? [];
638
+ const resolvedSpecifier = specifier.startsWith(".") ? path.posix.normalize(path.posix.join(importerResolveDir, specifier)) : specifier;
639
+ state.specifierToImporter.set(resolvedSpecifier, importerPath);
640
+ const staticCached = state.staticModuleCache.get(resolvedSpecifier);
636
641
  if (staticCached)
637
642
  return staticCached;
638
- const cached = state.moduleCache.get(specifier);
643
+ const cached = state.moduleCache.get(resolvedSpecifier);
639
644
  if (cached)
640
645
  return cached;
641
646
  if (!state.moduleLoader) {
642
647
  throw new Error(`No module loader registered. Cannot import: ${specifier}`);
643
648
  }
644
- const importerPath = state.moduleToFilename.get(referrer) ?? "<unknown>";
645
- const importerResolveDir = path.posix.dirname(importerPath);
646
649
  const result = await state.moduleLoader(specifier, {
647
650
  path: importerPath,
648
651
  resolveDir: importerResolveDir
649
652
  });
650
653
  const { code, resolveDir } = result;
654
+ if (result.filename.includes("/")) {
655
+ throw new Error(`moduleLoader returned a filename with slashes: "${result.filename}". ` + `filename must be a basename (e.g. "utils.js"), not a path.`);
656
+ }
657
+ const resolvedFilename = path.posix.join(resolveDir, result.filename);
651
658
  const hash = contentHash(code);
652
- const cacheKey = `${specifier}:${hash}`;
659
+ const cacheKey = `${resolvedSpecifier}:${hash}`;
660
+ const inFlightKey = `${result.static ? "static" : "dynamic"}:${cacheKey}`;
661
+ const staticCachedAfterLoad = state.staticModuleCache.get(resolvedSpecifier);
662
+ if (staticCachedAfterLoad)
663
+ return staticCachedAfterLoad;
664
+ const cachedAfterLoad = state.moduleCache.get(resolvedSpecifier);
665
+ if (cachedAfterLoad)
666
+ return cachedAfterLoad;
653
667
  const hashCached = state.moduleCache.get(cacheKey);
654
668
  if (hashCached)
655
669
  return hashCached;
656
- let transformed = state.transformCache.get(hash);
657
- if (!transformed) {
658
- transformed = await transformModuleCode(code, specifier);
659
- state.transformCache.set(hash, transformed);
660
- }
661
- if (transformed.sourceMap) {
662
- state.sourceMaps.set(specifier, transformed.sourceMap);
670
+ const inFlight = state.moduleLoadsInFlight.get(inFlightKey);
671
+ if (inFlight)
672
+ return inFlight;
673
+ const loadPromise = (async () => {
674
+ let mod;
675
+ try {
676
+ let transformed = state.transformCache.get(hash);
677
+ if (!transformed) {
678
+ transformed = await transformModuleCode(code, resolvedSpecifier);
679
+ state.transformCache.set(hash, transformed);
680
+ }
681
+ if (transformed.sourceMap) {
682
+ state.sourceMaps.set(resolvedSpecifier, transformed.sourceMap);
683
+ }
684
+ mod = await state.isolate.compileModule(transformed.code, {
685
+ filename: resolvedSpecifier
686
+ });
687
+ state.moduleToFilename.set(mod, resolvedFilename);
688
+ state.moduleImportChain.set(resolvedFilename, [...importerStack, importerPath]);
689
+ if (result.static) {
690
+ state.staticModuleCache.set(resolvedSpecifier, mod);
691
+ } else {
692
+ state.moduleCache.set(resolvedSpecifier, mod);
693
+ state.moduleCache.set(cacheKey, mod);
694
+ }
695
+ return mod;
696
+ } catch (err) {
697
+ const error = err instanceof Error ? err : new Error(String(err));
698
+ error.message = `Failed to compile module "${resolvedSpecifier}" (imported by "${importerPath}"):
699
+ ${error.message}`;
700
+ if (importerStack.length > 0) {
701
+ error.message += `
702
+
703
+ Import chain:
704
+ ${[...importerStack, importerPath].join(`
705
+ -> `)}`;
706
+ }
707
+ if (mod) {
708
+ state.moduleToFilename.delete(mod);
709
+ if (result.static) {
710
+ if (state.staticModuleCache.get(resolvedSpecifier) === mod) {
711
+ state.staticModuleCache.delete(resolvedSpecifier);
712
+ }
713
+ } else {
714
+ if (state.moduleCache.get(resolvedSpecifier) === mod) {
715
+ state.moduleCache.delete(resolvedSpecifier);
716
+ }
717
+ if (state.moduleCache.get(cacheKey) === mod) {
718
+ state.moduleCache.delete(cacheKey);
719
+ }
720
+ }
721
+ }
722
+ throw error;
723
+ }
724
+ })();
725
+ state.moduleLoadsInFlight.set(inFlightKey, loadPromise);
726
+ try {
727
+ return await loadPromise;
728
+ } finally {
729
+ state.moduleLoadsInFlight.delete(inFlightKey);
663
730
  }
664
- const mod = await state.isolate.compileModule(transformed.code, {
665
- filename: specifier
666
- });
667
- const resolvedPath = path.posix.join(resolveDir, path.posix.basename(specifier));
668
- state.moduleToFilename.set(mod, resolvedPath);
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
- }
675
- const resolver = createModuleResolver(state);
676
- await mod.instantiate(state.context, resolver);
677
- return mod;
678
731
  };
679
732
  }
680
733
  function convertFetchCallback(callback) {
@@ -700,10 +753,14 @@ async function createRuntime(options) {
700
753
  handles: {},
701
754
  moduleCache: new Map,
702
755
  staticModuleCache: new Map,
756
+ moduleLoadsInFlight: new Map,
703
757
  transformCache: new Map,
704
758
  moduleToFilename: new Map,
705
759
  sourceMaps: new Map,
760
+ moduleImportChain: new Map,
761
+ specifierToImporter: new Map,
706
762
  pendingCallbacks: [],
763
+ evalChain: Promise.resolve(),
707
764
  moduleLoader: opts.moduleLoader,
708
765
  customFunctions: opts.customFunctions
709
766
  };
@@ -839,6 +896,18 @@ async function createRuntime(options) {
839
896
  throw new Error("Fetch handle not available");
840
897
  }
841
898
  return state.handles.fetch.onClientWebSocketCommand(callback);
899
+ },
900
+ onEvent(callback) {
901
+ if (!state.handles.fetch) {
902
+ throw new Error("Fetch handle not available");
903
+ }
904
+ return state.handles.fetch.onEvent(callback);
905
+ },
906
+ dispatchEvent(event, payload) {
907
+ if (!state.handles.fetch) {
908
+ throw new Error("Fetch handle not available");
909
+ }
910
+ state.handles.fetch.dispatchEvent(event, payload);
842
911
  }
843
912
  };
844
913
  const timersHandle = {
@@ -923,45 +992,101 @@ async function createRuntime(options) {
923
992
  testEnvironment: testEnvironmentHandle,
924
993
  playwright: playwrightHandle,
925
994
  async eval(code, filenameOrOptions) {
926
- const options2 = typeof filenameOrOptions === "string" ? { filename: filenameOrOptions } : filenameOrOptions;
927
- const filename = normalizeEntryFilename(options2?.filename);
928
- try {
929
- const transformed = await transformEntryCode(code, filename);
930
- if (transformed.sourceMap) {
931
- state.sourceMaps.set(filename, transformed.sourceMap);
932
- }
933
- const mod = await state.isolate.compileModule(transformed.code, {
934
- filename
935
- });
936
- state.moduleToFilename.set(mod, filename);
937
- const resolver = createModuleResolver(state);
938
- await mod.instantiate(state.context, resolver);
939
- await mod.evaluate();
940
- const ns = mod.namespace;
941
- const runRef = await ns.get("default", { reference: true });
995
+ const runEval = async () => {
996
+ const options2 = typeof filenameOrOptions === "string" ? { filename: filenameOrOptions } : filenameOrOptions;
997
+ const filename = normalizeEntryFilename(options2?.filename);
942
998
  try {
943
- await runRef.apply(undefined, [], {
944
- result: { promise: true },
945
- ...options2?.maxExecutionMs ? { timeout: options2.maxExecutionMs } : {}
999
+ const transformed = await transformEntryCode(code, filename);
1000
+ if (transformed.sourceMap) {
1001
+ state.sourceMaps.set(filename, transformed.sourceMap);
1002
+ }
1003
+ const mod = await state.isolate.compileModule(transformed.code, {
1004
+ filename
946
1005
  });
947
- } finally {
948
- runRef.release();
949
- }
950
- if (state.pendingCallbacks.length > 0) {
951
- await Promise.all(state.pendingCallbacks);
952
- state.pendingCallbacks.length = 0;
953
- }
954
- } catch (err) {
955
- const error = err;
956
- if (error.stack && state.sourceMaps.size > 0) {
957
- error.stack = mapErrorStack(error.stack, state.sourceMaps);
1006
+ state.moduleToFilename.set(mod, filename);
1007
+ state.moduleImportChain.set(filename, []);
1008
+ const resolver = createModuleResolver(state);
1009
+ try {
1010
+ await mod.instantiate(state.context, resolver);
1011
+ } catch (err) {
1012
+ const error = err instanceof Error ? err : new Error(String(err));
1013
+ const specifierMatch = error.message.match(/The requested module '([^']+)'/);
1014
+ const exportMatch = error.message.match(/export named '([^']+)'/);
1015
+ const failingSpecifier = specifierMatch?.[1];
1016
+ const failingExport = exportMatch?.[1];
1017
+ const importerFile = failingSpecifier ? state.specifierToImporter.get(failingSpecifier) : undefined;
1018
+ const details = [];
1019
+ if (importerFile) {
1020
+ const chain = state.moduleImportChain.get(importerFile) ?? [];
1021
+ const fullChain = [...chain, importerFile];
1022
+ if (failingSpecifier)
1023
+ fullChain.push(failingSpecifier);
1024
+ const trimmed = fullChain.length > 12 ? fullChain.slice(-12) : fullChain;
1025
+ const prefix = fullChain.length > 12 ? ` ...
1026
+ ` : "";
1027
+ details.push(`Import chain:
1028
+ ` + prefix + trimmed.map((p) => ` ${p}`).join(`
1029
+ -> `));
1030
+ } else if (failingSpecifier) {
1031
+ for (const [modPath, chain] of state.moduleImportChain) {
1032
+ if (modPath.includes(failingSpecifier) || modPath.endsWith(failingSpecifier)) {
1033
+ const fullChain = [...chain, modPath];
1034
+ const trimmed = fullChain.length > 12 ? fullChain.slice(-12) : fullChain;
1035
+ details.push(`Import chain:
1036
+ ` + trimmed.map((p) => ` ${p}`).join(`
1037
+ -> `));
1038
+ break;
1039
+ }
1040
+ }
1041
+ }
1042
+ if (failingExport && failingSpecifier) {
1043
+ details.push(`Hint: If '${failingExport}' is a TypeScript type/interface, use \`import type\` to prevent it from being resolved at runtime:
1044
+ ` + ` import type { ${failingExport} } from '${failingSpecifier}';`);
1045
+ }
1046
+ const suffix = details.length > 0 ? `
1047
+
1048
+ ` + details.join(`
1049
+
1050
+ `) : "";
1051
+ error.message = `Module instantiation failed: ${error.message}${suffix}`;
1052
+ throw error;
1053
+ }
1054
+ await mod.evaluate();
1055
+ const ns = mod.namespace;
1056
+ const runRef = await ns.get("default", { reference: true });
1057
+ try {
1058
+ await runRef.apply(undefined, [], {
1059
+ result: { promise: true }
1060
+ });
1061
+ } finally {
1062
+ runRef.release();
1063
+ }
1064
+ if (state.pendingCallbacks.length > 0) {
1065
+ await Promise.all(state.pendingCallbacks);
1066
+ state.pendingCallbacks.length = 0;
1067
+ }
1068
+ } catch (err) {
1069
+ const error = err;
1070
+ if (error.stack && state.sourceMaps.size > 0) {
1071
+ error.stack = mapErrorStack(error.stack, state.sourceMaps);
1072
+ }
1073
+ throw error;
958
1074
  }
959
- throw error;
960
- }
1075
+ };
1076
+ const queuedEval = state.evalChain.then(runEval, runEval);
1077
+ state.evalChain = queuedEval.then(() => {
1078
+ return;
1079
+ }, () => {
1080
+ return;
1081
+ });
1082
+ return queuedEval;
961
1083
  },
962
1084
  clearModuleCache() {
963
1085
  state.moduleCache.clear();
1086
+ state.moduleLoadsInFlight.clear();
964
1087
  state.moduleToFilename.clear();
1088
+ state.moduleImportChain.clear();
1089
+ state.specifierToImporter.clear();
965
1090
  state.sourceMaps.clear();
966
1091
  },
967
1092
  async dispose() {
@@ -979,6 +1104,7 @@ async function createRuntime(options) {
979
1104
  state.handles.console?.dispose();
980
1105
  state.handles.core?.dispose();
981
1106
  state.moduleCache.clear();
1107
+ state.moduleLoadsInFlight.clear();
982
1108
  state.context.release();
983
1109
  state.isolate.dispose();
984
1110
  }
@@ -1007,4 +1133,4 @@ export {
1007
1133
  createNodeFileSystemHandler
1008
1134
  };
1009
1135
 
1010
- //# debugId=D363252DB857D59164756E2164756E21
1136
+ //# debugId=30C3A0A75715847864756E2164756E21