@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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as hasUserErrorDetail, c as setUserErrorDetail, d as tryCurrentSlice, f as CanceledError, i as captureGateStack, m as OutputGate, n as IoContext, o as isExceptionFromInputGateBroken, p as InputGate, r as atCheckpointEnd, s as requireInputLock, t as BrokenActorError, u as tryCurrentIoContext } from "./chunks/io-context-RmmjNtwm.js";
1
+ import { a as hasUserErrorDetail, c as setUserErrorDetail, d as tryCurrentSlice, f as CanceledError, i as captureGateStack, m as OutputGate, n as IoContext, o as isExceptionFromInputGateBroken, p as InputGate, r as atCheckpointEnd, s as requireInputLock, t as BrokenActorError, u as tryCurrentIoContext } from "./chunks/io-context-BBgKEsdR.js";
2
2
  import { RpcTarget as RpcTarget$1 } from "./cloudflare-workers.js";
3
3
  import { a as SqliteDatabase, c as getText, l as hasCurrentSqliteTable, o as getBlob, s as getInt64, t as ensureRuntimeStorageVersion, u as isNull } from "./chunks/sqlite-migrations-DsWmLP_B.js";
4
4
  import { ALARM_RETRY_MAX_TRIES, ALARM_RETRY_START_SECONDS, AlarmScheduler, RETRY_BACKOFF_MAX, RETRY_JITTER_FACTOR, alarmRetryDelayMs } from "./server/alarm-scheduler.js";
@@ -46,10 +46,10 @@ var ColoLocalActorNamespace = class {
46
46
  return this.#channel.getColoLocalActor({ actorId });
47
47
  }
48
48
  };
49
- var textEncoder$1 = new TextEncoder();
49
+ var textEncoder$2 = new TextEncoder();
50
50
  /** `kj::String::size()` is bytes; `String.prototype.length` is UTF-16 code units. */
51
51
  function utf8Length(value) {
52
- return textEncoder$1.encode(value).length;
52
+ return textEncoder$2.encode(value).length;
53
53
  }
54
54
  /**
55
55
  * ← `DurableObjectId` (`actor.h:42-84`). "DurableObjectId type seen by
@@ -1750,11 +1750,9 @@ function asRawRow(values) {
1750
1750
  * `transformMaybeBackpressure` keeps the branch because
1751
1751
  * `DeleteAllResults.backpressure` is still a promise in `io/actor-cache.ts`.
1752
1752
  *
1753
- * Not ported, because the substrate has no equivalent: Hibernatable WebSockets,
1754
- * which is the whole reason `DurableObjectState`'s eight WebSocket methods are
1755
- * named throwing stubs; V8's private wire bytes, replaced by a browser-safe
1756
- * structured-clone encoding with the same public value semantics; the billing
1757
- * counters
1753
+ * Not ported, because the substrate has no equivalent: V8's private wire bytes,
1754
+ * replaced by a browser-safe structured-clone encoding with the same public
1755
+ * value semantics; the billing counters
1758
1756
  * (`billingUnits`, `ActorObserver`, `updateStorageWriteUnit`) and the trace
1759
1757
  * spans, both already absent throughout; `enableSql`, a workerd namespace option
1760
1758
  * that exists to simulate a non-SQLite Durable Object; and `ReplicaActorOutgoingFactory`,
@@ -1773,14 +1771,6 @@ var FACET_NAME_MAX_LENGTH = 256;
1773
1771
  /** Root is at depth 0, so the deepest allowed facet is at depth 3. */
1774
1772
  var FACET_TREE_MAX_DEPTH = 4;
1775
1773
  /**
1776
- * The substrate boundary named in the package README: Hibernatable WebSockets
1777
- * exist so the platform can evict an actor while keeping its sockets open, and
1778
- * Chrome exposes no equivalent lifecycle. Under this repo's fail-closed tenet
1779
- * the throw IS the specified behaviour, which is why §2.5 orders the four
1780
- * silent no-op stubs beside it replaced.
1781
- */
1782
- var HIBERNATION_UNIMPLEMENTED_MESSAGE = "Hibernatable WebSockets are not available in this runtime: they exist so the platform can evict a Durable Object while keeping its sockets open, and there is no equivalent lifecycle to be faithful to.";
1783
- /**
1784
1774
  * ← what falls off the end of `DurableObjectFacets::get`'s class switch
1785
1775
  * (`actor-state.c++:1029-1043`).
1786
1776
  *
@@ -1828,7 +1818,7 @@ function isPutOptions(value) {
1828
1818
  var NULL_CHARACTER = "\0";
1829
1819
  /** ← the `0xff` upstream strips from the tail of a prefix, in UTF-16 code units. */
1830
1820
  var MAX_CODE_UNIT = 65535;
1831
- var textEncoder = new TextEncoder();
1821
+ var textEncoder$1 = new TextEncoder();
1832
1822
  var textDecoder = new TextDecoder();
1833
1823
  /** A byte JSON could never begin with, `DO`, and the local codec version. */
