@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/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-
|
|
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$
|
|
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$
|
|
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:
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
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(
|
|
2478
|
-
|
|
2467
|
+
acceptWebSocket(ws, tags) {
|
|
2468
|
+
this.#options.webSockets.acceptWebSocket(ws, tags);
|
|
2479
2469
|
}
|
|
2480
|
-
getWebSockets(
|
|
2481
|
-
|
|
2470
|
+
getWebSockets(tag) {
|
|
2471
|
+
return tag === void 0 ? this.#options.webSockets.getWebSockets() : this.#options.webSockets.getWebSockets(tag);
|
|
2482
2472
|
}
|
|
2483
|
-
setWebSocketAutoResponse(
|
|
2484
|
-
|
|
2473
|
+
setWebSocketAutoResponse(maybeReqResp) {
|
|
2474
|
+
this.#options.webSockets.setWebSocketAutoResponse(maybeReqResp);
|
|
2485
2475
|
}
|
|
2486
2476
|
getWebSocketAutoResponse() {
|
|
2487
|
-
|
|
2477
|
+
return this.#options.webSockets.getWebSocketAutoResponse();
|
|
2488
2478
|
}
|
|
2489
|
-
getWebSocketAutoResponseTimestamp(
|
|
2490
|
-
|
|
2479
|
+
getWebSocketAutoResponseTimestamp(ws) {
|
|
2480
|
+
return this.#options.webSockets.getWebSocketAutoResponseTimestamp(ws);
|
|
2491
2481
|
}
|
|
2492
|
-
setHibernatableWebSocketEventTimeout(
|
|
2493
|
-
|
|
2482
|
+
setHibernatableWebSocketEventTimeout(timeoutMs) {
|
|
2483
|
+
this.#options.webSockets.setHibernatableWebSocketEventTimeout(timeoutMs);
|
|
2494
2484
|
}
|
|
2495
2485
|
getHibernatableWebSocketEventTimeout() {
|
|
2496
|
-
|
|
2486
|
+
return this.#options.webSockets.getHibernatableWebSocketEventTimeout();
|
|
2497
2487
|
}
|
|
2498
|
-
getTags(
|
|
2499
|
-
|
|
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
|
|
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
|
|
3203
|
-
*
|
|
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
|
|
5612
|
-
#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,
|
|
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
|