@nimbus-sh/worker 0.9.0 → 0.11.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 (286) hide show
  1. package/NOTICE.md +5 -0
  2. package/README.md +10 -0
  3. package/dist/_shared/session-router.d.ts +7 -0
  4. package/dist/_shared/session-router.d.ts.map +1 -1
  5. package/dist/_shared/session-router.js +7 -0
  6. package/dist/bindings/body.d.ts +6 -0
  7. package/dist/bindings/body.d.ts.map +1 -0
  8. package/dist/bindings/body.js +37 -0
  9. package/dist/bindings/kv.d.ts +0 -2
  10. package/dist/bindings/kv.d.ts.map +1 -1
  11. package/dist/bindings/kv.js +3 -42
  12. package/dist/bindings/r2.d.ts +0 -2
  13. package/dist/bindings/r2.d.ts.map +1 -1
  14. package/dist/bindings/r2.js +4 -44
  15. package/dist/esbuild-cli-artifact.generated.d.ts +18 -0
  16. package/dist/esbuild-cli-artifact.generated.d.ts.map +1 -0
  17. package/dist/esbuild-cli-artifact.generated.js +17 -0
  18. package/dist/esbuild-wasm-bundle.generated.d.ts +24 -24
  19. package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
  20. package/dist/esbuild-wasm-bundle.generated.js +24 -24
  21. package/dist/facets/cirrus-real.d.ts.map +1 -1
  22. package/dist/facets/cirrus-real.js +11 -6
  23. package/dist/facets/compose.d.ts +11 -7
  24. package/dist/facets/compose.d.ts.map +1 -1
  25. package/dist/facets/compose.js +8 -31
  26. package/dist/facets/data-plan.d.ts +79 -0
  27. package/dist/facets/data-plan.d.ts.map +1 -0
  28. package/dist/facets/data-plan.js +477 -0
  29. package/dist/facets/esbuild-bundle-pool.d.ts +1 -1
  30. package/dist/facets/esbuild-bundle-pool.js +3 -3
  31. package/dist/facets/esbuild-transform.d.ts +38 -8
  32. package/dist/facets/esbuild-transform.d.ts.map +1 -1
  33. package/dist/facets/esbuild-transform.js +219 -19
  34. package/dist/facets/exec-telemetry.d.ts +7 -3
  35. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  36. package/dist/facets/manager.d.ts +198 -69
  37. package/dist/facets/manager.d.ts.map +1 -1
  38. package/dist/facets/manager.js +1276 -652
  39. package/dist/facets/opencode-staging.d.ts +0 -2
  40. package/dist/facets/opencode-staging.d.ts.map +1 -1
  41. package/dist/facets/opencode-staging.js +5 -11
  42. package/dist/facets/process.d.ts +4 -0
  43. package/dist/facets/process.d.ts.map +1 -1
  44. package/dist/facets/process.js +3 -0
  45. package/dist/facets/read-profile.d.ts +149 -0
  46. package/dist/facets/read-profile.d.ts.map +1 -0
  47. package/dist/facets/read-profile.js +483 -0
  48. package/dist/facets/real-vite-hmr.d.ts +2 -0
  49. package/dist/facets/real-vite-hmr.d.ts.map +1 -1
  50. package/dist/facets/real-vite-hmr.js +3 -2
  51. package/dist/facets/resident-identity.d.ts +0 -1
  52. package/dist/facets/resident-identity.d.ts.map +1 -1
  53. package/dist/facets/resident-identity.js +0 -3
  54. package/dist/facets/wasm-swap-registry.d.ts +34 -25
  55. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  56. package/dist/facets/wasm-swap-registry.js +62 -56
  57. package/dist/facets/ws-terminal.d.ts +4 -4
  58. package/dist/facets/ws-terminal.d.ts.map +1 -1
  59. package/dist/facets/ws-terminal.js +4 -5
  60. package/dist/git/commands.d.ts +33 -10
  61. package/dist/git/commands.d.ts.map +1 -1
  62. package/dist/git/commands.js +1891 -197
  63. package/dist/git/network-facet.d.ts +12 -2
  64. package/dist/git/network-facet.d.ts.map +1 -1
  65. package/dist/git/network-facet.js +69 -18
  66. package/dist/git/unified-diff.d.ts +74 -0
  67. package/dist/git/unified-diff.d.ts.map +1 -0
  68. package/dist/git/unified-diff.js +676 -0
  69. package/dist/git-bundle.generated.d.ts +7 -11
  70. package/dist/git-bundle.generated.d.ts.map +1 -1
  71. package/dist/git-bundle.generated.js +7 -11
  72. package/dist/hosted/commands.d.ts.map +1 -1
  73. package/dist/hosted/commands.js +64 -245
  74. package/dist/hosted/runtime.d.ts +22 -15
  75. package/dist/hosted/runtime.d.ts.map +1 -1
  76. package/dist/hosted/runtime.js +72 -14
  77. package/dist/hosted/services.d.ts +8 -1
  78. package/dist/hosted/services.d.ts.map +1 -1
  79. package/dist/hosted/services.js +12 -13
  80. package/dist/hosted/session.d.ts +165 -0
  81. package/dist/hosted/session.d.ts.map +1 -0
  82. package/dist/hosted/session.js +132 -0
  83. package/dist/loaders/generated-workers.d.ts +6 -3
  84. package/dist/loaders/generated-workers.d.ts.map +1 -1
  85. package/dist/loaders/generated-workers.js +8 -5
  86. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  87. package/dist/loaders/npm-resolve-preamble.js +0 -27
  88. package/dist/loaders/pre-bundle-preamble.d.ts +6 -3
  89. package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -1
  90. package/dist/loaders/pre-bundle-preamble.js +13 -8
  91. package/dist/node-shims-artifact.generated.d.ts +13 -4
  92. package/dist/node-shims-artifact.generated.d.ts.map +1 -1
  93. package/dist/node-shims-artifact.generated.js +16 -7
  94. package/dist/npm/bin-links.d.ts +3 -1
  95. package/dist/npm/bin-links.d.ts.map +1 -1
  96. package/dist/npm/bin-links.js +32 -8
  97. package/dist/npm/cache.d.ts +11 -1
  98. package/dist/npm/cache.d.ts.map +1 -1
  99. package/dist/npm/cache.js +21 -7
  100. package/dist/npm/install-batch-facet.d.ts +4 -2
  101. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  102. package/dist/npm/install-batch-facet.js +32 -18
  103. package/dist/npm/installer.d.ts +23 -3
  104. package/dist/npm/installer.d.ts.map +1 -1
  105. package/dist/npm/installer.js +330 -131
  106. package/dist/npm/npx-install.d.ts +3 -1
  107. package/dist/npm/npx-install.d.ts.map +1 -1
  108. package/dist/npm/npx-install.js +4 -1
  109. package/dist/npm/package-lock.d.ts +32 -0
  110. package/dist/npm/package-lock.d.ts.map +1 -0
  111. package/dist/npm/package-lock.js +65 -0
  112. package/dist/npm/placement.d.ts +16 -0
  113. package/dist/npm/placement.d.ts.map +1 -0
  114. package/dist/npm/placement.js +33 -0
  115. package/dist/npm/pre-bundle-facet.d.ts +1 -30
  116. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  117. package/dist/npm/pre-bundle-facet.js +3 -5
  118. package/dist/npm/r2-cache.d.ts +12 -6
  119. package/dist/npm/r2-cache.d.ts.map +1 -1
  120. package/dist/npm/r2-cache.js +34 -16
  121. package/dist/npm/resolve-one-facet.d.ts +3 -0
  122. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  123. package/dist/npm/resolve-one-facet.js +15 -3
  124. package/dist/npm/resolver.d.ts +11 -13
  125. package/dist/npm/resolver.d.ts.map +1 -1
  126. package/dist/npm/resolver.js +12 -25
  127. package/dist/opencode-artifact.generated.js +2 -2
  128. package/dist/replica/routing.d.ts +2 -38
  129. package/dist/replica/routing.d.ts.map +1 -1
  130. package/dist/replica/routing.js +3 -55
  131. package/dist/router/index.d.ts.map +1 -1
  132. package/dist/router/index.js +15 -2
  133. package/dist/router/public-directory.d.ts +18 -0
  134. package/dist/router/public-directory.d.ts.map +1 -1
  135. package/dist/router/public-directory.js +1 -1
  136. package/dist/router/remote-api.d.ts +3 -1
  137. package/dist/router/remote-api.d.ts.map +1 -1
  138. package/dist/router/remote-api.js +42 -79
  139. package/dist/runtime/bash-repl.js +2 -2
  140. package/dist/runtime/bun-repl.d.ts.map +1 -1
  141. package/dist/runtime/bun-repl.js +2 -13
  142. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  143. package/dist/runtime/cpython-resident.js +2 -10
  144. package/dist/runtime/esbuild-wasm-bytes.d.ts +37 -18
  145. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
  146. package/dist/runtime/esbuild-wasm-bytes.js +66 -92
  147. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  148. package/dist/runtime/facet-loader-host.js +0 -7
  149. package/dist/runtime/git-bundle-artifact.d.ts +17 -0
  150. package/dist/runtime/git-bundle-artifact.d.ts.map +1 -0
  151. package/dist/runtime/git-bundle-artifact.js +25 -0
  152. package/dist/runtime/node-repl.d.ts.map +1 -1
  153. package/dist/runtime/node-repl.js +2 -13
  154. package/dist/runtime/node-shims-artifact.d.ts +22 -23
  155. package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
  156. package/dist/runtime/node-shims-artifact.js +48 -78
  157. package/dist/runtime/node-shims.d.ts.map +1 -1
  158. package/dist/runtime/node-shims.js +2358 -996
  159. package/dist/runtime/opencode-artifact.d.ts.map +1 -1
  160. package/dist/runtime/opencode-artifact.js +17 -53
  161. package/dist/runtime/opencode-facet-runner.d.ts +3 -6
  162. package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
  163. package/dist/runtime/opencode-facet-runner.js +45 -10
  164. package/dist/runtime/opentui-wasm-bytes.d.ts.map +1 -1
  165. package/dist/runtime/opentui-wasm-bytes.js +16 -49
  166. package/dist/runtime/package-manager.d.ts +0 -2
  167. package/dist/runtime/package-manager.d.ts.map +1 -1
  168. package/dist/runtime/package-manager.js +0 -3
  169. package/dist/runtime/python-repl.d.ts.map +1 -1
  170. package/dist/runtime/python-repl.js +4 -35
  171. package/dist/runtime/repl-session.d.ts +10 -0
  172. package/dist/runtime/repl-session.d.ts.map +1 -1
  173. package/dist/runtime/repl-session.js +7 -0
  174. package/dist/runtime/ruby-repl.d.ts.map +1 -1
  175. package/dist/runtime/ruby-repl.js +2 -2
  176. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  177. package/dist/runtime/ruby-resident.js +41 -51
  178. package/dist/runtime/runtime-catalog.d.ts +10 -3
  179. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  180. package/dist/runtime/runtime-catalog.js +125 -15
  181. package/dist/runtime/sqlite-wasm-bytes.d.ts +4 -4
  182. package/dist/runtime/sqlite-wasm-bytes.d.ts.map +1 -1
  183. package/dist/runtime/sqlite-wasm-bytes.js +21 -57
  184. package/dist/runtime/staged-source.d.ts +67 -0
  185. package/dist/runtime/staged-source.d.ts.map +1 -0
  186. package/dist/runtime/staged-source.js +113 -0
  187. package/dist/runtime/wasi/preamble.d.ts +0 -22
  188. package/dist/runtime/wasi/preamble.d.ts.map +1 -1
  189. package/dist/runtime/wasi/preamble.js +98 -1425
  190. package/dist/runtime-catalog.generated.js +1 -1
  191. package/dist/session/agent.d.ts +14 -2
  192. package/dist/session/agent.d.ts.map +1 -1
  193. package/dist/session/agent.js +57 -19
  194. package/dist/session/ai.d.ts +0 -11
  195. package/dist/session/ai.d.ts.map +1 -1
  196. package/dist/session/ai.js +0 -4
  197. package/dist/session/fs-watch.d.ts +0 -11
  198. package/dist/session/fs-watch.d.ts.map +1 -1
  199. package/dist/session/fs-watch.js +0 -21
  200. package/dist/session/hibernation.d.ts +68 -2
  201. package/dist/session/hibernation.d.ts.map +1 -1
  202. package/dist/session/hibernation.js +95 -1
  203. package/dist/session/init-phases.d.ts +1 -2
  204. package/dist/session/init-phases.d.ts.map +1 -1
  205. package/dist/session/init-phases.js +1 -2
  206. package/dist/session/init.d.ts.map +1 -1
  207. package/dist/session/init.js +7 -20
  208. package/dist/session/keys.d.ts +0 -3
  209. package/dist/session/keys.d.ts.map +1 -1
  210. package/dist/session/keys.js +0 -3
  211. package/dist/session/legacy-reset.d.ts +11 -0
  212. package/dist/session/legacy-reset.d.ts.map +1 -0
  213. package/dist/session/legacy-reset.js +16 -0
  214. package/dist/session/nimbus-session.d.ts +50 -16
  215. package/dist/session/nimbus-session.d.ts.map +1 -1
  216. package/dist/session/nimbus-session.js +77 -71
  217. package/dist/session/npm-install-port.d.ts.map +1 -1
  218. package/dist/session/npm-install-port.js +2 -0
  219. package/dist/session/programmatic.d.ts +13 -18
  220. package/dist/session/programmatic.d.ts.map +1 -1
  221. package/dist/session/programmatic.js +53 -39
  222. package/dist/session/replica-routes.d.ts +2 -3
  223. package/dist/session/replica-routes.d.ts.map +1 -1
  224. package/dist/session/replica-routes.js +3 -10
  225. package/dist/session/routes.d.ts.map +1 -1
  226. package/dist/session/routes.js +11 -12
  227. package/dist/session/rpc.d.ts +108 -21
  228. package/dist/session/rpc.d.ts.map +1 -1
  229. package/dist/session/rpc.js +194 -39
  230. package/dist/session/start-real-vite.js +2 -2
  231. package/dist/session/state-store.d.ts +1 -17
  232. package/dist/session/state-store.d.ts.map +1 -1
  233. package/dist/session/state-store.js +3 -53
  234. package/dist/session/supervisor-op.d.ts +7 -0
  235. package/dist/session/supervisor-op.d.ts.map +1 -1
  236. package/dist/session/supervisor-op.js +6 -1
  237. package/dist/session/supervisor-rpc.d.ts +116 -19
  238. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  239. package/dist/session/supervisor-rpc.js +194 -84
  240. package/dist/session/vite-command.d.ts.map +1 -1
  241. package/dist/session/vite-command.js +17 -14
  242. package/dist/session/ws.d.ts.map +1 -1
  243. package/dist/session/ws.js +20 -8
  244. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  245. package/dist/shell/npm-bin-entrypoints.js +30 -5
  246. package/dist/vfs/facet-resident-limits.d.ts +33 -0
  247. package/dist/vfs/facet-resident-limits.d.ts.map +1 -0
  248. package/dist/vfs/facet-resident-limits.js +33 -0
  249. package/dist/vfs/facet-resident-store.d.ts +2 -10
  250. package/dist/vfs/facet-resident-store.d.ts.map +1 -1
  251. package/dist/vfs/facet-resident-store.js +1594 -219
  252. package/dist/workspace-host.d.ts +3 -1
  253. package/dist/workspace-host.d.ts.map +1 -1
  254. package/dist/workspace-host.js +2 -0
  255. package/dist/wrangler/nimbus-wrangler.d.ts +0 -2
  256. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
  257. package/dist/wrangler/nimbus-wrangler.js +8 -22
  258. package/package.json +34 -7
  259. package/public/_assets/esbuild-0.24.2.js +2380 -0
  260. package/public/_assets/opencode/1.16.2/index-attach.js +2 -2
  261. package/public/_assets/runtime/esbuild-cli-c4fda380d52befc1.js +6892 -0
  262. package/public/_assets/runtime/git-de3e60c9f3ff006a.js +103 -0
  263. package/public/_assets/runtime/{node-shims-acaa037e6d26ec0d.js → node-shims-06565b121555c194.js} +2879 -992
  264. package/public/_assets/runtime/resident-store-6ffb1939c53d982f.js +2264 -0
  265. package/public/_assets/runtime/vfs-write-ledger-e501e8fc793bc2c1.js +889 -0
  266. package/scripts/build-opencode-attach-entry.mjs +51 -1
  267. package/scripts/bundle-esbuild-wasm.mjs +55 -39
  268. package/scripts/bundle-facet-workers.mjs +159 -10
  269. package/scripts/bundle-git.mjs +88 -27
  270. package/scripts/bundle-node-shims.mjs +72 -46
  271. package/scripts/bundle-runtime.mjs +18 -421
  272. package/scripts/cf-git-patch.mjs +137 -0
  273. package/scripts/patch-install-deps.mjs +49 -96
  274. package/scripts/runtime-specs.mjs +435 -0
  275. package/vendor/git-http.generated.d.ts +82 -0
  276. package/vendor/git-upstream.generated.d.ts +4378 -0
  277. package/vendor/git.LICENSE +7 -0
  278. package/vendor/git.generated.d.mts +21 -0
  279. package/vendor/git.generated.mjs +103 -0
  280. package/dist/replica/suspension.d.ts +0 -36
  281. package/dist/replica/suspension.d.ts.map +0 -1
  282. package/dist/replica/suspension.js +0 -49
  283. package/dist/runtime/static-server.d.ts +0 -3
  284. package/dist/runtime/static-server.d.ts.map +0 -1
  285. package/dist/runtime/static-server.js +0 -133
  286. package/scripts/clean-dist.mjs +0 -7
