@statewalker/cellb 0.1.1 → 0.3.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.
Files changed (81) hide show
  1. package/README.md +51 -25
  2. package/dist/adapters/wasm-sqlite.d.ts +11 -0
  3. package/dist/adapters/wasm-sqlite.js +26 -3
  4. package/dist/adapters/wasm-sqlite.js.map +1 -1
  5. package/dist/bindings/assets.js +2 -2
  6. package/dist/bindings/assets.js.map +1 -1
  7. package/dist/bindings/r2.js +2 -2
  8. package/dist/bindings/r2.js.map +1 -1
  9. package/dist/browser.d.ts +0 -1
  10. package/dist/browser.js +0 -1
  11. package/dist/browser.js.map +1 -1
  12. package/dist/bucket/celld-cells.js +1 -1
  13. package/dist/bucket/celld-cells.js.map +1 -1
  14. package/dist/bucket/s3-object-store.js +1 -1
  15. package/dist/bucket/s3-object-store.js.map +1 -1
  16. package/dist/core/handles.d.ts +5 -2
  17. package/dist/core/handles.js +12 -1
  18. package/dist/core/handles.js.map +1 -1
  19. package/dist/core/id.js +3 -3
  20. package/dist/core/id.js.map +1 -1
  21. package/dist/core/namespace.d.ts +1 -1
  22. package/dist/core/namespace.js +2 -1
  23. package/dist/core/namespace.js.map +1 -1
  24. package/dist/core/object-host.d.ts +23 -0
  25. package/dist/core/object-host.js +87 -6
  26. package/dist/core/object-host.js.map +1 -1
  27. package/dist/core/runtime.d.ts +10 -0
  28. package/dist/core/runtime.js +43 -2
  29. package/dist/core/runtime.js.map +1 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +2 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/isolate/browser.js +1 -1
  34. package/dist/isolate/browser.js.map +1 -1
  35. package/dist/isolate/host.d.ts +5 -1
  36. package/dist/isolate/host.js +29 -2
  37. package/dist/isolate/host.js.map +1 -1
  38. package/dist/isolate/isolate-worker.js +11 -1
  39. package/dist/isolate/isolate-worker.js.map +1 -1
  40. package/dist/isolate/isolate.js +20 -0
  41. package/dist/isolate/isolate.js.map +1 -1
  42. package/dist/isolate/node-entry.js +11 -0
  43. package/dist/isolate/node-entry.js.map +1 -1
  44. package/dist/isolate/protocol.d.ts +5 -0
  45. package/dist/isolate/protocol.js +114 -2
  46. package/dist/isolate/protocol.js.map +1 -1
  47. package/dist/node-compat/crypto.js +3 -3
  48. package/dist/node-compat/crypto.js.map +1 -1
  49. package/dist/peer/celld-peer.d.ts +17 -0
  50. package/dist/peer/celld-peer.js +49 -7
  51. package/dist/peer/celld-peer.js.map +1 -1
  52. package/dist/project/browser-host.js +3 -2
  53. package/dist/project/browser-host.js.map +1 -1
  54. package/dist/project/node-project.js +2 -1
  55. package/dist/project/node-project.js.map +1 -1
  56. package/dist/project/site-loader.d.ts +45 -0
  57. package/dist/project/site-loader.js +97 -0
  58. package/dist/project/site-loader.js.map +1 -0
  59. package/dist/project/site.d.ts +0 -36
  60. package/dist/project/site.js +11 -99
  61. package/dist/project/site.js.map +1 -1
  62. package/dist/project/wrangler.js +10 -0
  63. package/dist/project/wrangler.js.map +1 -1
  64. package/dist/router/cell-router.js +1 -1
  65. package/dist/router/cell-router.js.map +1 -1
  66. package/dist/site.d.ts +1 -0
  67. package/dist/site.js +6 -0
  68. package/dist/site.js.map +1 -0
  69. package/dist/transport/port.d.ts +3 -4
  70. package/dist/transport/port.js +21 -79
  71. package/dist/transport/port.js.map +1 -1
  72. package/dist/ws/client.d.ts +23 -8
  73. package/dist/ws/client.js +50 -189
  74. package/dist/ws/client.js.map +1 -1
  75. package/dist/ws/frames.d.ts +31 -8
  76. package/dist/ws/frames.js +54 -7
  77. package/dist/ws/frames.js.map +1 -1
  78. package/dist/ws/server.d.ts +4 -0
  79. package/dist/ws/server.js +29 -5
  80. package/dist/ws/server.js.map +1 -1
  81. package/package.json +29 -10
