@statewalker/cellb 0.0.0-stage → 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/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 +362 -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,247 @@
|
|
|
1
|
+
import { idFromName, namespaceKey } from "../core/id.js";
|
|
2
|
+
import { shardOf } from "../core/shard.js";
|
|
3
|
+
import { errorResponse, stripControl } from "../transport/cells-http.js";
|
|
4
|
+
import { H } from "../transport/cells-link.js";
|
|
5
|
+
import { PortRendezvous, shardOfControlId, } from "../ws/control.js";
|
|
6
|
+
/** Thrown into in-flight requests of a shard whose owner is gone (CF: "Durable Object reset"). */
|
|
7
|
+
export class ShardOwnerLostError extends Error {
|
|
8
|
+
constructor(shard) {
|
|
9
|
+
super(`Owner of cell shard ${shard} was lost; the request may or may not have been applied`);
|
|
10
|
+
this.name = "ShardOwnerLostError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The single "server" of an origin (ServiceWorker, page or Node main thread):
|
|
15
|
+
* knows which shard owns which cell and holds one HTTP link per shard worker.
|
|
16
|
+
*
|
|
17
|
+
* - `ingress(req)`: public requests → any live shard's Worker `fetch`. Control
|
|
18
|
+
* headers are stripped first, so clients cannot address objects directly.
|
|
19
|
+
* - `internal(req)`: requests from shard workers (DO → DO across shards) →
|
|
20
|
+
* forwarded to the owning shard, control headers intact.
|
|
21
|
+
*/
|
|
22
|
+
export class CellRouter {
|
|
23
|
+
#opts;
|
|
24
|
+
#links = new Map();
|
|
25
|
+
#watchdog;
|
|
26
|
+
#waiters = new Map();
|
|
27
|
+
#rr = 0;
|
|
28
|
+
#wsSeq = 0;
|
|
29
|
+
#wsShard = new Map();
|
|
30
|
+
#ports = new PortRendezvous();
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.#opts = opts;
|
|
33
|
+
}
|
|
34
|
+
get attachedShards() {
|
|
35
|
+
return [...this.#links]
|
|
36
|
+
.filter(([, l]) => !l.draining)
|
|
37
|
+
.map(([shard]) => shard)
|
|
38
|
+
.sort((a, b) => a - b);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Attaches the link of a shard. Replacing a link means the previous owner is
|
|
42
|
+
* gone (Web Locks / leases guarantee a single live owner): its in-flight
|
|
43
|
+
* requests fail at once instead of hanging on a dead port.
|
|
44
|
+
*/
|
|
45
|
+
attach(shard, send, token, ctl) {
|
|
46
|
+
const prev = this.#links.get(shard);
|
|
47
|
+
this.#links.set(shard, { send, token, inflight: new Set(), ctl });
|
|
48
|
+
if (ctl) {
|
|
49
|
+
ctl.onmessage = (e) => this.#onControl(e.data);
|
|
50
|
+
ctl.start?.();
|
|
51
|
+
}
|
|
52
|
+
if (prev)
|
|
53
|
+
this.#fail(shard, prev);
|
|
54
|
+
if (token !== undefined && this.#opts.watchOwner)
|
|
55
|
+
this.#opts.watchOwner(shard, token).then(() => this.#links.get(shard)?.token === token && this.detach(shard), () => { });
|
|
56
|
+
for (const wake of this.#waiters.get(shard) ?? [])
|
|
57
|
+
wake();
|
|
58
|
+
this.#waiters.delete(shard);
|
|
59
|
+
}
|
|
60
|
+
detach(shard, send) {
|
|
61
|
+
const link = this.#links.get(shard);
|
|
62
|
+
if (!link || (send && link.send !== send))
|
|
63
|
+
return;
|
|
64
|
+
this.#links.delete(shard);
|
|
65
|
+
this.#fail(shard, link);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A planned hand-over of `shard`: new requests wait for the next owner to attach, the ones in
|
|
69
|
+
* flight get up to `timeoutMs` to finish, then the link goes (the rest fail as when an owner is
|
|
70
|
+
* lost). Only the link attached with `token` is drained.
|
|
71
|
+
*/
|
|
72
|
+
async drain(shard, token, timeoutMs) {
|
|
73
|
+
const link = this.#links.get(shard);
|
|
74
|
+
if (!link || link.token !== token)
|
|
75
|
+
return;
|
|
76
|
+
link.draining = true;
|
|
77
|
+
const end = Date.now() + timeoutMs;
|
|
78
|
+
while (link.inflight.size && Date.now() < end && this.#links.get(shard) === link)
|
|
79
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
80
|
+
this.detach(shard, link.send);
|
|
81
|
+
}
|
|
82
|
+
/** Number of requests currently forwarded to a shard (diagnostics/tests). */
|
|
83
|
+
inflight(shard) {
|
|
84
|
+
return this.#links.get(shard)?.inflight.size ?? 0;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* WebSocket ingress (a ServiceWorker cannot intercept WebSockets): asks a live
|
|
88
|
+
* shard, over its control channel, to run the Worker's fetch with an Upgrade
|
|
89
|
+
* request; resolves with the client end's port (transfer it to the page).
|
|
90
|
+
*/
|
|
91
|
+
async connectWebSocket(url, protocols = [], headers) {
|
|
92
|
+
const shard = this.#pick();
|
|
93
|
+
const link = await this.#linkFor(shard);
|
|
94
|
+
if (!link.ctl)
|
|
95
|
+
throw new Error(`shard ${shard} has no control channel: WebSockets unavailable`);
|
|
96
|
+
const id = `r:${++this.#wsSeq}`;
|
|
97
|
+
this.#wsShard.set(id, shard);
|
|
98
|
+
link.ctl.postMessage({ t: "ws-connect", id, url, protocols, headers });
|
|
99
|
+
try {
|
|
100
|
+
return await this.#ports.wait(id);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
this.#wsShard.delete(id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
#onControl(m) {
|
|
107
|
+
if (m.t !== "ws-accept" && m.t !== "ws-refuse")
|
|
108
|
+
return;
|
|
109
|
+
const target = shardOfControlId(m.id);
|
|
110
|
+
if (target !== undefined) {
|
|
111
|
+
// Cross-shard hand-over: the Worker that asked lives in shard `target`.
|
|
112
|
+
const ctl = this.#links.get(target)?.ctl;
|
|
113
|
+
if (!ctl) {
|
|
114
|
+
if (m.t === "ws-accept")
|
|
115
|
+
m.port.close();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
ctl.postMessage(m, m.t === "ws-accept" ? [m.port] : []);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.#ports.deliver(m.id, m.t === "ws-accept" ? { port: m.port, protocol: m.protocol } : new Error(m.message));
|
|
122
|
+
}
|
|
123
|
+
#fail(shard, link) {
|
|
124
|
+
this.#ports.failAll((id) => this.#wsShard.get(id) === shard, new ShardOwnerLostError(shard));
|
|
125
|
+
for (const ac of link.inflight)
|
|
126
|
+
ac.abort(new ShardOwnerLostError(shard));
|
|
127
|
+
link.inflight.clear();
|
|
128
|
+
}
|
|
129
|
+
/** Forwards with an abort handle; the handle lives until the response body ends. */
|
|
130
|
+
async #forward(shard, req) {
|
|
131
|
+
const link = await this.#linkFor(shard);
|
|
132
|
+
const ac = new AbortController();
|
|
133
|
+
link.inflight.add(ac);
|
|
134
|
+
this.#ensureWatchdog();
|
|
135
|
+
const done = () => link.inflight.delete(ac);
|
|
136
|
+
const lost = new Promise((_, reject) => ac.signal.addEventListener("abort", () => reject(ac.signal.reason), { once: true }));
|
|
137
|
+
lost.catch(() => { });
|
|
138
|
+
try {
|
|
139
|
+
const signal = AbortSignal.any([req.signal, ac.signal]);
|
|
140
|
+
const res = await Promise.race([
|
|
141
|
+
link.send(new Request(req, { signal, duplex: "half" })),
|
|
142
|
+
lost,
|
|
143
|
+
]);
|
|
144
|
+
if (!res.body)
|
|
145
|
+
return done(), res;
|
|
146
|
+
const { readable, writable } = new TransformStream();
|
|
147
|
+
res.body.pipeTo(writable, { signal: ac.signal }).then(done, done);
|
|
148
|
+
return new Response(readable, res);
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
done();
|
|
152
|
+
throw ac.signal.aborted ? ac.signal.reason : e;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
#ensureWatchdog() {
|
|
156
|
+
if (this.#watchdog || !this.#opts.isAlive)
|
|
157
|
+
return;
|
|
158
|
+
this.#watchdog = setInterval(async () => {
|
|
159
|
+
const busy = [...this.#links].filter(([, l]) => l.inflight.size > 0);
|
|
160
|
+
if (!busy.length) {
|
|
161
|
+
clearInterval(this.#watchdog);
|
|
162
|
+
this.#watchdog = undefined;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
for (const [shard, link] of busy) {
|
|
166
|
+
const alive = await this.#opts.isAlive(shard, link.token).catch(() => true);
|
|
167
|
+
if (!alive && this.#links.get(shard) === link)
|
|
168
|
+
this.detach(shard);
|
|
169
|
+
}
|
|
170
|
+
}, this.#opts.livenessIntervalMs ?? 1000);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The shard for ingress: the live one with the fewest requests in flight (a long request
|
|
174
|
+
* keeps its shard busy, wherever its Durable Objects run), round robin among equals.
|
|
175
|
+
*/
|
|
176
|
+
#pick() {
|
|
177
|
+
const live = this.attachedShards;
|
|
178
|
+
if (!live.length)
|
|
179
|
+
return 0;
|
|
180
|
+
const start = this.#rr++ % live.length;
|
|
181
|
+
let best = live[start];
|
|
182
|
+
for (let i = 1; i < live.length; i++) {
|
|
183
|
+
const s = live[(start + i) % live.length];
|
|
184
|
+
if (this.inflight(s) < this.inflight(best))
|
|
185
|
+
best = s;
|
|
186
|
+
}
|
|
187
|
+
return best;
|
|
188
|
+
}
|
|
189
|
+
ingress = async (req) => {
|
|
190
|
+
const shard = this.#pick();
|
|
191
|
+
try {
|
|
192
|
+
// The page's cookies (x-cellb-cookie, set by the ServiceWorker) go on to the shard.
|
|
193
|
+
return await this.#forward(shard, stripControl(req, [H.cookie]));
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
return new Response(`Cell shard ${shard} unavailable: ${e.message}`, {
|
|
197
|
+
status: 503,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
internal = async (req) => {
|
|
202
|
+
const kind = req.headers.get(H.kind);
|
|
203
|
+
try {
|
|
204
|
+
if (kind === "describe")
|
|
205
|
+
return Response.json({ bindings: this.#opts.bindings });
|
|
206
|
+
if (kind !== "fetch" && kind !== "rpc")
|
|
207
|
+
return await this.ingress(req);
|
|
208
|
+
return await this.#forward(this.shardOfRequest(req), req);
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
return errorResponse(e, 503);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
shardOfRequest(req) {
|
|
215
|
+
const explicit = req.headers.get(H.shard);
|
|
216
|
+
if (explicit !== null) {
|
|
217
|
+
const shard = Number(explicit);
|
|
218
|
+
if (Number.isInteger(shard) && shard >= 0 && shard < this.#opts.shards)
|
|
219
|
+
return shard;
|
|
220
|
+
throw new Error(`Invalid cell shard "${explicit}"`);
|
|
221
|
+
}
|
|
222
|
+
const binding = req.headers.get(H.binding) ?? "";
|
|
223
|
+
const target = req.headers.get(H.target) ?? "";
|
|
224
|
+
const className = this.#opts.bindings[binding];
|
|
225
|
+
if (!className)
|
|
226
|
+
throw new Error(`Unknown Durable Object binding "${binding}"`);
|
|
227
|
+
const hex = target.startsWith("name:")
|
|
228
|
+
? idFromName(namespaceKey(this.#opts.scriptName ?? "worker", className), target.slice(5)).toString()
|
|
229
|
+
: target.slice(3);
|
|
230
|
+
return shardOf(hex, this.#opts.shards);
|
|
231
|
+
}
|
|
232
|
+
async #linkFor(shard) {
|
|
233
|
+
const link = this.#links.get(shard);
|
|
234
|
+
if (link && !link.draining)
|
|
235
|
+
return link;
|
|
236
|
+
this.#opts.onMissingShard?.(shard);
|
|
237
|
+
const timeout = this.#opts.attachTimeoutMs ?? 10_000;
|
|
238
|
+
await new Promise((resolve, reject) => {
|
|
239
|
+
const t = setTimeout(() => reject(new Error(`shard ${shard} not attached within ${timeout} ms`)), timeout);
|
|
240
|
+
const list = this.#waiters.get(shard) ?? [];
|
|
241
|
+
list.push(() => (clearTimeout(t), resolve()));
|
|
242
|
+
this.#waiters.set(shard, list);
|
|
243
|
+
});
|
|
244
|
+
return this.#links.get(shard);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=cell-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cell-router.js","sourceRoot":"","sources":["../../src/router/cell-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { idFromName, namespaceKey } from \"../core/id.js\";\nimport { shardOf } from \"../core/shard.js\";\nimport { errorResponse, stripControl } from \"../transport/cells-http.js\";\nimport { H, type Send } from \"../transport/cells-link.js\";\nimport {\n type ControlMessage,\n type ControlPort,\n PortRendezvous,\n shardOfControlId,\n} from \"../ws/control.js\";\n\nexport interface CellRouterOptions {\n shards: number;\n /** Durable Object binding → class name, as in wrangler config. */\n bindings: Record<string, string>;\n /** The Worker script's name: ids derive from it (default \"worker\", as the runtime's). */\n scriptName?: string;\n /** Called when a request needs a shard that has no live link (e.g. ask pages to re-attach). */\n onMissingShard?: (shard: number) => void;\n /** How long a request waits for its shard to be attached (default 10 s). */\n attachTimeoutMs?: number;\n /**\n * Liveness probe of the owner that attached `token`. Polled every\n * `livenessIntervalMs` while the shard has requests in flight; `false`\n * invalidates the link. In a ServiceWorker: is the shard's Web Lock still\n * held by the same holder? (MessagePort has no close event in Chromium.)\n */\n isAlive?: (shard: number, token: unknown) => Promise<boolean>;\n livenessIntervalMs?: number;\n /**\n * Event-driven liveness (preferred over polling): resolves when the owner that\n * attached `token` is gone. Browser: a pending request on the owner's own Web\n * Lock, granted the moment its worker/tab dies. Node: worker \"exit\".\n */\n watchOwner?: (shard: number, token: unknown) => Promise<unknown>;\n}\n\n/** Thrown into in-flight requests of a shard whose owner is gone (CF: \"Durable Object reset\"). */\nexport class ShardOwnerLostError extends Error {\n constructor(shard: number) {\n super(`Owner of cell shard ${shard} was lost; the request may or may not have been applied`);\n this.name = \"ShardOwnerLostError\";\n }\n}\n\ninterface Link {\n send: Send;\n token: unknown;\n inflight: Set<AbortController>;\n ctl?: ControlPort;\n /** Being handed over: no new requests (they wait for the next owner), the current ones finish. */\n draining?: boolean;\n}\n\n/**\n * The single \"server\" of an origin (ServiceWorker, page or Node main thread):\n * knows which shard owns which cell and holds one HTTP link per shard worker.\n *\n * - `ingress(req)`: public requests → any live shard's Worker `fetch`. Control\n * headers are stripped first, so clients cannot address objects directly.\n * - `internal(req)`: requests from shard workers (DO → DO across shards) →\n * forwarded to the owning shard, control headers intact.\n */\nexport class CellRouter {\n readonly #opts: CellRouterOptions;\n readonly #links = new Map<number, Link>();\n #watchdog: ReturnType<typeof setInterval> | undefined;\n readonly #waiters = new Map<number, (() => void)[]>();\n #rr = 0;\n #wsSeq = 0;\n readonly #wsShard = new Map<string, number>();\n readonly #ports = new PortRendezvous();\n\n constructor(opts: CellRouterOptions) {\n this.#opts = opts;\n }\n\n get attachedShards(): number[] {\n return [...this.#links]\n .filter(([, l]) => !l.draining)\n .map(([shard]) => shard)\n .sort((a, b) => a - b);\n }\n\n /**\n * Attaches the link of a shard. Replacing a link means the previous owner is\n * gone (Web Locks / leases guarantee a single live owner): its in-flight\n * requests fail at once instead of hanging on a dead port.\n */\n attach(shard: number, send: Send, token?: unknown, ctl?: ControlPort): void {\n const prev = this.#links.get(shard);\n this.#links.set(shard, { send, token, inflight: new Set(), ctl });\n if (ctl) {\n ctl.onmessage = (e) => this.#onControl(e.data as ControlMessage);\n ctl.start?.();\n }\n if (prev) this.#fail(shard, prev);\n if (token !== undefined && this.#opts.watchOwner)\n this.#opts.watchOwner(shard, token).then(\n () => this.#links.get(shard)?.token === token && this.detach(shard),\n () => {},\n );\n for (const wake of this.#waiters.get(shard) ?? []) wake();\n this.#waiters.delete(shard);\n }\n\n detach(shard: number, send?: Send): void {\n const link = this.#links.get(shard);\n if (!link || (send && link.send !== send)) return;\n this.#links.delete(shard);\n this.#fail(shard, link);\n }\n\n /**\n * A planned hand-over of `shard`: new requests wait for the next owner to attach, the ones in\n * flight get up to `timeoutMs` to finish, then the link goes (the rest fail as when an owner is\n * lost). Only the link attached with `token` is drained.\n */\n async drain(shard: number, token: unknown, timeoutMs: number): Promise<void> {\n const link = this.#links.get(shard);\n if (!link || link.token !== token) return;\n link.draining = true;\n const end = Date.now() + timeoutMs;\n while (link.inflight.size && Date.now() < end && this.#links.get(shard) === link)\n await new Promise((r) => setTimeout(r, 20));\n this.detach(shard, link.send);\n }\n\n /** Number of requests currently forwarded to a shard (diagnostics/tests). */\n inflight(shard: number): number {\n return this.#links.get(shard)?.inflight.size ?? 0;\n }\n\n /**\n * WebSocket ingress (a ServiceWorker cannot intercept WebSockets): asks a live\n * shard, over its control channel, to run the Worker's fetch with an Upgrade\n * request; resolves with the client end's port (transfer it to the page).\n */\n async connectWebSocket(url: string, protocols: string[] = [], headers?: [string, string][]) {\n const shard = this.#pick();\n const link = await this.#linkFor(shard);\n if (!link.ctl) throw new Error(`shard ${shard} has no control channel: WebSockets unavailable`);\n const id = `r:${++this.#wsSeq}`;\n this.#wsShard.set(id, shard);\n link.ctl.postMessage({ t: \"ws-connect\", id, url, protocols, headers } satisfies ControlMessage);\n try {\n return await this.#ports.wait(id);\n } finally {\n this.#wsShard.delete(id);\n }\n }\n\n #onControl(m: ControlMessage): void {\n if (m.t !== \"ws-accept\" && m.t !== \"ws-refuse\") return;\n const target = shardOfControlId(m.id);\n if (target !== undefined) {\n // Cross-shard hand-over: the Worker that asked lives in shard `target`.\n const ctl = this.#links.get(target)?.ctl;\n if (!ctl) {\n if (m.t === \"ws-accept\") m.port.close();\n return;\n }\n ctl.postMessage(m, m.t === \"ws-accept\" ? [m.port as unknown as Transferable] : []);\n return;\n }\n this.#ports.deliver(\n m.id,\n m.t === \"ws-accept\" ? { port: m.port, protocol: m.protocol } : new Error(m.message),\n );\n }\n\n #fail(shard: number, link: Link): void {\n this.#ports.failAll((id) => this.#wsShard.get(id) === shard, new ShardOwnerLostError(shard));\n for (const ac of link.inflight) ac.abort(new ShardOwnerLostError(shard));\n link.inflight.clear();\n }\n\n /** Forwards with an abort handle; the handle lives until the response body ends. */\n async #forward(shard: number, req: Request): Promise<Response> {\n const link = await this.#linkFor(shard);\n const ac = new AbortController();\n link.inflight.add(ac);\n this.#ensureWatchdog();\n const done = () => link.inflight.delete(ac);\n const lost = new Promise<never>((_, reject) =>\n ac.signal.addEventListener(\"abort\", () => reject(ac.signal.reason), { once: true }),\n );\n lost.catch(() => {});\n try {\n const signal = AbortSignal.any([req.signal, ac.signal]);\n const res = await Promise.race([\n link.send(new Request(req, { signal, duplex: \"half\" } as RequestInit)),\n lost,\n ]);\n if (!res.body) return done(), res;\n const { readable, writable } = new TransformStream();\n res.body.pipeTo(writable, { signal: ac.signal }).then(done, done);\n return new Response(readable, res);\n } catch (e) {\n done();\n throw ac.signal.aborted ? ac.signal.reason : e;\n }\n }\n\n #ensureWatchdog(): void {\n if (this.#watchdog || !this.#opts.isAlive) return;\n this.#watchdog = setInterval(async () => {\n const busy = [...this.#links].filter(([, l]) => l.inflight.size > 0);\n if (!busy.length) {\n clearInterval(this.#watchdog);\n this.#watchdog = undefined;\n return;\n }\n for (const [shard, link] of busy) {\n const alive = await this.#opts.isAlive!(shard, link.token).catch(() => true);\n if (!alive && this.#links.get(shard) === link) this.detach(shard);\n }\n }, this.#opts.livenessIntervalMs ?? 1000);\n }\n\n /**\n * The shard for ingress: the live one with the fewest requests in flight (a long request\n * keeps its shard busy, wherever its Durable Objects run), round robin among equals.\n */\n #pick(): number {\n const live = this.attachedShards;\n if (!live.length) return 0;\n const start = this.#rr++ % live.length;\n let best = live[start];\n for (let i = 1; i < live.length; i++) {\n const s = live[(start + i) % live.length];\n if (this.inflight(s) < this.inflight(best)) best = s;\n }\n return best;\n }\n\n ingress = async (req: Request): Promise<Response> => {\n const shard = this.#pick();\n try {\n // The page's cookies (x-cellb-cookie, set by the ServiceWorker) go on to the shard.\n return await this.#forward(shard, stripControl(req, [H.cookie]));\n } catch (e) {\n return new Response(`Cell shard ${shard} unavailable: ${(e as Error).message}`, {\n status: 503,\n });\n }\n };\n\n internal = async (req: Request): Promise<Response> => {\n const kind = req.headers.get(H.kind);\n try {\n if (kind === \"describe\") return Response.json({ bindings: this.#opts.bindings });\n if (kind !== \"fetch\" && kind !== \"rpc\") return await this.ingress(req);\n return await this.#forward(this.shardOfRequest(req), req);\n } catch (e) {\n return errorResponse(e, 503);\n }\n };\n\n shardOfRequest(req: Request): number {\n const explicit = req.headers.get(H.shard);\n if (explicit !== null) {\n const shard = Number(explicit);\n if (Number.isInteger(shard) && shard >= 0 && shard < this.#opts.shards) return shard;\n throw new Error(`Invalid cell shard \"${explicit}\"`);\n }\n const binding = req.headers.get(H.binding) ?? \"\";\n const target = req.headers.get(H.target) ?? \"\";\n const className = this.#opts.bindings[binding];\n if (!className) throw new Error(`Unknown Durable Object binding \"${binding}\"`);\n const hex = target.startsWith(\"name:\")\n ? idFromName(\n namespaceKey(this.#opts.scriptName ?? \"worker\", className),\n target.slice(5),\n ).toString()\n : target.slice(3);\n return shardOf(hex, this.#opts.shards);\n }\n\n async #linkFor(shard: number): Promise<Link> {\n const link = this.#links.get(shard);\n if (link && !link.draining) return link;\n this.#opts.onMissingShard?.(shard);\n const timeout = this.#opts.attachTimeoutMs ?? 10_000;\n await new Promise<void>((resolve, reject) => {\n const t = setTimeout(\n () => reject(new Error(`shard ${shard} not attached within ${timeout} ms`)),\n timeout,\n );\n const list = this.#waiters.get(shard) ?? [];\n list.push(() => (clearTimeout(t), resolve()));\n this.#waiters.set(shard, list);\n });\n return this.#links.get(shard)!;\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type CellRuntime, type CellRuntimeOptions } from "../core/runtime.js";
|
|
2
|
+
import { type CellPort } from "../transport/port.js";
|
|
3
|
+
import { type ControlPort } from "../ws/control.js";
|
|
4
|
+
export interface ShardWorkerOptions extends Omit<CellRuntimeOptions, "route"> {
|
|
5
|
+
shard: number;
|
|
6
|
+
shards: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ShardWorker {
|
|
9
|
+
readonly runtime: CellRuntime;
|
|
10
|
+
/**
|
|
11
|
+
* (Re)connects to a router: `down` = router → shard (served here),
|
|
12
|
+
* `up` = shard → router (for DO calls into other shards), `ctl` = control
|
|
13
|
+
* channel for WebSocket port hand-over. Called again when the router restarts
|
|
14
|
+
* (a ServiceWorker is killed when idle and loses its ports).
|
|
15
|
+
*/
|
|
16
|
+
link(down: CellPort, up: CellPort, ctl?: ControlPort): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Drops the links of a router that is gone (a stopped ServiceWorker leaves no trace on its
|
|
19
|
+
* ports): calls into other shards wait for the next `link`, and `onUnlinked` is told so.
|
|
20
|
+
*/
|
|
21
|
+
unlink(): void;
|
|
22
|
+
}
|
|
23
|
+
/** Runs one shard: hosts the cells of shard `shard`, forwards the others upstream. */
|
|
24
|
+
export declare function createShardWorker(opts: ShardWorkerOptions): ShardWorker;
|
|
25
|
+
/**
|
|
26
|
+
* The router side of a shard, for a runtime built elsewhere (a project's, in a browser worker):
|
|
27
|
+
* `route` sends the cells of other shards up to the router (waiting until it is linked);
|
|
28
|
+
* `attach(runtime)` gives the runtime its links to the router.
|
|
29
|
+
*/
|
|
30
|
+
export declare function shardRouting(opts: {
|
|
31
|
+
shard: number;
|
|
32
|
+
shards: number;
|
|
33
|
+
/** A call into another shard waits after `unlink`: wake the router (once per `unlink`). */
|
|
34
|
+
onUnlinked?: () => void;
|
|
35
|
+
/**
|
|
36
|
+
* Called every `keepAliveMs` (default 20 s) while calls into other shards are in flight: they
|
|
37
|
+
* are no events for the router's ServiceWorker, which the browser stops when idle (~30 s).
|
|
38
|
+
*/
|
|
39
|
+
keepAlive?: () => void;
|
|
40
|
+
keepAliveMs?: number;
|
|
41
|
+
}): {
|
|
42
|
+
route: NonNullable<CellRuntimeOptions["route"]>;
|
|
43
|
+
attach(runtime: CellRuntime): ShardWorker;
|
|
44
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createCellRuntime } from "../core/runtime.js";
|
|
2
|
+
import { shardOf } from "../core/shard.js";
|
|
3
|
+
import { H, keepUpgrade } from "../transport/cells-link.js";
|
|
4
|
+
import { linkOverPort, serveRuntime } from "../transport/port.js";
|
|
5
|
+
import { PortRendezvous } from "../ws/control.js";
|
|
6
|
+
import { CellsResponse, CfWebSocket } from "../ws/server.js";
|
|
7
|
+
/** Runs one shard: hosts the cells of shard `shard`, forwards the others upstream. */
|
|
8
|
+
export function createShardWorker(opts) {
|
|
9
|
+
const shard = shardRouting(opts);
|
|
10
|
+
const runtime = createCellRuntime({
|
|
11
|
+
...opts,
|
|
12
|
+
vars: { ...opts.vars, SHARD: opts.shard },
|
|
13
|
+
route: shard.route,
|
|
14
|
+
});
|
|
15
|
+
return shard.attach(runtime);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The router side of a shard, for a runtime built elsewhere (a project's, in a browser worker):
|
|
19
|
+
* `route` sends the cells of other shards up to the router (waiting until it is linked);
|
|
20
|
+
* `attach(runtime)` gives the runtime its links to the router.
|
|
21
|
+
*/
|
|
22
|
+
export function shardRouting(opts) {
|
|
23
|
+
let upstream;
|
|
24
|
+
let control;
|
|
25
|
+
const pending = [];
|
|
26
|
+
const ports = new PortRendezvous();
|
|
27
|
+
let wsSeq = 0;
|
|
28
|
+
let unlinked = false;
|
|
29
|
+
let wokeAt = 0;
|
|
30
|
+
let outstanding = 0; // calls into other shards in flight
|
|
31
|
+
let keepAliveTimer;
|
|
32
|
+
const started = () => {
|
|
33
|
+
if (outstanding++ === 0 && opts.keepAlive)
|
|
34
|
+
keepAliveTimer = setInterval(opts.keepAlive, opts.keepAliveMs ?? 20_000);
|
|
35
|
+
};
|
|
36
|
+
const finished = () => {
|
|
37
|
+
if (--outstanding === 0)
|
|
38
|
+
clearInterval(keepAliveTimer);
|
|
39
|
+
};
|
|
40
|
+
/** Calls in flight over the current links: a dead link never answers them. */
|
|
41
|
+
let inflight = new Set();
|
|
42
|
+
const dropInflight = () => {
|
|
43
|
+
const lost = inflight;
|
|
44
|
+
inflight = new Set();
|
|
45
|
+
const err = new Error("The router was lost while this call was in flight; it may or may not have been applied");
|
|
46
|
+
for (const fail of lost)
|
|
47
|
+
fail(err);
|
|
48
|
+
};
|
|
49
|
+
const rawUp = async (req) => {
|
|
50
|
+
// Woken once per unlink, again if the router is still not back after a while.
|
|
51
|
+
if (!upstream && unlinked && Date.now() - wokeAt > 2_000) {
|
|
52
|
+
wokeAt = Date.now();
|
|
53
|
+
opts.onUnlinked?.();
|
|
54
|
+
}
|
|
55
|
+
while (!upstream)
|
|
56
|
+
await new Promise((r) => pending.push(r));
|
|
57
|
+
const send = upstream;
|
|
58
|
+
const calls = inflight;
|
|
59
|
+
started();
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
calls.add(reject);
|
|
62
|
+
send(req)
|
|
63
|
+
.then(resolve, reject)
|
|
64
|
+
.finally(() => calls.delete(reject));
|
|
65
|
+
}).finally(finished);
|
|
66
|
+
};
|
|
67
|
+
// DO → DO calls into other shards. A WebSocket upgrade gets an id; the owning
|
|
68
|
+
// shard hands the socket's port over the control channel (via the router).
|
|
69
|
+
const up = async (req) => {
|
|
70
|
+
if (req.headers.get("Upgrade")?.toLowerCase() !== "websocket")
|
|
71
|
+
return rawUp(req);
|
|
72
|
+
const id = `s${opts.shard}:${++wsSeq}`;
|
|
73
|
+
const headers = new Headers(req.headers);
|
|
74
|
+
headers.set(H.ws, id);
|
|
75
|
+
headers.set(H.upgrade, "websocket"); // hop-by-hop `Upgrade` does not survive the HTTP/1.1 link
|
|
76
|
+
const offered = req.headers.get("Sec-WebSocket-Protocol"); // forbidden in browser Requests
|
|
77
|
+
if (offered)
|
|
78
|
+
headers.set(H.wsProtocol, offered);
|
|
79
|
+
const res = await rawUp(new Request(req.url, { method: req.method, headers }));
|
|
80
|
+
if (res.headers.get(H.wsAccepted) !== id)
|
|
81
|
+
return res; // refused upgrade (plain HTTP answer)
|
|
82
|
+
const { port, protocol } = await ports.wait(id);
|
|
83
|
+
return new CellsResponse(null, {
|
|
84
|
+
status: 101,
|
|
85
|
+
webSocket: new CfWebSocket(port),
|
|
86
|
+
headers: protocol ? { "Sec-WebSocket-Protocol": protocol } : {},
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
// The owner travels with the request: the router need not derive it (it cannot for a
|
|
90
|
+
// project's internal namespaces, whose ids come from other keys).
|
|
91
|
+
const route = (_key, id) => {
|
|
92
|
+
const owner = shardOf(id.toString(), opts.shards);
|
|
93
|
+
if (owner === opts.shard)
|
|
94
|
+
return undefined;
|
|
95
|
+
return (req) => {
|
|
96
|
+
const headers = new Headers(req.headers);
|
|
97
|
+
headers.set(H.shard, String(owner));
|
|
98
|
+
return up(keepUpgrade(req, new Request(req, { headers })));
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
let runtime;
|
|
102
|
+
const post = (m) => control?.postMessage(m, m.t === "ws-accept" ? [m.port] : []);
|
|
103
|
+
const onControl = async (e) => {
|
|
104
|
+
const m = e.data;
|
|
105
|
+
if (m.t === "ws-accept")
|
|
106
|
+
return ports.deliver(m.id, { port: m.port, protocol: m.protocol });
|
|
107
|
+
if (m.t === "ws-refuse")
|
|
108
|
+
return ports.deliver(m.id, new Error(m.message));
|
|
109
|
+
if (m.t === "ws-connect") {
|
|
110
|
+
try {
|
|
111
|
+
const { port, protocol } = await runtime.connectWebSocket(m.url, m.protocols, m.headers);
|
|
112
|
+
post({ t: "ws-accept", id: m.id, protocol, port });
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
post({ t: "ws-refuse", id: m.id, message: String(err?.message ?? err) });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
let stopDown;
|
|
120
|
+
let closeUp;
|
|
121
|
+
const attach = (rt) => {
|
|
122
|
+
runtime = rt;
|
|
123
|
+
return {
|
|
124
|
+
runtime,
|
|
125
|
+
link,
|
|
126
|
+
unlink() {
|
|
127
|
+
if (!upstream)
|
|
128
|
+
return;
|
|
129
|
+
upstream = undefined;
|
|
130
|
+
unlinked = true;
|
|
131
|
+
wokeAt = 0;
|
|
132
|
+
dropInflight();
|
|
133
|
+
const [prevStop, prevClose] = [stopDown, closeUp];
|
|
134
|
+
stopDown = closeUp = undefined;
|
|
135
|
+
void Promise.allSettled([prevStop?.(), prevClose?.()]);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
const link = async (down, upPort, ctl) => {
|
|
140
|
+
const [prevStop, prevClose] = [stopDown, closeUp];
|
|
141
|
+
if (ctl) {
|
|
142
|
+
control = ctl;
|
|
143
|
+
ctl.onmessage = (e) => void onControl(e);
|
|
144
|
+
ctl.start?.();
|
|
145
|
+
}
|
|
146
|
+
stopDown = await serveRuntime(down, runtime, {
|
|
147
|
+
// A Worker in another shard opened a socket to one of our objects.
|
|
148
|
+
onUpgrade: (id, port, protocol) => post({ t: "ws-accept", id, protocol, port }),
|
|
149
|
+
});
|
|
150
|
+
const l = await linkOverPort(upPort);
|
|
151
|
+
closeUp = l.close;
|
|
152
|
+
// A new router: the previous one is gone, and so are the answers to calls sent to it.
|
|
153
|
+
if (upstream)
|
|
154
|
+
dropInflight();
|
|
155
|
+
upstream = l.send;
|
|
156
|
+
unlinked = false;
|
|
157
|
+
for (const w of pending.splice(0))
|
|
158
|
+
w();
|
|
159
|
+
await Promise.allSettled([prevStop?.(), prevClose?.()]);
|
|
160
|
+
};
|
|
161
|
+
return { route, attach };
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=shard-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shard-worker.js","sourceRoot":"","sources":["../../src/router/shard-worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { type CellRuntime, type CellRuntimeOptions, createCellRuntime } from \"../core/runtime.js\";\nimport { shardOf } from \"../core/shard.js\";\nimport { H, keepUpgrade, type Send } from \"../transport/cells-link.js\";\nimport { type CellPort, linkOverPort, serveRuntime } from \"../transport/port.js\";\nimport { type ControlMessage, type ControlPort, PortRendezvous } from \"../ws/control.js\";\nimport { CellsResponse, CfWebSocket } from \"../ws/server.js\";\n\nexport interface ShardWorkerOptions extends Omit<CellRuntimeOptions, \"route\"> {\n shard: number;\n shards: number;\n}\n\nexport interface ShardWorker {\n readonly runtime: CellRuntime;\n /**\n * (Re)connects to a router: `down` = router → shard (served here),\n * `up` = shard → router (for DO calls into other shards), `ctl` = control\n * channel for WebSocket port hand-over. Called again when the router restarts\n * (a ServiceWorker is killed when idle and loses its ports).\n */\n link(down: CellPort, up: CellPort, ctl?: ControlPort): Promise<void>;\n /**\n * Drops the links of a router that is gone (a stopped ServiceWorker leaves no trace on its\n * ports): calls into other shards wait for the next `link`, and `onUnlinked` is told so.\n */\n unlink(): void;\n}\n\n/** Runs one shard: hosts the cells of shard `shard`, forwards the others upstream. */\nexport function createShardWorker(opts: ShardWorkerOptions): ShardWorker {\n const shard = shardRouting(opts);\n const runtime = createCellRuntime({\n ...opts,\n vars: { ...opts.vars, SHARD: opts.shard },\n route: shard.route,\n });\n return shard.attach(runtime);\n}\n\n/**\n * The router side of a shard, for a runtime built elsewhere (a project's, in a browser worker):\n * `route` sends the cells of other shards up to the router (waiting until it is linked);\n * `attach(runtime)` gives the runtime its links to the router.\n */\nexport function shardRouting(opts: {\n shard: number;\n shards: number;\n /** A call into another shard waits after `unlink`: wake the router (once per `unlink`). */\n onUnlinked?: () => void;\n /**\n * Called every `keepAliveMs` (default 20 s) while calls into other shards are in flight: they\n * are no events for the router's ServiceWorker, which the browser stops when idle (~30 s).\n */\n keepAlive?: () => void;\n keepAliveMs?: number;\n}): {\n route: NonNullable<CellRuntimeOptions[\"route\"]>;\n attach(runtime: CellRuntime): ShardWorker;\n} {\n let upstream: Send | undefined;\n let control: ControlPort | undefined;\n const pending: (() => void)[] = [];\n const ports = new PortRendezvous();\n let wsSeq = 0;\n let unlinked = false;\n let wokeAt = 0;\n let outstanding = 0; // calls into other shards in flight\n let keepAliveTimer: ReturnType<typeof setInterval> | undefined;\n const started = () => {\n if (outstanding++ === 0 && opts.keepAlive)\n keepAliveTimer = setInterval(opts.keepAlive, opts.keepAliveMs ?? 20_000);\n };\n const finished = () => {\n if (--outstanding === 0) clearInterval(keepAliveTimer);\n };\n /** Calls in flight over the current links: a dead link never answers them. */\n let inflight = new Set<(err: Error) => void>();\n const dropInflight = () => {\n const lost = inflight;\n inflight = new Set();\n const err = new Error(\n \"The router was lost while this call was in flight; it may or may not have been applied\",\n );\n for (const fail of lost) fail(err);\n };\n\n const rawUp: Send = async (req) => {\n // Woken once per unlink, again if the router is still not back after a while.\n if (!upstream && unlinked && Date.now() - wokeAt > 2_000) {\n wokeAt = Date.now();\n opts.onUnlinked?.();\n }\n while (!upstream) await new Promise<void>((r) => pending.push(r));\n const send = upstream;\n const calls = inflight;\n started();\n return new Promise<Response>((resolve, reject) => {\n calls.add(reject);\n send(req)\n .then(resolve, reject)\n .finally(() => calls.delete(reject));\n }).finally(finished);\n };\n // DO → DO calls into other shards. A WebSocket upgrade gets an id; the owning\n // shard hands the socket's port over the control channel (via the router).\n const up: Send = async (req) => {\n if (req.headers.get(\"Upgrade\")?.toLowerCase() !== \"websocket\") return rawUp(req);\n const id = `s${opts.shard}:${++wsSeq}`;\n const headers = new Headers(req.headers);\n headers.set(H.ws, id);\n headers.set(H.upgrade, \"websocket\"); // hop-by-hop `Upgrade` does not survive the HTTP/1.1 link\n const offered = req.headers.get(\"Sec-WebSocket-Protocol\"); // forbidden in browser Requests\n if (offered) headers.set(H.wsProtocol, offered);\n const res = await rawUp(new Request(req.url, { method: req.method, headers }));\n if (res.headers.get(H.wsAccepted) !== id) return res; // refused upgrade (plain HTTP answer)\n const { port, protocol } = await ports.wait(id);\n return new CellsResponse(null, {\n status: 101,\n webSocket: new CfWebSocket(port),\n headers: protocol ? { \"Sec-WebSocket-Protocol\": protocol } : {},\n });\n };\n\n // The owner travels with the request: the router need not derive it (it cannot for a\n // project's internal namespaces, whose ids come from other keys).\n const route = (_key: string, id: { toString(): string }): Send | undefined => {\n const owner = shardOf(id.toString(), opts.shards);\n if (owner === opts.shard) return undefined;\n return (req) => {\n const headers = new Headers(req.headers);\n headers.set(H.shard, String(owner));\n return up(keepUpgrade(req, new Request(req, { headers })));\n };\n };\n let runtime!: CellRuntime;\n\n const post = (m: ControlMessage) =>\n control?.postMessage(m, m.t === \"ws-accept\" ? [m.port as unknown as Transferable] : []);\n const onControl = async (e: MessageEvent) => {\n const m = e.data as ControlMessage;\n if (m.t === \"ws-accept\") return ports.deliver(m.id, { port: m.port, protocol: m.protocol });\n if (m.t === \"ws-refuse\") return ports.deliver(m.id, new Error(m.message));\n if (m.t === \"ws-connect\") {\n try {\n const { port, protocol } = await runtime.connectWebSocket(m.url, m.protocols, m.headers);\n post({ t: \"ws-accept\", id: m.id, protocol, port });\n } catch (err) {\n post({ t: \"ws-refuse\", id: m.id, message: String((err as Error)?.message ?? err) });\n }\n }\n };\n\n let stopDown: (() => Promise<void>) | undefined;\n let closeUp: (() => Promise<void>) | undefined;\n const attach = (rt: CellRuntime): ShardWorker => {\n runtime = rt;\n return {\n runtime,\n link,\n unlink() {\n if (!upstream) return;\n upstream = undefined;\n unlinked = true;\n wokeAt = 0;\n dropInflight();\n const [prevStop, prevClose] = [stopDown, closeUp];\n stopDown = closeUp = undefined;\n void Promise.allSettled([prevStop?.(), prevClose?.()]);\n },\n };\n };\n const link: ShardWorker[\"link\"] = async (down, upPort, ctl) => {\n const [prevStop, prevClose] = [stopDown, closeUp];\n if (ctl) {\n control = ctl;\n ctl.onmessage = (e) => void onControl(e);\n ctl.start?.();\n }\n stopDown = await serveRuntime(down, runtime, {\n // A Worker in another shard opened a socket to one of our objects.\n onUpgrade: (id, port, protocol) => post({ t: \"ws-accept\", id, protocol, port }),\n });\n const l = await linkOverPort(upPort);\n closeUp = l.close;\n // A new router: the previous one is gone, and so are the answers to calls sent to it.\n if (upstream) dropInflight();\n upstream = l.send;\n unlinked = false;\n for (const w of pending.splice(0)) w();\n await Promise.allSettled([prevStop?.(), prevClose?.()]);\n };\n return { route, attach };\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DurableObjectNamespace } from "../core/namespace.js";
|
|
2
|
+
import type { CellRuntime } from "../core/runtime.js";
|
|
3
|
+
import type { ValueCodec } from "../ports.js";
|
|
4
|
+
import { type Send } from "./cells-link.js";
|
|
5
|
+
export interface CellsHttpOptions {
|
|
6
|
+
codec?: ValueCodec;
|
|
7
|
+
/**
|
|
8
|
+
* A stub.fetch answered 101 with a WebSocket: hand the client end's port to the
|
|
9
|
+
* caller out of band (the HTTP link cannot carry ports). Without it, WebSocket
|
|
10
|
+
* upgrades cannot cross this link.
|
|
11
|
+
*/
|
|
12
|
+
onUpgrade?: (id: string, port: MessagePort, protocol?: string) => void;
|
|
13
|
+
}
|
|
14
|
+
/** Server side: exposes a runtime as one HTTP handler. */
|
|
15
|
+
export declare function errorResponse(e: unknown, status?: number): Response;
|
|
16
|
+
export declare function cellsHttpHandler(runtime: CellRuntime, opts?: CellsHttpOptions): (req: Request) => Promise<Response>;
|
|
17
|
+
export interface RemoteRuntime {
|
|
18
|
+
/** Ingress into the remote Worker's default fetch handler. */
|
|
19
|
+
fetch(req: Request): Promise<Response>;
|
|
20
|
+
/** Namespaces whose stubs send HTTP over the link (idFromName computed locally). */
|
|
21
|
+
readonly env: Record<string, DurableObjectNamespace>;
|
|
22
|
+
}
|
|
23
|
+
/** Client side over any `send` (fetchOverDuplex over a port / WebSocket / peer, or the handler itself). */
|
|
24
|
+
export declare function remoteRuntime(send: Send, opts?: CellsHttpOptions): Promise<RemoteRuntime>;
|
|
25
|
+
export declare function stripControl(req: Request, keep?: string[]): Request;
|