@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,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* r2-cache.ts — L3 cross-tenant npm cache backed by R2 [W4]
|
|
3
|
+
* + L2 colo cache via `caches.default` [cache-and-scrub P3]
|
|
4
|
+
*
|
|
5
|
+
* Purpose
|
|
6
|
+
* ───────
|
|
7
|
+
* Mirrors the Pyodide / Python Workers Package Bundling System pattern
|
|
8
|
+
* (EW/SPEC at wiki.cfdata.org). The supervisor DO already maintains a
|
|
9
|
+
* per-tenant SQLite cache (src/npm-cache.ts). This module adds an L3
|
|
10
|
+
* cross-tenant tier in R2 so that one tenant's first-install of a package
|
|
11
|
+
* benefits every subsequent tenant on the platform — the win that drops
|
|
12
|
+
* Mossaic-class cold installs from ~60 s to a target ≤ 15 s.
|
|
13
|
+
*
|
|
14
|
+
* Caching layers (top-down):
|
|
15
|
+
* L1 — per-DO SQLite (warmest, in-memory; ~1 ms per file)
|
|
16
|
+
* L2 — `caches.default` (per-colo; ~50-500 µs hit / ~5-30 ms cold) ← P3 wins
|
|
17
|
+
* L3 — R2 (global; ~30-100 ms regional) ← THIS MODULE
|
|
18
|
+
* L4 — registry.npmjs.org origin (~100-300 ms cross-region)
|
|
19
|
+
*
|
|
20
|
+
* Two buckets, two key shapes:
|
|
21
|
+
* tarball: `${R2_CACHE_PREFIX}/t/<name>/<version>.tgz`
|
|
22
|
+
* packument: `${R2_CACHE_PREFIX}/p/<name>.json`
|
|
23
|
+
*
|
|
24
|
+
* Why two buckets:
|
|
25
|
+
* Tarballs are immutable (npm name@version is content-fixed since 2018).
|
|
26
|
+
* Packuments must expire (5-min TTL). Different eviction policies →
|
|
27
|
+
* different buckets, so storage / quota / monitoring stay clean.
|
|
28
|
+
*
|
|
29
|
+
* Cache invalidation:
|
|
30
|
+
* 1. Time-based, packuments only — TTL encoded in customMetadata.expiresAt
|
|
31
|
+
* 2. Schema bump — bump R2_CACHE_PREFIX to invalidate everything atomically.
|
|
32
|
+
* Stale data is left in place; bucket lifecycle policy can sweep it.
|
|
33
|
+
* 3. Manual delete — deleteTarball / deletePackument; useful in incident
|
|
34
|
+
* response (a poisoned cache key needs purging).
|
|
35
|
+
*
|
|
36
|
+
* Graceful degrade:
|
|
37
|
+
* If env.NPM_TARBALL_CACHE / NPM_PACKUMENT_CACHE bindings are missing
|
|
38
|
+
* (deploy without buckets created, or wrangler dev without remote
|
|
39
|
+
* buckets), all R2 calls return null on read and no-op on write. The
|
|
40
|
+
* installer falls through to the existing network-fetch path with
|
|
41
|
+
* identical behaviour to today's main.
|
|
42
|
+
*
|
|
43
|
+
* NOT in W4 scope (deferred):
|
|
44
|
+
* - Streamed R2 reads via ReadableStream<Uint8Array> RPC return type
|
|
45
|
+
* (W7 — Streams over RPC). Today: bytes returned via structured-clone
|
|
46
|
+
* subject to the 32 MiB cap. Packages > 30 MiB skip the R2 path and
|
|
47
|
+
* stream directly from npm.
|
|
48
|
+
* - npm publish webhook → cache invalidation (CT2 future).
|
|
49
|
+
* - L2 Cache API (D3.5) — separate work.
|
|
50
|
+
*
|
|
51
|
+
* References:
|
|
52
|
+
* - audit/sections/CF-INTERNAL-OPTIMIZATION-RESEARCH.md §D
|
|
53
|
+
* - audit/_drafts/D-npm-install.md (per-section provenance)
|
|
54
|
+
* - audit/sections/W4-plan.md
|
|
55
|
+
*/
|
|
56
|
+
/** Schema version baked into every cache key. Bump to invalidate
|
|
57
|
+
* everything atomically (e.g. if the storage shape changes or a bug
|
|
58
|
+
* poisoned a class of keys). */
|
|
59
|
+
export const R2_CACHE_PREFIX = 'v1';
|
|
60
|
+
/** Packument TTL — 60 min (cache-observability wave; was 5 min pre-wave).
|
|
61
|
+
*
|
|
62
|
+
* Rationale for the bump (~12× longer than pre-wave):
|
|
63
|
+
*
|
|
64
|
+
* - npm registry packuments change ONLY when a new version of the
|
|
65
|
+
* package is published. For >99% of packages this is sub-weekly.
|
|
66
|
+
* - The 5-min TTL fired roughly once per minute on a long install
|
|
67
|
+
* session (resolver re-fetches as dep BFS re-encounters cached
|
|
68
|
+
* names with new range constraints). 12× fewer re-fetches at
|
|
69
|
+
* 60 min TTL means ~12× lower registry roundtrip count for an
|
|
70
|
+
* equivalent workload.
|
|
71
|
+
* - npm registry origin is shared across all Nimbus users; lower
|
|
72
|
+
* aggregate load is good citizenship and lowers our risk of being
|
|
73
|
+
* rate-limited at the L4 boundary.
|
|
74
|
+
*
|
|
75
|
+
* Trade-off (the cost of the bump):
|
|
76
|
+
*
|
|
77
|
+
* - A package version published less than 60 min ago may not be
|
|
78
|
+
* resolvable from a Nimbus session whose colo's L2 cache has a
|
|
79
|
+
* pre-publish entry. Worst case: a user `npm i my-fresh@1.0.1`
|
|
80
|
+
* seconds after publishing my-fresh@1.0.1 sees the cached 1.0.0
|
|
81
|
+
* entry for up to 60 min.
|
|
82
|
+
* - Workaround for users hitting freshness issues: open a new
|
|
83
|
+
* session in a different colo (cold L2), OR wait for the TTL,
|
|
84
|
+
* OR (future) add `NIMBUS_CACHE_TTL_MS` env override.
|
|
85
|
+
*
|
|
86
|
+
* Reversibility: redeploy with this constant restored to 5*60_000
|
|
87
|
+
* reverts the behavior instantly (next request reads the new TTL).
|
|
88
|
+
* No data migration; existing R2 packument entries' customMetadata
|
|
89
|
+
* .expiresAt stamps remain valid against either TTL. */
|
|
90
|
+
export const PACKUMENT_TTL_MS = 60 * 60_000;
|
|
91
|
+
/** Cap on tarball bytes returned via this RPC. Workerd structured-clone
|
|
92
|
+
* cap is 32 MiB; we keep a comfortable margin to leave room for RPC
|
|
93
|
+
* framing + the call's own arg bytes. Tarballs above this size skip
|
|
94
|
+
* the R2 path and go straight to the network — they're the long tail
|
|
95
|
+
* for which W7 (streams over RPC) will close the gap. */
|
|
96
|
+
export const MAX_R2_TARBALL_BYTES = 30 * 1024 * 1024;
|
|
97
|
+
// ── L2 (caches.default) helpers ─────────────────────────────────────────
|
|
98
|
+
//
|
|
99
|
+
// The Workers Cache API requires `Request` instances as keys. We
|
|
100
|
+
// synthesize stable URLs in a reserved-invalid namespace so they
|
|
101
|
+
// can't collide with any user-visible request. The TTL is encoded
|
|
102
|
+
// in the wrapped Response's `Cache-Control` header — the cache layer
|
|
103
|
+
// honours it on its own (no manual expiration check needed inside
|
|
104
|
+
// `cacheGetBytes`).
|
|
105
|
+
//
|
|
106
|
+
// Why a dedicated invalid host:
|
|
107
|
+
// - Prevents accidental collisions with same-origin user requests.
|
|
108
|
+
// - `nimbus-cache.invalid.` is reserved per RFC 6761 (`.invalid.`
|
|
109
|
+
// TLD) so it can never resolve and never escapes the worker.
|
|
110
|
+
/**
|
|
111
|
+
* Synthetic L2 cache-key host. RFC-6761 reserved TLD.
|
|
112
|
+
*
|
|
113
|
+
* CLN-1 (2026-05-11): exported so `session/routes.ts` cache-purge helpers
|
|
114
|
+
* can use the same constant instead of hardcoding the literal. Bumping
|
|
115
|
+
* the schema (e.g. `nimbus-cache-v2.invalid`) now only requires editing
|
|
116
|
+
* one line.
|
|
117
|
+
*/
|
|
118
|
+
export const L2_KEY_HOST = 'https://nimbus-cache.invalid';
|
|
119
|
+
/** Build the L2 cache-key Request for a packument name. */
|
|
120
|
+
function packumentL2Key(name) {
|
|
121
|
+
// encodeURIComponent on the name so '@scope/pkg' becomes a single
|
|
122
|
+
// path segment — matches our R2 key shape's `${name}.json` (R2 keys
|
|
123
|
+
// allow any UTF-8, but URL paths need encoding).
|
|
124
|
+
return new Request(`${L2_KEY_HOST}/${R2_CACHE_PREFIX}/p/${encodeURIComponent(name)}.json`);
|
|
125
|
+
}
|
|
126
|
+
/** Build the L2 cache-key Request for a tarball name+version. */
|
|
127
|
+
function tarballL2Key(name, version) {
|
|
128
|
+
return new Request(`${L2_KEY_HOST}/${R2_CACHE_PREFIX}/t/${encodeURIComponent(name)}/${encodeURIComponent(version)}.tgz`);
|
|
129
|
+
}
|
|
130
|
+
/** Build the L2 cache-key Request for an asset (e.g. esbuild-wasm). */
|
|
131
|
+
function assetL2Key(assetPath) {
|
|
132
|
+
// Caller passes an already-encoded path (e.g. "esbuild-0.24.2.wasm").
|
|
133
|
+
// Sanitize defensively in case a future caller passes a raw path
|
|
134
|
+
// with leading slashes or query strings.
|
|
135
|
+
const clean = assetPath.replace(/^\/+/, '').split('?')[0];
|
|
136
|
+
return new Request(`${L2_KEY_HOST}/${R2_CACHE_PREFIX}/a/${clean}`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Best-effort `caches.default` lookup. Returns null on miss / when the
|
|
140
|
+
* Cache API is not exposed (some test harnesses) / on any thrown error.
|
|
141
|
+
*
|
|
142
|
+
* The Cache API is bound to `caches.default` in workerd; we tolerate it
|
|
143
|
+
* being absent (e.g. a test harness with a stripped global) so the
|
|
144
|
+
* graceful-degrade contract from the original L3 layer extends to L2.
|
|
145
|
+
*/
|
|
146
|
+
async function l2Get(key) {
|
|
147
|
+
try {
|
|
148
|
+
const c = globalThis.caches;
|
|
149
|
+
if (!c?.default)
|
|
150
|
+
return null;
|
|
151
|
+
const r = await c.default.match(key);
|
|
152
|
+
return r ?? null;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Best-effort `caches.default` write. The body Response must have a
|
|
160
|
+
* `Cache-Control` header with `max-age` for the cache layer to honour
|
|
161
|
+
* a TTL — without it, the cache MAY refuse to store. We always set it
|
|
162
|
+
* at call sites (eternal for tarball/asset; 5 min for packument).
|
|
163
|
+
*
|
|
164
|
+
* Returns true on success, false on any thrown error. Failure here
|
|
165
|
+
* MUST NOT block the L3 hit — the wrap is a perf optimisation only.
|
|
166
|
+
*/
|
|
167
|
+
async function l2Put(key, body) {
|
|
168
|
+
try {
|
|
169
|
+
const c = globalThis.caches;
|
|
170
|
+
if (!c?.default)
|
|
171
|
+
return false;
|
|
172
|
+
await c.default.put(key, body);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ── Key helpers ─────────────────────────────────────────────────────────
|
|
180
|
+
/**
|
|
181
|
+
* Compose the R2 object key for a tarball.
|
|
182
|
+
*
|
|
183
|
+
* Scheme A from W4-plan §3: `t/<name>/<version>.tgz`. Scope-prefixed
|
|
184
|
+
* names (`@scope/pkg`) keep their `@` and `/` because R2 keys allow any
|
|
185
|
+
* UTF-8; we don't URL-encode them. Examples:
|
|
186
|
+
*
|
|
187
|
+
* react@19.0.0 → `v1/t/react/19.0.0.tgz`
|
|
188
|
+
* @vitejs/plugin-react@4.3.4 → `v1/t/@vitejs/plugin-react/4.3.4.tgz`
|
|
189
|
+
*
|
|
190
|
+
* The integrity digest is NOT in the key (Scheme B was rejected per
|
|
191
|
+
* W4-plan §3; reads validate integrity post-fetch). Scheme A enables
|
|
192
|
+
* pipelining: as soon as the resolver yields {name, version}, the
|
|
193
|
+
* install facet can speculatively kick off getTarball() in parallel
|
|
194
|
+
* with the network fetch.
|
|
195
|
+
*/
|
|
196
|
+
export function tarballKey(name, version) {
|
|
197
|
+
return `${R2_CACHE_PREFIX}/t/${name}/${version}.tgz`;
|
|
198
|
+
}
|
|
199
|
+
/** Compose the R2 object key for a packument. */
|
|
200
|
+
export function packumentKey(name) {
|
|
201
|
+
return `${R2_CACHE_PREFIX}/p/${name}.json`;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* R2-backed npm cache client. Wraps two R2 bindings (tarballs + packuments)
|
|
205
|
+
* with the get/put/delete shape the SupervisorRPC methods need, fronted
|
|
206
|
+
* by an L2 colo cache via `caches.default`.
|
|
207
|
+
*
|
|
208
|
+
* Constructed once per request-scope (typically inside SupervisorRPC
|
|
209
|
+
* methods). Cheap to instantiate — no async init.
|
|
210
|
+
*
|
|
211
|
+
* ALL methods are null-bucket safe: pass `null` for either binding and
|
|
212
|
+
* the corresponding read returns null / write is a no-op. The L2 layer
|
|
213
|
+
* is also null-safe: missing `caches.default` falls through to the
|
|
214
|
+
* graceful-degrade path that mirrors today's behaviour.
|
|
215
|
+
*/
|
|
216
|
+
export class R2CacheClient {
|
|
217
|
+
tarballBucket;
|
|
218
|
+
packumentBucket;
|
|
219
|
+
_l2HitsPackument = 0;
|
|
220
|
+
_l3GetsPackument = 0;
|
|
221
|
+
_l2HitsTarball = 0;
|
|
222
|
+
_l3GetsTarball = 0;
|
|
223
|
+
/**
|
|
224
|
+
* Cache-observability wave: per-call events accumulated for the
|
|
225
|
+
* caller (SupervisorRPC) to forward to the DO isolate's cache-stats
|
|
226
|
+
* singleton. Read via _cacheEvents and replaced with a fresh [] on
|
|
227
|
+
* drain. Public field so the caller in supervisor-rpc.ts can drain
|
|
228
|
+
* without an explicit method call (saves an indirection).
|
|
229
|
+
*/
|
|
230
|
+
_cacheEvents = [];
|
|
231
|
+
constructor(tarballBucket, packumentBucket) {
|
|
232
|
+
this.tarballBucket = tarballBucket;
|
|
233
|
+
this.packumentBucket = packumentBucket;
|
|
234
|
+
}
|
|
235
|
+
_recordHit(tier, cacheKind, bytes) {
|
|
236
|
+
this._cacheEvents.push({ kind: 'hit', tier, cacheKind, bytes });
|
|
237
|
+
}
|
|
238
|
+
_recordMiss(tier, cacheKind) {
|
|
239
|
+
this._cacheEvents.push({ kind: 'miss', tier, cacheKind });
|
|
240
|
+
}
|
|
241
|
+
/** Per-instance counter snapshot. Used by the L2 cache probes. */
|
|
242
|
+
stats() {
|
|
243
|
+
return {
|
|
244
|
+
l2HitsPackument: this._l2HitsPackument,
|
|
245
|
+
l3GetsPackument: this._l3GetsPackument,
|
|
246
|
+
l2HitsTarball: this._l2HitsTarball,
|
|
247
|
+
l3GetsTarball: this._l3GetsTarball,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get a cached tarball, or null if absent / oversize-bypassed.
|
|
252
|
+
*
|
|
253
|
+
* Returns the gzipped tar bytes as Uint8Array. Caller is responsible
|
|
254
|
+
* for integrity verification before consuming — we do NOT verify here
|
|
255
|
+
* because the caller (batch-facet) has the integrity hash from the
|
|
256
|
+
* resolver's packument.
|
|
257
|
+
*
|
|
258
|
+
* L2 (cache-and-scrub W-B): we wrap the R2 read in `caches.default`
|
|
259
|
+
* with `Cache-Control: public, max-age=31536000, immutable` because
|
|
260
|
+
* `name@version` is content-addressed (immutable npm contract since
|
|
261
|
+
* 2018). On miss, fall through to R2 and write through to L2.
|
|
262
|
+
*/
|
|
263
|
+
async getTarball(name, version) {
|
|
264
|
+
// ── L2 fast path (per-colo) ───────────────────────────────────
|
|
265
|
+
const l2Key = tarballL2Key(name, version);
|
|
266
|
+
const l2Hit = await l2Get(l2Key);
|
|
267
|
+
if (l2Hit) {
|
|
268
|
+
this._l2HitsTarball++;
|
|
269
|
+
const ab = await l2Hit.arrayBuffer();
|
|
270
|
+
if (ab.byteLength > MAX_R2_TARBALL_BYTES) {
|
|
271
|
+
// Defensive bypass — record as miss (callable returns null,
|
|
272
|
+
// caller MUST fall through to L4). The L2 entry technically
|
|
273
|
+
// existed but is unusable; the consumer's POV is "L2 didn't
|
|
274
|
+
// give me usable bytes" → miss.
|
|
275
|
+
this._recordMiss('L2', 'tarball');
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
this._recordHit('L2', 'tarball', ab.byteLength);
|
|
279
|
+
return new Uint8Array(ab);
|
|
280
|
+
}
|
|
281
|
+
this._recordMiss('L2', 'tarball');
|
|
282
|
+
// ── L3 path (cross-tenant) ────────────────────────────────────
|
|
283
|
+
if (!this.tarballBucket) {
|
|
284
|
+
// No L3 binding configured — treat as miss so downstream can
|
|
285
|
+
// fall through to L4. Distinguishes "binding absent" from
|
|
286
|
+
// "binding present and empty" in the byte-counter (a miss
|
|
287
|
+
// here means caller will fetch from L4).
|
|
288
|
+
this._recordMiss('L3', 'tarball');
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
this._l3GetsTarball++;
|
|
292
|
+
const key = tarballKey(name, version);
|
|
293
|
+
const obj = await this.tarballBucket.get(key);
|
|
294
|
+
if (!obj) {
|
|
295
|
+
this._recordMiss('L3', 'tarball');
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const ab = await obj.arrayBuffer();
|
|
299
|
+
if (ab.byteLength > MAX_R2_TARBALL_BYTES) {
|
|
300
|
+
// Defense-in-depth: a bug or admin-uploaded oversized tarball
|
|
301
|
+
// shouldn't blow the structured-clone cap on the way back to
|
|
302
|
+
// the facet. Treat as miss; original install path will handle it.
|
|
303
|
+
// (Counter perspective: tier-3 said "yes, I have it" but the
|
|
304
|
+
// payload is unusable here, so from the caller's POV it's a miss
|
|
305
|
+
// — they go to L4.)
|
|
306
|
+
this._recordMiss('L3', 'tarball');
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
this._recordHit('L3', 'tarball', ab.byteLength);
|
|
310
|
+
// Write through to L2. Eternal TTL is correct: the npm registry
|
|
311
|
+
// enforces immutability — `name@version` is content-fixed since
|
|
312
|
+
// 2018 (the unpublish window only allows hard-delete, never
|
|
313
|
+
// overwrite). Same posture R2 uses (no TTL on the bucket). Best-
|
|
314
|
+
// effort write: failure is silent.
|
|
315
|
+
const wb = new Uint8Array(ab);
|
|
316
|
+
// Pass a fresh Uint8Array to Response so the underlying buffer
|
|
317
|
+
// is not detached when the original ArrayBuffer is consumed by
|
|
318
|
+
// the caller. The caller receives `wb` (the same view), and the
|
|
319
|
+
// cache writes a copy — workerd serializes through structured
|
|
320
|
+
// clone for `caches.default.put`.
|
|
321
|
+
const writeBack = new Response(wb, {
|
|
322
|
+
headers: {
|
|
323
|
+
'Content-Type': 'application/gzip',
|
|
324
|
+
'Cache-Control': 'public, max-age=31536000, immutable',
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
// Await the put so subsequent reads of the same key strictly
|
|
328
|
+
// hit L2 (no double-fetch race during fill). See the matching
|
|
329
|
+
// note in getPackument above.
|
|
330
|
+
await l2Put(l2Key, writeBack);
|
|
331
|
+
return wb;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Write a tarball to R2. Bytes are stored as-is (gzipped tar). No-op
|
|
335
|
+
* if the bucket binding is missing.
|
|
336
|
+
*
|
|
337
|
+
* Caller must ensure bytes have already passed integrity verification.
|
|
338
|
+
* We accept ArrayBuffer or Uint8Array. Returns true on success, false
|
|
339
|
+
* on failure (the cache is best-effort; failure must not break the
|
|
340
|
+
* install).
|
|
341
|
+
*/
|
|
342
|
+
async putTarball(name, version, bytes) {
|
|
343
|
+
if (!this.tarballBucket)
|
|
344
|
+
return false;
|
|
345
|
+
const size = bytes instanceof ArrayBuffer ? bytes.byteLength : bytes.length;
|
|
346
|
+
if (size > MAX_R2_TARBALL_BYTES)
|
|
347
|
+
return false;
|
|
348
|
+
try {
|
|
349
|
+
await this.tarballBucket.put(tarballKey(name, version), bytes, {
|
|
350
|
+
httpMetadata: { contentType: 'application/gzip' },
|
|
351
|
+
});
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/** Delete a single tarball cache entry. Idempotent. */
|
|
359
|
+
async deleteTarball(name, version) {
|
|
360
|
+
if (!this.tarballBucket)
|
|
361
|
+
return false;
|
|
362
|
+
try {
|
|
363
|
+
await this.tarballBucket.delete(tarballKey(name, version));
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get a cached packument with its TTL state.
|
|
372
|
+
*
|
|
373
|
+
* Returns CachedPackument with `expired` set true when the entry's
|
|
374
|
+
* customMetadata.expiresAt is in the past. Callers MUST honour
|
|
375
|
+
* `expired` — only treat as a hot hit when false. Stale-while-error
|
|
376
|
+
* is the only valid use of expired data.
|
|
377
|
+
*
|
|
378
|
+
* L2 (cache-and-scrub W-A): we wrap the R2 read in `caches.default`.
|
|
379
|
+
* On hit, we read both the packument JSON and the absolute
|
|
380
|
+
* `expiresAt` timestamp from the L2 entry's headers — the absolute
|
|
381
|
+
* timestamp matters because L2 may serve a cached response near
|
|
382
|
+
* the end of its 5-min TTL, and the caller's `expired` check still
|
|
383
|
+
* needs to fire correctly. On miss, we fall through to R2 and
|
|
384
|
+
* write back to L2 with a 5-min `Cache-Control: max-age=300`
|
|
385
|
+
* (matching the existing R2 customMetadata.expiresAt semantic).
|
|
386
|
+
*/
|
|
387
|
+
async getPackument(name) {
|
|
388
|
+
// ── L2 fast path (per-colo) ───────────────────────────────────
|
|
389
|
+
const l2Key = packumentL2Key(name);
|
|
390
|
+
const l2Hit = await l2Get(l2Key);
|
|
391
|
+
if (l2Hit) {
|
|
392
|
+
this._l2HitsPackument++;
|
|
393
|
+
const json = await l2Hit.text();
|
|
394
|
+
// Record L2 hit by JSON text length. Note this happens BEFORE
|
|
395
|
+
// we check expired — an expired L2 entry is still served (caller
|
|
396
|
+
// honours expired flag); the hit-rate counts the cache lookup
|
|
397
|
+
// success, the staleness is a separate axis.
|
|
398
|
+
this._recordHit('L2', 'packument', json.length);
|
|
399
|
+
const now = Date.now();
|
|
400
|
+
// Reconstruct ageMs from the L2 response's `Date` header (set
|
|
401
|
+
// implicitly by the cache layer at put time). Cache API
|
|
402
|
+
// reflects it back as `Date` on hits; if absent, default to
|
|
403
|
+
// "fresh enough" (ageMs=0).
|
|
404
|
+
const dateHdr = l2Hit.headers.get('date');
|
|
405
|
+
const uploaded = dateHdr ? new Date(dateHdr).getTime() : now;
|
|
406
|
+
const ageMs = Math.max(0, now - (Number.isFinite(uploaded) ? uploaded : now));
|
|
407
|
+
// expiresAt is encoded into a custom header (`X-Nimbus-ExpiresAt`)
|
|
408
|
+
// because Cache-Control's relative max-age is honoured by the
|
|
409
|
+
// cache layer but doesn't survive readback as an absolute
|
|
410
|
+
// timestamp. Caller still needs the absolute boundary.
|
|
411
|
+
const expiresAtRaw = l2Hit.headers.get('x-nimbus-expiresat');
|
|
412
|
+
const expiresAt = expiresAtRaw ? Number(expiresAtRaw) : 0;
|
|
413
|
+
const expired = expiresAt > 0
|
|
414
|
+
? now >= expiresAt
|
|
415
|
+
: ageMs >= PACKUMENT_TTL_MS;
|
|
416
|
+
return { json, ageMs, expired };
|
|
417
|
+
}
|
|
418
|
+
this._recordMiss('L2', 'packument');
|
|
419
|
+
// ── L3 path (cross-tenant) ────────────────────────────────────
|
|
420
|
+
if (!this.packumentBucket) {
|
|
421
|
+
this._recordMiss('L3', 'packument');
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
this._l3GetsPackument++;
|
|
425
|
+
const obj = await this.packumentBucket.get(packumentKey(name));
|
|
426
|
+
if (!obj) {
|
|
427
|
+
this._recordMiss('L3', 'packument');
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
const json = await obj.text();
|
|
431
|
+
this._recordHit('L3', 'packument', json.length);
|
|
432
|
+
const now = Date.now();
|
|
433
|
+
const uploaded = obj.uploaded?.getTime() ?? now;
|
|
434
|
+
const ageMs = Math.max(0, now - uploaded);
|
|
435
|
+
const expiresAtRaw = obj.customMetadata?.expiresAt;
|
|
436
|
+
const expiresAt = expiresAtRaw ? Number(expiresAtRaw) : 0;
|
|
437
|
+
const expired = expiresAt > 0
|
|
438
|
+
? now >= expiresAt
|
|
439
|
+
: ageMs >= PACKUMENT_TTL_MS;
|
|
440
|
+
// Write through to L2 — bounded by max-age=300 to match the
|
|
441
|
+
// existing R2 customMetadata.expiresAt 5-min TTL. We pass the
|
|
442
|
+
// absolute expiresAt as a custom header so reads can re-check
|
|
443
|
+
// the boundary even if the cache layer extends our entry.
|
|
444
|
+
// Best-effort: failure is silent (false return ignored).
|
|
445
|
+
if (!expired) {
|
|
446
|
+
const ttlSec = Math.max(1, Math.floor((expiresAt > 0 ? expiresAt - now : PACKUMENT_TTL_MS) / 1000));
|
|
447
|
+
const writeBack = new Response(json, {
|
|
448
|
+
headers: {
|
|
449
|
+
'Content-Type': 'application/json',
|
|
450
|
+
'Cache-Control': `public, max-age=${ttlSec}`,
|
|
451
|
+
'X-Nimbus-ExpiresAt': String(expiresAt > 0 ? expiresAt : now + PACKUMENT_TTL_MS),
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
// Await the L2 put so the entry is durable before we return.
|
|
455
|
+
// Two callers reading the same key back-to-back during the
|
|
456
|
+
// fill window would otherwise both miss L2 and double-fetch
|
|
457
|
+
// L3. The cost (~1-3 ms in workerd local; sub-ms at edge) is
|
|
458
|
+
// bounded by the response size and only paid on cold reads.
|
|
459
|
+
// Errors are swallowed by l2Put — failure is silent.
|
|
460
|
+
await l2Put(l2Key, writeBack);
|
|
461
|
+
}
|
|
462
|
+
return { json, ageMs, expired };
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Write a packument JSON to R2 with a TTL stamp in customMetadata.
|
|
466
|
+
* No-op if the bucket binding is missing.
|
|
467
|
+
*
|
|
468
|
+
* Returns true on success, false on failure (same best-effort posture
|
|
469
|
+
* as putTarball).
|
|
470
|
+
*/
|
|
471
|
+
async putPackument(name, json) {
|
|
472
|
+
if (!this.packumentBucket)
|
|
473
|
+
return false;
|
|
474
|
+
const expiresAt = Date.now() + PACKUMENT_TTL_MS;
|
|
475
|
+
try {
|
|
476
|
+
await this.packumentBucket.put(packumentKey(name), json, {
|
|
477
|
+
httpMetadata: { contentType: 'application/json' },
|
|
478
|
+
customMetadata: { expiresAt: String(expiresAt) },
|
|
479
|
+
});
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/** Delete a single packument cache entry. Idempotent. */
|
|
487
|
+
async deletePackument(name) {
|
|
488
|
+
if (!this.packumentBucket)
|
|
489
|
+
return false;
|
|
490
|
+
try {
|
|
491
|
+
await this.packumentBucket.delete(packumentKey(name));
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/** Lightweight feature-detection for callers that want to log path. */
|
|
499
|
+
hasTarballBucket() {
|
|
500
|
+
return this.tarballBucket !== null;
|
|
501
|
+
}
|
|
502
|
+
/** Lightweight feature-detection for callers that want to log path. */
|
|
503
|
+
hasPackumentBucket() {
|
|
504
|
+
return this.packumentBucket !== null;
|
|
505
|
+
}
|
|
506
|
+
}
|