@nimbus-sh/worker 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +2 -2
  2. package/dist/cirrus-npm-cjs.generated.d.ts +1 -1
  3. package/dist/cirrus-npm-cjs.generated.js +1 -1
  4. package/dist/facets/cirrus-real.d.ts.map +1 -1
  5. package/dist/facets/cirrus-real.js +6 -5
  6. package/dist/facets/compose.d.ts +148 -0
  7. package/dist/facets/compose.d.ts.map +1 -0
  8. package/dist/facets/compose.js +146 -0
  9. package/dist/facets/durable-images.d.ts +9 -5
  10. package/dist/facets/durable-images.d.ts.map +1 -1
  11. package/dist/facets/durable-images.js +17 -7
  12. package/dist/facets/esbuild-bundle-pool.d.ts +49 -0
  13. package/dist/facets/esbuild-bundle-pool.d.ts.map +1 -0
  14. package/dist/facets/esbuild-bundle-pool.js +122 -0
  15. package/dist/facets/exec-telemetry.d.ts +8 -0
  16. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  17. package/dist/facets/manager.d.ts +240 -109
  18. package/dist/facets/manager.d.ts.map +1 -1
  19. package/dist/facets/manager.js +677 -342
  20. package/dist/facets/process.d.ts +20 -16
  21. package/dist/facets/process.d.ts.map +1 -1
  22. package/dist/facets/process.js +59 -28
  23. package/dist/facets/real-vite-fs-shim.js +2 -2
  24. package/dist/facets/real-vite-hmr.d.ts +3 -1
  25. package/dist/facets/real-vite-hmr.d.ts.map +1 -1
  26. package/dist/facets/real-vite-hmr.js +18 -8
  27. package/dist/facets/vite-dev-server.d.ts +25 -47
  28. package/dist/facets/vite-dev-server.d.ts.map +1 -1
  29. package/dist/facets/vite-dev-server.js +185 -219
  30. package/dist/facets/wasm-image-digest.d.ts +25 -0
  31. package/dist/facets/wasm-image-digest.d.ts.map +1 -0
  32. package/dist/facets/wasm-image-digest.js +25 -0
  33. package/dist/facets/wasm-swap-registry.d.ts +51 -92
  34. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  35. package/dist/facets/wasm-swap-registry.js +68 -227
  36. package/dist/facets/ws-terminal.d.ts +9 -31
  37. package/dist/facets/ws-terminal.d.ts.map +1 -1
  38. package/dist/facets/ws-terminal.js +58 -51
  39. package/dist/git/network-facet.d.ts.map +1 -1
  40. package/dist/git/network-facet.js +6 -5
  41. package/dist/hosted/commands.d.ts +12 -0
  42. package/dist/hosted/commands.d.ts.map +1 -0
  43. package/dist/hosted/commands.js +1658 -0
  44. package/dist/hosted/runtime.d.ts +114 -0
  45. package/dist/hosted/runtime.d.ts.map +1 -0
  46. package/dist/hosted/runtime.js +288 -0
  47. package/dist/hosted/services.d.ts +40 -0
  48. package/dist/hosted/services.d.ts.map +1 -0
  49. package/dist/hosted/services.js +512 -0
  50. package/dist/loaders/child-process/spawn-pool.d.ts +3 -2
  51. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
  52. package/dist/loaders/child-process/spawn-pool.js +3 -2
  53. package/dist/loaders/generated-workers.d.ts +2 -2
  54. package/dist/loaders/generated-workers.d.ts.map +1 -1
  55. package/dist/loaders/generated-workers.js +3 -3
  56. package/dist/loaders/npm-resolve-preamble.d.ts +3 -6
  57. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  58. package/dist/loaders/npm-resolve-preamble.js +34 -108
  59. package/dist/node-shims-artifact.generated.js +3 -3
  60. package/dist/npm/install-batch-facet.d.ts +3 -2
  61. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  62. package/dist/npm/install-batch-facet.js +45 -15
  63. package/dist/npm/installer.d.ts +22 -17
  64. package/dist/npm/installer.d.ts.map +1 -1
  65. package/dist/npm/installer.js +209 -193
  66. package/dist/npm/pre-bundle-facet.d.ts +1 -1
  67. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  68. package/dist/npm/pre-bundle-facet.js +1 -1
  69. package/dist/npm/resolve-facet.d.ts +6 -0
  70. package/dist/npm/resolve-facet.d.ts.map +1 -1
  71. package/dist/npm/resolve-one-facet.d.ts +21 -20
  72. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  73. package/dist/npm/resolve-one-facet.js +85 -84
  74. package/dist/npm/semver.d.ts +59 -0
  75. package/dist/npm/semver.d.ts.map +1 -0
  76. package/dist/npm/semver.js +226 -0
  77. package/dist/router/remote-api.js +31 -1
  78. package/dist/runtime/bash-repl.d.ts +1 -1
  79. package/dist/runtime/bash-repl.d.ts.map +1 -1
  80. package/dist/runtime/bash-repl.js +38 -4
  81. package/dist/runtime/bun-runner.js +1 -1
  82. package/dist/runtime/html-entrypoint.d.ts +7 -0
  83. package/dist/runtime/html-entrypoint.d.ts.map +1 -1
  84. package/dist/runtime/html-entrypoint.js +18 -2
  85. package/dist/runtime/node-runner.d.ts +4 -1
  86. package/dist/runtime/node-runner.d.ts.map +1 -1
  87. package/dist/runtime/node-runner.js +9 -2
  88. package/dist/runtime/node-shims.d.ts.map +1 -1
  89. package/dist/runtime/node-shims.js +305 -29
  90. package/dist/runtime/opencode-facet-runner.js +16 -16
  91. package/dist/runtime/package-manager.d.ts +35 -122
  92. package/dist/runtime/package-manager.d.ts.map +1 -1
  93. package/dist/runtime/package-manager.js +61 -596
  94. package/dist/runtime/process-logs-api.d.ts +1 -1
  95. package/dist/runtime/process-logs-api.d.ts.map +1 -1
  96. package/dist/runtime/process-logs-api.js +1 -1
  97. package/dist/runtime/python-repl.d.ts +2 -1
  98. package/dist/runtime/python-repl.d.ts.map +1 -1
  99. package/dist/runtime/python-repl.js +82 -59
  100. package/dist/runtime/repl-session.d.ts +9 -47
  101. package/dist/runtime/repl-session.d.ts.map +1 -1
  102. package/dist/runtime/repl-session.js +105 -105
  103. package/dist/runtime/runtime-catalog.d.ts +13 -0
  104. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  105. package/dist/runtime/runtime-catalog.js +88 -0
  106. package/dist/session/agent.d.ts +1 -21
  107. package/dist/session/agent.d.ts.map +1 -1
  108. package/dist/session/ai.d.ts +5 -1
  109. package/dist/session/ai.d.ts.map +1 -1
  110. package/dist/session/ai.js +7 -0
  111. package/dist/session/helpers.d.ts +38 -0
  112. package/dist/session/helpers.d.ts.map +1 -1
  113. package/dist/session/helpers.js +76 -0
  114. package/dist/session/hibernation.d.ts +2 -1
  115. package/dist/session/hibernation.d.ts.map +1 -1
  116. package/dist/session/hibernation.js +7 -4
  117. package/dist/session/init-phases.d.ts +1 -1
  118. package/dist/session/init-phases.d.ts.map +1 -1
  119. package/dist/session/init-phases.js +2 -12
  120. package/dist/session/init.d.ts +15 -36
  121. package/dist/session/init.d.ts.map +1 -1
  122. package/dist/session/init.js +50 -2327
  123. package/dist/session/loopback.d.ts +2 -29
  124. package/dist/session/loopback.d.ts.map +1 -1
  125. package/dist/session/loopback.js +3 -27
  126. package/dist/session/nimbus-session.d.ts +77 -34
  127. package/dist/session/nimbus-session.d.ts.map +1 -1
  128. package/dist/session/nimbus-session.js +126 -514
  129. package/dist/session/npm-install-port.d.ts +18 -0
  130. package/dist/session/npm-install-port.d.ts.map +1 -0
  131. package/dist/session/npm-install-port.js +37 -0
  132. package/dist/session/port-capability.d.ts +44 -0
  133. package/dist/session/port-capability.d.ts.map +1 -1
  134. package/dist/session/port-capability.js +110 -0
  135. package/dist/session/programmatic.d.ts +36 -43
  136. package/dist/session/programmatic.d.ts.map +1 -1
  137. package/dist/session/programmatic.js +109 -115
  138. package/dist/session/routes.d.ts +30 -17
  139. package/dist/session/routes.d.ts.map +1 -1
  140. package/dist/session/routes.js +42 -131
  141. package/dist/session/rpc.d.ts +15 -15
  142. package/dist/session/rpc.d.ts.map +1 -1
  143. package/dist/session/rpc.js +98 -49
  144. package/dist/session/serving-port.d.ts +27 -0
  145. package/dist/session/serving-port.d.ts.map +1 -0
  146. package/dist/session/serving-port.js +27 -0
  147. package/dist/session/start-real-vite.d.ts +12 -0
  148. package/dist/session/start-real-vite.d.ts.map +1 -1
  149. package/dist/session/start-real-vite.js +4 -4
  150. package/dist/session/supervisor-op.d.ts +4 -4
  151. package/dist/session/supervisor-op.d.ts.map +1 -1
  152. package/dist/session/supervisor-op.js +24 -10
  153. package/dist/session/supervisor-rpc.d.ts +7 -7
  154. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  155. package/dist/session/supervisor-rpc.js +4 -0
  156. package/dist/session/vite-command.d.ts +18 -0
  157. package/dist/session/vite-command.d.ts.map +1 -0
  158. package/dist/session/vite-command.js +609 -0
  159. package/dist/session/ws.d.ts +61 -0
  160. package/dist/session/ws.d.ts.map +1 -1
  161. package/dist/session/ws.js +122 -12
  162. package/dist/shell/npm-bin-entrypoints.d.ts +0 -35
  163. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  164. package/dist/shell/npm-bin-entrypoints.js +12 -85
  165. package/dist/workspace-host.d.ts +6 -0
  166. package/dist/workspace-host.d.ts.map +1 -0
  167. package/dist/workspace-host.js +5 -0
  168. package/dist/workspace.d.ts +2 -0
  169. package/dist/workspace.d.ts.map +1 -0
  170. package/dist/workspace.js +1 -0
  171. package/package.json +34 -9
  172. package/public/_assets/runtime/{node-shims-9e1380d9ee430fcc.js → node-shims-8f8c64edd2a1f0f5.js} +305 -29
  173. package/public/index.html +1 -1
  174. package/scripts/bundle-facet-workers.mjs +1 -1
  175. package/scripts/bundle-npm-cjs.mjs +2 -2
  176. package/dist/facets/on-demand-bundle-gate.d.ts +0 -65
  177. package/dist/facets/on-demand-bundle-gate.d.ts.map +0 -1
  178. package/dist/facets/on-demand-bundle-gate.js +0 -125
  179. package/dist/npm/install-args.d.ts +0 -21
  180. package/dist/npm/install-args.d.ts.map +0 -1
  181. package/dist/npm/install-args.js +0 -70
  182. package/dist/npm/npm-log.d.ts +0 -31
  183. package/dist/npm/npm-log.d.ts.map +0 -1
  184. package/dist/npm/npm-log.js +0 -51
