sandlot 0.1.4 → 0.2.0

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 (115) hide show
  1. package/dist/browser/bundler.d.ts +68 -0
  2. package/dist/browser/bundler.d.ts.map +1 -0
  3. package/dist/browser/executor.d.ts +46 -0
  4. package/dist/browser/executor.d.ts.map +1 -0
  5. package/dist/browser/index.d.ts +9 -0
  6. package/dist/browser/index.d.ts.map +1 -0
  7. package/dist/browser/index.js +2692 -0
  8. package/dist/browser/preset.d.ts +63 -0
  9. package/dist/browser/preset.d.ts.map +1 -0
  10. package/dist/commands/index.d.ts +20 -11
  11. package/dist/commands/index.d.ts.map +1 -1
  12. package/dist/commands/types.d.ts +31 -132
  13. package/dist/commands/types.d.ts.map +1 -1
  14. package/dist/core/bundler-utils.d.ts +142 -0
  15. package/dist/core/bundler-utils.d.ts.map +1 -0
  16. package/dist/core/esm-types-resolver.d.ts +125 -0
  17. package/dist/core/esm-types-resolver.d.ts.map +1 -0
  18. package/dist/core/executor.d.ts +35 -0
  19. package/dist/core/executor.d.ts.map +1 -0
  20. package/dist/{fs.d.ts → core/fs.d.ts} +27 -29
  21. package/dist/core/fs.d.ts.map +1 -0
  22. package/dist/core/sandbox.d.ts +30 -0
  23. package/dist/core/sandbox.d.ts.map +1 -0
  24. package/dist/core/sandlot.d.ts +30 -0
  25. package/dist/core/sandlot.d.ts.map +1 -0
  26. package/dist/core/shared-module-registry.d.ts +46 -0
  27. package/dist/core/shared-module-registry.d.ts.map +1 -0
  28. package/dist/core/typechecker.d.ts +60 -0
  29. package/dist/core/typechecker.d.ts.map +1 -0
  30. package/dist/index.d.ts +11 -16
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1399 -2010
  33. package/dist/node/bundler.d.ts +48 -0
  34. package/dist/node/bundler.d.ts.map +1 -0
  35. package/dist/node/executor.d.ts +48 -0
  36. package/dist/node/executor.d.ts.map +1 -0
  37. package/dist/node/index.d.ts +9 -0
  38. package/dist/node/index.d.ts.map +1 -0
  39. package/dist/node/index.js +2646 -0
  40. package/dist/node/preset.d.ts +62 -0
  41. package/dist/node/preset.d.ts.map +1 -0
  42. package/dist/types.d.ts +525 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/package.json +16 -6
  45. package/src/browser/bundler.ts +294 -0
  46. package/src/browser/executor.ts +71 -0
  47. package/src/browser/index.ts +57 -0
  48. package/src/browser/preset.ts +179 -0
  49. package/src/commands/index.ts +526 -43
  50. package/src/commands/types.ts +82 -146
  51. package/src/core/bundler-utils.ts +630 -0
  52. package/src/core/esm-types-resolver.ts +432 -0
  53. package/src/core/executor.ts +161 -0
  54. package/src/{fs.ts → core/fs.ts} +59 -37
  55. package/src/core/sandbox.ts +621 -0
  56. package/src/core/sandlot.ts +77 -0
  57. package/src/core/shared-module-registry.ts +138 -0
  58. package/src/core/typechecker.ts +607 -0
  59. package/src/index.ts +104 -139
  60. package/src/node/bundler.ts +194 -0
  61. package/src/node/executor.ts +87 -0
  62. package/src/node/index.ts +39 -0
  63. package/src/node/preset.ts +178 -0
  64. package/src/types.ts +668 -0
  65. package/README.md +0 -243
  66. package/dist/build-emitter.d.ts +0 -47
  67. package/dist/build-emitter.d.ts.map +0 -1
  68. package/dist/builder.d.ts +0 -370
  69. package/dist/builder.d.ts.map +0 -1
  70. package/dist/bundler.d.ts +0 -152
  71. package/dist/bundler.d.ts.map +0 -1
  72. package/dist/commands/compile.d.ts +0 -13
  73. package/dist/commands/compile.d.ts.map +0 -1
  74. package/dist/commands/packages.d.ts +0 -17
  75. package/dist/commands/packages.d.ts.map +0 -1
  76. package/dist/commands/run.d.ts +0 -40
  77. package/dist/commands/run.d.ts.map +0 -1
  78. package/dist/commands.d.ts +0 -179
  79. package/dist/commands.d.ts.map +0 -1
  80. package/dist/fs.d.ts.map +0 -1
  81. package/dist/internal.d.ts +0 -79
  82. package/dist/internal.d.ts.map +0 -1
  83. package/dist/internal.js +0 -1942
  84. package/dist/loader.d.ts +0 -164
  85. package/dist/loader.d.ts.map +0 -1
  86. package/dist/packages.d.ts +0 -199
  87. package/dist/packages.d.ts.map +0 -1
  88. package/dist/runner.d.ts +0 -314
  89. package/dist/runner.d.ts.map +0 -1
  90. package/dist/sandbox-manager.d.ts +0 -261
  91. package/dist/sandbox-manager.d.ts.map +0 -1
  92. package/dist/sandbox.d.ts +0 -267
  93. package/dist/sandbox.d.ts.map +0 -1
  94. package/dist/shared-modules.d.ts +0 -148
  95. package/dist/shared-modules.d.ts.map +0 -1
  96. package/dist/shared-resources.d.ts +0 -102
  97. package/dist/shared-resources.d.ts.map +0 -1
  98. package/dist/ts-libs.d.ts +0 -85
  99. package/dist/ts-libs.d.ts.map +0 -1
  100. package/dist/typechecker.d.ts +0 -127
  101. package/dist/typechecker.d.ts.map +0 -1
  102. package/src/build-emitter.ts +0 -64
  103. package/src/builder.ts +0 -498
  104. package/src/bundler.ts +0 -575
  105. package/src/commands/compile.ts +0 -236
  106. package/src/commands/packages.ts +0 -154
  107. package/src/commands/run.ts +0 -245
  108. package/src/internal.ts +0 -119
  109. package/src/loader.ts +0 -229
  110. package/src/packages.ts +0 -936
  111. package/src/sandbox.ts +0 -398
  112. package/src/shared-modules.ts +0 -280
  113. package/src/shared-resources.ts +0 -166
  114. package/src/ts-libs.ts +0 -218
  115. package/src/typechecker.ts +0 -635
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Browser bundler implementation using esbuild-wasm.
3
+ *
4
+ * This module handles WASM initialization and provides a bundler that
5
+ * works entirely in the browser.
6
+ */
7
+
8
+ import type * as EsbuildTypes from "esbuild-wasm";
9
+ import type {
10
+ IBundler,
11
+ BundleOptions,
12
+ BundleResult,
13
+ BundleWarning,
14
+ BundleError,
15
+ } from "../types";
16
+ import {
17
+ createVfsPlugin,
18
+ isEsbuildBuildFailure,
19
+ convertEsbuildMessage,
20
+ } from "../core/bundler-utils";
21
+
22
+ /**
23
+ * esbuild-wasm version - should match what's in package.json
24
+ */
25
+ const ESBUILD_VERSION = "0.27.2";
26
+
27
+ // =============================================================================
28
+ // Global Singleton for esbuild-wasm initialization
29
+ // =============================================================================
30
+ // esbuild-wasm can only be initialized once per page. We track this globally
31
+ // so multiple EsbuildWasmBundler instances can share the same initialization.
32
+
33
+ interface EsbuildGlobalState {
34
+ esbuild: typeof EsbuildTypes | null;
35
+ initialized: boolean;
36
+ initPromise: Promise<void> | null;
37
+ }
38
+
39
+ const GLOBAL_KEY = "__sandlot_esbuild__";
40
+
41
+ function getGlobalState(): EsbuildGlobalState {
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ const g = globalThis as any;
44
+ if (!g[GLOBAL_KEY]) {
45
+ g[GLOBAL_KEY] = {
46
+ esbuild: null,
47
+ initialized: false,
48
+ initPromise: null,
49
+ };
50
+ }
51
+ return g[GLOBAL_KEY];
52
+ }
53
+
54
+ export interface EsbuildWasmBundlerOptions {
55
+ /**
56
+ * URL to the esbuild WASM file.
57
+ * @default "https://unpkg.com/esbuild-wasm@{version}/esbuild.wasm"
58
+ */
59
+ wasmUrl?: string;
60
+
61
+ /**
62
+ * URL to load esbuild-wasm module from.
63
+ * @default "https://esm.sh/esbuild-wasm@{version}"
64
+ */
65
+ esbuildUrl?: string;
66
+
67
+ /**
68
+ * Base URL for CDN imports.
69
+ * npm imports like "lodash" are rewritten to "{cdnBaseUrl}/lodash@{version}".
70
+ * @default "https://esm.sh"
71
+ */
72
+ cdnBaseUrl?: string;
73
+
74
+ /**
75
+ * Whether to initialize immediately on construction.
76
+ * If false, initialization happens lazily on first bundle() call.
77
+ * @default false
78
+ */
79
+ eagerInit?: boolean;
80
+ }
81
+
82
+ /**
83
+ * Browser bundler implementation using esbuild-wasm.
84
+ *
85
+ * Handles WASM initialization internally. The first bundle() call
86
+ * will wait for initialization if not already complete.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const bundler = new EsbuildWasmBundler();
91
+ * await bundler.initialize();
92
+ *
93
+ * const result = await bundler.bundle({
94
+ * fs: myFilesystem,
95
+ * entryPoint: "/src/index.ts",
96
+ * });
97
+ * ```
98
+ */
99
+ export class EsbuildWasmBundler implements IBundler {
100
+ private options: EsbuildWasmBundlerOptions;
101
+
102
+ constructor(options: EsbuildWasmBundlerOptions = {}) {
103
+ this.options = {
104
+ cdnBaseUrl: "https://esm.sh",
105
+ ...options,
106
+ };
107
+
108
+ if (options.eagerInit) {
109
+ this.initialize();
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Initialize the esbuild WASM module.
115
+ * Called automatically on first bundle() if not already initialized.
116
+ *
117
+ * Uses a global singleton pattern since esbuild-wasm can only be
118
+ * initialized once per page.
119
+ */
120
+ async initialize(): Promise<void> {
121
+ const state = getGlobalState();
122
+
123
+ // Already initialized globally
124
+ if (state.initialized && state.esbuild) {
125
+ return;
126
+ }
127
+
128
+ // Another instance is initializing - wait for it
129
+ if (state.initPromise) {
130
+ await state.initPromise;
131
+ return;
132
+ }
133
+
134
+ // We're the first - do the initialization
135
+ state.initPromise = this.doInitialize(state);
136
+ await state.initPromise;
137
+ }
138
+
139
+ private async doInitialize(state: EsbuildGlobalState): Promise<void> {
140
+ // Check for cross-origin isolation (needed for SharedArrayBuffer)
141
+ this.checkCrossOriginIsolation();
142
+
143
+ // Load esbuild-wasm from CDN
144
+ const esbuildUrl =
145
+ this.options.esbuildUrl ?? `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
146
+
147
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
+ const mod: any = await import(/* @vite-ignore */ esbuildUrl);
149
+ const esbuild = mod.default ?? mod;
150
+
151
+ if (typeof esbuild?.initialize !== "function") {
152
+ throw new Error(
153
+ "Failed to load esbuild-wasm: initialize function not found"
154
+ );
155
+ }
156
+
157
+ // Initialize with WASM binary
158
+ const wasmUrl =
159
+ this.options.wasmUrl ??
160
+ `https://unpkg.com/esbuild-wasm@${ESBUILD_VERSION}/esbuild.wasm`;
161
+
162
+ await esbuild.initialize({ wasmURL: wasmUrl });
163
+
164
+ // Store in global state
165
+ state.esbuild = esbuild;
166
+ state.initialized = true;
167
+ }
168
+
169
+ /**
170
+ * Get the initialized esbuild instance.
171
+ */
172
+ private getEsbuild(): typeof EsbuildTypes {
173
+ const state = getGlobalState();
174
+ if (!state.esbuild) {
175
+ throw new Error("esbuild not initialized - call initialize() first");
176
+ }
177
+ return state.esbuild;
178
+ }
179
+
180
+ private checkCrossOriginIsolation(): void {
181
+ if (typeof window === "undefined") return;
182
+
183
+ if (!window.crossOriginIsolated) {
184
+ console.warn(
185
+ "[sandlot] Cross-origin isolation is not enabled. " +
186
+ "esbuild-wasm may have reduced performance or fail on some browsers.\n" +
187
+ "To enable, add these headers to your server:\n" +
188
+ " Cross-Origin-Embedder-Policy: require-corp\n" +
189
+ " Cross-Origin-Opener-Policy: same-origin"
190
+ );
191
+ }
192
+ }
193
+
194
+ async bundle(options: BundleOptions): Promise<BundleResult> {
195
+ await this.initialize();
196
+
197
+ const esbuild = this.getEsbuild();
198
+
199
+ const {
200
+ fs,
201
+ entryPoint,
202
+ installedPackages = {},
203
+ sharedModules = [],
204
+ sharedModuleRegistry,
205
+ external = [],
206
+ format = "esm",
207
+ minify = false,
208
+ sourcemap = false,
209
+ target = ["es2020"],
210
+ } = options;
211
+
212
+ // Normalize entry point to absolute path
213
+ const normalizedEntry = entryPoint.startsWith("/")
214
+ ? entryPoint
215
+ : `/${entryPoint}`;
216
+
217
+ // Verify entry point exists
218
+ if (!fs.exists(normalizedEntry)) {
219
+ return {
220
+ success: false,
221
+ errors: [{ text: `Entry point not found: ${normalizedEntry}` }],
222
+ warnings: [],
223
+ };
224
+ }
225
+
226
+ // Track files included in the bundle
227
+ const includedFiles = new Set<string>();
228
+
229
+ // Create the VFS plugin
230
+ const plugin = createVfsPlugin({
231
+ fs,
232
+ entryPoint: normalizedEntry,
233
+ installedPackages,
234
+ sharedModules: new Set(sharedModules),
235
+ sharedModuleRegistry: sharedModuleRegistry ?? null,
236
+ cdnBaseUrl: this.options.cdnBaseUrl!,
237
+ includedFiles,
238
+ });
239
+
240
+ try {
241
+ // Run esbuild
242
+ const result = await esbuild.build({
243
+ entryPoints: [normalizedEntry],
244
+ bundle: true,
245
+ write: false,
246
+ format,
247
+ minify,
248
+ sourcemap: sourcemap ? "inline" : false,
249
+ target,
250
+ external,
251
+ // Cast to esbuild's Plugin type since our minimal interface is compatible
252
+ plugins: [plugin as EsbuildTypes.Plugin],
253
+ jsx: "automatic",
254
+ });
255
+
256
+ const code = result.outputFiles?.[0]?.text ?? "";
257
+
258
+ // Convert esbuild warnings to our format
259
+ const warnings: BundleWarning[] = result.warnings.map((w) =>
260
+ convertEsbuildMessage(w)
261
+ );
262
+
263
+ return {
264
+ success: true,
265
+ code,
266
+ warnings,
267
+ includedFiles: Array.from(includedFiles),
268
+ };
269
+ } catch (err) {
270
+ // esbuild throws BuildFailure with .errors array
271
+ if (isEsbuildBuildFailure(err)) {
272
+ const errors: BundleError[] = err.errors.map((e) =>
273
+ convertEsbuildMessage(e)
274
+ );
275
+ const warnings: BundleWarning[] = err.warnings.map((w) =>
276
+ convertEsbuildMessage(w)
277
+ );
278
+ return {
279
+ success: false,
280
+ errors,
281
+ warnings,
282
+ };
283
+ }
284
+
285
+ // Unknown error - wrap it
286
+ const message = err instanceof Error ? err.message : String(err);
287
+ return {
288
+ success: false,
289
+ errors: [{ text: message }],
290
+ warnings: [],
291
+ };
292
+ }
293
+ }
294
+ }
@@ -0,0 +1,71 @@
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
+
11
+ import type { IExecutor } from "../types";
12
+ import { createBasicExecutor, type BasicExecutorOptions } from "../core/executor";
13
+
14
+ /**
15
+ * Options for creating a MainThreadExecutor.
16
+ */
17
+ export type MainThreadExecutorOptions = BasicExecutorOptions;
18
+
19
+ /**
20
+ * Load a module from code using a Blob URL.
21
+ * The URL is revoked after import to avoid memory leaks.
22
+ */
23
+ async function loadModuleFromBlobUrl(code: string): Promise<Record<string, unknown>> {
24
+ const blob = new Blob([code], { type: "application/javascript" });
25
+ const url = URL.createObjectURL(blob);
26
+ try {
27
+ return await import(/* @vite-ignore */ url);
28
+ } finally {
29
+ URL.revokeObjectURL(url);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Executor that runs code in the main browser thread.
35
+ *
36
+ * WARNING: This executor provides NO isolation. The executed code has
37
+ * full access to the page's DOM, global variables, and network.
38
+ * Only use for trusted code (e.g., code you're developing).
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const executor = createMainThreadExecutor();
43
+ * const result = await executor.execute(bundledCode, {
44
+ * entryExport: 'main',
45
+ * context: { args: ['--verbose'] },
46
+ * timeout: 5000,
47
+ * });
48
+ * console.log(result.logs);
49
+ * ```
50
+ */
51
+ export class MainThreadExecutor implements IExecutor {
52
+ private executor: IExecutor;
53
+
54
+ constructor(options: MainThreadExecutorOptions = {}) {
55
+ this.executor = createBasicExecutor(loadModuleFromBlobUrl, options);
56
+ }
57
+
58
+ execute: IExecutor["execute"] = (...args) => this.executor.execute(...args);
59
+ }
60
+
61
+ /**
62
+ * Create a main thread executor.
63
+ *
64
+ * @param options - Executor options
65
+ * @returns A new MainThreadExecutor instance
66
+ */
67
+ export function createMainThreadExecutor(
68
+ options?: MainThreadExecutorOptions
69
+ ): MainThreadExecutor {
70
+ return new MainThreadExecutor(options);
71
+ }
@@ -0,0 +1,57 @@
1
+ // =============================================================================
2
+ // Sandlot v2 - Browser Entry Point
3
+ // =============================================================================
4
+ //
5
+ // Browser-specific implementations for Sandlot.
6
+ // Uses esbuild-wasm for bundling.
7
+ //
8
+ // Note: EsmTypesResolver is platform-independent and exported from the main
9
+ // "sandlot" entry point, not from "sandlot/browser".
10
+ //
11
+ // =============================================================================
12
+
13
+ // =============================================================================
14
+ // Browser Polyfills - inject before anything else loads
15
+ // =============================================================================
16
+ //
17
+ // Some dependencies (like just-bash) reference Node.js globals.
18
+ // Provide shims so they work in the browser without user configuration.
19
+ //
20
+ if (typeof window !== "undefined" && typeof globalThis.process === "undefined") {
21
+ (globalThis as Record<string, unknown>).process = {
22
+ env: {},
23
+ platform: "browser",
24
+ version: "v20.0.0",
25
+ browser: true,
26
+ cwd: () => "/",
27
+ nextTick: (fn: () => void) => setTimeout(fn, 0),
28
+ };
29
+ }
30
+
31
+ // -----------------------------------------------------------------------------
32
+ // Typechecker (platform-agnostic: re-exported for convenience)
33
+ // -----------------------------------------------------------------------------
34
+
35
+ export { Typechecker, createTypechecker } from "../core/typechecker";
36
+ export type { TypecheckerOptions } from "../core/typechecker";
37
+
38
+ // -----------------------------------------------------------------------------
39
+ // Bundler (browser-specific: uses esbuild-wasm)
40
+ // -----------------------------------------------------------------------------
41
+
42
+ export { EsbuildWasmBundler } from "./bundler";
43
+ export type { EsbuildWasmBundlerOptions } from "./bundler";
44
+
45
+ // -----------------------------------------------------------------------------
46
+ // Executor (browser-specific: runs in main thread)
47
+ // -----------------------------------------------------------------------------
48
+
49
+ export { MainThreadExecutor, createMainThreadExecutor } from "./executor";
50
+ export type { MainThreadExecutorOptions } from "./executor";
51
+
52
+ // -----------------------------------------------------------------------------
53
+ // Convenience Preset
54
+ // -----------------------------------------------------------------------------
55
+
56
+ export { createBrowserSandlot } from "./preset";
57
+ export type { CreateBrowserSandlotOptions } from "./preset";
@@ -0,0 +1,179 @@
1
+ import { createSandlot } from "../core/sandlot";
2
+ import {
3
+ EsmTypesResolver,
4
+ type EsmTypesResolverOptions,
5
+ } from "../core/esm-types-resolver";
6
+ import type { Sandlot, SandlotOptions } from "../types";
7
+ import { EsbuildWasmBundler, type EsbuildWasmBundlerOptions } from "./bundler";
8
+ import {
9
+ Typechecker,
10
+ type TypecheckerOptions,
11
+ } from "../core/typechecker";
12
+ import {
13
+ MainThreadExecutor,
14
+ type MainThreadExecutorOptions,
15
+ } from "./executor";
16
+
17
+ export interface CreateBrowserSandlotOptions
18
+ extends Omit<SandlotOptions, "bundler" | "typechecker" | "typesResolver" | "executor"> {
19
+ /**
20
+ * Custom bundler options, or a pre-configured bundler instance.
21
+ */
22
+ bundler?: EsbuildWasmBundlerOptions | SandlotOptions["bundler"];
23
+
24
+ /**
25
+ * Custom typechecker options, or a pre-configured typechecker instance.
26
+ * Set to `false` to disable type checking.
27
+ */
28
+ typechecker?:
29
+ | TypecheckerOptions
30
+ | SandlotOptions["typechecker"]
31
+ | false;
32
+
33
+ /**
34
+ * Custom types resolver options, or a pre-configured resolver instance.
35
+ * Set to `false` to disable type resolution.
36
+ */
37
+ typesResolver?:
38
+ | EsmTypesResolverOptions
39
+ | SandlotOptions["typesResolver"]
40
+ | false;
41
+
42
+ /**
43
+ * Custom executor options, or a pre-configured executor instance.
44
+ * Set to `false` to disable execution (sandbox.run() will throw).
45
+ * Defaults to MainThreadExecutor.
46
+ */
47
+ executor?:
48
+ | MainThreadExecutorOptions
49
+ | SandlotOptions["executor"]
50
+ | false;
51
+ }
52
+
53
+ /**
54
+ * Create a Sandlot instance pre-configured for browser environments.
55
+ *
56
+ * This is a convenience function that sets up sensible defaults:
57
+ * - EsbuildWasmBundler for bundling
58
+ * - Typechecker for type checking
59
+ * - FetchTypesResolver for npm type resolution
60
+ *
61
+ * @example Basic usage
62
+ * ```ts
63
+ * const sandlot = await createBrowserSandlot();
64
+ * const sandbox = await sandlot.createSandbox();
65
+ * ```
66
+ *
67
+ * @example With shared modules
68
+ * ```ts
69
+ * import React from "react";
70
+ * import ReactDOM from "react-dom/client";
71
+ *
72
+ * const sandlot = await createBrowserSandlot({
73
+ * sharedModules: {
74
+ * react: React,
75
+ * "react-dom/client": ReactDOM,
76
+ * },
77
+ * });
78
+ * ```
79
+ *
80
+ * @example Disable type checking for faster builds
81
+ * ```ts
82
+ * const sandlot = await createBrowserSandlot({
83
+ * typechecker: false,
84
+ * });
85
+ * ```
86
+ */
87
+ export async function createBrowserSandlot(
88
+ options: CreateBrowserSandlotOptions = {}
89
+ ): Promise<Sandlot> {
90
+ const { bundler, typechecker, typesResolver, executor, ...rest } = options;
91
+
92
+ // Create or use provided bundler
93
+ const bundlerInstance = isBundler(bundler)
94
+ ? bundler
95
+ : new EsbuildWasmBundler(bundler as EsbuildWasmBundlerOptions | undefined);
96
+
97
+ // Initialize bundler (loads WASM)
98
+ await bundlerInstance.initialize();
99
+
100
+ // Create or use provided typechecker
101
+ const typecheckerInstance =
102
+ typechecker === false
103
+ ? undefined
104
+ : isTypechecker(typechecker)
105
+ ? typechecker
106
+ : new Typechecker(
107
+ typechecker as TypecheckerOptions | undefined
108
+ );
109
+
110
+ // Create or use provided types resolver
111
+ const typesResolverInstance =
112
+ typesResolver === false
113
+ ? undefined
114
+ : isTypesResolver(typesResolver)
115
+ ? typesResolver
116
+ : new EsmTypesResolver(
117
+ typesResolver as EsmTypesResolverOptions | undefined
118
+ );
119
+
120
+ // Create or use provided executor (defaults to MainThreadExecutor)
121
+ const executorInstance =
122
+ executor === false
123
+ ? undefined
124
+ : isExecutor(executor)
125
+ ? executor
126
+ : new MainThreadExecutor(
127
+ executor as MainThreadExecutorOptions | undefined
128
+ );
129
+
130
+ return createSandlot({
131
+ ...rest,
132
+ bundler: bundlerInstance,
133
+ typechecker: typecheckerInstance,
134
+ typesResolver: typesResolverInstance,
135
+ executor: executorInstance,
136
+ });
137
+ }
138
+
139
+ // Type guards for detecting pre-configured instances
140
+
141
+ function isBundler(
142
+ value: unknown
143
+ ): value is SandlotOptions["bundler"] & { initialize(): Promise<void> } {
144
+ return (
145
+ typeof value === "object" &&
146
+ value !== null &&
147
+ "bundle" in value &&
148
+ typeof (value as { bundle: unknown }).bundle === "function"
149
+ );
150
+ }
151
+
152
+ function isTypechecker(value: unknown): value is SandlotOptions["typechecker"] {
153
+ return (
154
+ typeof value === "object" &&
155
+ value !== null &&
156
+ "typecheck" in value &&
157
+ typeof (value as { typecheck: unknown }).typecheck === "function"
158
+ );
159
+ }
160
+
161
+ function isTypesResolver(
162
+ value: unknown
163
+ ): value is SandlotOptions["typesResolver"] {
164
+ return (
165
+ typeof value === "object" &&
166
+ value !== null &&
167
+ "resolveTypes" in value &&
168
+ typeof (value as { resolveTypes: unknown }).resolveTypes === "function"
169
+ );
170
+ }
171
+
172
+ function isExecutor(value: unknown): value is SandlotOptions["executor"] {
173
+ return (
174
+ typeof value === "object" &&
175
+ value !== null &&
176
+ "execute" in value &&
177
+ typeof (value as { execute: unknown }).execute === "function"
178
+ );
179
+ }