@@ -1 +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"]}
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"]}
package/dist/site.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { createProjectSite } from "./project/site.js";
package/dist/site.js ADDED
@@ -0,0 +1,6 @@
1
+ // @statewalker/cellb/site: a project's sources compiled and served in the browser (an in-browser
2
+ // HTTP server for `browserProjectRuntime({ site })`). It needs the optional peer dependencies
3
+ // @statewalker/webrun-modules and @statewalker/webrun-site-builder; the embedder hosts the site in
4
+ // a ServiceWorker with @statewalker/webrun-site-host, the third optional peer.
5
+ export { createProjectSite } from "./project/site.js";
6
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,8FAA8F;AAC9F,mGAAmG;AACnG,+EAA+E;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// @statewalker/cellb/site: a project's sources compiled and served in the browser (an in-browser\n// HTTP server for `browserProjectRuntime({ site })`). It needs the optional peer dependencies\n// @statewalker/webrun-modules and @statewalker/webrun-site-builder; the embedder hosts the site in\n// a ServiceWorker with @statewalker/webrun-site-host, the third optional peer.\nexport { createProjectSite } from \"./project/site.js\";\n"]}
@@ -1,4 +1,4 @@
1
- import { overPipe } from "@statewalker/webrun-rpc";
1
+ import type { overPipe } from "@statewalker/webrun-rpc";
2
2
  import type { CellRuntime } from "../core/runtime.js";
3
3
  import { type CellsHttpOptions, type RemoteRuntime } from "./cells-http.js";
4
4
  import { type Send } from "./cells-link.js";
@@ -8,9 +8,8 @@ import { type Send } from "./cells-link.js";
8
8
  */
9
9
  export type CellPort = Parameters<typeof overPipe>[0];
10
10
  /**
11
- * Cell-host side: serves the runtime's HTTP surface over a port with
12
- * webrun-rpc (mux + backpressure) and webrun-http-streams (HTTP/1.1 on the
13
- * Duplex, streaming bodies). Resolves to an async disposer.
11
+ * Cell-host side: serves the runtime's HTTP surface over a port. Resolves to an async
12
+ * disposer.
14
13
  */
15
14
  export declare function serveRuntime(port: CellPort, runtime: CellRuntime, opts?: CellsHttpOptions): Promise<() => Promise<void>>;
16
15
  /** Front side (page, SW bridge, another tab): a RemoteRuntime over a port. */
@@ -1,100 +1,42 @@
1
- import { fetchOverDuplex, serveFetchOverDuplex } from "@statewalker/webrun-http-streams";
2
- import { connect, overPipe, serve, structuredCodec } from "@statewalker/webrun-rpc";
1
+ import { connectHttpLink, serveHttpLink } from "@statewalker/webrun-http-browser/sw";
3
2
  import { cellsHttpHandler, remoteRuntime, } from "./cells-http.js";
4
3
  import { withScheme } from "./cells-link.js";
5
4
  /**
6
- * Multiplexed ports per link: one per exchange in flight (they are released when an exchange
7
- * ends, is cancelled or has no body: fetchClosing, servingClosing; p31-port-leak.test.ts).
8
- * `globalThis.__cellbMaxPorts` overrides it (tests that check nothing leaks set it low).
5
+ * Multiplexed ports per link: one per exchange in flight, given back when the exchange ends
6
+ * (p31-port-leak.test.ts). 4096 per link, cellb's ceiling before the links were the package's (its
7
+ * default is 1024): a streaming response holds its port for its whole life.
8
+ * `globalThis.__cellbMaxPorts` overrides it: tests that check nothing leaks set it low.
9
9
  */
10
10
  const maxPorts = () => globalThis.__cellbMaxPorts ?? 4096;
11
11
  /**
12
- * Cell-host side: serves the runtime's HTTP surface over a port with
13
- * webrun-rpc (mux + backpressure) and webrun-http-streams (HTTP/1.1 on the
14
- * Duplex, streaming bodies). Resolves to an async disposer.
12
+ * The links are webrun-http-browser's (`connectHttpLink` / `serveHttpLink`): HTTP/1.1 with
13
+ * streaming bodies over webrun-rpc calls, one multiplexed port per exchange. They take a
14
+ * MessagePort; a worker_threads port has the same `postMessage` / `addEventListener` surface.
15
+ */
16
+ const asPort = (port) => port;
17
+ /**
18
+ * Cell-host side: serves the runtime's HTTP surface over a port. Resolves to an async
19
+ * disposer.
15
20
  */