@@ -24,11 +24,11 @@ import { BUNDLER_VERSION } from '@nimbus-sh/core/runtime/esbuild-service.js';
24
24
  import { NpmCache } from './cache.js';
25
25
  import { computeHoistPlan, } from './resolver.js';
26
26
  import { packumentUrl } from './r2-cache.js';
27
- import { npmAddedLine, npmHttpCacheLine, npmHttpFetchLine, npmTitleLine, } from './npm-log.js';
28
- import { applySwaps, findRejects, lookupSwap, lookupReject, shouldSkipPackage, shouldWarnSkipTransitive, isOptionalNativeBinding, formatSwapNotice, RegistryRejectError, emitRegistryEvent, } from '../facets/wasm-swap-registry.js';
27
+ import { satisfiesRange, isSemverRange } from './semver.js';
28
+ import { npmAddedLine, npmHttpCacheLine, npmHttpFetchLine, npmTitleLine, } from '@nimbus-sh/core/substrate/lifo/commands/system/npm-log.js';
29
+ import { applySwaps, findRejects, lookupSwap, isOptionalNativeBinding, formatSwapNotice, emitRegistryEvent, } from '../facets/wasm-swap-registry.js';
29
30
  import { resolvePackageEntry } from '@nimbus-sh/core/_shared/exports-resolver.js';
