sandlot 0.1.3 → 0.1.4
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.
- package/README.md +1 -1
- package/dist/bundler.d.ts +5 -1
- package/dist/bundler.d.ts.map +1 -1
- package/dist/index.js +51 -84
- package/dist/internal.js +50 -84
- package/dist/sandbox.d.ts +5 -5
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/ts-libs.d.ts +7 -20
- package/dist/ts-libs.d.ts.map +1 -1
- package/package.json +12 -3
- package/src/bundler.ts +55 -22
- package/src/sandbox.ts +7 -5
- package/src/ts-libs.ts +19 -121
package/README.md
CHANGED
|
@@ -176,7 +176,7 @@ localStorage.setItem("project", JSON.stringify(state));
|
|
|
176
176
|
| `sharedModules` | `string[]` | Modules to resolve from host (e.g., `['react']`). |
|
|
177
177
|
| `tsconfigPath` | `string` | Path to tsconfig.json (default: `/tsconfig.json`). |
|
|
178
178
|
| `onBuild` | `(result) => void` | Callback when a build succeeds. |
|
|
179
|
-
| `bashOptions` | `SandboxBashOptions` | Options for the just-bash shell (
|
|
179
|
+
| `bashOptions` | `SandboxBashOptions` | Options for the just-bash shell (env, limits). |
|
|
180
180
|
|
|
181
181
|
## Shell Commands
|
|
182
182
|
|
package/dist/bundler.d.ts
CHANGED
|
@@ -88,8 +88,12 @@ export interface BundleResult {
|
|
|
88
88
|
includedFiles: string[];
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Initialize esbuild
|
|
91
|
+
* Initialize esbuild. Called automatically on first bundle.
|
|
92
92
|
* Can be called explicitly to pre-warm.
|
|
93
|
+
*
|
|
94
|
+
* In browser environments, this loads and initializes esbuild-wasm.
|
|
95
|
+
* In server environments (Node.js, Bun, Deno), this loads native esbuild
|
|
96
|
+
* which doesn't require WASM initialization.
|
|
93
97
|
*/
|
|
94
98
|
export declare function initBundler(): Promise<void>;
|
|
95
99
|
/**
|
package/dist/bundler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundler.d.ts","sourceRoot":"","sources":["../src/bundler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,KAAK,YAAY,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"bundler.d.ts","sourceRoot":"","sources":["../src/bundler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,KAAK,YAAY,MAAM,cAAc,CAAC;AAuDlD;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,EAAE,EAAE,WAAW,CAAC;IAEhB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAE5B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;OAEG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;IAEhC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,QAAQ,EAAE,YAAY,CAAC,OAAO,EAAE,CAAC;IAEjC;;OAEG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAsCD;;;;;;;GAOG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAsBjD;AA+ND;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CA+D1E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAIzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC,CAOrF"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
1
9
|
// src/sandbox.ts
|
|
2
10
|
import { Bash } from "just-bash/browser";
|
|
3
11
|
|
|
@@ -1107,15 +1115,32 @@ function getSharedModuleRuntimeCode(moduleId) {
|
|
|
1107
1115
|
|
|
1108
1116
|
// src/bundler.ts
|
|
1109
1117
|
var esbuild = null;
|
|
1118
|
+
function isServerEnvironment() {
|
|
1119
|
+
if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
|
|
1123
|
+
return true;
|
|
1124
|
+
}
|
|
1125
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1110
1130
|
async function getEsbuild() {
|
|
1111
1131
|
if (esbuild)
|
|
1112
1132
|
return esbuild;
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1133
|
+
if (isServerEnvironment()) {
|
|
1134
|
+
const mod = await import("esbuild");
|
|
1135
|
+
esbuild = mod.default ?? mod;
|
|
1136
|
+
} else {
|
|
1137
|
+
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
1138
|
+
const mod = await import(cdnUrl);
|
|
1139
|
+
esbuild = mod.default ?? mod;
|
|
1140
|
+
if (typeof esbuild?.initialize !== "function") {
|
|
1141
|
+
console.error("esbuild-wasm module structure:", mod);
|
|
1142
|
+
throw new Error("Failed to load esbuild-wasm: initialize function not found");
|
|
1143
|
+
}
|
|
1119
1144
|
}
|
|
1120
1145
|
return esbuild;
|
|
1121
1146
|
}
|
|
@@ -1143,12 +1168,14 @@ async function initBundler() {
|
|
|
1143
1168
|
await initPromise;
|
|
1144
1169
|
return;
|
|
1145
1170
|
}
|
|
1146
|
-
checkCrossOriginIsolation();
|
|
1147
1171
|
initPromise = (async () => {
|
|
1148
1172
|
const es = await getEsbuild();
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1173
|
+
if (!isServerEnvironment() && typeof es.initialize === "function") {
|
|
1174
|
+
checkCrossOriginIsolation();
|
|
1175
|
+
await es.initialize({
|
|
1176
|
+
wasmURL: getWasmUrl()
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1152
1179
|
})();
|
|
1153
1180
|
await initPromise;
|
|
1154
1181
|
initialized = true;
|
|
@@ -2318,9 +2345,6 @@ function createDefaultCommands(deps) {
|
|
|
2318
2345
|
// src/ts-libs.ts
|
|
2319
2346
|
var TS_VERSION = "5.9.3";
|
|
2320
2347
|
var CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
|
|
2321
|
-
var DB_NAME = "ts-lib-cache";
|
|
2322
|
-
var DB_VERSION = 1;
|
|
2323
|
-
var STORE_NAME = "libs";
|
|
2324
2348
|
function getDefaultBrowserLibs() {
|
|
2325
2349
|
return ["es2020", "dom", "dom.iterable"];
|
|
2326
2350
|
}
|
|
@@ -2385,94 +2409,36 @@ async function fetchAllLibs(libs) {
|
|
|
2385
2409
|
}
|
|
2386
2410
|
return result;
|
|
2387
2411
|
}
|
|
2388
|
-
|
|
2389
|
-
return new Promise((resolve, reject) => {
|
|
2390
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
2391
|
-
request.onerror = () => reject(request.error);
|
|
2392
|
-
request.onsuccess = () => resolve(request.result);
|
|
2393
|
-
request.onupgradeneeded = (event) => {
|
|
2394
|
-
const db = event.target.result;
|
|
2395
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
2396
|
-
db.createObjectStore(STORE_NAME);
|
|
2397
|
-
}
|
|
2398
|
-
};
|
|
2399
|
-
});
|
|
2400
|
-
}
|
|
2401
|
-
function promisifyRequest(request) {
|
|
2402
|
-
return new Promise((resolve, reject) => {
|
|
2403
|
-
request.onsuccess = () => resolve(request.result);
|
|
2404
|
-
request.onerror = () => reject(request.error);
|
|
2405
|
-
});
|
|
2406
|
-
}
|
|
2407
|
-
function getCacheKey() {
|
|
2408
|
-
return `libs-${TS_VERSION}`;
|
|
2409
|
-
}
|
|
2412
|
+
var memoryCache = null;
|
|
2410
2413
|
|
|
2411
2414
|
class LibCache {
|
|
2412
|
-
db;
|
|
2413
|
-
constructor(db) {
|
|
2414
|
-
this.db = db;
|
|
2415
|
-
}
|
|
2416
|
-
static async create() {
|
|
2417
|
-
const db = await openDatabase();
|
|
2418
|
-
return new LibCache(db);
|
|
2419
|
-
}
|
|
2420
2415
|
async getOrFetch(libs) {
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
const missing = libs.filter((lib) => !cached.has(lib));
|
|
2416
|
+
if (memoryCache) {
|
|
2417
|
+
const missing = libs.filter((lib) => !memoryCache.has(lib));
|
|
2424
2418
|
if (missing.length === 0) {
|
|
2425
|
-
return
|
|
2419
|
+
return memoryCache;
|
|
2426
2420
|
}
|
|
2427
2421
|
console.log(`Cache missing libs: ${missing.join(", ")}, fetching all...`);
|
|
2428
2422
|
}
|
|
2429
2423
|
console.log(`Fetching TypeScript libs from CDN: ${libs.join(", ")}...`);
|
|
2430
2424
|
const fetched = await fetchAllLibs(libs);
|
|
2431
2425
|
console.log(`Fetched ${fetched.size} lib files`);
|
|
2432
|
-
|
|
2426
|
+
memoryCache = fetched;
|
|
2433
2427
|
return fetched;
|
|
2434
2428
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
const store = tx.objectStore(STORE_NAME);
|
|
2438
|
-
const key = getCacheKey();
|
|
2439
|
-
const cached = await promisifyRequest(store.get(key));
|
|
2440
|
-
if (!cached) {
|
|
2441
|
-
return null;
|
|
2442
|
-
}
|
|
2443
|
-
if (cached.version !== TS_VERSION) {
|
|
2444
|
-
console.log(`Cache version mismatch: ${cached.version} vs ${TS_VERSION}`);
|
|
2445
|
-
return null;
|
|
2446
|
-
}
|
|
2447
|
-
return new Map(Object.entries(cached.libs));
|
|
2448
|
-
}
|
|
2449
|
-
async set(libs) {
|
|
2450
|
-
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
2451
|
-
const store = tx.objectStore(STORE_NAME);
|
|
2452
|
-
const key = getCacheKey();
|
|
2453
|
-
const cached = {
|
|
2454
|
-
version: TS_VERSION,
|
|
2455
|
-
timestamp: Date.now(),
|
|
2456
|
-
libs: Object.fromEntries(libs)
|
|
2457
|
-
};
|
|
2458
|
-
await promisifyRequest(store.put(cached, key));
|
|
2429
|
+
get() {
|
|
2430
|
+
return memoryCache;
|
|
2459
2431
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
const store = tx.objectStore(STORE_NAME);
|
|
2463
|
-
await promisifyRequest(store.clear());
|
|
2432
|
+
set(libs) {
|
|
2433
|
+
memoryCache = libs;
|
|
2464
2434
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2435
|
+
clear() {
|
|
2436
|
+
memoryCache = null;
|
|
2467
2437
|
}
|
|
2468
2438
|
}
|
|
2469
2439
|
async function fetchAndCacheLibs(libs = getDefaultBrowserLibs()) {
|
|
2470
|
-
const cache =
|
|
2471
|
-
|
|
2472
|
-
return await cache.getOrFetch(libs);
|
|
2473
|
-
} finally {
|
|
2474
|
-
cache.close();
|
|
2475
|
-
}
|
|
2440
|
+
const cache = new LibCache;
|
|
2441
|
+
return cache.getOrFetch(libs);
|
|
2476
2442
|
}
|
|
2477
2443
|
|
|
2478
2444
|
// src/shared-resources.ts
|
|
@@ -2587,6 +2553,7 @@ async function createSandbox(options = {}) {
|
|
|
2587
2553
|
const defaultCommands = createDefaultCommands(commandDeps);
|
|
2588
2554
|
const bash = new Bash({
|
|
2589
2555
|
...bashOptions,
|
|
2556
|
+
cwd: "/",
|
|
2590
2557
|
fs,
|
|
2591
2558
|
customCommands: [...defaultCommands, ...customCommands]
|
|
2592
2559
|
});
|
package/dist/internal.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
1
9
|
// src/loader.ts
|
|
2
10
|
class ModuleLoadError extends Error {
|
|
3
11
|
constructor(message, cause) {
|
|
@@ -514,9 +522,6 @@ async function listPackages(fs) {
|
|
|
514
522
|
// src/ts-libs.ts
|
|
515
523
|
var TS_VERSION = "5.9.3";
|
|
516
524
|
var CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
|
|
517
|
-
var DB_NAME = "ts-lib-cache";
|
|
518
|
-
var DB_VERSION = 1;
|
|
519
|
-
var STORE_NAME = "libs";
|
|
520
525
|
function getDefaultBrowserLibs() {
|
|
521
526
|
return ["es2020", "dom", "dom.iterable"];
|
|
522
527
|
}
|
|
@@ -581,94 +586,36 @@ async function fetchAllLibs(libs) {
|
|
|
581
586
|
}
|
|
582
587
|
return result;
|
|
583
588
|
}
|
|
584
|
-
|
|
585
|
-
return new Promise((resolve, reject) => {
|
|
586
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
587
|
-
request.onerror = () => reject(request.error);
|
|
588
|
-
request.onsuccess = () => resolve(request.result);
|
|
589
|
-
request.onupgradeneeded = (event) => {
|
|
590
|
-
const db = event.target.result;
|
|
591
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
592
|
-
db.createObjectStore(STORE_NAME);
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
function promisifyRequest(request) {
|
|
598
|
-
return new Promise((resolve, reject) => {
|
|
599
|
-
request.onsuccess = () => resolve(request.result);
|
|
600
|
-
request.onerror = () => reject(request.error);
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
function getCacheKey() {
|
|
604
|
-
return `libs-${TS_VERSION}`;
|
|
605
|
-
}
|
|
589
|
+
var memoryCache = null;
|
|
606
590
|
|
|
607
591
|
class LibCache {
|
|
608
|
-
db;
|
|
609
|
-
constructor(db) {
|
|
610
|
-
this.db = db;
|
|
611
|
-
}
|
|
612
|
-
static async create() {
|
|
613
|
-
const db = await openDatabase();
|
|
614
|
-
return new LibCache(db);
|
|
615
|
-
}
|
|
616
592
|
async getOrFetch(libs) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const missing = libs.filter((lib) => !cached.has(lib));
|
|
593
|
+
if (memoryCache) {
|
|
594
|
+
const missing = libs.filter((lib) => !memoryCache.has(lib));
|
|
620
595
|
if (missing.length === 0) {
|
|
621
|
-
return
|
|
596
|
+
return memoryCache;
|
|
622
597
|
}
|
|
623
598
|
console.log(`Cache missing libs: ${missing.join(", ")}, fetching all...`);
|
|
624
599
|
}
|
|
625
600
|
console.log(`Fetching TypeScript libs from CDN: ${libs.join(", ")}...`);
|
|
626
601
|
const fetched = await fetchAllLibs(libs);
|
|
627
602
|
console.log(`Fetched ${fetched.size} lib files`);
|
|
628
|
-
|
|
603
|
+
memoryCache = fetched;
|
|
629
604
|
return fetched;
|
|
630
605
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const store = tx.objectStore(STORE_NAME);
|
|
634
|
-
const key = getCacheKey();
|
|
635
|
-
const cached = await promisifyRequest(store.get(key));
|
|
636
|
-
if (!cached) {
|
|
637
|
-
return null;
|
|
638
|
-
}
|
|
639
|
-
if (cached.version !== TS_VERSION) {
|
|
640
|
-
console.log(`Cache version mismatch: ${cached.version} vs ${TS_VERSION}`);
|
|
641
|
-
return null;
|
|
642
|
-
}
|
|
643
|
-
return new Map(Object.entries(cached.libs));
|
|
644
|
-
}
|
|
645
|
-
async set(libs) {
|
|
646
|
-
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
647
|
-
const store = tx.objectStore(STORE_NAME);
|
|
648
|
-
const key = getCacheKey();
|
|
649
|
-
const cached = {
|
|
650
|
-
version: TS_VERSION,
|
|
651
|
-
timestamp: Date.now(),
|
|
652
|
-
libs: Object.fromEntries(libs)
|
|
653
|
-
};
|
|
654
|
-
await promisifyRequest(store.put(cached, key));
|
|
606
|
+
get() {
|
|
607
|
+
return memoryCache;
|
|
655
608
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const store = tx.objectStore(STORE_NAME);
|
|
659
|
-
await promisifyRequest(store.clear());
|
|
609
|
+
set(libs) {
|
|
610
|
+
memoryCache = libs;
|
|
660
611
|
}
|
|
661
|
-
|
|
662
|
-
|
|
612
|
+
clear() {
|
|
613
|
+
memoryCache = null;
|
|
663
614
|
}
|
|
664
615
|
}
|
|
665
616
|
async function fetchAndCacheLibs(libs = getDefaultBrowserLibs()) {
|
|
666
|
-
const cache =
|
|
667
|
-
|
|
668
|
-
return await cache.getOrFetch(libs);
|
|
669
|
-
} finally {
|
|
670
|
-
cache.close();
|
|
671
|
-
}
|
|
617
|
+
const cache = new LibCache;
|
|
618
|
+
return cache.getOrFetch(libs);
|
|
672
619
|
}
|
|
673
620
|
// src/shared-modules.ts
|
|
674
621
|
var GLOBAL_KEY = "__sandlot_shared_modules__";
|
|
@@ -1132,15 +1079,32 @@ function formatDiagnosticsForAgent(diagnostics) {
|
|
|
1132
1079
|
|
|
1133
1080
|
// src/bundler.ts
|
|
1134
1081
|
var esbuild = null;
|
|
1082
|
+
function isServerEnvironment() {
|
|
1083
|
+
if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
|
|
1087
|
+
return true;
|
|
1088
|
+
}
|
|
1089
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
1090
|
+
return true;
|
|
1091
|
+
}
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1135
1094
|
async function getEsbuild() {
|
|
1136
1095
|
if (esbuild)
|
|
1137
1096
|
return esbuild;
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1097
|
+
if (isServerEnvironment()) {
|
|
1098
|
+
const mod = await import("esbuild");
|
|
1099
|
+
esbuild = mod.default ?? mod;
|
|
1100
|
+
} else {
|
|
1101
|
+
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
1102
|
+
const mod = await import(cdnUrl);
|
|
1103
|
+
esbuild = mod.default ?? mod;
|
|
1104
|
+
if (typeof esbuild?.initialize !== "function") {
|
|
1105
|
+
console.error("esbuild-wasm module structure:", mod);
|
|
1106
|
+
throw new Error("Failed to load esbuild-wasm: initialize function not found");
|
|
1107
|
+
}
|
|
1144
1108
|
}
|
|
1145
1109
|
return esbuild;
|
|
1146
1110
|
}
|
|
@@ -1168,12 +1132,14 @@ async function initBundler() {
|
|
|
1168
1132
|
await initPromise;
|
|
1169
1133
|
return;
|
|
1170
1134
|
}
|
|
1171
|
-
checkCrossOriginIsolation();
|
|
1172
1135
|
initPromise = (async () => {
|
|
1173
1136
|
const es = await getEsbuild();
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1137
|
+
if (!isServerEnvironment() && typeof es.initialize === "function") {
|
|
1138
|
+
checkCrossOriginIsolation();
|
|
1139
|
+
await es.initialize({
|
|
1140
|
+
wasmURL: getWasmUrl()
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1177
1143
|
})();
|
|
1178
1144
|
await initPromise;
|
|
1179
1145
|
initialized = true;
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ import { type BuildOutput, type ValidateFn } from "./commands";
|
|
|
4
4
|
import { type SharedResources } from "./shared-resources";
|
|
5
5
|
/**
|
|
6
6
|
* Options that can be passed through to the just-bash Bash constructor.
|
|
7
|
-
* Excludes options that sandlot controls internally (fs, customCommands, files).
|
|
7
|
+
* Excludes options that sandlot controls internally (fs, customCommands, files, cwd).
|
|
8
|
+
* The working directory is always root (/).
|
|
8
9
|
*/
|
|
9
|
-
export type SandboxBashOptions = Omit<BashOptions, 'fs' | 'customCommands' | 'files'>;
|
|
10
|
+
export type SandboxBashOptions = Omit<BashOptions, 'fs' | 'customCommands' | 'files' | 'cwd'>;
|
|
10
11
|
/**
|
|
11
12
|
* Options for creating a sandbox environment
|
|
12
13
|
*/
|
|
@@ -89,14 +90,13 @@ export interface SandboxOptions {
|
|
|
89
90
|
* Use this to configure environment variables, execution limits,
|
|
90
91
|
* network access, logging, and other bash-level settings.
|
|
91
92
|
*
|
|
92
|
-
* Note: `fs`, `customCommands`, and `
|
|
93
|
-
* and cannot be overridden here.
|
|
93
|
+
* Note: `fs`, `customCommands`, `files`, and `cwd` are controlled by sandlot
|
|
94
|
+
* and cannot be overridden here. The working directory is always root (/).
|
|
94
95
|
*
|
|
95
96
|
* @example
|
|
96
97
|
* ```ts
|
|
97
98
|
* const sandbox = await createSandbox({
|
|
98
99
|
* bashOptions: {
|
|
99
|
-
* cwd: '/src',
|
|
100
100
|
* env: { NODE_ENV: 'development' },
|
|
101
101
|
* executionLimits: { maxCommandCount: 1000 },
|
|
102
102
|
* },
|
package/dist/sandbox.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAA0B,MAAM,MAAM,CAAC;AAE1D,OAAO,EAA2C,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACxG,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI/E
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAA0B,MAAM,MAAM,CAAC;AAE1D,OAAO,EAA2C,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACxG,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI/E;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,gBAAgB,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC;AAE9F;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEtC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExD;;OAEG;IACH,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,EAAE,CAAC;IAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,EAAE,EAAE,UAAU,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;IAEX;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAE9B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,IAAI,YAAY,CAAC;IAEzB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC;IAE7E;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,EAAE,EAAE,UAAU,GAAG,IAAI,CAAC;IAEpC;;;OAGG;IACH,eAAe,IAAI,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA8GlF"}
|
package/dist/ts-libs.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* TypeScript standard library fetcher and cache.
|
|
3
3
|
*
|
|
4
4
|
* Fetches TypeScript's lib.*.d.ts files from jsDelivr CDN and caches
|
|
5
|
-
* them in
|
|
6
|
-
*
|
|
5
|
+
* them in memory. These files provide types for built-in JavaScript APIs
|
|
6
|
+
* (Array, Number, String) and browser APIs (console, window, document).
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Default libs for browser environment with ES2020 target.
|
|
@@ -46,21 +46,15 @@ export declare function fetchLibFile(name: string): Promise<string>;
|
|
|
46
46
|
*/
|
|
47
47
|
export declare function fetchAllLibs(libs: string[]): Promise<Map<string, string>>;
|
|
48
48
|
/**
|
|
49
|
-
* LibCache provides
|
|
49
|
+
* LibCache provides in-memory caching for TypeScript lib files.
|
|
50
50
|
*
|
|
51
51
|
* Usage:
|
|
52
52
|
* ```ts
|
|
53
|
-
* const cache =
|
|
53
|
+
* const cache = new LibCache();
|
|
54
54
|
* const libs = await cache.getOrFetch(getDefaultBrowserLibs());
|
|
55
55
|
* ```
|
|
56
56
|
*/
|
|
57
57
|
export declare class LibCache {
|
|
58
|
-
private db;
|
|
59
|
-
private constructor();
|
|
60
|
-
/**
|
|
61
|
-
* Create and initialize a LibCache instance.
|
|
62
|
-
*/
|
|
63
|
-
static create(): Promise<LibCache>;
|
|
64
58
|
/**
|
|
65
59
|
* Get cached libs if available, otherwise fetch from CDN and cache.
|
|
66
60
|
*
|
|
@@ -71,25 +65,18 @@ export declare class LibCache {
|
|
|
71
65
|
/**
|
|
72
66
|
* Get cached libs if available.
|
|
73
67
|
*/
|
|
74
|
-
get():
|
|
68
|
+
get(): Map<string, string> | null;
|
|
75
69
|
/**
|
|
76
70
|
* Store libs in the cache.
|
|
77
71
|
*/
|
|
78
|
-
set(libs: Map<string, string>):
|
|
72
|
+
set(libs: Map<string, string>): void;
|
|
79
73
|
/**
|
|
80
74
|
* Clear all cached libs.
|
|
81
75
|
*/
|
|
82
|
-
clear():
|
|
83
|
-
/**
|
|
84
|
-
* Close the database connection.
|
|
85
|
-
*/
|
|
86
|
-
close(): void;
|
|
76
|
+
clear(): void;
|
|
87
77
|
}
|
|
88
78
|
/**
|
|
89
79
|
* Convenience function to fetch and cache libs in one call.
|
|
90
|
-
* Creates a temporary LibCache, fetches libs, and returns the result.
|
|
91
|
-
*
|
|
92
|
-
* For repeated use, prefer creating a LibCache instance directly.
|
|
93
80
|
*
|
|
94
81
|
* @param libs - Lib names to fetch (defaults to getDefaultBrowserLibs())
|
|
95
82
|
* @returns Map of lib name to content
|
package/dist/ts-libs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ts-libs.d.ts","sourceRoot":"","sources":["../src/ts-libs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"ts-libs.d.ts","sourceRoot":"","sources":["../src/ts-libs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAY5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUhE;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA6C/E;AAQD;;;;;;;;GAQG;AACH,qBAAa,QAAQ;IACnB;;;;;OAKG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAuB9D;;OAEG;IACH,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAIjC;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAIpC;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,GAAE,MAAM,EAA4B,GACvC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAG9B"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandlot",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "TypeScript sandbox with esbuild bundling and type checking for browser and server",
|
|
5
5
|
"author": "blindmansion",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -43,8 +43,17 @@
|
|
|
43
43
|
"just-bash": "^2.6.0",
|
|
44
44
|
"typescript": "^5.9.3"
|
|
45
45
|
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"esbuild": ">=0.20.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"esbuild": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
46
54
|
"devDependencies": {
|
|
47
55
|
"@types/bun": "latest",
|
|
48
|
-
"@types/react": "^19"
|
|
56
|
+
"@types/react": "^19",
|
|
57
|
+
"esbuild": "^0.27.2"
|
|
49
58
|
}
|
|
50
59
|
}
|
package/src/bundler.ts
CHANGED
|
@@ -3,29 +3,55 @@ import type * as EsbuildTypes from "esbuild-wasm";
|
|
|
3
3
|
import { getPackageManifest, resolveToEsmUrl } from "./packages";
|
|
4
4
|
import { getSharedModuleRuntimeCode, getSharedModuleExports } from "./shared-modules";
|
|
5
5
|
|
|
6
|
-
// Lazily loaded esbuild module
|
|
6
|
+
// Lazily loaded esbuild module
|
|
7
7
|
let esbuild: typeof EsbuildTypes | null = null;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Detect if we're running in a server environment (Node.js, Bun, Deno)
|
|
11
|
+
* vs a browser environment.
|
|
12
|
+
*/
|
|
13
|
+
function isServerEnvironment(): boolean {
|
|
14
|
+
// Check for Bun
|
|
15
|
+
if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
// Check for Deno
|
|
19
|
+
if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
// Check for Node.js (process.versions.node exists)
|
|
23
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
async function getEsbuild(): Promise<typeof EsbuildTypes> {
|
|
10
30
|
if (esbuild) return esbuild;
|
|
11
31
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
if (isServerEnvironment()) {
|
|
33
|
+
// In server environments, use native esbuild for better performance
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
const mod: any = await import("esbuild");
|
|
36
|
+
esbuild = mod.default ?? mod;
|
|
37
|
+
} else {
|
|
38
|
+
// In browser, load esbuild-wasm from esm.sh CDN
|
|
39
|
+
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
const mod: any = await import(/* @vite-ignore */ cdnUrl);
|
|
43
|
+
|
|
44
|
+
// esm.sh typically provides both default and named exports
|
|
45
|
+
esbuild = mod.default ?? mod;
|
|
46
|
+
|
|
47
|
+
// Verify we have the initialize function (only needed for wasm version)
|
|
48
|
+
if (typeof esbuild?.initialize !== "function") {
|
|
49
|
+
console.error("esbuild-wasm module structure:", mod);
|
|
50
|
+
throw new Error("Failed to load esbuild-wasm: initialize function not found");
|
|
51
|
+
}
|
|
26
52
|
}
|
|
27
53
|
|
|
28
|
-
return esbuild
|
|
54
|
+
return esbuild!;
|
|
29
55
|
}
|
|
30
56
|
|
|
31
57
|
/**
|
|
@@ -166,8 +192,12 @@ function checkCrossOriginIsolation(): void {
|
|
|
166
192
|
}
|
|
167
193
|
|
|
168
194
|
/**
|
|
169
|
-
* Initialize esbuild
|
|
195
|
+
* Initialize esbuild. Called automatically on first bundle.
|
|
170
196
|
* Can be called explicitly to pre-warm.
|
|
197
|
+
*
|
|
198
|
+
* In browser environments, this loads and initializes esbuild-wasm.
|
|
199
|
+
* In server environments (Node.js, Bun, Deno), this loads native esbuild
|
|
200
|
+
* which doesn't require WASM initialization.
|
|
171
201
|
*/
|
|
172
202
|
export async function initBundler(): Promise<void> {
|
|
173
203
|
if (initialized) return;
|
|
@@ -177,13 +207,16 @@ export async function initBundler(): Promise<void> {
|
|
|
177
207
|
return;
|
|
178
208
|
}
|
|
179
209
|
|
|
180
|
-
checkCrossOriginIsolation();
|
|
181
|
-
|
|
182
210
|
initPromise = (async () => {
|
|
183
211
|
const es = await getEsbuild();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
212
|
+
|
|
213
|
+
// Native esbuild doesn't need initialization, only esbuild-wasm does
|
|
214
|
+
if (!isServerEnvironment() && typeof es.initialize === "function") {
|
|
215
|
+
checkCrossOriginIsolation();
|
|
216
|
+
await es.initialize({
|
|
217
|
+
wasmURL: getWasmUrl(),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
187
220
|
})();
|
|
188
221
|
|
|
189
222
|
await initPromise;
|
package/src/sandbox.ts
CHANGED
|
@@ -8,9 +8,10 @@ import { installPackage, parseImportPath } from "./packages";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Options that can be passed through to the just-bash Bash constructor.
|
|
11
|
-
* Excludes options that sandlot controls internally (fs, customCommands, files).
|
|
11
|
+
* Excludes options that sandlot controls internally (fs, customCommands, files, cwd).
|
|
12
|
+
* The working directory is always root (/).
|
|
12
13
|
*/
|
|
13
|
-
export type SandboxBashOptions = Omit<BashOptions, 'fs' | 'customCommands' | 'files'>;
|
|
14
|
+
export type SandboxBashOptions = Omit<BashOptions, 'fs' | 'customCommands' | 'files' | 'cwd'>;
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Options for creating a sandbox environment
|
|
@@ -101,14 +102,13 @@ export interface SandboxOptions {
|
|
|
101
102
|
* Use this to configure environment variables, execution limits,
|
|
102
103
|
* network access, logging, and other bash-level settings.
|
|
103
104
|
*
|
|
104
|
-
* Note: `fs`, `customCommands`, and `
|
|
105
|
-
* and cannot be overridden here.
|
|
105
|
+
* Note: `fs`, `customCommands`, `files`, and `cwd` are controlled by sandlot
|
|
106
|
+
* and cannot be overridden here. The working directory is always root (/).
|
|
106
107
|
*
|
|
107
108
|
* @example
|
|
108
109
|
* ```ts
|
|
109
110
|
* const sandbox = await createSandbox({
|
|
110
111
|
* bashOptions: {
|
|
111
|
-
* cwd: '/src',
|
|
112
112
|
* env: { NODE_ENV: 'development' },
|
|
113
113
|
* executionLimits: { maxCommandCount: 1000 },
|
|
114
114
|
* },
|
|
@@ -371,8 +371,10 @@ export async function createSandbox(options: SandboxOptions = {}): Promise<Sandb
|
|
|
371
371
|
const defaultCommands = createDefaultCommands(commandDeps);
|
|
372
372
|
|
|
373
373
|
// Create bash environment with the custom filesystem
|
|
374
|
+
// Always start in root directory (/) for consistent behavior
|
|
374
375
|
const bash = new Bash({
|
|
375
376
|
...bashOptions,
|
|
377
|
+
cwd: '/',
|
|
376
378
|
fs,
|
|
377
379
|
customCommands: [...defaultCommands, ...customCommands],
|
|
378
380
|
});
|
package/src/ts-libs.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* TypeScript standard library fetcher and cache.
|
|
3
3
|
*
|
|
4
4
|
* Fetches TypeScript's lib.*.d.ts files from jsDelivr CDN and caches
|
|
5
|
-
* them in
|
|
6
|
-
*
|
|
5
|
+
* them in memory. These files provide types for built-in JavaScript APIs
|
|
6
|
+
* (Array, Number, String) and browser APIs (console, window, document).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -17,13 +17,6 @@ const TS_VERSION = "5.9.3";
|
|
|
17
17
|
*/
|
|
18
18
|
const CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* IndexedDB database name for lib cache
|
|
22
|
-
*/
|
|
23
|
-
const DB_NAME = "ts-lib-cache";
|
|
24
|
-
const DB_VERSION = 1;
|
|
25
|
-
const STORE_NAME = "libs";
|
|
26
|
-
|
|
27
20
|
/**
|
|
28
21
|
* Default libs for browser environment with ES2020 target.
|
|
29
22
|
* These provide types for console, DOM APIs, and modern JS features.
|
|
@@ -145,74 +138,21 @@ export async function fetchAllLibs(libs: string[]): Promise<Map<string, string>>
|
|
|
145
138
|
}
|
|
146
139
|
|
|
147
140
|
/**
|
|
148
|
-
*
|
|
149
|
-
|
|
150
|
-
async function openDatabase(): Promise<IDBDatabase> {
|
|
151
|
-
return new Promise((resolve, reject) => {
|
|
152
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
153
|
-
|
|
154
|
-
request.onerror = () => reject(request.error);
|
|
155
|
-
request.onsuccess = () => resolve(request.result);
|
|
156
|
-
|
|
157
|
-
request.onupgradeneeded = (event) => {
|
|
158
|
-
const db = (event.target as IDBOpenDBRequest).result;
|
|
159
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
160
|
-
db.createObjectStore(STORE_NAME);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Promisify an IDBRequest
|
|
141
|
+
* In-memory cache for TypeScript lib files.
|
|
142
|
+
* Shared across all LibCache instances.
|
|
168
143
|
*/
|
|
169
|
-
|
|
170
|
-
return new Promise((resolve, reject) => {
|
|
171
|
-
request.onsuccess = () => resolve(request.result);
|
|
172
|
-
request.onerror = () => reject(request.error);
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Cache key for the current TypeScript version
|
|
178
|
-
*/
|
|
179
|
-
function getCacheKey(): string {
|
|
180
|
-
return `libs-${TS_VERSION}`;
|
|
181
|
-
}
|
|
144
|
+
let memoryCache: Map<string, string> | null = null;
|
|
182
145
|
|
|
183
146
|
/**
|
|
184
|
-
*
|
|
185
|
-
*/
|
|
186
|
-
interface CachedLibs {
|
|
187
|
-
version: string;
|
|
188
|
-
timestamp: number;
|
|
189
|
-
libs: Record<string, string>;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* LibCache provides IndexedDB-backed caching for TypeScript lib files.
|
|
147
|
+
* LibCache provides in-memory caching for TypeScript lib files.
|
|
194
148
|
*
|
|
195
149
|
* Usage:
|
|
196
150
|
* ```ts
|
|
197
|
-
* const cache =
|
|
151
|
+
* const cache = new LibCache();
|
|
198
152
|
* const libs = await cache.getOrFetch(getDefaultBrowserLibs());
|
|
199
153
|
* ```
|
|
200
154
|
*/
|
|
201
155
|
export class LibCache {
|
|
202
|
-
private db: IDBDatabase;
|
|
203
|
-
|
|
204
|
-
private constructor(db: IDBDatabase) {
|
|
205
|
-
this.db = db;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Create and initialize a LibCache instance.
|
|
210
|
-
*/
|
|
211
|
-
static async create(): Promise<LibCache> {
|
|
212
|
-
const db = await openDatabase();
|
|
213
|
-
return new LibCache(db);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
156
|
/**
|
|
217
157
|
* Get cached libs if available, otherwise fetch from CDN and cache.
|
|
218
158
|
*
|
|
@@ -221,12 +161,11 @@ export class LibCache {
|
|
|
221
161
|
*/
|
|
222
162
|
async getOrFetch(libs: string[]): Promise<Map<string, string>> {
|
|
223
163
|
// Try to get from cache first
|
|
224
|
-
|
|
225
|
-
if (cached) {
|
|
164
|
+
if (memoryCache) {
|
|
226
165
|
// Verify all requested libs are in cache
|
|
227
|
-
const missing = libs.filter((lib) => !
|
|
166
|
+
const missing = libs.filter((lib) => !memoryCache!.has(lib));
|
|
228
167
|
if (missing.length === 0) {
|
|
229
|
-
return
|
|
168
|
+
return memoryCache;
|
|
230
169
|
}
|
|
231
170
|
// Some libs missing, fetch all and update cache
|
|
232
171
|
console.log(`Cache missing libs: ${missing.join(", ")}, fetching all...`);
|
|
@@ -238,7 +177,7 @@ export class LibCache {
|
|
|
238
177
|
console.log(`Fetched ${fetched.size} lib files`);
|
|
239
178
|
|
|
240
179
|
// Cache the results
|
|
241
|
-
|
|
180
|
+
memoryCache = fetched;
|
|
242
181
|
|
|
243
182
|
return fetched;
|
|
244
183
|
}
|
|
@@ -246,64 +185,27 @@ export class LibCache {
|
|
|
246
185
|
/**
|
|
247
186
|
* Get cached libs if available.
|
|
248
187
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const store = tx.objectStore(STORE_NAME);
|
|
252
|
-
const key = getCacheKey();
|
|
253
|
-
|
|
254
|
-
const cached = await promisifyRequest<CachedLibs | undefined>(store.get(key));
|
|
255
|
-
if (!cached) {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Verify version matches
|
|
260
|
-
if (cached.version !== TS_VERSION) {
|
|
261
|
-
console.log(`Cache version mismatch: ${cached.version} vs ${TS_VERSION}`);
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return new Map(Object.entries(cached.libs));
|
|
188
|
+
get(): Map<string, string> | null {
|
|
189
|
+
return memoryCache;
|
|
266
190
|
}
|
|
267
191
|
|
|
268
192
|
/**
|
|
269
193
|
* Store libs in the cache.
|
|
270
194
|
*/
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const store = tx.objectStore(STORE_NAME);
|
|
274
|
-
const key = getCacheKey();
|
|
275
|
-
|
|
276
|
-
const cached: CachedLibs = {
|
|
277
|
-
version: TS_VERSION,
|
|
278
|
-
timestamp: Date.now(),
|
|
279
|
-
libs: Object.fromEntries(libs),
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
await promisifyRequest(store.put(cached, key));
|
|
195
|
+
set(libs: Map<string, string>): void {
|
|
196
|
+
memoryCache = libs;
|
|
283
197
|
}
|
|
284
198
|
|
|
285
199
|
/**
|
|
286
200
|
* Clear all cached libs.
|
|
287
201
|
*/
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const store = tx.objectStore(STORE_NAME);
|
|
291
|
-
await promisifyRequest(store.clear());
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Close the database connection.
|
|
296
|
-
*/
|
|
297
|
-
close(): void {
|
|
298
|
-
this.db.close();
|
|
202
|
+
clear(): void {
|
|
203
|
+
memoryCache = null;
|
|
299
204
|
}
|
|
300
205
|
}
|
|
301
206
|
|
|
302
207
|
/**
|
|
303
208
|
* Convenience function to fetch and cache libs in one call.
|
|
304
|
-
* Creates a temporary LibCache, fetches libs, and returns the result.
|
|
305
|
-
*
|
|
306
|
-
* For repeated use, prefer creating a LibCache instance directly.
|
|
307
209
|
*
|
|
308
210
|
* @param libs - Lib names to fetch (defaults to getDefaultBrowserLibs())
|
|
309
211
|
* @returns Map of lib name to content
|
|
@@ -311,10 +213,6 @@ export class LibCache {
|
|
|
311
213
|
export async function fetchAndCacheLibs(
|
|
312
214
|
libs: string[] = getDefaultBrowserLibs()
|
|
313
215
|
): Promise<Map<string, string>> {
|
|
314
|
-
const cache =
|
|
315
|
-
|
|
316
|
-
return await cache.getOrFetch(libs);
|
|
317
|
-
} finally {
|
|
318
|
-
cache.close();
|
|
319
|
-
}
|
|
216
|
+
const cache = new LibCache();
|
|
217
|
+
return cache.getOrFetch(libs);
|
|
320
218
|
}
|