@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,931 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus-wrangler.ts — Lightweight wrangler dev replacement for Nimbus.
|
|
3
|
+
*
|
|
4
|
+
* Architecture (from spec §8):
|
|
5
|
+
* nimbus-wrangler dev:
|
|
6
|
+
* 1. Reads wrangler.jsonc/toml from VFS
|
|
7
|
+
* 2. Bundles user's Worker code via EsbuildService
|
|
8
|
+
* 3. Creates a dynamic worker via LOADER.load() with the bundled code
|
|
9
|
+
* 4. Routes /worker/* requests to the dynamic worker's fetch()
|
|
10
|
+
* 5. On VFS file change: re-bundles and recreates the dynamic worker
|
|
11
|
+
* 6. Simulates KV via the worker's own storage
|
|
12
|
+
*
|
|
13
|
+
* The dynamic worker IS the user's Worker — running on the actual
|
|
14
|
+
* Cloudflare Workers runtime, not a simulation.
|
|
15
|
+
*/
|
|
16
|
+
import { normalizeVfsPath } from '../vfs/path.js';
|
|
17
|
+
import { registerInnerDoClass, clearInnerDoClasses } from '../facets/inner-do-registry.js';
|
|
18
|
+
import { KvEmulator } from '../bindings/kv.js';
|
|
19
|
+
import { D1Emulator } from '../bindings/d1.js';
|
|
20
|
+
import { R2Emulator } from '../bindings/r2.js';
|
|
21
|
+
// ── Proxy helpers ──────────────────────────────────────────────────────
|
|
22
|
+
/**
|
|
23
|
+
* Rewrite a Location header emitted by the inner Worker so that, when
|
|
24
|
+
* the browser follows it, the browser hits the outer-prefixed URL.
|
|
25
|
+
*
|
|
26
|
+
* Cases handled:
|
|
27
|
+
* - Same-origin absolute URL (http://localhost:.../s/foo/)
|
|
28
|
+
* → prepend outerWorkerBase to the pathname
|
|
29
|
+
* - Origin-relative (/s/foo/, /new)
|
|
30
|
+
* → prepend outerWorkerBase
|
|
31
|
+
* - Path-relative (foo/bar)
|
|
32
|
+
* → leave untouched (browser resolves against current URL, which
|
|
33
|
+
* is already outer-prefixed)
|
|
34
|
+
* - Cross-origin absolute URL (https://example.com/...)
|
|
35
|
+
* → leave untouched
|
|
36
|
+
* - Malformed / non-URL strings
|
|
37
|
+
* → leave untouched
|
|
38
|
+
*
|
|
39
|
+
* The outerWorkerBase is the full path prefix (e.g.
|
|
40
|
+
* "/s/nimble-otter-4271/worker"); do not include a trailing slash.
|
|
41
|
+
*/
|
|
42
|
+
export function rewriteLocationForOuter(location, outerWorkerBase, currentRequestUrl) {
|
|
43
|
+
if (!location)
|
|
44
|
+
return location;
|
|
45
|
+
const base = outerWorkerBase.replace(/\/+$/, '');
|
|
46
|
+
// Path-relative (no leading slash, no scheme): browser resolves
|
|
47
|
+
// against the current request URL, which is already outer-prefixed,
|
|
48
|
+
// so the right thing happens naturally.
|
|
49
|
+
if (!location.startsWith('/') && !/^[a-z]+:\/\//i.test(location)) {
|
|
50
|
+
return location;
|
|
51
|
+
}
|
|
52
|
+
// Origin-relative starting with /. Prepend the outer base.
|
|
53
|
+
if (location.startsWith('/')) {
|
|
54
|
+
// Don't double-prefix if the inner already emitted an outer path
|
|
55
|
+
// (defensive — it shouldn't, but caches / badly-behaved inner
|
|
56
|
+
// workers could produce one).
|
|
57
|
+
if (location.startsWith(base + '/') || location === base)
|
|
58
|
+
return location;
|
|
59
|
+
return base + location;
|
|
60
|
+
}
|
|
61
|
+
// Absolute URL. Rewrite only when same-origin; pass cross-origin through.
|
|
62
|
+
try {
|
|
63
|
+
const current = new URL(currentRequestUrl);
|
|
64
|
+
const loc = new URL(location);
|
|
65
|
+
if (loc.origin !== current.origin)
|
|
66
|
+
return location;
|
|
67
|
+
if (loc.pathname.startsWith(base + '/') || loc.pathname === base)
|
|
68
|
+
return location;
|
|
69
|
+
loc.pathname = base + loc.pathname;
|
|
70
|
+
return loc.toString();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return location;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Small HTML body that stands in for the inner Worker's missing-root
|
|
78
|
+
* handler. Nimbus, when loaded as an inner worker via worker_loaders,
|
|
79
|
+
* has no ASSETS binding to serve its landing page at `/`, so the raw
|
|
80
|
+
* src/index.ts fallthrough emits a 9-byte "Not found". This page
|
|
81
|
+
* replaces that so users visiting `/s/<outer>/worker/` see a useful
|
|
82
|
+
* orientation instead of a scary 404.
|
|
83
|
+
*
|
|
84
|
+
* Style is kept minimal and in line with the outer Nimbus "no dev
|
|
85
|
+
* server running" placeholder so the visual transition when wrangler
|
|
86
|
+
* comes up is not jarring.
|
|
87
|
+
*/
|
|
88
|
+
function renderWorkerRunningHtml(opts) {
|
|
89
|
+
const safeName = String(opts.workerName).replace(/[&<>"']/g, (c) => ({
|
|
90
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
91
|
+
}[c]));
|
|
92
|
+
const base = opts.outerWorkerBase.replace(/\/+$/, '');
|
|
93
|
+
const newHref = base + '/new';
|
|
94
|
+
return `<!DOCTYPE html>
|
|
95
|
+
<html lang="en"><head>
|
|
96
|
+
<meta charset="UTF-8">
|
|
97
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
98
|
+
<title>${safeName} — Worker running</title>
|
|
99
|
+
<style>
|
|
100
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
101
|
+
html,body{height:100%}
|
|
102
|
+
body{background:#0a0a0a;color:#e6edf3;font:15px/1.6 ui-sans-serif,-apple-system,"Segoe UI",sans-serif;
|
|
103
|
+
display:flex;align-items:center;justify-content:center;padding:24px;
|
|
104
|
+
background-image:radial-gradient(800px 500px at 50% -10%,rgba(100,255,218,0.06),transparent 60%)}
|
|
105
|
+
.card{max-width:560px;text-align:center}
|
|
106
|
+
.dot{width:8px;height:8px;border-radius:50%;background:#3fb950;display:inline-block;
|
|
107
|
+
box-shadow:0 0 6px rgba(63,185,80,0.8);animation:pulse 1.6s ease-in-out infinite;margin-right:8px;vertical-align:middle}
|
|
108
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
|
109
|
+
.badge{display:inline-block;padding:6px 14px;border:1px solid #30363d;border-radius:999px;
|
|
110
|
+
font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#8b949e;margin-bottom:18px}
|
|
111
|
+
h1{font-size:26px;color:#64ffda;margin-bottom:10px;font-family:ui-monospace,Menlo,monospace}
|
|
112
|
+
p{color:#8b949e;margin-bottom:22px}
|
|
113
|
+
code{font-family:ui-monospace,Menlo,monospace;background:#111;padding:2px 8px;border-radius:4px;color:#e6edf3;font-size:13px}
|
|
114
|
+
.links{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:12px}
|
|
115
|
+
a.btn{display:inline-block;background:#64ffda;color:#000;font-weight:700;padding:9px 18px;border-radius:6px;text-decoration:none}
|
|
116
|
+
a.btn.secondary{background:transparent;color:#8b949e;border:1px solid #30363d}
|
|
117
|
+
a.btn:hover{filter:brightness(1.1)}
|
|
118
|
+
</style></head>
|
|
119
|
+
<body><div class="card">
|
|
120
|
+
<span class="badge"><span class="dot"></span>${safeName} is running</span>
|
|
121
|
+
<h1>Worker has no route for <code>/</code></h1>
|
|
122
|
+
<p>The inner Worker is running, but it didn't define a handler for the root path. If this is Nimbus-in-Nimbus, visit <code>/new</code> to spawn a session.</p>
|
|
123
|
+
<div class="links">
|
|
124
|
+
<a class="btn" href="${newHref}">Open /new</a>
|
|
125
|
+
</div>
|
|
126
|
+
</div></body></html>`;
|
|
127
|
+
}
|
|
128
|
+
// ── NimbusWrangler ────────────────────────────────────────────────────────
|
|
129
|
+
export class NimbusWrangler {
|
|
130
|
+
vfs;
|
|
131
|
+
esbuild;
|
|
132
|
+
loaderEnv;
|
|
133
|
+
supervisorCtx; // ctx.facets for DO facets, ctx.exports for bindings
|
|
134
|
+
root;
|
|
135
|
+
onLog;
|
|
136
|
+
onHmrMessage;
|
|
137
|
+
running = false;
|
|
138
|
+
config = null;
|
|
139
|
+
workerStub = null;
|
|
140
|
+
buildVersion = 0;
|
|
141
|
+
unsubVfs = null;
|
|
142
|
+
rebuildTimer = null;
|
|
143
|
+
/** DO class map: binding name → DurableObjectClass from the inner worker. */
|
|
144
|
+
doClassMap = new Map();
|
|
145
|
+
/** Facet names we've created via ctx.facets.get — aborted on rebuild / stop. */
|
|
146
|
+
doFacetNames = new Set();
|
|
147
|
+
constructor(opts) {
|
|
148
|
+
this.vfs = opts.vfs;
|
|
149
|
+
this.esbuild = opts.esbuild;
|
|
150
|
+
this.loaderEnv = opts.env;
|
|
151
|
+
this.supervisorCtx = opts.ctx || null;
|
|
152
|
+
this.root = opts.root.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
153
|
+
this.onLog = opts.onLog;
|
|
154
|
+
this.onHmrMessage = opts.onHmrMessage;
|
|
155
|
+
}
|
|
156
|
+
/** Start the wrangler dev server. */
|
|
157
|
+
async start() {
|
|
158
|
+
// 1. Read wrangler config
|
|
159
|
+
this.config = this.readConfig();
|
|
160
|
+
if (!this.config) {
|
|
161
|
+
this.onLog('\x1b[31mNo wrangler.jsonc or wrangler.toml found in ' + this.root + '\x1b[0m\n');
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
// 2. Build and load the worker
|
|
165
|
+
const ok = await this.buildAndLoad();
|
|
166
|
+
if (!ok)
|
|
167
|
+
return false;
|
|
168
|
+
// 3. Watch for file changes
|
|
169
|
+
this.running = true;
|
|
170
|
+
this.unsubVfs = this.vfs.events.on((events) => {
|
|
171
|
+
this.handleVfsEvents(events);
|
|
172
|
+
});
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
/** Stop the wrangler dev server. */
|
|
176
|
+
stop() {
|
|
177
|
+
this.running = false;
|
|
178
|
+
if (this.unsubVfs) {
|
|
179
|
+
this.unsubVfs();
|
|
180
|
+
this.unsubVfs = null;
|
|
181
|
+
}
|
|
182
|
+
if (this.rebuildTimer) {
|
|
183
|
+
clearTimeout(this.rebuildTimer);
|
|
184
|
+
this.rebuildTimer = null;
|
|
185
|
+
}
|
|
186
|
+
// Abort live inner-DO facets so their stubs drop. Storage persists per
|
|
187
|
+
// docs — next start() or `nimbus-wrangler reset` controls deletion.
|
|
188
|
+
if (this.doFacetNames.size > 0 && this.supervisorCtx?.facets?.abort) {
|
|
189
|
+
for (const name of this.doFacetNames) {
|
|
190
|
+
try {
|
|
191
|
+
this.supervisorCtx.facets.abort(name, new Error('nimbus-wrangler stopped'));
|
|
192
|
+
}
|
|
193
|
+
catch { }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
this.doFacetNames.clear();
|
|
197
|
+
this.doClassMap.clear();
|
|
198
|
+
this.workerStub = null;
|
|
199
|
+
}
|
|
200
|
+
get isRunning() { return this.running; }
|
|
201
|
+
// ── Config reading ────────────────────────────────────────────────────
|
|
202
|
+
readConfig() {
|
|
203
|
+
// Try wrangler.jsonc first, then wrangler.json, then wrangler.toml
|
|
204
|
+
const jsonPaths = [
|
|
205
|
+
this.root + '/wrangler.jsonc',
|
|
206
|
+
this.root + '/wrangler.json',
|
|
207
|
+
];
|
|
208
|
+
for (const p of jsonPaths) {
|
|
209
|
+
if (this.vfs.exists(p)) {
|
|
210
|
+
try {
|
|
211
|
+
let text = this.vfs.readFileString(p);
|
|
212
|
+
// Strip JSONC comments while preserving content inside strings.
|
|
213
|
+
// Walk character by character, skip // and /* */ outside of quotes.
|
|
214
|
+
let cleaned = '';
|
|
215
|
+
let i = 0;
|
|
216
|
+
let inString = false;
|
|
217
|
+
while (i < text.length) {
|
|
218
|
+
if (inString) {
|
|
219
|
+
if (text[i] === '\\') {
|
|
220
|
+
cleaned += text[i] + (text[i + 1] || '');
|
|
221
|
+
i += 2;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (text[i] === '"')
|
|
225
|
+
inString = false;
|
|
226
|
+
cleaned += text[i];
|
|
227
|
+
i++;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
if (text[i] === '"') {
|
|
231
|
+
inString = true;
|
|
232
|
+
cleaned += text[i];
|
|
233
|
+
i++;
|
|
234
|
+
}
|
|
235
|
+
else if (text[i] === '/' && text[i + 1] === '/') {
|
|
236
|
+
while (i < text.length && text[i] !== '\n')
|
|
237
|
+
i++;
|
|
238
|
+
}
|
|
239
|
+
else if (text[i] === '/' && text[i + 1] === '*') {
|
|
240
|
+
i += 2;
|
|
241
|
+
while (i < text.length - 1 && !(text[i] === '*' && text[i + 1] === '/'))
|
|
242
|
+
i++;
|
|
243
|
+
i += 2;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
cleaned += text[i];
|
|
247
|
+
i++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return JSON.parse(cleaned);
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
this.onLog(`\x1b[33mWarning: could not parse ${p}: ${e?.message}\x1b[0m\n`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Try wrangler.toml (minimal parse)
|
|
259
|
+
const tomlPath = this.root + '/wrangler.toml';
|
|
260
|
+
if (this.vfs.exists(tomlPath)) {
|
|
261
|
+
try {
|
|
262
|
+
const text = this.vfs.readFileString(tomlPath);
|
|
263
|
+
return this.parseMinimalToml(text);
|
|
264
|
+
}
|
|
265
|
+
catch (e) {
|
|
266
|
+
this.onLog(`\x1b[33mWarning: could not parse ${tomlPath}: ${e?.message}\x1b[0m\n`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
/** Minimal TOML parser — handles key = "value" and main/name/compatibility_date. */
|
|
272
|
+
parseMinimalToml(text) {
|
|
273
|
+
const config = {};
|
|
274
|
+
for (const line of text.split('\n')) {
|
|
275
|
+
const trimmed = line.trim();
|
|
276
|
+
if (trimmed.startsWith('#') || !trimmed.includes('='))
|
|
277
|
+
continue;
|
|
278
|
+
const eqIdx = trimmed.indexOf('=');
|
|
279
|
+
const key = trimmed.substring(0, eqIdx).trim();
|
|
280
|
+
let val = trimmed.substring(eqIdx + 1).trim();
|
|
281
|
+
// Strip quotes
|
|
282
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
283
|
+
val = val.slice(1, -1);
|
|
284
|
+
}
|
|
285
|
+
if (key === 'main')
|
|
286
|
+
config.main = val;
|
|
287
|
+
else if (key === 'name')
|
|
288
|
+
config.name = val;
|
|
289
|
+
else if (key === 'compatibility_date')
|
|
290
|
+
config.compatibility_date = val;
|
|
291
|
+
}
|
|
292
|
+
return config;
|
|
293
|
+
}
|
|
294
|
+
// ── Build & load ──────────────────────────────────────────────────────
|
|
295
|
+
/**
|
|
296
|
+
* Resolve the user's `main:` entry to a canonical VFS-key path.
|
|
297
|
+
*
|
|
298
|
+
* The wrangler.jsonc `main` field is user-controlled and templates
|
|
299
|
+
* commonly emit any of:
|
|
300
|
+
* - "src/index.ts" — clean relative
|
|
301
|
+
* - "./src/index.ts" — npm-style with leading dot
|
|
302
|
+
* - "/src/index.ts" — workspace-absolute mistake
|
|
303
|
+
* - "src//index.ts" — accidental double-slash
|
|
304
|
+
*
|
|
305
|
+
* Naive `this.root + '/' + main` concatenation produced a malformed
|
|
306
|
+
* VFS key for every shape except the first, so vfs.exists() returned
|
|
307
|
+
* false even when the file was present at the canonical location.
|
|
308
|
+
*
|
|
309
|
+
* normalizeVfsPath collapses '.', '..', '//' runs and strips leading
|
|
310
|
+
* slashes — exactly the canonicalization SqliteVFS expects (every
|
|
311
|
+
* inode key in the live store is no-leading-slash, no internal
|
|
312
|
+
* doubles; src/vfs/sqlite-vfs.ts:_mkdirSingle uses the same
|
|
313
|
+
* `path.split('/').filter(Boolean)` shape via `normalizePath`).
|
|
314
|
+
*/
|
|
315
|
+
resolveEntryPath() {
|
|
316
|
+
return normalizeVfsPath(this.root + '/' + (this.config?.main ?? ''));
|
|
317
|
+
}
|
|
318
|
+
async buildAndLoad() {
|
|
319
|
+
if (!this.config?.main) {
|
|
320
|
+
this.onLog('\x1b[31mNo "main" entry point in wrangler config\x1b[0m\n');
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
const entryPoint = this.resolveEntryPath();
|
|
324
|
+
if (!this.vfs.exists(entryPoint)) {
|
|
325
|
+
this.onLog(`\x1b[31mEntry point not found: ${entryPoint}\x1b[0m\n`);
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
// Bundle via esbuild
|
|
329
|
+
this.onLog(' Building Worker...\n');
|
|
330
|
+
try {
|
|
331
|
+
// [WRANGLER-DEV-HANG P0b] Time-bound the bundle. Pre-fix,
|
|
332
|
+
// bundling Nimbus's own src/index.ts (~100 .ts files, ~25 MB
|
|
333
|
+
// closure) hung indefinitely with no error surfaced — neither
|
|
334
|
+
// result.errors[] nor !result.outputFiles[] paths fired because
|
|
335
|
+
// the await never returned. Likely cause(s): supervisor isolate
|
|
336
|
+
// OOM under the 64 MiB ceiling (esbuild-wasm baseline ~15-20 MiB
|
|
337
|
+
// + the AST of a 100-file source tree), or a vfsPlugin onResolve
|
|
338
|
+
// cycle. Either way the contract is "fail loud or succeed", not
|
|
339
|
+
// "hang silent" — see user-debug-transcript.txt lines 79-179
|
|
340
|
+
// (six attempts all hang at "Building Worker...").
|
|
341
|
+
//
|
|
342
|
+
// Timeout is generous (60s) so legitimate large-but-finishing
|
|
343
|
+
// builds aren't false-tripped. After timeout we surface a clear
|
|
344
|
+
// diagnostic with remediation hints.
|
|
345
|
+
const BUILD_TIMEOUT_MS = 60_000;
|
|
346
|
+
const buildOptions = {
|
|
347
|
+
bundle: true,
|
|
348
|
+
format: 'esm',
|
|
349
|
+
target: 'esnext',
|
|
350
|
+
platform: 'neutral',
|
|
351
|
+
minify: false,
|
|
352
|
+
// `node:*` handles prefixed imports; bare names (fs, path, etc.)
|
|
353
|
+
// are used by legacy CJS packages like esbuild-wasm's main.js.
|
|
354
|
+
// Mark both forms external so dynamic require('fs') round-trips
|
|
355
|
+
// to the inner runtime's nodejs_compat-provided modules.
|
|
356
|
+
external: [
|
|
357
|
+
'cloudflare:*',
|
|
358
|
+
'node:*',
|
|
359
|
+
'fs', 'path', 'os', 'crypto', 'util', 'stream', 'events',
|
|
360
|
+
'buffer', 'url', 'querystring', 'http', 'https', 'net', 'tls',
|
|
361
|
+
'child_process', 'worker_threads', 'perf_hooks', 'zlib',
|
|
362
|
+
'assert', 'fs/promises', 'process',
|
|
363
|
+
],
|
|
364
|
+
};
|
|
365
|
+
let timeoutHandle = null;
|
|
366
|
+
const result = await Promise.race([
|
|
367
|
+
this.esbuild.build([entryPoint], buildOptions),
|
|
368
|
+
new Promise((_, reject) => {
|
|
369
|
+
timeoutHandle = setTimeout(() => {
|
|
370
|
+
reject(new Error(`esbuild bundle exceeded ${BUILD_TIMEOUT_MS / 1000}s timeout. ` +
|
|
371
|
+
`entry=${entryPoint}; main='${this.config?.main}'. ` +
|
|
372
|
+
`Likely cause: supervisor heap pressure (64 MiB ceiling) or ` +
|
|
373
|
+
`large source closure. Mitigation: split worker into smaller ` +
|
|
374
|
+
`entries, reduce transitive imports, or bundle externally.`));
|
|
375
|
+
}, BUILD_TIMEOUT_MS);
|
|
376
|
+
}),
|
|
377
|
+
]).finally(() => { if (timeoutHandle)
|
|
378
|
+
clearTimeout(timeoutHandle); });
|
|
379
|
+
if (result.errors?.length) {
|
|
380
|
+
for (const e of result.errors) {
|
|
381
|
+
this.onLog(` \x1b[31merror: ${e.text}\x1b[0m\n`);
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
if (!result.outputFiles?.length) {
|
|
386
|
+
this.onLog(' \x1b[31mNo output from esbuild\x1b[0m\n');
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
const bundledCode = result.outputFiles[0].contents;
|
|
390
|
+
this.buildVersion++;
|
|
391
|
+
// On rebuild, abort any live DO facets so the NEXT get() runs the
|
|
392
|
+
// startup callback with the fresh class. Docs: abort invalidates
|
|
393
|
+
// all existing stubs but preserves storage; the next get() is the
|
|
394
|
+
// code-update pattern.
|
|
395
|
+
if (this.doFacetNames.size > 0 && this.supervisorCtx?.facets?.abort) {
|
|
396
|
+
for (const name of this.doFacetNames) {
|
|
397
|
+
try {
|
|
398
|
+
this.supervisorCtx.facets.abort(name, new Error('nimbus-wrangler rebuilding'));
|
|
399
|
+
}
|
|
400
|
+
catch { }
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Two-pass load to break the chicken-and-egg:
|
|
404
|
+
// Pass 1: load with NO env to extract any durable_objects classes.
|
|
405
|
+
// buildInnerEnv needs this.doClassMap to synthesize DO
|
|
406
|
+
// namespace bindings, but getDurableObjectClass() needs a
|
|
407
|
+
// worker stub, which we get only by loading.
|
|
408
|
+
// Pass 2: re-load with FULL env (including DO namespace shims)
|
|
409
|
+
// that the inner Worker actually uses.
|
|
410
|
+
//
|
|
411
|
+
// workerd caches by content hash, so the second load is cheap —
|
|
412
|
+
// it reuses the same compiled isolate and just swaps env.
|
|
413
|
+
const wrangCompatDate = this.config.compatibility_date || '2026-04-01';
|
|
414
|
+
// Filter flags that workerd refuses on dynamic-worker LOADER.load().
|
|
415
|
+
// `experimental` gates behind the parent process's --experimental
|
|
416
|
+
// CLI flag; dynamic workers can't inherit that, so workerd rejects
|
|
417
|
+
// the load with:
|
|
418
|
+
// "The compatibility flag experimental is experimental and may
|
|
419
|
+
// break or be removed in a future version of workerd. To use
|
|
420
|
+
// this flag, you must pass --experimental on the command line."
|
|
421
|
+
// The inner Worker rarely needs `experimental` — it's only required
|
|
422
|
+
// when the inner itself returns chained WorkerEntrypoint stubs
|
|
423
|
+
// across RPC (the NimbusLoaderRPC pattern, which WE implement but
|
|
424
|
+
// typical user Workers do not). Strip it here; document in the
|
|
425
|
+
// warning log so users who legitimately need it know.
|
|
426
|
+
const rawFlags = this.config.compatibility_flags || [];
|
|
427
|
+
const wrangCompatFlags = rawFlags.filter((f) => f !== 'experimental');
|
|
428
|
+
if (rawFlags.length !== wrangCompatFlags.length) {
|
|
429
|
+
this.onLog(` \x1b[2mnote: stripped 'experimental' from inner compat_flags (not propagatable via LOADER.load)\x1b[0m\n`);
|
|
430
|
+
}
|
|
431
|
+
const baseWorkerCode = {
|
|
432
|
+
compatibilityDate: wrangCompatDate,
|
|
433
|
+
compatibilityFlags: wrangCompatFlags,
|
|
434
|
+
mainModule: 'worker.js',
|
|
435
|
+
modules: { 'worker.js': bundledCode },
|
|
436
|
+
};
|
|
437
|
+
// Pass 1: class extraction (no env, no DO shims).
|
|
438
|
+
this.doClassMap.clear();
|
|
439
|
+
const doBindings = this.config.durable_objects?.bindings || [];
|
|
440
|
+
if (doBindings.length > 0) {
|
|
441
|
+
let probeWorker;
|
|
442
|
+
try {
|
|
443
|
+
probeWorker = this.loaderEnv.LOADER.load(baseWorkerCode);
|
|
444
|
+
}
|
|
445
|
+
catch (e) {
|
|
446
|
+
this.onLog(` \x1b[31mBuild error (probe load): ${e?.message || e}\x1b[0m\n`);
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
for (const b of doBindings) {
|
|
450
|
+
if (b.script_name && b.script_name !== this.config.name) {
|
|
451
|
+
this.onLog(` \x1b[31merror: durable_objects binding '${b.name}' references external script '${b.script_name}'. nimbus-wrangler cannot load external Workers by name.\x1b[0m\n`);
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const cls = probeWorker.getDurableObjectClass(b.class_name);
|
|
456
|
+
this.doClassMap.set(b.name, cls);
|
|
457
|
+
}
|
|
458
|
+
catch (e) {
|
|
459
|
+
this.onLog(` \x1b[31merror: durable_objects binding '${b.name}' => class '${b.class_name}' not found in bundle: ${e?.message || e}\x1b[0m\n`);
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Pass 2: real load with the full synthesized env. buildInnerEnv
|
|
465
|
+
// reads this.doClassMap populated above.
|
|
466
|
+
const innerEnv = this.buildInnerEnv();
|
|
467
|
+
const worker = this.loaderEnv.LOADER.load({
|
|
468
|
+
...baseWorkerCode,
|
|
469
|
+
env: innerEnv,
|
|
470
|
+
});
|
|
471
|
+
this.workerStub = worker.getEntrypoint();
|
|
472
|
+
for (const w of result.warnings || []) {
|
|
473
|
+
this.onLog(` \x1b[33mwarning: ${w.text}\x1b[0m\n`);
|
|
474
|
+
}
|
|
475
|
+
this.onLog(` \x1b[32mWorker built (v${this.buildVersion}, ${bundledCode.length} bytes)\x1b[0m\n`);
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
catch (e) {
|
|
479
|
+
this.onLog(` \x1b[31mBuild error: ${e?.message || e}\x1b[0m\n`);
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// ── Inner env synthesis ──────────────────────────────────────────────
|
|
484
|
+
//
|
|
485
|
+
// buildInnerEnv assembles the `env` object that the inner Worker's
|
|
486
|
+
// fetch(request, env, ctx) will see. Each binding category is built
|
|
487
|
+
// by a dedicated helper so they can be added/removed in isolation.
|
|
488
|
+
//
|
|
489
|
+
// Phases land in this order:
|
|
490
|
+
// Phase 0: vars + services (+ preflight of unsupported fields)
|
|
491
|
+
// Phase 1: assets → env[binding] = ctx.exports.NimbusAssetsRPC(...)
|
|
492
|
+
// Phase 2: worker_loaders → env[binding] = ctx.exports.NimbusLoaderRPC(...)
|
|
493
|
+
// Phase 3: durable_objects → env[binding] = synthesized DO namespace shim
|
|
494
|
+
//
|
|
495
|
+
// At the end of each phase the inner Worker has one more kind of binding
|
|
496
|
+
// it can access by name. Phase 0 is the plumbing: adding the `env` field
|
|
497
|
+
// to LOADER.load() at all, plus forwarding plain string vars.
|
|
498
|
+
buildInnerEnv() {
|
|
499
|
+
const env = {};
|
|
500
|
+
// ── vars ──
|
|
501
|
+
// Straight string copy. Collisions with synthesized binding names
|
|
502
|
+
// (later phases) would be surprising, so later phases will warn if
|
|
503
|
+
// they overwrite a vars-provided key.
|
|
504
|
+
if (this.config?.vars) {
|
|
505
|
+
for (const [k, v] of Object.entries(this.config.vars)) {
|
|
506
|
+
env[k] = v;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// ── services ──
|
|
510
|
+
// Honor a service binding only if the outer env has a field with the
|
|
511
|
+
// same NAME as the declared binding. In wrangler the `binding` field
|
|
512
|
+
// is the variable name inside the worker, while `service` is the name
|
|
513
|
+
// of the target deployed worker; in dev we only have the outer env's
|
|
514
|
+
// existing bindings to forward. Warn on every other case so the user
|
|
515
|
+
// knows not to expect real-wrangler passthrough.
|
|
516
|
+
if (this.config?.services?.length) {
|
|
517
|
+
for (const s of this.config.services) {
|
|
518
|
+
const name = s.binding;
|
|
519
|
+
if (!name)
|
|
520
|
+
continue;
|
|
521
|
+
if (this.loaderEnv && name in this.loaderEnv) {
|
|
522
|
+
env[name] = this.loaderEnv[name];
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
this.onLog(` \x1b[33mwarning: services binding '${name}' not present in outer env; env.${name} will be undefined\x1b[0m\n`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// ── assets ──
|
|
530
|
+
// Inner worker: env[binding].fetch(request) → reads a file under
|
|
531
|
+
// <vfsRoot>/<assetsDir>/<pathname>. Implemented via a WorkerEntrypoint
|
|
532
|
+
// loopback binding (NimbusAssetsRPC, re-exported from index.ts so
|
|
533
|
+
// ctx.exports auto-populates it).
|
|
534
|
+
if (this.config?.assets) {
|
|
535
|
+
const binding = this.config.assets.binding || 'ASSETS';
|
|
536
|
+
const directory = String(this.config.assets.directory || 'public');
|
|
537
|
+
const assetsDir = directory.replace(/^\.\//, '').replace(/^\/+/, '').replace(/\/+$/, '');
|
|
538
|
+
const ctxExports = this.supervisorCtx?.exports;
|
|
539
|
+
const doId = this.supervisorCtx?.id?.toString?.() || '';
|
|
540
|
+
if (!ctxExports?.NimbusAssetsRPC) {
|
|
541
|
+
this.onLog(` \x1b[33mwarning: ctx.exports.NimbusAssetsRPC unavailable; env.${binding} will not work\x1b[0m\n`);
|
|
542
|
+
}
|
|
543
|
+
else if (!doId) {
|
|
544
|
+
this.onLog(` \x1b[33mwarning: supervisor DO id unavailable; env.${binding} will not work\x1b[0m\n`);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
if (binding in env) {
|
|
548
|
+
this.onLog(` \x1b[33mwarning: assets binding '${binding}' overwrites a vars/services key with the same name\x1b[0m\n`);
|
|
549
|
+
}
|
|
550
|
+
env[binding] = ctxExports.NimbusAssetsRPC({
|
|
551
|
+
props: {
|
|
552
|
+
vfsRoot: this.root,
|
|
553
|
+
assetsDir,
|
|
554
|
+
doId,
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// ── worker_loaders ──
|
|
560
|
+
// Inner worker: env[binding].load({...}) / env[binding].get(id, cb).
|
|
561
|
+
// Implemented as a WorkerEntrypoint that forwards to the OUTER
|
|
562
|
+
// env.LOADER. Each inner call increments the `depth` prop; the
|
|
563
|
+
// handler rejects once depth exceeds the configured cap (default 4,
|
|
564
|
+
// override via the outer session's NIMBUS_INNER_LOADER_DEPTH env).
|
|
565
|
+
if (this.config?.worker_loaders?.length) {
|
|
566
|
+
const ctxExports = this.supervisorCtx?.exports;
|
|
567
|
+
if (!ctxExports?.NimbusLoaderRPC) {
|
|
568
|
+
for (const wl of this.config.worker_loaders) {
|
|
569
|
+
this.onLog(` \x1b[33mwarning: ctx.exports.NimbusLoaderRPC unavailable; env.${wl.binding} will not work\x1b[0m\n`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const currentDepth = this.loaderEnv?.NIMBUS_INNER_LOADER_DEPTH_CURRENT
|
|
574
|
+
? parseInt(String(this.loaderEnv.NIMBUS_INNER_LOADER_DEPTH_CURRENT), 10) || 0
|
|
575
|
+
: 0;
|
|
576
|
+
const nextDepth = currentDepth + 1;
|
|
577
|
+
for (const wl of this.config.worker_loaders) {
|
|
578
|
+
const binding = wl.binding;
|
|
579
|
+
if (!binding)
|
|
580
|
+
continue;
|
|
581
|
+
if (binding in env) {
|
|
582
|
+
this.onLog(` \x1b[33mwarning: worker_loaders binding '${binding}' overwrites a previous key\x1b[0m\n`);
|
|
583
|
+
}
|
|
584
|
+
env[binding] = ctxExports.NimbusLoaderRPC({
|
|
585
|
+
props: { depth: nextDepth },
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// ── durable_objects ──
|
|
591
|
+
// Inner worker: env[binding].idFromName(name) + env[binding].get(id).fetch(req).
|
|
592
|
+
// The class list was resolved and stored in this.doClassMap during
|
|
593
|
+
// buildAndLoad(); register each class into the module-level
|
|
594
|
+
// registry consulted by _rpcInnerDoFetch, then synthesize the
|
|
595
|
+
// namespace stub via ctx.exports.NimbusDurableObjectNamespace(...).
|
|
596
|
+
if (this.doClassMap.size > 0) {
|
|
597
|
+
const ctxExports = this.supervisorCtx?.exports;
|
|
598
|
+
const doId = this.supervisorCtx?.id?.toString?.() || '';
|
|
599
|
+
if (!ctxExports?.NimbusDurableObjectNamespace) {
|
|
600
|
+
for (const [bindingName] of this.doClassMap) {
|
|
601
|
+
this.onLog(` \x1b[33mwarning: ctx.exports.NimbusDurableObjectNamespace unavailable; env.${bindingName} will not work\x1b[0m\n`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
else if (!doId) {
|
|
605
|
+
for (const [bindingName] of this.doClassMap) {
|
|
606
|
+
this.onLog(` \x1b[33mwarning: supervisor DO id unavailable; env.${bindingName} will not work\x1b[0m\n`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
// Clear + repopulate the registry for this supervisor DO id.
|
|
611
|
+
clearInnerDoClasses(doId);
|
|
612
|
+
for (const [bindingName, cls] of this.doClassMap) {
|
|
613
|
+
registerInnerDoClass(doId, bindingName, cls);
|
|
614
|
+
if (bindingName in env) {
|
|
615
|
+
this.onLog(` \x1b[33mwarning: durable_objects binding '${bindingName}' overwrites a previous key\x1b[0m\n`);
|
|
616
|
+
}
|
|
617
|
+
env[bindingName] = ctxExports.NimbusDurableObjectNamespace({
|
|
618
|
+
props: {
|
|
619
|
+
bindingName,
|
|
620
|
+
supervisorDoId: doId,
|
|
621
|
+
},
|
|
622
|
+
});
|
|
623
|
+
// Track facet names we might create later so stop() can abort
|
|
624
|
+
// them cleanly. Exact ids aren't known until inner calls
|
|
625
|
+
// .get(id), so we just track the base name.
|
|
626
|
+
this.doFacetNames.add('innerDO-' + bindingName + '-*');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// ── kv_namespaces ── (W10)
|
|
631
|
+
// Inner worker: env[binding].get(key) / put / delete / list /
|
|
632
|
+
// getWithMetadata. Implementation is a plain JS class (KvEmulator)
|
|
633
|
+
// backed by SqliteVFS file blobs at <root>/.nimbus/kv/<binding>/.
|
|
634
|
+
// No ctx.exports loopback needed: KV is pure data, no callbacks
|
|
635
|
+
// back into the supervisor's fetch handlers. Each emulator instance
|
|
636
|
+
// is bound to one (binding, root) pair.
|
|
637
|
+
if (this.config?.kv_namespaces?.length) {
|
|
638
|
+
for (const kv of this.config.kv_namespaces) {
|
|
639
|
+
if (!kv.binding)
|
|
640
|
+
continue;
|
|
641
|
+
if (kv.binding in env) {
|
|
642
|
+
this.onLog(` \x1b[33mwarning: kv_namespaces binding '${kv.binding}' overwrites a previous key\x1b[0m\n`);
|
|
643
|
+
}
|
|
644
|
+
env[kv.binding] = new KvEmulator({
|
|
645
|
+
vfs: this.vfs,
|
|
646
|
+
root: this.root,
|
|
647
|
+
binding: kv.binding,
|
|
648
|
+
onLog: this.onLog,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// ── d1_databases ── (W10)
|
|
653
|
+
// Inner worker: env[binding].prepare(query) / batch / exec.
|
|
654
|
+
// Implementation: D1Emulator backed by the supervisor's SqlStorage
|
|
655
|
+
// with per-binding table-prefix isolation. See plan §14.1 — a
|
|
656
|
+
// child-DO-facet-per-binding upgrade is the W10.5 candidate.
|
|
657
|
+
//
|
|
658
|
+
// migrations_dir is honored: applyMigrations() walks the directory
|
|
659
|
+
// alphabetically and replays each .sql file once (idempotent via
|
|
660
|
+
// a per-binding ledger table).
|
|
661
|
+
if (this.config?.d1_databases?.length) {
|
|
662
|
+
const sqlStorage = this.supervisorCtx?.storage?.sql;
|
|
663
|
+
if (!sqlStorage) {
|
|
664
|
+
for (const d1 of this.config.d1_databases) {
|
|
665
|
+
this.onLog(` \x1b[33mwarning: ctx.storage.sql unavailable; env.${d1.binding} will not work\x1b[0m\n`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
for (const d1 of this.config.d1_databases) {
|
|
670
|
+
if (!d1.binding)
|
|
671
|
+
continue;
|
|
672
|
+
if (d1.binding in env) {
|
|
673
|
+
this.onLog(` \x1b[33mwarning: d1_databases binding '${d1.binding}' overwrites a previous key\x1b[0m\n`);
|
|
674
|
+
}
|
|
675
|
+
const emu = new D1Emulator({
|
|
676
|
+
sqlStorage,
|
|
677
|
+
binding: d1.binding,
|
|
678
|
+
vfs: this.vfs,
|
|
679
|
+
root: this.root,
|
|
680
|
+
migrationsDir: d1.migrations_dir,
|
|
681
|
+
onLog: this.onLog,
|
|
682
|
+
});
|
|
683
|
+
env[d1.binding] = emu;
|
|
684
|
+
// Fire migrations in the background. They're idempotent so
|
|
685
|
+
// racing rebuilds is safe.
|
|
686
|
+
if (d1.migrations_dir) {
|
|
687
|
+
emu.applyMigrations().then((r) => {
|
|
688
|
+
if (r.applied > 0) {
|
|
689
|
+
this.onLog(` \x1b[2mD1 ${d1.binding}: applied ${r.applied} migrations\x1b[0m\n`);
|
|
690
|
+
}
|
|
691
|
+
}).catch((e) => {
|
|
692
|
+
this.onLog(` \x1b[33mwarning: D1 ${d1.binding} migrations failed: ${e?.message || e}\x1b[0m\n`);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// ── r2_buckets ── (W10)
|
|
699
|
+
// Inner worker: env[binding].get(key) / put / head / list / delete.
|
|
700
|
+
// Implementation: R2Emulator backed by SqliteVFS file blobs at
|
|
701
|
+
// <root>/.nimbus/r2/<binding>/. Etag is sha256(body); conditionals
|
|
702
|
+
// (etagMatches/etagDoesNotMatch/uploaded*) are honored.
|
|
703
|
+
//
|
|
704
|
+
// Multipart upload methods throw a clear "not supported" error
|
|
705
|
+
// (W10.5 candidate; see plan §13 review B4).
|
|
706
|
+
if (this.config?.r2_buckets?.length) {
|
|
707
|
+
for (const r2 of this.config.r2_buckets) {
|
|
708
|
+
if (!r2.binding)
|
|
709
|
+
continue;
|
|
710
|
+
if (r2.binding in env) {
|
|
711
|
+
this.onLog(` \x1b[33mwarning: r2_buckets binding '${r2.binding}' overwrites a previous key\x1b[0m\n`);
|
|
712
|
+
}
|
|
713
|
+
env[r2.binding] = new R2Emulator({
|
|
714
|
+
vfs: this.vfs,
|
|
715
|
+
root: this.root,
|
|
716
|
+
binding: r2.binding,
|
|
717
|
+
onLog: this.onLog,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return env;
|
|
722
|
+
}
|
|
723
|
+
// ── VFS event handling (hot reload) ───────────────────────────────────
|
|
724
|
+
handleVfsEvents(events) {
|
|
725
|
+
if (!this.running)
|
|
726
|
+
return;
|
|
727
|
+
// Check if any changed file is under our project root
|
|
728
|
+
let needsRebuild = false;
|
|
729
|
+
for (const event of events) {
|
|
730
|
+
if (event.type !== 'change' && event.type !== 'add' && event.type !== 'unlink')
|
|
731
|
+
continue;
|
|
732
|
+
if (event.path.startsWith(this.root) &&
|
|
733
|
+
!event.path.includes('node_modules/') &&
|
|
734
|
+
!event.path.includes('/.nimbus/')) {
|
|
735
|
+
// W10: skip writes by KV/D1/R2 emulators (they live under
|
|
736
|
+
// <root>/.nimbus/{kv,r2}/...) — otherwise every emulator put
|
|
737
|
+
// triggers a rebuild and the system feedback-loops itself.
|
|
738
|
+
needsRebuild = true;
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (!needsRebuild)
|
|
743
|
+
return;
|
|
744
|
+
// Debounce rebuilds (250ms — allows rapid saves to coalesce)
|
|
745
|
+
if (this.rebuildTimer)
|
|
746
|
+
clearTimeout(this.rebuildTimer);
|
|
747
|
+
this.rebuildTimer = setTimeout(async () => {
|
|
748
|
+
this.rebuildTimer = null;
|
|
749
|
+
this.onLog('\n \x1b[33mFile change detected, rebuilding...\x1b[0m\n');
|
|
750
|
+
const ok = await this.buildAndLoad();
|
|
751
|
+
if (ok) {
|
|
752
|
+
this.onHmrMessage({ type: 'nimbus-hmr', event: 'full-reload' });
|
|
753
|
+
}
|
|
754
|
+
}, 250);
|
|
755
|
+
}
|
|
756
|
+
// ── Request handling ──────────────────────────────────────────────────
|
|
757
|
+
/**
|
|
758
|
+
* Forward a request to the user's Worker.
|
|
759
|
+
* Called from the DO's fetch() handler for /worker/* paths.
|
|
760
|
+
*
|
|
761
|
+
* Three things this proxy does besides a raw fetch:
|
|
762
|
+
* 1. Buffers the request body to an ArrayBuffer before forwarding.
|
|
763
|
+
* The inner Worker can legitimately respond with a 302 (e.g.
|
|
764
|
+
* Nimbus-in-Nimbus: POST /new → 302 /s/<id>/). Workerd's `fetch`
|
|
765
|
+
* defaults to follow-redirects which requires replaying the body;
|
|
766
|
+
* a stream body can only be read once. Buffering first sidesteps
|
|
767
|
+
* the "one-time-use body encountered a redirect" error. POSTs to
|
|
768
|
+
* Nimbus's own /new endpoint are typically empty, but KV/D1 sims
|
|
769
|
+
* and user workers may send non-trivial bodies.
|
|
770
|
+
* 2. Uses `redirect: 'manual'` so the inner's Location header comes
|
|
771
|
+
* back unchanged. We rewrite it below to prepend the outer worker
|
|
772
|
+
* prefix — otherwise the browser follows a bare `/s/<id>/` URL,
|
|
773
|
+
* which is an OUTER route and spawns a different session, not
|
|
774
|
+
* what the user wanted.
|
|
775
|
+
* 3. Replaces a "Not found" 404 at the inner root with a small
|
|
776
|
+
* landing page. Nimbus itself (the likely inner worker) has no
|
|
777
|
+
* route for `/` when loaded without an ASSETS binding — users
|
|
778
|
+
* hitting `/s/<outer>/worker/` directly would otherwise see a
|
|
779
|
+
* bare 9-byte "Not found" response and think the whole thing
|
|
780
|
+
* broke. The landing page explains the Worker is running and
|
|
781
|
+
* points them at `/worker/new` / their own routes.
|
|
782
|
+
*/
|
|
783
|
+
async handleRequest(request, pathname,
|
|
784
|
+
/**
|
|
785
|
+
* Outer-facing prefix of this proxy (e.g. "/s/nimble-otter-4271/worker").
|
|
786
|
+
* Used to rewrite inner-emitted Location headers that reference
|
|
787
|
+
* their own origin-relative paths. When absent, Location headers
|
|
788
|
+
* are passed through unchanged.
|
|
789
|
+
*/
|
|
790
|
+
outerWorkerBase) {
|
|
791
|
+
if (!this.workerStub) {
|
|
792
|
+
return new Response('Worker not loaded', { status: 503 });
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
// Build a new Request with the rewritten URL
|
|
796
|
+
const workerUrl = new URL(request.url);
|
|
797
|
+
workerUrl.pathname = pathname;
|
|
798
|
+
// Buffer the body up front. Necessary even when we set
|
|
799
|
+
// redirect: 'manual' (see below) because workerd's internal
|
|
800
|
+
// fetch plumbing can still try to replay a streaming body in
|
|
801
|
+
// some paths (e.g. retry-after errors). Capturing once here is
|
|
802
|
+
// both correct and cheap — user worker requests are small.
|
|
803
|
+
let bodyBuffer;
|
|
804
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
805
|
+
bodyBuffer = await request.arrayBuffer();
|
|
806
|
+
}
|
|
807
|
+
const workerReq = new Request(workerUrl.toString(), {
|
|
808
|
+
method: request.method,
|
|
809
|
+
headers: request.headers,
|
|
810
|
+
body: bodyBuffer,
|
|
811
|
+
// Don't auto-follow: a 302 to /s/<id>/ from the inner is its
|
|
812
|
+
// OWN path, not a path we can resolve — we need to rewrite
|
|
813
|
+
// and propagate it to the browser so the user lands on the
|
|
814
|
+
// correctly-prefixed outer URL.
|
|
815
|
+
redirect: 'manual',
|
|
816
|
+
});
|
|
817
|
+
const response = await this.workerStub.fetch(workerReq);
|
|
818
|
+
// ── Response post-processing ─────────────────────────────────
|
|
819
|
+
const newHeaders = new Headers(response.headers);
|
|
820
|
+
newHeaders.set('Access-Control-Allow-Origin', '*');
|
|
821
|
+
// (2) Rewrite Location headers so browser-follow lands on the
|
|
822
|
+
// outer-prefixed URL. Only rewrite for same-origin redirects —
|
|
823
|
+
// absolute cross-origin URLs pass through untouched.
|
|
824
|
+
if (outerWorkerBase && (response.status === 301 || response.status === 302 ||
|
|
825
|
+
response.status === 303 || response.status === 307 ||
|
|
826
|
+
response.status === 308)) {
|
|
827
|
+
const loc = response.headers.get('Location');
|
|
828
|
+
if (loc) {
|
|
829
|
+
const rewritten = rewriteLocationForOuter(loc, outerWorkerBase, request.url);
|
|
830
|
+
if (rewritten !== loc)
|
|
831
|
+
newHeaders.set('Location', rewritten);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
// (3) Replace a bare "Not found" at the root with a helpful landing.
|
|
835
|
+
// We detect this conservatively: only a literal "Not found" body
|
|
836
|
+
// with status 404 for a request to "/", which is exactly what
|
|
837
|
+
// Nimbus-as-inner emits when its default fallthrough runs. User
|
|
838
|
+
// workers that deliberately return 404 at other paths are untouched.
|
|
839
|
+
if (response.status === 404 && pathname === '/') {
|
|
840
|
+
const bodyText = await response.clone().text();
|
|
841
|
+
if (bodyText === 'Not found' || bodyText.length === 0) {
|
|
842
|
+
const html = renderWorkerRunningHtml({
|
|
843
|
+
workerName: this.config?.name || 'user worker',
|
|
844
|
+
outerWorkerBase: outerWorkerBase || '/worker',
|
|
845
|
+
});
|
|
846
|
+
return new Response(html, {
|
|
847
|
+
status: 200,
|
|
848
|
+
headers: {
|
|
849
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
850
|
+
'Cache-Control': 'no-store',
|
|
851
|
+
'Access-Control-Allow-Origin': '*',
|
|
852
|
+
},
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return new Response(response.body, {
|
|
857
|
+
status: response.status,
|
|
858
|
+
statusText: response.statusText,
|
|
859
|
+
headers: newHeaders,
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
catch (e) {
|
|
863
|
+
return Response.json({ error: e?.message || String(e) }, { status: 500, headers: { 'Access-Control-Allow-Origin': '*' } });
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
get stats() {
|
|
867
|
+
return {
|
|
868
|
+
running: this.running,
|
|
869
|
+
name: this.config?.name || 'unknown',
|
|
870
|
+
main: this.config?.main || 'unknown',
|
|
871
|
+
root: this.root,
|
|
872
|
+
buildVersion: this.buildVersion,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
// ── Test seams (W10 probes) ───────────────────────────────────────────
|
|
876
|
+
//
|
|
877
|
+
// These exist so probes can drive specific code paths (config parse,
|
|
878
|
+
// env synthesis, watcher installation) without running the full
|
|
879
|
+
// start() pipeline (which requires a real esbuild + LOADER + ctx).
|
|
880
|
+
//
|
|
881
|
+
// Production code does NOT use these; they're stable contracts only
|
|
882
|
+
// for the test probes. Naming convention: leading underscore + ForTest
|
|
883
|
+
// suffix.
|
|
884
|
+
/** @internal — test seam: parse the wrangler config and store it. Returns true on success. */
|
|
885
|
+
_readConfigForTest() {
|
|
886
|
+
this.config = this.readConfig();
|
|
887
|
+
return this.config != null;
|
|
888
|
+
}
|
|
889
|
+
/** @internal — test seam: invoke buildInnerEnv() without a probe-load pass. */
|
|
890
|
+
_buildInnerEnvForTest() {
|
|
891
|
+
return this.buildInnerEnv();
|
|
892
|
+
}
|
|
893
|
+
/** @internal — test seam: install the VFS file-watch listener and the
|
|
894
|
+
* mock-rebuild path (esbuild.build() is called, but the real
|
|
895
|
+
* buildAndLoad() pipeline is bypassed in favour of just calling
|
|
896
|
+
* esbuild). Used for hot-reload latency + nimbus-paths-not-watched
|
|
897
|
+
* probes. Production calls start() which installs the watcher AND the
|
|
898
|
+
* full rebuild pipeline. */
|
|
899
|
+
_installWatchersForTest() {
|
|
900
|
+
this.running = true;
|
|
901
|
+
this.unsubVfs = this.vfs.events.on(async (events) => {
|
|
902
|
+
let needsRebuild = false;
|
|
903
|
+
for (const event of events) {
|
|
904
|
+
if (event.type !== 'change' && event.type !== 'add' && event.type !== 'unlink')
|
|
905
|
+
continue;
|
|
906
|
+
if (event.path.startsWith(this.root) &&
|
|
907
|
+
!event.path.includes('node_modules/') &&
|
|
908
|
+
!event.path.includes('/.nimbus/')) {
|
|
909
|
+
needsRebuild = true;
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (!needsRebuild)
|
|
914
|
+
return;
|
|
915
|
+
if (this.rebuildTimer)
|
|
916
|
+
clearTimeout(this.rebuildTimer);
|
|
917
|
+
this.rebuildTimer = setTimeout(async () => {
|
|
918
|
+
this.rebuildTimer = null;
|
|
919
|
+
try {
|
|
920
|
+
// Same canonicalization as the initial build — see
|
|
921
|
+
// resolveEntryPath().
|
|
922
|
+
await this.esbuild.build([this.resolveEntryPath()], {
|
|
923
|
+
bundle: true, format: 'esm', target: 'esnext', platform: 'neutral',
|
|
924
|
+
});
|
|
925
|
+
this.onHmrMessage({ type: 'nimbus-hmr', event: 'full-reload' });
|
|
926
|
+
}
|
|
927
|
+
catch { }
|
|
928
|
+
}, 250);
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|