wasm-idle 1.0.5 → 1.0.7

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 (112) hide show
  1. package/README.md +52 -5
  2. package/dist/index.d.ts +4 -1
  3. package/dist/index.js +2 -0
  4. package/dist/playground/assetBridge.d.ts +11 -2
  5. package/dist/playground/assetBridge.js +190 -46
  6. package/dist/playground/assets.d.ts +9 -1
  7. package/dist/playground/assets.js +29 -5
  8. package/dist/playground/awk.js +1 -0
  9. package/dist/playground/bash.d.ts +1 -0
  10. package/dist/playground/bash.js +7 -1
  11. package/dist/playground/bqn.js +1 -0
  12. package/dist/playground/bqnPreflight.d.ts +2 -1
  13. package/dist/playground/bqnPreflight.js +1 -0
  14. package/dist/playground/c3.js +1 -0
  15. package/dist/playground/cachedClang.d.ts +10 -0
  16. package/dist/playground/cachedClang.js +330 -0
  17. package/dist/playground/clang.d.ts +8 -0
  18. package/dist/playground/clang.js +47 -8
  19. package/dist/playground/clangWorkerProtocol.d.ts +57 -0
  20. package/dist/playground/clangWorkerProtocol.js +11 -0
  21. package/dist/playground/clojurescript.js +1 -0
  22. package/dist/playground/clojurescriptPreflight.d.ts +2 -1
  23. package/dist/playground/clojurescriptPreflight.js +1 -0
  24. package/dist/playground/compiledArtifactCache.d.ts +21 -0
  25. package/dist/playground/compiledArtifactCache.js +67 -0
  26. package/dist/playground/elixir.js +4 -1
  27. package/dist/playground/forth.js +1 -0
  28. package/dist/playground/forthPreflight.d.ts +2 -1
  29. package/dist/playground/forthPreflight.js +1 -0
  30. package/dist/playground/fortran.js +7 -1
  31. package/dist/playground/haskell.js +4 -1
  32. package/dist/playground/haskellAssets.d.ts +2 -1
  33. package/dist/playground/haskellAssets.js +8 -8
  34. package/dist/playground/index.d.ts +1 -1
  35. package/dist/playground/index.js +2 -2
  36. package/dist/playground/j.js +1 -0
  37. package/dist/playground/jPreflight.d.ts +2 -1
  38. package/dist/playground/jPreflight.js +1 -0
  39. package/dist/playground/janet.js +1 -0
  40. package/dist/playground/java.js +20 -8
  41. package/dist/playground/julia.js +1 -0
  42. package/dist/playground/lfortranAssets.d.ts +2 -1
  43. package/dist/playground/lfortranAssets.js +1 -0
  44. package/dist/playground/lisp.js +4 -1
  45. package/dist/playground/lispAssets.d.ts +2 -1
  46. package/dist/playground/lispAssets.js +10 -0
  47. package/dist/playground/lldbManifest.d.ts +2 -2
  48. package/dist/playground/lldbManifest.js +17 -3
  49. package/dist/playground/lldbSession.d.ts +1 -0
  50. package/dist/playground/lldbSession.js +2 -1
  51. package/dist/playground/nim.js +1 -0
  52. package/dist/playground/ocaml.d.ts +1 -0
  53. package/dist/playground/ocaml.js +12 -1
  54. package/dist/playground/options.d.ts +2 -0
  55. package/dist/playground/pascal.js +1 -0
  56. package/dist/playground/perl.js +1 -0
  57. package/dist/playground/prolog.js +1 -0
  58. package/dist/playground/python.d.ts +7 -0
  59. package/dist/playground/python.js +41 -8
  60. package/dist/playground/runtimeAssetCache.d.ts +26 -0
  61. package/dist/playground/runtimeAssetCache.js +84 -0
  62. package/dist/playground/runtimeSession.d.ts +21 -0
  63. package/dist/playground/runtimeSession.js +119 -0
  64. package/dist/playground/rust.d.ts +1 -0
  65. package/dist/playground/rust.js +11 -1
  66. package/dist/playground/rustExecutableGraph.d.ts +1 -0
  67. package/dist/playground/rustExecutableGraph.js +1 -0
  68. package/dist/playground/staticRuntimePreflight.d.ts +2 -1
  69. package/dist/playground/staticRuntimePreflight.js +4 -2
  70. package/dist/playground/staticRuntimePreflightExecute.js +1 -0
  71. package/dist/playground/staticRuntimePreflightProtocol.d.ts +2 -1
  72. package/dist/playground/staticWorkerRuntime.d.ts +3 -1
  73. package/dist/playground/staticWorkerRuntime.js +44 -3
  74. package/dist/playground/tcl.js +1 -0
  75. package/dist/playground/tinygo.d.ts +2 -0
  76. package/dist/playground/tinygo.js +57 -3
  77. package/dist/playground/tinygoExecutableGraph.d.ts +4 -1
  78. package/dist/playground/tinygoExecutableGraph.js +59 -7
  79. package/dist/playground/typescript.js +4 -1
  80. package/dist/playground/wasmGoVersion.d.ts +1 -1
  81. package/dist/playground/wasmGoVersion.js +1 -1
  82. package/dist/playground/wasmOcamlVersion.d.ts +2 -2
  83. package/dist/playground/wasmOcamlVersion.js +1 -1
  84. package/dist/playground/wasmRustVersion.d.ts +90 -24
  85. package/dist/playground/wasmRustVersion.js +98 -23
  86. package/dist/playground/wasmTinyGoVersion.d.ts +40 -29
  87. package/dist/playground/wasmTinyGoVersion.js +43 -31
  88. package/dist/playground/worker/assets.d.ts +3 -0
  89. package/dist/playground/worker/assets.js +41 -0
  90. package/dist/playground/worker/cachedClangCompile.d.ts +1 -0
  91. package/dist/playground/worker/cachedClangCompile.js +85 -0
  92. package/dist/playground/worker/cachedClangExecute.d.ts +1 -0
  93. package/dist/playground/worker/cachedClangExecute.js +67 -0
  94. package/dist/playground/worker/clang.js +2 -8
  95. package/dist/playground/worker/clangStreaming.d.ts +1 -1
  96. package/dist/playground/worker/clangStreaming.js +20 -2
  97. package/dist/playground/worker/elixir.js +10 -8
  98. package/dist/playground/worker/fortran.js +4 -7
  99. package/dist/playground/worker/haskell.js +2 -0
  100. package/dist/playground/worker/lisp.js +2 -0
  101. package/dist/playground/worker/ocaml.js +13 -1
  102. package/dist/playground/worker/python.js +3 -2
  103. package/dist/playground/worker/runtimeAssetFetch.d.ts +11 -1
  104. package/dist/playground/worker/runtimeAssetFetch.js +64 -1
  105. package/dist/playground/worker/runtimeModule.d.ts +8 -0
  106. package/dist/playground/worker/runtimeModule.js +49 -0
  107. package/dist/playground/worker/rust.js +9 -2
  108. package/dist/playground/worker/staticRuntimePreflight.js +2 -0
  109. package/dist/playground/worker/typescript.js +4 -1
  110. package/dist/playground/worker/zig.js +7 -4
  111. package/dist/playground/zig.js +4 -1
  112. package/package.json +10 -7