@@ -13,7 +13,7 @@ import { z } from 'zod';
13
13
  import { buildRubyPreamble, normalizeRubyFacetResult, } from '@nimbus-sh/core/runtime/ruby-runner.js';
14
14
  import { VIRTUAL_SOCKET_KERNEL_SRC } from '@nimbus-sh/core/runtime/virtual-socket-kernel.generated.js';
15
15
  const RubySocketProcessBootResponseSchema = z.object({
16
- state: z.string().optional(),
16
+ state: z.enum(['listening', 'exited']),
17
17
  port: z.number().optional(),
18
18
  stdout: z.string().optional(),
19
19
  stderr: z.string().optional(),
@@ -35,7 +35,11 @@ export function rubyResidentStart(facetMgr) {
35
35
  // value may carry — so whichever host runs this process reads it itself.
36
36
  vfsWasmModules: { 'ruby+stdlib.wasm': args.wasmVfsPath },
37
37
  startArgs: args.startArgs,
38
+ foreground: { signal: args.signal, write: args.write },
38
39
  }).catch(() => null);
40
+ // Interrupted while it waited to bind or exit: the process is gone with it.
41
+ if (args.signal.aborted)
42
+ return { exitCode: 130, stdout: '', stderr: '' };
39
43
  if (!spawned) {
40
44
  return { exitCode: 1, stdout: '', stderr: 'ruby process boot failed\n' };
41
45
  }
@@ -69,34 +73,16 @@ export function rubyResidentStart(facetMgr) {
69
73
  port: routeablePort,
70
74
  };
71
75
  }
