@mcp-b/do-runtime 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +24 -3
- package/dist/chunks/{io-context-RmmjNtwm.js → io-context-BBgKEsdR.js} +12 -3
- package/dist/chunks/{io-context-RmmjNtwm.js.map → io-context-BBgKEsdR.js.map} +1 -1
- package/dist/conformance/host.d.ts +16 -2
- package/dist/conformance.js.map +1 -1
- package/dist/gate.js +1 -1
- package/dist/index.js +586 -191
- package/dist/index.js.map +1 -1
- package/dist/src/api/actor-state.d.ts +30 -21
- package/dist/src/api/global-scope.d.ts +12 -3
- package/dist/src/api/web-socket.d.ts +88 -73
- package/dist/src/index.d.ts +4 -4
- package/dist/src/io/io-context.d.ts +1 -0
- package/dist/src/server/actor-container.d.ts +16 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0821d6e: Replace the fail-closed Durable Object WebSocket stubs with workerd-compatible hibernatable WebSockets.
|
|
8
|
+
|
|
9
|
+
`WebSocketPair`, `WebSocketRequestResponsePair`, all eight `DurableObjectState` WebSocket methods, tags, structured-clone attachments, auto-responses, event timeouts, close state, and `webSocketMessage`/`webSocketClose`/`webSocketError` dispatch now run through actor input and output gates. `installActorScope()` installs the three WebSocket globals alongside the existing actor-scoped primitives.
|
|
10
|
+
|
|
11
|
+
Embedders that evict live actors can mirror socket state through the new optional `ports.hibernation` callbacks and rehydrate it through `ActorContainerOptions.webSockets` before the next constructor runs. `container.quiescence()` exposes the non-blocking eviction signals, and `gateHooks` makes both gates observable.
|
|
12
|
+
|
|
13
|
+
This is a breaking replacement for the exported hibernation-unavailable error and the previous `never`-typed methods. Hosts should remove reconnect-only fallbacks; applications can use the Agents SDK and PartyServer hibernation defaults.
|
|
14
|
+
|
|
3
15
|
## 0.5.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -157,7 +157,7 @@ Open a second container over the same directory and `increment()` answers `3`: t
|
|
|
157
157
|
|
|
158
158
|
Two runnable browser hosts live in [`examples/`](examples/), each with its own README and Playwright e2e (`pnpm test:examples`):
|
|
159
159
|
|
|
160
|
-
- [`examples/extension/`](examples/extension/) — a Chrome MV3 compatibility harness: service worker → offscreen document (with corpse recovery) → worker hosting an Agents SDK `Counter` and local sub-agents. Proves persistent state, sibling and nested facet isolation, overlapping async work, abort/delete lifecycle, sub-agent scheduling across host recreation, exclusive host ownership,
|
|
160
|
+
- [`examples/extension/`](examples/extension/) — a Chrome MV3 compatibility harness: service worker → offscreen document (with corpse recovery) → worker hosting an Agents SDK `Counter` and local sub-agents. Proves persistent state, sibling and nested facet isolation, overlapping async work, abort/delete lifecycle, sub-agent scheduling across host recreation, exclusive host ownership, hibernating `AgentClient` WebSockets, state sync, callable and streaming RPC, SDK queues, stateless MCP, inbound email routing, the MV3 CSP story (`'wasm-unsafe-eval'`), and `chrome.alarms` recreation of an evicted host before durable alarm delivery.
|
|
161
161
|
- [`examples/vibe-platform/`](examples/vibe-platform/) — a self-contained vibe-coding page that authors both a front-end and an Agents SDK `Agent`, runs them in-tab with durable SQLite-backed state, and exports the unchanged sources as a Wrangler project that passes `wrangler deploy --dry-run`.
|
|
162
162
|
|
|
163
163
|
## Hosting an actor
|
|
@@ -175,6 +175,9 @@ The runtime owns semantics; the host owns placement and substrate. `createActorC
|
|
|
175
175
|
| `ports.facets` | A `FacetHost`: place a child container, abort it, copy or delete its storage. |
|
|
176
176
|
| `ports.timer` | `now()` and `afterDelay()`, captured below any installed actor scope. |
|
|
177
177
|
| `ports.fetch` | Optional global outbound. Absent means `fetch` refuses by name, as a Worker with `globalOutbound: null` does. |
|
|
178
|
+
| `ports.hibernation` | Optional mirror callbacks for accepted sockets, attachment bytes, auto-response changes, and closure. Omit it when the host never rebuilds a live socket placement. |
|
|
179
|
+
| `webSockets` | Socket references and mirrored tags/attachments to register before the new instance constructor runs. |
|
|
180
|
+
| `gateHooks` | Optional input/output gate instrumentation for an embedding host. |
|
|
178
181
|
| `facet` | Present when constructing a local child: its id, depth, and the root-owned `FacetTree`. |
|
|
179
182
|
|
|
180
183
|
The lifecycle:
|
|
@@ -185,6 +188,7 @@ The lifecycle:
|
|
|
185
188
|
4. Use `container.run(fn, signal?)` for events that are not method calls: a WebSocket frame, a host callback. Its signal likewise stops only a queued event, not one already running.
|
|
186
189
|
5. Reach the platform through `container.globals` (or install it with `installActorScope`). For a host-provided promise an actor must await, wrap it once in `container.awaitIo()`.
|
|
187
190
|
6. Watch `container.onBroken`; dispose the placement; recreate it on the next event over the same storage. A failed `blockConcurrencyWhile()` rejects its caller with `BrokenActorError` and breaks the placement with that same error.
|
|
191
|
+
7. Before evicting, inspect `container.quiescence()`. Mirror live sockets through `ports.hibernation`, then build the replacement with `webSockets`; do not reconnect or call `acceptWebSocket()` again.
|
|
188
192
|
|
|
189
193
|
For a standard Durable Object binding, call
|
|
190
194
|
`createDurableObjectNamespace(uniqueKey, channel)` and put the result in `env`
|
|
@@ -221,7 +225,25 @@ Construct one `AlarmScheduler` per namespace over a `SqlDatabase` of its own. It
|
|
|
221
225
|
|
|
222
226
|
On workerd every awaitable thing is an io-context primitive, so "resuming from an await re-enters with a fresh input lock" never needs saying. Here it does. A raw `setTimeout` resolves a promise the runtime does not own; the continuation resumes with an empty invocation stack and the next `ctx.storage` call throws `no input lock available in this context`. That is by design — the alternative is a continuation that silently writes outside the gate.
|
|
223
227
|
|
|
224
|
-
`container.globals` is the complete gated set, bound to that container: `setTimeout`/`clearTimeout`/`setInterval`/`clearInterval` capture the critical section when armed and re-enter when fired; `scheduler.wait()` and `scheduler.yield()` resume under the actor; `fetch()` waits for output locks and releases the input gate while in flight; `crypto` re-enters on async completion;
|
|
228
|
+
`container.globals` is the complete gated set, bound to that container: `setTimeout`/`clearTimeout`/`setInterval`/`clearInterval` capture the critical section when armed and re-enter when fired; `scheduler.wait()` and `scheduler.yield()` resume under the actor; `fetch()` waits for output locks and releases the input gate while in flight; `crypto` re-enters on async completion; and `WebSocketPair` creates runtime-owned socket halves. Install it as the worker's globals (`installActorScope`) when one worker hosts one root, or hand it to application code explicitly when it must not.
|
|
229
|
+
|
|
230
|
+
### Hibernatable WebSockets
|
|
231
|
+
|
|
232
|
+
`ctx.acceptWebSocket(socket, tags)` enables class-method dispatch and the full
|
|
233
|
+
Workers state API: `getWebSockets`, `getTags`, attachments, auto-response pairs
|
|
234
|
+
and timestamps, and the hibernatable event timeout. The runtime works without a
|
|
235
|
+
hibernation port for hosts that keep a container alive.
|
|
236
|
+
|
|
237
|
+
An evicting host implements `ports.hibernation` as a mirror. It retains the same
|
|
238
|
+
raw socket reference plus copied tags and attachment bytes, drops the old
|
|
239
|
+
placement, and supplies that snapshot as `webSockets` on the replacement. The
|
|
240
|
+
registry is populated before the constructor, so SDKs can lazily rebuild their
|
|
241
|
+
connection wrappers without another upgrade or connect hook. Closed sockets are
|
|
242
|
+
removed before `webSocketClose` runs.
|
|
243
|
+
|
|
244
|
+
`container.quiescence()` reports armed timers, pending `waitUntil` work, input
|
|
245
|
+
lock state, and output-gate breakage without waiting. `drainWaitUntil()` is for
|
|
246
|
+
shutdown and intentionally never settles while a live interval remains armed.
|
|
225
247
|
|
|
226
248
|
Actor bundles can also install `doRuntimeAwaitTransform()` from `@mcp-b/do-runtime/vite`. A production build checks the final module graph and fails with transformed/total counts for any included module with an uncovered await; the development transform warns once per module if a transformed await reaches its fail-open path without an actor lock.
|
|
227
249
|
|
|
@@ -231,7 +253,6 @@ The browser cannot reproduce every workerd facility. Where it cannot, the runtim
|
|
|
231
253
|
|
|
232
254
|
| Area | Contract here |
|
|
233
255
|
| --- | --- |
|
|
234
|
-
| Hibernatable WebSockets | Unsupported; named methods throw. Use memory-only sockets and reconnect. |
|
|
235
256
|
| Cloudflare point-in-time recovery and read replication | Unsupported by local SQLite; named methods throw. Bookmarks are development counters, not recovery points. |
|
|
236
257
|
| Actor-class stub serialization | Throws; needs workerd's serializer and channel tokens. |
|
|
237
258
|
| Module-scope `waitUntil`, `cache`, `abortIsolate`, Workers RPC stub constructors | Named `cloudflare:workers` boundaries throw. |
|
|
@@ -995,13 +995,16 @@ var TimeoutManager = class {
|
|
|
995
995
|
const criticalSection = ctx.getCriticalSection();
|
|
996
996
|
const wake = new AbortController();
|
|
997
997
|
state.armed = wake;
|
|
998
|
-
const
|
|
998
|
+
const canceled = new Promise((resolve) => {
|
|
999
|
+
wake.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
1000
|
+
});
|
|
1001
|
+
const fired = Promise.race([this.#timer.afterDelay(state.params.msDelay, wake.signal).then(async () => {
|
|
999
1002
|
if (state.isCanceled) return;
|
|
1000
1003
|
state.armed = void 0;
|
|
1001
1004
|
await this.#fire(ctx, id, state, criticalSection);
|
|
1002
1005
|
}, (exception) => {
|
|
1003
1006
|
if (!state.isCanceled) throw exception;
|
|
1004
|
-
});
|
|
1007
|
+
}), canceled]);
|
|
1005
1008
|
ctx.addWaitUntil(fired);
|
|
1006
1009
|
}
|
|
1007
1010
|
/** ← the body of the `.then` at `io-context.c++:759-816`, which is one `context.run`. */
|
|
@@ -1042,6 +1045,9 @@ var TaskSet = class {
|
|
|
1042
1045
|
});
|
|
1043
1046
|
this.#tasks.add(task);
|
|
1044
1047
|
}
|
|
1048
|
+
size() {
|
|
1049
|
+
return this.#tasks.size;
|
|
1050
|
+
}
|
|
1045
1051
|
/** ← `kj::TaskSet::onEmpty()`. Re-checks, since a task can add another. */
|
|
1046
1052
|
async onEmpty() {
|
|
1047
1053
|
while (this.#tasks.size > 0) await Promise.all([...this.#tasks]);
|
|
@@ -1281,6 +1287,9 @@ var IoContext = class {
|
|
|
1281
1287
|
waitUntilStatus() {
|
|
1282
1288
|
return this.#waitUntilStatus?.exception;
|
|
1283
1289
|
}
|
|
1290
|
+
waitUntilTaskCount() {
|
|
1291
|
+
return this.#waitUntilTasks.size();
|
|
1292
|
+
}
|
|
1284
1293
|
/**
|
|
1285
1294
|
* ← `IncomingRequest::drain()`, actor branch. "For actors, all promises are canceled on
|
|
1286
1295
|
* actor shutdown, not on a fixed timeout, because work doesn't necessarily happen on a
|
|
@@ -1500,4 +1509,4 @@ var IoContext = class {
|
|
|
1500
1509
|
//#endregion
|
|
1501
1510
|
export { hasUserErrorDetail as a, setUserErrorDetail as c, tryCurrentSlice as d, CanceledError as f, captureGateStack as i, tryCurrentContinuation as l, OutputGate as m, IoContext as n, isExceptionFromInputGateBroken as o, InputGate as p, atCheckpointEnd as r, requireInputLock as s, BrokenActorError as t, tryCurrentIoContext as u };
|
|
1502
1511
|
|
|
1503
|
-
//# sourceMappingURL=io-context-
|
|
1512
|
+
//# sourceMappingURL=io-context-BBgKEsdR.js.map
|