1834
1824
  var VALUE_CODEC_HEADER = new Uint8Array([
@@ -1844,7 +1834,7 @@ var VALUE_CODEC_HEADER = new Uint8Array([
1844
1834
  * remain readable.
1845
1835
  */
1846
1836
  function serializeValue(value) {
1847
- const body = textEncoder.encode(JSON.stringify(serialize(value)));
1837
+ const body = textEncoder$1.encode(JSON.stringify(serialize(value)));
1848
1838
  const encoded = new Uint8Array(VALUE_CODEC_HEADER.byteLength + body.byteLength);
1849
1839
  encoded.set(VALUE_CODEC_HEADER);
1850
1840
  encoded.set(body, VALUE_CODEC_HEADER.byteLength);
@@ -2291,7 +2281,7 @@ var DurableObjectTransaction = class extends DurableObjectStorageOperations {
2291
2281
  * refuses, which is a bound a caller can hit.
2292
2282
  */
2293
2283
  function requireValidFacetName(name) {
2294
- if (textEncoder.encode(name).length > 256) throw new TypeError(`Facet name is too long (max 256 characters).`);
2284
+ if (textEncoder$1.encode(name).length > 256) throw new TypeError(`Facet name is too long (max 256 characters).`);
2295
2285
  }
2296
2286
  /**
2297
2287
  * ← the `KJ_SWITCH_ONEOF(options.$class)` lambda (`actor-state.c++:1029-1043`).
@@ -2474,29 +2464,29 @@ var DurableObjectState = class {
2474
2464
  if (options.mode !== "auto" && options.mode !== "disabled") throw new TypeError(`configureReadReplication() called with unknown mode setting: ${options.mode}.`);
2475
2465
  return this.#ctx.awaitIo(storage.getActorCacheInterface().configureReadReplication(options.mode === "auto"));
2476
2466
  }
2477
- acceptWebSocket(_ws, _tags) {
2478
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2467
+ acceptWebSocket(ws, tags) {
2468
+ this.#options.webSockets.acceptWebSocket(ws, tags);
2479
2469
  }
2480
- getWebSockets(_tag) {
2481
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2470
+ getWebSockets(tag) {
2471
+ return tag === void 0 ? this.#options.webSockets.getWebSockets() : this.#options.webSockets.getWebSockets(tag);
2482
2472
  }
2483
- setWebSocketAutoResponse(_maybeReqResp) {
2484
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2473
+ setWebSocketAutoResponse(maybeReqResp) {
2474
+ this.#options.webSockets.setWebSocketAutoResponse(maybeReqResp);
2485
2475
  }
2486
2476
  getWebSocketAutoResponse() {
2487
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2477
+ return this.#options.webSockets.getWebSocketAutoResponse();
2488
2478
  }
2489
- getWebSocketAutoResponseTimestamp(_ws) {
2490
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2479
+ getWebSocketAutoResponseTimestamp(ws) {
2480
+ return this.#options.webSockets.getWebSocketAutoResponseTimestamp(ws);
2491
2481
  }
2492
- setHibernatableWebSocketEventTimeout(_timeoutMs) {
2493
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2482
+ setHibernatableWebSocketEventTimeout(timeoutMs) {
2483
+ this.#options.webSockets.setHibernatableWebSocketEventTimeout(timeoutMs);
2494
2484
  }
2495
2485
  getHibernatableWebSocketEventTimeout() {
2496
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2486
+ return this.#options.webSockets.getHibernatableWebSocketEventTimeout();
2497
2487
  }
2498
- getTags(_ws) {
2499
- throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
2488
+ getTags(ws) {
2489
+ return this.#options.webSockets.getTags(ws);
2500
2490
  }
2501
2491
  };
2502
2492
  //#endregion
@@ -2766,6 +2756,528 @@ function gateReader(ctx, reader) {
2766
2756
  } });
2767
2757
  }
2768
2758
  //#endregion
2759
+ //#region src/api/web-socket.ts
2760
+ var WebSocketRequestResponsePairImpl = class {
2761
+ #request;
2762
+ #response;
2763
+ constructor(request, response) {
2764
+ this.#request = String(request);
2765
+ this.#response = String(response);
2766
+ }
2767
+ get request() {
2768
+ return this.#request;
2769
+ }
2770
+ get response() {
2771
+ return this.#response;
2772
+ }
2773
+ };
2774
+ var RuntimeWebSocketRequestResponsePair = new Proxy(WebSocketRequestResponsePairImpl, { apply() {
2775
+ throw new TypeError("Failed to construct 'WebSocketRequestResponsePair': Please use the 'new' operator, this DOM object constructor cannot be called as a function.");
2776
+ } });
2777
+ /** ← the `JSG_REQUIRE(!native.state.is<Accepted>(), ...)` at the head of `accept()`. */
2778
+ var ALREADY_ACCEPTED_MESSAGE = "acceptWebSocket(): this socket has already been accepted by an actor. A socket's frames are delivered by exactly one read loop, and a second accept would deliver them under two gates.";
2779
+ var HIBERNATION_ALREADY_ACCEPTED_MESSAGE = "Cannot call `acceptWebSocket()` if the WebSocket was already accepted via `accept()`";
2780
+ var HIBERNATION_AFTER_ACCEPT_MESSAGE = "Can't accept() WebSocket after enabling hibernation.";
2781
+ var HIBERNATION_PAIR_USED_MESSAGE = "Cannot call `acceptWebSocket()` on this WebSocket because its pair has already been accepted or used in a Response.";
2782
+ var MAX_HIBERNATABLE_SOCKETS = 32768;
2783
+ var MAX_TAGS = 10;
2784
+ var MAX_TAG_LENGTH = 256;
2785
+ var MAX_ATTACHMENT_BYTES = 16384;
2786
+ var MAX_AUTO_RESPONSE_BYTES = 2048;
2787
+ var MAX_EVENT_TIMEOUT = 6048e5;
2788
+ var MAX_CLOSE_REASON_BYTES = 123;
2789
+ var WEB_SOCKET_READY_STATES = {
2790
+ READY_STATE_CONNECTING: 0,
2791
+ READY_STATE_OPEN: 1,
2792
+ READY_STATE_CLOSING: 2,
2793
+ READY_STATE_CLOSED: 3,
2794
+ CONNECTING: 0,
2795
+ OPEN: 1,
2796
+ CLOSING: 2,
2797
+ CLOSED: 3
2798
+ };
2799
+ var textEncoder = new TextEncoder();
2800
+ var SOCKET_EVENTS = [
2801
+ "open",
2802
+ "message",
2803
+ "close",
2804
+ "error"
2805
+ ];
2806
+ var metadata = /* @__PURE__ */ new WeakMap();
2807
+ function socketMetadata(socket) {
2808
+ let value = metadata.get(socket);
2809
+ if (value === void 0) {
2810
+ value = {};
2811
+ metadata.set(socket, value);
2812
+ }
2813
+ return value;
2814
+ }
2815
+ function isRawWebSocket(value) {
2816
+ return typeof value === "object" && value !== null && "addEventListener" in value && typeof value.addEventListener === "function" && "send" in value && typeof value.send === "function" && "close" in value && typeof value.close === "function";
2817
+ }
2818
+ function requireWebSocket(value, operation) {
2819
+ if (!isRawWebSocket(value)) throw new TypeError(`Failed to execute '${operation}' on 'WebSocket': parameter 1 is not of type 'WebSocket'.`);
2820
+ return value;
2821
+ }
2822
+ function serializeAttachment(socket, value) {
2823
+ try {
2824
+ structuredClone(value);
2825
+ } catch (error) {
2826
+ if (error instanceof DOMException && error.name === "DataCloneError") throw new DOMException(error.message.replace(/^Failed to execute 'structuredClone' on '[^']+': /, ""), "DataCloneError");
2827
+ throw error;
2828
+ }
2829
+ const bytes = serializeValue(value);
2830
+ const measuredBytes = typeof value === "string" ? textEncoder.encode(value).byteLength + 5 : bytes.byteLength;
2831
+ if (measuredBytes > MAX_ATTACHMENT_BYTES) throw new Error(`A WebSocket 'attachment' cannot be larger than ${MAX_ATTACHMENT_BYTES} bytes.'attachment' was ${measuredBytes} bytes.`);
2832
+ const state = socketMetadata(socket);
2833
+ state.attachment = bytes;
2834
+ if (state.accepted?.mode === "hibernatable") state.accepted.registry.attachmentChanged(socket, bytes);
2835
+ }
2836
+ function deserializeAttachment(socket) {
2837
+ const attachment = socketMetadata(socket).attachment;
2838
+ if (attachment === void 0) return null;
2839
+ return deserializeValue("WebSocket attachment", attachment);
2840
+ }
2841
+ function serializeAttachmentMethod(value) {
2842
+ if (arguments.length === 0) throw new TypeError("Failed to execute 'serializeAttachment' on 'WebSocket': parameter 1 is not of type 'Value'.");
2843
+ serializeAttachment(requireWebSocket(this, "serializeAttachment"), value);
2844
+ }
2845
+ function deserializeAttachmentMethod() {
2846
+ return deserializeAttachment(requireWebSocket(this, "deserializeAttachment"));
2847
+ }
2848
+ function cloneMessageData(data) {
2849
+ if (typeof data === "string" || data instanceof Blob) return data;
2850
+ if (data instanceof ArrayBuffer) return data.slice(0);
2851
+ if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice().buffer;
2852
+ return String(data);
2853
+ }
2854
+ var MemoryWebSocketEndpoint = class extends EventTarget {
2855
+ peer;
2856
+ #sentClose = false;
2857
+ send(data) {
2858
+ this.peer.dispatchEvent(new MessageEvent("message", { data: cloneMessageData(data) }));
2859
+ }
2860
+ close(code = 1e3, reason = "") {
2861
+ if (this.#sentClose) return;
2862
+ this.#sentClose = true;
2863
+ this.peer.dispatchEvent(new CloseEvent("close", {
2864
+ code,
2865
+ reason,
2866
+ wasClean: true
2867
+ }));
2868
+ }
2869
+ };
2870
+ /** One public socket identity, in classic or hibernatable mode after acceptance. */
2871
+ var AcceptedWebSocket = class AcceptedWebSocket extends EventTarget {
2872
+ static READY_STATE_CONNECTING = WEB_SOCKET_READY_STATES.READY_STATE_CONNECTING;
2873
+ static READY_STATE_OPEN = WEB_SOCKET_READY_STATES.READY_STATE_OPEN;
2874
+ static READY_STATE_CLOSING = WEB_SOCKET_READY_STATES.READY_STATE_CLOSING;
2875
+ static READY_STATE_CLOSED = WEB_SOCKET_READY_STATES.READY_STATE_CLOSED;
2876
+ static CONNECTING = WEB_SOCKET_READY_STATES.CONNECTING;
2877
+ static OPEN = WEB_SOCKET_READY_STATES.OPEN;
2878
+ static CLOSING = WEB_SOCKET_READY_STATES.CLOSING;
2879
+ static CLOSED = WEB_SOCKET_READY_STATES.CLOSED;
2880
+ bufferedAmount = 0;
2881
+ extensions = "";
2882
+ protocol = "";
2883
+ url = "";
2884
+ #ctx;
2885
+ #socket;
2886
+ #pairState;
2887
+ #delivery = { mode: "pending" };
2888
+ #pump = Promise.resolve();
2889
+ #pending = [];
2890
+ #readyState = AcceptedWebSocket.OPEN;
2891
+ #ownClose = false;
2892
+ #peerClose = false;
2893
+ #binaryType = "blob";
2894
+ onopen = null;
2895
+ onmessage = null;
2896
+ onclose = null;
2897
+ onerror = null;
2898
+ constructor(ctx, socket, pairState) {
2899
+ super();
2900
+ this.#ctx = ctx;
2901
+ this.#socket = socket;
2902
+ this.#pairState = pairState;
2903
+ if (pairState === void 0) this.#enableClassic();
2904
+ for (const type of SOCKET_EVENTS) socket.addEventListener(type, (event) => {
2905
+ this.#receive(type, event);
2906
+ });
2907
+ }
2908
+ get readyState() {
2909
+ return this.#readyState;
2910
+ }
2911
+ get binaryType() {
2912
+ return this.#binaryType;
2913
+ }
2914
+ set binaryType(value) {
2915
+ this.#binaryType = value;
2916
+ }
2917
+ accept() {
2918
+ if (this.#delivery.mode === "hibernatable") throw new TypeError(HIBERNATION_AFTER_ACCEPT_MESSAGE);
2919
+ if (this.#delivery.mode === "classic") throw new Error(ALREADY_ACCEPTED_MESSAGE);
2920
+ if (this.#pairState !== void 0) this.#pairState.used = true;
2921
+ this.#enableClassic();
2922
+ }
2923
+ send(data) {
2924
+ if (this.#delivery.mode === "hibernatable" && this.#ownClose) throw new TypeError("Can't call WebSocket send() after close().");
2925
+ if (this.#peerClose || this.#readyState === AcceptedWebSocket.CLOSED) return;
2926
+ this.#markPairUsed();
2927
+ this.#enqueue(() => this.#socket.send(data));
2928
+ }
2929
+ close(code, reason = "") {
2930
+ if (this.#readyState === AcceptedWebSocket.CLOSED || this.#ownClose) return;
2931
+ if (this.#delivery.mode === "hibernatable" || this.#pairState !== void 0) validateClose(code, reason);
2932
+ this.#markPairUsed();
2933
+ this.#ownClose = true;
2934
+ this.#readyState = this.#peerClose ? AcceptedWebSocket.CLOSED : AcceptedWebSocket.CLOSING;
2935
+ this.#enqueue(() => this.#socket.close(code, reason));
2936
+ }
2937
+ serializeAttachment(value) {
2938
+ if (arguments.length === 0) serializeAttachmentMethod.call(this);
2939
+ else serializeAttachment(this, value);
2940
+ }
2941
+ deserializeAttachment() {
2942
+ return deserializeAttachment(this);
2943
+ }
2944
+ acceptHibernation(registry) {
2945
+ if (this.#delivery.mode !== "pending") throw new Error(HIBERNATION_ALREADY_ACCEPTED_MESSAGE);
2946
+ if (this.#pairState?.used === true && !this.#pairState.hibernationAccepted) throw new Error(HIBERNATION_PAIR_USED_MESSAGE);
2947
+ if (this.#pairState !== void 0) {
2948
+ this.#pairState.used = true;
2949
+ this.#pairState.hibernationAccepted = true;
2950
+ }
2951
+ this.#activateHibernation(registry, this.#ctx);
2952
+ }
2953
+ rehydrateHibernation(registry, ctx) {
2954
+ this.#activateHibernation(registry, ctx);
2955
+ }
2956
+ #activateHibernation(registry, ctx) {
2957
+ this.#ctx = ctx;
2958
+ this.#delivery = {
2959
+ mode: "hibernatable",
2960
+ registry
2961
+ };
2962
+ this.#pending = [];
2963
+ }
2964
+ markPairUsed() {
2965
+ this.#markPairUsed();
2966
+ }
2967
+ #markPairUsed() {
2968
+ if (this.#pairState !== void 0) this.#pairState.used = true;
2969
+ }
2970
+ #enableClassic() {
2971
+ const delivery = {
2972
+ mode: "classic",
2973
+ criticalSection: this.#ctx.getCriticalSection()
2974
+ };
2975
+ this.#delivery = delivery;
2976
+ socketMetadata(this).accepted = { mode: "classic" };
2977
+ const pending = this.#pending;
2978
+ this.#pending = [];
2979
+ for (const item of pending) this.#deliverClassic(item.type, item.event, delivery.criticalSection);
2980
+ }
2981
+ #receive(type, event) {
2982
+ if (type === "close") {
2983
+ this.#receiveClose(event);
2984
+ return;
2985
+ }
2986
+ const delivery = this.#delivery;
2987
+ if (delivery.mode === "pending") this.#pending.push({
2988
+ type,
2989
+ event
2990
+ });
2991
+ else if (delivery.mode === "classic") this.#deliverClassic(type, event, delivery.criticalSection);
2992
+ else delivery.registry.receive(this, type, event);
2993
+ }
2994
+ #receiveClose(event) {
2995
+ if (this.#ownClose) this.#readyState = AcceptedWebSocket.CLOSED;
2996
+ else {
2997
+ this.#peerClose = true;
2998
+ this.#readyState = AcceptedWebSocket.CLOSING;
2999
+ if (this.#delivery.mode === "classic" && this.#pairState !== void 0) {
3000
+ if (event.code === 1005 || event.code === 1006 || event.code === 1015) this.#readyState = AcceptedWebSocket.CLOSED;
3001
+ else this.close(event.code, event.reason);
3002
+ }
3003
+ }
3004
+ const delivery = this.#delivery;
3005
+ if (delivery.mode === "pending") this.#pending.push({
3006
+ type: "close",
3007
+ event
3008
+ });
3009
+ else if (delivery.mode === "classic") this.#deliverClassic("close", event, delivery.criticalSection);
3010
+ else delivery.registry.receive(this, "close", event);
3011
+ }
3012
+ #deliverClassic(type, event, criticalSection) {
3013
+ this.#ctx.addWaitUntil(this.#ctx.run(() => {
3014
+ const delivered = cloneEventFor(type, event);
3015
+ this.dispatchEvent(delivered);
3016
+ const handler = this[`on${type}`];
3017
+ handler?.(delivered);
3018
+ }, { input: criticalSection }));
3019
+ }
3020
+ #enqueue(write) {
3021
+ const outputLock = this.#ctx.waitForOutputLocks();
3022
+ this.#pump = this.#pump.then(async () => {
3023
+ await outputLock;
3024
+ write();
3025
+ });
3026
+ this.#ctx.addWaitUntil(this.#pump);
3027
+ }
3028
+ };
3029
+ for (const [name, value] of Object.entries(WEB_SOCKET_READY_STATES)) Object.defineProperty(AcceptedWebSocket.prototype, name, {
3030
+ value,
3031
+ enumerable: true
3032
+ });
3033
+ var HibernatableWebSocketRegistry = class {
3034
+ #ctx;
3035
+ #dispatch;
3036
+ #host;
3037
+ #entries = [];
3038
+ #autoResponse = null;
3039
+ #eventTimeout = null;
3040
+ #pairConstructor;
3041
+ constructor(ctx, dispatch, host, rehydrated = []) {
3042
+ this.#ctx = ctx;
3043
+ this.#dispatch = dispatch;
3044
+ this.#host = host;
3045
+ for (const value of rehydrated) this.#rehydrate(value);
3046
+ }
3047
+ get WebSocketPair() {
3048
+ const registry = this;
3049
+ this.#pairConstructor ??= new Proxy(class WebSocketPair {}, { construct: () => registry.#createPair() });
3050
+ return this.#pairConstructor;
3051
+ }
3052
+ acceptWebSocket(socket, tags) {
3053
+ if (!isRawWebSocket(socket)) throw new TypeError("Failed to execute 'acceptWebSocket' on 'DurableObjectState': parameter 1 is not of type 'WebSocket'.");
3054
+ const state = socketMetadata(socket);
3055
+ if (state.accepted !== void 0) throw new Error(HIBERNATION_ALREADY_ACCEPTED_MESSAGE);
3056
+ if (this.#entries.length >= MAX_HIBERNATABLE_SOCKETS) throw new Error(`only ${MAX_HIBERNATABLE_SOCKETS} websockets can be accepted on a single Durable Object instance`);
3057
+ const normalizedTags = normalizeTags(tags);
3058
+ if (socket instanceof AcceptedWebSocket) socket.acceptHibernation(this);
3059
+ else this.#listenRaw(socket);
3060
+ state.accepted = {
3061
+ mode: "hibernatable",
3062
+ registry: this
3063
+ };
3064
+ this.#entries.push({
3065
+ socket,
3066
+ tags: normalizedTags
3067
+ });
3068
+ this.#host?.accepted(socket, normalizedTags);
3069
+ }
3070
+ getWebSockets(tag) {
3071
+ if (arguments.length > 0) {
3072
+ if (typeof tag !== "string") return [];
3073
+ return this.#entries.filter((entry) => entry.tags.includes(tag)).map((entry) => entry.socket);
3074
+ }
3075
+ return this.#entries.map((entry) => entry.socket).reverse();
3076
+ }
3077
+ getTags(socket) {
3078
+ const state = isRawWebSocket(socket) ? metadata.get(socket) : void 0;
3079
+ if (state?.accepted === void 0) throw new Error("you must call 'acceptWebSocket()' before attempting to access the tags of a WebSocket.");
3080
+ if (state.accepted.mode !== "hibernatable") throw new Error("only hibernatable websockets can have tags.");
3081
+ const entry = this.#entries.find((candidate) => candidate.socket === socket);
3082
+ if (entry === void 0) throw new Error("you must call 'acceptWebSocket()' before attempting to access the tags of a WebSocket.");
3083
+ return [...entry.tags];
3084
+ }
3085
+ setWebSocketAutoResponse(pair) {
3086
+ if (pair === void 0) {
3087
+ this.#autoResponse = null;
3088
+ this.#host?.autoResponse(null);
3089
+ return;
3090
+ }
3091
+ if (!(pair instanceof WebSocketRequestResponsePairImpl)) throw new TypeError("Failed to execute 'setWebSocketAutoResponse' on 'DurableObjectState': parameter 1 is not of type 'WebSocketRequestResponsePair'.");
3092
+ validateAutoResponseSize("Request", pair.request);
3093
+ validateAutoResponseSize("Response", pair.response);
3094
+ this.#autoResponse = pair;
3095
+ this.#host?.autoResponse({
3096
+ request: pair.request,
3097
+ response: pair.response
3098
+ });
3099
+ }
3100
+ getWebSocketAutoResponse() {
3101
+ const pair = this.#autoResponse;
3102
+ return pair === null ? null : new RuntimeWebSocketRequestResponsePair(pair.request, pair.response);
3103
+ }
3104
+ getWebSocketAutoResponseTimestamp(socket) {
3105
+ if (!isRawWebSocket(socket)) throw new TypeError("Failed to execute 'getWebSocketAutoResponseTimestamp' on 'DurableObjectState': parameter 1 is not of type 'WebSocket'.");
3106
+ const timestamp = this.#entries.find((entry) => entry.socket === socket)?.autoResponseTimestamp;
3107
+ return timestamp === void 0 ? null : new Date(timestamp);
3108
+ }
3109
+ setHibernatableWebSocketEventTimeout(value) {
3110
+ if (value === void 0 || Number(value) === 0) {
3111
+ this.#eventTimeout = null;
3112
+ return;
3113
+ }
3114
+ const number = Number(value);
3115
+ if (Number.isNaN(number)) throw new TypeError("The value cannot be converted because it is not an integer.");
3116
+ if (number < 0) throw new TypeError("The value cannot be converted because it is negative and this API expects a positive number.");
3117
+ if (number > 4294967295) throw new TypeError("Value out of range. Must be less than or equal to 4294967295.");
3118
+ const timeout = Math.trunc(number);
3119
+ if (timeout > MAX_EVENT_TIMEOUT) throw new Error(`Event timeout should not exceed ${MAX_EVENT_TIMEOUT} ms.`);
3120
+ this.#eventTimeout = timeout;
3121
+ }
3122
+ getHibernatableWebSocketEventTimeout() {
3123
+ return this.#eventTimeout;
3124
+ }
3125
+ attachmentChanged(socket, bytes) {
3126
+ if (this.#entries.some((entry) => entry.socket === socket)) this.#host?.attachment(socket, bytes);
3127
+ }
3128
+ receive(socket, type, event) {
3129
+ const entry = this.#entries.find((candidate) => candidate.socket === socket);
3130
+ if (entry === void 0) return;
3131
+ if (type === "message") {
3132
+ const data = event.data;
3133
+ if (typeof data === "string" && data === this.#autoResponse?.request) {
3134
+ entry.autoResponseTimestamp = this.#ctx.now();
3135
+ socket.send(this.#autoResponse.response);
3136
+ return;
3137
+ }
3138
+ const message = cloneMessageData(data);
3139
+ if (message instanceof Blob) {
3140
+ this.#ctx.addWaitUntil(message.arrayBuffer().then((buffer) => {
3141
+ this.#schedule(() => this.#dispatch.message(socket, buffer));
3142
+ }));
3143
+ return;
3144
+ }
3145
+ this.#schedule(() => this.#dispatch.message(socket, message));
3146
+ return;
3147
+ }
3148
+ if (type === "close") {
3149
+ this.#remove(entry);
3150
+ const close = event;
3151
+ this.#schedule(() => this.#dispatch.close(socket, close.code, close.reason, close.wasClean));
3152
+ return;
3153
+ }
3154
+ if (type === "error") this.#schedule(() => this.#dispatch.error(socket, event));
3155
+ }
3156
+ #schedule(handler) {
3157
+ this.#ctx.addWaitUntil(this.#ctx.run(handler).then(() => {}));
3158
+ }
3159
+ #remove(entry) {
3160
+ const index = this.#entries.indexOf(entry);
3161
+ if (index === -1) return;
3162
+ this.#entries.splice(index, 1);
3163
+ this.#host?.closed(entry.socket);
3164
+ }
3165
+ #listenRaw(socket) {
3166
+ const state = socketMetadata(socket);
3167
+ if (state.rawListenersInstalled === true) return;
3168
+ state.rawListenersInstalled = true;
3169
+ for (const type of [
3170
+ "message",
3171
+ "close",
3172
+ "error"
3173
+ ]) socket.addEventListener(type, (event) => {
3174
+ const accepted = socketMetadata(socket).accepted;
3175
+ if (accepted?.mode === "hibernatable") accepted.registry.receive(socket, type, event);
3176
+ });
3177
+ }
3178
+ #rehydrate(value) {
3179
+ const socket = value.socket;
3180
+ if (!isRawWebSocket(socket)) throw new TypeError("ActorContainerOptions.webSockets contains a non-WebSocket value.");
3181
+ const tags = normalizeTags(value.tags);
3182
+ const state = socketMetadata(socket);
3183
+ state.accepted = {
3184
+ mode: "hibernatable",
3185
+ registry: this
3186
+ };
3187
+ if (value.attachment !== void 0) state.attachment = value.attachment.slice();
3188
+ if (socket instanceof AcceptedWebSocket) socket.rehydrateHibernation(this, this.#ctx);
3189
+ else this.#listenRaw(socket);
3190
+ const entry = {
3191
+ socket,
3192
+ tags
3193
+ };
3194
+ if (value.autoResponseTimestamp !== void 0) entry.autoResponseTimestamp = value.autoResponseTimestamp;
3195
+ this.#entries.push(entry);
3196
+ }
3197
+ #createPair() {
3198
+ const pairState = {
3199
+ used: false,
3200
+ hibernationAccepted: false
3201
+ };
3202
+ const left = new MemoryWebSocketEndpoint();
3203
+ const right = new MemoryWebSocketEndpoint();
3204
+ left.peer = right;
3205
+ right.peer = left;
3206
+ return {
3207
+ 0: new AcceptedWebSocket(this.#ctx, left, pairState),
3208
+ 1: new AcceptedWebSocket(this.#ctx, right, pairState)
3209
+ };
3210
+ }
3211
+ };
3212
+ function acceptWebSocket(ctx, socket) {
3213
+ const state = socketMetadata(socket);
3214
+ if (state.accepted !== void 0) throw new Error(ALREADY_ACCEPTED_MESSAGE);
3215
+ if (socket instanceof AcceptedWebSocket) {
3216
+ socket.accept();
3217
+ return socket;
3218
+ }
3219
+ state.accepted = { mode: "classic" };
3220
+ return new AcceptedWebSocket(ctx, socket);
3221
+ }
3222
+ function markWebSocketUsed(socket) {
3223
+ if (socket instanceof AcceptedWebSocket) socket.markPairUsed();
3224
+ }
3225
+ function installWebSocketGlobals(target, pairConstructor) {
3226
+ const constructor = globalThis.WebSocket;
3227
+ for (const [name, value] of Object.entries(WEB_SOCKET_READY_STATES)) {
3228
+ defineValue(constructor, name, value);
3229
+ defineValue(constructor.prototype, name, value);
3230
+ }
3231
+ defineValue(constructor.prototype, "serializeAttachment", serializeAttachmentMethod);
3232
+ defineValue(constructor.prototype, "deserializeAttachment", deserializeAttachmentMethod);
3233
+ defineValue(target, "WebSocket", constructor);
3234
+ defineValue(target, "WebSocketPair", pairConstructor);
3235
+ defineValue(target, "WebSocketRequestResponsePair", RuntimeWebSocketRequestResponsePair);
3236
+ }
3237
+ function defineValue(target, name, value) {
3238
+ if (Object.getOwnPropertyDescriptor(target, name)?.configurable === false) return;
3239
+ Object.defineProperty(target, name, {
3240
+ configurable: true,
3241
+ writable: true,
3242
+ value
3243
+ });
3244
+ }
3245
+ function normalizeTags(tags) {
3246
+ if (tags === void 0) return [];
3247
+ if (!Array.isArray(tags)) throw new TypeError("Failed to execute 'acceptWebSocket' on 'DurableObjectState': parameter 2 is not of type 'Array'.");
3248
+ if (tags.length > MAX_TAGS) throw new Error(`a Hibernatable WebSocket cannot have more than ${MAX_TAGS} tags`);
3249
+ const normalized = [...new Set(tags.map(String))];
3250
+ for (const tag of normalized) if (tag.length > MAX_TAG_LENGTH) throw new Error(`"${tag}" is longer than the max tag length (${MAX_TAG_LENGTH} characters).`);
3251
+ return normalized;
3252
+ }
3253
+ function validateAutoResponseSize(side, value) {
3254
+ const bytes = textEncoder.encode(value).byteLength;
3255
+ if (bytes > MAX_AUTO_RESPONSE_BYTES) throw new RangeError(`${side} cannot be larger than ${MAX_AUTO_RESPONSE_BYTES} bytes. A ${side.toLowerCase()} of size ${bytes} was provided.`);
3256
+ }
3257
+ function validateClose(code, reason) {
3258
+ if (code !== void 0 && code !== 1e3 && (code < 3e3 || code > 4999)) throw new DOMException(`Invalid WebSocket close code: ${code}.`, "InvalidAccessError");
3259
+ if (textEncoder.encode(reason).byteLength > MAX_CLOSE_REASON_BYTES) throw new DOMException(`WebSocket close reason must not be longer than ${MAX_CLOSE_REASON_BYTES} bytes when UTF-8 encoded.`, "SyntaxError");
3260
+ }
3261
+ function cloneEventFor(type, event) {
3262
+ if (type === "message") {
3263
+ const source = event;
3264
+ return new MessageEvent("message", {
3265
+ data: source.data,
3266
+ origin: source.origin,
3267
+ lastEventId: source.lastEventId
3268
+ });
3269
+ }
3270
+ if (type === "close") {
3271
+ const source = event;
3272
+ return new CloseEvent("close", {
3273
+ code: source.code,
3274
+ reason: source.reason,
3275
+ wasClean: source.wasClean
3276
+ });
3277
+ }
3278
+ return new Event(type);
3279
+ }
3280
+ //#endregion
2769
3281
  //#region src/api/global-scope.ts