72
- const reservedPorts = await facetMgr.waitForRouteablePorts(spawned.pid);
73
- if (reservedPorts.length > 0) {
74
- return {
75
- exitCode: 0,
76
- stdout: `${boot.stdout || ''}\x1b[2m[started (long-running): pid=${spawned.pid} cmd="${command}" port=${reservedPorts[0]}]\x1b[0m\n`,
77
- stderr: boot.stderr || '',
78
- spawnedPid: spawned.pid,
79
- port: reservedPorts[0],
80
- };
81
- }
82
- if (boot.state === 'exited') {
83
- const result = normalizeRubyFacetResult(boot.result) || {
84
- exitCode: 1,
85
- stdout: '',
86
- stderr: 'ruby process returned an invalid exit payload\n',
87
- };
88
- facetMgr.finishProcess(spawned.pid, result.exitCode, result.stderr || 'ruby process exited');
89
- return {
90
- exitCode: result.exitCode,
91
- stdout: result.stdout,
92
- stderr: result.stderr || result.error || '',
93
- };
94
- }
76
+ const result = normalizeRubyFacetResult(boot.result) || {
77
+ exitCode: 1,
78
+ stdout: '',
79
+ stderr: 'ruby process returned an invalid exit payload\n',
80
+ };
81
+ facetMgr.finishProcess(spawned.pid, result.exitCode, result.stderr || 'ruby process exited');
95
82
  return {
96
- exitCode: 0,
97
- stdout: `${boot.stdout || ''}\x1b[2m[started (long-running): pid=${spawned.pid} cmd="${command}"]\x1b[0m\n`,
98
- stderr: boot.stderr || '',
99
- spawnedPid: spawned.pid,
83
+ exitCode: result.exitCode,
84
+ stdout: result.stdout,
85
+ stderr: result.stderr || result.error || '',
100
86
  };
