sandlot 0.2.1 → 0.2.3

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.
Files changed (43) hide show
  1. package/dist/browser/bundler.d.ts +8 -0
  2. package/dist/browser/bundler.d.ts.map +1 -1
  3. package/dist/browser/iframe-executor.d.ts +82 -0
  4. package/dist/browser/iframe-executor.d.ts.map +1 -0
  5. package/dist/browser/index.d.ts +4 -2
  6. package/dist/browser/index.d.ts.map +1 -1
  7. package/dist/browser/index.js +339 -76
  8. package/dist/browser/main-thread-executor.d.ts +46 -0
  9. package/dist/browser/main-thread-executor.d.ts.map +1 -0
  10. package/dist/browser/preset.d.ts +7 -2
  11. package/dist/browser/preset.d.ts.map +1 -1
  12. package/dist/core/bundler-utils.d.ts +43 -1
  13. package/dist/core/bundler-utils.d.ts.map +1 -1
  14. package/dist/core/executor.d.ts.map +1 -1
  15. package/dist/core/sandbox.d.ts.map +1 -1
  16. package/dist/core/sandlot.d.ts.map +1 -1
  17. package/dist/index.js +63 -0
  18. package/dist/node/bundler.d.ts +5 -0
  19. package/dist/node/bundler.d.ts.map +1 -1
  20. package/dist/node/index.d.ts +2 -0
  21. package/dist/node/index.d.ts.map +1 -1
  22. package/dist/node/index.js +243 -75
  23. package/dist/node/preset.d.ts +16 -1
  24. package/dist/node/preset.d.ts.map +1 -1
  25. package/dist/node/wasm-bundler.d.ts +86 -0
  26. package/dist/node/wasm-bundler.d.ts.map +1 -0
  27. package/dist/types.d.ts +25 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/package.json +1 -1
  30. package/src/browser/bundler.ts +23 -106
  31. package/src/browser/iframe-executor.ts +320 -0
  32. package/src/browser/index.ts +9 -2
  33. package/src/browser/preset.ts +30 -6
  34. package/src/core/bundler-utils.ts +148 -0
  35. package/src/core/executor.ts +8 -7
  36. package/src/core/sandbox.ts +82 -0
  37. package/src/core/sandlot.ts +7 -0
  38. package/src/node/bundler.ts +17 -110
  39. package/src/node/index.ts +10 -0
  40. package/src/node/preset.ts +59 -5
  41. package/src/node/wasm-bundler.ts +195 -0
  42. package/src/types.ts +27 -0
  43. /package/src/browser/{executor.ts → main-thread-executor.ts} +0 -0
