@nimbus-sh/worker 0.1.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.
- package/README.md +219 -0
- package/dist/_shared/bytes.d.ts +18 -0
- package/dist/_shared/bytes.d.ts.map +1 -0
- package/dist/_shared/bytes.js +17 -0
- package/dist/_shared/cache-stats.d.ts +146 -0
- package/dist/_shared/cache-stats.d.ts.map +1 -0
- package/dist/_shared/cache-stats.js +162 -0
- package/dist/_shared/exports-resolver.d.ts +76 -0
- package/dist/_shared/exports-resolver.d.ts.map +1 -0
- package/dist/_shared/exports-resolver.js +284 -0
- package/dist/_shared/install-phase.d.ts +47 -0
- package/dist/_shared/install-phase.d.ts.map +1 -0
- package/dist/_shared/install-phase.js +1 -0
- package/dist/_shared/real-node-imports.d.ts +38 -0
- package/dist/_shared/real-node-imports.d.ts.map +1 -0
- package/dist/_shared/real-node-imports.js +46 -0
- package/dist/_shared/retry.d.ts +93 -0
- package/dist/_shared/retry.d.ts.map +1 -0
- package/dist/_shared/retry.js +161 -0
- package/dist/_shared/session-id.d.ts +39 -0
- package/dist/_shared/session-id.d.ts.map +1 -0
- package/dist/_shared/session-id.js +127 -0
- package/dist/_shared/session-router.d.ts +104 -0
- package/dist/_shared/session-router.d.ts.map +1 -0
- package/dist/_shared/session-router.js +165 -0
- package/dist/_shared/w7-frame.d.ts +93 -0
- package/dist/_shared/w7-frame.d.ts.map +1 -0
- package/dist/_shared/w7-frame.js +402 -0
- package/dist/auth/index.d.ts +16 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +15 -0
- package/dist/auth/middleware.d.ts +85 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +150 -0
- package/dist/auth/token.d.ts +96 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +226 -0
- package/dist/auth/types.d.ts +141 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +114 -0
- package/dist/bindings/d1.d.ts +102 -0
- package/dist/bindings/d1.d.ts.map +1 -0
- package/dist/bindings/d1.js +549 -0
- package/dist/bindings/kv.d.ts +82 -0
- package/dist/bindings/kv.d.ts.map +1 -0
- package/dist/bindings/kv.js +290 -0
- package/dist/bindings/r2.d.ts +137 -0
- package/dist/bindings/r2.d.ts.map +1 -0
- package/dist/bindings/r2.js +379 -0
- package/dist/cirrus-npm-cjs.generated.d.ts +17 -0
- package/dist/cirrus-npm-cjs.generated.d.ts.map +1 -0
- package/dist/cirrus-npm-cjs.generated.js +41 -0
- package/dist/cirrus-plugin-react.generated.d.ts +34 -0
- package/dist/cirrus-plugin-react.generated.d.ts.map +1 -0
- package/dist/cirrus-plugin-react.generated.js +33 -0
- package/dist/constants.d.ts +34 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +116 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts +41 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -0
- package/dist/esbuild-wasm-bundle.generated.js +40 -0
- package/dist/facets/cirrus-real.d.ts +147 -0
- package/dist/facets/cirrus-real.d.ts.map +1 -0
- package/dist/facets/cirrus-real.js +981 -0
- package/dist/facets/inner-do-registry.d.ts +41 -0
- package/dist/facets/inner-do-registry.d.ts.map +1 -0
- package/dist/facets/inner-do-registry.js +51 -0
- package/dist/facets/manager.d.ts +314 -0
- package/dist/facets/manager.d.ts.map +1 -0
- package/dist/facets/manager.js +2681 -0
- package/dist/facets/process.d.ts +318 -0
- package/dist/facets/process.d.ts.map +1 -0
- package/dist/facets/process.js +567 -0
- package/dist/facets/real-vite-fs-shim.d.ts +151 -0
- package/dist/facets/real-vite-fs-shim.d.ts.map +1 -0
- package/dist/facets/real-vite-fs-shim.js +1279 -0
- package/dist/facets/real-vite-hmr.d.ts +142 -0
- package/dist/facets/real-vite-hmr.d.ts.map +1 -0
- package/dist/facets/real-vite-hmr.js +476 -0
- package/dist/facets/vite-dev-server.d.ts +293 -0
- package/dist/facets/vite-dev-server.d.ts.map +1 -0
- package/dist/facets/vite-dev-server.js +2580 -0
- package/dist/facets/wasm-swap-registry.d.ts +261 -0
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -0
- package/dist/facets/wasm-swap-registry.js +551 -0
- package/dist/facets/ws-terminal.d.ts +107 -0
- package/dist/facets/ws-terminal.d.ts.map +1 -0
- package/dist/facets/ws-terminal.js +207 -0
- package/dist/git/commands.d.ts +13 -0
- package/dist/git/commands.d.ts.map +1 -0
- package/dist/git/commands.js +633 -0
- package/dist/git/network-facet.d.ts +61 -0
- package/dist/git/network-facet.d.ts.map +1 -0
- package/dist/git/network-facet.js +700 -0
- package/dist/git-bundle.generated.d.ts +16 -0
- package/dist/git-bundle.generated.d.ts.map +1 -0
- package/dist/git-bundle.generated.js +15 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/loaders/child-process/spawn-facet.d.ts +90 -0
- package/dist/loaders/child-process/spawn-facet.d.ts.map +1 -0
- package/dist/loaders/child-process/spawn-facet.js +103 -0
- package/dist/loaders/child-process/spawn-pool.d.ts +110 -0
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -0
- package/dist/loaders/child-process/spawn-pool.js +161 -0
- package/dist/loaders/fanout-pool.d.ts +195 -0
- package/dist/loaders/fanout-pool.d.ts.map +1 -0
- package/dist/loaders/fanout-pool.js +326 -0
- package/dist/loaders/generated-workers.d.ts +26 -0
- package/dist/loaders/generated-workers.d.ts.map +1 -0
- package/dist/loaders/generated-workers.js +25 -0
- package/dist/loaders/index.d.ts +15 -0
- package/dist/loaders/index.d.ts.map +1 -0
- package/dist/loaders/index.js +13 -0
- package/dist/loaders/loader-pool.d.ts +273 -0
- package/dist/loaders/loader-pool.d.ts.map +1 -0
- package/dist/loaders/loader-pool.js +624 -0
- package/dist/loaders/npm-resolve-preamble.d.ts +34 -0
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -0
- package/dist/loaders/npm-resolve-preamble.js +237 -0
- package/dist/loaders/pre-bundle-preamble.d.ts +41 -0
- package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -0
- package/dist/loaders/pre-bundle-preamble.js +114 -0
- package/dist/loaders/vendor/codegen.d.ts +24 -0
- package/dist/loaders/vendor/codegen.d.ts.map +1 -0
- package/dist/loaders/vendor/codegen.js +44 -0
- package/dist/loaders/vendor/errors.d.ts +24 -0
- package/dist/loaders/vendor/errors.d.ts.map +1 -0
- package/dist/loaders/vendor/errors.js +46 -0
- package/dist/loaders/vendor/index.d.ts +16 -0
- package/dist/loaders/vendor/index.d.ts.map +1 -0
- package/dist/loaders/vendor/index.js +11 -0
- package/dist/loaders/vendor/pool.d.ts +88 -0
- package/dist/loaders/vendor/pool.d.ts.map +1 -0
- package/dist/loaders/vendor/pool.js +353 -0
- package/dist/loaders/vendor/primitives.d.ts +11 -0
- package/dist/loaders/vendor/primitives.d.ts.map +1 -0
- package/dist/loaders/vendor/primitives.js +33 -0
- package/dist/loaders/vendor/serialize.d.ts +3 -0
- package/dist/loaders/vendor/serialize.d.ts.map +1 -0
- package/dist/loaders/vendor/serialize.js +25 -0
- package/dist/loaders/vendor/types.d.ts +60 -0
- package/dist/loaders/vendor/types.d.ts.map +1 -0
- package/dist/loaders/vendor/types.js +4 -0
- package/dist/npm/cache.d.ts +136 -0
- package/dist/npm/cache.d.ts.map +1 -0
- package/dist/npm/cache.js +429 -0
- package/dist/npm/install-batch-facet.d.ts +129 -0
- package/dist/npm/install-batch-facet.d.ts.map +1 -0
- package/dist/npm/install-batch-facet.js +598 -0
- package/dist/npm/install-facet.d.ts +29 -0
- package/dist/npm/install-facet.d.ts.map +1 -0
- package/dist/npm/install-facet.js +9 -0
- package/dist/npm/installer.d.ts +239 -0
- package/dist/npm/installer.d.ts.map +1 -0
- package/dist/npm/installer.js +2022 -0
- package/dist/npm/npx-install.d.ts +74 -0
- package/dist/npm/npx-install.d.ts.map +1 -0
- package/dist/npm/npx-install.js +302 -0
- package/dist/npm/pre-bundle-facet.d.ts +168 -0
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -0
- package/dist/npm/pre-bundle-facet.js +653 -0
- package/dist/npm/r2-cache.d.ts +286 -0
- package/dist/npm/r2-cache.d.ts.map +1 -0
- package/dist/npm/r2-cache.js +506 -0
- package/dist/npm/resolve-facet.d.ts +202 -0
- package/dist/npm/resolve-facet.d.ts.map +1 -0
- package/dist/npm/resolve-facet.js +833 -0
- package/dist/npm/resolve-one-facet.d.ts +204 -0
- package/dist/npm/resolve-one-facet.d.ts.map +1 -0
- package/dist/npm/resolve-one-facet.js +437 -0
- package/dist/npm/resolver.d.ts +109 -0
- package/dist/npm/resolver.d.ts.map +1 -0
- package/dist/npm/resolver.js +899 -0
- package/dist/npm/tarball-stream.d.ts +85 -0
- package/dist/npm/tarball-stream.d.ts.map +1 -0
- package/dist/npm/tarball-stream.js +205 -0
- package/dist/npm/tarball.d.ts +51 -0
- package/dist/npm/tarball.d.ts.map +1 -0
- package/dist/npm/tarball.js +185 -0
- package/dist/observability/diag-counters.d.ts +292 -0
- package/dist/observability/diag-counters.d.ts.map +1 -0
- package/dist/observability/diag-counters.js +302 -0
- package/dist/observability/heap-estimate.d.ts +166 -0
- package/dist/observability/heap-estimate.d.ts.map +1 -0
- package/dist/observability/heap-estimate.js +192 -0
- package/dist/observability/heavy-alloc-coord.d.ts +95 -0
- package/dist/observability/heavy-alloc-coord.d.ts.map +1 -0
- package/dist/observability/heavy-alloc-coord.js +149 -0
- package/dist/observability/oom-classify.d.ts +56 -0
- package/dist/observability/oom-classify.d.ts.map +1 -0
- package/dist/observability/oom-classify.js +118 -0
- package/dist/observability/oom-discriminator.d.ts +192 -0
- package/dist/observability/oom-discriminator.d.ts.map +1 -0
- package/dist/observability/oom-discriminator.js +233 -0
- package/dist/real-vite-bundle.generated.d.ts +46 -0
- package/dist/real-vite-bundle.generated.d.ts.map +1 -0
- package/dist/real-vite-bundle.generated.js +53 -0
- package/dist/replica/routing.d.ts +171 -0
- package/dist/replica/routing.d.ts.map +1 -0
- package/dist/replica/routing.js +288 -0
- package/dist/replica/suspension.d.ts +36 -0
- package/dist/replica/suspension.d.ts.map +1 -0
- package/dist/replica/suspension.js +49 -0
- package/dist/router/index.d.ts +156 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +220 -0
- package/dist/runtime/assets-loader.d.ts +71 -0
- package/dist/runtime/assets-loader.d.ts.map +1 -0
- package/dist/runtime/assets-loader.js +128 -0
- package/dist/runtime/barrel-detect.d.ts +46 -0
- package/dist/runtime/barrel-detect.d.ts.map +1 -0
- package/dist/runtime/barrel-detect.js +73 -0
- package/dist/runtime/barrel-synthesizer.d.ts +181 -0
- package/dist/runtime/barrel-synthesizer.d.ts.map +1 -0
- package/dist/runtime/barrel-synthesizer.js +541 -0
- package/dist/runtime/bun-repl.d.ts +46 -0
- package/dist/runtime/bun-repl.d.ts.map +1 -0
- package/dist/runtime/bun-repl.js +326 -0
- package/dist/runtime/bun-runner.d.ts +66 -0
- package/dist/runtime/bun-runner.d.ts.map +1 -0
- package/dist/runtime/bun-runner.js +219 -0
- package/dist/runtime/clang-runner.d.ts +38 -0
- package/dist/runtime/clang-runner.d.ts.map +1 -0
- package/dist/runtime/clang-runner.js +1033 -0
- package/dist/runtime/comment-strip.d.ts +37 -0
- package/dist/runtime/comment-strip.d.ts.map +1 -0
- package/dist/runtime/comment-strip.js +74 -0
- package/dist/runtime/esbuild-service.d.ts +201 -0
- package/dist/runtime/esbuild-service.d.ts.map +1 -0
- package/dist/runtime/esbuild-service.js +1444 -0
- package/dist/runtime/esbuild-wasm-bytes.d.ts +87 -0
- package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -0
- package/dist/runtime/esbuild-wasm-bytes.js +131 -0
- package/dist/runtime/framework-detect.d.ts +71 -0
- package/dist/runtime/framework-detect.d.ts.map +1 -0
- package/dist/runtime/framework-detect.js +178 -0
- package/dist/runtime/long-running-handle.d.ts +133 -0
- package/dist/runtime/long-running-handle.d.ts.map +1 -0
- package/dist/runtime/long-running-handle.js +192 -0
- package/dist/runtime/node-repl.d.ts +36 -0
- package/dist/runtime/node-repl.d.ts.map +1 -0
- package/dist/runtime/node-repl.js +293 -0
- package/dist/runtime/node-runner.d.ts +111 -0
- package/dist/runtime/node-runner.d.ts.map +1 -0
- package/dist/runtime/node-runner.js +194 -0
- package/dist/runtime/node-shims.d.ts +24 -0
- package/dist/runtime/node-shims.d.ts.map +1 -0
- package/dist/runtime/node-shims.js +3019 -0
- package/dist/runtime/package-manager.d.ts +118 -0
- package/dist/runtime/package-manager.d.ts.map +1 -0
- package/dist/runtime/package-manager.js +420 -0
- package/dist/runtime/port-registry.d.ts +80 -0
- package/dist/runtime/port-registry.d.ts.map +1 -0
- package/dist/runtime/port-registry.js +166 -0
- package/dist/runtime/process-logs-api.d.ts +102 -0
- package/dist/runtime/process-logs-api.d.ts.map +1 -0
- package/dist/runtime/process-logs-api.js +253 -0
- package/dist/runtime/process-logs.d.ts +311 -0
- package/dist/runtime/process-logs.d.ts.map +1 -0
- package/dist/runtime/process-logs.js +656 -0
- package/dist/runtime/process-table.d.ts +65 -0
- package/dist/runtime/process-table.d.ts.map +1 -0
- package/dist/runtime/process-table.js +107 -0
- package/dist/runtime/project-detect.d.ts +21 -0
- package/dist/runtime/project-detect.d.ts.map +1 -0
- package/dist/runtime/project-detect.js +46 -0
- package/dist/runtime/python-repl.d.ts +66 -0
- package/dist/runtime/python-repl.d.ts.map +1 -0
- package/dist/runtime/python-repl.js +563 -0
- package/dist/runtime/python-runner.d.ts +50 -0
- package/dist/runtime/python-runner.d.ts.map +1 -0
- package/dist/runtime/python-runner.js +870 -0
- package/dist/runtime/repl-session.d.ts +160 -0
- package/dist/runtime/repl-session.d.ts.map +1 -0
- package/dist/runtime/repl-session.js +393 -0
- package/dist/runtime/require-resolver.d.ts +52 -0
- package/dist/runtime/require-resolver.d.ts.map +1 -0
- package/dist/runtime/require-resolver.js +690 -0
- package/dist/runtime/router-basename.d.ts +36 -0
- package/dist/runtime/router-basename.d.ts.map +1 -0
- package/dist/runtime/router-basename.js +310 -0
- package/dist/runtime/ruby-repl.d.ts +43 -0
- package/dist/runtime/ruby-repl.d.ts.map +1 -0
- package/dist/runtime/ruby-repl.js +229 -0
- package/dist/runtime/ruby-runner.d.ts +62 -0
- package/dist/runtime/ruby-runner.d.ts.map +1 -0
- package/dist/runtime/ruby-runner.js +737 -0
- package/dist/runtime/runtime-catalog.d.ts +99 -0
- package/dist/runtime/runtime-catalog.d.ts.map +1 -0
- package/dist/runtime/runtime-catalog.js +183 -0
- package/dist/runtime/runtime-registry.d.ts +136 -0
- package/dist/runtime/runtime-registry.d.ts.map +1 -0
- package/dist/runtime/runtime-registry.js +318 -0
- package/dist/runtime/streams.d.ts +13 -0
- package/dist/runtime/streams.d.ts.map +1 -0
- package/dist/runtime/streams.js +402 -0
- package/dist/runtime/wasi-instance.d.ts +190 -0
- package/dist/runtime/wasi-instance.d.ts.map +1 -0
- package/dist/runtime/wasi-instance.js +2044 -0
- package/dist/runtime/wasm-runner.d.ts +117 -0
- package/dist/runtime/wasm-runner.d.ts.map +1 -0
- package/dist/runtime/wasm-runner.js +705 -0
- package/dist/session/bindings.d.ts +168 -0
- package/dist/session/bindings.d.ts.map +1 -0
- package/dist/session/bindings.js +524 -0
- package/dist/session/ctx-exports.d.ts +16 -0
- package/dist/session/ctx-exports.d.ts.map +1 -0
- package/dist/session/ctx-exports.js +22 -0
- package/dist/session/diag.d.ts +62 -0
- package/dist/session/diag.d.ts.map +1 -0
- package/dist/session/diag.js +118 -0
- package/dist/session/fs-watch.d.ts +132 -0
- package/dist/session/fs-watch.d.ts.map +1 -0
- package/dist/session/fs-watch.js +255 -0
- package/dist/session/helpers.d.ts +150 -0
- package/dist/session/helpers.d.ts.map +1 -0
- package/dist/session/helpers.js +472 -0
- package/dist/session/hibernation.d.ts +145 -0
- package/dist/session/hibernation.d.ts.map +1 -0
- package/dist/session/hibernation.js +385 -0
- package/dist/session/init-phases.d.ts +87 -0
- package/dist/session/init-phases.d.ts.map +1 -0
- package/dist/session/init-phases.js +137 -0
- package/dist/session/init.d.ts +46 -0
- package/dist/session/init.d.ts.map +1 -0
- package/dist/session/init.js +3579 -0
- package/dist/session/keys.d.ts +56 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +55 -0
- package/dist/session/nimbus-session.d.ts +434 -0
- package/dist/session/nimbus-session.d.ts.map +1 -0
- package/dist/session/nimbus-session.js +1084 -0
- package/dist/session/programmatic.d.ts +105 -0
- package/dist/session/programmatic.d.ts.map +1 -0
- package/dist/session/programmatic.js +326 -0
- package/dist/session/replica-routes.d.ts +68 -0
- package/dist/session/replica-routes.d.ts.map +1 -0
- package/dist/session/replica-routes.js +77 -0
- package/dist/session/routes.d.ts +29 -0
- package/dist/session/routes.d.ts.map +1 -0
- package/dist/session/routes.js +1331 -0
- package/dist/session/rpc.d.ts +323 -0
- package/dist/session/rpc.d.ts.map +1 -0
- package/dist/session/rpc.js +808 -0
- package/dist/session/state-store.d.ts +210 -0
- package/dist/session/state-store.d.ts.map +1 -0
- package/dist/session/state-store.js +459 -0
- package/dist/session/supervisor-rpc.d.ts +269 -0
- package/dist/session/supervisor-rpc.d.ts.map +1 -0
- package/dist/session/supervisor-rpc.js +437 -0
- package/dist/session/ws-hibernation-config.d.ts +64 -0
- package/dist/session/ws-hibernation-config.d.ts.map +1 -0
- package/dist/session/ws-hibernation-config.js +89 -0
- package/dist/session/ws.d.ts +95 -0
- package/dist/session/ws.d.ts.map +1 -0
- package/dist/session/ws.js +395 -0
- package/dist/shell/features.d.ts +410 -0
- package/dist/shell/features.d.ts.map +1 -0
- package/dist/shell/features.js +1885 -0
- package/dist/shell/unix-commands.d.ts +15 -0
- package/dist/shell/unix-commands.d.ts.map +1 -0
- package/dist/shell/unix-commands.js +3703 -0
- package/dist/stubs/lifo-ui.d.ts +10 -0
- package/dist/stubs/lifo-ui.d.ts.map +1 -0
- package/dist/stubs/lifo-ui.js +1 -0
- package/dist/tailwind-play.generated.d.ts +19 -0
- package/dist/tailwind-play.generated.d.ts.map +1 -0
- package/dist/tailwind-play.generated.js +18 -0
- package/dist/vfs/dev-provider.d.ts +64 -0
- package/dist/vfs/dev-provider.d.ts.map +1 -0
- package/dist/vfs/dev-provider.js +191 -0
- package/dist/vfs/events.d.ts +51 -0
- package/dist/vfs/events.d.ts.map +1 -0
- package/dist/vfs/events.js +127 -0
- package/dist/vfs/path.d.ts +30 -0
- package/dist/vfs/path.d.ts.map +1 -0
- package/dist/vfs/path.js +41 -0
- package/dist/vfs/seed-project.d.ts +62 -0
- package/dist/vfs/seed-project.d.ts.map +1 -0
- package/dist/vfs/seed-project.js +1045 -0
- package/dist/vfs/sqlite-vfs.d.ts +435 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -0
- package/dist/vfs/sqlite-vfs.js +1670 -0
- package/dist/vfs/symlink-registry.d.ts +64 -0
- package/dist/vfs/symlink-registry.d.ts.map +1 -0
- package/dist/vfs/symlink-registry.js +137 -0
- package/dist/wrangler/nimbus-wrangler.d.ts +157 -0
- package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -0
- package/dist/wrangler/nimbus-wrangler.js +931 -0
- package/package.json +77 -0
- package/public/_assets/cirrus-plugin-react.bundle.js +70740 -0
- package/public/_assets/esbuild-0.24.2.wasm +0 -0
- package/public/_assets/real-vite-bundle.js +101478 -0
- package/public/_assets/rollup.wasm +0 -0
- package/public/favicon.svg +8 -0
- package/public/index.html +565 -0
- package/public/s/index.html +2379 -0
- package/scripts/bundle-esbuild-wasm.mjs +244 -0
- package/scripts/bundle-facet-workers.mjs +137 -0
- package/scripts/bundle-git.mjs +118 -0
- package/scripts/bundle-npm-cjs.mjs +363 -0
- package/scripts/bundle-plugin-react.mjs +480 -0
- package/scripts/bundle-real-vite.mjs +959 -0
- package/scripts/bundle-runtime.mjs +600 -0
- package/scripts/bundle-tailwind-play.mjs +130 -0
- package/scripts/patch-lifo-ui.mjs +131 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session/rpc.ts — Supervisor RPC + W8 child_process + legacy VFS impls.
|
|
3
|
+
*
|
|
4
|
+
* Why free-functions instead of class methods:
|
|
5
|
+
* the DO RPC fabric calls these by name, so the supervisor's class
|
|
6
|
+
* MUST keep the method names as delegators (otherwise the fabric
|
|
7
|
+
* looks them up and finds nothing). Putting the bodies in free
|
|
8
|
+
* functions and having the class methods one-line-delegate keeps
|
|
9
|
+
* the class file small AND lets these be unit-tested without a DO
|
|
10
|
+
* harness (the `RpcHost` parameter is a narrow contract).
|
|
11
|
+
*
|
|
12
|
+
* Bodies for every `_rpc*`, `vfs*`, `_emit*`, and `_reportExternalExit`
|
|
13
|
+
* method live here. The class retains the method NAMES as one-line
|
|
14
|
+
* delegators in src/session/nimbus-session.ts.
|
|
15
|
+
* 1-line delegators (per plan §IX.4 R1: DO RPC fabric uses name dispatch
|
|
16
|
+
* via the stub).
|
|
17
|
+
*
|
|
18
|
+
* Per DEFECT-D1: ctx is taken via `(self as any).ctx` cast where needed
|
|
19
|
+
* (rpcInnerDoFetch uses self.ctx.id and self.ctx.facets; rpcPutRegistryEntries
|
|
20
|
+
* uses self.ctx.storage.sql). The InitHost-style escape applies because
|
|
21
|
+
* these ~3 sites would each need ctx threaded through; cast at boundary
|
|
22
|
+
* is acceptable per plan §IX recommendation 1.
|
|
23
|
+
*/
|
|
24
|
+
import { enc } from '../_shared/bytes.js';
|
|
25
|
+
import { getInnerDoClass } from '../facets/inner-do-registry.js';
|
|
26
|
+
import { NpmCache } from '../npm/cache.js';
|
|
27
|
+
import { EsbuildService } from '../runtime/esbuild-service.js';
|
|
28
|
+
import { notifyTerminalEvent } from '../runtime/process-logs-api.js';
|
|
29
|
+
import { NimbusLoaderPool } from '../loaders/loader-pool.js';
|
|
30
|
+
import { recordFailure, getLastRpcFrame, getLastFacetId, } from '../observability/oom-discriminator.js';
|
|
31
|
+
import { classifyError } from '../observability/oom-classify.js';
|
|
32
|
+
export async function _rpcReadFile(self, path) {
|
|
33
|
+
self.ensureSqliteFs();
|
|
34
|
+
try {
|
|
35
|
+
return self.sqliteFs.readFileString(path.replace(/^\/+/, ''));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Read a file as raw bytes (Uint8Array). Used by git network facet for
|
|
43
|
+
* binary .git/objects/** and packfile reads, where TextDecoder/TextEncoder
|
|
44
|
+
* round-tripping through readFile (string) would corrupt bytes.
|
|
45
|
+
*/
|
|
46
|
+
export async function _rpcReadFileBytes(self, path) {
|
|
47
|
+
self.ensureSqliteFs();
|
|
48
|
+
try {
|
|
49
|
+
return self.sqliteFs.readFile(path.replace(/^\/+/, ''));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Phase-3 inner-DO fetch dispatcher. Called by NimbusDOStub.fetch()
|
|
57
|
+
* from the inner Worker via the env.NIMBUS_SESSION loopback. We
|
|
58
|
+
* resolve the inner DO class from the module-level registry (keyed
|
|
59
|
+
* by <thisDoId>:<bindingName>), use ctx.facets.get with the inner's
|
|
60
|
+
* id string as the facet id, and forward the serialized Request.
|
|
61
|
+
*
|
|
62
|
+
* All steps run in THIS RPC method's context, so no cross-request
|
|
63
|
+
* I/O boundaries are crossed — the ctx.facets stub and its fetch()
|
|
64
|
+
* are both created here.
|
|
65
|
+
*/
|
|
66
|
+
export async function _rpcInnerDoFetch(self, req) {
|
|
67
|
+
const cls = getInnerDoClass(self.ctx.id.toString(), req.bindingName);
|
|
68
|
+
if (!cls) {
|
|
69
|
+
const body = enc.encode(`Nimbus: inner DO binding '${req.bindingName}' has no registered class (supervisor=${self.ctx.id.toString()})`);
|
|
70
|
+
return {
|
|
71
|
+
status: 502,
|
|
72
|
+
statusText: 'Bad Gateway',
|
|
73
|
+
headers: [['Content-Type', 'text/plain']],
|
|
74
|
+
body: body.buffer,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const facetName = 'innerDO-' + req.bindingName + '-' + req.id;
|
|
78
|
+
const facet = self.ctx.facets.get(facetName, async () => ({
|
|
79
|
+
class: cls,
|
|
80
|
+
id: req.id, // FacetStartupOptions.id — inner DO sees this as its ctx.id
|
|
81
|
+
}));
|
|
82
|
+
try {
|
|
83
|
+
// Reconstruct the Request in the current context.
|
|
84
|
+
const headers = new Headers();
|
|
85
|
+
for (const [k, v] of req.headers)
|
|
86
|
+
headers.append(k, v);
|
|
87
|
+
const r = new Request(req.url, {
|
|
88
|
+
method: req.method,
|
|
89
|
+
headers,
|
|
90
|
+
body: req.body,
|
|
91
|
+
});
|
|
92
|
+
const res = await facet.fetch(r);
|
|
93
|
+
const resHeaderList = [];
|
|
94
|
+
res.headers.forEach((v, k) => { resHeaderList.push([k, v]); });
|
|
95
|
+
const resBody = await res.arrayBuffer();
|
|
96
|
+
return {
|
|
97
|
+
status: res.status,
|
|
98
|
+
statusText: res.statusText,
|
|
99
|
+
headers: resHeaderList,
|
|
100
|
+
body: resBody,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
const body = enc.encode(`Nimbus inner DO error: ${e?.message || String(e)}`);
|
|
105
|
+
return {
|
|
106
|
+
status: 500,
|
|
107
|
+
statusText: 'Internal Server Error',
|
|
108
|
+
headers: [['Content-Type', 'text/plain']],
|
|
109
|
+
body: body.buffer,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function _rpcWriteFile(self, path, content) {
|
|
114
|
+
self.ensureSqliteFs();
|
|
115
|
+
const p = path.replace(/^\/+/, '');
|
|
116
|
+
const parts = p.split('/');
|
|
117
|
+
for (let i = 1; i < parts.length; i++) {
|
|
118
|
+
const dir = parts.slice(0, i).join('/');
|
|
119
|
+
if (dir && !self.sqliteFs.exists(dir))
|
|
120
|
+
self.sqliteFs.mkdir(dir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
// binary-fs wave: SqliteVFS.writeFile already accepts string | Uint8Array
|
|
123
|
+
// (sqlite-vfs.ts:937), so we forward the content shape unchanged. RPC
|
|
124
|
+
// structured-clone preserves Uint8Array across the boundary; structured-
|
|
125
|
+
// clone doesn't accept Buffer subclass instances, so fs.writeFileSync on
|
|
126
|
+
// a Buffer flows through node-shims.ts:writeFileSync which stores it as
|
|
127
|
+
// a plain Uint8Array on the cell — the shape that arrives here.
|
|
128
|
+
self.sqliteFs.writeFile(p, content);
|
|
129
|
+
}
|
|
130
|
+
export async function _rpcStat(self, path) {
|
|
131
|
+
self.ensureSqliteFs();
|
|
132
|
+
try {
|
|
133
|
+
return self.sqliteFs.stat(path.replace(/^\/+/, ''));
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export async function _rpcReaddir(self, path) {
|
|
140
|
+
self.ensureSqliteFs();
|
|
141
|
+
try {
|
|
142
|
+
return self.sqliteFs.readdir(path.replace(/^\/+/, ''));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export async function _rpcExists(self, path) {
|
|
149
|
+
self.ensureSqliteFs();
|
|
150
|
+
return self.sqliteFs.exists(path.replace(/^\/+/, ''));
|
|
151
|
+
}
|
|
152
|
+
export async function _rpcMkdir(self, path) {
|
|
153
|
+
self.ensureSqliteFs();
|
|
154
|
+
self.sqliteFs.mkdir(path.replace(/^\/+/, ''), { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Called by CirrusHmrRPC.hmrSend. Runs in the DO's own context so
|
|
158
|
+
* we can legally write to hibernatable WS sockets owned by this
|
|
159
|
+
* DO. The HmrBridge holds the client→WS map; we delegate to it.
|
|
160
|
+
*/
|
|
161
|
+
export async function _rpcHmrRelay(self, clientId, msg) {
|
|
162
|
+
if (!self.cirrusReal)
|
|
163
|
+
return;
|
|
164
|
+
self.cirrusReal.hmr.relayToBrowser(clientId, msg);
|
|
165
|
+
}
|
|
166
|
+
export async function _rpcUnlink(self, path) {
|
|
167
|
+
self.ensureSqliteFs();
|
|
168
|
+
try {
|
|
169
|
+
self.sqliteFs.unlink(path.replace(/^\/+/, ''));
|
|
170
|
+
}
|
|
171
|
+
catch { }
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Bulk-write files and directories via one transactionSync().
|
|
175
|
+
* Called from facets that accumulate writes locally (git clone/fetch/pull,
|
|
176
|
+
* potentially others) to avoid thousands of individual writeFile RPCs.
|
|
177
|
+
*
|
|
178
|
+
* payload: {
|
|
179
|
+
* inodes: BatchInodeEntry[],
|
|
180
|
+
* chunks: { path, chunkId, data: Uint8Array | ArrayBuffer }[],
|
|
181
|
+
* deletePaths?: string[]
|
|
182
|
+
* }
|
|
183
|
+
*/
|
|
184
|
+
export async function _rpcWriteBatch(self, payload) {
|
|
185
|
+
self.ensureSqliteFs();
|
|
186
|
+
const inodes = Array.isArray(payload?.inodes) ? payload.inodes : [];
|
|
187
|
+
const rawChunks = Array.isArray(payload?.chunks) ? payload.chunks : [];
|
|
188
|
+
const deletePaths = Array.isArray(payload?.deletePaths) ? payload.deletePaths : undefined;
|
|
189
|
+
// Normalize chunk data — RPC may deliver Uint8Array, ArrayBuffer, or { type: 'Buffer', data: [...] }
|
|
190
|
+
const chunks = rawChunks.map((c) => {
|
|
191
|
+
let data;
|
|
192
|
+
if (c.data instanceof Uint8Array) {
|
|
193
|
+
data = c.data;
|
|
194
|
+
}
|
|
195
|
+
else if (c.data instanceof ArrayBuffer) {
|
|
196
|
+
data = new Uint8Array(c.data);
|
|
197
|
+
}
|
|
198
|
+
else if (ArrayBuffer.isView(c.data)) {
|
|
199
|
+
data = new Uint8Array(c.data.buffer, c.data.byteOffset, c.data.byteLength);
|
|
200
|
+
}
|
|
201
|
+
else if (Array.isArray(c.data)) {
|
|
202
|
+
data = new Uint8Array(c.data);
|
|
203
|
+
}
|
|
204
|
+
else if (c.data && typeof c.data === 'object' && Array.isArray(c.data.data)) {
|
|
205
|
+
// Buffer JSON serialization fallback
|
|
206
|
+
data = new Uint8Array(c.data.data);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
data = new Uint8Array(0);
|
|
210
|
+
}
|
|
211
|
+
return { path: String(c.path), chunkId: Number(c.chunkId), data };
|
|
212
|
+
});
|
|
213
|
+
return self.sqliteFs.writeBatch({
|
|
214
|
+
inodes,
|
|
215
|
+
chunks,
|
|
216
|
+
deletePaths,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* W7 — Streaming bulk-write entry point. Receives a
|
|
221
|
+
* ReadableStream<Uint8Array> in the W7 wire format (see
|
|
222
|
+
* src/_shared/w7-frame.ts), decodes inode metadata + chunks lazily,
|
|
223
|
+
* and feeds them into SqliteVFS.writeStream().
|
|
224
|
+
*
|
|
225
|
+
* Bypasses the 32 MiB structured-clone cap that constrained the
|
|
226
|
+
* legacy writeBatch path — workerd flow-controls the byte stream
|
|
227
|
+
* end-to-end.
|
|
228
|
+
*
|
|
229
|
+
* Atomicity guarantee mirrors writeBatch: either ALL inodes +
|
|
230
|
+
* chunks land in SQLite or NONE do. SqliteVFS.writeStream defers
|
|
231
|
+
* the actual transactionSync until the chunk iterator is fully
|
|
232
|
+
* drained (v1 spool-then-commit), so a stream error mid-transit
|
|
233
|
+
* aborts before any SQL state mutates.
|
|
234
|
+
*/
|
|
235
|
+
export async function _rpcWriteBatchStream(self, stream) {
|
|
236
|
+
self.ensureSqliteFs();
|
|
237
|
+
// [P0a — COORDINATOR-OVERLOAD]
|
|
238
|
+
//
|
|
239
|
+
// Wave-1 (semaphore here): rejected. Parking peer-side awaits in a
|
|
240
|
+
// user-space queue extended each peer's _rpcFanoutExecute round-trip
|
|
241
|
+
// time, which made workerd cancel the peer→coordinator PARENT RPC
|
|
242
|
+
// with the same overload error (verified prod 7c3f1b25:
|
|
243
|
+
// "[batch-fanout] aborted: ExecutionError: Durable Object is
|
|
244
|
+
// overloaded"). The semaphore moved the queue-age problem one layer
|
|
245
|
+
// up — same symptom, worse blast radius (whole-batch abort instead
|
|
246
|
+
// of per-package fail).
|
|
247
|
+
//
|
|
248
|
+
// Wave-2 (shared flush + adaptive shard cap, no semaphore): the
|
|
249
|
+
// producer-side fix. The peer-side install-batch-facet now shares
|
|
250
|
+
// ONE inode/chunk accumulator across all packages in a peer's
|
|
251
|
+
// shard (src/npm/install-batch-facet.ts), so 39 packages → ~3-5
|
|
252
|
+
// RPCs to coordinator instead of 39+. Combined with shard cap of 8
|
|
253
|
+
// (src/npm/installer.ts), 620 deps → 8 peers × ~3 flushes = ~24
|
|
254
|
+
// total writeBatchStream RPCs at the coordinator (vs 620+ pre-fix).
|
|
255
|
+
// Workerd's input-gate queue depth on the coordinator stays well
|
|
256
|
+
// under the queue-age threshold without any user-space semaphore.
|
|
257
|
+
const { decodeWriteBatchStream } = await import('../_shared/w7-frame.js');
|
|
258
|
+
const decoded = await decodeWriteBatchStream(stream);
|
|
259
|
+
return self.sqliteFs.writeStream({
|
|
260
|
+
inodes: decoded.inodes,
|
|
261
|
+
chunkIter: decoded.chunkIter,
|
|
262
|
+
deletePaths: decoded.deletePaths,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Bulk-write npm registry cache entries in ONE RPC. Used by the
|
|
267
|
+
* resolver-facet to flush a wave of resolved packages back to the
|
|
268
|
+
* supervisor without per-entry round-trips.
|
|
269
|
+
*
|
|
270
|
+
* Payload is the array of RegistryCacheEntry shapes from src/npm-cache.ts.
|
|
271
|
+
* Returns { written, failed } so the facet can surface partial-write
|
|
272
|
+
* warnings to the install log.
|
|
273
|
+
*/
|
|
274
|
+
export async function _rpcPutRegistryEntries(self, entries) {
|
|
275
|
+
self.ensureSqliteFs();
|
|
276
|
+
const npmCache = new NpmCache(self.ctx.storage.sql);
|
|
277
|
+
if (!Array.isArray(entries))
|
|
278
|
+
return { written: 0, failed: 0 };
|
|
279
|
+
return npmCache.putRegistryEntries(entries);
|
|
280
|
+
}
|
|
281
|
+
export async function _rpcStdout(self, pid, data) {
|
|
282
|
+
// Always buffer raw data (keeps ANSI for replay). Terminal paint only
|
|
283
|
+
// if someone is listening — detached sessions shouldn't silently lose
|
|
284
|
+
// output. Skip pid=0 (the supervisor-rpc fallback when no props.pid
|
|
285
|
+
// was threaded) to avoid polluting a sentinel slot with output from
|
|
286
|
+
// un-traceable facets.
|
|
287
|
+
try {
|
|
288
|
+
if (pid > 0)
|
|
289
|
+
self.processLogs.append(pid, 'stdout', data);
|
|
290
|
+
if (self.terminal)
|
|
291
|
+
self.terminal.write(data);
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
// Fix 5: surface RPC envelope errors when NIMBUS_DEBUG=1. Silent
|
|
295
|
+
// drops here are exactly what hides bugs; default-off so we don't
|
|
296
|
+
// blow up terminals with normal-operation noise, but diagnosable on
|
|
297
|
+
// demand.
|
|
298
|
+
if (self.nimbusDebug && self.terminal) {
|
|
299
|
+
try {
|
|
300
|
+
self.terminal.write(`\x1b[33m[rpc-error] _rpcStdout(pid=${pid}) threw: ${e?.message || e}\x1b[0m\r\n`);
|
|
301
|
+
}
|
|
302
|
+
catch { }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
export async function _rpcStderr(self, pid, data) {
|
|
307
|
+
try {
|
|
308
|
+
if (pid > 0)
|
|
309
|
+
self.processLogs.append(pid, 'stderr', data);
|
|
310
|
+
// Terminal gets red wrapping; the ring buffer keeps it raw so the
|
|
311
|
+
// stream tag can drive color decisions at replay time.
|
|
312
|
+
if (self.terminal)
|
|
313
|
+
self.terminal.write(`\x1b[31m${data}\x1b[0m`);
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
if (self.nimbusDebug && self.terminal) {
|
|
317
|
+
try {
|
|
318
|
+
self.terminal.write(`\x1b[33m[rpc-error] _rpcStderr(pid=${pid}) threw: ${e?.message || e}\x1b[0m\r\n`);
|
|
319
|
+
}
|
|
320
|
+
catch { }
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Called by facets from their `finally` block after I/O has drained.
|
|
326
|
+
* Marks the log store so `logs` / `ps` can show the exit code, and
|
|
327
|
+
* fires `_emitExitDump` if the process exited non-zero with buffered
|
|
328
|
+
* output.
|
|
329
|
+
*
|
|
330
|
+
* Idempotent — double-call is a no-op (ProcessLogStore.markExit guards).
|
|
331
|
+
*/
|
|
332
|
+
export async function _rpcReportExit(self, pid, code, tail) {
|
|
333
|
+
if (pid <= 0)
|
|
334
|
+
return; // Ignore the pid-0 sentinel.
|
|
335
|
+
if (tail)
|
|
336
|
+
self.processLogs.append(pid, 'stderr', tail);
|
|
337
|
+
// Guard against double-reporting: if we've already recorded exit
|
|
338
|
+
// (e.g. from an external kill path) don't dump twice.
|
|
339
|
+
if (self.processLogs.getExit(pid))
|
|
340
|
+
return;
|
|
341
|
+
self.processLogs.markExit(pid, code);
|
|
342
|
+
// Structured exit notification for the tabs UI. Idempotent on the
|
|
343
|
+
// client — subscribeExit fires once, and the shell-exec finalizer
|
|
344
|
+
// also emits, so we dedupe on pid there. Include the command (when
|
|
345
|
+
// available via ProcessTable) so the UI can surface a tab for pids
|
|
346
|
+
// whose spawn event was suppressed (e.g. `node -e` short evals).
|
|
347
|
+
const cmdFromTable = self.processTable.get(pid)?.command;
|
|
348
|
+
notifyTerminalEvent(self.terminal, { type: 'exit', pid, code, command: cmdFromTable });
|
|
349
|
+
// SHELL-FOLLOWUPS-5 (2026-05-11): only dump on non-zero exit.
|
|
350
|
+
//
|
|
351
|
+
// Pre-fix Fix-4 policy was "dump whenever the ring buffer has
|
|
352
|
+
// bytes, regardless of code" (intent: catch the
|
|
353
|
+
// clean-but-silent failure where stderr traceback was buffered
|
|
354
|
+
// but user wasn't watching). Real-world cost: every successful
|
|
355
|
+
// `node -e`, `python -c`, etc. printed stdout once live, then
|
|
356
|
+
// again as a post-exit dump — double-print on the happy path.
|
|
357
|
+
//
|
|
358
|
+
// New policy:
|
|
359
|
+
// - Non-zero exit AND non-empty buffer → dump (failure context)
|
|
360
|
+
// - Zero exit → no dump (live stream already showed it)
|
|
361
|
+
//
|
|
362
|
+
// Reconnect-replay path is preserved by the `logs <pid>` shell
|
|
363
|
+
// command + `/api/processes/<pid>/logs` endpoint, neither of
|
|
364
|
+
// which depends on the inline dump.
|
|
365
|
+
//
|
|
366
|
+
// NOTE: _emitShellExecDone (below) carries an identical gate;
|
|
367
|
+
// both paths must agree because either may fire first depending
|
|
368
|
+
// on facet vs. shell-finalizer ordering.
|
|
369
|
+
if (code !== 0 && self.processLogs.size(pid) > 0) {
|
|
370
|
+
self._emitExitDump(pid, code);
|
|
371
|
+
}
|
|
372
|
+
// Fix 5: verbose exit trace gated on NIMBUS_DEBUG=1. Facets already
|
|
373
|
+
// get a spawn banner via FacetManager.onSpawn; this closes the loop.
|
|
374
|
+
if (self.nimbusDebug && self.terminal) {
|
|
375
|
+
const entry = self.processTable.get(pid);
|
|
376
|
+
const cmd = entry?.command || `pid ${pid}`;
|
|
377
|
+
const colorExit = code === 0 ? '\x1b[2m' : '\x1b[2;31m';
|
|
378
|
+
self.terminal.write(`${colorExit}[facet exited: pid=${pid} code=${code} cmd="${cmd}"]\x1b[0m\r\n`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Emit a formatted exit-dump banner + last 30 lines of output to the
|
|
383
|
+
* terminal. Called from both the facet-reported exit path and the
|
|
384
|
+
* external-kill path (timeout / abort).
|
|
385
|
+
*
|
|
386
|
+
* Race notes:
|
|
387
|
+
* - Terminal.write is buffered with a 5ms flush; concurrent writes
|
|
388
|
+
* from facet stdout still in flight interleave cleanly at flush
|
|
389
|
+
* time.
|
|
390
|
+
* - If no terminal is attached, the dump is simply skipped — the
|
|
391
|
+
* log buffer still has everything, so `logs <pid>` recovers it.
|
|
392
|
+
*/
|
|
393
|
+
export function _emitExitDump(self, pid, code) {
|
|
394
|
+
if (!self.terminal)
|
|
395
|
+
return;
|
|
396
|
+
const entry = self.processTable.get(pid);
|
|
397
|
+
const cmd = entry?.command || `pid ${pid}`;
|
|
398
|
+
const chunks = self.processLogs.tail(pid, { lines: 30 });
|
|
399
|
+
const sep = '─'.repeat(60);
|
|
400
|
+
const color = code === 0 ? '\x1b[2;33m' : '\x1b[31m'; // yellow-dim for clean-silent
|
|
401
|
+
self.terminal.write(`\r\n${color}${sep}\r\n` +
|
|
402
|
+
`Process ${pid} (${cmd}) exited with code ${code}\r\n` +
|
|
403
|
+
`${sep}\x1b[0m\r\n`);
|
|
404
|
+
for (const c of chunks) {
|
|
405
|
+
const painted = c.stream === 'stderr' ? `\x1b[31m${c.data}\x1b[0m` : c.data;
|
|
406
|
+
self.terminal.write(painted);
|
|
407
|
+
}
|
|
408
|
+
self.terminal.write(`${color}${sep}\x1b[0m\r\n`);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Fix 3 + Fix 4 + Fix 5: finalizer for shellExecuteTracked.
|
|
412
|
+
*
|
|
413
|
+
* Runs after a tracked shell.execute finishes (any path). Chooses when
|
|
414
|
+
* to emit the exit-dump banner and when to log the debug trace.
|
|
415
|
+
*
|
|
416
|
+
* Dump policy (Fix 4):
|
|
417
|
+
* - Non-zero exit AND any buffered output → always dump.
|
|
418
|
+
* - Zero exit AND buffered output has >0 bytes → dump anyway. Rationale:
|
|
419
|
+
* an npm run that returned "success" while the ring buffer still has
|
|
420
|
+
* a traceback is the exact "clean-but-silent failure" we're hunting.
|
|
421
|
+
* The replay is unique information the user didn't see live (e.g.
|
|
422
|
+
* because the terminal was reconnected after the fact).
|
|
423
|
+
* - Zero exit AND empty buffer → nothing to say. Skip.
|
|
424
|
+
*
|
|
425
|
+
* Trace policy (Fix 5):
|
|
426
|
+
* - NIMBUS_DEBUG=1: always print `[exited pid=N code=C duration=Xms]`.
|
|
427
|
+
* - Default: print only for non-zero OR long-running scripts (the
|
|
428
|
+
* cmd-start banner makes them expect an exit marker).
|
|
429
|
+
*
|
|
430
|
+
* Called with the already-marked pid (processTable.exit + processLogs.markExit
|
|
431
|
+
* ran in shellExecuteTracked's finally).
|
|
432
|
+
*/
|
|
433
|
+
export function _emitShellExecDone(self, pid, cmd, code, durationMs) {
|
|
434
|
+
const bufSize = self.processLogs.size(pid);
|
|
435
|
+
// SHELL-FOLLOWUPS-5 (2026-05-11): only dump on non-zero exit.
|
|
436
|
+
//
|
|
437
|
+
// Pre-fix policy was "dump regardless of code when buffer non-empty"
|
|
438
|
+
// (Fix 4 from W3.5 era, intent: catch clean-but-silent failure
|
|
439
|
+
// where user couldn't see live output e.g. across reconnect).
|
|
440
|
+
//
|
|
441
|
+
// Real-world cost: for every successful `node -e`, `python -c`,
|
|
442
|
+
// `npx X --version`, etc., user saw output once live + once
|
|
443
|
+
// again in the post-exit dump. Annoying double-print for the
|
|
444
|
+
// common-case interactive session.
|
|
445
|
+
//
|
|
446
|
+
// New policy:
|
|
447
|
+
// - Non-zero exit AND non-empty buffer → dump (failure context)
|
|
448
|
+
// - Zero exit → no dump (live stream already showed it)
|
|
449
|
+
//
|
|
450
|
+
// Reconnect-replay path is preserved by the `logs <pid>` shell
|
|
451
|
+
// command + `/api/processes/<pid>/logs` endpoint, neither of
|
|
452
|
+
// which depends on the inline dump.
|
|
453
|
+
const shouldDump = bufSize > 0 && code !== 0;
|
|
454
|
+
if (shouldDump) {
|
|
455
|
+
self._emitExitDump(pid, code);
|
|
456
|
+
}
|
|
457
|
+
if (self.terminal) {
|
|
458
|
+
const traceAlways = self.nimbusDebug;
|
|
459
|
+
const isLongRunning = /^(vite|wrangler|next|nuxt|astro|remix|dev|serve|start|watch)\b/.test(cmd);
|
|
460
|
+
if (traceAlways || code !== 0 || isLongRunning) {
|
|
461
|
+
const colorExit = code === 0 ? '\x1b[2m' : '\x1b[2;31m';
|
|
462
|
+
self.terminal.write(`${colorExit}[shell exited: pid=${pid} code=${code} duration=${durationMs}ms]\x1b[0m\r\n`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* External-exit path: invoked by FacetManager when a process is killed
|
|
468
|
+
* outside the facet's own try/finally (timeout, explicit abort, or the
|
|
469
|
+
* `kill` shell command). Appends a synthetic stderr line so the dump
|
|
470
|
+
* has useful context, then runs the same dump machinery.
|
|
471
|
+
*/
|
|
472
|
+
export function _reportExternalExit(self, pid, code, reason) {
|
|
473
|
+
if (self.processLogs.getExit(pid))
|
|
474
|
+
return;
|
|
475
|
+
if (reason) {
|
|
476
|
+
self.processLogs.append(pid, 'stderr', `[process killed: ${reason}]\n`);
|
|
477
|
+
}
|
|
478
|
+
self.processLogs.markExit(pid, code, reason);
|
|
479
|
+
const cmdFromTable = self.processTable.get(pid)?.command;
|
|
480
|
+
notifyTerminalEvent(self.terminal, { type: 'exit', pid, code, reason, command: cmdFromTable });
|
|
481
|
+
if (self.terminal && self.processLogs.size(pid) > 0) {
|
|
482
|
+
self._emitExitDump(pid, code);
|
|
483
|
+
}
|
|
484
|
+
// W5 Lever 5: ring entry for every external exit with a non-zero
|
|
485
|
+
// code. The FacetManager already records its own exits inline via
|
|
486
|
+
// _w5RecordTermination — this catches the residual paths
|
|
487
|
+
// (timeouts dispatched via the timeout-handler in FacetManager
|
|
488
|
+
// call back through hooks.onExternalExit, which reaches here).
|
|
489
|
+
// The ring is bounded; double-recording is harmless.
|
|
490
|
+
if (code !== 0) {
|
|
491
|
+
try {
|
|
492
|
+
let cause = classifyError(reason);
|
|
493
|
+
if (code === 124 && cause === 'unknown')
|
|
494
|
+
cause = 'rpc_timeout';
|
|
495
|
+
recordFailure({
|
|
496
|
+
at: Date.now(),
|
|
497
|
+
phase: 'facet',
|
|
498
|
+
cause,
|
|
499
|
+
rssEstimateBytes: self._diagPeakRss,
|
|
500
|
+
heapUsedBytes: self._diagPeakHeapUsed,
|
|
501
|
+
lruBytes: 0, inFlightBytes: 0,
|
|
502
|
+
lastRpcFrame: getLastRpcFrame(),
|
|
503
|
+
lastFacetId: getLastFacetId(),
|
|
504
|
+
exitCode: code,
|
|
505
|
+
pid,
|
|
506
|
+
message: reason,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
catch { /* fail-soft */ }
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* W1: bootstrap the log-janitor alarm cycle.
|
|
514
|
+
*
|
|
515
|
+
* Schedules an alarm for `Date.now() + 60_000` with reason
|
|
516
|
+
* `'log-janitor'`. When it fires, `dispatchAlarm` (in
|
|
517
|
+
* ./hibernation.ts) runs `processLogs.dropOlderThan(orphanCheck)` and
|
|
518
|
+
* re-schedules the next 60s alarm — fully replacing the setTimeout
|
|
519
|
+
* chain we used to run.
|
|
520
|
+
*
|
|
521
|
+
* Why alarm-based instead of setTimeout: per Cloudflare DO docs
|
|
522
|
+
* (https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/),
|
|
523
|
+
* a recurring `setTimeout`/`setInterval` PREVENTS the DO from
|
|
524
|
+
* hibernating. That left every Nimbus session billed for duration
|
|
525
|
+
* continuously while in memory. Alarms persist across hibernation;
|
|
526
|
+
* the DO sleeps between fires, dropping per-session billable
|
|
527
|
+
* duration by ~99% during idle windows.
|
|
528
|
+
*
|
|
529
|
+
* Idempotent: guarded by `self.processLogsJanitorWired`. Safe to
|
|
530
|
+
* call repeatedly. Fail-soft: scheduleAlarm warn+returns on
|
|
531
|
+
* runtimes without setAlarm (older wrangler-dev).
|
|
532
|
+
*/
|
|
533
|
+
export function _ensureLogJanitor(self, ctx) {
|
|
534
|
+
if (self.processLogsJanitorWired)
|
|
535
|
+
return;
|
|
536
|
+
self.processLogsJanitorWired = true;
|
|
537
|
+
// Fire-and-forget: scheduleAlarm is async (storage IO) but the
|
|
538
|
+
// alarm bootstrap is non-blocking from the ctor's POV. Any throw
|
|
539
|
+
// is swallowed inside scheduleAlarm.
|
|
540
|
+
void (async () => {
|
|
541
|
+
try {
|
|
542
|
+
const { scheduleAlarm } = await import('./hibernation.js');
|
|
543
|
+
await scheduleAlarm(ctx, 'log-janitor', Date.now() + 60_000);
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
console.warn('[nimbus/W1] _ensureLogJanitor bootstrap threw:', e?.message);
|
|
547
|
+
}
|
|
548
|
+
})();
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* W1: orphan-pid predicate exposed for the alarm dispatcher. A pid is
|
|
552
|
+
* "orphaned" if the process table has no record of it — either reap()
|
|
553
|
+
* already removed it, or it never fully registered. Long-running
|
|
554
|
+
* facets that hang and get GC'd fall into this category.
|
|
555
|
+
*/
|
|
556
|
+
export function _logJanitorOrphanCheck(self) {
|
|
557
|
+
return (pid) => !self.processTable.get(pid);
|
|
558
|
+
}
|
|
559
|
+
export async function _rpcPrefetch(self, cwd, entryCode) {
|
|
560
|
+
// W2.6a: de-quarantined. require-resolver.ts is now the primary
|
|
561
|
+
// content-bundle source for FacetManager.exec via buildPrefetchBundle.
|
|
562
|
+
// This RPC entrypoint is retained for facet-side callers that may
|
|
563
|
+
// want to refresh the bundle mid-execution; today only the
|
|
564
|
+
// SupervisorRPC.prefetch surface exposes it externally.
|
|
565
|
+
self.ensureSqliteFs();
|
|
566
|
+
const { prefetchForRequire } = await import('../runtime/require-resolver.js');
|
|
567
|
+
return prefetchForRequire(self.sqliteFs, entryCode, cwd).bundle;
|
|
568
|
+
}
|
|
569
|
+
export async function _rpcRegisterPort(self, pid, port) {
|
|
570
|
+
// Port registration stores the facet association
|
|
571
|
+
// The actual facet stub is stored by FacetManager separately
|
|
572
|
+
self.portRegistry.register(port, pid, null);
|
|
573
|
+
}
|
|
574
|
+
export async function _rpcUnregisterPort(self, port) {
|
|
575
|
+
self.portRegistry.unregister(port);
|
|
576
|
+
}
|
|
577
|
+
export async function _rpcTransform(self, code, loader) {
|
|
578
|
+
if (!self.esbuildService) {
|
|
579
|
+
self.ensureSqliteFs();
|
|
580
|
+
self.esbuildService = new EsbuildService(self.sqliteFs);
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
const result = await self.esbuildService.transform(code, {
|
|
584
|
+
loader: loader || 'ts',
|
|
585
|
+
format: 'esm',
|
|
586
|
+
target: 'esnext',
|
|
587
|
+
sourcemap: 'inline',
|
|
588
|
+
});
|
|
589
|
+
return { code: result.code, map: result.map };
|
|
590
|
+
}
|
|
591
|
+
catch (e) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// ── child_process RPC entrypoints [W8 Phase 1] ────────────────────────
|
|
596
|
+
//
|
|
597
|
+
// Delegate to the lazily-constructed FacetProcessManager. Defensive
|
|
598
|
+
// ensureFacetProcessManager() handles cold-start cases where a child
|
|
599
|
+
// facet calls cp* before the supervisor has initialized the broker
|
|
600
|
+
// (e.g., immediately after DO hibernation wake-up).
|
|
601
|
+
export async function _rpcCpSpawn(self, req) {
|
|
602
|
+
const fpm = self._ensureFacetProcessManager();
|
|
603
|
+
return fpm.spawn(req);
|
|
604
|
+
}
|
|
605
|
+
export async function _rpcCpStdinWrite(self, childPid, data) {
|
|
606
|
+
const fpm = self._ensureFacetProcessManager();
|
|
607
|
+
return fpm.stdinWrite(childPid, data);
|
|
608
|
+
}
|
|
609
|
+
export async function _rpcCpStdinEnd(self, childPid) {
|
|
610
|
+
const fpm = self._ensureFacetProcessManager();
|
|
611
|
+
fpm.stdinEnd(childPid);
|
|
612
|
+
}
|
|
613
|
+
export async function _rpcCpReadOutput(self, childPid, fd, sinceSeq, waitMs) {
|
|
614
|
+
const fpm = self._ensureFacetProcessManager();
|
|
615
|
+
return fpm.readOutput(childPid, fd, sinceSeq, waitMs);
|
|
616
|
+
}
|
|
617
|
+
export async function _rpcCpDrainOutput(self, childPid) {
|
|
618
|
+
const fpm = self._ensureFacetProcessManager();
|
|
619
|
+
return fpm.drainOutput(childPid);
|
|
620
|
+
}
|
|
621
|
+
export async function _rpcCpKill(self, childPid, signal) {
|
|
622
|
+
const fpm = self._ensureFacetProcessManager();
|
|
623
|
+
return fpm.kill(childPid, signal);
|
|
624
|
+
}
|
|
625
|
+
export async function _rpcCpWait(self, childPid, waitMs) {
|
|
626
|
+
const fpm = self._ensureFacetProcessManager();
|
|
627
|
+
return fpm.wait(childPid, waitMs);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* arch-gaps gap #1: dispatch a single cp.spawn request inline using the
|
|
631
|
+
* existing pure-builtin / facet-direct logic, returning final stdout/
|
|
632
|
+
* stderr/exitCode rather than streaming via hooks. Called by
|
|
633
|
+
* spawn-facet.ts:runSpawnInIsolate from inside a fresh Worker Loader
|
|
634
|
+
* isolate (the per-spawn fresh-isolate envelope).
|
|
635
|
+
*
|
|
636
|
+
* The fpm exposes a `dispatchInline(req, kind)` that adapts the
|
|
637
|
+
* existing _dispatch path (originally hook-based) into a string-result
|
|
638
|
+
* shape. That adapter is responsible for ensuring stdout/stderr are
|
|
639
|
+
* accumulated inline rather than streamed.
|
|
640
|
+
*/
|
|
641
|
+
export async function _rpcCpDispatchInline(self, req, kind) {
|
|
642
|
+
const fpm = self._ensureFacetProcessManager();
|
|
643
|
+
return fpm.dispatchInline(req, kind);
|
|
644
|
+
}
|
|
645
|
+
// ── Legacy VFS RPC Entrypoints (direct method calls) ──────────────────
|
|
646
|
+
// Kept for backward compatibility with direct DO stub callers.
|
|
647
|
+
/** RPC: Read a file from the VFS. Returns ArrayBuffer or null. */
|
|
648
|
+
export function vfsReadFile(self, path) {
|
|
649
|
+
self.ensureSqliteFs();
|
|
650
|
+
try {
|
|
651
|
+
const stripped = path.replace(/^\/+/, '');
|
|
652
|
+
const data = self.sqliteFs.readFile(stripped);
|
|
653
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/** RPC: Read a file as string. Returns string or null. */
|
|
660
|
+
export function vfsReadFileString(self, path) {
|
|
661
|
+
self.ensureSqliteFs();
|
|
662
|
+
try {
|
|
663
|
+
const stripped = path.replace(/^\/+/, '');
|
|
664
|
+
return self.sqliteFs.readFileString(stripped);
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
/** RPC: Stat a path. Returns { type, size, mtime, mode } or null. */
|
|
671
|
+
export function vfsStat(self, path) {
|
|
672
|
+
self.ensureSqliteFs();
|
|
673
|
+
try {
|
|
674
|
+
const stripped = path.replace(/^\/+/, '');
|
|
675
|
+
return self.sqliteFs.stat(stripped);
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/** RPC: Check if path exists. */
|
|
682
|
+
export function vfsExists(self, path) {
|
|
683
|
+
self.ensureSqliteFs();
|
|
684
|
+
const stripped = path.replace(/^\/+/, '');
|
|
685
|
+
return self.sqliteFs.exists(stripped);
|
|
686
|
+
}
|
|
687
|
+
/** RPC: List directory contents. Returns array of { name, type }. */
|
|
688
|
+
export function vfsReaddir(self, path) {
|
|
689
|
+
self.ensureSqliteFs();
|
|
690
|
+
try {
|
|
691
|
+
const stripped = path.replace(/^\/+/, '');
|
|
692
|
+
return self.sqliteFs.readdir(stripped);
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
return [];
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/** RPC: Write a file to the VFS. */
|
|
699
|
+
export function vfsWriteFile(self, path, data) {
|
|
700
|
+
self.ensureSqliteFs();
|
|
701
|
+
const stripped = path.replace(/^\/+/, '');
|
|
702
|
+
self.sqliteFs.writeFile(stripped, new Uint8Array(data));
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* RPC: peer-DO execute leg of NimbusFanoutPool's POC B topology.
|
|
706
|
+
*
|
|
707
|
+
* Called by a coordinator NimbusSession DO via
|
|
708
|
+
* `env.NIMBUS_SESSION.idFromName(siblingName).get()._rpcFanoutExecute(...)`.
|
|
709
|
+
* THIS DO instance acts as a peer worker: it runs ONE NimbusLoaderPool
|
|
710
|
+
* over its assigned shard and returns the per-task results.
|
|
711
|
+
*
|
|
712
|
+
* Cap-sidestep mechanic
|
|
713
|
+
* ─────────────────────
|
|
714
|
+
* The supervisor's `submitMany` makes N RPC calls to N peer DOs.
|
|
715
|
+
* Each RPC is a stub.fetch / RPC method invocation, NOT an
|
|
716
|
+
* `env.LOADER.get()` from the supervisor's own method context — so
|
|
717
|
+
* those N calls don't count against the V8 4-loaders-per-method cap.
|
|
718
|
+
* Inside this RPC handler, we run a SINGLE LoaderPool with concurrency
|
|
719
|
+
* matching the shard size — and since the shard arrived via the peer
|
|
720
|
+
* router (capped at MAX_PEER_FANOUT = 32 peers, so each shard is
|
|
721
|
+
* ⌈totalTasks / 32⌉ wide), the in-DO pool stays well under 4.
|
|
722
|
+
*
|
|
723
|
+
* Failure model
|
|
724
|
+
* ─────────────
|
|
725
|
+
* Throws bubble back to the coordinator's RPC promise (rejects on
|
|
726
|
+
* the supervisor side). The coordinator's `submitMany` Promise.all
|
|
727
|
+
* surfaces the first reject; the install path treats it as a hard
|
|
728
|
+
* failure (matching today's single-facet `pool.submit` posture).
|
|
729
|
+
*
|
|
730
|
+
* Bytes-isolation
|
|
731
|
+
* ───────────────
|
|
732
|
+
* The fnSource string is forwarded verbatim into a fresh
|
|
733
|
+
* NimbusLoaderPool, which serializes it into the loader's worker
|
|
734
|
+
* code. No supervisor-side eval. Same trust posture as every other
|
|
735
|
+
* NimbusLoaderPool dispatch.
|
|
736
|
+
*/
|
|
737
|
+
export async function _rpcFanoutExecute(self, fnSource, args, poolOpts = {}) {
|
|
738
|
+
if (!Array.isArray(args)) {
|
|
739
|
+
throw new TypeError('_rpcFanoutExecute: args must be an array');
|
|
740
|
+
}
|
|
741
|
+
if (args.length === 0)
|
|
742
|
+
return { results: [] };
|
|
743
|
+
// Concurrency = shard size, capped at 4 (the V8 in-DO ceiling).
|
|
744
|
+
// Shard size on the coordinator side is at most ⌈totalTasks / N⌉
|
|
745
|
+
// where N <= MAX_PEER_FANOUT (32) — for typical 50-pkg installs
|
|
746
|
+
// with N=8 peers, that's 7 tasks per peer, capped to 4 here so
|
|
747
|
+
// each peer DO stays safely below the cap.
|
|
748
|
+
const concurrency = Math.min(args.length, 4);
|
|
749
|
+
const pool = new NimbusLoaderPool(self.env, self.ctx, {
|
|
750
|
+
concurrency,
|
|
751
|
+
timeoutMs: poolOpts.timeoutMs,
|
|
752
|
+
tag: poolOpts.tag ?? 'fanout-peer',
|
|
753
|
+
preamble: poolOpts.preamble,
|
|
754
|
+
wasmModules: poolOpts.wasmModules,
|
|
755
|
+
extraBindings: poolOpts.extraBindings,
|
|
756
|
+
omitSupervisor: poolOpts.omitSupervisor,
|
|
757
|
+
// INSTALL-HONESTY: route SUPERVISOR.* back to the coordinator
|
|
758
|
+
// (the user's session DO), not the peer DO. When undefined
|
|
759
|
+
// (back-compat with non-fanout callers), NimbusLoaderPool falls
|
|
760
|
+
// back to ctx.id.toString() — the legacy behavior, correct for
|
|
761
|
+
// single-DO callers.
|
|
762
|
+
supervisorDoIdOverride: poolOpts.coordinatorDoId,
|
|
763
|
+
});
|
|
764
|
+
try {
|
|
765
|
+
// mapSource accepts the pre-serialized fnSource forwarded by the
|
|
766
|
+
// coordinator (the function was already validated +
|
|
767
|
+
// serialized via serializeFunction on the coordinator side).
|
|
768
|
+
const results = await pool.mapSource(fnSource, args);
|
|
769
|
+
return { results };
|
|
770
|
+
}
|
|
771
|
+
finally {
|
|
772
|
+
try {
|
|
773
|
+
pool.dispose();
|
|
774
|
+
}
|
|
775
|
+
catch { /* best-effort */ }
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// ── Cache-observability stats forward (cache-observability wave) ──────
|
|
779
|
+
//
|
|
780
|
+
// SupervisorRPC handlers run in a SEPARATE isolate from the DO they
|
|
781
|
+
// service (loopback service-binding semantics). When they bump
|
|
782
|
+
// per-tier cache counters via src/_shared/cache-stats.ts, they bump
|
|
783
|
+
// the LOCAL singleton in the SupervisorRPC isolate. /api/_diag/cache
|
|
784
|
+
// reads the DO's singleton — different grid, no visibility.
|
|
785
|
+
//
|
|
786
|
+
// Fix: SupervisorRPC handlers forward the bump via this DO-side RPC.
|
|
787
|
+
// Pattern mirrors recordR2RaceCounters in installer.ts:1168 where the
|
|
788
|
+
// facet returns counters and the supervisor folds them into the DO
|
|
789
|
+
// singleton. Here the loopback boundary is the equivalent of the
|
|
790
|
+
// facet-supervisor boundary.
|
|
791
|
+
//
|
|
792
|
+
// One forward per (tier, kind, isHit, bytes) tuple. Batch via the
|
|
793
|
+
// `events` array so a single supervisor RPC handler can flush multiple
|
|
794
|
+
// bumps in one round-trip.
|
|
795
|
+
import { recordHit as _rpcRecordHit, recordMiss as _rpcRecordMiss } from '../_shared/cache-stats.js';
|
|
796
|
+
export async function _rpcRecordCacheStats(_self, events) {
|
|
797
|
+
// Defensive iteration — caller is in-house (supervisor-rpc.ts) but
|
|
798
|
+
// a malformed event must NOT throw and break the install. Iterate
|
|
799
|
+
// with type-narrowing; an unknown `kind` is silently skipped.
|
|
800
|
+
for (const e of events) {
|
|
801
|
+
if (e.kind === 'hit') {
|
|
802
|
+
_rpcRecordHit(e.tier, e.cacheKind, e.bytes);
|
|
803
|
+
}
|
|
804
|
+
else if (e.kind === 'miss') {
|
|
805
|
+
_rpcRecordMiss(e.tier, e.cacheKind);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|