101
87
  };
102
88
  }
@@ -205,6 +191,19 @@ export function buildRubySocketProcessWorker(preamble) {
205
191
  ' return true;',
206
192
  '};',
207
193
  '',
194
+ // What the process writes goes out as it is written: to the command that
195
+ // launched it while that command waits, to the process log after. One
196
+ // write at a time, in order; the runner's own markers are not output.
197
+ 'globalThis.__nimbusRubyEmitChain = globalThis.__nimbusRubyEmitChain || Promise.resolve();',
198
+ 'globalThis.__nimbusRubyEmit = function __nimbusRubyEmit(stream, text) {',
199
+ ' const supervisor = globalThis.__nimbusRubySupervisor;',
200
+ ' if (!supervisor) return;',
201
+ ' const clean = String(text).replace(/__NIMBUS_RESUMED_(true|false)_[^\\n]*\\n?/g, "").replace(/__NIMBUS_RUBY_EXIT_-?\\d+\\n?/g, "");',
202
+ ' if (!clean) return;',
203
+ ' const bytes = new TextEncoder().encode(clean);',
204
+ ' globalThis.__nimbusRubyEmitChain = globalThis.__nimbusRubyEmitChain.then(() => supervisor[stream](bytes)).catch(() => {});',
205
+ '};',
206
+ '',
208
207
  'async function __nimbusStartRubyProcess(args) {',
209
208
  ' if (!globalThis.__nimbusRubyProcessPromise) {',
210
209
  ' const stdoutStart = (globalThis.__nimbusRubyStdout || []).length;',
@@ -216,7 +215,6 @@ export function buildRubySocketProcessWorker(preamble) {
216
215
  ' userEnv: args.userEnv || {},',
217
216
  ' progName: args.progName || "ruby",',
218
217
  ' cwd: args.cwd || "/home/user",',
219
- ' fsSnapshot: args.fsSnapshot,',
220
218
  ' };',
221
219
  ' globalThis.__nimbusRubyProcessPromise = globalThis.__rubyRun(globalThis.__nimbusRubyProcessArgs).then((result) => {',
222
220
  ' globalThis.__nimbusRubyProcessResult = result;',
@@ -224,18 +222,20 @@ export function buildRubySocketProcessWorker(preamble) {
224
222
  ' });',
225
223
  ' }',
226
224
  ' const started = globalThis.__nimbusRubyProcessOutputStart || { stdoutStart: 0, stderrStart: 0 };',
227
- ' const listen = globalThis.__nimbusVirtualSockets.waitForListen(10_000).then((port) => ({ state: port ? "listening" : "pending", port }));',
225
+ // No deadline: a server reports once it binds, however long its requires take.
226
+ ' const listen = globalThis.__nimbusVirtualSockets.waitForListen().then((port) => ({ state: "listening", port }));',
228
227
  ' const exit = globalThis.__nimbusRubyProcessPromise.then((result) => ({ state: "exited", result }));',
229
228
  ' const first = await Promise.race([listen, exit]);',
230
229
  ' const registrations = globalThis.__nimbusVirtualPortRegistrationPromises || [];',
231
230
  ' if (registrations.length > 0) await Promise.allSettled(registrations.splice(0));',
232
- ' const stdout = (globalThis.__nimbusRubyStdout || []).slice(started.stdoutStart).join("");',
233
- ' const stderr = (globalThis.__nimbusRubyStderr || []).slice(started.stderrStart).join("");',
231
+ // Streamed already, so the answer carries none of it.
232
+ ' await globalThis.__nimbusRubyEmitChain;',
233
+ ' const streamed = !!globalThis.__nimbusRubySupervisor;',
234
+ ' const stdout = streamed ? "" : (globalThis.__nimbusRubyStdout || []).slice(started.stdoutStart).join("");',
235
+ ' const stderr = streamed ? "" : (globalThis.__nimbusRubyStderr || []).slice(started.stderrStart).join("");',
234
236
  ' if (first.state === "listening") return { state: "listening", port: first.port, stdout, stderr };',
235
- ' if (first.state === "exited") return { state: "exited", result: first.result, stdout, stderr };',
236
- ' const currentPort = globalThis.__nimbusVirtualSockets.firstListeningPort();',
237
- ' if (currentPort) return { state: "listening", port: currentPort, stdout, stderr };',
238
- ' return { state: "running", stdout, stderr };',
237
+ ' const result = streamed ? { ...first.result, stdout: "", stderr: "" } : first.result;',
238
+ ' return { state: "exited", result, stdout, stderr };',
239
239
  '}',
240
240
  '',
241
241
  // Only adopt a real binding: routed handleHttpRequest/fetch hops resolve
@@ -244,25 +244,15 @@ export function buildRubySocketProcessWorker(preamble) {
244
244
  'function __nimbusAdoptRubySupervisor(env) {',
245
245
  ' const supervisor = env && env.SUPERVISOR;',
246
246
  ' if (supervisor) globalThis.__nimbusRubySupervisor = supervisor;',
247
- // The WASI filesystem takes the same stub. That is what turns the
248
- // spawn-time seed into a cache over the session VFS, so a server that
249
- // never exits still persists its writes instead of losing them entirely.
247
+ // The WASI filesystem takes the same stub: every file syscall the server
248
+ // makes reads and writes the session VFS directly, so a server that never
249
+ // exits has nothing to flush between requests.
250
250
  ' __wasiAdoptSupervisor(supervisor);',
251
251
  '}',
252
- // A resident process parks between requests, and parking is the only
253
- // moment "durable while running" can be made true: by the time the caller
254
- // holds a response, everything the request wrote has reached the VFS.
255
- 'async function __nimbusParkRuby(value) {',
256
- // Revalidate drains first, then spends ONE round trip on the subtree
257
- // revision. An unchanged subtree keeps the whole cache; a changed one
258
- // drops the clean half so the next read sees another process's writes.
259
- ' await __wasiRevalidateFS();',
260
- ' return value;',
261
- '}',
262
252
  'export class NimbusProcess extends DurableObject {',
263
253
  ' async startProcess(args) {',
264
254
  ' __nimbusAdoptRubySupervisor(this.env);',
265
- ' return __nimbusParkRuby(await __nimbusStartRubyProcess(args || {}));',
255
+ ' return __nimbusStartRubyProcess(args || {});',
266
256
  ' }',
267
257
  ' async fetch(request) {',
268
258
  ' __nimbusAdoptRubySupervisor(this.env);',
@@ -273,7 +263,7 @@ export function buildRubySocketProcessWorker(preamble) {
273
263
  ' const hinted = Number(request.headers.get("X-Nimbus-Port") || 0);',
274
264
  ' const port = hinted || Array.from(globalThis.__nimbusVirtualSockets.listeners.keys())[0];',
275
265
  ' if (!port) return new Response("Nimbus Ruby process has no listening virtual socket", { status: 502 });',
276
- ' return __nimbusParkRuby(await globalThis.__nimbusVirtualSockets.handleHttpRequest(port, request));',
266
+ ' return globalThis.__nimbusVirtualSockets.handleHttpRequest(port, request);',
277
267
  ' }',
278
268
  '}',
279
269
  ].join('\n');
@@ -53,6 +53,7 @@ type R2BucketLike = {
53
53
  get(key: string): Promise<{
54
54
  arrayBuffer(): Promise<ArrayBuffer>;
55
55
  text(): Promise<string>;
56
+ readonly body: ReadableStream<Uint8Array>;
56
57
  } | null>;
57
58
  } | null | undefined;
58
59
  /** Minimal env shape this module consumes. */
@@ -95,15 +96,21 @@ export declare function fetchCatalog(env: RuntimeCatalogEnv): Promise<RuntimeCat
95
96
  */
96
97
  export declare function fetchManifest(env: RuntimeCatalogEnv, entry: CatalogVersionEntry): Promise<RuntimeManifest>;
97
98
  /**
98
- * Fetch the blob a manifest file entry points at, verified against the
99
- * digest that same entry carries.
99
+ * Stream the blob a manifest file entry points at, verified against the
100
+ * digest that same entry carries: from the colo cache when it has it, else
101
+ * from R2, read once either way. The stream errors at its end, rather than
102
+ * closing, with a {@link RuntimeBlobDigestMismatch} when its bytes do not
103
+ * hash to the digest; a colo-cache entry that fails is evicted and
104
+ * distrusted first, so the installer's second read of that blob comes from
105
+ * R2 whatever the cache holds by then. The installer commits
106
+ * a blob only after that clean close, so no step holds a blob whole.
100
107
  *
101
108
  * The digest is not optional and does not travel separately from the key:
102
109
  * a `ManifestFile` always has both, and it is the only thing this takes.
103
110
  * Blobs are interpreters, so an unverified read here is arbitrary code
104
111
  * execution in whichever session installs it.
105
112
  */
106
- export declare function fetchBlob(env: RuntimeCatalogEnv, file: ManifestFile): Promise<Uint8Array>;
113
+ export declare function fetchBlob(env: RuntimeCatalogEnv, file: ManifestFile): Promise<ReadableStream<Uint8Array>>;
107
114
  export declare function runtimeAbiForCatalogName(name: string): RuntimePackageAbi;
108
115
  /**
109
116
  * The R2 catalog as a core `RuntimeSource` — the worker adapter half of the
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-catalog.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAKH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,yCAAyC,CAAC;AAEjD,2CAA2C;AAC3C,KAAK,YAAY,GAAG;IAClB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QACxB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;KACzB,GAAG,IAAI,CAAC,CAAC;CACX,GAAG,IAAI,GAAG,SAAS,CAAC;AAErB,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,CAAC,EAAE,YAAY,CAAC;CACrC;AAID,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB;2EACuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAiBD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAElE;AA4DD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAuBlF;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,eAAe,CAAC,CA8B1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,iBAAiB,EACtB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAID,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAExE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,iBAAiB,GAAG,aAAa,CAwE1E"}
1
+ {"version":3,"file":"runtime-catalog.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAKH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,6CAA6C,CAAC;AAErD,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,yCAAyC,CAAC;AAEjD,2CAA2C;AAC3C,KAAK,YAAY,GAAG;IAClB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QACxB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;QACpC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;KAC3C,GAAG,IAAI,CAAC,CAAC;CACX,GAAG,IAAI,GAAG,SAAS,CAAC;AAErB,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,CAAC,EAAE,YAAY,CAAC;CACrC;AAID,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB;2EACuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/C;AAiBD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAElE;AA4DD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAuBlF;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,eAAe,CAAC,CA8B1B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,iBAAiB,EACtB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CA8BrC;AA2DD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAExE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,iBAAiB,GAAG,aAAa,CAwE1E"}
@@ -46,11 +46,11 @@
46
46
  * makes a stale pin a cache miss rather than an outage.
47
47
  */
48
48
  import { z } from 'zod/v4';
49
- import { sha256Hex } from '@nimbus-sh/core/_shared/crypto.js';
49
+ import { sha256Hex, sha256Incremental } from '@nimbus-sh/core/_shared/crypto.js';
50
50
  import { RUNTIME_CATALOG_SHA256 } from '../runtime-catalog.generated.js';
51
51
  import { HexSha256Schema, parseRuntimeManifest, } from '@nimbus-sh/core/runtime/runtime-manifest.js';
52
52
  import { runtimeEntrypoints } from '@nimbus-sh/core/runtime/installed-runtimes.js';
53
- import { splitRuntimeSpec, SUPERSEDED_RUNTIMES, } from '@nimbus-sh/core/runtime/runtime-package.js';
53
+ import { blobPieces, RuntimeBlobDigestMismatch, splitRuntimeSpec, SUPERSEDED_RUNTIMES, } from '@nimbus-sh/core/runtime/runtime-package.js';
54
54
  import { NIMBUS_RUNTIME_ABIS, NATIVE_UNSUPPORTED_ABI, } from '@nimbus-sh/core/runtime/os-contracts.js';
55
55
  const CatalogVersionEntrySchema = z.object({
56
56
  manifest: z.string().min(1),
@@ -162,8 +162,14 @@ export async function fetchManifest(env, entry) {
162
162
  return parseRuntimeManifest(parseJsonBytes(bytes));
163
163
  }
164
164
  /**
165
- * Fetch the blob a manifest file entry points at, verified against the
166
- * digest that same entry carries.
165
+ * Stream the blob a manifest file entry points at, verified against the
166
+ * digest that same entry carries: from the colo cache when it has it, else
167
+ * from R2, read once either way. The stream errors at its end, rather than
168
+ * closing, with a {@link RuntimeBlobDigestMismatch} when its bytes do not
169
+ * hash to the digest; a colo-cache entry that fails is evicted and
170
+ * distrusted first, so the installer's second read of that blob comes from
171
+ * R2 whatever the cache holds by then. The installer commits
172
+ * a blob only after that clean close, so no step holds a blob whole.
167
173
  *
168
174
  * The digest is not optional and does not travel separately from the key:
169
175
  * a `ManifestFile` always has both, and it is the only thing this takes.
@@ -175,9 +181,15 @@ export async function fetchBlob(env, file) {
175
181
  if (!address) {
176
182
  throw new Error(`manifest entry ${file.path} has no usable sha256 (${file.sha256})`);
177
183
  }
178
- const cached = await l2Get(address);
179
- if (cached)
180
- return cached;
184
+ const cached = l2Distrusted.has(address.sha256) ? null : await l2GetBody(address);
185
+ if (cached) {
186
+ return verifiedRead(address, cached, null, async (actual) => {
187
+ l2Distrusted.add(address.sha256);
188
+ await l2Evict(address);
189
+ return `sha256 mismatch for blob ${file.content}: manifest expects ${address.sha256}, `
190
+ + `the colo cache held ${actual} and was evicted`;
191
+ });
192
+ }
181
193
  const r2 = env.NIMBUS_RUNTIME_CACHE;
182
194
  if (!r2) {
183
195
  throw new Error('NIMBUS_RUNTIME_CACHE binding missing — blob cannot be fetched');
@@ -186,14 +198,59 @@ export async function fetchBlob(env, file) {
186
198
  if (!obj) {
187
199
  throw new Error(`blob ${file.content} not in R2 — manifest references a missing blob`);
188
200
  }
189
- const bytes = new Uint8Array(await obj.arrayBuffer());
190
- const verified = await verifyBytes(address, bytes);
191
- if (!verified) {
192
- throw new Error(`sha256 mismatch for blob ${file.content}: manifest expects ${address.sha256}, ` +
193
- `R2 holds ${await sha256Hex(bytes)}`);
194
- }
195
- await l2Put(verified, 'application/octet-stream');
196
- return verified.bytes;
201
+ return verifiedRead(address, obj.body, l2FillWriter(address), (actual) => `sha256 mismatch for blob ${file.content}: manifest expects ${address.sha256}, R2 holds ${actual}`);
202
+ }
203
+ /**
204
+ * `body` as a stream that errors at its end, with a
205
+ * {@link RuntimeBlobDigestMismatch}, rather than closing, unless it hashed to
206
+ * `address`. It is read once. When `fill` is given, the same read feeds L2:
207
+ * each piece reaches the cache before the consumer, and the cache entry is
208
+ * closed only after the digest matched; a mismatch, a failed read or an
209
+ * abandoned stream aborts it, and an aborted body stores nothing. The
210
+ * cache's own pace bounds the pair, so neither side buffers what the other
211
+ * lags by.
212
+ */
213
+ function verifiedRead(address, body, fill, mismatch) {
214
+ const pieces = blobPieces(body);
215
+ const digest = sha256Incremental();
216
+ const drop = (reason) => {
217
+ fill?.abort(reason).catch(() => { });
218
+ fill = null;
219
+ };
220
+ return new ReadableStream({
221
+ async pull(controller) {
222
+ try {
223
+ const next = await pieces.next();
224
+ if (next.done) {
225
+ const actual = await digest.hex();
226
+ if (actual !== address.sha256)
227
+ throw new RuntimeBlobDigestMismatch(await mismatch(actual));
228
+ fill?.close().catch(() => { });
229
+ controller.close();
230
+ return;
231
+ }
232
+ await digest.update(next.value);
233
+ if (fill) {
234
+ try {
235
+ await fill.ready;
236
+ fill.write(next.value).catch(() => { });
237
+ }
238
+ catch (error) {
239
+ drop(error);
240
+ }
241
+ }
242
+ controller.enqueue(next.value);
243
+ }
244
+ catch (error) {
245
+ drop(error);
246
+ throw error;
247
+ }
248
+ },
249
+ async cancel(reason) {
250
+ drop(reason);
251
+ await pieces.return(undefined);
252
+ },
253
+ }, { highWaterMark: 0 });
197
254
  }
198
255
  // ── RuntimeSource ────────────────────────────────────────────────────
199
256
  export function runtimeAbiForCatalogName(name) {
@@ -314,6 +371,59 @@ async function l2Put(verified, contentType) {
314
371
  }
315
372
  catch { /* best-effort */ }
316
373
  }
374
+ /**
375
+ * The body of the blob entry at `address`, or null on a miss or a stripped
376
+ * Cache API. Unverified: served only through {@link verifiedRead}, whose
377
+ * mismatch evicts it and sends later reads of that blob to R2. Hashing an entry before serving it would read every
378
+ * blob from the cache twice, which was most of an install's time.
379
+ */
380
+ async function l2GetBody(address) {
381
+ try {
382
+ const caches = globalThis.caches;
383
+ if (!caches?.default)
384
+ return null;
385
+ const hit = await caches.default.match(new Request(l2Url(address)));
386
+ return hit?.ok && hit.body ? hit.body : null;
387
+ }
388
+ catch {
389
+ return null;
390
+ }
391
+ }
392
+ /**
393
+ * Blob digests whose colo-cache entry failed verification in this isolate.
394
+ * Their reads go to R2 from then on: a purge lands some time after it is
395
+ * asked for, and a shared cache can be poisoned again, so the installer's
396
+ * second read of a failed blob must not depend on either.
397
+ */
398
+ const l2Distrusted = new Set();
399
+ /** Drop the entry at `address` from the colo cache for other isolates. */
400
+ async function l2Evict(address) {
401
+ try {
402
+ await globalThis.caches?.default?.delete(new Request(l2Url(address)));
403
+ }
404
+ catch { /* best-effort */ }
405
+ }
406
+ /** A writer whose bytes become the L2 entry at `address` when it closes;
407
+ * aborting it stores nothing. Null where there is no cache. */
408
+ function l2FillWriter(address) {
409
+ try {
410
+ const caches = globalThis.caches;
411
+ if (!caches?.default)
412
+ return null;
413
+ const { readable, writable } = new TransformStream();
414
+ const resp = new Response(readable, {
415
+ headers: {
416
+ 'Content-Type': 'application/octet-stream',
417
+ 'Cache-Control': 'public, max-age=31536000, immutable',
418
+ },
419
+ });
420
+ caches.default.put(new Request(l2Url(address)), resp).catch(() => { });
421
+ return writable.getWriter();
422
+ }
423
+ catch {
424
+ return null;
425
+ }
426
+ }
317
427
  function l2Url(address) {
318
428
  return `${L2_NS}/${address.scope}/${address.sha256}`;
319
429
  }
@@ -10,10 +10,10 @@
10
10
  * via its `instantiateWasm` hook with that pre-compiled module —
11
11
  * request-time WebAssembly.compile(bytes) is blocked inside facets.
12
12
  *
13
- * Mirrors esbuild-wasm-bytes.ts: NO module-scope cache (no supervisor
14
- * residency), L2 colo cache via caches.default keyed by the version-pinned
15
- * asset URL, ASSETS as the source of truth, and a sha-256 integrity check on
16
- * both tiers so a stale or tampered asset never gets compiled.
13
+ * NO module-scope cache (no supervisor residency). The read itself is
14
+ * runtime/staged-source.ts: L2 keyed by the version-pinned asset URL, ASSETS
15
+ * as the source of truth, and a sha-256 check on both tiers so a stale or
16
+ * tampered asset never gets compiled.
17
17
  */
18
18
  /**
19
19
  * Path inside env.ASSETS where the sql.js wasm binary lives. Versioned so
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-wasm-bytes.d.ts","sourceRoot":"","sources":["../../src/runtime/sqlite-wasm-bytes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,+BAAyC,CAAC;AAE7E,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;CACpD;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,2DAAqE,CAAC;AAErG;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAwDxF"}
1
+ {"version":3,"file":"sqlite-wasm-bytes.d.ts","sourceRoot":"","sources":["../../src/runtime/sqlite-wasm-bytes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,+BAAyC,CAAC;AAE7E,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;CACpD;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,2DAAqE,CAAC;AAErG;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAElF"}
@@ -10,15 +10,14 @@
10
10
  * via its `instantiateWasm` hook with that pre-compiled module —
11
11
  * request-time WebAssembly.compile(bytes) is blocked inside facets.
12
12
  *
13
- * Mirrors esbuild-wasm-bytes.ts: NO module-scope cache (no supervisor
14
- * residency), L2 colo cache via caches.default keyed by the version-pinned
15
- * asset URL, ASSETS as the source of truth, and a sha-256 integrity check on
16
- * both tiers so a stale or tampered asset never gets compiled.
13
+ * NO module-scope cache (no supervisor residency). The read itself is
14
+ * runtime/staged-source.ts: L2 keyed by the version-pinned asset URL, ASSETS
15
+ * as the source of truth, and a sha-256 check on both tiers so a stale or
16
+ * tampered asset never gets compiled.
17
17
  */
18
18
  import { SQLJS_VERSION } from '@nimbus-sh/core/constants.js';
19
19
  import { SQLITE_WASM_SHA256 } from '../sqlite-wasm-bundle.generated.js';
20
- import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
21
- import { sha256Hex } from '@nimbus-sh/core/_shared/crypto.js';
20
+ import { fetchStagedBytes } from './staged-source.js';
22
21
  /**
23
22
  * Path inside env.ASSETS where the sql.js wasm binary lives. Versioned so
24
23
  * a future sql.js bump produces a different asset name and forces a fresh
@@ -38,55 +37,20 @@ export const SQLITE_WASM_L2_KEY = `https://nimbus-cache.invalid/_assets/sqljs-${
38
37
  * failures are silent — ASSETS is always the source of truth. A non-200
39
38
  * from ASSETS, or a digest mismatch on either tier, throws.
40
39
  */
41
- export async function fetchSqliteWasmBytes(env) {
42
- const caches = globalThis.caches;
43
- // ── L2 fast path ────────────────────────────────────────────────
44
- let ab = null;
45
- try {
46
- if (caches?.default) {
47
- const hit = await caches.default.match(new Request(SQLITE_WASM_L2_KEY));
48
- if (hit && hit.ok)
49
- ab = await hit.arrayBuffer();
50
- }
51
- }
52
- catch { /* fall through to ASSETS */ }
53
- const fromCache = ab !== null;
54
- if (!ab) {
55
- // ── ASSETS path ───────────────────────────────────────────────
56
- const url = `https://nimbus-internal.invalid${SQLITE_WASM_ASSET_PATH}`;
57
- const res = await env.ASSETS.fetch(new Request(url));
58
- try {
59
- if (!res.ok) {
60
- throw new Error(`sql.js wasm asset fetch failed: ${res.status} ${res.statusText} ` +
61
- `for ${SQLITE_WASM_ASSET_PATH} — deploy is missing the wasm asset`);
62
- }
63
- ab = await res.arrayBuffer();
64
- }
65
- finally {
66
- disposeRpcResource(res);
67
- }
68
- }
69
- const digest = await sha256Hex(ab);
70
- if (digest !== SQLITE_WASM_SHA256) {
71
- throw new Error(`sql.js wasm integrity check failed: expected ${SQLITE_WASM_SHA256}, got ` +
72
- `${digest} (${fromCache ? 'L2 cache' : 'ASSETS'}) for ${SQLITE_WASM_ASSET_PATH} — ` +
73
- 'the staged asset is corrupt or out of sync; rerun ' +
74
- 'scripts/bundle-sqlite-wasm.mjs and redeploy');
75
- }
76
- // ── L2 write-back (best-effort) ─────────────────────────────────
77
- if (!fromCache) {
78
- try {
79
- if (caches?.default) {
80
- const writeBack = new Response(new Uint8Array(ab), {
81
- headers: {
82
- 'Content-Type': 'application/wasm',
83
- 'Cache-Control': 'public, max-age=31536000, immutable',
84
- },
85
- });
86
- await caches.default.put(new Request(SQLITE_WASM_L2_KEY), writeBack);
87
- }
88
- }
89
- catch { /* silent */ }
90
- }
91
- return ab;
40
+ export function fetchSqliteWasmBytes(env) {
41
+ return fetchStagedBytes(env, SQLITE_WASM_ASSET);
92
42
  }
43
+ const SQLITE_WASM_ASSET = {
44
+ path: SQLITE_WASM_ASSET_PATH,
45
+ l2Key: SQLITE_WASM_L2_KEY,
46
+ sha256: SQLITE_WASM_SHA256,
47
+ contentType: 'application/wasm',
48
+ poisonedCache: 'reject',
49
+ missingBinding: `Nimbus: node:sqlite requires an env.ASSETS binding (serves ${SQLITE_WASM_ASSET_PATH})`,
50
+ fetchFailed: (res) => `sql.js wasm asset fetch failed: ${res.status} ${res.statusText} ` +
51
+ `for ${SQLITE_WASM_ASSET_PATH} — deploy is missing the wasm asset`,
52
+ integrityFailed: (digest, from) => `sql.js wasm integrity check failed: expected ${SQLITE_WASM_SHA256}, got ` +
53
+ `${digest} (${from}) for ${SQLITE_WASM_ASSET_PATH} — ` +
54
+ 'the staged asset is corrupt or out of sync; rerun ' +
55
+ 'scripts/bundle-sqlite-wasm.mjs and redeploy',
56
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * staged-source.ts — the one supervisor-side read of a build artifact a bundle
3
+ * script staged under public/_assets/ instead of inlining it in the Worker
4
+ * bundle (wasm modules, facet source texts).
5
+ *
6
+ * The staging script pins each artifact by sha-256 in a `.generated.ts`. This
7
+ * module reads it through L2 (caches.default) under a key that changes with
8
+ * the bytes, with ASSETS as the source of truth, and hands out nothing that
9
+ * fails the pinned digest: a stale or partial asset never reaches workerd's
10
+ * loader or a facet. L2 is written only with verified bytes read from ASSETS.
11
+ *
12
+ * Artifacts differ only in what they name in errors and in what a bad L2 entry
13
+ * means to their caller (`poisonedCache`), so each passes those in.
14
+ */
15
+ /** Minimal env shape — any env with an ASSETS Fetcher binding. */
16
+ export interface StagedSourceEnv {
17
+ ASSETS?: {
18
+ fetch(req: Request): Promise<Response>;
19
+ };
20
+ }
21
+ /** One staged artifact, as its `.generated.ts` pins it. */
22
+ export interface StagedAsset {
23
+ /** Asset path ASSETS serves, e.g. `/_assets/runtime/node-shims-<buildId>.js`. */
24
+ path: string;
25
+ /** L2 key; carries the build id or version, so it changes with the bytes. */
26
+ l2Key: string;
27
+ /** Full digest the bytes must match. */
28
+ sha256: string;
29
+ /** Content-Type of the L2 copy, where the artifact has one. */
30
+ contentType?: string;
31
+ /**
32
+ * An L2 entry that fails the digest: `reject` throws, `refetch` deletes the
33
+ * entry and reads ASSETS instead (whose bytes then replace it).
34
+ */
35
+ poisonedCache: 'reject' | 'refetch';
36
+ /** Message for a deploy with no ASSETS binding. */
37
+ missingBinding: string;
38
+ /** Message for a non-OK ASSETS response. */
39
+ fetchFailed(res: Response): string;
40
+ /** Message for bytes that do not match `sha256`. */
41
+ integrityFailed(digest: string, from: 'L2 cache' | 'ASSETS'): string;
42
+ }
43
+ /** Fetch a staged artifact's bytes and verify them against its pinned digest. */
44
+ export declare function fetchStagedBytes(env: StagedSourceEnv, asset: StagedAsset): Promise<ArrayBuffer>;
45
+ /** {@link fetchStagedBytes}, decoded as UTF-8. */
46
+ export declare function fetchStagedText(env: StagedSourceEnv, asset: StagedAsset): Promise<string>;
47
+ /**
48
+ * One load per isolate: every caller shares the first call's promise, and a
49
+ * rejected one is dropped so the next call loads again instead of pinning the
50
+ * error.
51
+ */
52
+ export declare function memoizeUntilRejected<A, T>(load: (arg: A) => Promise<T>): (arg: A) => Promise<T>;
53
+ /**
54
+ * The `StagedAsset` of a facet source text staged under
55
+ * public/_assets/runtime/ by `stagedBy` and needed by `requiredBy`: a bad L2
56
+ * entry is replaced from ASSETS, since every later fetch in the colo for the
57
+ * build would otherwise fail on it.
58
+ */
59
+ export declare function stagedRuntimeSource(source: {
60
+ label: string;
61
+ entry: string;
62
+ buildId: string;
63
+ sha256: string;
64
+ stagedBy: string;
65
+ requiredBy: string;
66
+ }): StagedAsset;
67
+ //# sourceMappingURL=staged-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staged-source.d.ts","sourceRoot":"","sources":["../../src/runtime/staged-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;CACrD;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,EAAE,QAAQ,GAAG,SAAS,CAAC;IACpC,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAAC;IACnC,oDAAoD;IACpD,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;CACtE;AAED,iFAAiF;AACjF,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CA2CrG;AAED,kDAAkD;AAClD,wBAAsB,eAAe,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE/F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAU/F;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,WAAW,CAkBd"}