@statewalker/cellb 0.2.0 → 0.4.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 (41) hide show
  1. package/README.md +7 -5
  2. package/dist/bucket/celld-cells.js +25 -3
  3. package/dist/bucket/celld-cells.js.map +1 -1
  4. package/dist/isolate/host.d.ts +1 -1
  5. package/dist/isolate/host.js +17 -6
  6. package/dist/isolate/host.js.map +1 -1
  7. package/dist/isolate/isolate.js +8 -0
  8. package/dist/isolate/isolate.js.map +1 -1
  9. package/dist/isolate/protocol.d.ts +3 -2
  10. package/dist/isolate/protocol.js +47 -51
  11. package/dist/isolate/protocol.js.map +1 -1
  12. package/dist/peer/celld-peer.d.ts +17 -0
  13. package/dist/peer/celld-peer.js +49 -7
  14. package/dist/peer/celld-peer.js.map +1 -1
  15. package/dist/project/browser-shards.d.ts +8 -9
  16. package/dist/project/browser-shards.js +88 -52
  17. package/dist/project/browser-shards.js.map +1 -1
  18. package/dist/project/browser-tabs.d.ts +14 -4
  19. package/dist/project/browser-tabs.js +193 -208
  20. package/dist/project/browser-tabs.js.map +1 -1
  21. package/dist/project/wrangler.js +1 -1
  22. package/dist/project/wrangler.js.map +1 -1
  23. package/dist/transport/cells-http.d.ts +6 -0
  24. package/dist/transport/cells-http.js +22 -1
  25. package/dist/transport/cells-http.js.map +1 -1
  26. package/dist/transport/cells-link.d.ts +2 -0
  27. package/dist/transport/cells-link.js +5 -0
  28. package/dist/transport/cells-link.js.map +1 -1
  29. package/dist/transport/port.d.ts +3 -4
  30. package/dist/transport/port.js +25 -79
  31. package/dist/transport/port.js.map +1 -1
  32. package/dist/ws/client.d.ts +23 -8
  33. package/dist/ws/client.js +50 -189
  34. package/dist/ws/client.js.map +1 -1
  35. package/dist/ws/frames.d.ts +17 -8
  36. package/dist/ws/frames.js +3 -7
  37. package/dist/ws/frames.js.map +1 -1
  38. package/dist/ws/server.d.ts +17 -19
  39. package/dist/ws/server.js +53 -121
  40. package/dist/ws/server.js.map +1 -1
  41. package/package.json +5 -6
package/README.md CHANGED
@@ -226,10 +226,12 @@ docs/ topic pages
226
226
  - **The gates use no `AsyncLocalStorage`**, which browsers lack.
227
227
  - **A browser origin has a fixed number of shards**, because an OPFS pool is exclusive per worker
228
228
  ([docs/browser-host.md](docs/browser-host.md)).
229
- - **WebSockets travel over MessagePorts**, because a ServiceWorker cannot accept them. A port
230
- has no send buffer to measure, so the server end acknowledges each message it dispatched and
231
- a client's `bufferedAmount` counts the bytes not acknowledged yet: the WHATWG signal a sender
232
- throttles on. workerd's server-side WebSocket has no `bufferedAmount`, and cellb's has none.
229
+ - **WebSockets travel over MessagePorts**, because a ServiceWorker cannot accept them. The client
230
+ (`createWebSocketClass`) and the frames on the port are `webrun-http-browser`'s; the ends of a
231
+ `WebSocketPair` are cellb's, with workerd's semantics. A port has no send buffer to measure, so
232
+ the server end acknowledges the bytes of each message it dispatched and a client's
233
+ `bufferedAmount` counts the bytes not acknowledged yet: the WHATWG signal a sender throttles on.
234
+ workerd's server-side WebSocket has no `bufferedAmount`, and cellb's has none.
233
235
  - **Each loaded Worker gets a realm of its own**, a thread or a Dedicated Worker: as in celld's
234
236
  isolates, loaded code must not see the host's globals or env.
235
237
  - **`idFromName` is synchronous**, with `@noble/hashes` instead of `crypto.subtle`, which is
@@ -257,7 +259,7 @@ Queues, Workflows, …) in [docs/services/](docs/services/). What is isolated, a
257
259
  - `@statewalker/webrun-rpc`, `webrun-http-streams`, `webrun-streams` — HTTP with streaming bodies
258
260
  over MessagePorts.
259
261
  - `@statewalker/webrun-files` — the FilesApi for logs, backups and KV blobs;
260
- `webrun-http-browser` — ServiceWorker ingress.
262
+ `webrun-http-browser` — the HTTP links over MessagePorts, the WebSocket client and its frames.
261
263
  - Optional peers, for `@statewalker/cellb/site` only: `webrun-modules` (compiles a project's
262
264
  sources in the browser; it brings lightningcss-wasm and @jspm/core, about 30 MB),
263
265
  `webrun-site-builder` (the in-browser HTTP server), and `webrun-site-host`, which the
@@ -17,6 +17,9 @@ export function celldBucket(store, opts) {
17
17
  let renew;
18
18
  let deadline;
19
19
  let leaseWritten;
20
+ /** The lease write on its way to the bucket, if any: `close()` waits for it before the delete. */
21
+ let writing = Promise.resolve();
22
+ let closed = false;
20
23
  /** nodes/<node>.json, renewed every ttl/3; a failed renewal stops every cell (self-fencing). */
21
24
  // celld decodes every node record (ownership_store.rs NodeLeaseWire / NodeLoadWire): a record
22
25
  // missing a required load field breaks its fleet posture repair. This one is complete. It
@@ -47,7 +50,15 @@ export function celldBucket(store, opts) {
47
50
  }
48
51
  })();
49
52
  let validUntil = 0;