2770
3282
  /**
2771
3283
  * ← workerd `src/workerd/api/global-scope.{h,c++}` — the alarm half, and the
@@ -3019,10 +3531,11 @@ var ActorGlobalScope = class {
3019
3531
  #readCurrentExternalEntry;
3020
3532
  scheduler;
3021
3533
  crypto;
3022
- constructor(ctx, options = {}) {
3534
+ constructor(ctx, options) {
3023
3535
  this.#ctx = ctx;
3024
3536
  this.#fetch = options.fetch;
3025
3537
  this.#readCurrentExternalEntry = options.currentExternalEntry;
3538
+ installWebSocketGlobals(this, options.webSockets.WebSocketPair);
3026
3539
  this.scheduler = new Scheduler(this);
3027
3540
  this.crypto = new GatedCrypto((op) => {
3028
3541
  this.#requireOwnSlice(op);
@@ -3127,6 +3640,7 @@ var ActorGlobalScope = class {
3127
3640
  * scope instead. A single-actor host simply writes `() => scope`.
3128
3641
  */
3129
3642
  function actorScopeBindings(resolve) {
3643
+ const BoundWebSocketPair = new Proxy(class WebSocketPair {}, { construct: () => new (resolve()).WebSocketPair() });
3130
3644
  return {
3131
3645
  awaitIo: (promise) => resolve().awaitIo(promise),
3132
3646
  scheduler: {
@@ -3143,6 +3657,9 @@ function actorScopeBindings(resolve) {
3143
3657
  },
3144
3658
  fetch: (input, init) => resolve().fetch(input, init),
3145
3659
  crypto: scopeCrypto(resolve),
3660
+ WebSocket: globalThis.WebSocket,
3661
+ WebSocketPair: BoundWebSocketPair,
3662
+ WebSocketRequestResponsePair: RuntimeWebSocketRequestResponsePair,
3146
3663
  get currentExternalEntry() {
3147
3664
  return resolve().currentExternalEntry;
3148
3665
  }
@@ -3153,7 +3670,7 @@ function actorScopeBindings(resolve) {
3153
3670
  * an operation actually runs.
3154
3671
  *
3155
3672
  * That laziness is required rather than tidy, and both lanes proved it. A facet's
3156
- * module destructures its seven names at module scope, which is BEFORE its container
3673
+ * module destructures its actor globals at module scope, which is BEFORE its container
3157
3674
  * exists — so a `crypto` that resolved on read threw at import. And on the root
3158
3675
  * path `globalThis.crypto` is read by things that are not the actor at all: capnweb,
3159
3676
  * the sqlite driver, the test runner. So the binding is a pair of plain objects
@@ -3199,8 +3716,8 @@ var ASYNC_SUBTLE_METHODS = [
3199
3716
  * one that is not.
3200
3717
  *
3201
3718
  * **A host should call this rather than assigning the names itself**, and the
3202
- * reason is the failure it prevents: a host that installs five of the six leaves
3203
- * one primitive ungated, and an ungated primitive that WORKS is invisible until
3719
+ * reason is the failure it prevents: a host that installs only a subset leaves a
3720
+ * primitive ungated, and an ungated primitive that WORKS is invisible until
3204
3721
  * a continuation after it touches storage — possibly never, on the path that
3205
3722
  * matters. The set is the package's, so it can grow without every host growing
3206
3723
  * with it.
@@ -3229,153 +3746,6 @@ function installActorScope(target, resolve) {
3229
3746
  });
3230
3747
  }
3231
3748
  }
3232
- //#endregion
3233
- //#region src/api/web-socket.ts
3234
- /** ← the `JSG_REQUIRE(!native.state.is<Accepted>(), ...)` at the head of `accept()`. */
3235
- var ALREADY_ACCEPTED_MESSAGE = "acceptWebSocket(): this socket has already been accepted by an actor. A socket's frames are delivered by exactly one read loop, and a second accept would deliver them under two gates.";
3236
- /** Sockets this runtime has accepted, so the refusal above is answerable. */
3237
- var accepted = /* @__PURE__ */ new WeakSet();
3238
- /** The four events a `WebSocket` dispatches, which `readLoop` and its `.then` cover upstream. */
3239
- var SOCKET_EVENTS = [
3240
- "open",
3241
- "message",
3242
- "close",
3243
- "error"
3244
- ];
3245
- /**
3246
- * ← `WebSocket::Accepted` (`web-socket.h:~300-360`), reached through
3247
- * `accept()` → `internalAccept(js, IoContext::current().getCriticalSection())`
3248
- * → `startReadLoop` (`web-socket.c++:133`, `:426`, `:429-433`, `:507`).
3249
- *
3250
- * An `EventTarget`, so a consumer registers listeners the way it would on a real
3251
- * socket — but on THIS object rather than on the raw one, because this is what
3252
- * runs them inside a gated slice.
3253
- */
3254
- var AcceptedWebSocket = class extends EventTarget {
3255
- #ctx;
3256
- #socket;
3257
- /**
3258
- * ← `readLoop`'s `cs` parameter, captured at accept and replayed for every
3259
- * frame via `mapAddRef(cs)` (`web-socket.c++:1110`). A socket accepted inside
3260
- * `blockConcurrencyWhile` therefore delivers its messages inside that critical
3261
- * section — §1.8's second bullet, and the reason this is captured here rather
3262
- * than read when a frame arrives.
3263
- */
3264
- #criticalSection;
3265
- /**
3266
- * ← `OutgoingMessagesMap outgoingMessages` plus `ensurePumping`
3267
- * (`web-socket.h:582-590`, `web-socket.c++:948-975`), as a chain.
3268
- *
3269
- * The table is insertion-ordered and the pump awaits each entry's own
3270
- * `outputLock` before sending it, so messages leave in order and message N
3271
- * waits only for the writes outstanding when IT was enqueued. A promise chain
3272
- * is the same two properties with nothing to schedule.
3273
- */
3274
- #pump = Promise.resolve();
3275
- onopen = null;
3276
- onmessage = null;
3277
- onclose = null;
3278
- onerror = null;
3279
- constructor(ctx, socket) {
3280
- super();
3281
- this.#ctx = ctx;
3282
- this.#socket = socket;
3283
- this.#criticalSection = ctx.getCriticalSection();
3284
- for (const type of SOCKET_EVENTS) socket.addEventListener(type, (event) => {
3285
- this.#deliver(type, event);
3286
- });
3287
- }
3288
- /**
3289
- * ← `co_await context.run([...](auto& wLock) { dispatchEventImpl(...) }, mapAddRef(cs))`
3290
- * (`web-socket.c++:1065-1110`).
3291
- *
3292
- * The run rides `addWaitUntil`, as upstream's read loop does ("We put the read
3293
- * loop in a `waitUntil`, since there would otherwise be a race condition
3294
- * between delivering the final close message and the request being canceled",
3295
- * `web-socket.c++:537-541`). That is also what stops a listener's throw
3296
- * becoming an unhandled rejection: it lands in `waitUntilStatus()`.
3297
- */
3298
- #deliver(type, event) {
3299
- this.#ctx.addWaitUntil(this.#ctx.run(() => {
3300
- const delivered = cloneEventFor(type, event);
3301
- this.dispatchEvent(delivered);
3302
- const handler = this[`on${type}`];
3303
- handler?.(delivered);
3304
- }, { input: this.#criticalSection }));
3305
- }
3306
- /**
3307
- * ← `WebSocket::send` (`web-socket.c++:~640`), which inserts a
3308
- * `GatedMessage{IoContext::current().waitForOutputLocksIfNecessary(), …}`.
3309
- *
3310
- * Synchronous, as upstream's is: the wait is the pump's, not the caller's. The
3311
- * output gate is what "blocks all outgoing messages from an actor that would
3312
- * allow the rest of the world to observe the actor's state" (§1.1), and a
3313
- * socket frame is exactly such a message.
3314
- *
3315
- * `waitForOutputLocksIfNecessary()` collapses to `waitForOutputLocks()` here
3316
- * for the reason the whole file collapses `kj::Maybe<Worker::Actor&>`: its
3317
- * body is `actor.map(…)` (`io-context.c++:383-386`) and every context in this
3318
- * runtime is an actor context.
3319
- */
3320
- send(data) {
3321
- this.#enqueue(() => {
3322
- this.#socket.send(data);
3323
- });
3324
- }
3325
- /** ← `WebSocket::close`, which enqueues a `Close` through the same gate. */
3326
- close(code, reason) {
3327
- this.#enqueue(() => {
3328
- this.#socket.close(code, reason);
3329
- });
3330
- }
3331
- #enqueue(write) {
3332
- const outputLock = this.#ctx.waitForOutputLocks();
3333
- this.#pump = this.#pump.then(async () => {
3334
- await outputLock;
3335
- write();
3336
- });
3337
- this.#ctx.addWaitUntil(this.#pump);
3338
- }
3339
- };
3340
- /**
3341
- * ← `accept()` / `state.acceptWebSocket()`, as the one verb.
3342
- *
3343
- * Named for what upstream names it, because the critical-section capture is a
3344
- * property of accepting rather than of constructing: "a socket accepted inside a
3345
- * `blockConcurrencyWhile` delivers its messages inside that critical section"
3346
- * (§1.8).
3347
- */
3348
- function acceptWebSocket(ctx, socket) {
3349
- if (accepted.has(socket)) throw new Error(ALREADY_ACCEPTED_MESSAGE);
3350
- accepted.add(socket);
3351
- return new AcceptedWebSocket(ctx, socket);
3352
- }
3353
- /**
3354
- * An `Event` may be dispatched by exactly one target at a time, so the raw
3355
- * socket's event object cannot be re-dispatched: `dispatchEvent` on an event
3356
- * that is already dispatched throws `InvalidStateError`, and one that has
3357
- * finished carries the raw socket as its `target`. Rebuilding it is what makes
3358
- * `event.target` the accepted socket, which is what a listener expects.
3359
- */
3360
- function cloneEventFor(type, event) {
3361
- if (type === "message") {
3362
- const source = event;
3363
- return new MessageEvent("message", {
3364
- data: source.data,
3365
- origin: source.origin,
3366
- lastEventId: source.lastEventId
3367
- });
3368
- }
3369
- if (type === "close") {
3370
- const source = event;
3371
- return new CloseEvent("close", {
3372
- code: source.code,
3373
- reason: source.reason,
3374
- wasClean: source.wasClean
3375
- });
3376
- }
3377
- return new Event(type);
3378
- }
3379
3749
  /**
3380
3750
  * ← the message every unimplemented `ActorCacheInterface` PITR method throws.
3381
3751
  * `ActorSqlite` overrides two of the four; the other two keep this.
@@ -5608,14 +5978,16 @@ var ActorTree = class {
5608
5978
  * which is the whole mechanism behind §1.10's parent↔child re-entrancy.
5609
5979
  */
