wasm-idle 1.0.9 → 1.0.10

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 (35) hide show
  1. package/README.md +20 -23
  2. package/dist/playground/assetBridge.d.ts +1 -1
  3. package/dist/playground/assetBridge.js +172 -117
  4. package/dist/playground/assets.d.ts +4 -4
  5. package/dist/playground/assets.js +7 -4
  6. package/dist/playground/cachedClang.js +1 -0
  7. package/dist/playground/clang.js +5 -1
  8. package/dist/playground/clangWorkerProtocol.d.ts +1 -0
  9. package/dist/playground/runtimeAssetCache.d.ts +6 -0
  10. package/dist/playground/runtimeAssetCache.js +109 -0
  11. package/dist/playground/staticRuntimeModuleVersion.d.ts +1 -1
  12. package/dist/playground/staticRuntimeModuleVersion.js +1 -1
  13. package/dist/playground/wasmBashVersion.d.ts +25 -25
  14. package/dist/playground/wasmBashVersion.js +12 -12
  15. package/dist/playground/wasmElixirVersion.d.ts +9 -9
  16. package/dist/playground/wasmElixirVersion.js +9 -9
  17. package/dist/playground/wasmHaskellVersion.d.ts +1 -1
  18. package/dist/playground/wasmHaskellVersion.js +1 -1
  19. package/dist/playground/wasmLispVersion.d.ts +1 -1
  20. package/dist/playground/wasmLispVersion.js +1 -1
  21. package/dist/playground/wasmOcamlVersion.d.ts +4 -4
  22. package/dist/playground/wasmOcamlVersion.js +3 -3
  23. package/dist/playground/wasmPrologVersion.d.ts +19 -19
  24. package/dist/playground/wasmPrologVersion.js +18 -18
  25. package/dist/playground/wasmTypeScriptVersion.d.ts +5 -5
  26. package/dist/playground/wasmTypeScriptVersion.js +5 -5
  27. package/dist/playground/worker/cachedClangCompile.js +7 -1
  28. package/dist/playground/worker/clang.js +13 -8
  29. package/dist/playground/worker/clangStreaming.js +6 -0
  30. package/dist/playground/worker/python.js +7 -3
  31. package/dist/playground/worker/runtimeModule.d.ts +1 -1
  32. package/dist/playground/worker/runtimeModule.js +1 -1
  33. package/dist/testPublicEnv.d.ts +2 -0
  34. package/dist/testPublicEnv.js +8 -0
  35. package/package.json +47 -47