@@ -5,7 +5,7 @@ import { createWasmIdleSharedBuffer } from './sharedBuffer';
5
5
  import { WorkerSession } from './workerSession';
6
6
  import { reportWorkerInputReady, reportWorkerProgress } from './workerProgress';
7
7
  import { WASM_OCAML_RUNTIME_PROFILE } from './wasmOcamlVersion';
8
- import { BusyError, CancelledError, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, ProtocolError, RuntimeConfigurationError, TimeoutError, WorkspaceValidationError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
8
+ import { BusyError, CancelledError, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, ProtocolError, RuntimeConfigurationError, TimeoutError, WorkspaceValidationError, resolveExecutionLimits, resolveRuntimeAssetCacheOptions, validateExecutionWorkspace } from '@wasm-idle/core';
9
9
  const OCAML_EXECUTION_LIMIT_KEYS = [
10
10
  'assetTimeoutMs',
11
11
  'startupTimeoutMs',
@@ -55,6 +55,7 @@ class Ocaml {
55
55
  moduleReceiptIdentity = '';
56
56
  manifestReceiptIdentity = '';
57
57
  maxAssetBytes = 0;
58
+ persistentCache = resolveRuntimeAssetCacheOptions();
58
59
  oncompilerdiagnostic;
59
60
  waitingForInput = false;
60
61
  pendingEof = false;
@@ -308,6 +309,7 @@ class Ocaml {
308
309
  let nextModuleReceipt;
309
310
  let nextManifestReceipt;
310
311
  let buffer;
312
+ let persistentCache;
311
313
  try {
312
314
  signal = options.signal;
313
315
  if (!this.isOperationActive(operation)) {
@@ -322,6 +324,7 @@ class Ocaml {
322
324
  return Promise.reject(this.releaseBeforeSession(operation, 'OCaml runtime startup cancelled'));
323
325
  }
324
326
  limits = this.resolveOperationLimits(operation, configuredLimits);
327
+ persistentCache = resolveRuntimeAssetCacheOptions(typeof runtimeAssets === 'object' ? runtimeAssets?.persistentCache : undefined, options.persistentCache);
325
328
  if (!this.isOperationActive(operation)) {
326
329
  return Promise.reject(this.releaseBeforeSession(operation, 'OCaml runtime startup cancelled'));
327
330
  }
@@ -459,6 +462,7 @@ class Ocaml {
459
462
  this.moduleReceiptIdentity = receiptIdentity(nextModuleReceipt);
460
463
  this.manifestReceiptIdentity = receiptIdentity(nextManifestReceipt);
461
464
  this.maxAssetBytes = limits.maxAssetBytes;
465
+ this.persistentCache = persistentCache;
462
466
  resolve();
463
467
  this.completeOperation(operation);
464
468
  return;
@@ -473,6 +477,7 @@ class Ocaml {
473
477
  worker.onmessage = handler;
474
478
  worker.postMessage({
475
479
  load: true,
480
+ persistentCache,
476
481
  moduleUrl: nextModuleUrl,
477
482
  manifestUrl: nextManifestUrl,
478
483
  moduleReceipt: nextModuleReceipt,
@@ -481,6 +486,7 @@ class Ocaml {
481
486
  });
482
487
  }
483
488
  else {
489
+ this.persistentCache = persistentCache;
484
490
  progress?.set?.(1);
485
491
  if (!this.isOperationActive(operation))
486
492
  return;
@@ -529,6 +535,7 @@ class Ocaml {
529
535
  let buffer;
530
536
  let outputCallback;
531
537
  let onDiagnostic;
538
+ let persistentCache;
532
539
  try {
533
540
  signal = options.signal;
534
541
  if (!this.isOperationActive(operation)) {
@@ -545,6 +552,9 @@ class Ocaml {
545
552
  if (!configuredWorker)
546
553
  throw 'Worker not loaded';
547
554
  worker = configuredWorker;
555
+ persistentCache = resolveRuntimeAssetCacheOptions(this.persistentCache, options.persistentCache);
556
+ if (!this.isOperationActive(operation))
557
+ throw this.releaseBeforeSession(operation, 'OCaml execution cancelled');
548
558
  const configuredTarget = options.ocamlBackend;
549
559
  if (!this.isOperationActive(operation)) {
550
560
  throw this.releaseBeforeSession(operation, 'OCaml execution cancelled');
@@ -814,6 +824,7 @@ class Ocaml {
814
824
  try {
815
825
  worker.postMessage({
816
826
  code,
827
+ persistentCache,
817
828
  prepare,
818
829
  target,
819
830
  wasmBinaryenMode,
@@ -21,6 +21,8 @@ export interface SandboxWorkspaceFile {
21
21
  content: string;
22
22
  }
23
23
  export interface SandboxExecutionOptions {
24
+ /** Overrides instance/global persistent asset caching for this operation. */
25
+ persistentCache?: import('@wasm-idle/core').RuntimeAssetCacheOptions;
24
26
  debugMode?: BrowserDebugMode;
25
27
  /** @deprecated Use debugMode. true maps to trace unless the caller explicitly selects lldb. */
26
28
  debug?: boolean;
@@ -40,6 +40,7 @@ class Pascal extends StaticWorkerRuntimeSandbox {
40
40
  systemPascalUrl: resolved.systemPascalUrl,
41
41
  profile,
42
42
  limits: context.limits,
43
+ persistentCache: context.persistentCache,
43
44
  signal: context.signal,
44
45
  reportProgress(progress) {
45
46
  loadedByAsset.set(progress.assetKey, progress.loadedBytes);
@@ -38,6 +38,7 @@ class Perl extends StaticWorkerRuntimeSandbox {
38
38
  manifestUrl: urls.manifestUrl || '',
39
39
  profile,
40
40
  limits: context.limits,
41
+ persistentCache: context.persistentCache,
41
42
  signal: context.signal,
42
43
  reportProgress(progress) {
43
44
  loadedByAsset.set(progress.assetKey, progress.loadedBytes);
@@ -47,6 +47,7 @@ class Prolog extends StaticWorkerRuntimeSandbox {
47
47
  manifestUrl: urls.manifestUrl || '',
48
48
  profile,
49
49
  limits: context.limits,
50
+ persistentCache: context.persistentCache,
50
51
  signal: context.signal,
51
52
  reportProgress(progress) {
52
53
  if (progress.kind === 'asset') {
@@ -1,5 +1,6 @@
1
1
  import type { DebugCommand, DebugSessionEvent, SandboxExecutionOptions } from './options';
2
2
  import { WorkerAssetBridge } from './assetBridge';
3
+ import { RuntimeAssetCache } from './runtimeAssetCache';
3
4
  import { type PlaygroundRuntimeAssets } from './assets';
4
5
  import type { Sandbox, SandboxProgress } from './sandbox';
5
6
  declare class Python implements Sandbox {
@@ -26,6 +27,10 @@ declare class Python implements Sandbox {
26
27
  exit: boolean;
27
28
  assetBridge: WorkerAssetBridge | null;
28
29
  private activeOperation;
30
+ private runtimeAssetCache;
31
+ private ownsRuntimeAssetCache;
32
+ private disposed;
33
+ private disposal?;
29
34
  private readonly workerSession;
30
35
  private beginOperation;
31
36
  private isOperationActive;
@@ -46,5 +51,7 @@ declare class Python implements Sandbox {
46
51
  kill(): void;
47
52
  terminate(reason?: unknown): void;
48
53
  clear(): Promise<void>;
54
+ setRuntimeAssetCache(cache: RuntimeAssetCache): void;
55
+ dispose(): Promise<void>;
49
56
  }
50
57
  export default Python;
@@ -1,4 +1,5 @@
1
1
  import { WorkerAssetBridge } from './assetBridge';
2
+ import { RuntimeAssetCache } from './runtimeAssetCache';
2
3
  import { resolveRuntimeAssetConfig } from './assets';
3
4
  import { bufferedSequence, flushBufferedEof, flushQueuedStdin, resetBufferedStdin, waitForBufferedSequenceChange } from './stdinBuffer';
4
5
  import { createWasmIdleSharedBuffer, requireSharedArrayBuffer } from './sharedBuffer';
@@ -29,6 +30,10 @@ class Python {
29
30
  exit = true;
30
31
  assetBridge = null;
31
32
  activeOperation = null;
33
+ runtimeAssetCache = new RuntimeAssetCache();
34
+ ownsRuntimeAssetCache = true;
35
+ disposed = false;
36
+ disposal;
32
37
  workerSession = new WorkerSession({
33
38
  label: 'Python',
34
39
  onDispose: (worker) => {
@@ -44,6 +49,8 @@ class Python {
44
49
  }
45
50
  });
46
51
  beginOperation(phase) {
52
+ if (this.disposed)
53
+ throw new Error('Python runtime has been disposed');
47
54
  if (this.activeOperation) {
48
55
  throw new BusyError('Python runtime already has an active operation', {
49
56
  runtimeId: 'PYTHON3',
@@ -198,7 +205,7 @@ class Python {
198
205
  this.pendingInput = [];
199
206
  this.waitingForInput = false;
200
207
  this.pendingEof = false;
201
- const assetConfig = resolveRuntimeAssetConfig('python', runtimeAssets, typeof window !== 'undefined' ? window.location.href : '');
208
+ const assetConfig = resolveRuntimeAssetConfig('python', runtimeAssets, typeof window !== 'undefined' ? window.location.href : '', options.persistentCache);
202
209
  if (!this.isOperationActive(operation))
203
210
  return;
204
211
  const needsWorkerReset = !this.worker ||
@@ -220,7 +227,7 @@ class Python {
220
227
  }
221
228
  this.worker = worker;
222
229
  this.workerSession.attach(worker);
223
- const assetBridge = new WorkerAssetBridge(worker, 'python', assetConfig, progress, limits.maxAssetBytes);
230
+ const assetBridge = new WorkerAssetBridge(worker, 'python', assetConfig, progress, limits.maxAssetBytes, false, this.runtimeAssetCache);
224
231
  if (!this.isOperationActive(operation) || this.worker !== worker) {
225
232
  assetBridge.dispose();
226
233
  return;
@@ -259,7 +266,8 @@ class Python {
259
266
  assets: {
260
267
  baseUrl: assetConfig.baseUrl,
261
268
  maxAssetBytes: limits.maxAssetBytes,
262
- useAssetBridge: assetConfig.useAssetBridge
269
+ useAssetBridge: assetConfig.useAssetBridge,
270
+ useModuleBridge: true
263
271
  }
264
272
  });
265
273
  return;
@@ -335,11 +343,13 @@ class Python {
335
343
  activePath = options.activePath;
336
344
  debugPath = options.debugPath;
337
345
  workspaceFiles = options.workspaceFiles;
346
+ const persistentCache = options.persistentCache;
338
347
  if (!this.isOperationActive(activeOperation)) {
339
348
  return Promise.reject(this.releaseBeforeSession(activeOperation, 'Python execution cancelled'));
340
349
  }
341
350
  if (debug)
342
351
  requireSharedArrayBuffer('Python debugging');
352
+ this.assetBridge?.setExecutionPersistentCache(persistentCache);
343
353
  this.setBreakpoints(debug ? breakpoints : []);
344
354
  }
345
355
  catch (error) {
@@ -545,11 +555,34 @@ class Python {
545
555
  resetBufferedStdin(this.watchResultBuffer);
546
556
  const debugBuffer = new Int32Array(this.debugBuffer);
547
557
  debugBuffer.fill(0);
548
- await new Promise((resolve) => setTimeout(resolve, 200));
549
- if (!this.exit) {
550
- this.workerSession.terminate();
551
- this.exit = true;
552
- }
558
+ new Uint8Array(this.interruptBuffer).fill(0);
559
+ }
560
+ setRuntimeAssetCache(cache) {
561
+ if (this.disposed)
562
+ throw new Error('Python runtime has been disposed');
563
+ if (this.worker || this.activeOperation)
564
+ throw new Error('Cannot replace an active runtime cache');
565
+ if (cache === this.runtimeAssetCache)
566
+ return;
567
+ if (this.ownsRuntimeAssetCache)
568
+ this.runtimeAssetCache.dispose();
569
+ this.runtimeAssetCache = cache;
570
+ this.ownsRuntimeAssetCache = false;
571
+ }
572
+ dispose() {
573
+ if (this.disposal)
574
+ return this.disposal;
575
+ this.disposed = true;
576
+ this.disposal = Promise.resolve()
577
+ .then(() => this.clear())
578
+ .finally(() => {
579
+ if (this.ownsRuntimeAssetCache)
580
+ this.runtimeAssetCache.dispose();
581
+ this.output = null;
582
+ this.ondebug = undefined;
583
+ this.image = undefined;
584
+ });
585
+ return this.disposal;
553
586
  }
554
587
  }
555
588
  export default Python;
@@ -0,0 +1,26 @@
1
+ /** Immutable bootstrap assets only: never store a worker, Wasm instance, or user filesystem. */
2
+ export declare class RuntimeAssetCache {
3
+ private readonly maxBytes;
4
+ private readonly maxEntries;
5
+ private entries;
6
+ private identities;
7
+ private nextIdentity;
8
+ private bytes;
9
+ private hits;
10
+ private misses;
11
+ private disposed;
12
+ constructor(maxBytes?: number, maxEntries?: number);
13
+ /** Identity is owner-local; a custom loader must never alias another loader. */
14
+ identity(value: object | undefined): number;
15
+ get<T>(key: string): T | undefined;
16
+ /** Only the trusted host publishes fully verified results; failures are never retained. */
17
+ set<T>(key: string, value: T, bytes: number): void;
18
+ stats(): {
19
+ hits: number;
20
+ misses: number;
21
+ entries: number;
22
+ bytes: number;
23
+ disposed: boolean;
24
+ };
25
+ dispose(): void;
26
+ }
@@ -0,0 +1,84 @@
1
+ /** Immutable bootstrap assets only: never store a worker, Wasm instance, or user filesystem. */
2
+ export class RuntimeAssetCache {
3
+ maxBytes;
4
+ maxEntries;
5
+ entries = new Map();
6
+ identities = new WeakMap();
7
+ nextIdentity = 0;
8
+ bytes = 0;
9
+ hits = 0;
10
+ misses = 0;
11
+ disposed = false;
12
+ constructor(maxBytes = 256 * 1024 * 1024, maxEntries = 64) {
13
+ this.maxBytes = maxBytes;
14
+ this.maxEntries = maxEntries;
15
+ if (!Number.isSafeInteger(maxBytes) ||
16
+ maxBytes < 0 ||
17
+ !Number.isSafeInteger(maxEntries) ||
18
+ maxEntries < 0) {
19
+ throw new TypeError('Runtime cache limits must be non-negative safe integers');
20
+ }
21
+ }
22
+ /** Identity is owner-local; a custom loader must never alias another loader. */
23
+ identity(value) {
24
+ if (!value)
25
+ return 0;
26
+ let identity = this.identities.get(value);
27
+ if (identity === undefined) {
28
+ identity = ++this.nextIdentity;
29
+ this.identities.set(value, identity);
30
+ }
31
+ return identity;
32
+ }
33
+ get(key) {
34
+ if (this.disposed)
35
+ return undefined;
36
+ const entry = this.entries.get(key);
37
+ if (!entry) {
38
+ this.misses++;
39
+ return undefined;
40
+ }
41
+ this.hits++;
42
+ this.entries.delete(key);
43
+ this.entries.set(key, entry);
44
+ return entry.value;
45
+ }
46
+ /** Only the trusted host publishes fully verified results; failures are never retained. */
47
+ set(key, value, bytes) {
48
+ if (this.disposed)
49
+ return;
50
+ if (!Number.isSafeInteger(bytes) || bytes < 0)
51
+ throw new TypeError('Invalid cache byte count');
52
+ const charge = bytes + key.length * 2;
53
+ if (this.maxEntries === 0 || charge > this.maxBytes)
54
+ return;
55
+ const previous = this.entries.get(key);
56
+ if (previous)
57
+ this.bytes -= previous.bytes;
58
+ this.entries.delete(key);
59
+ this.entries.set(key, { value, bytes: charge });
60
+ this.bytes += charge;
61
+ while (this.entries.size > this.maxEntries || this.bytes > this.maxBytes) {
62
+ const oldest = this.entries.keys().next().value;
63
+ this.bytes -= this.entries.get(oldest).bytes;
64
+ this.entries.delete(oldest);
65
+ }
66
+ }
67
+ stats() {
68
+ return {
69
+ hits: this.hits,
70
+ misses: this.misses,
71
+ entries: this.entries.size,
72
+ bytes: this.bytes,
73
+ disposed: this.disposed
74
+ };
75
+ }
76
+ dispose() {
77
+ if (this.disposed)
78
+ return;
79
+ this.disposed = true;
80
+ this.entries.clear();
81
+ this.identities = new WeakMap();
82
+ this.bytes = 0;
83
+ }
84
+ }
@@ -0,0 +1,21 @@
1
+ import { type PlaygroundBindingOptions } from '@wasm-idle/core';
2
+ import type { PlaygroundBinding, SandboxRuntimeAssets } from './sandbox';
3
+ import { RuntimeAssetCache } from './runtimeAssetCache';
4
+ import { CompiledArtifactCache } from './compiledArtifactCache';
5
+ export interface PlaygroundRuntimeSession {
6
+ createBinding(): PlaygroundBinding & {
7
+ dispose(): Promise<void>;
8
+ };
9
+ selectLanguage(language: string): Promise<void>;
10
+ dispose(): Promise<void>;
11
+ getCacheStats(): {
12
+ language: string | undefined;
13
+ assets: ReturnType<RuntimeAssetCache['stats']>;
14
+ artifacts: ReturnType<CompiledArtifactCache['stats']>;
15
+ };
16
+ }
17
+ /**
18
+ * A browser-owned language session. Panels own disposable execution leases, not the
19
+ * compiler cache. A problem/editor change may release all leases without cooling it.
20
+ */
21
+ export declare function createRuntimeSession(runtimeAssets: SandboxRuntimeAssets, options?: PlaygroundBindingOptions): PlaygroundRuntimeSession;
@@ -0,0 +1,119 @@
1
+ import { createPlaygroundBinding, normalizeLanguageId, RuntimeConfigurationError } from '@wasm-idle/core';
2
+ import playground from './index';
3
+ import { RuntimeAssetCache } from './runtimeAssetCache';
4
+ import { CompiledArtifactCache } from './compiledArtifactCache';
5
+ import { createCachedClangSandbox } from './cachedClang';
6
+ /**
7
+ * A browser-owned language session. Panels own disposable execution leases, not the
8
+ * compiler cache. A problem/editor change may release all leases without cooling it.
9
+ */
10
+ export function createRuntimeSession(runtimeAssets, options = {}) {
11
+ let language;
12
+ let epoch = 0;
13
+ let disposed = false;
14
+ let disposal;
15
+ let assetCache = new RuntimeAssetCache();
16
+ let artifactCache = new CompiledArtifactCache();
17
+ const leases = new Set();
18
+ let retiring = Promise.resolve();
19
+ const stale = () => new RuntimeConfigurationError('Runtime session was disposed or its language changed', {
20
+ phase: 'dispose'
21
+ });
22
+ const release = (owned) => {
23
+ const previous = retiring;
24
+ retiring = Promise.allSettled([previous, ...owned.map((lease) => lease.dispose())]).then((results) => {
25
+ const failure = results.find((result) => result.status === 'rejected');
26
+ if (failure?.status === 'rejected')
27
+ throw failure.reason;
28
+ });
29
+ // Errors remain visible to the caller without unhandled background rejections.
30
+ void retiring.catch(() => undefined);
31
+ return retiring;
32
+ };
33
+ const session = {
34
+ async selectLanguage(value) {
35
+ if (disposed)
36
+ throw stale();
37
+ const next = normalizeLanguageId(value) || value.trim().toUpperCase();
38
+ if (!next)
39
+ return;
40
+ if (next === language)
41
+ return await retiring;
42
+ language = next;
43
+ epoch++;
44
+ assetCache.dispose();
45
+ artifactCache.clear();
46
+ assetCache = new RuntimeAssetCache();
47
+ artifactCache = new CompiledArtifactCache();
48
+ await release([...leases].filter((lease) => lease.epoch !== undefined));
49
+ },
50
+ createBinding() {
51
+ if (disposed)
52
+ throw stale();
53
+ let closed = false;
54
+ let closing;
55
+ const lease = {
56
+ epoch: undefined,
57
+ dispose() {
58
+ if (closing)
59
+ return closing;
60
+ closed = true;
61
+ leases.delete(lease);
62
+ closing = Promise.resolve().then(() => core.dispose());
63
+ return closing;
64
+ }
65
+ };
66
+ const core = createPlaygroundBinding(runtimeAssets, async (target) => {
67
+ if (closed || disposed)
68
+ throw stale();
69
+ const selected = session.selectLanguage(target);
70
+ const selectedEpoch = epoch;
71
+ await selected;
72
+ if (closed || disposed || epoch !== selectedEpoch)
73
+ throw stale();
74
+ lease.epoch = selectedEpoch;
75
+ const cache = assetCache;
76
+ const artifacts = artifactCache;
77
+ const sandbox = (await playground(target));
78
+ if (closed || disposed || epoch !== selectedEpoch) {
79
+ if (sandbox.dispose)
80
+ await sandbox.dispose();
81
+ else
82
+ await sandbox.terminate();
83
+ throw stale();
84
+ }
85
+ sandbox.setRuntimeAssetCache?.(cache);
86
+ return target === 'C' || target === 'CPP'
87
+ ? createCachedClangSandbox(sandbox, target, cache, artifacts)
88
+ : sandbox;
89
+ }, options);
90
+ leases.add(lease);
91
+ const binding = core;
92
+ // Do not override core.dispose in place: the lease calls that exact owner method.
93
+ return new Proxy(binding, {
94
+ get(target, property) {
95
+ if (property === 'dispose')
96
+ return lease.dispose;
97
+ const value = Reflect.get(target, property);
98
+ return typeof value === 'function' ? value.bind(target) : value;
99
+ }
100
+ });
101
+ },
102
+ dispose() {
103
+ if (disposal)
104
+ return disposal;
105
+ disposed = true;
106
+ epoch++;
107
+ assetCache.dispose();
108
+ artifactCache.clear();
109
+ disposal = release([...leases]);
110
+ return disposal;
111
+ },
112
+ getCacheStats: () => ({
113
+ language,
114
+ assets: assetCache.stats(),
115
+ artifacts: artifactCache.stats()
116
+ })
117
+ };
118
+ return session;
119
+ }
@@ -19,6 +19,7 @@ declare class Rust implements Sandbox {
19
19
  debugManifestUrl: string;
20
20
  debugManifestReceipt?: Readonly<RuntimeAssetIntegrityEntry>;
21
21
  executableGraphFingerprint: string;
22
+ private persistentCacheBaseline?;
22
23
  oncompilerdiagnostic?: (diagnostic: CompilerDiagnostic) => void;
23
24
  waitingForInput: boolean;
24
25
  pendingEof: boolean;
@@ -8,7 +8,7 @@ import { resolveRustNonDebugResourceLimits } from './rustWorkerLimits';
8
8
  import { WASM_RUST_EXECUTABLE_GRAPH_PROFILE, WASM_RUST_RUNTIME_PROFILE } from './wasmRustVersion';
9
9
  import { WorkerSession } from './workerSession';
10
10
  import { reportWorkerInputReady, reportWorkerProgress } from './workerProgress';
11
- import { resolveExecutionLimits, TimeoutError } from '@wasm-idle/core';
11
+ import { resolveExecutionLimits, resolveRuntimeAssetCacheOptions, TimeoutError } from '@wasm-idle/core';
12
12
  const debugBreakpointBufferInts = 1028;
13
13
  const rustLldbSourcePath = '/workspace/main.rs';
14
14
  const outputEncoder = new TextEncoder();
@@ -54,6 +54,7 @@ class Rust {
54
54
  debugManifestUrl = '';
55
55
  debugManifestReceipt;
56
56
  executableGraphFingerprint = '';
57
+ persistentCacheBaseline;
57
58
  oncompilerdiagnostic;
58
59
  waitingForInput = false;
59
60
  pendingEof = false;
@@ -122,6 +123,7 @@ class Rust {
122
123
  let clearCandidateWait = () => { };
123
124
  try {
124
125
  const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
126
+ const persistentCache = resolveRuntimeAssetCacheOptions(typeof runtimeAssets === 'object' ? runtimeAssets.persistentCache : undefined, _options.persistentCache);
125
127
  const nextCompilerUrl = resolveRustCompilerUrl(runtimeAssets, currentUrl);
126
128
  const nextDebugModuleUrl = resolveRustDebugModuleUrl(runtimeAssets, currentUrl);
127
129
  const debugRuntime = resolveDebugRuntimeUrls(runtimeAssets, currentUrl);
@@ -175,6 +177,7 @@ class Rust {
175
177
  this.executableGraphFingerprint !==
176
178
  WASM_RUST_EXECUTABLE_GRAPH_PROFILE.fingerprint;
177
179
  if (!needsWorkerReset) {
180
+ this.persistentCacheBaseline = persistentCache;
178
181
  this.debugRuntimeBaseUrl = nextDebugRuntimeBaseUrl;
179
182
  this.debugManifestUrl = nextDebugManifestUrl;
180
183
  this.debugManifestReceipt = nextDebugManifestReceipt;
@@ -182,6 +185,7 @@ class Rust {
182
185
  return;
183
186
  }
184
187
  nextExecutableGraph = await loadVerifiedRustExecutableGraph({
188
+ persistentCache,
185
189
  moduleUrl: nextCompilerUrl,
186
190
  currentUrl,
187
191
  profile: WASM_RUST_EXECUTABLE_GRAPH_PROFILE,
@@ -255,6 +259,7 @@ class Rust {
255
259
  };
256
260
  candidateWorker.postMessage({
257
261
  load: true,
262
+ persistentCache,
258
263
  compilerUrl: nextExecutableGraph.entryUrl,
259
264
  debugModuleUrl: nextDebugModuleUrl,
260
265
  path: nextAssetPath,
@@ -274,6 +279,7 @@ class Rust {
274
279
  this.waitingForInput = false;
275
280
  this.pendingEof = false;
276
281
  this.compilerUrl = nextCompilerUrl;
282
+ this.persistentCacheBaseline = persistentCache;
277
283
  this.debugModuleUrl = nextDebugModuleUrl;
278
284
  this.assetPath = nextAssetPath;
279
285
  this.debugRuntimeBaseUrl = nextDebugRuntimeBaseUrl;
@@ -360,7 +366,9 @@ class Rust {
360
366
  }
361
367
  let limits;
362
368
  let nonDebugResourceLimits;
369
+ let persistentCache;
363
370
  try {
371
+ persistentCache = resolveRuntimeAssetCacheOptions(this.persistentCacheBaseline, options.persistentCache);
364
372
  limits = resolveExecutionLimits(options.limits);
365
373
  nonDebugResourceLimits =
366
374
  debugMode === 'none' ? resolveRustNonDebugResourceLimits(limits) : undefined;
@@ -567,6 +575,7 @@ class Rust {
567
575
  startPhaseTimeout('run', limits.runTimeoutMs);
568
576
  const compilerWorker = this.worker;
569
577
  const lldbSession = new LldbSandboxSession({
578
+ persistentCache,
570
579
  manifestUrl: this.debugManifestUrl,
571
580
  manifestReceipt: this.debugManifestReceipt,
572
581
  runtimeBaseUrl: this.debugRuntimeBaseUrl,
@@ -671,6 +680,7 @@ class Rust {
671
680
  this.begin = Date.now();
672
681
  try {
673
682
  this.worker.postMessage({
683
+ persistentCache,
674
684
  code,
675
685
  prepare,
676
686
  buffer: this.buffer,
@@ -70,6 +70,7 @@ export interface RustExecutableGraphProgress {
70
70
  readonly totalBytes: number;
71
71
  }
72
72
  export interface LoadRustExecutableGraphOptions {
73
+ readonly persistentCache?: import('@wasm-idle/core').RuntimeAssetCacheOptions;
73
74
  readonly moduleUrl: string;
74
75
  readonly currentUrl?: string;
75
76
  readonly profile: unknown;
@@ -822,6 +822,7 @@ export async function loadVerifiedRustExecutableGraph(options) {
822
822
  if (options.assetTimeoutMs !== undefined)
823
823
  limits.assetTimeoutMs = options.assetTimeoutMs;
824
824
  const preflight = await preflightRuntimeAssets({
825
+ persistentCache: options.persistentCache,
825
826
  manifest: createPreflightManifest(profile, sourceContext.keysByPath),
826
827
  runtimeId: 'rust/executable-graph',
827
828
  rootUrl: sourceContext.storageRootUrl,
@@ -1,4 +1,4 @@
1
- import { type ExecutionLimits } from '@wasm-idle/core';
1
+ import { type ExecutionLimits, type RuntimeAssetCacheOptions } from '@wasm-idle/core';
2
2
  import { type StaticRuntimePreflightProgress, type StaticRuntimePreflightRuntimeId } from './staticRuntimePreflightProtocol';
3
3
  export interface StaticRuntimeWorkerPreflightRequest {
4
4
  readonly runtimeId: StaticRuntimePreflightRuntimeId;
@@ -7,6 +7,7 @@ export interface StaticRuntimeWorkerPreflightRequest {
7
7
  readonly manifestUrl: string;
8
8
  readonly profile: unknown;
9
9
  readonly limits?: Partial<ExecutionLimits>;
10
+ readonly persistentCache?: RuntimeAssetCacheOptions;
10
11
  readonly signal?: AbortSignal;
11
12
  readonly reportProgress?: (progress: StaticRuntimePreflightProgress) => void;
12
13
  }
@@ -1,8 +1,9 @@
1
- import { CancelledError, ProtocolError, TimeoutError, WorkerStartupError, resolveExecutionLimits } from '@wasm-idle/core';
1
+ import { CancelledError, ProtocolError, TimeoutError, WorkerStartupError, resolveExecutionLimits, resolveRuntimeAssetCacheOptions } from '@wasm-idle/core';
2
2
  import { STATIC_RUNTIME_PREFLIGHT_PROTOCOL_VERSION, collectStaticRuntimePreflightTransferables, deserializeStaticRuntimePreflightError, isStaticRuntimePreflightProgress, isStaticRuntimePreflightSerializedError } from './staticRuntimePreflightProtocol';
3
3
  const REQUEST_ID = 1;
4
4
  export async function preflightStaticRuntimeAssetsInWorker(request) {
5
5
  const limits = resolveExecutionLimits(request.limits);
6
+ const persistentCache = resolveRuntimeAssetCacheOptions(request.persistentCache);
6
7
  if (request.signal?.aborted) {
7
8
  throw new CancelledError(`${request.displayName} runtime preflight cancelled`, {
8
9
  cause: request.signal.reason,
@@ -210,7 +211,8 @@ export async function preflightStaticRuntimeAssetsInWorker(request) {
210
211
  baseUrl: request.baseUrl,
211
212
  manifestUrl: request.manifestUrl,
212
213
  profile: request.profile,
213
- limits
214
+ limits,
215
+ persistentCache
214
216
  });
215
217
  }
216
218
  catch (error) {
@@ -8,6 +8,7 @@ export async function executeStaticRuntimePreflight(request, reportProgress, sig
8
8
  baseUrl: request.baseUrl,
9
9
  manifestUrl: request.manifestUrl,
10
10
  limits: request.limits,
11
+ persistentCache: request.persistentCache,
11
12
  signal,
12
13
  reportProgress: (progress) => reportProgress({ kind: 'asset', progress })
13
14
  };