16
21
  export function serveRuntime(port, runtime, opts) {
17
- return serve({ mux: overPipe(port, { codec: structuredCodec, side: "responder", maxPorts: maxPorts() }) }, servingClosing(serveFetchOverDuplex(cellsHttpHandler(runtime, opts))));
18
- }
19
- async function fetchClosing(call, req) {
20
- let output;
21
- const res = await fetchOverDuplex(((input) => {
22
- output = call(input);
23
- return output;
24
- }), req);
25
- const drain = () => void (async () => {
26
- try {
27
- for await (const _ of output)
28
- ;
29
- }
30
- catch { }
31
- })();
32
- if (!output)
33
- return res;
34
- if (res.body === null) {
35
- drain();
36
- return res;
37
- }
38
- // The same once the body is read to its end, or cancelled by its reader (then the exchange
39
- // is abandoned: the call's iterator is returned, which closes its port).
40
- const reader = res.body.getReader();
41
- const body = new ReadableStream({
42
- async pull(ctl) {
43
- const r = await reader.read();
44
- if (r.done) {
45
- ctl.close();
46
- drain();
47
- }
48
- else
49
- ctl.enqueue(r.value);
50
- },
51
- async cancel(reason) {
52
- await reader.cancel(reason).catch(() => { });
53
- await output.return?.(undefined).catch?.(() => { });
54
- },
55
- });
56
- return Object.assign(new Response(body, res), {
57
- webSocket: res.webSocket ?? null,
58
- });
59
- }
60
- function servingClosing(duplex) {
61
- return async function* (input) {
62
- const it = input[Symbol.asyncIterator]();
63
- const rest = { [Symbol.asyncIterator]: () => it };
64
- try {
65
- yield* duplex(rest);
66
- }
67
- finally {
68
- void (async () => {
69
- try {
70
- for (let r = await it.next(); !r.done; r = await it.next())
71
- ;
72
- }
73
- catch { }
74
- })();
75
- }
76
- };
22
+ return serveHttpLink(asPort(port), cellsHttpHandler(runtime, opts), { maxPorts: maxPorts() });
77
23
  }
78
24
  /** Front side (page, SW bridge, another tab): a RemoteRuntime over a port. */
79
25
  export async function connectRuntime(port, opts) {
80
- const { call, close } = await connect({
81
- mux: overPipe(port, { codec: structuredCodec, side: "initiator", maxPorts: maxPorts() }),
82
- });
83
- const remote = await remoteRuntime((req) => fetchClosing(call, req), opts);
84
- return Object.assign(remote, { close: async () => void (await close()) });
26
+ const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });
27
+ const remote = await remoteRuntime((req) => link.send(req), opts);
28
+ return Object.assign(remote, { close: () => link.close() });
85
29
  }
86
30
  /** Serves any HTTP handler over a port (responder side of a link). */
87
31
  export function serveHandlerOverPort(port, handler) {
88
- return serve({ mux: overPipe(port, { codec: structuredCodec, side: "responder", maxPorts: maxPorts() }) }, servingClosing(serveFetchOverDuplex(handler)));
32
+ return serveHttpLink(asPort(port), handler, { maxPorts: maxPorts() });
89
33
  }
90
34
  /** A `send` over a port (initiator side of a link); the scheme rides in a header. */
91
35
  export async function linkOverPort(port) {
92
- const { call, close } = await connect({
93
- mux: overPipe(port, { codec: structuredCodec, side: "initiator", maxPorts: maxPorts() }),
94
- });
36
+ const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });
95
37
  return {
96
- send: (req) => fetchClosing(call, withScheme(req)),
97
- close: async () => void (await close()),
38
+ send: (req) => link.send(withScheme(req)),
39
+ close: () => link.close(),
98
40
  };
99
41
  }