@@ -225,9 +225,13 @@ class RuntimeLoadProgress {
225
225
  progress;
226
226
  lockedTotal;
227
227
  measurementInvalid = false;
228
- constructor(runtime, languageSysroots = false) {
228
+ constructor(runtime, languageSysroots = false, clangdHeaders = false) {
229
229
  this.expectedAssets = expectedAssetsForRuntime(runtime);
230
230
  this.optionalAssets = new Set();
231
+ if (runtime === 'clangd' && !clangdHeaders) {
232
+ this.expectedAssets.delete('clangd.headers.json.gz');
233
+ this.optionalAssets.add('clangd.headers.json.gz');
234
+ }
231
235
  if (runtime === 'clang') {
232
236
  this.expectedAssets.delete(printscanLongDoubleAsset);
233
237
  this.optionalAssets.add(printscanLongDoubleAsset);
@@ -387,7 +391,6 @@ export class WorkerAssetBridge {
387
391
  runtime;
388
392
  config;
389
393
  persistentCacheBaseline;
390
- persistentCacheBySignal = new WeakMap();
391
394
  progress;
392
395
  expectedAssets;
393
396
  languageSysroots;
@@ -404,7 +407,7 @@ export class WorkerAssetBridge {
404
407
  this.config = { ...config, persistentCache: this.persistentCacheBaseline };
405
408
  this.maxAssetBytes = requireBridgeMaxAssetBytes(maxAssetBytes);
406
409
  this.languageSysroots = canUseClangLanguageSysroots(runtime, config, languageSysroots);
407
- this.progress = new RuntimeLoadProgress(runtime, this.languageSysroots);
410
+ this.progress = new RuntimeLoadProgress(runtime, this.languageSysroots, config.assetPrefix === 'clangd' || !!config.integrity?.['clangd.headers.json.gz']);
408
411
  this.expectedAssets = expectedAssetsForRuntime(runtime, this.languageSysroots);
409
412
  this.progress.reset(progress);
410
413
  }
@@ -455,7 +458,7 @@ export class WorkerAssetBridge {
455
458
  this.maxAssetBytes = nextMaxAssetBytes;
456
459
  this.languageSysroots = nextLanguageSysroots;
457
460
  this.expectedAssets = expectedAssetsForRuntime(this.runtime, nextLanguageSysroots);
458
- this.progress = new RuntimeLoadProgress(this.runtime, nextLanguageSysroots);
461
+ this.progress = new RuntimeLoadProgress(this.runtime, nextLanguageSysroots, config.assetPrefix === 'clangd' || !!config.integrity?.['clangd.headers.json.gz']);
459
462
  this.progress.reset(progress);
460
463
  if (this.state !== 'rebinding' || this.generation !== generation) {
461
464
  throw new Error('Cannot rebind a disposed worker asset bridge');
@@ -482,7 +485,7 @@ export class WorkerAssetBridge {
482
485
  return;
483
486
  this.progress.reset(progress);
484
487
  }
485
- configuredReceiptByteLimit(asset, value) {
488
+ configuredReceiptByteLimit(context, asset, value) {
486
489
  if (value === undefined)
487
490
  return undefined;
488
491
  if (!Number.isSafeInteger(value) || value < 0) {
@@ -492,37 +495,37 @@ export class WorkerAssetBridge {
492
495
  });
493
496
  }
494
497
  const byteLimit = value;
495
- if (byteLimit > this.maxAssetBytes) {
496
- throw runtimeAssetSizeError(asset, this.maxAssetBytes);
498
+ if (byteLimit > context.maxAssetBytes) {
499
+ throw runtimeAssetSizeError(asset, context.maxAssetBytes);
497
500
  }
498
501
  return byteLimit;
499
502
  }
500
- runtimeAssetByteLimit(asset) {
501
- const configured = this.config.integrity?.[asset];
503
+ runtimeAssetByteLimit(context, asset) {
504
+ const configured = context.config.integrity?.[asset];
502
505
  if (!configured || typeof configured === 'string')
503
- return this.maxAssetBytes;
504
- return (this.configuredReceiptByteLimit(asset, configured.uncompressedBytes ?? configured.bytes) ?? this.maxAssetBytes);
506
+ return context.maxAssetBytes;
507
+ return (this.configuredReceiptByteLimit(context, asset, configured.uncompressedBytes ?? configured.bytes) ?? context.maxAssetBytes);
505
508
  }
506
- sourceAssetByteLimit(asset) {
507
- const configured = this.config.integrity?.[asset];
509
+ sourceAssetByteLimit(context, asset) {
510
+ const configured = context.config.integrity?.[asset];
508
511
  if (!configured || typeof configured === 'string')
509
- return this.maxAssetBytes;
512
+ return context.maxAssetBytes;
510
513
  const paired = configured.uncompressedBytes !== undefined ||
511
514
  configured.uncompressedSha256 !== undefined;
512
515
  if (asset.endsWith('.gz')) {
513
516
  if (!paired)
514
- return this.maxAssetBytes;
515
- const deliveryLimit = this.configuredReceiptByteLimit(asset, configured.bytes);
516
- const runtimeLimit = this.configuredReceiptByteLimit(asset, configured.uncompressedBytes);
517
+ return context.maxAssetBytes;
518
+ const deliveryLimit = this.configuredReceiptByteLimit(context, asset, configured.bytes);
519
+ const runtimeLimit = this.configuredReceiptByteLimit(context, asset, configured.uncompressedBytes);
517
520
  return deliveryLimit !== undefined || runtimeLimit !== undefined
518
521
  ? Math.max(deliveryLimit ?? 0, runtimeLimit ?? 0)
519
- : this.maxAssetBytes;
522
+ : context.maxAssetBytes;
520
523
  }
521
- const deliveryLimit = this.configuredReceiptByteLimit(asset, configured.bytes);
522
- const runtimeLimit = this.configuredReceiptByteLimit(asset, configured.uncompressedBytes);
524
+ const deliveryLimit = this.configuredReceiptByteLimit(context, asset, configured.bytes);
525
+ const runtimeLimit = this.configuredReceiptByteLimit(context, asset, configured.uncompressedBytes);
523
526
  return deliveryLimit !== undefined || runtimeLimit !== undefined
524
527
  ? Math.max(deliveryLimit ?? 0, runtimeLimit ?? 0)
525
- : this.maxAssetBytes;
528
+ : context.maxAssetBytes;
526
529
  }
527
530
  handleMessage(event) {
528
531
  const assetRequest = event.data?.assetRequest;
@@ -546,69 +549,112 @@ export class WorkerAssetBridge {
546
549
  const worker = this.worker;
547
550
  const generation = this.generation;
548
551
  const controller = new AbortController();
549
- this.persistentCacheBySignal.set(controller.signal, resolveRuntimeAssetCacheOptions(this.config.persistentCache));
552
+ const progress = this.progress;
553
+ const context = this.captureLoadContext();
554
+ const receiveProgress = (event) => {
555
+ if (controller.signal.aborted || generation !== this.generation)
556
+ return;
557
+ if (event.kind === 'download') {
558
+ progress.update(event.asset, event.loaded, event.total);
559
+ }
560
+ else {
561
+ progress.activity(event.phase, event.asset);
562
+ }
563
+ };
564
+ const loadContext = (report) => ({
565
+ ...context,
566
+ progress: {
567
+ update: (asset, loaded, total) => report({ kind: 'download', asset, loaded, total }, `download:${asset}`),
568
+ activity: (phase, asset) => report({ kind: 'activity', asset, phase }, `activity:${asset}`)
569
+ }
570
+ });
550
571
  this.activeLoads.add(controller);
551
572
  try {
552
- this.validateAssetRequest(request.asset);
553
- const key = JSON.stringify([
573
+ this.validateAssetRequest(loadContext(receiveProgress), request.asset);
574
+ const identity = [
554
575
  this.runtime,
555
- this.config.baseUrl,
556
- this.cache?.identity(this.config.loader),
557
- integrityKey(this.config),
558
- allowedBaseUrlsKey(this.config),
559
- this.config.useAssetBridge,
560
- this.maxAssetBytes,
576
+ context.config.baseUrl,
577
+ this.cache?.identity(context.config.loader),
578
+ integrityKey(context.config),
579
+ allowedBaseUrlsKey(context.config),
580
+ context.config.useAssetBridge,
581
+ context.maxAssetBytes,
561
582
  this.languageSysroots,
562
- request.asset,
563
- request.module === true ? 'module' : 'bytes'
564
- ]);
583
+ request.asset
584
+ ];
585
+ const bytesKey = JSON.stringify([...identity, 'bytes']);
586
+ const moduleKey = JSON.stringify([...identity, 'module']);
587
+ const share = (key, signal, start, report = receiveProgress) => {
588
+ // Completed verified values can cross storage policies. In-flight work cannot:
589
+ // a request disabling persistence must not join a load that reads or writes it.
590
+ const operationKey = JSON.stringify([key, context.config.persistentCache]);
591
+ return this.cache
592
+ ? this.cache.share(operationKey, signal, start, report)
593
+ : start(signal, report);
594
+ };
595
+ const loadBytes = async (signal, report = receiveProgress, retain = false) => {
596
+ let loaded = this.cache?.get(bytesKey);
597
+ if (loaded)
598
+ return loaded;
599
+ loaded = await share(bytesKey, signal, async (operationSignal, publish) => {
600
+ const loaded = await this.loadVerifiedAsset(loadContext(publish), request.asset, operationSignal);
601
+ // Keep shared backing buffers private, even when the result exceeds retention limits.
602
+ return this.cache
603
+ ? { bytes: Uint8Array.from(loaded.bytes), mimeType: loaded.mimeType }
604
+ : loaded;
605
+ }, report);
606
+ if (signal.aborted)
607
+ throw runtimeAssetAbortReason(signal);
608
+ if (retain)
609
+ this.cache?.set(bytesKey, loaded, loaded.bytes.byteLength);
610
+ return loaded;
611
+ };
565
612
  if (request.module === true) {
566
613
  if (!/\.wasm(?:\.gz)?$/u.test(request.asset)) {
567
614
  throw new Error('Only runtime Wasm assets can be compiled');
568
615
  }
569
- let module = this.cache?.get(key);
616
+ let module = this.cache?.get(moduleKey);
570
617
  if (!module) {
571
- const streaming = this.runtime === 'clang' &&
572
- shouldStreamBundledClang(this.config) &&
573
- (request.asset === 'bin/clang.wasm.gz' ||
574
- request.asset === 'bin/lld.wasm.gz');
575
- let bytes;
576
- if (streaming) {
577
- const receipt = BUNDLED_CLANG_ASSET_INTEGRITY[request.asset];
578
- module = await compileVerifiedWasmAsset(new URL(request.asset, this.config.baseUrl).href, receipt, {
579
- fetch: globalThis.fetch.bind(globalThis),
580
- persistentCache: this.persistentCacheBySignal.get(controller.signal),
581
- maxAssetBytes: this.maxAssetBytes,
582
- signal: controller.signal,
583
- onProgress: (loaded, total) => this.progress.update(request.asset, loaded, total)
584
- });
585
- bytes = receipt.uncompressedBytes;
586
- }
587
- else {
588
- const loaded = await this.loadVerifiedAsset(request.asset, controller.signal);
589
- bytes = loaded.bytes.byteLength;
590
- module = await WebAssembly.compile(Uint8Array.from(loaded.bytes));
591
- }
592
- if (controller.signal.aborted || generation !== this.generation)
593
- return;
594
- this.cache?.set(key, module, bytes);
618
+ module = await share(moduleKey, controller.signal, async (signal, report) => {
619
+ const streaming = this.runtime === 'clang' &&
620
+ shouldStreamBundledClang(context.config) &&
621
+ (request.asset === 'bin/clang.wasm.gz' ||
622
+ request.asset === 'bin/lld.wasm.gz');
623
+ let bytes;
624
+ let module;
625
+ if (streaming) {
626
+ const receipt = BUNDLED_CLANG_ASSET_INTEGRITY[request.asset];
627
+ module = await compileVerifiedWasmAsset(new URL(request.asset, context.config.baseUrl).href, receipt, {
628
+ fetch: globalThis.fetch.bind(globalThis),
629
+ persistentCache: context.config.persistentCache,
630
+ maxAssetBytes: context.maxAssetBytes,
631
+ signal,
632
+ onProgress: (loaded, total) => report({
633
+ kind: 'download',
634
+ asset: request.asset,
635
+ loaded,
636
+ total
637
+ }, `download:${request.asset}`)
638
+ });
639
+ bytes = receipt.uncompressedBytes;
640
+ }
641
+ else {
642
+ const loaded = await loadBytes(signal, (event) => report(event, `${event.kind}:${event.asset}`));
643
+ bytes = loaded.bytes.byteLength;
644
+ module = await WebAssembly.compile(Uint8Array.from(loaded.bytes));
645
+ }
646
+ if (signal.aborted)
647
+ throw runtimeAssetAbortReason(signal);
648
+ this.cache?.set(moduleKey, module, bytes);
649
+ return module;
650
+ });
595
651
  }
596
652
  if (controller.signal.aborted || generation !== this.generation)
597
653
  return;
598
654
  worker.postMessage({ assetResponse: { id: request.id, ok: true, module } });
599
655
  return;
600
656
  }
601
- let loaded = this.cache?.get(key);
602
- if (!loaded) {
603
- loaded = await this.loadVerifiedAsset(request.asset, controller.signal);
604
- if (controller.signal.aborted || generation !== this.generation)
605
- return;
606
- // Keep private copies: never transfer or lend the owner cache's backing buffer.
607
- if (this.cache) {
608
- loaded = { bytes: Uint8Array.from(loaded.bytes), mimeType: loaded.mimeType };
609
- this.cache.set(key, loaded, loaded.bytes.byteLength);
610
- }
611
- }
657
+ const loaded = await loadBytes(controller.signal, receiveProgress, true);
612
658
  if (this.runtime === 'python' && request.asset === 'pyodide-lock.json') {
613
659
  this.pythonPackageAssets = readPythonPackageAssets(loaded.bytes);
614
660
  }
@@ -644,15 +690,24 @@ export class WorkerAssetBridge {
644
690
  this.activeLoads.delete(controller);
645
691
  }
646
692
  }
647
- async loadVerifiedAsset(asset, signal) {
648
- const loaded = await this.loadAsset(asset, signal);
693
+ captureLoadContext() {
694
+ return {
695
+ config: { ...this.config },
696
+ maxAssetBytes: this.maxAssetBytes,
697
+ expectedAssets: new Set(this.expectedAssets),
698
+ pythonPackageAssets: new Set(this.pythonPackageAssets),
699
+ progress: this.progress
700
+ };
701
+ }
702
+ async loadVerifiedAsset(context, asset, signal) {
703
+ const loaded = await this.loadAsset(asset, signal, context);
649
704
  const deliveryBytes = canonicalUint8Array(loaded.bytes);
650
- const sourceAssetByteLimit = this.sourceAssetByteLimit(asset);
651
- const runtimeAssetByteLimit = this.runtimeAssetByteLimit(asset);
705
+ const sourceAssetByteLimit = this.sourceAssetByteLimit(context, asset);
706
+ const runtimeAssetByteLimit = this.runtimeAssetByteLimit(context, asset);
652
707
  requireRuntimeAssetSize(asset, deliveryBytes.byteLength, sourceAssetByteLimit);
653
708
  let normalizedRuntimeBytes;
654
709
  if (asset.endsWith('.gz')) {
655
- this.progress.activity('decompressing', asset);
710
+ context.progress.activity('decompressing', asset);
656
711
  normalizedRuntimeBytes = await decompressGzip(deliveryBytes, asset, runtimeAssetByteLimit, signal);
657
712
  }
658
713
  else {
@@ -673,10 +728,10 @@ export class WorkerAssetBridge {
673
728
  signal.addEventListener('abort', cancelOnAbort, { once: true });
674
729
  });
675
730
  try {
676
- if (this.config.integrity?.[asset]) {
677
- this.progress.activity('verifying', asset);
731
+ if (context.config.integrity?.[asset]) {
732
+ context.progress.activity('verifying', asset);
678
733
  }
679
- const verification = this.verifyIntegrity(asset, deliveryBytes, runtimeBytes, loaded.mimeType, !httpDecodedGzip);
734
+ const verification = this.verifyIntegrity(context, asset, deliveryBytes, runtimeBytes, loaded.mimeType, !httpDecodedGzip);
680
735
  await Promise.race([verification, aborted]);
681
736
  }
682
737
  finally {
@@ -694,27 +749,27 @@ export class WorkerAssetBridge {
694
749
  transferOwnership: normalizedRuntimeBytes === deliveryBytes ? loaded.transferOwnership : true
695
750
  };
696
751
  }
697
- validateAssetRequest(asset) {
698
- if (!this.expectedAssets.has(asset) && !this.pythonPackageAssets.has(asset)) {
752
+ validateAssetRequest(context, asset) {
753
+ if (!context.expectedAssets.has(asset) && !context.pythonPackageAssets.has(asset)) {
699
754
  throw new Error(`Unexpected ${this.runtime} runtime asset: ${asset}`);
700
755
  }
701
- if (this.config.integrity && !Object.hasOwn(this.config.integrity, asset)) {
756
+ if (context.config.integrity && !Object.hasOwn(context.config.integrity, asset)) {
702
757
  throw new Error(`Runtime asset ${asset} is missing integrity metadata`);
703
758
  }
704
759
  }
705
- async loadAsset(asset, signal) {
706
- this.validateAssetRequest(asset);
760
+ async loadAsset(asset, signal, context = this.captureLoadContext()) {
761
+ this.validateAssetRequest(context, asset);
707
762
  if (signal.aborted) {
708
763
  throw runtimeAssetAbortReason(signal);
709
764
  }
710
- const sourceAssetByteLimit = this.sourceAssetByteLimit(asset);
711
- this.runtimeAssetByteLimit(asset);
765
+ const sourceAssetByteLimit = this.sourceAssetByteLimit(context, asset);
766
+ this.runtimeAssetByteLimit(context, asset);
712
767
  const reportProgress = (loaded, total) => {
713
768
  if (!signal.aborted)
714
- this.progress.update(asset, loaded, total);
769
+ context.progress.update(asset, loaded, total);
715
770
  };
716
- if (this.config.loader) {
717
- const pendingResult = Promise.resolve(this.config.loader({
771
+ if (context.config.loader) {
772
+ const pendingResult = Promise.resolve(context.config.loader({
718
773
  runtime: this.runtime,
719
774
  asset,
720
775
  reportProgress,
@@ -749,17 +804,17 @@ export class WorkerAssetBridge {
749
804
  if (signal.aborted) {
750
805
  throw runtimeAssetAbortReason(signal);
751
806
  }
752
- const loaded = await this.normalizeLoaderResult(result, asset, signal, sourceAssetByteLimit);
807
+ const loaded = await this.normalizeLoaderResult(context, result, asset, signal, sourceAssetByteLimit);
753
808
  if (signal.aborted) {
754
809
  throw runtimeAssetAbortReason(signal);
755
810
  }
756
811
  if (loaded)
757
812
  return loaded;
758
813
  }
759
- return await this.fetchAsset(asset, asset, signal, sourceAssetByteLimit);
814
+ return await this.fetchAsset(context, asset, asset, signal, sourceAssetByteLimit);
760
815
  }
761
- async verifyIntegrity(asset, deliveryBytes, runtimeBytes, mimeType, hasDeliveryBytes = true) {
762
- const configured = this.config.integrity?.[asset];
816
+ async verifyIntegrity(context, asset, deliveryBytes, runtimeBytes, mimeType, hasDeliveryBytes = true) {
817
+ const configured = context.config.integrity?.[asset];
763
818
  if (!configured)
764
819
  return;
765
820
  const expected = typeof configured === 'string' ? { sha256: configured } : configured;
@@ -796,15 +851,15 @@ export class WorkerAssetBridge {
796
851
  mimeType
797
852
  });
798
853
  }
799
- async normalizeLoaderResult(result, asset, signal, maxAssetBytes) {
854
+ async normalizeLoaderResult(context, result, asset, signal, maxAssetBytes) {
800
855
  if (!result)
801
856
  return null;
802
857
  if (typeof result === 'string' || result instanceof URL) {
803
- return await this.fetchAsset(String(result), asset, signal, maxAssetBytes);
858
+ return await this.fetchAsset(context, String(result), asset, signal, maxAssetBytes);
804
859
  }
805
860
  const directBytes = snapshotLoaderBytes(result, asset, maxAssetBytes);
806
861
  if (directBytes) {
807
- this.progress.update(asset, directBytes.byteLength, directBytes.byteLength);
862
+ context.progress.update(asset, directBytes.byteLength, directBytes.byteLength);
808
863
  return { bytes: directBytes, transferOwnership: true };
809
864
  }
810
865
  const directBlob = tryCanonicalBlob(result);
@@ -827,23 +882,23 @@ export class WorkerAssetBridge {
827
882
  requireRuntimeAssetSize(asset, size, maxAssetBytes);
828
883
  const source = await readAbortableArrayBuffer(blob, signal);
829
884
  const bytes = snapshotMaterializedArrayBuffer(source, asset, maxAssetBytes);
830
- this.progress.update(asset, bytes.byteLength, bytes.byteLength);
885
+ context.progress.update(asset, bytes.byteLength, bytes.byteLength);
831
886
  return { bytes, mimeType, transferOwnership: true };
832
887
  }
833
888
  if ('url' in result && result.url) {
834
- return await this.fetchAsset(String(result.url), asset, signal, maxAssetBytes);
889
+ return await this.fetchAsset(context, String(result.url), asset, signal, maxAssetBytes);
835
890
  }
836
891
  if ('data' in result) {
837
892
  if (typeof result.data === 'string') {
838
893
  requireRuntimeAssetSize(asset, boundedUtf8ByteLength(result.data, maxAssetBytes), maxAssetBytes);
839
894
  const bytes = canonicalUint8Array(encoder.encode(result.data));
840
895
  requireRuntimeAssetSize(asset, bytes.byteLength, maxAssetBytes);
841
- this.progress.update(asset, bytes.byteLength, bytes.byteLength);
896
+ context.progress.update(asset, bytes.byteLength, bytes.byteLength);
842
897
  return { bytes, mimeType: result.mimeType, transferOwnership: true };
843
898
  }
844
899
  const bytes = snapshotLoaderBytes(result.data, asset, maxAssetBytes);
845
900
  if (bytes) {
846
- this.progress.update(asset, bytes.byteLength, bytes.byteLength);
901
+ context.progress.update(asset, bytes.byteLength, bytes.byteLength);
847
902
  return {
848
903
  bytes,
849
904
  mimeType: result.mimeType,
@@ -853,15 +908,15 @@ export class WorkerAssetBridge {
853
908
  }
854
909
  return null;
855
910
  }
856
- async fetchAsset(url, asset, signal, maxAssetBytes = this.sourceAssetByteLimit(asset)) {
857
- const requestUrl = this.requireAllowedAssetUrl(asset, url);
858
- const persistentCache = this.persistentCacheBySignal.get(signal) ?? this.config.persistentCache;
859
- const configured = this.config.integrity?.[asset];
911
+ async fetchAsset(context, url, asset, signal, maxAssetBytes = this.sourceAssetByteLimit(context, asset)) {
912
+ const requestUrl = this.requireAllowedAssetUrl(context, asset, url);
913
+ const persistentCache = context.config.persistentCache;
914
+ const configured = context.config.integrity?.[asset];
860
915
  const expected = typeof configured === 'string' ? { sha256: configured } : configured;
861
- const lock = this.config.assetPrefix
916
+ const lock = context.config.assetPrefix
862
917
  ? resolveRuntimeAssetLockEntry(requestUrl, {
863
- assetRoot: this.config.baseUrl,
864
- assetPrefix: this.config.assetPrefix
918
+ assetRoot: context.config.baseUrl,
919
+ assetPrefix: context.config.assetPrefix
865
920
  })
866
921
  : undefined;
867
922
  // Existing .gz receipts without a pair describe logical bytes, not stored gzip bytes.
@@ -873,8 +928,8 @@ export class WorkerAssetBridge {
873
928
  bytes: receipt.bytes,
874
929
  validationKey: JSON.stringify([
875
930
  'bridge-v1',
876
- this.config.baseUrl,
877
- allowedBaseUrlsKey(this.config),
931
+ context.config.baseUrl,
932
+ allowedBaseUrlsKey(context.config),
878
933
  maxAssetBytes
879
934
  ])
880
935
  }
@@ -887,11 +942,11 @@ export class WorkerAssetBridge {
887
942
  });
888
943
  if (bytes) {
889
944
  requireRuntimeAssetSize(asset, bytes.byteLength, maxAssetBytes);
890
- this.progress.update(asset, bytes.byteLength, bytes.byteLength);
945
+ context.progress.update(asset, bytes.byteLength, bytes.byteLength);
891
946
  return { bytes, mimeType: receipt?.mediaType, transferOwnership: true };
892
947
  }
893
948
  }
894
- const loaded = await this.fetchAssetFromNetwork(url, asset, signal, maxAssetBytes);
949
+ const loaded = await this.fetchAssetFromNetwork(context, url, asset, signal, maxAssetBytes);
895
950
  if (identity && !loaded.contentEncoding) {
896
951
  // Only checksum-matching bytes are published. Existing runtime validation still runs.
897
952
  await writePersistentRuntimeAsset({
@@ -903,8 +958,8 @@ export class WorkerAssetBridge {
903
958
  }
904
959
  return loaded;
905
960
  }
906
- async fetchAssetFromNetwork(url, asset, signal, maxAssetBytes = this.sourceAssetByteLimit(asset)) {
907
- const requestUrl = this.requireAllowedAssetUrl(asset, url);
961
+ async fetchAssetFromNetwork(context, url, asset, signal, maxAssetBytes = this.sourceAssetByteLimit(context, asset)) {
962
+ const requestUrl = this.requireAllowedAssetUrl(context, asset, url);
908
963
  if (signal.aborted)
909
964
  throw runtimeAssetAbortReason(signal);
910
965
  const pendingResponse = Promise.resolve(fetch(requestUrl.href, {
@@ -994,7 +1049,7 @@ export class WorkerAssetBridge {
994
1049
  const contentEncoding = response.headers.get('content-encoding') || undefined;
995
1050
  if (!response.body) {
996
1051
  const bytes = snapshotMaterializedArrayBuffer(await readAbortableArrayBuffer(response, signal), asset, maxAssetBytes);
997
- this.progress.update(asset, bytes.byteLength, contentLength ?? bytes.byteLength);
1052
+ context.progress.update(asset, bytes.byteLength, contentLength ?? bytes.byteLength);
998
1053
  return { bytes, contentEncoding, mimeType, transferOwnership: true };
999
1054
  }
1000
1055
  const reader = response.body.getReader();
@@ -1062,11 +1117,11 @@ export class WorkerAssetBridge {
1062
1117
  }
1063
1118
  bytes.set(chunk, receivedLength);
1064
1119
  receivedLength = nextLength;
1065
- this.progress.update(asset, receivedLength, contentLength);
1120
+ context.progress.update(asset, receivedLength, contentLength);
1066
1121
  }
1067
1122
  if (receivedLength !== bytes.byteLength)
1068
1123
  bytes = bytes.slice(0, receivedLength);
1069
- this.progress.update(asset, receivedLength, contentLength ?? receivedLength);
1124
+ context.progress.update(asset, receivedLength, contentLength ?? receivedLength);
1070
1125
  loadedAsset = { bytes, contentEncoding, mimeType, transferOwnership: true };
1071
1126
  }
1072
1127
  catch (error) {
@@ -1105,10 +1160,10 @@ export class WorkerAssetBridge {
1105
1160
  throw releaseError;
1106
1161
  return loadedAsset;
1107
1162
  }
1108
- requireAllowedAssetUrl(asset, value) {
1163
+ requireAllowedAssetUrl(context, asset, value) {
1109
1164
  let url;
1110
1165
  try {
1111
- url = new URL(value, this.config.baseUrl);
1166
+ url = new URL(value, context.config.baseUrl);
1112
1167
  }
1113
1168
  catch {
1114
1169
  throw new Error(`Runtime asset ${asset} has an invalid URL`);
@@ -1122,7 +1177,7 @@ export class WorkerAssetBridge {
1122
1177
  if (url.hash) {
1123
1178
  throw new Error(`Runtime asset ${asset} URL must not include a fragment`);
1124
1179
  }
1125
- const allowed = [this.config.baseUrl, ...(this.config.allowedBaseUrls || [])].some((baseUrl) => {
1180
+ const allowed = [context.config.baseUrl, ...(context.config.allowedBaseUrls || [])].some((baseUrl) => {
1126
1181
  let base;
1127
1182
  try {
1128
1183
  base = new URL(baseUrl, url);
@@ -653,15 +653,15 @@ export interface ResolvedZigRuntimeAssetConfig {
653
653
  stdlibUrl: string;
654
654
  integrity: ZigExecutionAssetReceipts;
655
655
  }
656
- export declare const PYTHON_RUNTIME_LOAD_ASSETS: readonly ["pyodide.mjs", "pyodide.asm.js", "pyodide-lock.json", "pyodide.asm.wasm", "python_stdlib.zip"];
656
+ export declare const PYTHON_RUNTIME_LOAD_ASSETS: readonly ["pyodide.mjs", "pyodide.asm.mjs", "pyodide-lock.json", "pyodide.asm.wasm", "python_stdlib.zip"];
657
657
  export declare const JAVA_RUNTIME_LOAD_ASSETS: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
658
658
  export declare const CLANG_RUNTIME_LOAD_ASSETS: readonly ["runtime-manifest.v1.json", "bin/memfs.wasm.gz", "bin/clang.wasm.gz", "bin/lld.wasm.gz", "bin/sysroot.tar.gz"];
659
- export declare const CLANGD_RUNTIME_LOAD_ASSETS: readonly ["clangd.js", "clangd.wasm.gz"];
659
+ export declare const CLANGD_RUNTIME_LOAD_ASSETS: readonly ["clangd.js", "clangd.wasm.gz", "clangd.headers.json.gz"];
660
660
  export declare const RUNTIME_LOAD_ASSETS: {
661
- python: readonly ["pyodide.mjs", "pyodide.asm.js", "pyodide-lock.json", "pyodide.asm.wasm", "python_stdlib.zip"];
661
+ python: readonly ["pyodide.mjs", "pyodide.asm.mjs", "pyodide-lock.json", "pyodide.asm.wasm", "python_stdlib.zip"];
662
662
  java: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
663
663
  clang: readonly ["runtime-manifest.v1.json", "bin/memfs.wasm.gz", "bin/clang.wasm.gz", "bin/lld.wasm.gz", "bin/sysroot.tar.gz"];
664
- clangd: readonly ["clangd.js", "clangd.wasm.gz"];
664
+ clangd: readonly ["clangd.js", "clangd.wasm.gz", "clangd.headers.json.gz"];
665
665
  };
666
666
  export declare function resolveRuntimeAssetConfig(runtime: RuntimeAssetRuntime, options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string, callCache?: RuntimeAssetCacheOptions): ResolvedRuntimeAssetConfig;
667
667
  export declare function resolveRustCompilerUrl(options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): string;
@@ -1,4 +1,4 @@
1
- import { env as dynamicPublicEnv } from '$env/dynamic/public';
1
+ import * as publicEnv from '$app/env/public';
2
2
  import { resolveRuntimeAssetCacheOptions, resolveRuntimeAssetLockEntry } from '@wasm-idle/core';
3
3
  import { BUNDLED_CLANG_ASSET_INTEGRITY } from './clangAssetIntegrity';
4
4
  import { snapshotDOuterAssetConfig } from './dOuterAssets';
@@ -29,10 +29,9 @@ import { WASM_FORTRAN_EXECUTION_ASSET_RECEIPTS } from './wasmFortranExecutionAss
29
29
  import { WASM_D_OUTER_ASSET_RECEIPTS } from './wasmDIntegrity';
30
30
  import { WASM_ZIG_ASSET_RECEIPTS } from './wasmZigVersion';
31
31
  import { snapshotZigExecutionAssetReceipts } from './zigAssets';
32
- const publicEnv = (dynamicPublicEnv || {});
33
32
  export const PYTHON_RUNTIME_LOAD_ASSETS = [
34
33
  'pyodide.mjs',
35
- 'pyodide.asm.js',
34
+ 'pyodide.asm.mjs',
36
35
  'pyodide-lock.json',
37
36
  'pyodide.asm.wasm',
38
37
  'python_stdlib.zip'
@@ -45,7 +44,11 @@ export const CLANG_RUNTIME_LOAD_ASSETS = [
45
44
  'bin/lld.wasm.gz',
46
45
  'bin/sysroot.tar.gz'
47
46
  ];
48
- export const CLANGD_RUNTIME_LOAD_ASSETS = ['clangd.js', 'clangd.wasm.gz'];
47
+ export const CLANGD_RUNTIME_LOAD_ASSETS = [
48
+ 'clangd.js',
49
+ 'clangd.wasm.gz',
50
+ 'clangd.headers.json.gz'
51
+ ];
49
52
  export const RUNTIME_LOAD_ASSETS = {
50
53
  python: PYTHON_RUNTIME_LOAD_ASSETS,
51
54
  java: JAVA_RUNTIME_LOAD_ASSETS,
@@ -280,6 +280,7 @@ export function createCachedClangSandbox(legacy, language, runtimeCache, cache =
280
280
  maxAssetBytes: limit,
281
281
  languageSysroots,
282
282
  log,
283
+ persistentCache: executionAssetConfig.persistentCache,
283
284
  ...(language === 'CPP' && precompiledHeader
284
285
  ? { precompiledHeader }
285
286
  : {}),
@@ -142,6 +142,7 @@ class Clang {
142
142
  };
143
143
  this.worker.postMessage({
144
144
  load: true,
145
+ persistentCache: assetConfig.persistentCache,
145
146
  verifiedStreaming: bundledAssets,
146
147
  languageSysrootProfiles,
147
148
  log,
@@ -305,7 +306,8 @@ class Clang {
305
306
  activePath: options.activePath,
306
307
  cppVersion: options.cppVersion,
307
308
  cVersion: options.cVersion,
308
- debugMode
309
+ debugMode,
310
+ persistentCache: persistentCache ?? this.persistentCache
309
311
  });
310
312
  if (results) {
311
313
  this.elapse = Date.now() - this.begin;
@@ -340,6 +342,7 @@ class Clang {
340
342
  if (precompiledHeader)
341
343
  this.precompiledHeaderSent = { worker: this.worker, header: precompiledHeader };
342
344
  this.worker?.postMessage({
345
+ persistentCache: persistentCache ?? this.persistentCache,
343
346
  ...(precompiledHeader ? { precompiledHeader } : {}),
344
347
  code,
345
348
  prepare,
@@ -494,6 +497,7 @@ class Clang {
494
497
  };
495
498
  worker.postMessage({
496
499
  load: true,
500
+ persistentCache: runtime.assetConfig.persistentCache,
497
501
  verifiedStreaming: runtime.verifiedStreaming,
498
502
  languageSysrootProfiles: runtime.languageSysrootProfiles,
499
503
  log: false,
@@ -9,6 +9,7 @@ export type ClangCompileWorkerRequest = {
9
9
  assets: WorkerRuntimeAssetConfig;
10
10
  /** The last precompiled <bits/stdc++.h>; the worker ignores it when its key differs. */
11
11
  precompiledHeader?: BrowserClangPrecompiledHeader;
12
+ persistentCache?: import('@wasm-idle/core').RuntimeAssetCacheOptions;
12
13
  };
13
14
  /**
14
15
  * `precompiledHeader: 'building'` means the worker stays alive after the result and later posts
@@ -3,6 +3,8 @@ export declare class RuntimeAssetCache {
3
3
  private readonly maxBytes;
4
4
  private readonly maxEntries;
5
5
  private entries;
6
+ private inFlight;
7
+ private inFlightKeyBytes;
6
8
  private identities;
7
9
  private nextIdentity;
8
10
  private bytes;
@@ -15,11 +17,15 @@ export declare class RuntimeAssetCache {
15
17
  get<T>(key: string): T | undefined;
16
18
  /** Only the trusted host publishes fully verified results; failures are never retained. */
17
19
  set<T>(key: string, value: T, bytes: number): void;
20
+ /** Share work, but give every consumer its own cancellation and progress subscription. */
21
+ share<T, Progress = never>(key: string, signal: AbortSignal, start: (signal: AbortSignal, report: (progress: Progress, progressKey?: string) => void) => Promise<T>, onProgress?: (progress: Progress) => void): Promise<T>;
22
+ private removeOperation;
18
23
  stats(): {
19
24
  hits: number;
20
25
  misses: number;
21
26
  entries: number;
22
27
  bytes: number;
28
+ inFlight: number;
23
29
  disposed: boolean;
24
30
  };
25
31
  dispose(): void;