5610
5980
  var ActorImpl = class {
5611
- #inputGate = new InputGate();
5612
- #outputGate = new OutputGate();
5981
+ #inputGate;
5982
+ #outputGate;
5613
5983
  #isFacet;
5614
5984
  /** Assigned after construction; `storage` is a WXT auto-import in extension bundles. */
5615
5985
  actorStorage;
5616
5986
  classInstance = { kind: "before-ctor" };
5617
- constructor(isFacet) {
5987
+ constructor(isFacet, hooks = {}) {
5618
5988
  this.#isFacet = isFacet;
5989
+ this.#inputGate = new InputGate(hooks.input);
5990
+ this.#outputGate = new OutputGate(hooks.output);
5619
5991
  }
5620
5992
  getInputGate() {
5621
5993
  return this.#inputGate;
@@ -5925,6 +6297,7 @@ var ActorContainerImpl = class {
5925
6297
  #facets;
5926
6298
  #tree;
5927
6299
  #env;
6300
+ #webSockets;
5928
6301
  state;
5929
6302
  facetTree;
5930
6303
  globals;
@@ -5932,16 +6305,22 @@ var ActorContainerImpl = class {
5932
6305
  #alarmTail = Promise.resolve();
5933
6306
  constructor(options, db, tree, facetTree) {
5934
6307
  const facet = options.facet;
5935
- this.#actor = new ActorImpl(facet !== void 0);
6308
+ this.#actor = new ActorImpl(facet !== void 0, options.gateHooks);
5936
6309
  this.#ctx = new IoContext(this.#actor, options.ports.timer);
5937
6310
  this.#env = options.env;
5938
6311
  this.#tree = tree;
5939
6312
  this.#cache = new ActorSqlite(db, this.#actor.getOutputGate(), async () => {}, facet === void 0 ? options.ports.alarms : DEFAULT_ALARM_OUTLET);
5940
6313
  this.#actor.actorStorage = this.#cache;
5941
6314
  this.#durableStorage = new DurableObjectStorage(this.#ctx, this.#cache);
6315
+ this.#webSockets = new HibernatableWebSocketRegistry(this.#ctx, {
6316
+ message: (socket, message) => this.#runWebSocketHandler("webSocketMessage", socket, message),
6317
+ close: (socket, code, reason, wasClean) => this.#runWebSocketHandler("webSocketClose", socket, code, reason, wasClean),
6318
+ error: (socket, error) => this.#runWebSocketHandler("webSocketError", socket, error)
6319
+ }, options.ports.hibernation, options.webSockets);
5942
6320
  this.globals = new ActorGlobalScope(this.#ctx, {
5943
6321
  fetch: options.ports.fetch,
5944
- currentExternalEntry: () => this.#currentExternalEntry
6322
+ currentExternalEntry: () => this.#currentExternalEntry,
6323
+ webSockets: this.#webSockets
5945
6324
  });
5946
6325
  this.facetTree = facetTree;
5947
6326
  this.#facets = new FacetManagerImpl(this, options.ports.facets, facet?.id ?? 0, facet?.depth ?? 0, this.facetTree);
@@ -5955,7 +6334,8 @@ var ActorContainerImpl = class {
5955
6334
  props: void 0,
5956
6335
  storage: this.#durableStorage,
5957
6336
  facets: this.#facets,
5958
- globals: actorScopeBindings(() => this.globals)
6337
+ globals: actorScopeBindings(() => this.globals),
6338
+ webSockets: this.#webSockets
5959
6339
  });
5960
6340
  }
5961
6341
  get onBroken() {
@@ -6089,6 +6469,14 @@ var ActorContainerImpl = class {
6089
6469
  drainWaitUntil() {
6090
6470
  return this.#ctx.drainWaitUntil();
6091
6471
  }
6472
+ quiescence() {
6473
+ return {
6474
+ armedTimers: this.#ctx.getTimeoutCount(),
6475
+ pendingWaitUntil: this.#ctx.waitUntilTaskCount(),
6476
+ inputLockHeld: this.#ctx.hasCurrent(),
6477
+ outputGateBroken: this.#ctx.isOutputGateBroken()
6478
+ };
6479
+ }
6092
6480
  /** ← `WorkerdApi::compileGlobals`'s `Global::WorkerLoader` arm. */
6093
6481
  workerLoader(channel, options) {
6094
6482
  return new WorkerLoader(this.#ctx, channel, options);
@@ -6182,6 +6570,13 @@ var ActorContainerImpl = class {
6182
6570
  if (!hasAlarmHandler(instance.instance)) throw new TypeError("Your Durable Object class must have an alarm() handler.");
6183
6571
  return instance.instance.alarm(new AlarmInvocationInfo(scheduledTime, retryCount));
6184
6572
  }
6573
+ #runWebSocketHandler(name, socket, ...args) {
6574
+ const instance = this.#actor.classInstance;
6575
+ if (instance.kind !== "running") return void 0;
6576
+ const handler = Reflect.get(instance.instance, name);
6577
+ if (typeof handler !== "function") return void 0;
6578
+ return Reflect.apply(handler, instance.instance, [socket, ...args]);
6579
+ }
6185
6580
  };
6186
6581
  /**
6187
6582
  * Builds one actor: the two gates, the `IoContext` over them, the storage engine
@@ -6252,6 +6647,6 @@ function newRpcSession(port, localMain) {
6252
6647
  return newMessagePortRpcSession(port, localMain);
6253
6648
  }
6254
6649
  //#endregion
6255
- export { ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE, ALARM_RETRY_MAX_TRIES, ALARM_RETRY_START_SECONDS, ALLOW_EXPERIMENTAL_MESSAGE, ALREADY_ACCEPTED_MESSAGE, AlarmInvocationInfo, AlarmScheduler, BYOB_READER_UNGATABLE_MESSAGE, BrokenActorError, CanceledError, DEAD_LOAD_CONTEXT_MESSAGE, DEFAULT_ALARM_OUTLET, FACET_ALARM_UNIMPLEMENTED_MESSAGE, FACET_NAME_MAX_LENGTH, FACET_TREE_MAX_DEPTH, FOREIGN_SLICE_MESSAGE, HIBERNATION_UNIMPLEMENTED_MESSAGE, LoopbackDurableObjectClass, NOT_BYTES_MESSAGE, NO_GLOBAL_OUTBOUND_MESSAGE, NO_MODULES_MESSAGE, PITR_UNIMPLEMENTED_MESSAGE, REPLICATION_UNIMPLEMENTED_MESSAGE, RETRY_BACKOFF_MAX, RETRY_JITTER_FACTOR, STREAMING_TAILS_EXPERIMENTAL_MESSAGE, WorkerLoader, WorkerStub, actorScopeBindings, alarmRetryDelayMs, asLoopbackDurableObjectClass, createActorContainer, createDurableObjectNamespace, gateRequestBody, installActorScope, jsModuleInPythonWorkerMessage, moduleFieldCountMessage, moduleNameMessage, newRpcSession, noFacets, notSerializableMessage, pythonModuleInJsWorkerMessage, typeScriptModuleNameMessage };
6650
+ export { ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE, ALARM_RETRY_MAX_TRIES, ALARM_RETRY_START_SECONDS, ALLOW_EXPERIMENTAL_MESSAGE, ALREADY_ACCEPTED_MESSAGE, AlarmInvocationInfo, AlarmScheduler, BYOB_READER_UNGATABLE_MESSAGE, BrokenActorError, CanceledError, DEAD_LOAD_CONTEXT_MESSAGE, DEFAULT_ALARM_OUTLET, FACET_ALARM_UNIMPLEMENTED_MESSAGE, FACET_NAME_MAX_LENGTH, FACET_TREE_MAX_DEPTH, FOREIGN_SLICE_MESSAGE, LoopbackDurableObjectClass, NOT_BYTES_MESSAGE, NO_GLOBAL_OUTBOUND_MESSAGE, NO_MODULES_MESSAGE, PITR_UNIMPLEMENTED_MESSAGE, REPLICATION_UNIMPLEMENTED_MESSAGE, RETRY_BACKOFF_MAX, RETRY_JITTER_FACTOR, STREAMING_TAILS_EXPERIMENTAL_MESSAGE, RuntimeWebSocketRequestResponsePair as WebSocketRequestResponsePair, WorkerLoader, WorkerStub, actorScopeBindings, alarmRetryDelayMs, asLoopbackDurableObjectClass, createActorContainer, createDurableObjectNamespace, gateRequestBody, installActorScope, installWebSocketGlobals, jsModuleInPythonWorkerMessage, markWebSocketUsed, moduleFieldCountMessage, moduleNameMessage, newRpcSession, noFacets, notSerializableMessage, pythonModuleInJsWorkerMessage, typeScriptModuleNameMessage };
6256
6651
 
6257
6652
  //# sourceMappingURL=index.js.map