@statewalker/cellb 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +323 -2
- package/dist/adapters/files-backup.d.ts +16 -0
- package/dist/adapters/files-backup.js +32 -0
- package/dist/adapters/files-backup.js.map +1 -0
- package/dist/adapters/files-changelog.d.ts +33 -0
- package/dist/adapters/files-changelog.js +96 -0
- package/dist/adapters/files-changelog.js.map +1 -0
- package/dist/adapters/files-ltx.d.ts +35 -0
- package/dist/adapters/files-ltx.js +9 -0
- package/dist/adapters/files-ltx.js.map +1 -0
- package/dist/adapters/ltx-vfs.d.ts +21 -0
- package/dist/adapters/ltx-vfs.js +286 -0
- package/dist/adapters/ltx-vfs.js.map +1 -0
- package/dist/adapters/node-sqlite.d.ts +45 -0
- package/dist/adapters/node-sqlite.js +254 -0
- package/dist/adapters/node-sqlite.js.map +1 -0
- package/dist/adapters/wasm-page-hook.d.ts +26 -0
- package/dist/adapters/wasm-page-hook.js +55 -0
- package/dist/adapters/wasm-page-hook.js.map +1 -0
- package/dist/adapters/wasm-session.d.ts +11 -0
- package/dist/adapters/wasm-session.js +76 -0
- package/dist/adapters/wasm-session.js.map +1 -0
- package/dist/adapters/wasm-sqlite.d.ts +89 -0
- package/dist/adapters/wasm-sqlite.js +258 -0
- package/dist/adapters/wasm-sqlite.js.map +1 -0
- package/dist/bindings/assets.d.ts +26 -0
- package/dist/bindings/assets.js +260 -0
- package/dist/bindings/assets.js.map +1 -0
- package/dist/bindings/cache.d.ts +16 -0
- package/dist/bindings/cache.js +34 -0
- package/dist/bindings/cache.js.map +1 -0
- package/dist/bindings/celld-kv.d.ts +26 -0
- package/dist/bindings/celld-kv.js +81 -0
- package/dist/bindings/celld-kv.js.map +1 -0
- package/dist/bindings/celld-queues.d.ts +56 -0
- package/dist/bindings/celld-queues.js +223 -0
- package/dist/bindings/celld-queues.js.map +1 -0
- package/dist/bindings/celld-workflows.d.ts +22 -0
- package/dist/bindings/celld-workflows.js +54 -0
- package/dist/bindings/celld-workflows.js.map +1 -0
- package/dist/bindings/cron.d.ts +27 -0
- package/dist/bindings/cron.js +232 -0
- package/dist/bindings/cron.js.map +1 -0
- package/dist/bindings/d1-migrations.d.ts +22 -0
- package/dist/bindings/d1-migrations.js +32 -0
- package/dist/bindings/d1-migrations.js.map +1 -0
- package/dist/bindings/html-rewriter.d.ts +8 -0
- package/dist/bindings/html-rewriter.js +75 -0
- package/dist/bindings/html-rewriter.js.map +1 -0
- package/dist/bindings/r2-celld.d.ts +3 -0
- package/dist/bindings/r2-celld.js +113 -0
- package/dist/bindings/r2-celld.js.map +1 -0
- package/dist/bindings/r2.d.ts +236 -0
- package/dist/bindings/r2.js +419 -0
- package/dist/bindings/r2.js.map +1 -0
- package/dist/bindings/worker-loader.d.ts +83 -0
- package/dist/bindings/worker-loader.js +183 -0
- package/dist/bindings/worker-loader.js.map +1 -0
- package/dist/bindings/workers-ai.d.ts +39 -0
- package/dist/bindings/workers-ai.js +175 -0
- package/dist/bindings/workers-ai.js.map +1 -0
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +8 -0
- package/dist/browser.js.map +1 -0
- package/dist/bucket/celld-cells.d.ts +86 -0
- package/dist/bucket/celld-cells.js +273 -0
- package/dist/bucket/celld-cells.js.map +1 -0
- package/dist/bucket/celld-format.d.ts +10 -0
- package/dist/bucket/celld-format.js +27 -0
- package/dist/bucket/celld-format.js.map +1 -0
- package/dist/bucket/idb-object-store.d.ts +9 -0
- package/dist/bucket/idb-object-store.js +73 -0
- package/dist/bucket/idb-object-store.js.map +1 -0
- package/dist/bucket/ltx-log.d.ts +34 -0
- package/dist/bucket/ltx-log.js +289 -0
- package/dist/bucket/ltx-log.js.map +1 -0
- package/dist/bucket/object-store.d.ts +51 -0
- package/dist/bucket/object-store.js +114 -0
- package/dist/bucket/object-store.js.map +1 -0
- package/dist/bucket/s3-object-store.d.ts +25 -0
- package/dist/bucket/s3-object-store.js +151 -0
- package/dist/bucket/s3-object-store.js.map +1 -0
- package/dist/bucket/sqlite-object-store.d.ts +10 -0
- package/dist/bucket/sqlite-object-store.js +107 -0
- package/dist/bucket/sqlite-object-store.js.map +1 -0
- package/dist/core/alarms.d.ts +27 -0
- package/dist/core/alarms.js +88 -0
- package/dist/core/alarms.js.map +1 -0
- package/dist/core/async-context.d.ts +34 -0
- package/dist/core/async-context.js +50 -0
- package/dist/core/async-context.js.map +1 -0
- package/dist/core/celld-schema.d.ts +14 -0
- package/dist/core/celld-schema.js +34 -0
- package/dist/core/celld-schema.js.map +1 -0
- package/dist/core/durable-object.d.ts +28 -0
- package/dist/core/durable-object.js +31 -0
- package/dist/core/durable-object.js.map +1 -0
- package/dist/core/gates.d.ts +33 -0
- package/dist/core/gates.js +89 -0
- package/dist/core/gates.js.map +1 -0
- package/dist/core/handles.d.ts +90 -0
- package/dist/core/handles.js +224 -0
- package/dist/core/handles.js.map +1 -0
- package/dist/core/id.d.ts +19 -0
- package/dist/core/id.js +64 -0
- package/dist/core/id.js.map +1 -0
- package/dist/core/namespace.d.ts +38 -0
- package/dist/core/namespace.js +141 -0
- package/dist/core/namespace.js.map +1 -0
- package/dist/core/object-host.d.ts +91 -0
- package/dist/core/object-host.js +616 -0
- package/dist/core/object-host.js.map +1 -0
- package/dist/core/request-body.d.ts +9 -0
- package/dist/core/request-body.js +55 -0
- package/dist/core/request-body.js.map +1 -0
- package/dist/core/runtime.d.ts +121 -0
- package/dist/core/runtime.js +366 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/shard.d.ts +7 -0
- package/dist/core/shard.js +10 -0
- package/dist/core/shard.js.map +1 -0
- package/dist/core/sql-split.d.ts +15 -0
- package/dist/core/sql-split.js +101 -0
- package/dist/core/sql-split.js.map +1 -0
- package/dist/core/sql-storage.d.ts +53 -0
- package/dist/core/sql-storage.js +407 -0
- package/dist/core/sql-storage.js.map +1 -0
- package/dist/core/state.d.ts +44 -0
- package/dist/core/state.js +93 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/storage.d.ts +103 -0
- package/dist/core/storage.js +351 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/worker-rejections.d.ts +21 -0
- package/dist/core/worker-rejections.js +55 -0
- package/dist/core/worker-rejections.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/isolate/browser.d.ts +16 -0
- package/dist/isolate/browser.js +37 -0
- package/dist/isolate/browser.js.map +1 -0
- package/dist/isolate/host.d.ts +24 -0
- package/dist/isolate/host.js +74 -0
- package/dist/isolate/host.js.map +1 -0
- package/dist/isolate/isolate-worker.d.ts +1 -0
- package/dist/isolate/isolate-worker.js +53 -0
- package/dist/isolate/isolate-worker.js.map +1 -0
- package/dist/isolate/isolate.d.ts +10 -0
- package/dist/isolate/isolate.js +169 -0
- package/dist/isolate/isolate.js.map +1 -0
- package/dist/isolate/node-entry.d.ts +1 -0
- package/dist/isolate/node-entry.js +12 -0
- package/dist/isolate/node-entry.js.map +1 -0
- package/dist/isolate/protocol.d.ts +66 -0
- package/dist/isolate/protocol.js +205 -0
- package/dist/isolate/protocol.js.map +1 -0
- package/dist/ltx/crc64.d.ts +6 -0
- package/dist/ltx/crc64.js +46 -0
- package/dist/ltx/crc64.js.map +1 -0
- package/dist/ltx/ltx-page-server.d.ts +65 -0
- package/dist/ltx/ltx-page-server.js +309 -0
- package/dist/ltx/ltx-page-server.js.map +1 -0
- package/dist/ltx/ltx-pages.d.ts +81 -0
- package/dist/ltx/ltx-pages.js +315 -0
- package/dist/ltx/ltx-pages.js.map +1 -0
- package/dist/ltx/ltx-store-worker.d.ts +1 -0
- package/dist/ltx/ltx-store-worker.js +38 -0
- package/dist/ltx/ltx-store-worker.js.map +1 -0
- package/dist/ltx/ltx.d.ts +45 -0
- package/dist/ltx/ltx.js +203 -0
- package/dist/ltx/ltx.js.map +1 -0
- package/dist/ltx/lz4.d.ts +9 -0
- package/dist/ltx/lz4.js +156 -0
- package/dist/ltx/lz4.js.map +1 -0
- package/dist/ltx/opfs-sync-files.d.ts +23 -0
- package/dist/ltx/opfs-sync-files.js +191 -0
- package/dist/ltx/opfs-sync-files.js.map +1 -0
- package/dist/ltx/xxh32.d.ts +1 -0
- package/dist/ltx/xxh32.js +43 -0
- package/dist/ltx/xxh32.js.map +1 -0
- package/dist/miniflare-compat/blob-service.d.ts +13 -0
- package/dist/miniflare-compat/blob-service.js +66 -0
- package/dist/miniflare-compat/blob-service.js.map +1 -0
- package/dist/miniflare-compat/kv-binding.d.ts +44 -0
- package/dist/miniflare-compat/kv-binding.js +99 -0
- package/dist/miniflare-compat/kv-binding.js.map +1 -0
- package/dist/miniflare-compat/shims/assert.d.ts +15 -0
- package/dist/miniflare-compat/shims/assert.js +24 -0
- package/dist/miniflare-compat/shims/assert.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-cloudflare.d.ts +7 -0
- package/dist/miniflare-compat/shims/workerd-cloudflare.js +6 -0
- package/dist/miniflare-compat/shims/workerd-cloudflare.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-globals.d.ts +1 -0
- package/dist/miniflare-compat/shims/workerd-globals.js +30 -0
- package/dist/miniflare-compat/shims/workerd-globals.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-internal.d.ts +20 -0
- package/dist/miniflare-compat/shims/workerd-internal.js +23 -0
- package/dist/miniflare-compat/shims/workerd-internal.js.map +1 -0
- package/dist/miniflare-compat/shims/workerd-performance.d.ts +4 -0
- package/dist/miniflare-compat/shims/workerd-performance.js +17 -0
- package/dist/miniflare-compat/shims/workerd-performance.js.map +1 -0
- package/dist/miniflare-compat/simulator-fetcher.d.ts +9 -0
- package/dist/miniflare-compat/simulator-fetcher.js +14 -0
- package/dist/miniflare-compat/simulator-fetcher.js.map +1 -0
- package/dist/miniflare-compat/vendored.d.ts +15 -0
- package/dist/miniflare-compat/vendored.js +27 -0
- package/dist/miniflare-compat/vendored.js.map +1 -0
- package/dist/node-compat/async_hooks.d.ts +22 -0
- package/dist/node-compat/async_hooks.js +176 -0
- package/dist/node-compat/async_hooks.js.map +1 -0
- package/dist/node-compat/browser-registry.d.ts +3 -0
- package/dist/node-compat/browser-registry.js +39 -0
- package/dist/node-compat/browser-registry.js.map +1 -0
- package/dist/node-compat/crypto.d.ts +60 -0
- package/dist/node-compat/crypto.js +143 -0
- package/dist/node-compat/crypto.js.map +1 -0
- package/dist/node-compat/diagnostics_channel.d.ts +27 -0
- package/dist/node-compat/diagnostics_channel.js +46 -0
- package/dist/node-compat/diagnostics_channel.js.map +1 -0
- package/dist/node-compat/fs.d.ts +35 -0
- package/dist/node-compat/fs.js +40 -0
- package/dist/node-compat/fs.js.map +1 -0
- package/dist/node-compat/globals.d.ts +3 -0
- package/dist/node-compat/globals.js +4 -0
- package/dist/node-compat/globals.js.map +1 -0
- package/dist/node-compat/install.d.ts +5 -0
- package/dist/node-compat/install.js +12 -0
- package/dist/node-compat/install.js.map +1 -0
- package/dist/node-compat/module.d.ts +12 -0
- package/dist/node-compat/module.js +36 -0
- package/dist/node-compat/module.js.map +1 -0
- package/dist/node-compat/modules.d.ts +8 -0
- package/dist/node-compat/modules.js +23 -0
- package/dist/node-compat/modules.js.map +1 -0
- package/dist/node-compat/os.d.ts +36 -0
- package/dist/node-compat/os.js +36 -0
- package/dist/node-compat/os.js.map +1 -0
- package/dist/node-compat/process.d.ts +14 -0
- package/dist/node-compat/process.js +16 -0
- package/dist/node-compat/process.js.map +1 -0
- package/dist/node-compat/shims.d.ts +4 -0
- package/dist/node.d.ts +5 -0
- package/dist/node.js +6 -0
- package/dist/node.js.map +1 -0
- package/dist/peer/celld-peer.d.ts +53 -0
- package/dist/peer/celld-peer.js +555 -0
- package/dist/peer/celld-peer.js.map +1 -0
- package/dist/ports.d.ts +183 -0
- package/dist/ports.js +10 -0
- package/dist/ports.js.map +1 -0
- package/dist/project/browser-bundle.d.ts +17 -0
- package/dist/project/browser-bundle.js +125 -0
- package/dist/project/browser-bundle.js.map +1 -0
- package/dist/project/browser-cookies.d.ts +12 -0
- package/dist/project/browser-cookies.js +90 -0
- package/dist/project/browser-cookies.js.map +1 -0
- package/dist/project/browser-host.d.ts +173 -0
- package/dist/project/browser-host.js +200 -0
- package/dist/project/browser-host.js.map +1 -0
- package/dist/project/browser-modules.d.ts +17 -0
- package/dist/project/browser-modules.js +89 -0
- package/dist/project/browser-modules.js.map +1 -0
- package/dist/project/browser-shards.d.ts +62 -0
- package/dist/project/browser-shards.js +210 -0
- package/dist/project/browser-shards.js.map +1 -0
- package/dist/project/browser-tabs.d.ts +103 -0
- package/dist/project/browser-tabs.js +418 -0
- package/dist/project/browser-tabs.js.map +1 -0
- package/dist/project/cloudflare-email.d.ts +8 -0
- package/dist/project/cloudflare-email.js +11 -0
- package/dist/project/cloudflare-email.js.map +1 -0
- package/dist/project/cloudflare-workers.d.ts +12 -0
- package/dist/project/cloudflare-workers.js +16 -0
- package/dist/project/cloudflare-workers.js.map +1 -0
- package/dist/project/host-modules.d.ts +4 -0
- package/dist/project/host-modules.js +40 -0
- package/dist/project/host-modules.js.map +1 -0
- package/dist/project/jsonc.d.ts +2 -0
- package/dist/project/jsonc.js +27 -0
- package/dist/project/jsonc.js.map +1 -0
- package/dist/project/lower-await.d.ts +2 -0
- package/dist/project/lower-await.js +151 -0
- package/dist/project/lower-await.js.map +1 -0
- package/dist/project/node-project.d.ts +38 -0
- package/dist/project/node-project.js +373 -0
- package/dist/project/node-project.js.map +1 -0
- package/dist/project/own-module.d.ts +6 -0
- package/dist/project/own-module.js +13 -0
- package/dist/project/own-module.js.map +1 -0
- package/dist/project/scheduler.d.ts +2 -0
- package/dist/project/scheduler.js +3 -0
- package/dist/project/scheduler.js.map +1 -0
- package/dist/project/site.d.ts +74 -0
- package/dist/project/site.js +206 -0
- package/dist/project/site.js.map +1 -0
- package/dist/project/virtual-modules.d.ts +3 -0
- package/dist/project/worker-scope.d.ts +27 -0
- package/dist/project/worker-scope.js +46 -0
- package/dist/project/worker-scope.js.map +1 -0
- package/dist/project/workerd-message-channel.d.ts +13 -0
- package/dist/project/workerd-message-channel.js +137 -0
- package/dist/project/workerd-message-channel.js.map +1 -0
- package/dist/project/workers-global-names.d.ts +6 -0
- package/dist/project/workers-global-names.js +15 -0
- package/dist/project/workers-global-names.js.map +1 -0
- package/dist/project/workers-globals-module.d.ts +8 -0
- package/dist/project/workers-globals-module.js +9 -0
- package/dist/project/workers-globals-module.js.map +1 -0
- package/dist/project/workers-globals.d.ts +9 -0
- package/dist/project/workers-globals.js +19 -0
- package/dist/project/workers-globals.js.map +1 -0
- package/dist/project/wrangler.d.ts +116 -0
- package/dist/project/wrangler.js +342 -0
- package/dist/project/wrangler.js.map +1 -0
- package/dist/router/cell-router.d.ts +72 -0
- package/dist/router/cell-router.js +247 -0
- package/dist/router/cell-router.js.map +1 -0
- package/dist/router/shard-worker.d.ts +44 -0
- package/dist/router/shard-worker.js +163 -0
- package/dist/router/shard-worker.js.map +1 -0
- package/dist/transport/cells-http.d.ts +25 -0
- package/dist/transport/cells-http.js +141 -0
- package/dist/transport/cells-http.js.map +1 -0
- package/dist/transport/cells-link.d.ts +27 -0
- package/dist/transport/cells-link.js +71 -0
- package/dist/transport/cells-link.js.map +1 -0
- package/dist/transport/port.d.ts +26 -0
- package/dist/transport/port.js +100 -0
- package/dist/transport/port.js.map +1 -0
- package/dist/v8/codec.d.ts +6 -0
- package/dist/v8/codec.js +9 -0
- package/dist/v8/codec.js.map +1 -0
- package/dist/v8/serialize.d.ts +10 -0
- package/dist/v8/serialize.js +601 -0
- package/dist/v8/serialize.js.map +1 -0
- package/dist/ws/client.d.ts +25 -0
- package/dist/ws/client.js +203 -0
- package/dist/ws/client.js.map +1 -0
- package/dist/ws/control.d.ts +48 -0
- package/dist/ws/control.js +44 -0
- package/dist/ws/control.js.map +1 -0
- package/dist/ws/frames.d.ts +38 -0
- package/dist/ws/frames.js +39 -0
- package/dist/ws/frames.js.map +1 -0
- package/dist/ws/server.d.ts +99 -0
- package/dist/ws/server.js +295 -0
- package/dist/ws/server.js.map +1 -0
- package/dist/ws/sw-connector.d.ts +10 -0
- package/dist/ws/sw-connector.js +25 -0
- package/dist/ws/sw-connector.js.map +1 -0
- package/dist/ws/upgrade.d.ts +28 -0
- package/dist/ws/upgrade.js +75 -0
- package/dist/ws/upgrade.js.map +1 -0
- package/package.json +111 -3
- package/vendor/celld/LICENSE +202 -0
- package/vendor/celld/kv.js +1131 -0
- package/vendor/celld/queues.js +1656 -0
- package/vendor/celld/workflows.js +1610 -0
- package/vendor/html-rewriter-wasm/html_rewriter.js +925 -0
- package/vendor/html-rewriter-wasm/html_rewriter_bg.wasm +0 -0
- package/vendor/miniflare/d1-api.js +738 -0
- package/vendor/miniflare/d1-database.js +6978 -0
- package/vendor/miniflare/kv-namespace.js +3061 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One project shared by every tab of an origin, its shards balanced over the tabs.
|
|
3
|
+
*
|
|
4
|
+
* ServiceWorker: installProjectRouter(self, { basePath: "/app/" })
|
|
5
|
+
* each page: joinProjectShards({ name, shards, spawn, config, code, serviceWorker })
|
|
6
|
+
* shard worker: serveProjectShard({ sqlite3 })
|
|
7
|
+
*
|
|
8
|
+
* Requests to `<basePath><name>/…` reach the project's Worker through the ServiceWorker, whichever
|
|
9
|
+
* tab sent them (with their URL unchanged, unless `stripBase`); a Durable Object call runs in the
|
|
10
|
+
* shard that owns the object, whichever tab holds that shard.
|
|
11
|
+
*
|
|
12
|
+
* Each tab holds a presence lock; every tab computes the same owner for each shard from the tabs
|
|
13
|
+
* present (`shardOwner`: an even split, oldest tabs first), takes the shards it should own (one
|
|
14
|
+
* Web Lock each) and gives up the others. A shard's OPFS pool belongs to the origin, so its cells
|
|
15
|
+
* move with it from tab to tab. A shard given up is drained first (its requests in flight
|
|
16
|
+
* finish, new ones wait for the next owner); a tab that closes runs no code, so the requests in
|
|
17
|
+
* flight in its shards fail (ShardOwnerLostError) and the other tabs take the shards over.
|
|
18
|
+
*/
|
|
19
|
+
import { CellRouter } from "../router/cell-router.js";
|
|
20
|
+
import { linkOverPort, serveHandlerOverPort } from "../transport/port.js";
|
|
21
|
+
import { SW_SOCKET } from "../ws/sw-connector.js";
|
|
22
|
+
import { applyWorkerCookies, withPageCookies } from "./browser-cookies.js";
|
|
23
|
+
import { SHARD_KEEPALIVE, SHARD_WAKE, TAB_INIT, TAB_LINK, TAB_WATCH, } from "./browser-shards.js";
|
|
24
|
+
const ATTACH = "cellb:attach";
|
|
25
|
+
const NEED_LINKS = "cellb:need-links";
|
|
26
|
+
/** A page opens a socket (`serviceWorkerConnector`). */
|
|
27
|
+
const SOCKET = SW_SOCKET;
|
|
28
|
+
const DRAIN = "cellb:drain";
|
|
29
|
+
/** A shard's call waits for the ServiceWorker: this message starts it, and it asks for links. */
|
|
30
|
+
const WAKE = "cellb:wake";
|
|
31
|
+
/** Calls between shards are in flight: the message itself resets the ServiceWorker's idle timer. */
|
|
32
|
+
const KEEPALIVE = "cellb:keepalive";
|
|
33
|
+
/**
|
|
34
|
+
* The tab that should run `shard`: the tabs sorted by id (ids start with the time the tab joined),
|
|
35
|
+
* shard i to tab i mod n — every tab runs ⌊shards/n⌋ or ⌈shards/n⌉ shards. A tab joining or
|
|
36
|
+
* leaving moves the shards whose owner index changes; the oldest tabs keep theirs first.
|
|
37
|
+
*/
|
|
38
|
+
export function shardOwner(shard, tabs) {
|
|
39
|
+
if (!tabs.length)
|
|
40
|
+
return undefined;
|
|
41
|
+
const sorted = [...tabs].sort();
|
|
42
|
+
return sorted[shard % sorted.length];
|
|
43
|
+
}
|
|
44
|
+
/** The page side: this tab takes part in running the project's shards. */
|
|
45
|
+
export async function joinProjectShards(opts) {
|
|
46
|
+
const prefix = `cellb:${opts.name}`;
|
|
47
|
+
const tabId = `${Date.now().toString(36).padStart(9, "0")}-${crypto.randomUUID()}`;
|
|
48
|
+
const presenceLock = `${prefix}:tab:${tabId}`;
|
|
49
|
+
let leave;
|
|
50
|
+
await new Promise((held) => {
|
|
51
|
+
void navigator.locks.request(presenceLock, () => {
|
|
52
|
+
held();
|
|
53
|
+
return new Promise((r) => (leave = r));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
const channel = new BroadcastChannel(`${prefix}:tabs`);
|
|
57
|
+
const held = new Map();
|
|
58
|
+
const running = new Set();
|
|
59
|
+
let closed = false;
|
|
60
|
+
const tabs = async () => {
|
|
61
|
+
const { held: locks = [] } = await navigator.locks.query();
|
|
62
|
+
const ids = locks
|
|
63
|
+
.map((l) => l.name ?? "")
|
|
64
|
+
.filter((n) => n.startsWith(`${prefix}:tab:`))
|
|
65
|
+
.map((n) => n.slice(`${prefix}:tab:`.length));
|
|
66
|
+
return ids.includes(tabId) ? ids : [...ids, tabId];
|
|
67
|
+
};
|
|
68
|
+
const ask = (w, data, ports = []) => new Promise((resolve) => {
|
|
69
|
+
const { port1, port2 } = new MessageChannel();
|
|
70
|
+
port1.onmessage = (e) => {
|
|
71
|
+
port1.close();
|
|
72
|
+
resolve(e.data);
|
|
73
|
+
};
|
|
74
|
+
w.postMessage(data, [port2, ...ports]);
|
|
75
|
+
});
|
|
76
|
+
/** Links a shard's worker to the router (again, after a ServiceWorker restart). */
|
|
77
|
+
const attach = async (shard, w, ownerLock) => {
|
|
78
|
+
const down = new MessageChannel(); // router → shard
|
|
79
|
+
const up = new MessageChannel(); // shard → router
|
|
80
|
+
const ctl = new MessageChannel(); // WebSocket port hand-over
|
|
81
|
+
const linked = await ask(w, { type: TAB_LINK }, [down.port2, up.port2, ctl.port2]);
|
|
82
|
+
if (!linked.ok)
|
|
83
|
+
throw new Error(linked.error);
|
|
84
|
+
const sw = await opts.serviceWorker();
|
|
85
|
+
const routerLock = await new Promise((resolve, reject) => {
|
|
86
|
+
const { port1, port2 } = new MessageChannel();
|
|
87
|
+
port1.onmessage = (e) => (port1.close(), e.data?.ok ? resolve(e.data.routerLock) : reject(new Error(e.data?.error)));
|
|
88
|
+
sw.postMessage({
|
|
89
|
+
type: ATTACH,
|
|
90
|
+
name: opts.name,
|
|
91
|
+
shards: opts.shards,
|
|
92
|
+
scriptName: opts.config.name ?? "worker",
|
|
93
|
+
shard,
|
|
94
|
+
ownerLock,
|
|
95
|
+
down: down.port1,
|
|
96
|
+
up: up.port1,
|
|
97
|
+
ctl: ctl.port1,
|
|
98
|
+
}, [port2, down.port1, up.port1, ctl.port1]);
|
|
99
|
+
});
|
|
100
|
+
// The shard drops these links once the ServiceWorker is gone, and wakes it when it needs it.
|
|
101
|
+
const watching = await ask(w, { type: TAB_WATCH, routerLock });
|
|
102
|
+
if (!watching.ok)
|
|
103
|
+
throw new Error(watching.error);
|
|
104
|
+
};
|
|
105
|
+
/** Starts the ServiceWorker again (WAKE: it asks the tabs for their shards' links), or keeps it up. */
|
|
106
|
+
const tell = async (type) => {
|
|
107
|
+
const sw = await opts.serviceWorker();
|
|
108
|
+
sw.postMessage({ type, name: opts.name });
|
|
109
|
+
};
|
|
110
|
+
const owners = new Map(); // shard → its worker's owner lock
|
|
111
|
+
/** Asks the router to stop sending the shard new requests and let the current ones finish. */
|
|
112
|
+
const drain = async (shard, ownerLock) => {
|
|
113
|
+
const sw = await opts.serviceWorker();
|
|
114
|
+
const ms = opts.drainMs ?? 5_000;
|
|
115
|
+
await new Promise((resolve) => {
|
|
116
|
+
const { port1, port2 } = new MessageChannel();
|
|
117
|
+
const t = setTimeout(resolve, ms + 1_000); // a ServiceWorker that does not answer
|
|
118
|
+
port1.onmessage = () => (clearTimeout(t), port1.close(), resolve());
|
|
119
|
+
sw.postMessage({ type: DRAIN, name: opts.name, shard, ownerLock, ms }, [port2]);
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
const changed = () => opts.onChange?.([...running].sort((a, b) => a - b));
|
|
123
|
+
/** Waits for the shard's lock, then runs its worker until `stop` is aborted. */
|
|
124
|
+
const take = (shard) => {
|
|
125
|
+
const h = { stop: new AbortController() };
|
|
126
|
+
held.set(shard, h);
|
|
127
|
+
h.running = navigator.locks
|
|
128
|
+
.request(`${prefix}:shard:${shard}`, { signal: h.stop.signal }, async () => {
|
|
129
|
+
const w = opts.spawn(shard);
|
|
130
|
+
h.worker = w;
|
|
131
|
+
w.addEventListener("message", (e) => {
|
|
132
|
+
if (e.data?.type === SHARD_WAKE)
|
|
133
|
+
void tell(WAKE).catch(() => { });
|
|
134
|
+
if (e.data?.type === SHARD_KEEPALIVE)
|
|
135
|
+
void tell(KEEPALIVE).catch(() => { });
|
|
136
|
+
});
|
|
137
|
+
try {
|
|
138
|
+
const started = await ask(w, {
|
|
139
|
+
type: TAB_INIT,
|
|
140
|
+
name: opts.name,
|
|
141
|
+
shard,
|
|
142
|
+
shards: opts.shards,
|
|
143
|
+
config: opts.config,
|
|
144
|
+
code: opts.code,
|
|
145
|
+
assetFiles: opts.assetFiles,
|
|
146
|
+
ai: opts.ai,
|
|
147
|
+
bucket: opts.bucket && { ...opts.bucket, node: `${opts.bucket.node}-s${shard}` },
|
|
148
|
+
pool: `${opts.name}-shard-${shard}`,
|
|
149
|
+
filesDir: `${opts.name}-files-${shard}`,
|
|
150
|
+
});
|
|
151
|
+
if (!started.ok || !started.ownerLock)
|
|
152
|
+
throw new Error(started.error);
|
|
153
|
+
// What the configuration asks for and this runtime does not provide (a cron trigger
|
|
154
|
+
// without a scheduled() handler, say): said once, not silently ignored.
|
|
155
|
+
if (started.unsupported?.length)
|
|
156
|
+
console.warn(`cellb: project ${opts.name} shard ${shard}: unsupported: ${started.unsupported.join("; ")}`);
|
|
157
|
+
owners.set(shard, started.ownerLock);
|
|
158
|
+
if (!h.stop.signal.aborted) {
|
|
159
|
+
await attach(shard, w, started.ownerLock);
|
|
160
|
+
running.add(shard);
|
|
161
|
+
changed();
|
|
162
|
+
await new Promise((r) => h.stop.signal.aborted ? r() : h.stop.signal.addEventListener("abort", () => r()));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
// A planned hand-over drains the shard's requests in flight first; then the runtime
|
|
167
|
+
// closes (and the pool with it) before the lock goes to the next tab.
|
|
168
|
+
const lock = owners.get(shard);
|
|
169
|
+
if (lock && running.has(shard))
|
|
170
|
+
await drain(shard, lock).catch(() => { });
|
|
171
|
+
running.delete(shard);
|
|
172
|
+
owners.delete(shard);
|
|
173
|
+
await ask(w, { type: "cellb:shard-close" }).catch(() => { });
|
|
174
|
+
w.terminate();
|
|
175
|
+
changed();
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
.catch((err) => {
|
|
179
|
+
// Aborted while waiting is expected; a shard that failed to start is reported.
|
|
180
|
+
if (!h.stop.signal.aborted)
|
|
181
|
+
console.error(`cellb: ${opts.name} shard ${shard} failed: ${err?.message ?? err}`);
|
|
182
|
+
})
|
|
183
|
+
.finally(() => {
|
|
184
|
+
if (held.get(shard) === h)
|
|
185
|
+
held.delete(shard);
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
let balancing;
|
|
189
|
+
let again = false;
|
|
190
|
+
/** Takes the shards this tab should own and gives up the others. */
|
|
191
|
+
const balance = async () => {
|
|
192
|
+
if (balancing) {
|
|
193
|
+
again = true;
|
|
194
|
+
return balancing;
|
|
195
|
+
}
|
|
196
|
+
balancing = (async () => {
|
|
197
|
+
do {
|
|
198
|
+
again = false;
|
|
199
|
+
if (closed)
|
|
200
|
+
return;
|
|
201
|
+
const present = await tabs();
|
|
202
|
+
for (let shard = 0; shard < opts.shards; shard++) {
|
|
203
|
+
const mine = shardOwner(shard, present) === tabId;
|
|
204
|
+
const h = held.get(shard);
|
|
205
|
+
if (mine && !h)
|
|
206
|
+
take(shard);
|
|
207
|
+
else if (!mine && h)
|
|
208
|
+
h.stop.abort();
|
|
209
|
+
}
|
|
210
|
+
} while (again);
|
|
211
|
+
})().finally(() => (balancing = undefined));
|
|
212
|
+
return balancing;
|
|
213
|
+
};
|
|
214
|
+
channel.onmessage = () => void balance();
|
|
215
|
+
const timer = setInterval(() => void balance(), opts.checkMs ?? 1000);
|
|
216
|
+
const onSw = (e) => {
|
|
217
|
+
if (e.data?.type !== NEED_LINKS || (e.data.name && e.data.name !== opts.name))
|
|
218
|
+
return;
|
|
219
|
+
for (const shard of running) {
|
|
220
|
+
const w = held.get(shard)?.worker;
|
|
221
|
+
const lock = owners.get(shard);
|
|
222
|
+
if (w && lock)
|
|
223
|
+
void attach(shard, w, lock).catch(() => { });
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
navigator.serviceWorker.addEventListener("message", onSw);
|
|
227
|
+
channel.postMessage({ joined: tabId });
|
|
228
|
+
await balance();
|
|
229
|
+
return {
|
|
230
|
+
owned: () => [...running].sort((a, b) => a - b),
|
|
231
|
+
tabs,
|
|
232
|
+
async close() {
|
|
233
|
+
closed = true;
|
|
234
|
+
clearInterval(timer);
|
|
235
|
+
navigator.serviceWorker.removeEventListener("message", onSw);
|
|
236
|
+
const stopping = [...held.values()];
|
|
237
|
+
for (const h of stopping)
|
|
238
|
+
h.stop.abort();
|
|
239
|
+
await Promise.all(stopping.map((h) => h.running));
|
|
240
|
+
leave();
|
|
241
|
+
channel.postMessage({ left: tabId });
|
|
242
|
+
channel.close();
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** The ServiceWorker side: one CellRouter per project, fed by the tabs' shard workers. */
|
|
247
|
+
export function installProjectRouter(scope, opts) {
|
|
248
|
+
const sw = scope;
|
|
249
|
+
const base = opts.basePath.endsWith("/") ? opts.basePath : `${opts.basePath}/`;
|
|
250
|
+
// Held while this ServiceWorker lives: the browser releases it when it stops the worker, which
|
|
251
|
+
// tells the shards that their links to it are dead (MessagePorts report nothing).
|
|
252
|
+
const routerLock = `cellb:router:${crypto.randomUUID()}`;
|
|
253
|
+
const lockHeld = new Promise((held) => {
|
|
254
|
+
void navigator.locks.request(routerLock, () => {
|
|
255
|
+
held();
|
|
256
|
+
return new Promise(() => { });
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
const routers = new Map();
|
|
260
|
+
const created = new Map();
|
|
261
|
+
const ready = new Map();
|
|
262
|
+
let asking;
|
|
263
|
+
const askForLinks = (name) => {
|
|
264
|
+
asking ??= sw.clients
|
|
265
|
+
.matchAll({ type: "window", includeUncontrolled: true })
|
|
266
|
+
.then((cs) => {
|
|
267
|
+
for (const c of cs)
|
|
268
|
+
c.postMessage({ type: NEED_LINKS, name });
|
|
269
|
+
})
|
|
270
|
+
.finally(() => setTimeout(() => (asking = undefined), 500));
|
|
271
|
+
return asking;
|
|
272
|
+
};
|
|
273
|
+
const routerFor = (name, shards, scriptName) => {
|
|
274
|
+
let router = routers.get(name);
|
|
275
|
+
if (!router) {
|
|
276
|
+
router = new CellRouter({
|
|
277
|
+
shards,
|
|
278
|
+
bindings: {}, // shards name the owner of every internal request (x-cellb-shard)
|
|
279
|
+
scriptName,
|
|
280
|
+
attachTimeoutMs: opts.attachTimeoutMs ?? 15_000,
|
|
281
|
+
onMissingShard: () => void askForLinks(name),
|
|
282
|
+
// The owner worker's own lock: released when it closes, its tab closes or it dies.
|
|
283
|
+
watchOwner: (_shard, ownerLock) => navigator.locks.request(ownerLock, async () => { }),
|
|
284
|
+
isAlive: async (_shard, ownerLock) => !!(await navigator.locks.query()).held?.some((l) => l.name === ownerLock),
|
|
285
|
+
livenessIntervalMs: 5_000,
|
|
286
|
+
});
|
|
287
|
+
routers.set(name, router);
|
|
288
|
+
created.get(name)?.();
|
|
289
|
+
}
|
|
290
|
+
return router;
|
|
291
|
+
};
|
|
292
|
+
/** A project's router; after a ServiceWorker restart, once a tab has attached a shard again. */
|
|
293
|
+
const routerNamed = (name) => {
|
|
294
|
+
const r = routers.get(name);
|
|
295
|
+
if (r)
|
|
296
|
+
return Promise.resolve(r);
|
|
297
|
+
let wait = ready.get(name);
|
|
298
|
+
if (!wait) {
|
|
299
|
+
wait = new Promise((resolve, reject) => {
|
|
300
|
+
const t = setTimeout(() => reject(new Error(`project ${name} has no shards in any tab`)), opts.attachTimeoutMs ?? 15_000);
|
|
301
|
+
created.set(name, () => (clearTimeout(t), resolve()));
|
|
302
|
+
}).finally(() => (ready.delete(name), created.delete(name)));
|
|
303
|
+
ready.set(name, wait);
|
|
304
|
+
void askForLinks(name);
|
|
305
|
+
}
|
|
306
|
+
return wait.then(() => routers.get(name));
|
|
307
|
+
};
|
|
308
|
+
sw.addEventListener("message", (e) => {
|
|
309
|
+
const m = e.data;
|
|
310
|
+
const reply = e.ports[0];
|
|
311
|
+
if (m?.type === ATTACH)
|
|
312
|
+
e.waitUntil((async () => {
|
|
313
|
+
try {
|
|
314
|
+
const router = routerFor(m.name, m.shards, m.scriptName);
|
|
315
|
+
await serveHandlerOverPort(m.up, router.internal);
|
|
316
|
+
const link = await linkOverPort(m.down);
|
|
317
|
+
router.attach(m.shard, link.send, m.ownerLock, m.ctl);
|
|
318
|
+
await lockHeld;
|
|
319
|
+
reply?.postMessage({ ok: true, routerLock });
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
reply?.postMessage({ ok: false, error: String(err?.message ?? err) });
|
|
323
|
+
}
|
|
324
|
+
})());
|
|
325
|
+
else if (m?.type === DRAIN)
|
|
326
|
+
e.waitUntil((async () => {
|
|
327
|
+
await routers.get(m.name)?.drain(m.shard, m.ownerLock, m.ms);
|
|
328
|
+
reply?.postMessage({ ok: true });
|
|
329
|
+
})());
|
|
330
|
+
else if (m?.type === WAKE)
|
|
331
|
+
// Started by this message: stay up until a tab has linked the project's shards again.
|
|
332
|
+
e.waitUntil(routerNamed(m.name).catch(() => { }));
|
|
333
|
+
else if (m?.type === SOCKET)
|
|
334
|
+
e.waitUntil((async () => {
|
|
335
|
+
try {
|
|
336
|
+
const { name, path } = split(new URL(m.url));
|
|
337
|
+
const url = new URL(m.url);
|
|
338
|
+
if (opts.stripBase && !atRoot(url))
|
|
339
|
+
url.pathname = path;
|
|
340
|
+
const { port, protocol } = await (await routerNamed(name)).connectWebSocket(url.toString(), m.protocols ?? []);
|
|
341
|
+
reply.postMessage({ ok: true, port, protocol }, [port]);
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
reply.postMessage({ ok: false, error: String(err?.message ?? err) });
|
|
345
|
+
}
|
|
346
|
+
})());
|
|
347
|
+
});
|
|
348
|
+
/** Whether `url` goes to the root project (`opts.root`). */
|
|
349
|
+
const atRoot = (url) => !!opts.root &&
|
|
350
|
+
url.origin === sw.location.origin &&
|
|
351
|
+
!url.pathname.startsWith(base) &&
|
|
352
|
+
!(opts.root.passthrough ?? []).some((p) => url.pathname.startsWith(p));
|
|
353
|
+
/** `<base><name>/<rest>` → the project's name and the Worker's path `/<rest>`. */
|
|
354
|
+
const split = (url) => {
|
|
355
|
+
if (opts.root && atRoot(url))
|
|
356
|
+
return { name: opts.root.name, path: url.pathname };
|
|
357
|
+
if (!url.pathname.startsWith(base))
|
|
358
|
+
throw new Error(`not a project URL: ${url}`);
|
|
359
|
+
const rest = url.pathname.slice(base.length);
|
|
360
|
+
const slash = rest.indexOf("/");
|
|
361
|
+
const name = decodeURIComponent(slash < 0 ? rest : rest.slice(0, slash));
|
|
362
|
+
return { name, path: slash < 0 ? "/" : rest.slice(slash) };
|
|
363
|
+
};
|
|
364
|
+
sw.addEventListener("fetch", (e) => {
|
|
365
|
+
const url = new URL(e.request.url);
|
|
366
|
+
const root = atRoot(url);
|
|
367
|
+
if (!root) {
|
|
368
|
+
if (url.origin !== sw.location.origin || !url.pathname.startsWith(base))
|
|
369
|
+
return;
|
|
370
|
+
if (e.request.mode === "navigate")
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const { name, path } = split(url);
|
|
374
|
+
if (!name)
|
|
375
|
+
return;
|
|
376
|
+
const req = e.request;
|
|
377
|
+
const target = opts.stripBase && !root ? new URL(`${path}${url.search}`, url.origin) : url;
|
|
378
|
+
const hostPage = root && req.mode === "navigate" ? opts.root?.hostPage : undefined;
|
|
379
|
+
if (hostPage)
|
|
380
|
+
e.respondWith(runningShards(name, opts.root?.hostWaitMs ?? 2_000).then((running) => running
|
|
381
|
+
? serve(name, req, target)
|
|
382
|
+
: Response.redirect(`${new URL(hostPage, url.origin).href}?next=${encodeURIComponent(`${url.pathname}${url.search}`)}`, 302)));
|
|
383
|
+
else
|
|
384
|
+
e.respondWith(serve(name, req, target));
|
|
385
|
+
});
|
|
386
|
+
/** Whether a tab runs project `name`'s shards, waiting up to `ms` for one to link. */
|
|
387
|
+
const runningShards = async (name, ms) => {
|
|
388
|
+
const live = () => (routers.get(name)?.attachedShards.length ?? 0) > 0;
|
|
389
|
+
if (live())
|
|
390
|
+
return true;
|
|
391
|
+
void askForLinks(name);
|
|
392
|
+
for (const until = Date.now() + ms; Date.now() < until;) {
|
|
393
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
394
|
+
if (live())
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
return false;
|
|
398
|
+
};
|
|
399
|
+
/** A request to project `name`, with the page's cookies in and the Worker's out. */
|
|
400
|
+
const serve = (name, req, target) => routerNamed(name).then(async (router) => {
|
|
401
|
+
try {
|
|
402
|
+
return await applyWorkerCookies(sw, await router.ingress(new Request(target, {
|
|
403
|
+
method: req.method,
|
|
404
|
+
headers: await withPageCookies(sw, req),
|
|
405
|
+
body: req.body,
|
|
406
|
+
signal: req.signal,
|
|
407
|
+
duplex: "half",
|
|
408
|
+
})));
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
// A response the page can read, instead of a network error with no cause.
|
|
412
|
+
console.error(`cellb: ${req.method} ${target.pathname} failed:`, err);
|
|
413
|
+
return new Response(`cellb: ${err?.stack ?? err}`, { status: 502 });
|
|
414
|
+
}
|
|
415
|
+
}, (err) => new Response(String(err?.message ?? err), { status: 503 }));
|
|
416
|
+
return { routers };
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=browser-tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-tabs.js","sourceRoot":"","sources":["../../src/project/browser-tabs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One project shared by every tab of an origin, its shards balanced over the tabs.\n *\n * ServiceWorker: installProjectRouter(self, { basePath: \"/app/\" })\n * each page: joinProjectShards({ name, shards, spawn, config, code, serviceWorker })\n * shard worker: serveProjectShard({ sqlite3 })\n *\n * Requests to `<basePath><name>/…` reach the project's Worker through the ServiceWorker, whichever\n * tab sent them (with their URL unchanged, unless `stripBase`); a Durable Object call runs in the\n * shard that owns the object, whichever tab holds that shard.\n *\n * Each tab holds a presence lock; every tab computes the same owner for each shard from the tabs\n * present (`shardOwner`: an even split, oldest tabs first), takes the shards it should own (one\n * Web Lock each) and gives up the others. A shard's OPFS pool belongs to the origin, so its cells\n * move with it from tab to tab. A shard given up is drained first (its requests in flight\n * finish, new ones wait for the next owner); a tab that closes runs no code, so the requests in\n * flight in its shards fail (ShardOwnerLostError) and the other tabs take the shards over.\n */\nimport { CellRouter } from \"../router/cell-router.js\";\nimport { linkOverPort, serveHandlerOverPort } from \"../transport/port.js\";\nimport { SW_SOCKET } from \"../ws/sw-connector.js\";\nimport { applyWorkerCookies, withPageCookies } from \"./browser-cookies.js\";\nimport type { BrowserBucket, BrowserProjectOptions } from \"./browser-host.js\";\nimport {\n SHARD_KEEPALIVE,\n SHARD_WAKE,\n TAB_INIT,\n TAB_LINK,\n TAB_WATCH,\n type TabShardInit,\n} from \"./browser-shards.js\";\n\nconst ATTACH = \"cellb:attach\";\nconst NEED_LINKS = \"cellb:need-links\";\n/** A page opens a socket (`serviceWorkerConnector`). */\nconst SOCKET = SW_SOCKET;\nconst DRAIN = \"cellb:drain\";\n/** A shard's call waits for the ServiceWorker: this message starts it, and it asks for links. */\nconst WAKE = \"cellb:wake\";\n/** Calls between shards are in flight: the message itself resets the ServiceWorker's idle timer. */\nconst KEEPALIVE = \"cellb:keepalive\";\n\n/**\n * The tab that should run `shard`: the tabs sorted by id (ids start with the time the tab joined),\n * shard i to tab i mod n — every tab runs ⌊shards/n⌋ or ⌈shards/n⌉ shards. A tab joining or\n * leaving moves the shards whose owner index changes; the oldest tabs keep theirs first.\n */\nexport function shardOwner(shard: number, tabs: string[]): string | undefined {\n if (!tabs.length) return undefined;\n const sorted = [...tabs].sort();\n return sorted[shard % sorted.length];\n}\n\nexport interface ProjectTabsOptions {\n /** The project's name in this origin: its URL path, locks and OPFS pools derive from it. */\n name: string;\n shards: number;\n /** Starts the worker of one shard; its script calls `serveProjectShard`. */\n spawn: (shard: number) => Worker;\n config: BrowserProjectOptions[\"config\"];\n code: BrowserProjectOptions[\"code\"];\n assetFiles?: BrowserProjectOptions[\"assetFiles\"];\n /** The OpenAI-compatible endpoint behind the project's `ai` binding. */\n ai?: BrowserProjectOptions[\"ai\"];\n /** Each shard is its own fleet node, `<node>-s<shard>`. */\n bucket?: BrowserBucket;\n /** The active ServiceWorker that runs `installProjectRouter`. */\n serviceWorker: () => Promise<ServiceWorker>;\n /** Called whenever the shards this tab runs change. */\n onChange?: (owned: number[]) => void;\n /** How often the tab checks who is present (tabs that crash release their locks silently). */\n checkMs?: number;\n /**\n * A shard handed to another tab first lets its requests in flight finish, for up to this long\n * (default 5 s); new ones wait for the next owner.\n */\n drainMs?: number;\n}\n\nexport interface ProjectTabs {\n /** The shards this tab runs now. */\n owned(): number[];\n /** Ids of the tabs present (this one included). */\n tabs(): Promise<string[]>;\n /** Gives up every shard (closing its runtime first) and leaves. */\n close(): Promise<void>;\n}\n\ninterface Held {\n /** Aborts the wait for the lock, or ends the hold once running. */\n stop: AbortController;\n worker?: Worker;\n running?: Promise<void>;\n}\n\n/** The page side: this tab takes part in running the project's shards. */\nexport async function joinProjectShards(opts: ProjectTabsOptions): Promise<ProjectTabs> {\n const prefix = `cellb:${opts.name}`;\n const tabId = `${Date.now().toString(36).padStart(9, \"0\")}-${crypto.randomUUID()}`;\n const presenceLock = `${prefix}:tab:${tabId}`;\n let leave!: () => void;\n await new Promise<void>((held) => {\n void navigator.locks.request(presenceLock, () => {\n held();\n return new Promise<void>((r) => (leave = r));\n });\n });\n const channel = new BroadcastChannel(`${prefix}:tabs`);\n const held = new Map<number, Held>();\n const running = new Set<number>();\n let closed = false;\n\n const tabs = async (): Promise<string[]> => {\n const { held: locks = [] } = await navigator.locks.query();\n const ids = locks\n .map((l) => l.name ?? \"\")\n .filter((n) => n.startsWith(`${prefix}:tab:`))\n .map((n) => n.slice(`${prefix}:tab:`.length));\n return ids.includes(tabId) ? ids : [...ids, tabId];\n };\n\n const ask = (w: Worker, data: unknown, ports: MessagePort[] = []) =>\n new Promise<{\n ok: boolean;\n error?: string;\n ownerLock?: string;\n unsupported?: string[];\n }>((resolve) => {\n const { port1, port2 } = new MessageChannel();\n port1.onmessage = (e) => {\n port1.close();\n resolve(e.data);\n };\n w.postMessage(data, [port2, ...ports]);\n });\n\n /** Links a shard's worker to the router (again, after a ServiceWorker restart). */\n const attach = async (shard: number, w: Worker, ownerLock: string) => {\n const down = new MessageChannel(); // router → shard\n const up = new MessageChannel(); // shard → router\n const ctl = new MessageChannel(); // WebSocket port hand-over\n const linked = await ask(w, { type: TAB_LINK }, [down.port2, up.port2, ctl.port2]);\n if (!linked.ok) throw new Error(linked.error);\n const sw = await opts.serviceWorker();\n const routerLock = await new Promise<string>((resolve, reject) => {\n const { port1, port2 } = new MessageChannel();\n port1.onmessage = (e) => (\n port1.close(), e.data?.ok ? resolve(e.data.routerLock) : reject(new Error(e.data?.error))\n );\n sw.postMessage(\n {\n type: ATTACH,\n name: opts.name,\n shards: opts.shards,\n scriptName: opts.config.name ?? \"worker\",\n shard,\n ownerLock,\n down: down.port1,\n up: up.port1,\n ctl: ctl.port1,\n },\n [port2, down.port1, up.port1, ctl.port1],\n );\n });\n // The shard drops these links once the ServiceWorker is gone, and wakes it when it needs it.\n const watching = await ask(w, { type: TAB_WATCH, routerLock });\n if (!watching.ok) throw new Error(watching.error);\n };\n\n /** Starts the ServiceWorker again (WAKE: it asks the tabs for their shards' links), or keeps it up. */\n const tell = async (type: typeof WAKE | typeof KEEPALIVE) => {\n const sw = await opts.serviceWorker();\n sw.postMessage({ type, name: opts.name });\n };\n const owners = new Map<number, string>(); // shard → its worker's owner lock\n\n /** Asks the router to stop sending the shard new requests and let the current ones finish. */\n const drain = async (shard: number, ownerLock: string) => {\n const sw = await opts.serviceWorker();\n const ms = opts.drainMs ?? 5_000;\n await new Promise<void>((resolve) => {\n const { port1, port2 } = new MessageChannel();\n const t = setTimeout(resolve, ms + 1_000); // a ServiceWorker that does not answer\n port1.onmessage = () => (clearTimeout(t), port1.close(), resolve());\n sw.postMessage({ type: DRAIN, name: opts.name, shard, ownerLock, ms }, [port2]);\n });\n };\n\n const changed = () => opts.onChange?.([...running].sort((a, b) => a - b));\n\n /** Waits for the shard's lock, then runs its worker until `stop` is aborted. */\n const take = (shard: number) => {\n const h: Held = { stop: new AbortController() };\n held.set(shard, h);\n h.running = navigator.locks\n .request(`${prefix}:shard:${shard}`, { signal: h.stop.signal }, async () => {\n const w = opts.spawn(shard);\n h.worker = w;\n w.addEventListener(\"message\", (e) => {\n if (e.data?.type === SHARD_WAKE) void tell(WAKE).catch(() => {});\n if (e.data?.type === SHARD_KEEPALIVE) void tell(KEEPALIVE).catch(() => {});\n });\n try {\n const started = await ask(w, {\n type: TAB_INIT,\n name: opts.name,\n shard,\n shards: opts.shards,\n config: opts.config,\n code: opts.code,\n assetFiles: opts.assetFiles,\n ai: opts.ai,\n bucket: opts.bucket && { ...opts.bucket, node: `${opts.bucket.node}-s${shard}` },\n pool: `${opts.name}-shard-${shard}`,\n filesDir: `${opts.name}-files-${shard}`,\n } satisfies TabShardInit);\n if (!started.ok || !started.ownerLock) throw new Error(started.error);\n // What the configuration asks for and this runtime does not provide (a cron trigger\n // without a scheduled() handler, say): said once, not silently ignored.\n if (started.unsupported?.length)\n console.warn(\n `cellb: project ${opts.name} shard ${shard}: unsupported: ${started.unsupported.join(\"; \")}`,\n );\n owners.set(shard, started.ownerLock);\n if (!h.stop.signal.aborted) {\n await attach(shard, w, started.ownerLock);\n running.add(shard);\n changed();\n await new Promise<void>((r) =>\n h.stop.signal.aborted ? r() : h.stop.signal.addEventListener(\"abort\", () => r()),\n );\n }\n } finally {\n // A planned hand-over drains the shard's requests in flight first; then the runtime\n // closes (and the pool with it) before the lock goes to the next tab.\n const lock = owners.get(shard);\n if (lock && running.has(shard)) await drain(shard, lock).catch(() => {});\n running.delete(shard);\n owners.delete(shard);\n await ask(w, { type: \"cellb:shard-close\" }).catch(() => {});\n w.terminate();\n changed();\n }\n })\n .catch((err) => {\n // Aborted while waiting is expected; a shard that failed to start is reported.\n if (!h.stop.signal.aborted)\n console.error(\n `cellb: ${opts.name} shard ${shard} failed: ${(err as Error)?.message ?? err}`,\n );\n })\n .finally(() => {\n if (held.get(shard) === h) held.delete(shard);\n });\n };\n\n let balancing: Promise<void> | undefined;\n let again = false;\n /** Takes the shards this tab should own and gives up the others. */\n const balance = async () => {\n if (balancing) {\n again = true;\n return balancing;\n }\n balancing = (async () => {\n do {\n again = false;\n if (closed) return;\n const present = await tabs();\n for (let shard = 0; shard < opts.shards; shard++) {\n const mine = shardOwner(shard, present) === tabId;\n const h = held.get(shard);\n if (mine && !h) take(shard);\n else if (!mine && h) h.stop.abort();\n }\n } while (again);\n })().finally(() => (balancing = undefined));\n return balancing;\n };\n\n channel.onmessage = () => void balance();\n const timer = setInterval(() => void balance(), opts.checkMs ?? 1000);\n const onSw = (e: MessageEvent) => {\n if (e.data?.type !== NEED_LINKS || (e.data.name && e.data.name !== opts.name)) return;\n for (const shard of running) {\n const w = held.get(shard)?.worker;\n const lock = owners.get(shard);\n if (w && lock) void attach(shard, w, lock).catch(() => {});\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", onSw);\n channel.postMessage({ joined: tabId });\n await balance();\n\n return {\n owned: () => [...running].sort((a, b) => a - b),\n tabs,\n async close() {\n closed = true;\n clearInterval(timer);\n navigator.serviceWorker.removeEventListener(\"message\", onSw);\n const stopping = [...held.values()];\n for (const h of stopping) h.stop.abort();\n await Promise.all(stopping.map((h) => h.running));\n leave();\n channel.postMessage({ left: tabId });\n channel.close();\n },\n };\n}\n\n/**\n * A ServiceWorker's global scope (its `self`), as `installProjectRouter` takes it: typed without\n * TypeScript's WebWorker lib, so that code importing cellb compiles with the DOM lib alone.\n */\nexport interface ServiceWorkerScope {\n readonly location: { readonly origin: string };\n readonly clients: unknown;\n addEventListener(type: string, listener: (event: never) => unknown): void;\n}\n\n/** The ServiceWorker side: one CellRouter per project, fed by the tabs' shard workers. */\nexport function installProjectRouter(\n scope: ServiceWorkerScope,\n opts: {\n /** Requests to `<basePath><name>/…` go to project `name`. */\n basePath: string;\n /**\n * The Worker sees the request's URL as the page sent it (default), as a Worker on a\n * Cloudflare route does: mount its routes under `<basePath><name>/`. With `stripBase`, it\n * sees `/<rest>` instead — URLs it builds from the request then lack the prefix.\n */\n stripBase?: boolean;\n attachTimeoutMs?: number;\n /**\n * A project served at the origin's root, for apps that build absolute paths (EmDash's\n * `/_emdash/…`, say) and so cannot live under `<basePath><n>/`. Every same-origin request\n * outside `basePath` and outside the `passthrough` path prefixes (the host page's own files)\n * goes to project `name` with its URL unchanged, navigations included.\n *\n * With `hostPage`, a navigation that finds no tab running the project's shards (after\n * `hostWaitMs`, default 2 s, for one to link) is redirected there, with the URL it asked\n * for as `?next=`: the page that starts the project, which a person reopening the site\n * would otherwise never reach, as every path of the origin belongs to the project.\n */\n root?: { name: string; passthrough?: string[]; hostPage?: string; hostWaitMs?: number };\n },\n): { routers: Map<string, CellRouter> } {\n const sw = scope as unknown as ServiceWorkerGlobalScope;\n const base = opts.basePath.endsWith(\"/\") ? opts.basePath : `${opts.basePath}/`;\n // Held while this ServiceWorker lives: the browser releases it when it stops the worker, which\n // tells the shards that their links to it are dead (MessagePorts report nothing).\n const routerLock = `cellb:router:${crypto.randomUUID()}`;\n const lockHeld = new Promise<void>((held) => {\n void navigator.locks.request(routerLock, () => {\n held();\n return new Promise<void>(() => {});\n });\n });\n const routers = new Map<string, CellRouter>();\n const created = new Map<string, () => void>();\n const ready = new Map<string, Promise<void>>();\n\n let asking: Promise<void> | undefined;\n const askForLinks = (name?: string) => {\n asking ??= sw.clients\n .matchAll({ type: \"window\", includeUncontrolled: true })\n .then((cs) => {\n for (const c of cs) c.postMessage({ type: NEED_LINKS, name });\n })\n .finally(() => setTimeout(() => (asking = undefined), 500));\n return asking;\n };\n\n const routerFor = (name: string, shards: number, scriptName: string) => {\n let router = routers.get(name);\n if (!router) {\n router = new CellRouter({\n shards,\n bindings: {}, // shards name the owner of every internal request (x-cellb-shard)\n scriptName,\n attachTimeoutMs: opts.attachTimeoutMs ?? 15_000,\n onMissingShard: () => void askForLinks(name),\n // The owner worker's own lock: released when it closes, its tab closes or it dies.\n watchOwner: (_shard, ownerLock) =>\n navigator.locks.request(ownerLock as string, async () => {}),\n isAlive: async (_shard, ownerLock) =>\n !!(await navigator.locks.query()).held?.some((l) => l.name === ownerLock),\n livenessIntervalMs: 5_000,\n });\n routers.set(name, router);\n created.get(name)?.();\n }\n return router;\n };\n\n /** A project's router; after a ServiceWorker restart, once a tab has attached a shard again. */\n const routerNamed = (name: string): Promise<CellRouter> => {\n const r = routers.get(name);\n if (r) return Promise.resolve(r);\n let wait = ready.get(name);\n if (!wait) {\n wait = new Promise<void>((resolve, reject) => {\n const t = setTimeout(\n () => reject(new Error(`project ${name} has no shards in any tab`)),\n opts.attachTimeoutMs ?? 15_000,\n );\n created.set(name, () => (clearTimeout(t), resolve()));\n }).finally(() => (ready.delete(name), created.delete(name)));\n ready.set(name, wait);\n void askForLinks(name);\n }\n return wait.then(() => routers.get(name)!);\n };\n\n sw.addEventListener(\"message\", (e: ExtendableMessageEvent) => {\n const m = e.data;\n const reply = e.ports[0];\n if (m?.type === ATTACH)\n e.waitUntil(\n (async () => {\n try {\n const router = routerFor(m.name, m.shards, m.scriptName);\n await serveHandlerOverPort(m.up, router.internal);\n const link = await linkOverPort(m.down);\n router.attach(m.shard, link.send, m.ownerLock, m.ctl);\n await lockHeld;\n reply?.postMessage({ ok: true, routerLock });\n } catch (err) {\n reply?.postMessage({ ok: false, error: String((err as Error)?.message ?? err) });\n }\n })(),\n );\n else if (m?.type === DRAIN)\n e.waitUntil(\n (async () => {\n await routers.get(m.name)?.drain(m.shard, m.ownerLock, m.ms);\n reply?.postMessage({ ok: true });\n })(),\n );\n else if (m?.type === WAKE)\n // Started by this message: stay up until a tab has linked the project's shards again.\n e.waitUntil(routerNamed(m.name).catch(() => {}));\n else if (m?.type === SOCKET)\n e.waitUntil(\n (async () => {\n try {\n const { name, path } = split(new URL(m.url));\n const url = new URL(m.url);\n if (opts.stripBase && !atRoot(url)) url.pathname = path;\n const { port, protocol } = await (await routerNamed(name)).connectWebSocket(\n url.toString(),\n m.protocols ?? [],\n );\n reply.postMessage({ ok: true, port, protocol }, [port]);\n } catch (err) {\n reply.postMessage({ ok: false, error: String((err as Error)?.message ?? err) });\n }\n })(),\n );\n });\n\n /** Whether `url` goes to the root project (`opts.root`). */\n const atRoot = (url: URL) =>\n !!opts.root &&\n url.origin === sw.location.origin &&\n !url.pathname.startsWith(base) &&\n !(opts.root.passthrough ?? []).some((p) => url.pathname.startsWith(p));\n\n /** `<base><name>/<rest>` → the project's name and the Worker's path `/<rest>`. */\n const split = (url: URL) => {\n if (opts.root && atRoot(url)) return { name: opts.root.name, path: url.pathname };\n if (!url.pathname.startsWith(base)) throw new Error(`not a project URL: ${url}`);\n const rest = url.pathname.slice(base.length);\n const slash = rest.indexOf(\"/\");\n const name = decodeURIComponent(slash < 0 ? rest : rest.slice(0, slash));\n return { name, path: slash < 0 ? \"/\" : rest.slice(slash) };\n };\n\n sw.addEventListener(\"fetch\", (e: FetchEvent) => {\n const url = new URL(e.request.url);\n const root = atRoot(url);\n if (!root) {\n if (url.origin !== sw.location.origin || !url.pathname.startsWith(base)) return;\n if (e.request.mode === \"navigate\") return;\n }\n const { name, path } = split(url);\n if (!name) return;\n const req = e.request;\n const target = opts.stripBase && !root ? new URL(`${path}${url.search}`, url.origin) : url;\n const hostPage = root && req.mode === \"navigate\" ? opts.root?.hostPage : undefined;\n if (hostPage)\n e.respondWith(\n runningShards(name, opts.root?.hostWaitMs ?? 2_000).then((running) =>\n running\n ? serve(name, req, target)\n : Response.redirect(\n `${new URL(hostPage, url.origin).href}?next=${encodeURIComponent(`${url.pathname}${url.search}`)}`,\n 302,\n ),\n ),\n );\n else e.respondWith(serve(name, req, target));\n });\n\n /** Whether a tab runs project `name`'s shards, waiting up to `ms` for one to link. */\n const runningShards = async (name: string, ms: number): Promise<boolean> => {\n const live = () => (routers.get(name)?.attachedShards.length ?? 0) > 0;\n if (live()) return true;\n void askForLinks(name);\n for (const until = Date.now() + ms; Date.now() < until; ) {\n await new Promise((r) => setTimeout(r, 100));\n if (live()) return true;\n }\n return false;\n };\n\n /** A request to project `name`, with the page's cookies in and the Worker's out. */\n const serve = (name: string, req: Request, target: URL): Promise<Response> =>\n routerNamed(name).then(\n async (router) => {\n try {\n return await applyWorkerCookies(\n sw,\n await router.ingress(\n new Request(target, {\n method: req.method,\n headers: await withPageCookies(sw, req),\n body: req.body,\n signal: req.signal,\n duplex: \"half\",\n } as RequestInit),\n ),\n );\n } catch (err) {\n // A response the page can read, instead of a network error with no cause.\n console.error(`cellb: ${req.method} ${target.pathname} failed:`, err);\n return new Response(`cellb: ${(err as Error)?.stack ?? err}`, { status: 502 });\n }\n },\n (err) => new Response(String((err as Error)?.message ?? err), { status: 503 }),\n );\n\n return { routers };\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cloudflare:email` module for Worker code run by cellb. cellb has no Email Routing,
|
|
3
|
+
* so the module only lets code that imports it load (the Agents SDK imports `EmailMessage`
|
|
4
|
+
* at top level); building a message throws.
|
|
5
|
+
*/
|
|
6
|
+
export declare class EmailMessage {
|
|
7
|
+
constructor(_from: string, _to: string, _raw: unknown);
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cloudflare:email` module for Worker code run by cellb. cellb has no Email Routing,
|
|
3
|
+
* so the module only lets code that imports it load (the Agents SDK imports `EmailMessage`
|
|
4
|
+
* at top level); building a message throws.
|
|
5
|
+
*/
|
|
6
|
+
export class EmailMessage {
|
|
7
|
+
constructor(_from, _to, _raw) {
|
|
8
|
+
throw new Error("cloudflare:email is not supported by cellb");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=cloudflare-email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-email.js","sourceRoot":"","sources":["../../src/project/cloudflare-email.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACvB,YAAY,KAAa,EAAE,GAAW,EAAE,IAAa;QACnD,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;CACF","sourcesContent":["/**\n * The `cloudflare:email` module for Worker code run by cellb. cellb has no Email Routing,\n * so the module only lets code that imports it load (the Agents SDK imports `EmailMessage`\n * at top level); building a message throws.\n */\nexport class EmailMessage {\n constructor(_from: string, _to: string, _raw: unknown) {\n throw new Error(\"cloudflare:email is not supported by cellb\");\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cloudflare:workers` module for Worker code run by cellb (mapped by the
|
|
3
|
+
* bundler / webrun-modules). Exposes the classes Workers code imports.
|
|
4
|
+
*/
|
|
5
|
+
export { DurableObject, WorkerEntrypoint } from "../core/durable-object.js";
|
|
6
|
+
/** Base class of Workflows (`class X extends WorkflowEntrypoint { run(event, step) }`). */
|
|
7
|
+
export declare class WorkflowEntrypoint<Env = unknown> {
|
|
8
|
+
protected ctx: unknown;
|
|
9
|
+
protected env: Env;
|
|
10
|
+
constructor(ctx: unknown, env: Env);
|
|
11
|
+
}
|
|
12
|
+
export { RpcTarget } from "../core/durable-object.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cloudflare:workers` module for Worker code run by cellb (mapped by the
|
|
3
|
+
* bundler / webrun-modules). Exposes the classes Workers code imports.
|
|
4
|
+
*/
|
|
5
|
+
export { DurableObject, WorkerEntrypoint } from "../core/durable-object.js";
|
|
6
|
+
/** Base class of Workflows (`class X extends WorkflowEntrypoint { run(event, step) }`). */
|
|
7
|
+
export class WorkflowEntrypoint {
|
|
8
|
+
ctx;
|
|
9
|
+
env;
|
|
10
|
+
constructor(ctx, env) {
|
|
11
|
+
this.ctx = ctx;
|
|
12
|
+
this.env = env;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export { RpcTarget } from "../core/durable-object.js";
|
|
16
|
+
//# sourceMappingURL=cloudflare-workers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-workers.js","sourceRoot":"","sources":["../../src/project/cloudflare-workers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE5E,2FAA2F;AAC3F,MAAM,OAAO,kBAAkB;IACnB,GAAG,CAAU;IACb,GAAG,CAAM;IACnB,YAAY,GAAY,EAAE,GAAQ;QAChC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["/**\n * The `cloudflare:workers` module for Worker code run by cellb (mapped by the\n * bundler / webrun-modules). Exposes the classes Workers code imports.\n */\nexport { DurableObject, WorkerEntrypoint } from \"../core/durable-object.js\";\n\n/** Base class of Workflows (`class X extends WorkflowEntrypoint { run(event, step) }`). */\nexport class WorkflowEntrypoint<Env = unknown> {\n protected ctx: unknown;\n protected env: Env;\n constructor(ctx: unknown, env: Env) {\n this.ctx = ctx;\n this.env = env;\n }\n}\n\nexport { RpcTarget } from \"../core/durable-object.js\";\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const NODE_MODULES_GLOBAL = "__cellsNodeModules";
|
|
2
|
+
export declare const CELLS_GLOBALS_MODULE = "cells:globals";
|
|
3
|
+
/** The source of a module the runtime provides, or `undefined`. */
|
|
4
|
+
export declare function cellbHostModule(spec: string): string | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a bundler other than `bundleWorkerForBrowser` needs to build a Worker that runs in
|
|
3
|
+
* cellb's browser realm (webrun-bundler in a browser, for one): the sources of the modules the
|
|
4
|
+
* runtime provides, by specifier, and the module whose exports replace the Workers globals.
|
|
5
|
+
*
|
|
6
|
+
* - `cloudflare:workers`: the runtime's classes, and the project's `env`, `exports` and
|
|
7
|
+
* `waitUntil` (`globalThis.__cellsProjectScope`).
|
|
8
|
+
* - `node:*` (nodejs_compat): cellb's polyfills, read from `globalThis.__cellsNodeModules`,
|
|
9
|
+
* which `browserProjectRuntime` sets; every name the polyfill exports is a named export.
|
|
10
|
+
* - `cells:globals` (to inject): `Request`, `Response`, `WebSocketPair`… of the runtime.
|
|
11
|
+
*
|
|
12
|
+
* The bundle should lower `await` (AsyncLocalStorage follows promise continuations).
|
|
13
|
+
*/
|
|
14
|
+
import { nodeRegistryEntries } from "../node-compat/browser-registry.js";
|
|
15
|
+
import { PROJECT_WORKERS_MODULE_SHIM } from "./browser-modules.js";
|
|
16
|
+
import { SCHEDULER_EXPRESSION } from "./scheduler.js";
|
|
17
|
+
import { WORKERS_GLOBAL_NAMES } from "./workers-global-names.js";
|
|
18
|
+
export const NODE_MODULES_GLOBAL = "__cellsNodeModules";
|
|
19
|
+
export const CELLS_GLOBALS_MODULE = "cells:globals";
|
|
20
|
+
const IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
21
|
+
function nodeModuleSource(spec) {
|
|
22
|
+
const name = spec.startsWith("node:") ? spec : `node:${spec}`;
|
|
23
|
+
if (!Object.hasOwn(nodeRegistryEntries, name))
|
|
24
|
+
return undefined;
|
|
25
|
+
const entry = nodeRegistryEntries[name];
|
|
26
|
+
const names = Object.keys(entry).filter((k) => k !== "default" && IDENT.test(k));
|
|
27
|
+
const m = `globalThis.${NODE_MODULES_GLOBAL}[${JSON.stringify(name)}]`;
|
|
28
|
+
return `const m = ${m}; export default m.default ?? m;${names.length ? ` export const { ${names.join(", ")} } = m;` : ""}`;
|
|
29
|
+
}
|
|
30
|
+
/** The source of a module the runtime provides, or `undefined`. */
|
|
31
|
+
export function cellbHostModule(spec) {
|
|
32
|
+
if (spec === "cloudflare:workers")
|
|
33
|
+
return PROJECT_WORKERS_MODULE_SHIM;
|
|
34
|
+
if (spec === CELLS_GLOBALS_MODULE)
|
|
35
|
+
return `${WORKERS_GLOBAL_NAMES.map((n) => `export const ${n} = globalThis.__cellsGlobals?.${n} ?? globalThis.${n};`).join(" ")} export const scheduler = ${SCHEDULER_EXPRESSION};`;
|
|
36
|
+
if (spec.startsWith("node:"))
|
|
37
|
+
return nodeModuleSource(spec);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=host-modules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-modules.js","sourceRoot":"","sources":["../../src/project/host-modules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,CAAC,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAEpD,MAAM,KAAK,GAAG,oBAAoB,CAAC;AAEnC,SAAS,gBAAgB,CAAC,IAAY;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,mBAAmB,EAAE,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAChE,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAA4B,CAAC;IACnE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,MAAM,CAAC,GAAG,cAAc,mBAAmB,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;IACvE,OAAO,aAAa,CAAC,mCAAmC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,mBAAmB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC7H,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,IAAI,KAAK,oBAAoB;QAAE,OAAO,2BAA2B,CAAC;IACtE,IAAI,IAAI,KAAK,oBAAoB;QAC/B,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,iCAAiC,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,6BAA6B,oBAAoB,GAAG,CAAC;IACrL,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["/**\n * What a bundler other than `bundleWorkerForBrowser` needs to build a Worker that runs in\n * cellb's browser realm (webrun-bundler in a browser, for one): the sources of the modules the\n * runtime provides, by specifier, and the module whose exports replace the Workers globals.\n *\n * - `cloudflare:workers`: the runtime's classes, and the project's `env`, `exports` and\n * `waitUntil` (`globalThis.__cellsProjectScope`).\n * - `node:*` (nodejs_compat): cellb's polyfills, read from `globalThis.__cellsNodeModules`,\n * which `browserProjectRuntime` sets; every name the polyfill exports is a named export.\n * - `cells:globals` (to inject): `Request`, `Response`, `WebSocketPair`… of the runtime.\n *\n * The bundle should lower `await` (AsyncLocalStorage follows promise continuations).\n */\nimport { nodeRegistryEntries } from \"../node-compat/browser-registry.js\";\nimport { PROJECT_WORKERS_MODULE_SHIM } from \"./browser-modules.js\";\nimport { SCHEDULER_EXPRESSION } from \"./scheduler.js\";\nimport { WORKERS_GLOBAL_NAMES } from \"./workers-global-names.js\";\n\nexport const NODE_MODULES_GLOBAL = \"__cellsNodeModules\";\nexport const CELLS_GLOBALS_MODULE = \"cells:globals\";\n\nconst IDENT = /^[A-Za-z_$][\\w$]*$/;\n\nfunction nodeModuleSource(spec: string): string | undefined {\n const name = spec.startsWith(\"node:\") ? spec : `node:${spec}`;\n if (!Object.hasOwn(nodeRegistryEntries, name)) return undefined;\n const entry = nodeRegistryEntries[name] as Record<string, unknown>;\n const names = Object.keys(entry).filter((k) => k !== \"default\" && IDENT.test(k));\n const m = `globalThis.${NODE_MODULES_GLOBAL}[${JSON.stringify(name)}]`;\n return `const m = ${m}; export default m.default ?? m;${names.length ? ` export const { ${names.join(\", \")} } = m;` : \"\"}`;\n}\n\n/** The source of a module the runtime provides, or `undefined`. */\nexport function cellbHostModule(spec: string): string | undefined {\n if (spec === \"cloudflare:workers\") return PROJECT_WORKERS_MODULE_SHIM;\n if (spec === CELLS_GLOBALS_MODULE)\n return `${WORKERS_GLOBAL_NAMES.map((n) => `export const ${n} = globalThis.__cellsGlobals?.${n} ?? globalThis.${n};`).join(\" \")} export const scheduler = ${SCHEDULER_EXPRESSION};`;\n if (spec.startsWith(\"node:\")) return nodeModuleSource(spec);\n return undefined;\n}\n"]}
|