100
42
  //# sourceMappingURL=port.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/transport/port.ts"],"names":[],"mappings":"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","sourcesContent":["import { fetchOverDuplex, serveFetchOverDuplex } from \"@statewalker/webrun-http-streams\";\nimport { connect, overPipe, serve, structuredCodec } from \"@statewalker/webrun-rpc\";\nimport type { CellRuntime } from \"../core/runtime.js\";\nimport {\n type CellsHttpOptions,\n cellsHttpHandler,\n type RemoteRuntime,\n remoteRuntime,\n} from \"./cells-http.js\";\nimport { type Send, withScheme } from \"./cells-link.js\";\n\n/**\n * Multiplexed ports per link: one per exchange in flight (they are released when an exchange\n * ends, is cancelled or has no body: fetchClosing, servingClosing; p31-port-leak.test.ts).\n * `globalThis.__cellbMaxPorts` overrides it (tests that check nothing leaks set it low).\n */\nconst maxPorts = () => (globalThis as { __cellbMaxPorts?: number }).__cellbMaxPorts ?? 4096;\n\n/**\n * A DEDICATED message pipe (MessagePort, or a worker_threads port). Don't pass a\n * Worker object that also carries other messages — transfer a MessageChannel port.\n */\nexport type CellPort = Parameters<typeof overPipe>[0];\n\n/**\n * Cell-host side: serves the runtime's HTTP surface over a port with\n * webrun-rpc (mux + backpressure) and webrun-http-streams (HTTP/1.1 on the\n * Duplex, streaming bodies). Resolves to an async disposer.\n */\nexport function serveRuntime(\n port: CellPort,\n runtime: CellRuntime,\n opts?: CellsHttpOptions,\n): Promise<() => Promise<void>> {\n return serve(\n { mux: overPipe(port, { codec: structuredCodec, side: \"responder\", maxPorts: maxPorts() }) },\n servingClosing(serveFetchOverDuplex(cellsHttpHandler(runtime, opts))),\n ) as Promise<() => Promise<void>>;\n}\n\n/**\n * fetchOverDuplex, draining the call's output when the response has no body (HEAD, 204, 304…):\n * webrun-http-streams then returns without reading the exchange to its end, and the call's\n * multiplexed port stayed open, one per such request, until `maxPorts` (1024) refused calls.\n */\ntype Call = Parameters<typeof fetchOverDuplex>[0];\nasync function fetchClosing(call: Call, req: Request): Promise<Response> {\n let output: AsyncIterable<unknown> | undefined;\n const res = await fetchOverDuplex(\n ((input: unknown) => {\n output = (call as (i: unknown) => AsyncIterable<unknown>)(input);\n return output;\n }) as Call,\n req,\n );\n const drain = () =>\n void (async () => {\n try {\n for await (const _ of output as AsyncIterable<unknown>);\n } catch {}\n })();\n if (!output) return res;\n if (res.body === null) {\n drain();\n return res;\n }\n // The same once the body is read to its end, or cancelled by its reader (then the exchange\n // is abandoned: the call's iterator is returned, which closes its port).\n const reader = (res.body as ReadableStream<Uint8Array>).getReader();\n const body = new ReadableStream<Uint8Array>({\n async pull(ctl) {\n const r = await reader.read();\n if (r.done) {\n ctl.close();\n drain();\n } else ctl.enqueue(r.value);\n },\n async cancel(reason) {\n await reader.cancel(reason).catch(() => {});\n await (output as AsyncGenerator<unknown>).return?.(undefined).catch?.(() => {});\n },\n });\n return Object.assign(new Response(body, res), {\n webSocket: (res as { webSocket?: unknown }).webSocket ?? null,\n });\n}\n\n/**\n * serveFetchOverDuplex, draining what is left of the request side once the response is out:\n * a handler that does not read the request body (a GET) left the exchange half open, and its\n * multiplexed port open, one per request.\n */\ntype Duplex = ReturnType<typeof serveFetchOverDuplex>;\nfunction servingClosing(duplex: Duplex): Duplex {\n return async function* (input: AsyncIterable<unknown>) {\n const it = input[Symbol.asyncIterator]();\n const rest: AsyncIterable<unknown> = { [Symbol.asyncIterator]: () => it };\n try {\n yield* (duplex as (i: AsyncIterable<unknown>) => AsyncIterable<unknown>)(rest);\n } finally {\n void (async () => {\n try {\n for (let r = await it.next(); !r.done; r = await it.next());\n } catch {}\n })();\n }\n } as unknown as Duplex;\n}\n\n/** Front side (page, SW bridge, another tab): a RemoteRuntime over a port. */\nexport async function connectRuntime(\n port: CellPort,\n opts?: CellsHttpOptions,\n): Promise<RemoteRuntime & { close(): Promise<void> }> {\n const { call, close } = await connect({\n mux: overPipe(port, { codec: structuredCodec, side: \"initiator\", maxPorts: maxPorts() }),\n });\n const remote = await remoteRuntime((req) => fetchClosing(call, req), opts);\n return Object.assign(remote, { close: async () => void (await close()) });\n}\n\n/** Serves any HTTP handler over a port (responder side of a link). */\nexport function serveHandlerOverPort(port: CellPort, handler: Send): Promise<() => Promise<void>> {\n return serve(\n { mux: overPipe(port, { codec: structuredCodec, side: \"responder\", maxPorts: maxPorts() }) },\n servingClosing(serveFetchOverDuplex(handler)),\n ) as Promise<() => Promise<void>>;\n}\n\n/** A `send` over a port (initiator side of a link); the scheme rides in a header. */\nexport async function linkOverPort(\n port: CellPort,\n): Promise<{ send: Send; close(): Promise<void> }> {\n const { call, close } = await connect({\n mux: overPipe(port, { codec: structuredCodec, side: \"initiator\", maxPorts: maxPorts() }),\n });\n return {\n send: (req) => fetchClosing(call, withScheme(req)),\n close: async () => void (await close()),\n };\n}\n"]}
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/transport/port.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAGrF,OAAO,EAEL,gBAAgB,EAEhB,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAa,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAE,UAA2C,CAAC,eAAe,IAAI,IAAI,CAAC;AAQ5F;;;;GAIG;AACH,MAAM,MAAM,GAAG,CAAC,IAAc,EAAE,EAAE,CAAC,IAA8B,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAc,EACd,OAAoB,EACpB,IAAuB;IAEvB,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AAChG,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAc,EACd,IAAuB;IAEvB,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAClE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAAC,IAAc,EAAE,OAAa;IAChE,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAc;IAEd,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC3E,OAAO;QACL,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE;KAC1B,CAAC;AACJ,CAAC","sourcesContent":["import { connectHttpLink, serveHttpLink } from \"@statewalker/webrun-http-browser/sw\";\nimport type { overPipe } from \"@statewalker/webrun-rpc\";\nimport type { CellRuntime } from \"../core/runtime.js\";\nimport {\n type CellsHttpOptions,\n cellsHttpHandler,\n type RemoteRuntime,\n remoteRuntime,\n} from \"./cells-http.js\";\nimport { type Send, withScheme } from \"./cells-link.js\";\n\n/**\n * Multiplexed ports per link: one per exchange in flight, given back when the exchange ends\n * (p31-port-leak.test.ts). 4096 per link, cellb's ceiling before the links were the package's (its\n * default is 1024): a streaming response holds its port for its whole life.\n * `globalThis.__cellbMaxPorts` overrides it: tests that check nothing leaks set it low.\n */\nconst maxPorts = () => (globalThis as { __cellbMaxPorts?: number }).__cellbMaxPorts ?? 4096;\n\n/**\n * A DEDICATED message pipe (MessagePort, or a worker_threads port). Don't pass a\n * Worker object that also carries other messages — transfer a MessageChannel port.\n */\nexport type CellPort = Parameters<typeof overPipe>[0];\n\n/**\n * The links are webrun-http-browser's (`connectHttpLink` / `serveHttpLink`): HTTP/1.1 with\n * streaming bodies over webrun-rpc calls, one multiplexed port per exchange. They take a\n * MessagePort; a worker_threads port has the same `postMessage` / `addEventListener` surface.\n */\nconst asPort = (port: CellPort) => port as unknown as MessagePort;\n\n/**\n * Cell-host side: serves the runtime's HTTP surface over a port. Resolves to an async\n * disposer.\n */\nexport function serveRuntime(\n port: CellPort,\n runtime: CellRuntime,\n opts?: CellsHttpOptions,\n): Promise<() => Promise<void>> {\n return serveHttpLink(asPort(port), cellsHttpHandler(runtime, opts), { maxPorts: maxPorts() });\n}\n\n/** Front side (page, SW bridge, another tab): a RemoteRuntime over a port. */\nexport async function connectRuntime(\n port: CellPort,\n opts?: CellsHttpOptions,\n): Promise<RemoteRuntime & { close(): Promise<void> }> {\n const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });\n const remote = await remoteRuntime((req) => link.send(req), opts);\n return Object.assign(remote, { close: () => link.close() });\n}\n\n/** Serves any HTTP handler over a port (responder side of a link). */\nexport function serveHandlerOverPort(port: CellPort, handler: Send): Promise<() => Promise<void>> {\n return serveHttpLink(asPort(port), handler, { maxPorts: maxPorts() });\n}\n\n/** A `send` over a port (initiator side of a link); the scheme rides in a header. */\nexport async function linkOverPort(\n port: CellPort,\n): Promise<{ send: Send; close(): Promise<void> }> {\n const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });\n return {\n send: (req) => link.send(withScheme(req)),\n close: () => link.close(),\n };\n}\n"]}
@@ -3,23 +3,38 @@ export type WebSocketConnector = (url: string, protocols: string[]) => Promise<{
3
3
  port: MessagePort;
4
4
  protocol?: string;
5
5
  }>;