@@ -62,6 +62,14 @@ export declare class EsbuildWasmBundler implements IBundler {
62
62
  * Get the initialized esbuild instance.
63
63
  */
64
64
  private getEsbuild;
65
+ /**
66
+ * Dispose of the esbuild WASM service.
67
+ * This stops the esbuild service and allows the process to exit.
68
+ *
69
+ * Note: Since esbuild-wasm uses a global singleton, this affects all
70
+ * instances. After dispose(), you'll need to create a new bundler.
71
+ */
72
+ dispose(): Promise<void>;
65
73
  private checkCrossOriginIsolation;
66
74
  bundle(options: BundleOptions): Promise<BundleResult>;
67
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bundler.d.ts","sourceRoot":"","sources":["../../src/browser/bundler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACV,QAAQ,EACR,aAAa,EACb,YAAY,EAGb,MAAM,UAAU,CAAC;AAuClB,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,kBAAmB,YAAW,QAAQ;IACjD,OAAO,CAAC,OAAO,CAA4B;gBAE/B,OAAO,GAAE,yBAA8B;IAWnD;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAmBnB,YAAY;IA8B1B;;OAEG;IACH,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,yBAAyB;IAc3B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CAoG5D"}
1
+ {"version":3,"file":"bundler.d.ts","sourceRoot":"","sources":["../../src/browser/bundler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAmCtE,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,kBAAmB,YAAW,QAAQ;IACjD,OAAO,CAAC,OAAO,CAA4B;gBAE/B,OAAO,GAAE,yBAA8B;IAWnD;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAmBnB,YAAY;IA8B1B;;OAEG;IACH,OAAO,CAAC,UAAU;IAQlB;;;;;;OAMG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAU9B,OAAO,CAAC,yBAAyB;IAc3B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;CAU5D"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Iframe executor for browser environments.
3
+ *
4
+ * This executor runs code in a sandboxed iframe, providing DOM isolation
5
+ * and configurable security policies via the sandbox attribute.
6
+ *
7
+ * Key characteristics:
8
+ * - Per-execution lifecycle: fresh iframe for each execute() call
9
+ * - Configurable sandbox attributes (default: allow-scripts only)
10
+ * - No shared module support (use MainThreadExecutor for that)
11
+ * - Communication via postMessage
12
+ */
13
+ import type { IExecutor, ExecuteOptions, ExecuteResult } from "../types";
14
+ /**
15
+ * Options for creating an IframeExecutor.
16
+ */
17
+ export interface IframeExecutorOptions {
18
+ /**
19
+ * Sandbox attributes for the iframe.
20
+ * @default ["allow-scripts"]
21
+ *
22
+ * Common options:
23
+ * - "allow-scripts": Required for code execution
24
+ * - "allow-same-origin": Enables localStorage, cookies (reduces isolation)
25
+ * - "allow-modals": Enables alert/confirm/prompt
26
+ *
27
+ * Security note: "allow-scripts" + "allow-same-origin" together allows
28
+ * the iframe code to potentially remove the sandbox via script.
29
+ */
30
+ sandbox?: string[];
31
+ /**
32
+ * Default timeout in milliseconds.
33
+ * @default 30000
34
+ */
35
+ defaultTimeout?: number;
36
+ /**
37
+ * Container element for iframes.
38
+ * Iframes are created hidden (display: none).
39
+ * @default document.body
40
+ */
41
+ container?: HTMLElement;
42
+ }
43
+ /**
44
+ * Executor that runs code in a sandboxed iframe.
45
+ *
46
+ * Each execute() call creates a fresh iframe, runs the code, and destroys
47
+ * the iframe. This provides clean isolation between executions.
48
+ *
49
+ * Note: This executor does NOT support shared modules. The iframe runs
50
+ * in complete isolation. Use MainThreadExecutor if you need shared modules.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // Default: strict sandboxing (allow-scripts only)
55
+ * const executor = createIframeExecutor();
56
+ *
57
+ * // With additional permissions
58
+ * const executor = createIframeExecutor({
59
+ * sandbox: ["allow-scripts", "allow-same-origin"],
60
+ * });
61
+ *
62
+ * const result = await executor.execute(bundledCode, {
63
+ * entryExport: 'main',
64
+ * context: { args: ['--verbose'] },
65
+ * timeout: 5000,
66
+ * });
67
+ * console.log(result.logs);
68
+ * ```
69
+ */
70
+ export declare class IframeExecutor implements IExecutor {
71
+ private options;
72
+ constructor(options?: IframeExecutorOptions);
73
+ execute(code: string, options?: ExecuteOptions): Promise<ExecuteResult>;
74
+ }
75
+ /**
76
+ * Create an iframe executor.
77
+ *
78
+ * @param options - Executor options
79
+ * @returns A new IframeExecutor instance
80
+ */
81
+ export declare function createIframeExecutor(options?: IframeExecutorOptions): IframeExecutor;
82
+ //# sourceMappingURL=iframe-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iframe-executor.d.ts","sourceRoot":"","sources":["../../src/browser/iframe-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;CACzB;AAmID;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,cAAe,YAAW,SAAS;IAC9C,OAAO,CAAC,OAAO,CAEb;gBAEU,OAAO,GAAE,qBAA0B;IAQzC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;CA2FlF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAEhB"}
@@ -2,8 +2,10 @@ export { Typechecker, createTypechecker } from "../core/typechecker";
2
2
  export type { TypecheckerOptions } from "../core/typechecker";
3
3
  export { EsbuildWasmBundler } from "./bundler";
4
4
  export type { EsbuildWasmBundlerOptions } from "./bundler";
5
- export { MainThreadExecutor, createMainThreadExecutor } from "./executor";
6
- export type { MainThreadExecutorOptions } from "./executor";
5
+ export { MainThreadExecutor, createMainThreadExecutor } from "./main-thread-executor";
6
+ export type { MainThreadExecutorOptions } from "./main-thread-executor";
7
+ export { IframeExecutor, createIframeExecutor } from "./iframe-executor";
8
+ export type { IframeExecutorOptions } from "./iframe-executor";
7
9
  export { createBrowserSandlot } from "./preset";
8
10
  export type { CreateBrowserSandlotOptions } from "./preset";
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACrE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAM9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAM3D,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAC1E,YAAY,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAM5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,YAAY,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACrE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAM9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAM3D,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACtF,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAMxE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzE,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAM/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,YAAY,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC"}
@@ -649,6 +649,78 @@ function generateSharedModuleCode(moduleId, registry) {
649
649
  }
650
650
  return code;
651
651
  }
652
+ async function executeBundleWithEsbuild(options) {
653
+ const { esbuild, bundleOptions, cdnBaseUrl, bundleCdnImports } = options;
654
+ const {
655
+ fs,
656
+ entryPoint,
657
+ installedPackages = {},
658
+ sharedModules = [],
659
+ sharedModuleRegistry,
660
+ external = [],
661
+ format = "esm",
662
+ minify = false,
663
+ sourcemap = false,
664
+ target = ["es2020"]
665
+ } = bundleOptions;
666
+ const normalizedEntry = entryPoint.startsWith("/") ? entryPoint : `/${entryPoint}`;
667
+ if (!fs.exists(normalizedEntry)) {
668
+ return {
669
+ success: false,
670
+ errors: [{ text: `Entry point not found: ${normalizedEntry}` }],
671
+ warnings: []
672
+ };
673
+ }
674
+ const includedFiles = new Set;
675
+ const plugin = createVfsPlugin({
676
+ fs,
677
+ entryPoint: normalizedEntry,
678
+ installedPackages,
679
+ sharedModules: new Set(sharedModules),
680
+ sharedModuleRegistry: sharedModuleRegistry ?? null,
681
+ cdnBaseUrl,
682
+ includedFiles,
683
+ bundleCdnImports
684
+ });
685
+ try {
686
+ const result = await esbuild.build({
687
+ entryPoints: [normalizedEntry],
688
+ bundle: true,
689
+ write: false,
690
+ format,
691
+ minify,
692
+ sourcemap: sourcemap ? "inline" : false,
693
+ target,
694
+ external,
695
+ plugins: [plugin],
696
+ jsx: "automatic"
697
+ });
698
+ const code = result.outputFiles?.[0]?.text ?? "";
699
+ const warnings = result.warnings.map((w) => convertEsbuildMessage(w));
700
+ return {
701
+ success: true,
702
+ code,
703
+ warnings,
704
+ includedFiles: Array.from(includedFiles)
705
+ };
706
+ } catch (err) {
707
+ if (isEsbuildBuildFailure(err)) {
708
+ const errors = err.errors.map((e) => convertEsbuildMessage(e));
709
+ const warnings = err.warnings.map((w) => convertEsbuildMessage(w));
710
+ return {
711
+ success: false,
712
+ errors,
713
+ warnings
714
+ };
715
+ }
716
+ const message = err instanceof Error ? err.message : String(err);
717
+ return {
718
+ success: false,
719
+ errors: [{ text: message }],
720
+ warnings: []
721
+ };
722
+ }
723
+ }
652
724
 
653
725
  // src/browser/bundler.ts
654
726
  var ESBUILD_VERSION = "0.27.2";
@@ -708,6 +780,15 @@ class EsbuildWasmBundler {
708
780
  }
709
781
  return state.esbuild;
710
782
  }
783
+ async dispose() {
784
+ const state = getGlobalState();
785
+ if (state.esbuild) {
786
+ await state.esbuild.stop();
787
+ state.esbuild = null;
788
+ state.initialized = false;
789
+ state.initPromise = null;
790
+ }
791
+ }
711
792
  checkCrossOriginIsolation() {
712
793
  if (typeof window === "undefined")
713
794
  return;
@@ -720,75 +801,12 @@ To enable, add these headers to your server:
720
801
  }
721
802
  async bundle(options) {
722
803
  await this.initialize();
723
- const esbuild = this.getEsbuild();
724
- const {
725
- fs,
726
- entryPoint,
727
- installedPackages = {},
728
- sharedModules = [],
729
- sharedModuleRegistry,
730
- external = [],
731
- format = "esm",
732
- minify = false,
733
- sourcemap = false,
734
- target = ["es2020"]
735
- } = options;
736
- const normalizedEntry = entryPoint.startsWith("/") ? entryPoint : `/${entryPoint}`;
737
- if (!fs.exists(normalizedEntry)) {
738
- return {
739
- success: false,
740
- errors: [{ text: `Entry point not found: ${normalizedEntry}` }],
741
- warnings: []
742
- };
743
- }
744
- const includedFiles = new Set;
745
- const plugin = createVfsPlugin({
746
- fs,
747
- entryPoint: normalizedEntry,
748
- installedPackages,
749
- sharedModules: new Set(sharedModules),
750
- sharedModuleRegistry: sharedModuleRegistry ?? null,
804
+ return executeBundleWithEsbuild({
805
+ esbuild: this.getEsbuild(),
806
+ bundleOptions: options,
751
807
  cdnBaseUrl: this.options.cdnBaseUrl,
752
- includedFiles
808
+ bundleCdnImports: false
753
809
  });
754
- try {
755
- const result = await esbuild.build({
756
- entryPoints: [normalizedEntry],
757
- bundle: true,
758
- write: false,
759
- format,
760
- minify,
761
- sourcemap: sourcemap ? "inline" : false,
762
- target,
763
- external,
764
- plugins: [plugin],
765
- jsx: "automatic"
766
- });
767
- const code = result.outputFiles?.[0]?.text ?? "";
768
- const warnings = result.warnings.map((w) => convertEsbuildMessage(w));
769
- return {
770
- success: true,
771
- code,
772
- warnings,
773
- includedFiles: Array.from(includedFiles)
774
- };
775
- } catch (err) {
776
- if (isEsbuildBuildFailure(err)) {
777
- const errors = err.errors.map((e) => convertEsbuildMessage(e));
778
- const warnings = err.warnings.map((w) => convertEsbuildMessage(w));
779
- return {
780
- success: false,
781
- errors,
782
- warnings
783
- };
784
- }
785
- const message = err instanceof Error ? err.message : String(err);
786
- return {
787
- success: false,
788
- errors: [{ text: message }],
789
- warnings: []
790
- };
791
- }
792
810
  }
793
811
  }
794
812
  // src/core/executor.ts
@@ -813,23 +831,18 @@ function createBasicExecutor(loadModule, options = {}) {
813
831
  const formatArgs = (...args) => args.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(" ");
814
832
  const captureLog = (...args) => {
815
833
  logs.push(formatArgs(...args));
816
- originalConsole.log.apply(console, args);
817
834
  };
818
835
  const captureWarn = (...args) => {
819
836
  logs.push(`[warn] ${formatArgs(...args)}`);
820
- originalConsole.warn.apply(console, args);
821
837
  };
822
838
  const captureError = (...args) => {
823
839
  logs.push(`[error] ${formatArgs(...args)}`);
824
- originalConsole.error.apply(console, args);
825
840
  };
826
841
  const captureInfo = (...args) => {
827
842
  logs.push(`[info] ${formatArgs(...args)}`);
828
- originalConsole.info.apply(console, args);
829
843
  };
830
844
  const captureDebug = (...args) => {
831
845
  logs.push(`[debug] ${formatArgs(...args)}`);
832
- originalConsole.debug.apply(console, args);
833
846
  };
834
847
  const restoreConsole = () => {
835
848
  console.log = originalConsole.log;
@@ -856,10 +869,16 @@ function createBasicExecutor(loadModule, options = {}) {
856
869
  }
857
870
  };
858
871
  if (timeout > 0) {
872
+ let timeoutId;
859
873
  const timeoutPromise = new Promise((_, reject) => {
860
- setTimeout(() => reject(new Error(`Execution timed out after ${timeout}ms`)), timeout);
874
+ timeoutId = setTimeout(() => reject(new Error(`Execution timed out after ${timeout}ms`)), timeout);
861
875
  });
862
- await Promise.race([executeExport(), timeoutPromise]);
876
+ try {
877
+ await Promise.race([executeExport(), timeoutPromise]);
878
+ } finally {
879
+ if (timeoutId)
880
+ clearTimeout(timeoutId);
881
+ }
863
882
  } else {
864
883
  await executeExport();
865
884
  }
@@ -885,7 +904,7 @@ function createBasicExecutor(loadModule, options = {}) {
885
904
  };
886
905
  }
887
906
 
888
- // src/browser/executor.ts
907
+ // src/browser/main-thread-executor.ts
889
908
  async function loadModuleFromBlobUrl(code) {
890
909
  const blob = new Blob([code], { type: "application/javascript" });
891
910
  const url = URL.createObjectURL(blob);
@@ -906,6 +925,182 @@ class MainThreadExecutor {
906
925
  function createMainThreadExecutor(options) {
907
926
  return new MainThreadExecutor(options);
908
927
  }
928
+ // src/browser/iframe-executor.ts
929
+ var BOOTSTRAP_HTML = `<!DOCTYPE html>
930
+ <html>
931
+ <head>
932
+ <meta charset="utf-8">
933
+ </head>
934
+ <body>
935
+ <script type="module">
936
+ // Capture console methods and forward to parent
937
+ function formatArgs(...args) {
938
+ return args
939
+ .map(v => typeof v === "object" ? JSON.stringify(v) : String(v))
940
+ .join(" ");
941
+ }
942
+
943
+ function createLogger(level) {
944
+ return (...args) => {
945
+ parent.postMessage({ type: "log", level, args: formatArgs(...args) }, "*");
946
+ };
947
+ }
948
+
949
+ console.log = createLogger("log");
950
+ console.warn = createLogger("warn");
951
+ console.error = createLogger("error");
952
+ console.info = createLogger("info");
953
+ console.debug = createLogger("debug");
954
+
955
+ // Handle unhandled promise rejections
956
+ window.addEventListener("unhandledrejection", (event) => {
957
+ const message = event.reason instanceof Error
958
+ ? event.reason.message
959
+ : String(event.reason);
960
+ parent.postMessage({
961
+ type: "result",
962
+ success: false,
963
+ error: "Unhandled promise rejection: " + message
964
+ }, "*");
965
+ });
966
+
967
+ // Handle uncaught errors
968
+ window.addEventListener("error", (event) => {
969
+ parent.postMessage({
970
+ type: "result",
971
+ success: false,
972
+ error: event.message || "Unknown error"
973
+ }, "*");
974
+ });
975
+
976
+ // Listen for execute messages from parent
977
+ window.addEventListener("message", async (event) => {
978
+ if (event.data?.type !== "execute") return;
979
+
980
+ const { code, entryExport, context } = event.data;
981
+
982
+ try {
983
+ // Create Blob URL and import as ESM module
984
+ const blob = new Blob([code], { type: "application/javascript" });
985
+ const url = URL.createObjectURL(blob);
986
+
987
+ let module;
988
+ try {
989
+ module = await import(url);
990
+ } finally {
991
+ URL.revokeObjectURL(url);
992
+ }
993
+
994
+ // Execute the appropriate export
995
+ let returnValue;
996
+
997
+ if (entryExport === "main" && typeof module.main === "function") {
998
+ returnValue = await module.main(context);
999
+ } else if (entryExport === "default" && typeof module.default === "function") {
1000
+ returnValue = await module.default();
1001
+ } else if (entryExport === "default" && module.default !== undefined) {
1002
+ returnValue = module.default;
1003
+ }
1004
+ // If neither export exists, top-level code already ran on import
1005
+
1006
+ parent.postMessage({ type: "result", success: true, returnValue }, "*");
1007
+ } catch (err) {
1008
+ const message = err instanceof Error ? err.message : String(err);
1009
+ parent.postMessage({ type: "result", success: false, error: message }, "*");
1010
+ }
1011
+ });
1012
+
1013
+ // Signal that we're ready to receive code
1014
+ parent.postMessage({ type: "ready" }, "*");
1015
+ </script>
1016
+ </body>
1017
+ </html>`;
1018
+
1019
+ class IframeExecutor {
1020
+ options;
1021
+ constructor(options = {}) {
1022
+ this.options = {
1023
+ sandbox: options.sandbox ?? ["allow-scripts"],
1024
+ defaultTimeout: options.defaultTimeout ?? 30000,
1025
+ container: options.container
1026
+ };
1027
+ }
1028
+ async execute(code, options = {}) {
1029
+ const {
1030
+ entryExport = "main",
1031
+ context = {},
1032
+ timeout = this.options.defaultTimeout
1033
+ } = options;
1034
+ const startTime = performance.now();
1035
+ const logs = [];
1036
+ const container = this.options.container ?? document.body;
1037
+ const iframe = document.createElement("iframe");
1038
+ iframe.style.display = "none";
1039
+ iframe.sandbox.add(...this.options.sandbox);
1040
+ iframe.srcdoc = BOOTSTRAP_HTML;
1041
+ let resolved = false;
1042
+ return new Promise((resolve) => {
1043
+ const cleanup = () => {
1044
+ if (iframe.parentNode) {
1045
+ iframe.parentNode.removeChild(iframe);
1046
+ }
1047
+ window.removeEventListener("message", handleMessage);
1048
+ if (timeoutId)
1049
+ clearTimeout(timeoutId);
1050
+ };
1051
+ const finish = (result) => {
1052
+ if (resolved)
1053
+ return;
1054
+ resolved = true;
1055
+ cleanup();
1056
+ resolve(result);
1057
+ };
1058
+ const handleMessage = (event) => {
1059
+ if (event.source !== iframe.contentWindow)
1060
+ return;
1061
+ const data = event.data;
1062
+ if (data.type === "log") {
1063
+ const prefix = data.level === "log" ? "" : `[${data.level}] `;
1064
+ logs.push(prefix + data.args);
1065
+ } else if (data.type === "result") {
1066
+ const executionTimeMs = performance.now() - startTime;
1067
+ finish({
1068
+ success: data.success,
1069
+ logs,
1070
+ returnValue: data.returnValue,
1071
+ error: data.error,
1072
+ executionTimeMs
1073
+ });
1074
+ } else if (data.type === "ready") {
1075
+ const message = {
1076
+ type: "execute",
1077
+ code,
1078
+ entryExport,
1079
+ context
1080
+ };
1081
+ iframe.contentWindow?.postMessage(message, "*");
1082
+ }
1083
+ };
1084
+ let timeoutId;
1085
+ if (timeout > 0) {
1086
+ timeoutId = setTimeout(() => {
1087
+ const executionTimeMs = performance.now() - startTime;
1088
+ finish({
1089
+ success: false,
1090
+ logs,
1091
+ error: `Execution timed out after ${timeout}ms`,
1092
+ executionTimeMs
1093
+ });
1094
+ }, timeout);
1095
+ }
1096
+ window.addEventListener("message", handleMessage);
1097
+ container.appendChild(iframe);
1098
+ });
1099
+ }
1100
+ }
1101
+ function createIframeExecutor(options) {
1102
+ return new IframeExecutor(options);
1103
+ }
909
1104
  // src/core/shared-module-registry.ts
910
1105
  var instanceCounter = 0;
911
1106
  function generateInstanceId() {
@@ -2183,6 +2378,64 @@ async function createSandboxImpl(fs, options, context) {
2183
2378
  if (!fs.exists(TSCONFIG_PATH)) {
2184
2379
  fs.writeFile(TSCONFIG_PATH, JSON.stringify(DEFAULT_TSCONFIG, null, 2));
2185
2380
  }
2381
+ if (sharedModuleRegistry && typesResolver) {
2382
+ const sharedModuleIds = sharedModuleRegistry.list();
2383
+ const typesFetches = sharedModuleIds.map(async (moduleId) => {
2384
+ try {
2385
+ const typeFiles = await typesResolver.resolveTypes(moduleId);
2386
+ return { moduleId, typeFiles, error: null };
2387
+ } catch (err) {
2388
+ console.warn(`[sandlot] Failed to fetch types for shared module "${moduleId}":`, err);
2389
+ return { moduleId, typeFiles: {}, error: err };
2390
+ }
2391
+ });
2392
+ const results = await Promise.all(typesFetches);
2393
+ for (const { moduleId, typeFiles } of results) {
2394
+ if (Object.keys(typeFiles).length === 0)
2395
+ continue;
2396
+ let packageName = moduleId;
2397
+ let subpath;
2398
+ if (moduleId.startsWith("@")) {
2399
+ const parts = moduleId.split("/");
2400
+ if (parts.length >= 2) {
2401
+ packageName = `${parts[0]}/${parts[1]}`;
2402
+ subpath = parts.length > 2 ? parts.slice(2).join("/") : undefined;
2403
+ }
2404
+ } else {
2405
+ const slashIndex = moduleId.indexOf("/");
2406
+ if (slashIndex !== -1) {
2407
+ packageName = moduleId.slice(0, slashIndex);
2408
+ subpath = moduleId.slice(slashIndex + 1);
2409
+ }
2410
+ }
2411
+ const packageDir = `/node_modules/${packageName}`;
2412
+ let typesEntry = null;
2413
+ let fallbackEntry = null;
2414
+ for (const [filePath, content] of Object.entries(typeFiles)) {
2415
+ const fullPath = filePath.startsWith("/") ? filePath : `${packageDir}/${filePath}`;
2416
+ const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
2417
+ ensureDir(fs, dir);
2418
+ fs.writeFile(fullPath, content);
2419
+ const relativePath = fullPath.replace(`${packageDir}/`, "");
2420
+ if (relativePath === "index.d.ts") {
2421
+ typesEntry = "index.d.ts";
2422
+ } else if (!fallbackEntry && relativePath.endsWith(".d.ts") && !relativePath.includes("/")) {
2423
+ fallbackEntry = relativePath;
2424
+ }
2425
+ }
2426
+ const finalTypesEntry = typesEntry ?? fallbackEntry ?? "index.d.ts";
2427
+ const pkgJsonPath = `${packageDir}/package.json`;
2428
+ if (!fs.exists(pkgJsonPath)) {
2429
+ const pkgJson = {
2430
+ name: packageName,
2431
+ version: "shared",
2432
+ types: finalTypesEntry,
2433
+ main: finalTypesEntry.replace(/\.d\.ts$/, ".js")
2434
+ };
2435
+ fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
2436
+ }
2437
+ }
2438
+ }
2186
2439
  async function install(packageSpec) {
2187
2440
  const { name, version } = parsePackageSpec(packageSpec);
2188
2441
  let resolvedVersion = version ?? "latest";
@@ -2450,6 +2703,11 @@ function createSandlot(options) {
2450
2703
  },
2451
2704
  get sharedModules() {
2452
2705
  return sharedModuleRegistry;
2706
+ },
2707
+ async dispose() {
2708
+ if (bundler.dispose) {
2709
+ await bundler.dispose();
2710
+ }
2453
2711
  }
2454
2712
  };
2455
2713
  }
@@ -2647,7 +2905,7 @@ async function createBrowserSandlot(options = {}) {
2647
2905
  await bundlerInstance.initialize();
2648
2906
  const typecheckerInstance = typechecker === false ? undefined : isTypechecker(typechecker) ? typechecker : new Typechecker(typechecker);
2649
2907
  const typesResolverInstance = typesResolver === false ? undefined : isTypesResolver(typesResolver) ? typesResolver : new EsmTypesResolver(typesResolver);
2650
- const executorInstance = executor === false ? undefined : isExecutor(executor) ? executor : new MainThreadExecutor(executor);
2908
+ const executorInstance = executor === false ? undefined : executor === "iframe" ? new IframeExecutor : isExecutor(executor) ? executor : isIframeExecutorOptions(executor) ? new IframeExecutor(executor) : new MainThreadExecutor(executor);
2651
2909
  return createSandlot({
2652
2910
  ...rest,
2653
2911
  bundler: bundlerInstance,
@@ -2668,6 +2926,9 @@ function isTypesResolver(value) {
2668
2926
  function isExecutor(value) {
2669
2927
  return typeof value === "object" && value !== null && "execute" in value && typeof value.execute === "function";
2670
2928
  }
2929
+ function isIframeExecutorOptions(value) {
2930
+ return typeof value === "object" && value !== null && (("sandbox" in value) || ("container" in value));
2931
+ }
2671
2932
 
2672
2933
  // src/browser/index.ts
2673
2934
  if (typeof window !== "undefined" && typeof globalThis.process === "undefined") {
@@ -2683,8 +2944,10 @@ if (typeof window !== "undefined" && typeof globalThis.process === "undefined")
2683
2944
  export {
2684
2945
  createTypechecker,
2685
2946
  createMainThreadExecutor,
2947
+ createIframeExecutor,
2686
2948
  createBrowserSandlot,
2687
2949
  Typechecker,
2688
2950
  MainThreadExecutor,
2951
+ IframeExecutor,
2689
2952
  EsbuildWasmBundler
2690
2953
  };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Main thread executor for browser environments.
3
+ *
4
+ * This executor runs code directly in the main thread. It provides no
5
+ * isolation from the host environment - use only for trusted code.
6
+ *
7
+ * For untrusted code, consider using a Worker or iframe-based executor
8
+ * that provides proper sandboxing.
9
+ */
10
+ import type { IExecutor } from "../types";
11
+ import { type BasicExecutorOptions } from "../core/executor";
12
+ /**
13
+ * Options for creating a MainThreadExecutor.
14
+ */
15
+ export type MainThreadExecutorOptions = BasicExecutorOptions;
16
+ /**
17
+ * Executor that runs code in the main browser thread.
18
+ *
19
+ * WARNING: This executor provides NO isolation. The executed code has
20
+ * full access to the page's DOM, global variables, and network.
21
+ * Only use for trusted code (e.g., code you're developing).
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const executor = createMainThreadExecutor();
26
+ * const result = await executor.execute(bundledCode, {
27
+ * entryExport: 'main',
28
+ * context: { args: ['--verbose'] },
29
+ * timeout: 5000,
30
+ * });
31
+ * console.log(result.logs);
32
+ * ```
33
+ */
34
+ export declare class MainThreadExecutor implements IExecutor {
35
+ private executor;
36
+ constructor(options?: MainThreadExecutorOptions);
37
+ execute: IExecutor["execute"];
38
+ }
39
+ /**
40
+ * Create a main thread executor.
41
+ *
42
+ * @param options - Executor options
43
+ * @returns A new MainThreadExecutor instance
44
+ */
45
+ export declare function createMainThreadExecutor(options?: MainThreadExecutorOptions): MainThreadExecutor;
46
+ //# sourceMappingURL=main-thread-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-thread-executor.d.ts","sourceRoot":"","sources":["../../src/browser/main-thread-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAElF;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,CAAC;AAgB7D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAClD,OAAO,CAAC,QAAQ,CAAY;gBAEhB,OAAO,GAAE,yBAA8B;IAInD,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,CAA+C;CAC7E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,CAAC,EAAE,yBAAyB,GAClC,kBAAkB,CAEpB"}