50
- const writeLease = async () => {
53
+ const writeLease = () => {
54
+ const w = putLease();
55
+ // Every write in flight, not only the latest: they may land in any order.
56
+ writing = Promise.all([writing, w.catch(() => { })]);
57
+ return w;
58
+ };
59
+ const putLease = async () => {
60
+ if (closed)
61
+ return;
51
62
  const t = now();
52
63
  const body = {
53
64
  node: opts.node,
@@ -81,7 +92,8 @@ export function celldBucket(store, opts) {
81
92
  // Our authority lasts until the record we just wrote expires — minus a margin, so a node
82
93
  // that stalls (a paused process, a frozen tab) stops acknowledging before celld may take
83
94
  // over: celld waits for expires_ms; we stop at expires_ms - margin.
84
- validUntil = t + ttl - Math.min(1_000, ttl / 4);
95
+ if (!closed)
96
+ validUntil = t + ttl - Math.min(1_000, ttl / 4);
85
97
  };
86
98
  const owned = new Set();
87
99
  /** Cells whose ownership record was found lost, with the time their refusal ends. */
@@ -89,6 +101,8 @@ export function celldBucket(store, opts) {
89
101
  const ensureLease = () => {
90
102
  leaseWritten ??= probeKeyReady.then(writeLease).then(() => {
91
103
  const tick = () => {
104
+ if (closed)
105
+ return;
92
106
  renew = setTimeout(() => {
93
107
  writeLease().then(tick, (e) => lost.abort(e));
94
108
  }, ttl / 3);
@@ -113,7 +127,7 @@ export function celldBucket(store, opts) {
113
127
  };
114
128
  // One LTX writer (ltx-log.ts) holds each cell's epoch, TXID and page mirror.
115
129
  // A write is acknowledged only while our node lease is still ours (self-fencing).
116
- const log = ltxLog(store, { now, authorized: () => now() < validUntil });
130
+ const log = ltxLog(store, { now, authorized: () => !closed && now() < validUntil });
117
131
  const ownership = {
118
132
  async tryAcquire(cellKey) {
119
133
  await ensureLease();
@@ -260,10 +274,18 @@ export function celldBucket(store, opts) {
260
274
  },
261
275
  /** Clean shutdown: stop renewing, remove this node's lease record, close the store. */
262
276
  async close() {
277
+ closed = true;
263
278
  if (renew)
264
279
  clearTimeout(renew);
265
280
  if (deadline)
266
281
  clearTimeout(deadline);
282
+ validUntil = 0; // no write is acknowledged once closing has begun
283
+ // A renewal already on its way would write the record back after the delete, and the
284
+ // node's cells would stay "owned by a live node" for a whole lease: let it land first —
285
+ // for at most a lease, since one that lands later expires within a lease anyway.
286
+ let waited;
287
+ await Promise.race([writing, new Promise((r) => (waited = setTimeout(r, ttl)))]);
288
+ clearTimeout(waited);
267
289
  if (leaseWritten)
268
290
  await store.delete(`nodes/${opts.node}.json`).catch(() => { });
269
291
  store.close?.();
@@ -1 +1 @@
1
- {"version":3,"file":"celld-cells.js","sourceRoot":"","sources":["../../src/bucket/celld-cells.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from \"@noble/hashes/utils.js\";\nimport { scopeOf } from \"../core/celld-schema.js\";\nimport { decodeLtx, encodeLtx, ltxFileName } from \"../ltx/ltx.js\";\nimport type { BackupInfo, CellBackup, CellDurability } from \"../ports.js\";\nimport { equal, pad16, pageSizeOf, toRollback } from \"./celld-format.js\";\nimport { ltxLog } from \"./ltx-log.js\";\nimport { type ObjectStore, PreconditionFailedError } from \"./object-store.js\";\n\n/**\n * Cells stored the way celld stores them (docs/guarantees.md, and the keys celld dev writes\n * in `.celld/dev/objects.sqlite3`), on any ObjectStore:\n *\n * cells/<Class>:<idHex>/own.json {\"node\",\"epoch\"}: ownership record\n * cells/<Class>:<idHex>/ltx/e<epoch>/0000/<min>-<max>.ltx LTX v3, legacy LZ4 frames\n * nodes/<node>.json {\"node\",\"expires_ms\",\"addr\",…}: node lease\n *\n * Ownership: an activation creates the record (If-None-Match) or advances its epoch with a\n * compare-and-swap (If-Match), taking over only from a node whose lease expired. Durability:\n * each committed transaction becomes an LTX file in the cell's current epoch prefix — the\n * first one a snapshot (TXID 1), then the changed pages — and the output gate waits for the\n * PUT. Restore: the newest epoch that starts with a snapshot, its files in TXID order.\n */\nexport interface CelldBucketOptions {\n /** This node's name (celld: `dev-…` / the fleet node name). */\n node: string;\n addr?: string;\n leaseTtlMs?: number;\n now?: () => number;\n /** The waker-role lease (celld: 3 × its due-scan tick); match 3 × the runtime's wakePollMs. */\n wakerTtlMs?: number;\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n/** celld 0.6.3: how long a node refuses a cell whose ownership record it found lost. */\nexport const LOST_RECORD_RECHECK_MS = 10_000;\nconst lostRecordError = (scope: string) =>\n Object.assign(\n new Error(\n `ownership record lost: cells/${scope}/own.json is missing, but the bucket holds LTX under ` +\n `cells/${scope}/ltx/; refusing to claim ${scope} as a new cell (celld docs/guarantees.md: ` +\n `write a released record {\"node\": \"\", \"epoch\": <newest epoch>} to restore it)`,\n ),\n { name: \"OwnershipRecordLostError\" },\n );\n\n/** The bucket's `close()` also closes `store` (when it has a `close`): pass a store of its own. */\nexport function celldBucket(store: ObjectStore & { close?(): void }, opts: CelldBucketOptions) {\n const now = opts.now ?? (() => Date.now());\n const ttl = opts.leaseTtlMs ?? 10_000;\n const lost = new AbortController();\n let renew: ReturnType<typeof setTimeout> | undefined;\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let leaseWritten: Promise<void> | undefined;\n\n /** nodes/<node>.json, renewed every ttl/3; a failed renewal stops every cell (self-fencing). */\n // celld decodes every node record (ownership_store.rs NodeLeaseWire / NodeLoadWire): a record\n // missing a required load field breaks its fleet posture repair. This one is complete. It\n // reports pressure and zero placement weight, so celld never offers this node a cell — it\n // takes cells only when a request arrives.\n //\n // The address must not be empty: celld treats a live owner WITHOUT an address as dead and\n // takes its cells over (logic/lib.rs apply_node_lease_result) — a split brain. With an\n // address celld routes a request for our cell to it; a node that does not accept celld's\n // peer protocol (a browser) gets an unroutable placeholder, so the request fails instead.\n let addr = opts.addr || `${opts.node}.cellb.invalid:9`;\n // celld's per-process generation is its signed-probe public key (Ed25519, peer_probe.rs):\n // `probe_public_key` = `ownership_index_generation`. A real key lets `celld diagnose --peer`\n // verify that a probe reached this node.\n let generation = bytesToHex(crypto.getRandomValues(new Uint8Array(32)));\n let probeKey: CryptoKey | undefined;\n const probeKeyReady = (async () => {\n try {\n const pair = (await crypto.subtle.generateKey({ name: \"Ed25519\" }, true, [\n \"sign\",\n \"verify\",\n ])) as CryptoKeyPair;\n probeKey = pair.privateKey;\n generation = bytesToHex(new Uint8Array(await crypto.subtle.exportKey(\"raw\", pair.publicKey)));\n } catch {\n // no Ed25519 in this WebCrypto: the record keeps a random generation (probes then fail)\n }\n })();\n let validUntil = 0;\n const writeLease = async () => {\n const t = now();\n const body = {\n node: opts.node,\n expires_ms: t + ttl,\n addr,\n probe_public_key: generation,\n peer_protocol: 5,\n paced_handoff: false,\n ownership_index_generation: generation,\n load: {\n sampled_ms: t,\n owned_cells: owned.size,\n placement_weight: 0,\n bucket_format: 2,\n resident_cells: owned.size,\n host_websockets: 0,\n rss_bytes: 0,\n in_use_bytes: 0,\n cpu_percent_x100: 0,\n open_fds: 0,\n fd_limit: 0,\n pressured: true,\n memory_headroom: false,\n shed_cells: 0,\n restoring: 0,\n rebalance_paused: false,\n draining: false,\n },\n };\n await store.put(`nodes/${opts.node}.json`, enc.encode(JSON.stringify(body)));\n // Our authority lasts until the record we just wrote expires — minus a margin, so a node\n // that stalls (a paused process, a frozen tab) stops acknowledging before celld may take\n // over: celld waits for expires_ms; we stop at expires_ms - margin.\n validUntil = t + ttl - Math.min(1_000, ttl / 4);\n };\n const owned = new Set<string>();\n /** Cells whose ownership record was found lost, with the time their refusal ends. */\n const lostRecords = new Map<string, number>();\n const ensureLease = () => {\n leaseWritten ??= probeKeyReady.then(writeLease).then(() => {\n const tick = () => {\n renew = setTimeout(() => {\n writeLease().then(tick, (e) => lost.abort(e));\n }, ttl / 3);\n // If renewals stop arriving (a stalled process, an unreachable bucket), every cell\n // stops at the local deadline — before celld may take it over.\n if (deadline) clearTimeout(deadline);\n deadline = setTimeout(\n () => {\n if (now() >= validUntil)\n lost.abort(new Error(`node ${opts.node}: lease deadline passed`));\n },\n Math.max(0, validUntil - now()),\n );\n (deadline as { unref?: () => void }).unref?.();\n (renew as { unref?: () => void }).unref?.();\n };\n tick();\n });\n return leaseWritten;\n };\n\n const readJson = async <T>(key: string) => {\n const o = await store.get(key);\n return o ? { value: JSON.parse(dec.decode(o.body)) as T, etag: o.etag } : undefined;\n };\n\n // One LTX writer (ltx-log.ts) holds each cell's epoch, TXID and page mirror.\n // A write is acknowledged only while our node lease is still ours (self-fencing).\n const log = ltxLog(store, { now, authorized: () => now() < validUntil });\n\n const ownership = {\n async tryAcquire(cellKey: string) {\n await ensureLease();\n const scope = scopeOf(cellKey);\n const key = `cells/${scope}/own.json`;\n const t0 = now();\n if ((lostRecords.get(scope) ?? 0) > t0) throw lostRecordError(scope); // still refused\n let cur = await readJson<{ node: string; epoch: number }>(key);\n // celld 0.6.3 (actor.rs read_owner_for_claim): a missing record next to LTX means the\n // record was LOST (e.g. a lifecycle rule) — claiming epoch 1 would start the cell empty.\n // Read once more, then refuse the cell for 10 s without asking the bucket again.\n if (!cur && (await store.list(`cells/${scope}/ltx`)).length > 0) {\n cur = await readJson<{ node: string; epoch: number }>(key);\n if (!cur) {\n lostRecords.set(scope, t0 + LOST_RECORD_RECHECK_MS);\n throw lostRecordError(scope);\n }\n }\n let epoch: number;\n try {\n if (!cur) {\n epoch = 1;\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifNoneMatch: \"*\",\n });\n } else {\n const holder = cur.value.node;\n if (holder && holder !== opts.node) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${holder}.json`);\n if (lease && lease.value.expires_ms > now()) return undefined; // a live owner\n }\n epoch = cur.value.epoch + 1; // every activation advances the epoch\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifMatch: cur.etag,\n });\n }\n } catch (e) {\n if (e instanceof PreconditionFailedError) return undefined; // another node won the race\n throw e;\n }\n log.begin(cellKey, epoch); // a new owner epoch starts a new generation\n owned.add(cellKey);\n return {\n name: cellKey,\n epoch,\n lost: lost.signal,\n release: async () => {\n const now2 = await readJson<{ node: string; epoch: number }>(key);\n if (now2?.value.node === opts.node && now2.value.epoch === epoch)\n await store\n .put(key, enc.encode(JSON.stringify({ node: \"\", epoch })), { ifMatch: now2.etag })\n .catch(() => {});\n log.forget(cellKey);\n owned.delete(cellKey);\n },\n };\n },\n };\n\n // celld's waker role (wake.rs try_hold_waker): one node per fleet wakes alarms of cells that\n // nobody owns or whose owner is dead — an advisory CAS lease in wake/waker.json; every node\n // wakes its own cells; a live owner's cells are never hinted.\n const holdWaker = async (ttlMs: number) => {\n const key = \"wake/waker.json\";\n const cur = await readJson<{ node: string; expires_ms: number }>(key);\n const t = now();\n if (cur && cur.value.node !== opts.node && cur.value.expires_ms > t) return false;\n try {\n await store.put(\n key,\n enc.encode(JSON.stringify({ node: opts.node, expires_ms: t + ttlMs })),\n cur ? { ifMatch: cur.etag } : { ifNoneMatch: \"*\" },\n );\n return true;\n } catch (e) {\n if (!(e instanceof PreconditionFailedError)) return false;\n const again = await readJson<{ node: string; expires_ms: number }>(key);\n return again?.value.node === opts.node && again.value.expires_ms > t;\n }\n };\n const allWakes = log.wakes!.bind(log);\n const durability = Object.assign(log, {\n async wakes() {\n const due = await allWakes();\n let elected: boolean | undefined;\n const out: typeof due = [];\n for (const w of due) {\n const own = await readJson<{ node: string }>(`cells/${scopeOf(w.cellKey)}/own.json`);\n const owner = own?.value.node;\n if (owner === opts.node) {\n out.push(w); // our own cell: always\n continue;\n }\n let ownerExpires = 0;\n if (owner) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${owner}.json`);\n ownerExpires = lease?.value.expires_ms ?? 0;\n }\n elected ??= await holdWaker(opts.wakerTtlMs ?? 90_000);\n if (!elected) continue; // unowned or dead owners are the elected waker's job\n // A live owner fires its own alarms; the waker looks again when that owner's lease\n // would have run out (a stopped owner's cells are then due here).\n out.push(ownerExpires > now() ? { ...w, at: Math.max(w.at, ownerExpires + 1) } : w);\n }\n return out;\n },\n });\n\n return {\n ownership,\n durability,\n backup: durability,\n /** The fleet bucket itself (R2 objects live directly in it, under `r2/<bucket>/`). */\n objects: store,\n /**\n * The address celld nodes reach this node at (its celld peer server): published in the\n * node record now, so celld routes requests for our cells here instead of refusing them.\n */\n /**\n * Answers celld's signed peer probe (peer_probe.rs): an Ed25519 signature over\n * \"cells-peer-probe-v1\\n<node>\\n<advertised addr>\\n<challenge>\" by this node's probe key.\n */\n async probe(challenge: string) {\n await probeKeyReady;\n if (!probeKey || !/^[0-9a-f]{64}$/.test(challenge))\n throw new Error(\"cannot answer the probe\");\n const payload = `cells-peer-probe-v1\\n${opts.node}\\n${addr}\\n${challenge}`;\n const signature = new Uint8Array(\n await crypto.subtle.sign(\"Ed25519\", probeKey, new TextEncoder().encode(payload)),\n );\n return {\n version: 1,\n node: opts.node,\n advertise: addr,\n challenge,\n signature: bytesToHex(signature),\n };\n },\n async advertise(address: string) {\n addr = address;\n if (leaseWritten) await writeLease();\n },\n /** Clean shutdown: stop renewing, remove this node's lease record, close the store. */\n async close() {\n if (renew) clearTimeout(renew);\n if (deadline) clearTimeout(deadline);\n if (leaseWritten) await store.delete(`nodes/${opts.node}.json`).catch(() => {});\n store.close?.();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"celld-cells.js","sourceRoot":"","sources":["../../src/bucket/celld-cells.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from \"@noble/hashes/utils.js\";\nimport { scopeOf } from \"../core/celld-schema.js\";\nimport { decodeLtx, encodeLtx, ltxFileName } from \"../ltx/ltx.js\";\nimport type { BackupInfo, CellBackup, CellDurability } from \"../ports.js\";\nimport { equal, pad16, pageSizeOf, toRollback } from \"./celld-format.js\";\nimport { ltxLog } from \"./ltx-log.js\";\nimport { type ObjectStore, PreconditionFailedError } from \"./object-store.js\";\n\n/**\n * Cells stored the way celld stores them (docs/guarantees.md, and the keys celld dev writes\n * in `.celld/dev/objects.sqlite3`), on any ObjectStore:\n *\n * cells/<Class>:<idHex>/own.json {\"node\",\"epoch\"}: ownership record\n * cells/<Class>:<idHex>/ltx/e<epoch>/0000/<min>-<max>.ltx LTX v3, legacy LZ4 frames\n * nodes/<node>.json {\"node\",\"expires_ms\",\"addr\",…}: node lease\n *\n * Ownership: an activation creates the record (If-None-Match) or advances its epoch with a\n * compare-and-swap (If-Match), taking over only from a node whose lease expired. Durability:\n * each committed transaction becomes an LTX file in the cell's current epoch prefix — the\n * first one a snapshot (TXID 1), then the changed pages — and the output gate waits for the\n * PUT. Restore: the newest epoch that starts with a snapshot, its files in TXID order.\n */\nexport interface CelldBucketOptions {\n /** This node's name (celld: `dev-…` / the fleet node name). */\n node: string;\n addr?: string;\n leaseTtlMs?: number;\n now?: () => number;\n /** The waker-role lease (celld: 3 × its due-scan tick); match 3 × the runtime's wakePollMs. */\n wakerTtlMs?: number;\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n/** celld 0.6.3: how long a node refuses a cell whose ownership record it found lost. */\nexport const LOST_RECORD_RECHECK_MS = 10_000;\nconst lostRecordError = (scope: string) =>\n Object.assign(\n new Error(\n `ownership record lost: cells/${scope}/own.json is missing, but the bucket holds LTX under ` +\n `cells/${scope}/ltx/; refusing to claim ${scope} as a new cell (celld docs/guarantees.md: ` +\n `write a released record {\"node\": \"\", \"epoch\": <newest epoch>} to restore it)`,\n ),\n { name: \"OwnershipRecordLostError\" },\n );\n\n/** The bucket's `close()` also closes `store` (when it has a `close`): pass a store of its own. */\nexport function celldBucket(store: ObjectStore & { close?(): void }, opts: CelldBucketOptions) {\n const now = opts.now ?? (() => Date.now());\n const ttl = opts.leaseTtlMs ?? 10_000;\n const lost = new AbortController();\n let renew: ReturnType<typeof setTimeout> | undefined;\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let leaseWritten: Promise<void> | undefined;\n /** The lease write on its way to the bucket, if any: `close()` waits for it before the delete. */\n let writing: Promise<unknown> = Promise.resolve();\n let closed = false;\n\n /** nodes/<node>.json, renewed every ttl/3; a failed renewal stops every cell (self-fencing). */\n // celld decodes every node record (ownership_store.rs NodeLeaseWire / NodeLoadWire): a record\n // missing a required load field breaks its fleet posture repair. This one is complete. It\n // reports pressure and zero placement weight, so celld never offers this node a cell — it\n // takes cells only when a request arrives.\n //\n // The address must not be empty: celld treats a live owner WITHOUT an address as dead and\n // takes its cells over (logic/lib.rs apply_node_lease_result) — a split brain. With an\n // address celld routes a request for our cell to it; a node that does not accept celld's\n // peer protocol (a browser) gets an unroutable placeholder, so the request fails instead.\n let addr = opts.addr || `${opts.node}.cellb.invalid:9`;\n // celld's per-process generation is its signed-probe public key (Ed25519, peer_probe.rs):\n // `probe_public_key` = `ownership_index_generation`. A real key lets `celld diagnose --peer`\n // verify that a probe reached this node.\n let generation = bytesToHex(crypto.getRandomValues(new Uint8Array(32)));\n let probeKey: CryptoKey | undefined;\n const probeKeyReady = (async () => {\n try {\n const pair = (await crypto.subtle.generateKey({ name: \"Ed25519\" }, true, [\n \"sign\",\n \"verify\",\n ])) as CryptoKeyPair;\n probeKey = pair.privateKey;\n generation = bytesToHex(new Uint8Array(await crypto.subtle.exportKey(\"raw\", pair.publicKey)));\n } catch {\n // no Ed25519 in this WebCrypto: the record keeps a random generation (probes then fail)\n }\n })();\n let validUntil = 0;\n const writeLease = () => {\n const w = putLease();\n // Every write in flight, not only the latest: they may land in any order.\n writing = Promise.all([writing, w.catch(() => {})]);\n return w;\n };\n const putLease = async () => {\n if (closed) return;\n const t = now();\n const body = {\n node: opts.node,\n expires_ms: t + ttl,\n addr,\n probe_public_key: generation,\n peer_protocol: 5,\n paced_handoff: false,\n ownership_index_generation: generation,\n load: {\n sampled_ms: t,\n owned_cells: owned.size,\n placement_weight: 0,\n bucket_format: 2,\n resident_cells: owned.size,\n host_websockets: 0,\n rss_bytes: 0,\n in_use_bytes: 0,\n cpu_percent_x100: 0,\n open_fds: 0,\n fd_limit: 0,\n pressured: true,\n memory_headroom: false,\n shed_cells: 0,\n restoring: 0,\n rebalance_paused: false,\n draining: false,\n },\n };\n await store.put(`nodes/${opts.node}.json`, enc.encode(JSON.stringify(body)));\n // Our authority lasts until the record we just wrote expires — minus a margin, so a node\n // that stalls (a paused process, a frozen tab) stops acknowledging before celld may take\n // over: celld waits for expires_ms; we stop at expires_ms - margin.\n if (!closed) validUntil = t + ttl - Math.min(1_000, ttl / 4);\n };\n const owned = new Set<string>();\n /** Cells whose ownership record was found lost, with the time their refusal ends. */\n const lostRecords = new Map<string, number>();\n const ensureLease = () => {\n leaseWritten ??= probeKeyReady.then(writeLease).then(() => {\n const tick = () => {\n if (closed) return;\n renew = setTimeout(() => {\n writeLease().then(tick, (e) => lost.abort(e));\n }, ttl / 3);\n // If renewals stop arriving (a stalled process, an unreachable bucket), every cell\n // stops at the local deadline — before celld may take it over.\n if (deadline) clearTimeout(deadline);\n deadline = setTimeout(\n () => {\n if (now() >= validUntil)\n lost.abort(new Error(`node ${opts.node}: lease deadline passed`));\n },\n Math.max(0, validUntil - now()),\n );\n (deadline as { unref?: () => void }).unref?.();\n (renew as { unref?: () => void }).unref?.();\n };\n tick();\n });\n return leaseWritten;\n };\n\n const readJson = async <T>(key: string) => {\n const o = await store.get(key);\n return o ? { value: JSON.parse(dec.decode(o.body)) as T, etag: o.etag } : undefined;\n };\n\n // One LTX writer (ltx-log.ts) holds each cell's epoch, TXID and page mirror.\n // A write is acknowledged only while our node lease is still ours (self-fencing).\n const log = ltxLog(store, { now, authorized: () => !closed && now() < validUntil });\n\n const ownership = {\n async tryAcquire(cellKey: string) {\n await ensureLease();\n const scope = scopeOf(cellKey);\n const key = `cells/${scope}/own.json`;\n const t0 = now();\n if ((lostRecords.get(scope) ?? 0) > t0) throw lostRecordError(scope); // still refused\n let cur = await readJson<{ node: string; epoch: number }>(key);\n // celld 0.6.3 (actor.rs read_owner_for_claim): a missing record next to LTX means the\n // record was LOST (e.g. a lifecycle rule) — claiming epoch 1 would start the cell empty.\n // Read once more, then refuse the cell for 10 s without asking the bucket again.\n if (!cur && (await store.list(`cells/${scope}/ltx`)).length > 0) {\n cur = await readJson<{ node: string; epoch: number }>(key);\n if (!cur) {\n lostRecords.set(scope, t0 + LOST_RECORD_RECHECK_MS);\n throw lostRecordError(scope);\n }\n }\n let epoch: number;\n try {\n if (!cur) {\n epoch = 1;\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifNoneMatch: \"*\",\n });\n } else {\n const holder = cur.value.node;\n if (holder && holder !== opts.node) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${holder}.json`);\n if (lease && lease.value.expires_ms > now()) return undefined; // a live owner\n }\n epoch = cur.value.epoch + 1; // every activation advances the epoch\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifMatch: cur.etag,\n });\n }\n } catch (e) {\n if (e instanceof PreconditionFailedError) return undefined; // another node won the race\n throw e;\n }\n log.begin(cellKey, epoch); // a new owner epoch starts a new generation\n owned.add(cellKey);\n return {\n name: cellKey,\n epoch,\n lost: lost.signal,\n release: async () => {\n const now2 = await readJson<{ node: string; epoch: number }>(key);\n if (now2?.value.node === opts.node && now2.value.epoch === epoch)\n await store\n .put(key, enc.encode(JSON.stringify({ node: \"\", epoch })), { ifMatch: now2.etag })\n .catch(() => {});\n log.forget(cellKey);\n owned.delete(cellKey);\n },\n };\n },\n };\n\n // celld's waker role (wake.rs try_hold_waker): one node per fleet wakes alarms of cells that\n // nobody owns or whose owner is dead — an advisory CAS lease in wake/waker.json; every node\n // wakes its own cells; a live owner's cells are never hinted.\n const holdWaker = async (ttlMs: number) => {\n const key = \"wake/waker.json\";\n const cur = await readJson<{ node: string; expires_ms: number }>(key);\n const t = now();\n if (cur && cur.value.node !== opts.node && cur.value.expires_ms > t) return false;\n try {\n await store.put(\n key,\n enc.encode(JSON.stringify({ node: opts.node, expires_ms: t + ttlMs })),\n cur ? { ifMatch: cur.etag } : { ifNoneMatch: \"*\" },\n );\n return true;\n } catch (e) {\n if (!(e instanceof PreconditionFailedError)) return false;\n const again = await readJson<{ node: string; expires_ms: number }>(key);\n return again?.value.node === opts.node && again.value.expires_ms > t;\n }\n };\n const allWakes = log.wakes!.bind(log);\n const durability = Object.assign(log, {\n async wakes() {\n const due = await allWakes();\n let elected: boolean | undefined;\n const out: typeof due = [];\n for (const w of due) {\n const own = await readJson<{ node: string }>(`cells/${scopeOf(w.cellKey)}/own.json`);\n const owner = own?.value.node;\n if (owner === opts.node) {\n out.push(w); // our own cell: always\n continue;\n }\n let ownerExpires = 0;\n if (owner) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${owner}.json`);\n ownerExpires = lease?.value.expires_ms ?? 0;\n }\n elected ??= await holdWaker(opts.wakerTtlMs ?? 90_000);\n if (!elected) continue; // unowned or dead owners are the elected waker's job\n // A live owner fires its own alarms; the waker looks again when that owner's lease\n // would have run out (a stopped owner's cells are then due here).\n out.push(ownerExpires > now() ? { ...w, at: Math.max(w.at, ownerExpires + 1) } : w);\n }\n return out;\n },\n });\n\n return {\n ownership,\n durability,\n backup: durability,\n /** The fleet bucket itself (R2 objects live directly in it, under `r2/<bucket>/`). */\n objects: store,\n /**\n * The address celld nodes reach this node at (its celld peer server): published in the\n * node record now, so celld routes requests for our cells here instead of refusing them.\n */\n /**\n * Answers celld's signed peer probe (peer_probe.rs): an Ed25519 signature over\n * \"cells-peer-probe-v1\\n<node>\\n<advertised addr>\\n<challenge>\" by this node's probe key.\n */\n async probe(challenge: string) {\n await probeKeyReady;\n if (!probeKey || !/^[0-9a-f]{64}$/.test(challenge))\n throw new Error(\"cannot answer the probe\");\n const payload = `cells-peer-probe-v1\\n${opts.node}\\n${addr}\\n${challenge}`;\n const signature = new Uint8Array(\n await crypto.subtle.sign(\"Ed25519\", probeKey, new TextEncoder().encode(payload)),\n );\n return {\n version: 1,\n node: opts.node,\n advertise: addr,\n challenge,\n signature: bytesToHex(signature),\n };\n },\n async advertise(address: string) {\n addr = address;\n if (leaseWritten) await writeLease();\n },\n /** Clean shutdown: stop renewing, remove this node's lease record, close the store. */\n async close() {\n closed = true;\n if (renew) clearTimeout(renew);\n if (deadline) clearTimeout(deadline);\n validUntil = 0; // no write is acknowledged once closing has begun\n // A renewal already on its way would write the record back after the delete, and the\n // node's cells would stay \"owned by a live node\" for a whole lease: let it land first —\n // for at most a lease, since one that lands later expires within a lease anyway.\n let waited: ReturnType<typeof setTimeout> | undefined;\n await Promise.race([writing, new Promise((r) => (waited = setTimeout(r, ttl)))]);\n clearTimeout(waited);\n if (leaseWritten) await store.delete(`nodes/${opts.node}.json`).catch(() => {});\n store.close?.();\n },\n };\n}\n"]}
@@ -24,5 +24,5 @@ export declare function isolatedModuleLoader(opts: {
24
24
  }): IsolatedModuleLoader;
25
25
  /** A Worker Loader's module loader whose isolates end with `close()`. */
26
26
  export type IsolatedModuleLoader = ModuleLoader & {
27
- close(): void;
27
+ close(): Promise<void>;
28
28
  };
@@ -22,7 +22,7 @@ export function isolatedModuleLoader(opts) {
22
22
  terminate: () => isolate.terminate(),
23
23
  };
24
24
  isolates.add(running);
25
- const { call, fetch, closeSockets } = await hostPort(isolate.port, async (m) => {
25
+ const { call, fetch } = await hostPort(isolate.port, async (m) => {
26
26
  if (m.type === "durable") {
27
27
  const st = opts.storage?.() ?? {};
28
28
  if (m.op === "load")
@@ -44,7 +44,9 @@ export function isolatedModuleLoader(opts) {
44
44
  console[m.level](...m.args);
45
45
  }
46
46
  }, fetcher);
47
- running.closeSockets = closeSockets;
47
+ // Before it ends, the isolate tells its sockets' clients it is going away (1001): their
48
+ // frames go straight to the isolate (protocol.ts), so only it can.
49
+ running.goAway = () => call({ type: "goAway" });
48
50
  const exports = await call({
49
51
  type: "load",
50
52
  ...where,
@@ -79,11 +81,20 @@ export function isolatedModuleLoader(opts) {
79
81
  };
80
82
  return Object.assign(load, {
81
83
  close() {
82
- for (const isolate of isolates) {
83
- isolate.closeSockets?.(); // its sockets' clients hear 1001 before the isolate ends
84
- isolate.terminate();
85
- }
84
+ const ending = [...isolates];
86
85
  isolates.clear();
86
+ // Each isolate closes its sockets (1001), within a second; then it ends.
87
+ return Promise.all(ending.map(async (isolate) => {
88
+ let timer;
89
+ await Promise.race([
90
+ isolate.goAway?.().catch(() => { }),
91
+ new Promise((r) => {
92
+ timer = setTimeout(r, 1_000);
93
+ }),
94
+ ]);
95
+ clearTimeout(timer);
96
+ isolate.terminate();
97
+ })).then(() => { });
87
98
  },
88
99
  });
89
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/isolate/host.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type LogSink,\n type ModuleLoader,\n outboundFetch,\n type WorkerCode,\n} from \"../bindings/worker-loader.js\";\nimport { ISOLATED, type IsolatedClass } from \"../core/handles.js\";\nimport type { CellStorage } from \"../ports.js\";\nimport { hostPort, type PortLike } from \"./protocol.js\";\n\n/**\n * Worker Loader whose code runs in a separate realm per loaded Worker (an isolate). Returns\n * a module facade: each export is an object whose `fetch()` and methods cross the port.\n * Durable Object classes cannot cross (their storage API is synchronous): use an in-realm\n * loader for `getDurableObjectClass` (see `workerLoader(load, { classes })`).\n */\nexport function isolatedModuleLoader(opts: {\n /**\n * The host's storage, so facets of loaded code are logged and restored like the root's\n * own (celld: one stream per facet under the root, the root's epoch).\n */\n storage?: () => CellStorage | undefined;\n spawn(id: string): { port: PortLike; terminate(): void };\n prepare(id: string, code: WorkerCode): Promise<{ source?: string; url?: string }>;\n}): IsolatedModuleLoader {\n // Every isolate this loader started: `close()` ends them (a project runtime's close does).\n const isolates = new Set<{ terminate(): void; closeSockets?(): void }>();\n const load = async (id: string, code: WorkerCode, logs?: LogSink) => {\n // The env crosses as any call does: bindings and other stubs by reference.\n const env: Record<string, unknown> = { ...code.env };\n // The loaded code's global fetch(): blocked in the isolate, or sent back here to the Fetcher.\n const fetcher = outboundFetch(code);\n const outbound = code.globalOutbound === null ? \"blocked\" : fetcher ? \"fetcher\" : undefined;\n const where = await opts.prepare(id, code);\n const isolate = opts.spawn(id);\n const running: { terminate(): void; closeSockets?(): void } = {\n terminate: () => isolate.terminate(),\n };\n isolates.add(running);\n const { call, fetch, closeSockets } = await hostPort(\n isolate.port,\n async (m) => {\n if (m.type === \"durable\") {\n const st = opts.storage?.() ?? {};\n if (m.op === \"load\") return st.backup?.load?.(m.cellKey);\n if (m.op === \"committed\" && st.durability) {\n const info = m.info;\n const token = info.image;\n await st.durability.committed(m.cellKey, {\n ...info,\n image:\n typeof token === \"number\"\n ? () => call<Uint8Array | undefined>({ type: \"image\", token })\n : token,\n });\n }\n return;\n }\n if (m.type === \"log\") {\n logs?.(m.level, m.args);\n (console as any)[m.level](...m.args);\n }\n },\n fetcher,\n );\n running.closeSockets = closeSockets;\n const exports = await call<Record<string, string>>({\n type: \"load\",\n ...where,\n env,\n outbound,\n });\n const facade = (name: string) =>\n new Proxy(\n {},\n {\n get(_t, prop) {\n if (prop === \"then\" || typeof prop === \"symbol\") return undefined;\n // Called as a plain-object entrypoint (entrypointStub): `ctx` comes last, after `env`\n // (the isolate has its own); its props cross with the call.\n if (prop === \"fetch\")\n return (req: Request, _env: unknown, ctx?: { props?: unknown }) =>\n fetch({ name, props: ctx?.props }, req);\n return (...args: unknown[]) => {\n const ctx = args.pop() as { props?: unknown };\n args.pop();\n return call({ type: \"call\", name, method: prop, args, props: ctx.props });\n };\n },\n },\n );\n const module: Record<string | symbol, unknown> = Object.fromEntries(\n Object.keys(exports).map((k) => [k, facade(k)]),\n );\n // Durable Object classes of the loaded code run in the isolate too (facets).\n module[ISOLATED] = (className: string): IsolatedClass => ({\n fetch: (cellKey, req) => fetch({ className, cellKey }, req),\n call: (cellKey, method, args) =>\n call({ type: \"object\", op: \"call\", className, cellKey, method, args }),\n restart: (cellKey) => call({ type: \"object\", op: \"restart\", className, cellKey }),\n delete: (cellKey) => call({ type: \"object\", op: \"delete\", className, cellKey }),\n attach: (cellKey, epoch, idName) =>\n call({ type: \"object\", op: \"attach\", className, cellKey, epoch, idName }),\n });\n return module as Record<string, unknown>;\n };\n return Object.assign(load, {\n close() {\n for (const isolate of isolates) {\n isolate.closeSockets?.(); // its sockets' clients hear 1001 before the isolate ends\n isolate.terminate();\n }\n isolates.clear();\n },\n });\n}\n\n/** A Worker Loader's module loader whose isolates end with `close()`. */\nexport type IsolatedModuleLoader = ModuleLoader & { close(): void };\n"]}
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/isolate/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,aAAa,GAEd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAsB,MAAM,oBAAoB,CAAC;AAElE,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AAExD;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAQpC;IACC,2FAA2F;IAC3F,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsD,CAAC;IAC/E,MAAM,IAAI,GAAG,KAAK,EAAE,EAAU,EAAE,IAAgB,EAAE,IAAc,EAAE,EAAE;QAClE,2EAA2E;QAC3E,MAAM,GAAG,GAA4B,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrD,8FAA8F;QAC9F,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5F,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAuD;YAClE,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE;SACrC,CAAC;QACF,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACtB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,CACpC,OAAO,CAAC,IAAI,EACZ,KAAK,EAAE,CAAC,EAAE,EAAE;YACV,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC;gBAClC,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM;oBAAE,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBACzD,IAAI,CAAC,CAAC,EAAE,KAAK,WAAW,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;oBAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;oBACpB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;oBACzB,MAAM,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE;wBACvC,GAAG,IAAI;wBACP,KAAK,EACH,OAAO,KAAK,KAAK,QAAQ;4BACvB,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAyB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;4BAC9D,CAAC,CAAC,KAAK;qBACZ,CAAC,CAAC;gBACL,CAAC;gBACD,OAAO;YACT,CAAC;YACD,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;gBACrB,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;gBACvB,OAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC;QACH,CAAC,EACD,OAAO,CACR,CAAC;QACF,wFAAwF;QACxF,mEAAmE;QACnE,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAyB;YACjD,IAAI,EAAE,MAAM;YACZ,GAAG,KAAK;YACR,GAAG;YACH,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAC9B,IAAI,KAAK,CACP,EAAE,EACF;YACE,GAAG,CAAC,EAAE,EAAE,IAAI;gBACV,IAAI,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,KAAK,QAAQ;oBAAE,OAAO,SAAS,CAAC;gBAClE,sFAAsF;gBACtF,4DAA4D;gBAC5D,IAAI,IAAI,KAAK,OAAO;oBAClB,OAAO,CAAC,GAAY,EAAE,IAAa,EAAE,GAAyB,EAAE,EAAE,CAChE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,CAAC;gBAC5C,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;oBAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAyB,CAAC;oBAC9C,IAAI,CAAC,GAAG,EAAE,CAAC;oBACX,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC5E,CAAC,CAAC;YACJ,CAAC;SACF,CACF,CAAC;QACJ,MAAM,MAAM,GAAqC,MAAM,CAAC,WAAW,CACjE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAChD,CAAC;QACF,6EAA6E;QAC7E,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAiB,EAAiB,EAAE,CAAC,CAAC;YACxD,KAAK,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,GAAG,CAAC;YAC3D,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAC9B,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACxE,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;YACjF,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;YAC/E,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACjC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;SAC5E,CAAC,CAAC;QACH,OAAO,MAAiC,CAAC;IAC3C,CAAC,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QACzB,KAAK;YACH,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;YAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,yEAAyE;YACzE,OAAO,OAAO,CAAC,GAAG,CAChB,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;gBAC3B,IAAI,KAAgD,CAAC;gBACrD,MAAM,OAAO,CAAC,IAAI,CAAC;oBACjB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;oBAClC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;wBAChB,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;oBAC/B,CAAC,CAAC;iBACH,CAAC,CAAC;gBACH,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,CAAC,CAAC,CACH,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnB,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import {\n type LogSink,\n type ModuleLoader,\n outboundFetch,\n type WorkerCode,\n} from \"../bindings/worker-loader.js\";\nimport { ISOLATED, type IsolatedClass } from \"../core/handles.js\";\nimport type { CellStorage } from \"../ports.js\";\nimport { hostPort, type PortLike } from \"./protocol.js\";\n\n/**\n * Worker Loader whose code runs in a separate realm per loaded Worker (an isolate). Returns\n * a module facade: each export is an object whose `fetch()` and methods cross the port.\n * Durable Object classes cannot cross (their storage API is synchronous): use an in-realm\n * loader for `getDurableObjectClass` (see `workerLoader(load, { classes })`).\n */\nexport function isolatedModuleLoader(opts: {\n /**\n * The host's storage, so facets of loaded code are logged and restored like the root's\n * own (celld: one stream per facet under the root, the root's epoch).\n */\n storage?: () => CellStorage | undefined;\n spawn(id: string): { port: PortLike; terminate(): void };\n prepare(id: string, code: WorkerCode): Promise<{ source?: string; url?: string }>;\n}): IsolatedModuleLoader {\n // Every isolate this loader started: `close()` ends them (a project runtime's close does).\n const isolates = new Set<{ terminate(): void; goAway?(): Promise<unknown> }>();\n const load = async (id: string, code: WorkerCode, logs?: LogSink) => {\n // The env crosses as any call does: bindings and other stubs by reference.\n const env: Record<string, unknown> = { ...code.env };\n // The loaded code's global fetch(): blocked in the isolate, or sent back here to the Fetcher.\n const fetcher = outboundFetch(code);\n const outbound = code.globalOutbound === null ? \"blocked\" : fetcher ? \"fetcher\" : undefined;\n const where = await opts.prepare(id, code);\n const isolate = opts.spawn(id);\n const running: { terminate(): void; goAway?(): Promise<unknown> } = {\n terminate: () => isolate.terminate(),\n };\n isolates.add(running);\n const { call, fetch } = await hostPort(\n isolate.port,\n async (m) => {\n if (m.type === \"durable\") {\n const st = opts.storage?.() ?? {};\n if (m.op === \"load\") return st.backup?.load?.(m.cellKey);\n if (m.op === \"committed\" && st.durability) {\n const info = m.info;\n const token = info.image;\n await st.durability.committed(m.cellKey, {\n ...info,\n image:\n typeof token === \"number\"\n ? () => call<Uint8Array | undefined>({ type: \"image\", token })\n : token,\n });\n }\n return;\n }\n if (m.type === \"log\") {\n logs?.(m.level, m.args);\n (console as any)[m.level](...m.args);\n }\n },\n fetcher,\n );\n // Before it ends, the isolate tells its sockets' clients it is going away (1001): their\n // frames go straight to the isolate (protocol.ts), so only it can.\n running.goAway = () => call({ type: \"goAway\" });\n const exports = await call<Record<string, string>>({\n type: \"load\",\n ...where,\n env,\n outbound,\n });\n const facade = (name: string) =>\n new Proxy(\n {},\n {\n get(_t, prop) {\n if (prop === \"then\" || typeof prop === \"symbol\") return undefined;\n // Called as a plain-object entrypoint (entrypointStub): `ctx` comes last, after `env`\n // (the isolate has its own); its props cross with the call.\n if (prop === \"fetch\")\n return (req: Request, _env: unknown, ctx?: { props?: unknown }) =>\n fetch({ name, props: ctx?.props }, req);\n return (...args: unknown[]) => {\n const ctx = args.pop() as { props?: unknown };\n args.pop();\n return call({ type: \"call\", name, method: prop, args, props: ctx.props });\n };\n },\n },\n );\n const module: Record<string | symbol, unknown> = Object.fromEntries(\n Object.keys(exports).map((k) => [k, facade(k)]),\n );\n // Durable Object classes of the loaded code run in the isolate too (facets).\n module[ISOLATED] = (className: string): IsolatedClass => ({\n fetch: (cellKey, req) => fetch({ className, cellKey }, req),\n call: (cellKey, method, args) =>\n call({ type: \"object\", op: \"call\", className, cellKey, method, args }),\n restart: (cellKey) => call({ type: \"object\", op: \"restart\", className, cellKey }),\n delete: (cellKey) => call({ type: \"object\", op: \"delete\", className, cellKey }),\n attach: (cellKey, epoch, idName) =>\n call({ type: \"object\", op: \"attach\", className, cellKey, epoch, idName }),\n });\n return module as Record<string, unknown>;\n };\n return Object.assign(load, {\n close(): Promise<void> {\n const ending = [...isolates];\n isolates.clear();\n // Each isolate closes its sockets (1001), within a second; then it ends.\n return Promise.all(\n ending.map(async (isolate) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n await Promise.race([\n isolate.goAway?.().catch(() => {}),\n new Promise((r) => {\n timer = setTimeout(r, 1_000);\n }),\n ]);\n clearTimeout(timer);\n isolate.terminate();\n }),\n ).then(() => {});\n },\n });\n}\n\n/** A Worker Loader's module loader whose isolates end with `close()`. */\nexport type IsolatedModuleLoader = ModuleLoader & { close(): Promise<void> };\n"]}
@@ -6,6 +6,7 @@ import { ctxExports, entrypointStub } from "../core/handles.js";
6
6
  import { runInDurableObject } from "../core/namespace.js";
7
7
  import { createCellRuntime } from "../core/runtime.js";
8
8
  import { realmWorkerScope } from "../project/worker-scope.js";
9
+ import { CfWebSocket } from "../ws/server.js";
9
10
  import { isolatePort } from "./protocol.js";
10
11
  export function serveIsolate(port, importCode, opts = {}) {
11
12
  let module = {};
@@ -146,6 +147,13 @@ export function serveIsolate(port, importCode, opts = {}) {
146
147
  }
147
148
  if (m.type === "image")
148
149
  return await images.get(m.token)?.();
150
+ // The host is stopping this isolate: the sockets accepted here hear 1001 first.
151
+ // A socket whose early frames went through a relay port hears it one hop later: the
152
+ // answer waits a macrotask, so the host does not terminate the isolate before that.
153
+ if (m.type === "goAway") {
154
+ CfWebSocket.goAway("the loaded Worker stopped");
155
+ return await new Promise((r) => setTimeout(r, 0));
156
+ }
149
157
  if (m.type === "object") {
150
158
  const { rt, stub, key } = await facet(m.className, m.cellKey);
151
159
  if (m.op === "attach") {
@@ -1 +1 @@
1
- {"version":3,"file":"isolate.js","sourceRoot":"","sources":["../../src/isolate/isolate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The isolate side: loads the Worker code in THIS realm and serves fetch/RPC for the host.\n * `importCode` is realm specific (data: URL of a bundle on Node, a site URL in browsers).\n */\nimport { ctxExports, entrypointStub } from \"../core/handles.js\";\nimport type { DurableObjectId } from \"../core/id.js\";\nimport { runInDurableObject } from \"../core/namespace.js\";\nimport { type CellRuntime, createCellRuntime, type WorkerModule } from \"../core/runtime.js\";\nimport type { Lease, LeaseHandle, OpenCellSql } from \"../ports.js\";\nimport { realmWorkerScope } from \"../project/worker-scope.js\";\nimport { type FetchTarget, isolatePort, type PortLike } from \"./protocol.js\";\n\nexport function serveIsolate(\n port: PortLike,\n importCode: (\n load: any,\n logs: (level: string, args: unknown[]) => void,\n ) => Promise<Record<string, unknown>>,\n opts: {\n patchConsole?: boolean;\n /**\n * Storage for Durable Object classes run INSIDE this isolate (facets of loaded code):\n * each facet is a cell of an isolate-local runtime, with its own SQLite.\n */\n openSql?: (load: any) => OpenCellSql | Promise<OpenCellSql>;\n } = {},\n) {\n let module: Record<string, unknown> = {};\n const runtimes = new Map<string, CellRuntime>();\n // Facet logging through the host: the isolate-local cell key → the facet's key in the\n // root's storage (celld: <root>/facets/<sha>) and the root's epoch.\n const facetKeys = new Map<string, { key: string; epoch?: number }>();\n const images = new Map<number, () => unknown>();\n let nextImage = 0;\n const hostLogged = (internal: string) => facetKeys.get(internal);\n const durability = {\n capturePages: true,\n async committed(cellKey: string, info: any) {\n const f = hostLogged(cellKey);\n if (!f) return;\n const token = ++nextImage;\n if (typeof info.image === \"function\") images.set(token, info.image);\n try {\n await call({\n type: \"durable\",\n op: \"committed\",\n cellKey: f.key,\n info: {\n seq: info.seq,\n pages: info.pages,\n epoch: f.epoch,\n image: typeof info.image === \"function\" ? token : info.image,\n },\n });\n } finally {\n images.delete(token);\n }\n },\n };\n const backup = {\n async load(cellKey: string) {\n const f = hostLogged(cellKey);\n return f\n ? ((await call({ type: \"durable\", op: \"load\", cellKey: f.key })) as Uint8Array | undefined)\n : undefined;\n },\n async save() {},\n };\n // The host holds the root's lease and attaches each facet with its epoch: handing that epoch\n // out as the facet's own lease makes its ObjectHost rebuild the local database from the\n // bucket when it opens, as a root does under a lease (a pool kept from an earlier ownership\n // is older than what other nodes wrote since). Without an epoch, the local file stays primary.\n const ownership: Lease = {\n async tryAcquire(cellKey) {\n const lost = new AbortController().signal; // fencing stays with the root's lease, in the host\n return {\n name: cellKey,\n epoch: hostLogged(cellKey)?.epoch,\n lost,\n release: async () => {},\n } as LeaseHandle;\n },\n };\n let loadMsg: any;\n let sql: Promise<OpenCellSql> | undefined;\n const objectRuntime = async (className: string) => {\n let rt = runtimes.get(className);\n if (!rt) {\n if (!opts.openSql)\n throw new Error(\"this isolate cannot host Durable Object classes (no storage)\");\n sql ??= Promise.resolve(opts.openSql(loadMsg));\n const openSql = await sql;\n if (runtimes.has(className)) return runtimes.get(className)!;\n if (typeof module[className] !== \"function\")\n throw new Error(`Loaded worker does not export class \"${className}\"`);\n rt = createCellRuntime({\n worker: { ...(module as WorkerModule), default: {} },\n durableObjects: { [className]: { className } },\n openSql,\n vars: env, // the loaded Worker's env: plain values and host stubs\n storage: { durability, backup, ownership },\n facets: true, // rows move to this run's scope on open, as celld does for facets\n ctxIdOf: (key) => {\n const name = idNames.get(key);\n return name === undefined\n ? undefined\n : (rt!.env[className] as { idFromName(n: string): DurableObjectId }).idFromName(name);\n },\n });\n runtimes.set(className, rt);\n }\n return rt;\n };\n let env: Record<string, unknown> = {};\n let exports: Record<string, unknown> = {};\n // The loaded Worker is this realm's own: its `cloudflare:workers` reads `env` and `exports` here.\n const scope = realmWorkerScope();\n // The name of the `id` a facet was given by `ctx.facets.get`, by its isolate-local cell key:\n // the facet's `ctx.id` (partyserver's `this.name`). Two facets may be given the same id; each\n // keeps its own object and storage, addressed by the parent's facet cell key.\n const idNames = new Map<string, string>();\n // A facet of loaded code: the object lives here, named by the parent's facet cell key.\n const facet = async (className: string, cellKey: string) => {\n const rt = await objectRuntime(className);\n const ns = rt.env[className] as {\n getByName(n: string): any;\n idFromName(n: string): { toString(): string };\n };\n return { rt, stub: ns.getByName(cellKey), key: `${className}/${ns.idFromName(cellKey)}` };\n };\n const exported = (name: string, props?: unknown) => {\n const target = module[name];\n if (target === undefined) throw new Error(`Loaded worker does not export \"${name}\"`);\n return entrypointStub(target, env, props, exports);\n };\n const fetchTarget = async (t: FetchTarget, req: Request): Promise<Response> =>\n \"className\" in t\n ? (await facet(t.className, t.cellKey)).stub.fetch(req)\n : exported(t.name, t.props).fetch(req);\n const handle = async (m: any) => {\n if (m.type === \"load\") {\n loadMsg = m;\n env = m.env; // host bindings arrive as stubs: every call goes back to the host\n // Logs go to the host (Tail Workers, and the host prints them): through the global\n // console, or — where the realm has other code that logs — a console scoped to the\n // loaded modules (importCode receives the sink).\n const sink = (level: string, args: unknown[]) =>\n call({ type: \"log\", level, args: safe(args) }).catch(() => {});\n if (opts.patchConsole !== false)\n for (const level of [\"log\", \"info\", \"warn\", \"error\", \"debug\"] as const)\n console[level] = (...args: unknown[]) => sink(level, args);\n if (m.outbound === \"fetcher\")\n globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) =>\n outbound(new Request(input, init))) as typeof fetch;\n if (m.outbound === \"blocked\")\n globalThis.fetch = (async () => {\n throw new Error(\n \"This worker is not permitted to access the internet via global functions like fetch(). It must use capabilities (such as bindings in 'env') to talk to the outside world.\",\n );\n }) as typeof fetch;\n scope.bind({ env, exports }); // read while the module is evaluated\n module = await importCode(m, sink);\n exports = ctxExports(module, env);\n scope.bind({ env, exports });\n return Object.fromEntries(Object.entries(module).map(([k, v]) => [k, typeof v]));\n }\n if (m.type === \"image\") return await images.get(m.token)?.();\n if (m.type === \"object\") {\n const { rt, stub, key } = await facet(m.className, m.cellKey);\n if (m.op === \"attach\") {\n facetKeys.set(key, { key: m.cellKey, epoch: m.epoch });\n if (typeof m.idName === \"string\") idNames.set(key, m.idName);\n return;\n }\n if (m.op === \"call\") return stub[m.method](...m.args);\n if (m.op === \"restart\") return rt.restartObject(key);\n if (m.op === \"delete\") {\n await runInDurableObject(\n stub,\n (_o: unknown, ctx: { storage: { deleteAll(): Promise<void> } }) =>\n ctx.storage.deleteAll(),\n );\n return rt.restartObject(key);\n }\n throw new Error(`unknown object op ${m.op}`);\n }\n if (m.type === \"call\") return exported(m.name, m.props)[m.method](...m.args);\n throw new Error(`unknown isolate message ${m.type}`);\n };\n const { call, outbound } = isolatePort(port, handle, fetchTarget);\n}\n\nconst safe = (args: unknown[]) =>\n args.map((a) => {\n try {\n return structuredClone(a);\n } catch {\n return String(a);\n }\n });\n"]}
1
+ {"version":3,"file":"isolate.js","sourceRoot":"","sources":["../../src/isolate/isolate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The isolate side: loads the Worker code in THIS realm and serves fetch/RPC for the host.\n * `importCode` is realm specific (data: URL of a bundle on Node, a site URL in browsers).\n */\nimport { ctxExports, entrypointStub } from \"../core/handles.js\";\nimport type { DurableObjectId } from \"../core/id.js\";\nimport { runInDurableObject } from \"../core/namespace.js\";\nimport { type CellRuntime, createCellRuntime, type WorkerModule } from \"../core/runtime.js\";\nimport type { Lease, LeaseHandle, OpenCellSql } from \"../ports.js\";\nimport { realmWorkerScope } from \"../project/worker-scope.js\";\nimport { CfWebSocket } from \"../ws/server.js\";\nimport { type FetchTarget, isolatePort, type PortLike } from \"./protocol.js\";\n\nexport function serveIsolate(\n port: PortLike,\n importCode: (\n load: any,\n logs: (level: string, args: unknown[]) => void,\n ) => Promise<Record<string, unknown>>,\n opts: {\n patchConsole?: boolean;\n /**\n * Storage for Durable Object classes run INSIDE this isolate (facets of loaded code):\n * each facet is a cell of an isolate-local runtime, with its own SQLite.\n */\n openSql?: (load: any) => OpenCellSql | Promise<OpenCellSql>;\n } = {},\n) {\n let module: Record<string, unknown> = {};\n const runtimes = new Map<string, CellRuntime>();\n // Facet logging through the host: the isolate-local cell key → the facet's key in the\n // root's storage (celld: <root>/facets/<sha>) and the root's epoch.\n const facetKeys = new Map<string, { key: string; epoch?: number }>();\n const images = new Map<number, () => unknown>();\n let nextImage = 0;\n const hostLogged = (internal: string) => facetKeys.get(internal);\n const durability = {\n capturePages: true,\n async committed(cellKey: string, info: any) {\n const f = hostLogged(cellKey);\n if (!f) return;\n const token = ++nextImage;\n if (typeof info.image === \"function\") images.set(token, info.image);\n try {\n await call({\n type: \"durable\",\n op: \"committed\",\n cellKey: f.key,\n info: {\n seq: info.seq,\n pages: info.pages,\n epoch: f.epoch,\n image: typeof info.image === \"function\" ? token : info.image,\n },\n });\n } finally {\n images.delete(token);\n }\n },\n };\n const backup = {\n async load(cellKey: string) {\n const f = hostLogged(cellKey);\n return f\n ? ((await call({ type: \"durable\", op: \"load\", cellKey: f.key })) as Uint8Array | undefined)\n : undefined;\n },\n async save() {},\n };\n // The host holds the root's lease and attaches each facet with its epoch: handing that epoch\n // out as the facet's own lease makes its ObjectHost rebuild the local database from the\n // bucket when it opens, as a root does under a lease (a pool kept from an earlier ownership\n // is older than what other nodes wrote since). Without an epoch, the local file stays primary.\n const ownership: Lease = {\n async tryAcquire(cellKey) {\n const lost = new AbortController().signal; // fencing stays with the root's lease, in the host\n return {\n name: cellKey,\n epoch: hostLogged(cellKey)?.epoch,\n lost,\n release: async () => {},\n } as LeaseHandle;\n },\n };\n let loadMsg: any;\n let sql: Promise<OpenCellSql> | undefined;\n const objectRuntime = async (className: string) => {\n let rt = runtimes.get(className);\n if (!rt) {\n if (!opts.openSql)\n throw new Error(\"this isolate cannot host Durable Object classes (no storage)\");\n sql ??= Promise.resolve(opts.openSql(loadMsg));\n const openSql = await sql;\n if (runtimes.has(className)) return runtimes.get(className)!;\n if (typeof module[className] !== \"function\")\n throw new Error(`Loaded worker does not export class \"${className}\"`);\n rt = createCellRuntime({\n worker: { ...(module as WorkerModule), default: {} },\n durableObjects: { [className]: { className } },\n openSql,\n vars: env, // the loaded Worker's env: plain values and host stubs\n storage: { durability, backup, ownership },\n facets: true, // rows move to this run's scope on open, as celld does for facets\n ctxIdOf: (key) => {\n const name = idNames.get(key);\n return name === undefined\n ? undefined\n : (rt!.env[className] as { idFromName(n: string): DurableObjectId }).idFromName(name);\n },\n });\n runtimes.set(className, rt);\n }\n return rt;\n };\n let env: Record<string, unknown> = {};\n let exports: Record<string, unknown> = {};\n // The loaded Worker is this realm's own: its `cloudflare:workers` reads `env` and `exports` here.\n const scope = realmWorkerScope();\n // The name of the `id` a facet was given by `ctx.facets.get`, by its isolate-local cell key:\n // the facet's `ctx.id` (partyserver's `this.name`). Two facets may be given the same id; each\n // keeps its own object and storage, addressed by the parent's facet cell key.\n const idNames = new Map<string, string>();\n // A facet of loaded code: the object lives here, named by the parent's facet cell key.\n const facet = async (className: string, cellKey: string) => {\n const rt = await objectRuntime(className);\n const ns = rt.env[className] as {\n getByName(n: string): any;\n idFromName(n: string): { toString(): string };\n };\n return { rt, stub: ns.getByName(cellKey), key: `${className}/${ns.idFromName(cellKey)}` };\n };\n const exported = (name: string, props?: unknown) => {\n const target = module[name];\n if (target === undefined) throw new Error(`Loaded worker does not export \"${name}\"`);\n return entrypointStub(target, env, props, exports);\n };\n const fetchTarget = async (t: FetchTarget, req: Request): Promise<Response> =>\n \"className\" in t\n ? (await facet(t.className, t.cellKey)).stub.fetch(req)\n : exported(t.name, t.props).fetch(req);\n const handle = async (m: any) => {\n if (m.type === \"load\") {\n loadMsg = m;\n env = m.env; // host bindings arrive as stubs: every call goes back to the host\n // Logs go to the host (Tail Workers, and the host prints them): through the global\n // console, or — where the realm has other code that logs — a console scoped to the\n // loaded modules (importCode receives the sink).\n const sink = (level: string, args: unknown[]) =>\n call({ type: \"log\", level, args: safe(args) }).catch(() => {});\n if (opts.patchConsole !== false)\n for (const level of [\"log\", \"info\", \"warn\", \"error\", \"debug\"] as const)\n console[level] = (...args: unknown[]) => sink(level, args);\n if (m.outbound === \"fetcher\")\n globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) =>\n outbound(new Request(input, init))) as typeof fetch;\n if (m.outbound === \"blocked\")\n globalThis.fetch = (async () => {\n throw new Error(\n \"This worker is not permitted to access the internet via global functions like fetch(). It must use capabilities (such as bindings in 'env') to talk to the outside world.\",\n );\n }) as typeof fetch;\n scope.bind({ env, exports }); // read while the module is evaluated\n module = await importCode(m, sink);\n exports = ctxExports(module, env);\n scope.bind({ env, exports });\n return Object.fromEntries(Object.entries(module).map(([k, v]) => [k, typeof v]));\n }\n if (m.type === \"image\") return await images.get(m.token)?.();\n // The host is stopping this isolate: the sockets accepted here hear 1001 first.\n // A socket whose early frames went through a relay port hears it one hop later: the\n // answer waits a macrotask, so the host does not terminate the isolate before that.\n if (m.type === \"goAway\") {\n CfWebSocket.goAway(\"the loaded Worker stopped\");\n return await new Promise((r) => setTimeout(r, 0));\n }\n if (m.type === \"object\") {\n const { rt, stub, key } = await facet(m.className, m.cellKey);\n if (m.op === \"attach\") {\n facetKeys.set(key, { key: m.cellKey, epoch: m.epoch });\n if (typeof m.idName === \"string\") idNames.set(key, m.idName);\n return;\n }\n if (m.op === \"call\") return stub[m.method](...m.args);\n if (m.op === \"restart\") return rt.restartObject(key);\n if (m.op === \"delete\") {\n await runInDurableObject(\n stub,\n (_o: unknown, ctx: { storage: { deleteAll(): Promise<void> } }) =>\n ctx.storage.deleteAll(),\n );\n return rt.restartObject(key);\n }\n throw new Error(`unknown object op ${m.op}`);\n }\n if (m.type === \"call\") return exported(m.name, m.props)[m.method](...m.args);\n throw new Error(`unknown isolate message ${m.type}`);\n };\n const { call, outbound } = isolatePort(port, handle, fetchTarget);\n}\n\nconst safe = (args: unknown[]) =>\n args.map((a) => {\n try {\n return structuredClone(a);\n } catch {\n return String(a);\n }\n });\n"]}
@@ -22,7 +22,9 @@
22
22
  * isolate → host for the loaded code's global `fetch()` when a `globalOutbound` Fetcher routes it.
23
23
  * A WebSocket upgrade (host → isolate) has a port of its own instead, since HTTP/1.1 drops the
24
24
  * `Upgrade` header: its meta carries the request's method and headers; the isolate answers with
25
- * the response's head (and body, unless it is a 101), then both sides relay the socket's frames.
25
+ * the response's head and closes the port. A 101's head carries the socket's own MessagePort,
26
+ * transferred: the socket's frames then go straight between the client and the isolate, outside
27
+ * the multiplexer, so open sockets hold none of its ports (it allows `maxPorts` at once).
26
28
  */
27
29
  export interface PortLike {
28
30
  postMessage(msg: unknown, transfer?: Transferable[]): void;
@@ -56,7 +58,6 @@ type Handle = (msg: any) => Promise<unknown>;
56
58
  export declare function hostPort(port: PortLike, handle: Handle, outbound?: (req: Request) => Promise<Response>): Promise<{
57
59
  call: Call;
58
60
  fetch: Fetch;
59
- closeSockets(): void;
60
61
  }>;
61
62
  /**
62
63
  * The isolate's end: accepts the ports the host opens. Everything the isolate calls the host