30
31
  import { encodeWriteBatchStream } from '@nimbus-sh/platform/w7-frame.js';
31
- import { IsolatePool } from '@nimbus-sh/fabric/isolate-pool.js';
32
32
  import { Fanout, IN_DO_THRESHOLD } from '@nimbus-sh/fabric/fanout.js';
33
33
  import { TAR_STREAM_PREAMBLE, W7_FRAME_PREAMBLE } from '../loaders/generated-workers.js';
34
34
  import { installPackagesInFacet, } from './install-batch-facet.js';
@@ -36,12 +36,10 @@ import { setInstallPhase, recordInstallFacetCounters, recordPreBundleSummary, re
36
36
  import { recordCacheStatEvents } from '@nimbus-sh/core/_shared/cache-stats.js';
37
37
  import { estimateSupervisorHeap } from '@nimbus-sh/platform/heap-estimate.js';
38
38
  import { describeError } from '@nimbus-sh/platform/oom-classify.js';
39
- import { resolveOnePackumentInFacet, } from './resolve-one-facet.js';
39
+ import { resolveOnePackumentInFacet, parseRegistryRequest, } from './resolve-one-facet.js';
40
40
  import { NPM_RESOLVE_PREAMBLE } from '../loaders/npm-resolve-preamble.js';
41
41
  import { prebundleOne, buildSliceForSpecifierWithCap, externalsForSpecifier, } from './pre-bundle-facet.js';
42
- import { PRE_BUNDLE_PREAMBLE } from '../loaders/pre-bundle-preamble.js';
43
- import { fetchEsbuildWasmBytes } from '../runtime/esbuild-wasm-bytes.js';
44
- import { CHUNK_SIZE, PRE_BUNDLE_CONCURRENCY, PRE_BUNDLE_SLICE_CAP_BYTES, SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES, } from '@nimbus-sh/platform/limits.js';
42
+ import { CHUNK_SIZE, PRE_BUNDLE_CONCURRENCY, PRE_BUNDLE_SLICE_CAP_BYTES, } from '@nimbus-sh/platform/limits.js';
45
43
  import { acquireSupervisorAllocation } from '@nimbus-sh/platform/heavy-alloc-coord.js';
46
44
  import { countPackageFiles, BARREL_PKG_FILE_THRESHOLD, packageNameFromSpecifier } from '@nimbus-sh/core/runtime/barrel-detect.js';
47
45
  import { scanNamedImports, namedImportSignature, buildSyntheticEntry, buildScopedSliceForSynthetic, syntheticEntryPath, } from '../runtime/barrel-synthesizer.js';
@@ -53,6 +51,8 @@ export class NpmInstaller {
53
51
  vfs;
54
52
  cache;
55
53
  esbuild;
54
+ /** The session's esbuild facet pool, shared with the on-demand dev-server path. */
55
+ bundlePool;
56
56
  ctx;
57
57
  env;
58
58
  onProgress;
@@ -75,6 +75,7 @@ export class NpmInstaller {
75
75
  this.vfs = vfs.as(CRED_KERNEL);
76
76
  this.cache = new NpmCache(sql);
77
77
  this.esbuild = opts?.esbuild ?? null;
78
+ this.bundlePool = opts?.bundlePool ?? null;
78
79
  this.ctx = opts?.ctx;
79
80
  this.env = opts?.env;
80
81
  this.onProgress = opts?.onProgress;
@@ -93,6 +94,11 @@ export class NpmInstaller {
93
94
  const start = Date.now();
94
95
  const projDir = projectDir.replace(/^\/+/, '').replace(/\/+$/, '');
95
96
  const nmDir = projDir + '/node_modules';
97
+ // Per-invocation progress wins over whatever the ctor bound — the
98
+ // installer is shared, the terminal it should speak to is not.
99
+ const previousProgress = this.onProgress;
100
+ if (opts?.onProgress !== undefined)
101
+ this.onProgress = opts.onProgress;
96
102
  const log = (msg) => this.onProgress?.(msg);
97
103
  this.npmLog = opts?.npmLog ?? (() => { });
98
104
  // Reset phase to 'idle' on any exit path so /api/_diag/memory
@@ -105,6 +111,7 @@ export class NpmInstaller {
105
111
  finally {
106
112
  setInstallPhase('idle');
107
113
  this.npmLog = () => { };
114
+ this.onProgress = previousProgress;
108
115
  }
109
116
  }
110
117
  async _installInner(projDir, nmDir, opts, log, start) {
@@ -118,19 +125,22 @@ export class NpmInstaller {
118
125
  this.npmLog('verbose', npmTitleLine(opts?.packages ?? []));
119
126
  let phaseStart = Date.now();
120
127
  log('Checking lockfile...');
121
- const specs = await this.buildSpecs(projDir, opts?.packages, opts?.production);
128
+ const { specs, devOnly, optionalRoots, advised } = await this.buildSpecs(projDir, opts?.packages, opts?.production);
129
+ // EBADPLATFORM refusals (os/cpu/libc allowlists on a required
130
+ // package) fail the install like real npm; every refused name was
131
+ // already logged as a [skip] line, name them again with the reason.
132
+ const recordRefusals = (rejected) => {
133
+ for (const r of rejected) {
134
+ if (!r.required)
135
+ continue;
136
+ failed.push(r.name);
137
+ log(`npm ERR! ${r.name}: ${r.reason}`);
138
+ }
139
+ };
122
140
  if (Object.keys(specs).length === 0) {
123
141
  log('No dependencies to install.');
124
142
  return { installed, failed, totalFiles: 0, elapsed: Date.now() - start, cachedHits: 0, phases: {} };
125
143
  }
126
- // W11: framework detection. If the project depends on a framework
127
- // (next/astro/nuxt/remix/sveltekit) or generic vite, we exempt
128
- // FRAMEWORK_REQUIRED_PACKAGES (vite, ...) from the SKIP_PACKAGES set
129
- // so the framework's CLI can `import 'vite'` from node_modules.
130
- const frameworkAware = await this.detectFrameworkAware(projDir);
131
- if (frameworkAware) {
132
- log(`Framework detected — installing framework-required packages (vite, …).`);
133
- }
134
144
  const lockfile = this.cache.readLockfile(projDir);
135
145
  let resolved;
136
146
  let usedLockfile = false;
@@ -156,9 +166,13 @@ export class NpmInstaller {
156
166
  phaseStart = Date.now();
157
167
  setInstallPhase('resolve');
158
168
  log(`Resolving ${Object.keys(specs).length} dependencies (path: fanout, fetch: ${this.fetchFn ? 'facet-proxy' : 'global'})...`);
159
- const tree = await this.resolveTreeViaFanout(specs, log, { frameworkAware });
169
+ const tree = await this.resolveTreeViaFanout(specs, log, { optionalRoots, devOnly, advised });
160
170
  resolved = tree.resolved;
161
171
  phases['resolve'] = Date.now() - phaseStart;
172
+ // Transitive platform-gate refusals. Required ones join `failed`
173
+ // with their reason; optional-edge ones stay out of the outcome
174
+ // lists. Either way the rest of the tree resolved and installs.
175
+ recordRefusals(tree.rejected);
162
176
  // A dependency the walk could not resolve is missing from the
163
177
  // install and from every subtree beneath it. Name it and its
164
178
  // reason, and carry it into `failed` so the command exits non-zero.
@@ -168,8 +182,9 @@ export class NpmInstaller {
168
182
  }
169
183
  if (resolved.size === 0) {
170
184
  log('No packages resolved.');
185
+ const refused = new Set(tree.rejected.map((r) => r.name));
171
186
  for (const name of Object.keys(specs)) {
172
- if (!tree.unresolved.has(name))
187
+ if (!tree.unresolved.has(name) && !refused.has(name))
173
188
  failed.push(name);
174
189
  }
175
190
  return {
@@ -306,7 +321,7 @@ export class NpmInstaller {
306
321
  // the npm command-handler returns to the shell, the shell
307
322
  // prints its prompt, and THEN the orphan promise's safeProgress
308
323
  // fires — visually corrupting the freshly-rendered prompt
309
- // ("user@nimbus:~/app$ [npm] Pre-bundle complete: ...").
324
+ // ("user@nimbus:~/example-app$ [npm] Pre-bundle complete: ...").
310
325
  //
311
326
  // Fix:
312
327
  // Suppress writes to ctx.stdout once install() has returned.
@@ -433,7 +448,13 @@ export class NpmInstaller {
433
448
  * - X.5-G G1 optional-native silent-skip,
434
449
  * - X.5-drizzle best-effort tagging on optional-peer subtrees,
435
450
  * - W6 swap / warn / reject decisions (top-level enforcement; the
436
- * per-package task ALSO checks these for transitive correctness),
451
+ * per-package task ALSO checks these for transitive correctness).
452
+ * A package is required iff it is reachable from a required root
453
+ * (every selected spec except optionalDependencies roots) through
454
+ * a chain of required edges (`dependencies` minus the package's
455
+ * own optionalDependencies, required peers); W6 refusals are
456
+ * classified against the closure computed over the resolved
457
+ * graph at end of walk.
437
458
  * - cache flushing (one batched putRegistryEntries at end).
438
459
  *
439
460
  * The per-package task (resolveOnePackumentInFacet) owns ONLY the
@@ -444,7 +465,19 @@ export class NpmInstaller {
444
465
  */
445
466
  async resolveTreeViaFanout(specs, log, opts = {}) {
446
467
  const t0 = Date.now();
447
- const frameworkAware = !!opts.frameworkAware;
468
+ const optionalRoots = opts.optionalRoots ?? new Set();
469
+ const devOnly = opts.devOnly ?? new Set();
470
+ // Policy advisories already announced — one `note:` line per name
471
+ // per install, shared with applyW6Registry which runs first.
472
+ const advised = opts.advised ?? new Set();
473
+ const noteAdvisory = (ev) => {
474
+ if (advised.has(ev.from))
475
+ return;
476
+ advised.add(ev.from);
477
+ const hint = ev.suggest ? ` … try: ${ev.suggest}` : '';
478
+ const marker = devOnly.has(ev.from) ? ' (declared in devDependencies)' : '';
479
+ log(`[npm] note: ${ev.from} has no Workers-compatible build${marker}: ${ev.reason}${hint}`);
480
+ };
448
481
  const __f2Diag = (globalThis.process?.env?.NIMBUS_DIAG_INSTALL_PIPELINE === '1');
449
482
  // Per-walk state — supervisor side.
450
483
  const resolved = new Map();
@@ -452,6 +485,10 @@ export class NpmInstaller {
452
485
  // A name lands here at most once: the frontier marks it `seen` before
453
486
  // dispatch, so no later parent re-enqueues it.
454
487
  const unresolved = new Map();
488
+ // EBADPLATFORM refusals (os/cpu/libc allowlists), in walk order.
489
+ // Announced as `[skip]` lines at record time; required-vs-optional is
490
+ // classified at end of walk, when every required edge has been seen.
491
+ const refusals = new Map();
455
492
  const seen = new Set();
456
493
  const topLevelNames = new Set(Object.keys(specs));
457
494
  const optionalNames = new Set(); // X.5-G G1
@@ -554,9 +591,7 @@ export class NpmInstaller {
554
591
  name,
555
592
  range,
556
593
  cachedEntries,
557
- topLevel: topLevelNames.has(name),
558
594
  isOptional: optionalNames.has(name),
559
- frameworkAware,
560
595
  fetchTimeoutMs: 15_000,
561
596
  retries: 3,
562
597
  };
@@ -587,7 +622,8 @@ export class NpmInstaller {
587
622
  unresolved.set(taskName, `resolver fanout returned no result at layer ${layerN}`);
588
623
  continue;
589
624
  }
590
- // Forward messages + events.
625
+ // Forward messages + events. `advisory` events are the policy's
626
+ // "cannot run here" list — one note per name per install.
591
627
  for (const m of res.messages)
592
628
  log(m);
593
629
  for (const ev of res.events) {
@@ -595,6 +631,8 @@ export class NpmInstaller {
595
631
  emitRegistryEvent(ev);
596
632
  }
597
633
  catch { /* swallow sink errors */ }
634
+ if (ev.type === 'advisory')
635
+ noteAdvisory(ev);
598
636
  }
599
637
  // Accumulate cache writes for end-of-walk flush.
600
638
  for (const cw of res.cacheWrites)
@@ -617,7 +655,12 @@ export class NpmInstaller {
617
655
  r2Losses++;
618
656
  if (res.packumentBytesDecoded > 0)
619
657
  totalPackumentsDecoded++;
620
- // W6 reject error handling.
658
+ // w6-reject: an os/cpu/libc platform-gate refusal — npm's
659
+ // EBADPLATFORM. Announced as a [skip] line now and classified
660
+ // required-vs-optional at end of walk, when the required-edge
661
+ // closure is final; best-effort optional-peer subtrees still
662
+ // skip silently. Table rejects are advisories handled above and
663
+ // never reach this branch.
621
664
  if (res.error && res.error.type === 'w6-reject') {
622
665
  if (bestEffortNames.has(taskName)) {
623
666
  // X.5-drizzle: silent-skip inside best-effort optional-peer
@@ -627,14 +670,11 @@ export class NpmInstaller {
627
670
  emitRegistryEvent({ type: 'transitive-skip', from: taskName, reason });
628
671
  continue;
629
672
  }
630
- // Real reject: throw RegistryRejectError to abort install.
631
- const rejectEntry = {
632
- from: res.error.from,
633
- reason: res.error.reason,
634
- suggest: res.error.suggest,
635
- transitive: 'fail',
636
- };
637
- throw new RegistryRejectError([rejectEntry]);
673
+ refusals.set(taskName, res.error.reason);
674
+ const hint = res.error.suggest ? ` … try: ${res.error.suggest}` : '';
675
+ log(`[resolve-fanout] [skip] ${taskName} — ${res.error.reason}${hint}`);
676
+ emitRegistryEvent({ type: 'transitive-skip', from: taskName, reason: res.error.reason });
677
+ continue;
638
678
  }
639
679
  // Resolution failure. Optional (X.5-G G1) and best-effort
640
680
  // optional-peer (X.5-drizzle) edges are allowed to disappear;
@@ -710,22 +750,15 @@ export class NpmInstaller {
710
750
  }
711
751
  }
712
752
  // X.5-F R2.5 + X.5-J: optional peers when THIS pkg is the
713
- // user's top-level. Filter through the policy reject list.
753
+ // user's top-level. They resolve best-effort; a policy-listed
754
+ // name that resolves installs with an advisory like any
755
+ // package, and one that does not is skipped quietly.
714
756
  if (topLevelNames.has(pkg.name)) {
715
757
  const allPeers = pkg.__allPeerDependencies;
716
758
  if (allPeers) {
717
759
  for (const [peerName, peerRange] of Object.entries(allPeers)) {
718
760
  if (resolved.has(peerName) || seen.has(peerName))
719
761
  continue;
720
- const peerFail = lookupReject(peerName);
721
- const peerWarn = shouldWarnSkipTransitive(peerName);
722
- const peerReject = peerFail || peerWarn;
723
- if (peerReject) {
724
- const reason = `optional peer in REJECT_INSTALL: ${peerName} — ${peerReject.reason}`;
725
- log(`[resolve-fanout] [skip] ${peerName} — ${reason}`);
726
- emitRegistryEvent({ type: 'transitive-skip', from: peerName, reason });
727
- continue;
728
- }
729
762
  topLevelNames.add(peerName);
730
763
  bestEffortNames.add(peerName);
731
764
  queue.push([peerName, peerRange]);
@@ -735,6 +768,35 @@ export class NpmInstaller {
735
768
  }
736
769
  layerN++;
737
770
  }
771
+ // G2: a refused package is required iff it is reachable from a
772
+ // required root — every selected spec except optionalDependencies
773
+ // roots — through required edges (`dependencies` minus the package's
774
+ // own optionalDependencies, which npm overrides as optional, and
775
+ // required peers). Computed now over the resolved graph: the walk
776
+ // sees names in BFS order, so incremental propagation would freeze a
777
+ // dev/optional first sighting before a later required edge lands.
778
+ const requiredNames = new Set();
779
+ const pendingRequired = Object.keys(specs).filter((name) => !optionalRoots.has(name));
780
+ for (let i = 0; i < pendingRequired.length; i++) {
781
+ const name = pendingRequired[i];
782
+ if (requiredNames.has(name))
783
+ continue;
784
+ requiredNames.add(name);
785
+ const pkg = resolved.get(name);
786
+ if (!pkg)
787
+ continue;
788
+ const optional = pkg.optionalDependencies ?? {};
789
+ for (const dep of Object.keys(pkg.dependencies)) {
790
+ if (!Object.hasOwn(optional, dep))
791
+ pendingRequired.push(dep);
792
+ }
793
+ for (const peer of Object.keys(pkg.peerDependencies ?? {}))
794
+ pendingRequired.push(peer);
795
+ }
796
+ const rejected = [];
797
+ for (const [name, reason] of refusals) {
798
+ rejected.push({ name, reason, required: requiredNames.has(name) });
799
+ }
738
800
  // End-of-walk: flush all cache writes in one RPC-equivalent call
739
801
  // (this.cache is a SQLite handle; one putRegistryEntries call =
740
802
  // O(N) prepared statements within a single DO event-loop turn,
@@ -767,7 +829,7 @@ export class NpmInstaller {
767
829
  `, dispatch barriers=${dispatchBarriers}, ` +
768
830
  `elapsed=${((Date.now() - t0) / 1000).toFixed(1)}s` +
769
831
  (unresolved.size > 0 ? `, unresolved=${unresolved.size}` : ''));
770
- return { resolved, unresolved };
832
+ return { resolved, unresolved, rejected };
771
833
  }
772
834
  /**
773
835
  * Batch install via two-tier fan-out (Fanout).
@@ -985,102 +1047,71 @@ export class NpmInstaller {
985
1047
  }
986
1048
  }
987
1049
  // ── Spec building ─────────────────────────────────────────────────────
988
- /**
989
- * Build the dependency specs from package.json + explicit packages.
990
- */
991
- /**
992
- * W11: framework detection at install time. Reads package.json from the
993
- * project root and runs detectFramework() against its deps + the basenames
994
- * we can see in node_modules-adjacent siblings.
995
- *
996
- * Returns true if the project is detected as one of {next, astro, nuxt,
997
- * remix, sveltekit, vite, wrangler}. False for 'unknown'.
998
- */
999
- async detectFrameworkAware(projDir) {
1000
- try {
1001
- const pkgPath = projDir + '/package.json';
1002
- if (!this.vfs.exists(pkgPath))
1003
- return false;
1004
- const pkg = JSON.parse(this.vfs.readFileString(pkgPath));
1005
- // Lazy-load the detector to avoid a hard dep cycle.
1006
- const { detectFramework } = await import('@nimbus-sh/core/runtime/framework-detect.js');
1007
- // Snapshot root files. Best-effort — if readdir throws we proceed
1008
- // with an empty set (still detects via deps for most frameworks).
1009
- const files = new Set();
1010
- try {
1011
- for (const e of this.vfs.readdir(projDir))
1012
- files.add(e.name);
1013
- }
1014
- catch { /* tolerate */ }
1015
- // Optional file contents — read only the vite.config.* if present
1016
- // (used by the Remix gate).
1017
- const fileContents = {};
1018
- for (const c of ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']) {
1019
- if (files.has(c)) {
1020
- try {
1021
- fileContents[c] = this.vfs.readFileString(projDir + '/' + c);
1022
- }
1023
- catch { }
1024
- }
1025
- }
1026
- const result = detectFramework({
1027
- pkg: { dependencies: pkg.dependencies, devDependencies: pkg.devDependencies, scripts: pkg.scripts },
1028
- files,
1029
- fileContents,
1030
- });
1031
- return result.framework !== 'unknown';
1032
- }
1033
- catch {
1034
- return false;
1035
- }
1036
- }
1037
1050
  async buildSpecs(projDir, explicitPackages, production) {
1038
1051
  const specs = {};
1052
+ // Which names only a devDependency asked for — diagnostics only, so an
1053
+ // advisory can say whether anything this project RUNS actually needs
1054
+ // the package. A devDependency is still a required root.
1055
+ const devOnly = new Set();
1056
+ // package.json optionalDependencies: the only roots the required
1057
+ // closure does not cover — npm installs them best-effort.
1058
+ const optionalRoots = new Set();
1059
+ // Names the top-level pass already announced — the walk dedupes its
1060
+ // own advisories against it.
1061
+ const advised = new Set();
1039
1062
  if (explicitPackages && explicitPackages.length > 0) {
1040
1063
  // Explicit packages: npm install react react-dom@18.2.0
1041
1064
  for (const pkg of explicitPackages) {
1042
1065
  const parsed = parseExplicitPackageSpec(pkg);
1043
1066
  specs[parsed.name] = parsed.range;
1044
1067
  }
1045
- return this.applyW6Registry(specs);
1068
+ const applied = this.applyW6Registry(specs, new Set(), { advised });
1069
+ return { specs: applied.specs, devOnly, optionalRoots, advised };
1046
1070
  }
1047
1071
  // Read from package.json
1048
1072
  const pkgJsonPath = projDir + '/package.json';
1049
1073
  if (!this.vfs.exists(pkgJsonPath))
1050
- return specs;
1051
- // Which names only a devDependency asked for, so a reject can say whether
1052
- // anything this project RUNS actually needs the package it refused.
1053
- const devOnly = new Set();
1074
+ return { specs, devOnly, optionalRoots, advised };
1054
1075
  try {
1055
1076
  const pkgJson = JSON.parse(this.vfs.readFileString(pkgJsonPath));
1056
- // Always include dependencies
1077
+ // Every declared dependency is a spec. What cannot run here is
1078
+ // named by the advisory pass below; nothing a project declares is
1079
+ // quietly left out of node_modules.
1057
1080
  for (const [name, range] of Object.entries(pkgJson.dependencies || {})) {
1058
- if (!shouldSkipPackage(name)) {
1059
- specs[name] = range;
1060
- }
1081
+ specs[name] = range;
1061
1082
  }
1062
- // Include devDeps unless production mode, skipping build-only
1063
1083
  for (const [name, range] of Object.entries(pkgJson.devDependencies || {})) {
1064
- if (shouldSkipPackage(name) || name in specs)
1065
- continue;
1066
- if (production)
1084
+ if (name in specs || production)
1067
1085
  continue;
1068
1086
  specs[name] = range;
1069
1087
  devOnly.add(name);
1070
1088
  }
1089
+ // npm semantics: an optionalDependencies entry overrides the same
1090
+ // name in dependencies — it is a best-effort root, not required.
1091
+ for (const [name, range] of Object.entries(pkgJson.optionalDependencies || {})) {
1092
+ specs[name] = range;
1093
+ optionalRoots.add(name);
1094
+ }
1071
1095
  }
1072
1096
  catch { /* corrupt package.json */ }
1073
- return this.applyW6Registry(specs, devOnly);
1097
+ const applied = this.applyW6Registry(specs, optionalRoots, { declared: true, devOnly, advised });
1098
+ return { specs: applied.specs, devOnly, optionalRoots, advised };
1074
1099
  }
1075
1100
  /**
1076
1101
  * W6: apply the PACKAGE_ABI_POLICY swap rewrites and reject deny list
1077
- * to a top-level spec map. Emits `[swap]` notices via onProgress; throws
1078
- * a multi-line error on any reject (with `transitive='warn'` rejects
1079
- * also failing at top level — they only soften at depth>0).
1102
+ * to a top-level spec map. Emits `[swap]` notices via onProgress.
1103
+ *
1104
+ * G2: rejects are advisories — a listed package stays in the spec map
1105
+ * and installs like any other (npm parity: it cannot run here, but
1106
+ * install is the wrong place to say so). Each gets one `[npm] note:`
1107
+ * line per install and an `advisory` registry event; the require-side
1108
+ * refusal carries the reason when the artifact is invoked.
1109
+ * `advised` dedupes across the top-level pass and the walk (the
1110
+ * package still resolves and can surface transitively).
1080
1111
  *
1081
1112
  * Idempotent: running on already-swapped specs is a no-op.
1082
1113
  */
1083
- applyW6Registry(specs, devOnly = new Set()) {
1114
+ applyW6Registry(specs, optionalRoots = new Set(), options = {}) {
1084
1115
  const { specs: swapped, swaps } = applySwaps(specs);
1085
1116
  for (const s of swaps) {
1086
1117
  // onProgress is unguarded everywhere else in this file (rg the
@@ -1090,49 +1121,62 @@ export class NpmInstaller {
1090
1121
  // W6.5: telemetry — fire-and-forget; sink swallows its own errors.
1091
1122
  emitRegistryEvent({ type: 'swap', from: s.from, to: s.to, ctx: 'top' });
1092
1123
  }
1093
- const rejects = findRejects(swapped, 'top');
1094
- if (rejects.length > 0) {
1095
- // W6.5: emit one reject event per offending package BEFORE throwing,
1096
- // so the telemetry sink sees them even if the install aborts.
1097
- for (const r of rejects) {
1098
- emitRegistryEvent({
1099
- type: 'reject',
1100
- from: r.from,
1101
- reason: r.reason,
1102
- suggest: r.suggest,
1103
- ctx: 'top',
1104
- });
1105
- }
1106
- throw new RegistryRejectError(rejects, devOnly);
1124
+ for (const r of findRejects(swapped, 'top')) {
1125
+ emitRegistryEvent({ type: 'advisory', from: r.from, reason: r.reason, suggest: r.suggest, ctx: 'top' });
1126
+ if (options.advised?.has(r.from))
1127
+ continue;
1128
+ options.advised?.add(r.from);
1129
+ const hint = r.suggest ? ` … try: ${r.suggest}` : '';
1130
+ const marker = options.devOnly?.has(r.from) ? ' (declared in devDependencies)' : '';
1131
+ this.onProgress?.(`[npm] note: ${r.from} has no Workers-compatible build${marker}: ${r.reason}${hint}`);
1107
1132
  }
1108
- return swapped;
1133
+ return { specs: swapped };
1109
1134
  }
1110
1135
  // ── Lockfile ──────────────────────────────────────────────────────────
1111
1136
  /**
1112
1137
  * Check if a lockfile is still valid against current package.json specs.
1113
1138
  */
1114
1139
  isLockfileValid(lockfile, specs) {
1115
- // Every spec must be in the lockfile
1116
- for (const name of Object.keys(specs)) {
1117
- if (shouldSkipPackage(name))
1140
+ // Every spec must be in the lockfile at a version that still
1141
+ // satisfies the declared range — a package.json edit must never be
1142
+ // answered by yesterday's pin.
1143
+ for (const [name, range] of Object.entries(specs)) {
1144
+ const entry = lockfile.get(name);
1145
+ if (!entry)
1146
+ return false;
1147
+ // An npm: alias answers the INNER range — the same parser the
1148
+ // resolver facet uses for alias specs turns `vliw: npm:react@^19`
1149
+ // into range `^19` checked against the pin under `vliw`. A spec
1150
+ // that is not a semver range at all (git:, github:, URL, file:)
1151
+ // answers presence-only: the pin's record is the lockfile's whole
1152
+ // truth for it.
1153
+ const req = parseRegistryRequest(name, range);
1154
+ if (!isSemverRange(req.range))
1118
1155
  continue;
1119
- if (!lockfile.has(name))
1156
+ if (!satisfiesRange(entry.resolvedVer, req.range))
1120
1157
  return false;
1121
1158
  }
1122
- // X.5-F R2: every locked package's REQUIRED peerDependencies must
1123
- // also be in the lockfile. Lockfiles built before X.5-F lack peer
1124
- // entries; we invalidate them so the next install re-resolves and
1125
- // picks up peers (e.g. radix-react-dialog needs react+react-dom in
1126
- // the tree, ts-jest needs typescript). The peer info comes from
1127
- // the registry cache — if the cache miss happens too, we play it
1128
- // safe and invalidate (forcing a fresh resolve which is correct).
1159
+ // Every locked package's REQUIRED edges — `dependencies` minus the
1160
+ // names its optionalDependencies override, plus required peers —
1161
+ // must also be in the lockfile. Lockfiles built before X.5-F lack
1162
+ // peer entries and lockfiles written after a required refusal lack
1163
+ // the refused name's siblings' targets; invalidating either forces
1164
+ // the next install to re-resolve and re-report the refusal instead
1165
+ // of printing Done! over a tree that is missing packages. The edge
1166
+ // info comes from the registry cache — if the cache miss happens
1167
+ // too, we play it safe and invalidate (a fresh resolve repopulates
1168
+ // it and is always correct).
1129
1169
  for (const [, entry] of lockfile) {
1130
1170
  const cached = this.cache.getRegistryEntry(entry.name, entry.resolvedVer);
1131
- if (!cached) {
1132
- // Registry cache miss for this locked entry — can't verify
1133
- // peers. Invalidate to be safe (next install will repopulate
1134
- // the registry cache while resolving).
1171
+ if (!cached)
1135
1172
  return false;
1173
+ const optional = safeJsonParse(cached.optionalDepsJson || '{}', {});
1174
+ const deps = safeJsonParse(cached.depsJson || '{}', {});
1175
+ for (const depName of Object.keys(deps)) {
1176
+ if (Object.hasOwn(optional, depName))
1177
+ continue;
1178
+ if (!lockfile.has(depName))
1179
+ return false;
1136
1180
  }
1137
1181
  const peers = safeJsonParse(cached.peerDepsJson || '{}', {});
1138
1182
  for (const peerName of Object.keys(peers)) {
@@ -1313,9 +1357,9 @@ export class NpmInstaller {
1313
1357
  //
1314
1358
  // We gate on this.esbuild presence purely as a feature flag: a caller
1315
1359
  // that constructs the installer without esbuild (e.g. minimal
1316
- // headless test) opts out of pre-bundling entirely. ctx and env must
1317
- // also be present because the facet pool needs them.
1318
- if (!this.esbuild || !this.ctx || !this.env)
1360
+ // headless test) opts out of pre-bundling entirely. The session's
1361
+ // bundle pool must also be present because the facets run in it.
1362
+ if (!this.esbuild || !this.bundlePool)
1319
1363
  return;
1320
1364
  const usedSpecifiers = this.scanBareImports(projDir);
1321
1365
  // Vite plugins / postcss plugins / build-time tools NEVER ship to the
@@ -1509,12 +1553,12 @@ export class NpmInstaller {
1509
1553
  // esbuild's WASM linear memory is per-FACET (~30–80 MiB) and lives
1510
1554
  // outside the supervisor. Per-slot try/catch handles failures —
1511
1555
  // /preview/@modules/ on-demand bundling recovers.
1512
- // Fetch the esbuild-wasm bytes from the static-assets layer.
1513
- // The supervisor briefly holds the 12 MiB ArrayBuffer between this
1514
- // line and the LOADER hand-off below; after pool construction
1515
- // returns, the only reference is inside workerd's loader cache
1516
- // (where it should live). No supervisor-side caching — see
1517
- // src/esbuild-wasm-bytes.ts for the full architectural rationale.
1556
+ // The esbuild-wasm bytes ride in the session's shared bundle pool
1557
+ // (facets/esbuild-bundle-pool.ts), constructed once per session and
1558
+ // disposed with the installer and dev server — not per pre-bundle
1559
+ // phase — so the supervisor retains one copy, leased once from the
1560
+ // shared allocation budget. See src/esbuild-wasm-bytes.ts for why
1561
+ // the bytes are never cached supervisor-side beyond that.
1518
1562
  //
1519
1563
  // Bytes are shipped into each facet via IsolatePool's
1520
1564
  // `wasmModules` option which workerd registers in the LOADER
@@ -1540,7 +1584,7 @@ export class NpmInstaller {
1540
1584
  // slots' settled work, and surface in the supervisor as an
1541
1585
  // unhandled rejection. Swallow with a console.error so the
1542
1586
  // pre-bundle phase keeps running. Same pattern is used in the
1543
- // pool dispose finally block below.
1587
+ // summary finally block below.
1544
1588
  const safeProgress = (msg) => {
1545
1589
  try {
1546
1590
  this.onProgress?.(msg);
@@ -1552,42 +1596,18 @@ export class NpmInstaller {
1552
1596
  catch { }
1553
1597
  }
1554
1598
  };
1599
+ // Acquired BEFORE any per-slice lease below: first construction
1600
+ // reserves the full supervisor budget while the wasm bytes are
1601
+ // fetched, so a slice lease taken first would wait on itself.
1555
1602
  let pool;
1556
- let retainedWasmRelease = null;
1557
- const setupAllocation = await acquireSupervisorAllocation(SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES);
1558
1603
  try {
1559
- // No fallback: a missing wasm asset is a deploy bug. Surface loudly via
1560
- // the thrown fetch error so this background phase aborts cleanly.
1561
- const wasmBytes = await fetchEsbuildWasmBytes(this.env);
1562
- const maxRetainedWasmBytes = SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES - PRE_BUNDLE_SLICE_CAP_BYTES;
1563
- if (wasmBytes.byteLength > maxRetainedWasmBytes) {
1564
- throw new RangeError(`pre-bundle wasm payload ${wasmBytes.byteLength} exceeds the ${maxRetainedWasmBytes}-byte retained budget`);
1565
- }
1566
- // IsolatePool keeps the constructor-time module bytes until
1567
- // dispose(), so retain their exact credit rather than treating
1568
- // construction as a handoff that immediately frees the ArrayBuffer.
1569
- setupAllocation.shrinkTo(wasmBytes.byteLength);
1570
- try {
1571
- pool = new IsolatePool(this.env, this.ctx, {
1572
- concurrency: PRE_BUNDLE_CONCURRENCY,
1573
- timeoutMs: 60_000,
1574
- retries: 0,
1575
- tag: 'pre-bundle',
1576
- preamble: PRE_BUNDLE_PREAMBLE,
1577
- wasmModules: { 'esbuild.wasm': wasmBytes },
1578
- });
1579
- retainedWasmRelease = setupAllocation.release;
1580
- }
1581
- catch (e) {
1582
- // Pool construction failures remain best-effort pre-bundle failures,
1583
- // not npm install failures.
1584
- safeProgress(`Pre-bundle skipped: failed to construct facet pool: ${e?.message || e}`);
1585
- return;
1586
- }
1604
+ pool = await this.bundlePool.acquire();
1587
1605
  }
1588
- finally {
1589
- if (!retainedWasmRelease)
1590
- setupAllocation.release();
1606
+ catch (e) {
1607
+ // Pool construction failures remain best-effort pre-bundle failures,
1608
+ // not npm install failures.
1609
+ safeProgress(`Pre-bundle skipped: failed to construct facet pool: ${e?.message || e}`);
1610
+ return;
1591
1611
  }
1592
1612
  const queue = pending.slice(); // copy; will shift
1593
1613
  let okCount = 0;
@@ -1777,10 +1797,11 @@ export class NpmInstaller {
1777
1797
  // failure mode that can throw inside runSlot is caught above
1778
1798
  // (slice walk, externals, pool.submit, putEsmBundle) — but a
1779
1799
  // future regression that adds an unguarded throw to runSlot
1780
- // would bubble out here. The outer try/finally guarantees
1781
- // pool.dispose() runs and the diag counters get updated for
1782
- // whatever partial run completed; the catch below additionally
1783
- // logs so the failure mode is visible in supervisor logs.
1800
+ // would bubble out here. The outer try/finally guarantees the
1801
+ // diag counters get updated for whatever partial run completed;
1802
+ // the pool itself belongs to the session and outlives this phase.
1803
+ // The catch below additionally logs so the failure mode is visible
1804
+ // in supervisor logs.
1784
1805
  await Promise.all(Array.from({ length: PRE_BUNDLE_CONCURRENCY }, (_, i) => runSlot(i)));
1785
1806
  }
1786
1807
  catch (e) {
@@ -1822,11 +1843,6 @@ export class NpmInstaller {
1822
1843
  }
1823
1844
  catch { }
1824
1845
  }
1825
- try {
1826
- pool.dispose();
1827
- }
1828
- catch { /* best-effort */ }
1829
- retainedWasmRelease?.();
1830
1846
  }
1831
1847
  }
1832
1848
  /**