6
+ type IntervalTimers = {
7
+ setInterval(fn: () => void, ms: number): unknown;
8
+ clearInterval(id: unknown): void;
9
+ };
6
10
  export interface CellsWebSocketOptions {
7
- /** Liveness ping period (ms); 0 disables. The peer is declared dead after 2 missed periods. */
11
+ /**
12
+ * Liveness ping period (ms, default 10 s); 0 disables the heartbeat. The peer is declared dead
13
+ * after 2 periods without a pong.
14
+ */
8
15
  heartbeatMs?: number;
9
16
  /**
10
- * Interval timers for the heartbeat. On a page's main thread pass worker-timers'
11
- * `setInterval`/`clearInterval`: background-tab throttling of main-thread timers would
17
+ * Timers for the heartbeat (and, when given, the close handshake's timeout). On a page's main
18
+ * thread pass worker-timers' functions: background-tab throttling of main-thread timers would
12
19
  * otherwise delay pings and make the server drop a live socket. Workers and Node do not
13
20
  * throttle, so the built-in timers are the default.
14
21
  */
15
- timers?: {
16
- setInterval(fn: () => void, ms: number): unknown;
17
- clearInterval(id: unknown): void;
22
+ timers?: IntervalTimers & {
23
+ setTimeout?(fn: () => void, ms: number): unknown;
24
+ clearTimeout?(id: unknown): void;
18
25
  };
26
+ /** How long `close()` waits for the server's echo before closing with 1006 (default 5 s). */
27
+ closeTimeoutMs?: number;
19
28
  }
20
29
  /**
21
- * A WHATWG `WebSocket` class whose sockets reach a Worker through `connect`. Typed as the
22
- * platform's `WebSocket` (the class implements that interface; its own state is private).
30
+ * A WHATWG `WebSocket` class whose sockets reach a Worker through `connect` (page →
31
+ * ServiceWorker → the shard worker that owns the object, or in-process). It is
32
+ * `@statewalker/webrun-http-browser`'s client (`createWebSocketClass`): the socket's frames on its
33
+ * MessagePort are that package's (version `WS_PORT_PROTOCOL_VERSION`), which the server ends of
34
+ * cellb's WebSocketPairs speak. Installed for user code through webrun-modules `~deps` (provided
35
+ * `WebSocket`), so the real `globalThis.WebSocket` is left untouched. Typed as the platform's
36
+ * `WebSocket` (the class implements that interface).
23
37
  */
24
38
  export declare function createWebSocketClass(connect: WebSocketConnector, options?: CellsWebSocketOptions): typeof WebSocket;
25
39
  export type CellsWebSocketClass = ReturnType<typeof createWebSocketClass>;
40
+ export {};
package/dist/ws/client.js CHANGED
@@ -1,203 +1,64 @@
1
- import { byteSize, CellsCloseEvent, checkClose, DEFAULT_HEARTBEAT_MS, toFrameData, } from "./frames.js";
1
+ import { createWebSocketClass as createPortWebSocketClass } from "@statewalker/webrun-http-browser/sw";
2
+ /** The longest period a timer takes (2³¹−1 ms, about 24.8 days): `heartbeatMs: 0` pings that rarely. */
3
+ const LONGEST_PERIOD_MS = 2 ** 31 - 1;
2
4
  /**
3
- * A WHATWG-WebSocket-compatible class whose transport is a MessagePort obtained
4
- * from `connect` (page → ServiceWorker → owning shard worker, or in-process).
5
- * Installed for user code through webrun-modules `~deps` (provided `WebSocket`),
6
- * so the real `globalThis.WebSocket` is left untouched.
7
- */
8
- /** RFC 7230 token (subprotocol names). */
9
- const TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
10
- /**
11
- * A WHATWG `WebSocket` class whose sockets reach a Worker through `connect`. Typed as the
12
- * platform's `WebSocket` (the class implements that interface; its own state is private).
5
+ * A WHATWG `WebSocket` class whose sockets reach a Worker through `connect` (page →
6
+ * ServiceWorker → the shard worker that owns the object, or in-process). It is
7
+ * `@statewalker/webrun-http-browser`'s client (`createWebSocketClass`): the socket's frames on its
8
+ * MessagePort are that package's (version `WS_PORT_PROTOCOL_VERSION`), which the server ends of
9
+ * cellb's WebSocketPairs speak. Installed for user code through webrun-modules `~deps` (provided
10
+ * `WebSocket`), so the real `globalThis.WebSocket` is left untouched. Typed as the platform's
11
+ * `WebSocket` (the class implements that interface).
13
12
  */
14
13
  export function createWebSocketClass(connect, options = {}) {
15
- const heartbeatMs = options.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
16
- const timers = options.timers ?? {
17
- setInterval: (fn, ms) => setInterval(fn, ms),
18
- clearInterval: (id) => clearInterval(id),
19
- };
20
- class CellsWebSocket extends EventTarget {
21
- static CONNECTING = 0;
22
- static OPEN = 1;
23
- static CLOSING = 2;
24
- static CLOSED = 3;
25
- CONNECTING = 0;
26
- OPEN = 1;
27
- CLOSING = 2;
28
- CLOSED = 3;
29
- url;
30
- readyState = 0;
31
- protocol = "";
32
- extensions = "";
33
- #buffered = 0;
34
- /** Sizes of the messages posted and not acknowledged yet, in order (the port keeps it). */
35
- #unacked = [];
36
- /** Bytes sent and not yet received by the server end (it acknowledges each message). */
37
- get bufferedAmount() {
38
- return this.#buffered;
39
- }
40
- #binaryType = "blob";
41
- get binaryType() {
42
- return this.#binaryType;
43
- }
44
- /** Only "blob" and "arraybuffer" are taken; any other value is ignored (WHATWG). */
45
- set binaryType(v) {
46
- if (v === "blob" || v === "arraybuffer")
47
- this.#binaryType = v;
48
- }
49
- onopen = null;
50
- onmessage = null;
51
- onclose = null;
52
- onerror = null;
53
- #port;
54
- #pendingClose;
55
- #beat;
56
- #lastPong = 0;
57
- #startHeartbeat() {
58
- if (!heartbeatMs)
59
- return;
60
- this.#lastPong = Date.now();
61
- this.#beat = timers.setInterval(() => {
62
- if (this.readyState >= 3)
63
- return this.#stopHeartbeat();
64
- if (Date.now() - this.#lastPong > 2 * heartbeatMs)
65
- return this.#fail(); // peer gone
66
- this.#port?.postMessage({ t: "p", hb: heartbeatMs });
67
- }, heartbeatMs);
68
- this.#port?.postMessage({ t: "p", hb: heartbeatMs });
69
- }
70
- #stopHeartbeat() {
71
- if (this.#beat !== undefined)
72
- timers.clearInterval(this.#beat);
73
- this.#beat = undefined;
74
- }
14
+ const { timers } = options;
15
+ const setTimer = timers?.setTimeout?.bind(timers) ?? ((fn, ms) => setTimeout(fn, ms));
16
+ const clearTimer = timers?.clearTimeout?.bind(timers) ??
17
+ ((id) => clearTimeout(id));
18
+ const Base = createPortWebSocketClass(async (url, protocols) => {
19
+ const { port, protocol } = await connect(url, protocols);
20
+ return { port, protocol: protocol ?? "" };
21
+ }, {
22
+ heartbeatMs: options.heartbeatMs === 0 ? LONGEST_PERIOD_MS : options.heartbeatMs,
23
+ closeTimeoutMs: options.closeTimeoutMs,
24
+ timers: timers && {
25
+ setInterval: (fn, ms) => timers.setInterval(fn, ms),
26
+ clearInterval: (id) => timers.clearInterval(id),
27
+ setTimeout: setTimer,
28
+ clearTimeout: clearTimer,
29
+ },
30
+ });
31
+ // Two of cellb's own client's rules the package's does not apply.
32
+ class CellsWebSocket extends Base {
75
33
  constructor(url, protocols = []) {
76
- super();
77
- // WHATWG "new WebSocket(url, protocols)" validation, in spec order.
78
- let u;
79
- try {
80
- u = new URL(String(url), globalThis.location?.href ?? "http://localhost/");
81
- }
82
- catch {
83
- throw new DOMException(`The URL '${String(url)}' is invalid.`, "SyntaxError");
84
- }
85
- if (u.protocol === "http:")
86
- u.protocol = "ws:";
87
- if (u.protocol === "https:")
88
- u.protocol = "wss:";
89
- if (u.protocol !== "ws:" && u.protocol !== "wss:")
90
- throw new DOMException(`The URL's scheme must be either 'ws' or 'wss'.`, "SyntaxError");
91
- if (u.hash || String(url).includes("#"))
92
- throw new DOMException("The URL contains a fragment identifier.", "SyntaxError");
93
34
  const list = typeof protocols === "string" ? [protocols] : [...protocols];
35
+ // Subprotocols are duplicates case-insensitively (WHATWG; WPT
36
+ // Create-protocols-repeated-case-insensitive).
94
37
  const seen = new Set();
95
38
  for (const p of list) {
96
- if (!TOKEN.test(p))
97
- throw new DOMException(`The subprotocol '${p}' is invalid.`, "SyntaxError");
98
- if (seen.has(p.toLowerCase()))
39
+ const key = p.toLowerCase();
40
+ if (seen.has(key))
99
41
  throw new DOMException(`The subprotocol '${p}' is duplicated.`, "SyntaxError");
100
- seen.add(p.toLowerCase());
42
+ seen.add(key);
101
43
  }
102
- this.url = u.toString();
103
- connect(this.url, list).then(({ port, protocol }) => this.#opened(port, protocol ?? ""), () => this.#fail());
104
- }
105
- #emit(ev, handler) {
106
- handler?.call(this, ev);
107
- this.dispatchEvent(ev);
108
- }
109
- #opened(port, protocol) {
110
- if (this.readyState === 3) {
111
- port.close();
112
- return;
113
- }
114
- this.#port = port;
115
- this.protocol = protocol;
116
- port.onmessage = (e) => this.#frame(e.data);
117
- port.start?.();
118
- if (this.#pendingClose) {
119
- // close() was called while connecting
120
- this.readyState = 2;
121
- port.postMessage({
122
- t: "c",
123
- code: this.#pendingClose.code ?? 1000,
124
- reason: this.#pendingClose.reason ?? "",
125
- });
126
- return;
127
- }
128
- this.readyState = 1;
129
- this.#startHeartbeat();
130
- this.#emit(new Event("open"), this.onopen);
131
- }
132
- #fail() {
133
- if (this.readyState === 3)
134
- return;
135
- this.#stopHeartbeat();
136
- this.#port?.close();
137
- this.readyState = 3;
138
- this.#emit(new Event("error"), this.onerror);
139
- this.#emit(new CellsCloseEvent("close", { code: 1006, reason: "", wasClean: false }), this.onclose);
140
- }
141
- #frame(f) {
142
- if (f.t === "q") {
143
- this.#lastPong = Date.now();
144
- return;
145
- }
146
- if (f.t === "p")
147
- return;
148
- if (f.t === "a") {
149
- this.#buffered -= this.#unacked.shift() ?? 0;
150
- return;
151
- }
152
- if (f.t === "m") {
153
- if (this.readyState !== 1)
154
- return;
155
- let data = f.d;
156
- if (typeof data !== "string" && this.binaryType === "blob")
157
- data = new Blob([data]);
158
- this.#emit(new MessageEvent("message", { data, origin: new URL(this.url).origin }), this.onmessage);
159
- }
160
- else {
161
- const echo = this.readyState === 1;
162
- if (echo)
163
- this.#port.postMessage({ t: "c", code: f.code, reason: f.reason }); // close handshake
164
- this.readyState = 3;
165
- this.#stopHeartbeat();
166
- this.#port.close();
167
- this.#emit(new CellsCloseEvent("close", { code: f.code, reason: f.reason, wasClean: true }), this.onclose);
168
- }
169
- }
170
- send(data) {
171
- if (this.readyState === 0)
172
- throw new DOMException("Still in CONNECTING state.", "InvalidStateError");
173
- if (this.readyState !== 1)
174
- return; // spec: silently discarded after closing
175
- if (data instanceof Blob) {
176
- this.#buffered += data.size;
177
- void data.arrayBuffer().then((b) => this.readyState === 1 && this.#post(b));
178
- return;
179
- }
180
- const d = toFrameData(data);
181
- this.#buffered += byteSize(d);
182
- this.#post(d);
183
- }
184
- #post(d) {
185
- this.#unacked.push(byteSize(d));
186
- this.#port.postMessage({ t: "m", d }, typeof d === "string" ? [] : [d]);
187
- }
188
- close(code, reason) {
189
- checkClose(code, reason);
190
- if (this.readyState >= 2)
191
- return;
192
- if (this.readyState === 0) {
193
- this.#pendingClose = { code, reason };
194
- this.readyState = 2;
195
- return;
196
- }
197
- this.readyState = 2;
198
- this.#port.postMessage({ t: "c", code: code ?? 1005, reason: reason ?? "" });
44
+ super(resolved(url), list);
199
45
  }
200
46
  }
201
47
  return CellsWebSocket;
202
48
  }
49
+ /**
50
+ * A relative url resolved against the page, or, where there is none (Node, a worker without
51
+ * `location`), against `http://localhost/`, as cellb's client always did. An url that does not
52
+ * parse is passed on, for the class to refuse with its SyntaxError.
53
+ */
54
+ function resolved(url) {
55
+ if (globalThis.location)
56
+ return url;
57
+ try {
58
+ return new URL(String(url), "http://localhost/").href;
59
+ }
60
+ catch {
61
+ return url;
62
+ }
63
+ }
203
64
  //# sourceMappingURL=client.js.map