@rivetkit/engine-runner 0.0.0-0-0-0-preview-guard-stops.9d82529

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/mod.cjs ADDED
@@ -0,0 +1,2901 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;// src/mod.ts
2
+ var _enginerunnerprotocol = require('@rivetkit/engine-runner-protocol'); var protocol = _interopRequireWildcard(_enginerunnerprotocol);
3
+ var _uuid = require('uuid');
4
+
5
+ // src/log.ts
6
+ var LOGGER;
7
+ function setLogger(logger2) {
8
+ LOGGER = logger2;
9
+ }
10
+ function logger() {
11
+ return LOGGER;
12
+ }
13
+
14
+ // src/utils.ts
15
+ var MAX_PAYLOAD_SIZE = 20 * 1024 * 1024;
16
+ function unreachable(x) {
17
+ throw `Unreachable: ${x}`;
18
+ }
19
+ function calculateBackoff(attempt, options = {}) {
20
+ const {
21
+ initialDelay = 1e3,
22
+ maxDelay = 3e4,
23
+ multiplier = 2,
24
+ jitter = true
25
+ } = options;
26
+ let delay = Math.min(initialDelay * multiplier ** attempt, maxDelay);
27
+ if (jitter) {
28
+ delay = delay * (1 + Math.random() * 0.25);
29
+ }
30
+ return Math.floor(delay);
31
+ }
32
+ function parseWebSocketCloseReason(reason) {
33
+ var _a;
34
+ const [mainPart, rayId] = reason.split("#");
35
+ const [group, error] = mainPart.split(".");
36
+ if (!group || !error) {
37
+ (_a = logger()) == null ? void 0 : _a.warn({ msg: "failed to parse close reason", reason });
38
+ return void 0;
39
+ }
40
+ return {
41
+ group,
42
+ error,
43
+ rayId
44
+ };
45
+ }
46
+ var U16_MAX = 65535;
47
+ function wrappingLtU16(a, b) {
48
+ return a !== b && wrappingSub(b, a, U16_MAX) < U16_MAX / 2;
49
+ }
50
+ function wrappingLteU16(a, b) {
51
+ return a === b || wrappingLtU16(a, b);
52
+ }
53
+ function wrappingAddU16(a, b) {
54
+ return (a + b) % (U16_MAX + 1);
55
+ }
56
+ function wrappingSubU16(a, b) {
57
+ return wrappingSub(a, b, U16_MAX);
58
+ }
59
+ function wrappingSub(a, b, max) {
60
+ const result = a - b;
61
+ if (result < 0) {
62
+ return result + max + 1;
63
+ }
64
+ return result;
65
+ }
66
+ function arraysEqual(a, b) {
67
+ const ua = new Uint8Array(a);
68
+ const ub = new Uint8Array(b);
69
+ if (ua.length !== ub.length) return false;
70
+ for (let i = 0; i < ua.length; i++) {
71
+ if (ua[i] !== ub[i]) return false;
72
+ }
73
+ return true;
74
+ }
75
+ function promiseWithResolvers() {
76
+ let resolve;
77
+ let reject;
78
+ const promise = new Promise((res, rej) => {
79
+ resolve = res;
80
+ reject = rej;
81
+ });
82
+ return { promise, resolve, reject };
83
+ }
84
+ function idToStr(id) {
85
+ const bytes = new Uint8Array(id);
86
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
87
+ }
88
+ function stringifyError(error) {
89
+ var _a;
90
+ if (error instanceof Error) {
91
+ return `${error.name}: ${error.message}${error.stack ? `
92
+ ${error.stack}` : ""}`;
93
+ } else if (typeof error === "string") {
94
+ return error;
95
+ } else if (typeof error === "object" && error !== null) {
96
+ try {
97
+ return `${JSON.stringify(error)}`;
98
+ } catch (e2) {
99
+ return `[object ${((_a = error.constructor) == null ? void 0 : _a.name) || "Object"}]`;
100
+ }
101
+ } else {
102
+ return String(error);
103
+ }
104
+ }
105
+
106
+ // src/actor.ts
107
+ var RunnerActor = (_class = class {
108
+ constructor(actorId, generation, config, hibernatingRequests) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);
109
+ this.hibernatingRequests = hibernatingRequests;
110
+ this.actorId = actorId;
111
+ this.generation = generation;
112
+ this.config = config;
113
+ this.actorStartPromise = promiseWithResolvers();
114
+ }
115
+
116
+
117
+
118
+ __init() {this.pendingRequests = []}
119
+ __init2() {this.webSockets = []}
120
+
121
+ __init3() {this.lastCommandIdx = -1n}
122
+ __init4() {this.nextEventIdx = 0n}
123
+ __init5() {this.eventHistory = []}
124
+ /**
125
+ * If restoreHibernatingRequests has been called. This is used to assert
126
+ * that the caller is implemented correctly.
127
+ **/
128
+ __init6() {this.hibernationRestored = false}
129
+ /**
130
+ * Set when the actor has explicitly requested to stop (e.g. c.destroy()).
131
+ * Used to send StopCode.Ok (graceful) vs StopCode.Error (ungraceful) so
132
+ * the engine crash policy handles sleepable actors correctly.
133
+ **/
134
+ __init7() {this.stopIntentSent = false}
135
+ // Pending request methods
136
+ getPendingRequest(gatewayId, requestId) {
137
+ var _a;
138
+ return (_a = this.pendingRequests.find(
139
+ (entry) => arraysEqual(entry.gatewayId, gatewayId) && arraysEqual(entry.requestId, requestId)
140
+ )) == null ? void 0 : _a.request;
141
+ }
142
+ createPendingRequest(gatewayId, requestId, clientMessageIndex) {
143
+ var _a, _b;
144
+ const exists = this.getPendingRequest(gatewayId, requestId) !== void 0;
145
+ if (exists) {
146
+ (_a = logger()) == null ? void 0 : _a.warn({
147
+ msg: "attempting to set pending request twice, replacing existing",
148
+ gatewayId: idToStr(gatewayId),
149
+ requestId: idToStr(requestId)
150
+ });
151
+ this.deletePendingRequest(gatewayId, requestId);
152
+ }
153
+ this.pendingRequests.push({
154
+ gatewayId,
155
+ requestId,
156
+ request: {
157
+ resolve: () => {
158
+ },
159
+ reject: () => {
160
+ },
161
+ actorId: this.actorId,
162
+ gatewayId,
163
+ requestId,
164
+ clientMessageIndex
165
+ }
166
+ });
167
+ (_b = logger()) == null ? void 0 : _b.debug({
168
+ msg: "added pending request",
169
+ gatewayId: idToStr(gatewayId),
170
+ requestId: idToStr(requestId),
171
+ length: this.pendingRequests.length
172
+ });
173
+ }
174
+ createPendingRequestWithStreamController(gatewayId, requestId, clientMessageIndex, streamController) {
175
+ var _a, _b;
176
+ const exists = this.getPendingRequest(gatewayId, requestId) !== void 0;
177
+ if (exists) {
178
+ (_a = logger()) == null ? void 0 : _a.warn({
179
+ msg: "attempting to set pending request twice, replacing existing",
180
+ gatewayId: idToStr(gatewayId),
181
+ requestId: idToStr(requestId)
182
+ });
183
+ this.deletePendingRequest(gatewayId, requestId);
184
+ }
185
+ this.pendingRequests.push({
186
+ gatewayId,
187
+ requestId,
188
+ request: {
189
+ resolve: () => {
190
+ },
191
+ reject: () => {
192
+ },
193
+ actorId: this.actorId,
194
+ gatewayId,
195
+ requestId,
196
+ clientMessageIndex,
197
+ streamController
198
+ }
199
+ });
200
+ (_b = logger()) == null ? void 0 : _b.debug({
201
+ msg: "added pending request with stream controller",
202
+ gatewayId: idToStr(gatewayId),
203
+ requestId: idToStr(requestId),
204
+ length: this.pendingRequests.length
205
+ });
206
+ }
207
+ deletePendingRequest(gatewayId, requestId) {
208
+ var _a;
209
+ const index = this.pendingRequests.findIndex(
210
+ (entry) => arraysEqual(entry.gatewayId, gatewayId) && arraysEqual(entry.requestId, requestId)
211
+ );
212
+ if (index !== -1) {
213
+ this.pendingRequests.splice(index, 1);
214
+ (_a = logger()) == null ? void 0 : _a.debug({
215
+ msg: "removed pending request",
216
+ gatewayId: idToStr(gatewayId),
217
+ requestId: idToStr(requestId),
218
+ length: this.pendingRequests.length
219
+ });
220
+ }
221
+ }
222
+ // WebSocket methods
223
+ getWebSocket(gatewayId, requestId) {
224
+ var _a;
225
+ return (_a = this.webSockets.find(
226
+ (entry) => arraysEqual(entry.gatewayId, gatewayId) && arraysEqual(entry.requestId, requestId)
227
+ )) == null ? void 0 : _a.ws;
228
+ }
229
+ setWebSocket(gatewayId, requestId, ws) {
230
+ var _a;
231
+ const exists = this.getWebSocket(gatewayId, requestId) !== void 0;
232
+ if (exists) {
233
+ (_a = logger()) == null ? void 0 : _a.warn({ msg: "attempting to set websocket twice" });
234
+ return;
235
+ }
236
+ this.webSockets.push({ gatewayId, requestId, ws });
237
+ }
238
+ deleteWebSocket(gatewayId, requestId) {
239
+ const index = this.webSockets.findIndex(
240
+ (entry) => arraysEqual(entry.gatewayId, gatewayId) && arraysEqual(entry.requestId, requestId)
241
+ );
242
+ if (index !== -1) {
243
+ this.webSockets.splice(index, 1);
244
+ }
245
+ }
246
+ handleAckEvents(lastEventIdx) {
247
+ this.eventHistory = this.eventHistory.filter(
248
+ (event) => event.checkpoint.index > lastEventIdx
249
+ );
250
+ }
251
+ recordEvent(eventWrapper) {
252
+ this.eventHistory.push(eventWrapper);
253
+ }
254
+ }, _class);
255
+
256
+ // src/stringify.ts
257
+ function stringifyArrayBuffer(buffer) {
258
+ return `ArrayBuffer(${buffer.byteLength})`;
259
+ }
260
+ function stringifyBigInt(value) {
261
+ return `${value}n`;
262
+ }
263
+ function stringifyMap(map) {
264
+ const entries = Array.from(map.entries()).map(([k, v]) => `"${k}": "${v}"`).join(", ");
265
+ return `Map(${map.size}){${entries}}`;
266
+ }
267
+ function stringifyMessageId(messageId) {
268
+ return `MessageId{gatewayId: ${idToStr(messageId.gatewayId)}, requestId: ${idToStr(messageId.requestId)}, messageIndex: ${messageId.messageIndex}}`;
269
+ }
270
+ function stringifyToServerTunnelMessageKind(kind) {
271
+ switch (kind.tag) {
272
+ case "ToServerResponseStart": {
273
+ const { status, headers, body, stream } = kind.val;
274
+ const bodyStr = body === null ? "null" : stringifyArrayBuffer(body);
275
+ return `ToServerResponseStart{status: ${status}, headers: ${stringifyMap(headers)}, body: ${bodyStr}, stream: ${stream}}`;
276
+ }
277
+ case "ToServerResponseChunk": {
278
+ const { body, finish } = kind.val;
279
+ return `ToServerResponseChunk{body: ${stringifyArrayBuffer(body)}, finish: ${finish}}`;
280
+ }
281
+ case "ToServerResponseAbort":
282
+ return "ToServerResponseAbort";
283
+ case "ToServerWebSocketOpen": {
284
+ const { canHibernate } = kind.val;
285
+ return `ToServerWebSocketOpen{canHibernate: ${canHibernate}}`;
286
+ }
287
+ case "ToServerWebSocketMessage": {
288
+ const { data, binary } = kind.val;
289
+ return `ToServerWebSocketMessage{data: ${stringifyArrayBuffer(data)}, binary: ${binary}}`;
290
+ }
291
+ case "ToServerWebSocketMessageAck": {
292
+ const { index } = kind.val;
293
+ return `ToServerWebSocketMessageAck{index: ${index}}`;
294
+ }
295
+ case "ToServerWebSocketClose": {
296
+ const { code, reason, hibernate } = kind.val;
297
+ const codeStr = code === null ? "null" : code.toString();
298
+ const reasonStr = reason === null ? "null" : `"${reason}"`;
299
+ return `ToServerWebSocketClose{code: ${codeStr}, reason: ${reasonStr}, hibernate: ${hibernate}}`;
300
+ }
301
+ }
302
+ }
303
+ function stringifyToClientTunnelMessageKind(kind) {
304
+ switch (kind.tag) {
305
+ case "ToClientRequestStart": {
306
+ const { actorId, method, path, headers, body, stream } = kind.val;
307
+ const bodyStr = body === null ? "null" : stringifyArrayBuffer(body);
308
+ return `ToClientRequestStart{actorId: "${actorId}", method: "${method}", path: "${path}", headers: ${stringifyMap(headers)}, body: ${bodyStr}, stream: ${stream}}`;
309
+ }
310
+ case "ToClientRequestChunk": {
311
+ const { body, finish } = kind.val;
312
+ return `ToClientRequestChunk{body: ${stringifyArrayBuffer(body)}, finish: ${finish}}`;
313
+ }
314
+ case "ToClientRequestAbort":
315
+ return "ToClientRequestAbort";
316
+ case "ToClientWebSocketOpen": {
317
+ const { actorId, path, headers } = kind.val;
318
+ return `ToClientWebSocketOpen{actorId: "${actorId}", path: "${path}", headers: ${stringifyMap(headers)}}`;
319
+ }
320
+ case "ToClientWebSocketMessage": {
321
+ const { data, binary } = kind.val;
322
+ return `ToClientWebSocketMessage{data: ${stringifyArrayBuffer(data)}, binary: ${binary}}`;
323
+ }
324
+ case "ToClientWebSocketClose": {
325
+ const { code, reason } = kind.val;
326
+ const codeStr = code === null ? "null" : code.toString();
327
+ const reasonStr = reason === null ? "null" : `"${reason}"`;
328
+ return `ToClientWebSocketClose{code: ${codeStr}, reason: ${reasonStr}}`;
329
+ }
330
+ }
331
+ }
332
+ function stringifyCommand(command) {
333
+ switch (command.tag) {
334
+ case "CommandStartActor": {
335
+ const { config, hibernatingRequests } = command.val;
336
+ const keyStr = config.key === null ? "null" : `"${config.key}"`;
337
+ const inputStr = config.input === null ? "null" : stringifyArrayBuffer(config.input);
338
+ const hibernatingRequestsStr = hibernatingRequests.length > 0 ? `[${hibernatingRequests.map((hr) => `{gatewayId: ${idToStr(hr.gatewayId)}, requestId: ${idToStr(hr.requestId)}}`).join(", ")}]` : "[]";
339
+ return `CommandStartActor{config: {name: "${config.name}", key: ${keyStr}, createTs: ${stringifyBigInt(config.createTs)}, input: ${inputStr}}, hibernatingRequests: ${hibernatingRequestsStr}}`;
340
+ }
341
+ case "CommandStopActor": {
342
+ return `CommandStopActor`;
343
+ }
344
+ }
345
+ }
346
+ function stringifyCommandWrapper(wrapper) {
347
+ return `CommandWrapper{actorId: "${wrapper.checkpoint.actorId}", generation: "${wrapper.checkpoint.generation}", index: ${stringifyBigInt(wrapper.checkpoint.index)}, inner: ${stringifyCommand(wrapper.inner)}}`;
348
+ }
349
+ function stringifyEvent(event) {
350
+ switch (event.tag) {
351
+ case "EventActorIntent": {
352
+ const { intent } = event.val;
353
+ const intentStr = intent.tag === "ActorIntentSleep" ? "Sleep" : intent.tag === "ActorIntentStop" ? "Stop" : "Unknown";
354
+ return `EventActorIntent{intent: ${intentStr}}`;
355
+ }
356
+ case "EventActorStateUpdate": {
357
+ const { state } = event.val;
358
+ let stateStr;
359
+ if (state.tag === "ActorStateRunning") {
360
+ stateStr = "Running";
361
+ } else if (state.tag === "ActorStateStopped") {
362
+ const { code, message } = state.val;
363
+ const messageStr = message === null ? "null" : `"${message}"`;
364
+ stateStr = `Stopped{code: ${code}, message: ${messageStr}}`;
365
+ } else {
366
+ stateStr = "Unknown";
367
+ }
368
+ return `EventActorStateUpdate{state: ${stateStr}}`;
369
+ }
370
+ case "EventActorSetAlarm": {
371
+ const { alarmTs } = event.val;
372
+ const alarmTsStr = alarmTs === null ? "null" : stringifyBigInt(alarmTs);
373
+ return `EventActorSetAlarm{alarmTs: ${alarmTsStr}}`;
374
+ }
375
+ }
376
+ }
377
+ function stringifyEventWrapper(wrapper) {
378
+ return `EventWrapper{actorId: ${wrapper.checkpoint.actorId}, generation: "${wrapper.checkpoint.generation}", index: ${stringifyBigInt(wrapper.checkpoint.index)}, inner: ${stringifyEvent(wrapper.inner)}}`;
379
+ }
380
+ function stringifyToServer(message) {
381
+ switch (message.tag) {
382
+ case "ToServerInit": {
383
+ const {
384
+ name,
385
+ version,
386
+ totalSlots,
387
+ prepopulateActorNames,
388
+ metadata
389
+ } = message.val;
390
+ const prepopulateActorNamesStr = prepopulateActorNames === null ? "null" : `Map(${prepopulateActorNames.size})`;
391
+ const metadataStr = metadata === null ? "null" : `"${metadata}"`;
392
+ return `ToServerInit{name: "${name}", version: ${version}, totalSlots: ${totalSlots}, prepopulateActorNames: ${prepopulateActorNamesStr}, metadata: ${metadataStr}}`;
393
+ }
394
+ case "ToServerEvents": {
395
+ const events = message.val;
396
+ return `ToServerEvents{count: ${events.length}, events: [${events.map((e) => stringifyEventWrapper(e)).join(", ")}]}`;
397
+ }
398
+ case "ToServerAckCommands": {
399
+ const { lastCommandCheckpoints } = message.val;
400
+ const checkpointsStr = lastCommandCheckpoints.length > 0 ? `[${lastCommandCheckpoints.map((cp) => `{actorId: "${cp.actorId}", index: ${stringifyBigInt(cp.index)}}`).join(", ")}]` : "[]";
401
+ return `ToServerAckCommands{lastCommandCheckpoints: ${checkpointsStr}}`;
402
+ }
403
+ case "ToServerStopping":
404
+ return "ToServerStopping";
405
+ case "ToServerPong": {
406
+ const { ts } = message.val;
407
+ return `ToServerPong{ts: ${stringifyBigInt(ts)}}`;
408
+ }
409
+ case "ToServerKvRequest": {
410
+ const { actorId, requestId, data } = message.val;
411
+ const dataStr = stringifyKvRequestData(data);
412
+ return `ToServerKvRequest{actorId: "${actorId}", requestId: ${requestId}, data: ${dataStr}}`;
413
+ }
414
+ case "ToServerTunnelMessage": {
415
+ const { messageId, messageKind } = message.val;
416
+ return `ToServerTunnelMessage{messageId: ${stringifyMessageId(messageId)}, messageKind: ${stringifyToServerTunnelMessageKind(messageKind)}}`;
417
+ }
418
+ }
419
+ }
420
+ function stringifyToClient(message) {
421
+ switch (message.tag) {
422
+ case "ToClientInit": {
423
+ const { runnerId, metadata } = message.val;
424
+ const metadataStr = `{runnerLostThreshold: ${stringifyBigInt(metadata.runnerLostThreshold)}}`;
425
+ return `ToClientInit{runnerId: "${runnerId}", metadata: ${metadataStr}}`;
426
+ }
427
+ case "ToClientPing": {
428
+ const { ts } = message.val;
429
+ return `ToClientPing{ts: ${stringifyBigInt(ts)}}`;
430
+ }
431
+ case "ToClientCommands": {
432
+ const commands = message.val;
433
+ return `ToClientCommands{count: ${commands.length}, commands: [${commands.map((c) => stringifyCommandWrapper(c)).join(", ")}]}`;
434
+ }
435
+ case "ToClientAckEvents": {
436
+ const { lastEventCheckpoints } = message.val;
437
+ const checkpointsStr = lastEventCheckpoints.length > 0 ? `[${lastEventCheckpoints.map((cp) => `{actorId: "${cp.actorId}", index: ${stringifyBigInt(cp.index)}}`).join(", ")}]` : "[]";
438
+ return `ToClientAckEvents{lastEventCheckpoints: ${checkpointsStr}}`;
439
+ }
440
+ case "ToClientKvResponse": {
441
+ const { requestId, data } = message.val;
442
+ const dataStr = stringifyKvResponseData(data);
443
+ return `ToClientKvResponse{requestId: ${requestId}, data: ${dataStr}}`;
444
+ }
445
+ case "ToClientTunnelMessage": {
446
+ const { messageId, messageKind } = message.val;
447
+ return `ToClientTunnelMessage{messageId: ${stringifyMessageId(messageId)}, messageKind: ${stringifyToClientTunnelMessageKind(messageKind)}}`;
448
+ }
449
+ }
450
+ }
451
+ function stringifyKvRequestData(data) {
452
+ switch (data.tag) {
453
+ case "KvGetRequest": {
454
+ const { keys } = data.val;
455
+ return `KvGetRequest{keys: ${keys.length}}`;
456
+ }
457
+ case "KvListRequest": {
458
+ const { query, reverse, limit } = data.val;
459
+ const reverseStr = reverse === null ? "null" : reverse.toString();
460
+ const limitStr = limit === null ? "null" : stringifyBigInt(limit);
461
+ return `KvListRequest{query: ${stringifyKvListQuery(query)}, reverse: ${reverseStr}, limit: ${limitStr}}`;
462
+ }
463
+ case "KvPutRequest": {
464
+ const { keys, values } = data.val;
465
+ return `KvPutRequest{keys: ${keys.length}, values: ${values.length}}`;
466
+ }
467
+ case "KvDeleteRequest": {
468
+ const { keys } = data.val;
469
+ return `KvDeleteRequest{keys: ${keys.length}}`;
470
+ }
471
+ case "KvDeleteRangeRequest": {
472
+ const { start, end } = data.val;
473
+ return `KvDeleteRangeRequest{start: ${stringifyArrayBuffer(start)}, end: ${stringifyArrayBuffer(end)}}`;
474
+ }
475
+ case "KvDropRequest":
476
+ return "KvDropRequest";
477
+ }
478
+ }
479
+ function stringifyKvListQuery(query) {
480
+ switch (query.tag) {
481
+ case "KvListAllQuery":
482
+ return "KvListAllQuery";
483
+ case "KvListRangeQuery": {
484
+ const { start, end, exclusive } = query.val;
485
+ return `KvListRangeQuery{start: ${stringifyArrayBuffer(start)}, end: ${stringifyArrayBuffer(end)}, exclusive: ${exclusive}}`;
486
+ }
487
+ case "KvListPrefixQuery": {
488
+ const { key } = query.val;
489
+ return `KvListPrefixQuery{key: ${stringifyArrayBuffer(key)}}`;
490
+ }
491
+ }
492
+ }
493
+ function stringifyKvResponseData(data) {
494
+ switch (data.tag) {
495
+ case "KvErrorResponse": {
496
+ const { message } = data.val;
497
+ return `KvErrorResponse{message: "${message}"}`;
498
+ }
499
+ case "KvGetResponse": {
500
+ const { keys, values, metadata } = data.val;
501
+ return `KvGetResponse{keys: ${keys.length}, values: ${values.length}, metadata: ${metadata.length}}`;
502
+ }
503
+ case "KvListResponse": {
504
+ const { keys, values, metadata } = data.val;
505
+ return `KvListResponse{keys: ${keys.length}, values: ${values.length}, metadata: ${metadata.length}}`;
506
+ }
507
+ case "KvPutResponse":
508
+ return "KvPutResponse";
509
+ case "KvDeleteResponse":
510
+ return "KvDeleteResponse";
511
+ case "KvDropResponse":
512
+ return "KvDropResponse";
513
+ }
514
+ }
515
+
516
+ // src/websocket-tunnel-adapter.ts
517
+
518
+
519
+ var _virtualwebsocket = require('@rivetkit/virtual-websocket');
520
+ var HIBERNATABLE_SYMBOL = /* @__PURE__ */ Symbol("hibernatable");
521
+ var WebSocketTunnelAdapter = class {
522
+ constructor(tunnel, actorId, requestId, serverMessageIndex, hibernatable, isRestoringHibernatable, request, sendCallback, closeCallback) {
523
+ this.request = request;
524
+ var _a;
525
+ this.#tunnel = tunnel;
526
+ this.#actorId = actorId;
527
+ this.#requestId = requestId;
528
+ this.#hibernatable = hibernatable;
529
+ this.#serverMessageIndex = serverMessageIndex;
530
+ this.#sendCallback = sendCallback;
531
+ this.#closeCallback = closeCallback;
532
+ this.#ws = new (0, _virtualwebsocket.VirtualWebSocket)({
533
+ getReadyState: () => this.#readyState,
534
+ onSend: (data) => this.#handleSend(data),
535
+ onClose: (code, reason) => this.#close(code, reason, true),
536
+ onTerminate: () => this.#terminate()
537
+ });
538
+ if (isRestoringHibernatable) {
539
+ (_a = this.#log) == null ? void 0 : _a.debug({
540
+ msg: "setting WebSocket to OPEN state for restored connection",
541
+ actorId: this.#actorId,
542
+ requestId: this.#requestId
543
+ });
544
+ this.#readyState = 1;
545
+ }
546
+ }
547
+ #readyState = 0;
548
+ #binaryType = "nodebuffer";
549
+ #ws;
550
+ #tunnel;
551
+ #actorId;
552
+ #requestId;
553
+ #hibernatable;
554
+ #serverMessageIndex;
555
+ #sendCallback;
556
+ #closeCallback;
557
+ get [HIBERNATABLE_SYMBOL]() {
558
+ return this.#hibernatable;
559
+ }
560
+ get #log() {
561
+ return this.#tunnel.log;
562
+ }
563
+ get websocket() {
564
+ return this.#ws;
565
+ }
566
+ #handleSend(data) {
567
+ let isBinary = false;
568
+ let messageData;
569
+ if (typeof data === "string") {
570
+ const encoder = new TextEncoder();
571
+ if (encoder.encode(data).byteLength > MAX_PAYLOAD_SIZE) {
572
+ throw new Error("WebSocket message too large");
573
+ }
574
+ messageData = data;
575
+ } else if (data instanceof ArrayBuffer) {
576
+ if (data.byteLength > MAX_PAYLOAD_SIZE)
577
+ throw new Error("WebSocket message too large");
578
+ isBinary = true;
579
+ messageData = data;
580
+ } else if (ArrayBuffer.isView(data)) {
581
+ if (data.byteLength > MAX_PAYLOAD_SIZE)
582
+ throw new Error("WebSocket message too large");
583
+ isBinary = true;
584
+ const view = data;
585
+ const buffer = view.buffer instanceof SharedArrayBuffer ? new Uint8Array(
586
+ view.buffer,
587
+ view.byteOffset,
588
+ view.byteLength
589
+ ).slice().buffer : view.buffer.slice(
590
+ view.byteOffset,
591
+ view.byteOffset + view.byteLength
592
+ );
593
+ messageData = buffer;
594
+ } else {
595
+ throw new Error("Unsupported data type");
596
+ }
597
+ this.#sendCallback(messageData, isBinary);
598
+ }
599
+ // Called by Tunnel when WebSocket is opened
600
+ _handleOpen(requestId) {
601
+ if (this.#readyState !== 0) return;
602
+ this.#readyState = 1;
603
+ this.#ws.dispatchEvent({
604
+ type: "open",
605
+ rivetRequestId: requestId,
606
+ target: this.#ws
607
+ });
608
+ }
609
+ // Called by Tunnel when message is received
610
+ _handleMessage(requestId, data, serverMessageIndex, isBinary) {
611
+ var _a, _b, _c;
612
+ if (this.#readyState !== 1) {
613
+ (_a = this.#log) == null ? void 0 : _a.warn({
614
+ msg: "WebSocket message ignored - not in OPEN state",
615
+ requestId: this.#requestId,
616
+ actorId: this.#actorId,
617
+ currentReadyState: this.#readyState
618
+ });
619
+ return true;
620
+ }
621
+ if (this.#hibernatable) {
622
+ const previousIndex = this.#serverMessageIndex;
623
+ if (wrappingLteU16(serverMessageIndex, previousIndex)) {
624
+ (_b = this.#log) == null ? void 0 : _b.info({
625
+ msg: "received duplicate hibernating websocket message",
626
+ requestId,
627
+ actorId: this.#actorId,
628
+ previousIndex,
629
+ receivedIndex: serverMessageIndex
630
+ });
631
+ return true;
632
+ }
633
+ const expectedIndex = wrappingAddU16(previousIndex, 1);
634
+ if (serverMessageIndex !== expectedIndex) {
635
+ const closeReason = "ws.message_index_skip";
636
+ (_c = this.#log) == null ? void 0 : _c.warn({
637
+ msg: "hibernatable websocket message index out of sequence, closing connection",
638
+ requestId,
639
+ actorId: this.#actorId,
640
+ previousIndex,
641
+ expectedIndex,
642
+ receivedIndex: serverMessageIndex,
643
+ closeReason,
644
+ gap: wrappingSubU16(
645
+ wrappingSubU16(serverMessageIndex, previousIndex),
646
+ 1
647
+ )
648
+ });
649
+ this.#close(1008, closeReason, true);
650
+ return true;
651
+ }
652
+ this.#serverMessageIndex = serverMessageIndex;
653
+ }
654
+ let messageData = data;
655
+ if (isBinary && data instanceof Uint8Array) {
656
+ if (this.#binaryType === "nodebuffer") {
657
+ messageData = Buffer.from(data);
658
+ } else if (this.#binaryType === "arraybuffer") {
659
+ messageData = data.buffer.slice(
660
+ data.byteOffset,
661
+ data.byteOffset + data.byteLength
662
+ );
663
+ }
664
+ }
665
+ this.#ws.dispatchEvent({
666
+ type: "message",
667
+ data: messageData,
668
+ rivetRequestId: requestId,
669
+ rivetMessageIndex: serverMessageIndex,
670
+ target: this.#ws
671
+ });
672
+ return false;
673
+ }
674
+ // Called by Tunnel when close is received
675
+ _handleClose(_requestId, code, reason) {
676
+ this.#close(code, reason, true);
677
+ }
678
+ // Close without sending close message to tunnel
679
+ _closeWithoutCallback(code, reason) {
680
+ this.#close(code, reason, false);
681
+ }
682
+ // Public close method (used by tunnel.ts for stale websocket cleanup)
683
+ close(code, reason) {
684
+ this.#close(code, reason, true);
685
+ }
686
+ #close(code, reason, sendCallback) {
687
+ if (this.#readyState >= 2) return;
688
+ this.#readyState = 2;
689
+ if (sendCallback) this.#closeCallback(code, reason);
690
+ this.#readyState = 3;
691
+ this.#ws.triggerClose(_nullishCoalesce(code, () => ( 1e3)), _nullishCoalesce(reason, () => ( "")));
692
+ }
693
+ #terminate() {
694
+ this.#readyState = 3;
695
+ this.#closeCallback(1006, "Abnormal Closure");
696
+ this.#ws.triggerClose(1006, "Abnormal Closure", false);
697
+ }
698
+ };
699
+
700
+ // src/tunnel.ts
701
+ var Tunnel = class {
702
+ #runner;
703
+ /** Maps request IDs to actor IDs for lookup */
704
+ #requestToActor = [];
705
+ /** Buffer for messages when not connected */
706
+ #bufferedMessages = [];
707
+ get log() {
708
+ return this.#runner.log;
709
+ }
710
+ constructor(runner) {
711
+ this.#runner = runner;
712
+ }
713
+ start() {
714
+ }
715
+ resendBufferedEvents() {
716
+ var _a;
717
+ if (this.#bufferedMessages.length === 0) {
718
+ return;
719
+ }
720
+ (_a = this.log) == null ? void 0 : _a.info({
721
+ msg: "resending buffered tunnel messages",
722
+ count: this.#bufferedMessages.length
723
+ });
724
+ const messages = this.#bufferedMessages;
725
+ this.#bufferedMessages = [];
726
+ for (const { gatewayId, requestId, messageKind } of messages) {
727
+ this.#sendMessage(gatewayId, requestId, messageKind);
728
+ }
729
+ }
730
+ shutdown() {
731
+ for (const [_actorId, actor] of this.#runner.actors) {
732
+ for (const entry of actor.pendingRequests) {
733
+ entry.request.reject(new RunnerShutdownError());
734
+ }
735
+ actor.pendingRequests = [];
736
+ for (const entry of actor.webSockets) {
737
+ if (!entry.ws[HIBERNATABLE_SYMBOL]) {
738
+ entry.ws._closeWithoutCallback(1e3, "ws.tunnel_shutdown");
739
+ }
740
+ }
741
+ actor.webSockets = [];
742
+ }
743
+ this.#requestToActor = [];
744
+ }
745
+ async restoreHibernatingRequests(actorId, metaEntries) {
746
+ var _a, _b, _c, _d;
747
+ const actor = this.#runner.getActor(actorId);
748
+ if (!actor) {
749
+ throw new Error(
750
+ `Actor ${actorId} not found for restoring hibernating requests`
751
+ );
752
+ }
753
+ if (actor.hibernationRestored) {
754
+ throw new Error(
755
+ `Actor ${actorId} already restored hibernating requests`
756
+ );
757
+ }
758
+ (_a = this.log) == null ? void 0 : _a.debug({
759
+ msg: "restoring hibernating requests",
760
+ actorId,
761
+ requests: actor.hibernatingRequests.length
762
+ });
763
+ const backgroundOperations = [];
764
+ let connectedButNotLoadedCount = 0;
765
+ let restoredCount = 0;
766
+ for (const { gatewayId, requestId } of actor.hibernatingRequests) {
767
+ const requestIdStr = idToStr(requestId);
768
+ const meta = metaEntries.find(
769
+ (entry) => arraysEqual(entry.gatewayId, gatewayId) && arraysEqual(entry.requestId, requestId)
770
+ );
771
+ if (!meta) {
772
+ (_b = this.log) == null ? void 0 : _b.warn({
773
+ msg: "closing websocket that is not persisted",
774
+ requestId: requestIdStr
775
+ });
776
+ this.#sendMessage(gatewayId, requestId, {
777
+ tag: "ToServerWebSocketClose",
778
+ val: {
779
+ code: 1e3,
780
+ reason: "ws.meta_not_found_during_restore",
781
+ hibernate: false
782
+ }
783
+ });
784
+ connectedButNotLoadedCount++;
785
+ } else {
786
+ const request = buildRequestForWebSocket(
787
+ meta.path,
788
+ meta.headers
789
+ );
790
+ const restoreOperation = this.#createWebSocket(
791
+ actorId,
792
+ gatewayId,
793
+ requestId,
794
+ requestIdStr,
795
+ meta.serverMessageIndex,
796
+ true,
797
+ true,
798
+ request,
799
+ meta.path,
800
+ meta.headers,
801
+ false
802
+ ).then(() => {
803
+ var _a2;
804
+ const actor2 = this.#runner.getActor(actorId);
805
+ if (actor2) {
806
+ actor2.createPendingRequest(
807
+ gatewayId,
808
+ requestId,
809
+ meta.clientMessageIndex
810
+ );
811
+ }
812
+ (_a2 = this.log) == null ? void 0 : _a2.info({
813
+ msg: "connection successfully restored",
814
+ actorId,
815
+ requestId: requestIdStr
816
+ });
817
+ }).catch((err) => {
818
+ var _a2;
819
+ (_a2 = this.log) == null ? void 0 : _a2.error({
820
+ msg: "error creating websocket during restore",
821
+ requestId: requestIdStr,
822
+ error: stringifyError(err)
823
+ });
824
+ this.#sendMessage(gatewayId, requestId, {
825
+ tag: "ToServerWebSocketClose",
826
+ val: {
827
+ code: 1011,
828
+ reason: "ws.restore_error",
829
+ hibernate: false
830
+ }
831
+ });
832
+ });
833
+ backgroundOperations.push(restoreOperation);
834
+ restoredCount++;
835
+ }
836
+ }
837
+ let loadedButNotConnectedCount = 0;
838
+ for (const meta of metaEntries) {
839
+ const requestIdStr = idToStr(meta.requestId);
840
+ const isConnected = actor.hibernatingRequests.some(
841
+ (req) => arraysEqual(req.gatewayId, meta.gatewayId) && arraysEqual(req.requestId, meta.requestId)
842
+ );
843
+ if (!isConnected) {
844
+ (_c = this.log) == null ? void 0 : _c.warn({
845
+ msg: "removing stale persisted websocket",
846
+ requestId: requestIdStr
847
+ });
848
+ const request = buildRequestForWebSocket(
849
+ meta.path,
850
+ meta.headers
851
+ );
852
+ const cleanupOperation = this.#createWebSocket(
853
+ actorId,
854
+ meta.gatewayId,
855
+ meta.requestId,
856
+ requestIdStr,
857
+ meta.serverMessageIndex,
858
+ true,
859
+ true,
860
+ request,
861
+ meta.path,
862
+ meta.headers,
863
+ true
864
+ ).then((adapter) => {
865
+ adapter.close(1e3, "ws.stale_metadata");
866
+ }).catch((err) => {
867
+ var _a2;
868
+ (_a2 = this.log) == null ? void 0 : _a2.error({
869
+ msg: "error creating stale websocket during restore",
870
+ requestId: requestIdStr,
871
+ error: stringifyError(err)
872
+ });
873
+ });
874
+ backgroundOperations.push(cleanupOperation);
875
+ loadedButNotConnectedCount++;
876
+ }
877
+ }
878
+ await Promise.allSettled(backgroundOperations);
879
+ actor.hibernationRestored = true;
880
+ (_d = this.log) == null ? void 0 : _d.info({
881
+ msg: "restored hibernatable websockets",
882
+ actorId,
883
+ restoredCount,
884
+ connectedButNotLoadedCount,
885
+ loadedButNotConnectedCount
886
+ });
887
+ }
888
+ /**
889
+ * Called from WebSocketOpen message and when restoring hibernatable WebSockets.
890
+ *
891
+ * engineAlreadyClosed will be true if this is only being called to trigger
892
+ * the close callback and not to send a close message to the server. This
893
+ * is used specifically to clean up zombie WebSocket connections.
894
+ */
895
+ async #createWebSocket(actorId, gatewayId, requestId, requestIdStr, serverMessageIndex, isHibernatable, isRestoringHibernatable, request, path, headers, engineAlreadyClosed) {
896
+ var _a;
897
+ (_a = this.log) == null ? void 0 : _a.debug({
898
+ msg: "createWebSocket creating adapter",
899
+ actorId,
900
+ requestIdStr,
901
+ isHibernatable,
902
+ path
903
+ });
904
+ const adapter = new WebSocketTunnelAdapter(
905
+ this,
906
+ actorId,
907
+ requestIdStr,
908
+ serverMessageIndex,
909
+ isHibernatable,
910
+ isRestoringHibernatable,
911
+ request,
912
+ (data, isBinary) => {
913
+ const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data).buffer : data;
914
+ this.#sendMessage(gatewayId, requestId, {
915
+ tag: "ToServerWebSocketMessage",
916
+ val: {
917
+ data: dataBuffer,
918
+ binary: isBinary
919
+ }
920
+ });
921
+ },
922
+ (code, reason) => {
923
+ if (!engineAlreadyClosed) {
924
+ this.#sendMessage(gatewayId, requestId, {
925
+ tag: "ToServerWebSocketClose",
926
+ val: {
927
+ code: code || null,
928
+ reason: reason || null,
929
+ hibernate: false
930
+ }
931
+ });
932
+ }
933
+ const actor2 = this.#runner.getActor(actorId);
934
+ if (actor2) {
935
+ actor2.deleteWebSocket(gatewayId, requestId);
936
+ actor2.deletePendingRequest(gatewayId, requestId);
937
+ }
938
+ this.#removeRequestToActor(gatewayId, requestId);
939
+ }
940
+ );
941
+ const actor = this.#runner.getActor(actorId);
942
+ if (!actor) {
943
+ throw new Error(`Actor ${actorId} not found`);
944
+ }
945
+ actor.setWebSocket(gatewayId, requestId, adapter);
946
+ this.addRequestToActor(gatewayId, requestId, actorId);
947
+ await this.#runner.config.websocket(
948
+ this.#runner,
949
+ actorId,
950
+ adapter.websocket,
951
+ gatewayId,
952
+ requestId,
953
+ request,
954
+ path,
955
+ headers,
956
+ isHibernatable,
957
+ isRestoringHibernatable
958
+ );
959
+ return adapter;
960
+ }
961
+ addRequestToActor(gatewayId, requestId, actorId) {
962
+ this.#requestToActor.push({ gatewayId, requestId, actorId });
963
+ }
964
+ #removeRequestToActor(gatewayId, requestId) {
965
+ const index = this.#requestToActor.findIndex(
966
+ (entry) => arraysEqual(entry.gatewayId, gatewayId) && arraysEqual(entry.requestId, requestId)
967
+ );
968
+ if (index !== -1) {
969
+ this.#requestToActor.splice(index, 1);
970
+ }
971
+ }
972
+ getRequestActor(gatewayId, requestId) {
973
+ var _a, _b;
974
+ const entry = this.#requestToActor.find(
975
+ (entry2) => arraysEqual(entry2.gatewayId, gatewayId) && arraysEqual(entry2.requestId, requestId)
976
+ );
977
+ if (!entry) {
978
+ (_a = this.log) == null ? void 0 : _a.warn({
979
+ msg: "missing requestToActor entry",
980
+ requestId: idToStr(requestId)
981
+ });
982
+ return void 0;
983
+ }
984
+ const actor = this.#runner.getActor(entry.actorId);
985
+ if (!actor) {
986
+ (_b = this.log) == null ? void 0 : _b.warn({
987
+ msg: "missing actor for requestToActor lookup",
988
+ requestId: idToStr(requestId),
989
+ actorId: entry.actorId
990
+ });
991
+ return void 0;
992
+ }
993
+ return actor;
994
+ }
995
+ async getAndWaitForRequestActor(gatewayId, requestId) {
996
+ const actor = this.getRequestActor(gatewayId, requestId);
997
+ if (!actor) return;
998
+ await actor.actorStartPromise.promise;
999
+ return actor;
1000
+ }
1001
+ #sendMessage(gatewayId, requestId, messageKind) {
1002
+ var _a, _b, _c, _d;
1003
+ if (!this.#runner.getPegboardWebSocketIfReady()) {
1004
+ (_a = this.log) == null ? void 0 : _a.debug({
1005
+ msg: "buffering tunnel message, socket not connected to engine",
1006
+ requestId: idToStr(requestId),
1007
+ message: stringifyToServerTunnelMessageKind(messageKind)
1008
+ });
1009
+ this.#bufferedMessages.push({ gatewayId, requestId, messageKind });
1010
+ return;
1011
+ }
1012
+ const gatewayIdStr = idToStr(gatewayId);
1013
+ const requestIdStr = idToStr(requestId);
1014
+ const actor = this.getRequestActor(gatewayId, requestId);
1015
+ if (!actor) {
1016
+ (_b = this.log) == null ? void 0 : _b.warn({
1017
+ msg: "cannot send tunnel message, actor not found",
1018
+ gatewayId: gatewayIdStr,
1019
+ requestId: requestIdStr
1020
+ });
1021
+ return;
1022
+ }
1023
+ let clientMessageIndex;
1024
+ const pending = actor.getPendingRequest(gatewayId, requestId);
1025
+ if (pending) {
1026
+ clientMessageIndex = pending.clientMessageIndex;
1027
+ pending.clientMessageIndex++;
1028
+ } else {
1029
+ (_c = this.log) == null ? void 0 : _c.warn({
1030
+ msg: "missing pending request for send message, defaulting to message index 0",
1031
+ gatewayId: gatewayIdStr,
1032
+ requestId: requestIdStr
1033
+ });
1034
+ clientMessageIndex = 0;
1035
+ }
1036
+ const messageId = {
1037
+ gatewayId,
1038
+ requestId,
1039
+ messageIndex: clientMessageIndex
1040
+ };
1041
+ const messageIdStr = `${idToStr(messageId.gatewayId)}-${idToStr(messageId.requestId)}-${messageId.messageIndex}`;
1042
+ (_d = this.log) == null ? void 0 : _d.debug({
1043
+ msg: "sending tunnel msg",
1044
+ messageId: messageIdStr,
1045
+ gatewayId: gatewayIdStr,
1046
+ requestId: requestIdStr,
1047
+ messageIndex: clientMessageIndex,
1048
+ message: stringifyToServerTunnelMessageKind(messageKind)
1049
+ });
1050
+ const message = {
1051
+ tag: "ToServerTunnelMessage",
1052
+ val: {
1053
+ messageId,
1054
+ messageKind
1055
+ }
1056
+ };
1057
+ this.#runner.__sendToServer(message);
1058
+ }
1059
+ closeActiveRequests(actor) {
1060
+ const actorId = actor.actorId;
1061
+ for (const entry of actor.pendingRequests) {
1062
+ entry.request.reject(new Error(`Actor ${actorId} stopped`));
1063
+ if (entry.gatewayId && entry.requestId) {
1064
+ this.#removeRequestToActor(entry.gatewayId, entry.requestId);
1065
+ }
1066
+ }
1067
+ for (const entry of actor.webSockets) {
1068
+ const isHibernatable = entry.ws[HIBERNATABLE_SYMBOL];
1069
+ if (!isHibernatable) {
1070
+ entry.ws._closeWithoutCallback(1e3, "actor.stopped");
1071
+ }
1072
+ }
1073
+ }
1074
+ async #fetch(actorId, gatewayId, requestId, request) {
1075
+ var _a;
1076
+ if (!this.#runner.hasActor(actorId)) {
1077
+ (_a = this.log) == null ? void 0 : _a.warn({
1078
+ msg: "ignoring request for unknown actor",
1079
+ actorId
1080
+ });
1081
+ return new Response("Actor not found", {
1082
+ status: 503,
1083
+ headers: { "x-rivet-error": "runner.actor_not_found" }
1084
+ });
1085
+ }
1086
+ const fetchHandler = this.#runner.config.fetch(
1087
+ this.#runner,
1088
+ actorId,
1089
+ gatewayId,
1090
+ requestId,
1091
+ request
1092
+ );
1093
+ if (!fetchHandler) {
1094
+ return new Response("Not Implemented", { status: 501 });
1095
+ }
1096
+ return fetchHandler;
1097
+ }
1098
+ async handleTunnelMessage(message) {
1099
+ var _a;
1100
+ const { gatewayId, requestId, messageIndex } = message.messageId;
1101
+ const gatewayIdStr = idToStr(gatewayId);
1102
+ const requestIdStr = idToStr(requestId);
1103
+ (_a = this.log) == null ? void 0 : _a.debug({
1104
+ msg: "receive tunnel msg",
1105
+ gatewayId: gatewayIdStr,
1106
+ requestId: requestIdStr,
1107
+ messageIndex: message.messageId.messageIndex,
1108
+ message: stringifyToClientTunnelMessageKind(message.messageKind)
1109
+ });
1110
+ switch (message.messageKind.tag) {
1111
+ case "ToClientRequestStart":
1112
+ await this.#handleRequestStart(
1113
+ gatewayId,
1114
+ requestId,
1115
+ message.messageKind.val
1116
+ );
1117
+ break;
1118
+ case "ToClientRequestChunk":
1119
+ await this.#handleRequestChunk(
1120
+ gatewayId,
1121
+ requestId,
1122
+ message.messageKind.val
1123
+ );
1124
+ break;
1125
+ case "ToClientRequestAbort":
1126
+ await this.#handleRequestAbort(gatewayId, requestId);
1127
+ break;
1128
+ case "ToClientWebSocketOpen":
1129
+ await this.#handleWebSocketOpen(
1130
+ gatewayId,
1131
+ requestId,
1132
+ message.messageKind.val
1133
+ );
1134
+ break;
1135
+ case "ToClientWebSocketMessage": {
1136
+ await this.#handleWebSocketMessage(
1137
+ gatewayId,
1138
+ requestId,
1139
+ messageIndex,
1140
+ message.messageKind.val
1141
+ );
1142
+ break;
1143
+ }
1144
+ case "ToClientWebSocketClose":
1145
+ await this.#handleWebSocketClose(
1146
+ gatewayId,
1147
+ requestId,
1148
+ message.messageKind.val
1149
+ );
1150
+ break;
1151
+ default:
1152
+ unreachable(message.messageKind);
1153
+ }
1154
+ }
1155
+ async #handleRequestStart(gatewayId, requestId, req) {
1156
+ var _a, _b, _c;
1157
+ const requestIdStr = idToStr(requestId);
1158
+ const actor = await this.#runner.getAndWaitForActor(req.actorId);
1159
+ if (!actor) {
1160
+ (_a = this.log) == null ? void 0 : _a.warn({
1161
+ msg: "actor does not exist in handleRequestStart, request will leak",
1162
+ actorId: req.actorId,
1163
+ requestId: requestIdStr
1164
+ });
1165
+ return;
1166
+ }
1167
+ this.addRequestToActor(gatewayId, requestId, req.actorId);
1168
+ try {
1169
+ const headers = new Headers();
1170
+ for (const [key, value] of req.headers) {
1171
+ headers.append(key, value);
1172
+ }
1173
+ const request = new Request(`http://localhost${req.path}`, {
1174
+ method: req.method,
1175
+ headers,
1176
+ body: req.body ? new Uint8Array(req.body) : void 0
1177
+ });
1178
+ if (req.stream) {
1179
+ const stream = new ReadableStream({
1180
+ start: (controller) => {
1181
+ const existing = actor.getPendingRequest(
1182
+ gatewayId,
1183
+ requestId
1184
+ );
1185
+ if (existing) {
1186
+ existing.streamController = controller;
1187
+ existing.actorId = req.actorId;
1188
+ existing.gatewayId = gatewayId;
1189
+ existing.requestId = requestId;
1190
+ } else {
1191
+ actor.createPendingRequestWithStreamController(
1192
+ gatewayId,
1193
+ requestId,
1194
+ 0,
1195
+ controller
1196
+ );
1197
+ }
1198
+ }
1199
+ });
1200
+ const streamingRequest = new Request(request, {
1201
+ body: stream,
1202
+ duplex: "half"
1203
+ });
1204
+ const response = await this.#fetch(
1205
+ req.actorId,
1206
+ gatewayId,
1207
+ requestId,
1208
+ streamingRequest
1209
+ );
1210
+ await this.#sendResponse(
1211
+ actor.actorId,
1212
+ actor.generation,
1213
+ gatewayId,
1214
+ requestId,
1215
+ response
1216
+ );
1217
+ } else {
1218
+ actor.createPendingRequest(gatewayId, requestId, 0);
1219
+ const response = await this.#fetch(
1220
+ req.actorId,
1221
+ gatewayId,
1222
+ requestId,
1223
+ request
1224
+ );
1225
+ await this.#sendResponse(
1226
+ actor.actorId,
1227
+ actor.generation,
1228
+ gatewayId,
1229
+ requestId,
1230
+ response
1231
+ );
1232
+ }
1233
+ } catch (error) {
1234
+ if (error instanceof RunnerShutdownError) {
1235
+ (_b = this.log) == null ? void 0 : _b.debug({ msg: "catught runner shutdown error" });
1236
+ } else {
1237
+ (_c = this.log) == null ? void 0 : _c.error({ msg: "error handling request", error });
1238
+ this.#sendResponseError(
1239
+ actor.actorId,
1240
+ actor.generation,
1241
+ gatewayId,
1242
+ requestId,
1243
+ 500,
1244
+ "Internal Server Error"
1245
+ );
1246
+ }
1247
+ } finally {
1248
+ if (this.#runner.hasActor(req.actorId, actor.generation)) {
1249
+ actor.deletePendingRequest(gatewayId, requestId);
1250
+ this.#removeRequestToActor(gatewayId, requestId);
1251
+ }
1252
+ }
1253
+ }
1254
+ async #handleRequestChunk(gatewayId, requestId, chunk) {
1255
+ const actor = await this.getAndWaitForRequestActor(
1256
+ gatewayId,
1257
+ requestId
1258
+ );
1259
+ if (actor) {
1260
+ const pending = actor.getPendingRequest(gatewayId, requestId);
1261
+ if (pending == null ? void 0 : pending.streamController) {
1262
+ pending.streamController.enqueue(new Uint8Array(chunk.body));
1263
+ if (chunk.finish) {
1264
+ pending.streamController.close();
1265
+ actor.deletePendingRequest(gatewayId, requestId);
1266
+ this.#removeRequestToActor(gatewayId, requestId);
1267
+ }
1268
+ }
1269
+ }
1270
+ }
1271
+ async #handleRequestAbort(gatewayId, requestId) {
1272
+ const actor = await this.getAndWaitForRequestActor(
1273
+ gatewayId,
1274
+ requestId
1275
+ );
1276
+ if (actor) {
1277
+ const pending = actor.getPendingRequest(gatewayId, requestId);
1278
+ if (pending == null ? void 0 : pending.streamController) {
1279
+ pending.streamController.error(new Error("Request aborted"));
1280
+ }
1281
+ actor.deletePendingRequest(gatewayId, requestId);
1282
+ this.#removeRequestToActor(gatewayId, requestId);
1283
+ }
1284
+ }
1285
+ async #sendResponse(actorId, generation, gatewayId, requestId, response) {
1286
+ var _a;
1287
+ if (!this.#runner.hasActor(actorId, generation)) {
1288
+ (_a = this.log) == null ? void 0 : _a.warn({
1289
+ msg: "actor not loaded to send response, assuming gateway has closed request",
1290
+ actorId,
1291
+ generation,
1292
+ requestId
1293
+ });
1294
+ return;
1295
+ }
1296
+ const body = response.body ? await response.arrayBuffer() : null;
1297
+ if (body && body.byteLength > MAX_PAYLOAD_SIZE) {
1298
+ throw new Error("Response body too large");
1299
+ }
1300
+ const headers = /* @__PURE__ */ new Map();
1301
+ response.headers.forEach((value, key) => {
1302
+ headers.set(key, value);
1303
+ });
1304
+ if (body && !headers.has("content-length")) {
1305
+ headers.set("content-length", String(body.byteLength));
1306
+ }
1307
+ this.#sendMessage(gatewayId, requestId, {
1308
+ tag: "ToServerResponseStart",
1309
+ val: {
1310
+ status: response.status,
1311
+ headers,
1312
+ body: body || null,
1313
+ stream: false
1314
+ }
1315
+ });
1316
+ }
1317
+ #sendResponseError(actorId, generation, gatewayId, requestId, status, message) {
1318
+ var _a;
1319
+ if (!this.#runner.hasActor(actorId, generation)) {
1320
+ (_a = this.log) == null ? void 0 : _a.warn({
1321
+ msg: "actor not loaded to send response, assuming gateway has closed request",
1322
+ actorId,
1323
+ generation,
1324
+ requestId
1325
+ });
1326
+ return;
1327
+ }
1328
+ const headers = /* @__PURE__ */ new Map();
1329
+ headers.set("content-type", "text/plain");
1330
+ this.#sendMessage(gatewayId, requestId, {
1331
+ tag: "ToServerResponseStart",
1332
+ val: {
1333
+ status,
1334
+ headers,
1335
+ body: new TextEncoder().encode(message).buffer,
1336
+ stream: false
1337
+ }
1338
+ });
1339
+ }
1340
+ async #handleWebSocketOpen(gatewayId, requestId, open) {
1341
+ var _a, _b, _c;
1342
+ const requestIdStr = idToStr(requestId);
1343
+ const actor = await this.#runner.getAndWaitForActor(open.actorId);
1344
+ if (!actor) {
1345
+ (_a = this.log) == null ? void 0 : _a.warn({
1346
+ msg: "ignoring websocket for unknown actor",
1347
+ actorId: open.actorId
1348
+ });
1349
+ this.#sendMessage(gatewayId, requestId, {
1350
+ tag: "ToServerWebSocketClose",
1351
+ val: {
1352
+ code: 1011,
1353
+ reason: "Actor not found",
1354
+ hibernate: false
1355
+ }
1356
+ });
1357
+ return;
1358
+ }
1359
+ const existingAdapter = actor.getWebSocket(gatewayId, requestId);
1360
+ if (existingAdapter) {
1361
+ (_b = this.log) == null ? void 0 : _b.warn({
1362
+ msg: "closing existing websocket for duplicate open event for the same request id",
1363
+ requestId: requestIdStr
1364
+ });
1365
+ existingAdapter._closeWithoutCallback(1e3, "ws.duplicate_open");
1366
+ }
1367
+ try {
1368
+ const request = buildRequestForWebSocket(
1369
+ open.path,
1370
+ Object.fromEntries(open.headers)
1371
+ );
1372
+ const canHibernate = this.#runner.config.hibernatableWebSocket.canHibernate(
1373
+ actor.actorId,
1374
+ gatewayId,
1375
+ requestId,
1376
+ request
1377
+ );
1378
+ const adapter = await this.#createWebSocket(
1379
+ actor.actorId,
1380
+ gatewayId,
1381
+ requestId,
1382
+ requestIdStr,
1383
+ 0,
1384
+ canHibernate,
1385
+ false,
1386
+ request,
1387
+ open.path,
1388
+ Object.fromEntries(open.headers),
1389
+ false
1390
+ );
1391
+ actor.createPendingRequest(gatewayId, requestId, 0);
1392
+ this.#sendMessage(gatewayId, requestId, {
1393
+ tag: "ToServerWebSocketOpen",
1394
+ val: {
1395
+ canHibernate
1396
+ }
1397
+ });
1398
+ adapter._handleOpen(requestId);
1399
+ } catch (error) {
1400
+ (_c = this.log) == null ? void 0 : _c.error({ msg: "error handling websocket open", error });
1401
+ this.#sendMessage(gatewayId, requestId, {
1402
+ tag: "ToServerWebSocketClose",
1403
+ val: {
1404
+ code: 1011,
1405
+ reason: "Server Error",
1406
+ hibernate: false
1407
+ }
1408
+ });
1409
+ actor.deleteWebSocket(gatewayId, requestId);
1410
+ actor.deletePendingRequest(gatewayId, requestId);
1411
+ this.#removeRequestToActor(gatewayId, requestId);
1412
+ }
1413
+ }
1414
+ async #handleWebSocketMessage(gatewayId, requestId, serverMessageIndex, msg) {
1415
+ var _a;
1416
+ const actor = await this.getAndWaitForRequestActor(
1417
+ gatewayId,
1418
+ requestId
1419
+ );
1420
+ if (actor) {
1421
+ const adapter = actor.getWebSocket(gatewayId, requestId);
1422
+ if (adapter) {
1423
+ const data = msg.binary ? new Uint8Array(msg.data) : new TextDecoder().decode(new Uint8Array(msg.data));
1424
+ adapter._handleMessage(
1425
+ requestId,
1426
+ data,
1427
+ serverMessageIndex,
1428
+ msg.binary
1429
+ );
1430
+ return;
1431
+ }
1432
+ }
1433
+ (_a = this.log) == null ? void 0 : _a.warn({
1434
+ msg: "missing websocket for incoming websocket message, this may indicate the actor stopped before processing a message",
1435
+ requestId
1436
+ });
1437
+ }
1438
+ sendHibernatableWebSocketMessageAck(gatewayId, requestId, clientMessageIndex) {
1439
+ var _a, _b, _c;
1440
+ const requestIdStr = idToStr(requestId);
1441
+ (_a = this.log) == null ? void 0 : _a.debug({
1442
+ msg: "ack ws msg",
1443
+ requestId: requestIdStr,
1444
+ index: clientMessageIndex
1445
+ });
1446
+ if (clientMessageIndex < 0 || clientMessageIndex > 65535)
1447
+ throw new Error("Invalid websocket ack index");
1448
+ const actor = this.getRequestActor(gatewayId, requestId);
1449
+ if (!actor) {
1450
+ (_b = this.log) == null ? void 0 : _b.warn({
1451
+ msg: "cannot send websocket ack, actor not found",
1452
+ requestId: requestIdStr
1453
+ });
1454
+ return;
1455
+ }
1456
+ const pending = actor.getPendingRequest(gatewayId, requestId);
1457
+ if (!(pending == null ? void 0 : pending.gatewayId)) {
1458
+ (_c = this.log) == null ? void 0 : _c.warn({
1459
+ msg: "cannot send websocket ack, gatewayId not found in pending request",
1460
+ requestId: requestIdStr
1461
+ });
1462
+ return;
1463
+ }
1464
+ this.#sendMessage(pending.gatewayId, requestId, {
1465
+ tag: "ToServerWebSocketMessageAck",
1466
+ val: {
1467
+ index: clientMessageIndex
1468
+ }
1469
+ });
1470
+ }
1471
+ async #handleWebSocketClose(gatewayId, requestId, close) {
1472
+ const actor = await this.getAndWaitForRequestActor(
1473
+ gatewayId,
1474
+ requestId
1475
+ );
1476
+ if (actor) {
1477
+ const adapter = actor.getWebSocket(gatewayId, requestId);
1478
+ if (adapter) {
1479
+ adapter._handleClose(
1480
+ requestId,
1481
+ close.code || void 0,
1482
+ close.reason || void 0
1483
+ );
1484
+ actor.deleteWebSocket(gatewayId, requestId);
1485
+ actor.deletePendingRequest(gatewayId, requestId);
1486
+ this.#removeRequestToActor(gatewayId, requestId);
1487
+ }
1488
+ }
1489
+ }
1490
+ };
1491
+ function buildRequestForWebSocket(path, headers) {
1492
+ const fullHeaders = {
1493
+ ...headers,
1494
+ Upgrade: "websocket",
1495
+ Connection: "Upgrade"
1496
+ };
1497
+ if (!path.startsWith("/")) {
1498
+ throw new Error("Path must start with leading slash");
1499
+ }
1500
+ const request = new Request(`http://actor${path}`, {
1501
+ method: "GET",
1502
+ headers: fullHeaders
1503
+ });
1504
+ return request;
1505
+ }
1506
+
1507
+ // src/websocket.ts
1508
+ var webSocketPromise = null;
1509
+ async function importWebSocket() {
1510
+ if (webSocketPromise !== null) {
1511
+ return webSocketPromise;
1512
+ }
1513
+ webSocketPromise = (async () => {
1514
+ var _a, _b, _c;
1515
+ let _WebSocket;
1516
+ if (typeof WebSocket !== "undefined") {
1517
+ _WebSocket = WebSocket;
1518
+ (_a = logger()) == null ? void 0 : _a.debug({ msg: "using native websocket" });
1519
+ } else {
1520
+ try {
1521
+ const ws = await Promise.resolve().then(() => _interopRequireWildcard(require("ws")));
1522
+ _WebSocket = ws.default;
1523
+ (_b = logger()) == null ? void 0 : _b.debug({ msg: "using websocket from npm" });
1524
+ } catch (e3) {
1525
+ _WebSocket = class MockWebSocket {
1526
+ constructor() {
1527
+ throw new Error(
1528
+ 'WebSocket support requires installing the "ws" peer dependency.'
1529
+ );
1530
+ }
1531
+ };
1532
+ (_c = logger()) == null ? void 0 : _c.debug({ msg: "using mock websocket" });
1533
+ }
1534
+ }
1535
+ return _WebSocket;
1536
+ })();
1537
+ return webSocketPromise;
1538
+ }
1539
+
1540
+ // src/mod.ts
1541
+ var KV_EXPIRE = 3e4;
1542
+ var PROTOCOL_VERSION = 7;
1543
+ var EVENT_BACKLOG_WARN_THRESHOLD = 1e4;
1544
+ var SIGNAL_HANDLERS = [];
1545
+ var RunnerShutdownError = class extends Error {
1546
+ constructor() {
1547
+ super("Runner shut down");
1548
+ }
1549
+ };
1550
+ var Runner = class {
1551
+ #config;
1552
+ #runnerKey = _uuid.v4.call(void 0, );
1553
+ get config() {
1554
+ return this.#config;
1555
+ }
1556
+ #actors = /* @__PURE__ */ new Map();
1557
+ // WebSocket
1558
+ #pegboardWebSocket;
1559
+
1560
+ #started = false;
1561
+ #shutdown = false;
1562
+ #draining = false;
1563
+ #reconnectAttempt = 0;
1564
+ #reconnectTimeout;
1565
+ // Protocol metadata
1566
+ #protocolMetadata;
1567
+ // Runner lost threshold management
1568
+ #runnerLostTimeout;
1569
+ // Event storage for resending
1570
+ #eventBacklogWarned = false;
1571
+ // Command acknowledgment
1572
+ #ackInterval;
1573
+ // KV operations
1574
+ #nextKvRequestId = 0;
1575
+ #kvRequests = /* @__PURE__ */ new Map();
1576
+ #kvCleanupInterval;
1577
+ // Tunnel for HTTP/WebSocket forwarding
1578
+ #tunnel;
1579
+ // Cached child logger with runner-specific attributes
1580
+ #logCached;
1581
+ get log() {
1582
+ if (this.#logCached) return this.#logCached;
1583
+ const l = logger();
1584
+ if (l) {
1585
+ if (this.runnerId) {
1586
+ this.#logCached = l.child({
1587
+ runnerId: this.runnerId
1588
+ });
1589
+ return this.#logCached;
1590
+ } else {
1591
+ return l;
1592
+ }
1593
+ }
1594
+ return void 0;
1595
+ }
1596
+ constructor(config) {
1597
+ this.#config = config;
1598
+ if (this.#config.logger) setLogger(this.#config.logger);
1599
+ this.#kvCleanupInterval = setInterval(() => {
1600
+ var _a;
1601
+ try {
1602
+ this.#cleanupOldKvRequests();
1603
+ } catch (err) {
1604
+ (_a = this.log) == null ? void 0 : _a.error({
1605
+ msg: "error cleaning up kv requests",
1606
+ error: stringifyError(err)
1607
+ });
1608
+ }
1609
+ }, 15e3);
1610
+ }
1611
+ // MARK: Manage actors
1612
+ sleepActor(actorId, generation) {
1613
+ const actor = this.getActor(actorId, generation);
1614
+ if (!actor) return;
1615
+ this.#sendActorIntent(actorId, actor.generation, "sleep");
1616
+ }
1617
+ async stopActor(actorId, generation) {
1618
+ const actor = this.getActor(actorId, generation);
1619
+ if (!actor) return;
1620
+ this.#sendActorIntent(actorId, actor.generation, "stop");
1621
+ }
1622
+ /**
1623
+ * Like stopActor but marks the actor for graceful destruction.
1624
+ * This ensures the engine destroys the actor instead of sleeping it.
1625
+ *
1626
+ * NOTE: If a drain (GoingAway) occurs after this is called but before the
1627
+ * stop completes, the engine's going_away flag overrides graceful_exit and
1628
+ * the actor will sleep instead of being destroyed. The destroy intent is
1629
+ * lost in this race. This is acceptable since the actor will be rescheduled
1630
+ * elsewhere and can be destroyed on the next wake.
1631
+ */
1632
+ destroyActor(actorId, generation) {
1633
+ const actor = this.getActor(actorId, generation);
1634
+ if (!actor) return;
1635
+ actor.stopIntentSent = true;
1636
+ this.#sendActorIntent(actorId, actor.generation, "stop");
1637
+ }
1638
+ async forceStopActor(actorId, generation) {
1639
+ var _a, _b;
1640
+ (_a = this.log) == null ? void 0 : _a.debug({
1641
+ msg: "force stopping actor",
1642
+ actorId
1643
+ });
1644
+ const actor = this.getActor(actorId, generation);
1645
+ if (!actor) return;
1646
+ try {
1647
+ await this.#config.onActorStop(actorId, actor.generation);
1648
+ } catch (err) {
1649
+ console.error(`Error in onActorStop for actor ${actorId}:`, err);
1650
+ }
1651
+ (_b = this.#tunnel) == null ? void 0 : _b.closeActiveRequests(actor);
1652
+ this.#sendActorStateUpdate(actorId, actor.generation, "stopped");
1653
+ this.#removeActor(actorId, generation);
1654
+ }
1655
+ #handleLost() {
1656
+ var _a;
1657
+ (_a = this.log) == null ? void 0 : _a.info({
1658
+ msg: "stopping all actors due to runner lost threshold"
1659
+ });
1660
+ for (const [_, request] of this.#kvRequests.entries()) {
1661
+ request.reject(new RunnerShutdownError());
1662
+ }
1663
+ this.#kvRequests.clear();
1664
+ this.#stopAllActors();
1665
+ }
1666
+ #stopAllActors() {
1667
+ const actorIds = Array.from(this.#actors.keys());
1668
+ for (const actorId of actorIds) {
1669
+ this.forceStopActor(actorId).catch((err) => {
1670
+ var _a;
1671
+ (_a = this.log) == null ? void 0 : _a.error({
1672
+ msg: "error stopping actor",
1673
+ actorId,
1674
+ error: stringifyError(err)
1675
+ });
1676
+ });
1677
+ }
1678
+ }
1679
+ getActor(actorId, generation) {
1680
+ var _a, _b;
1681
+ const actor = this.#actors.get(actorId);
1682
+ if (!actor) {
1683
+ (_a = this.log) == null ? void 0 : _a.warn({
1684
+ msg: "actor not found",
1685
+ actorId
1686
+ });
1687
+ return void 0;
1688
+ }
1689
+ if (generation !== void 0 && actor.generation !== generation) {
1690
+ (_b = this.log) == null ? void 0 : _b.warn({
1691
+ msg: "actor generation mismatch",
1692
+ actorId,
1693
+ generation
1694
+ });
1695
+ return void 0;
1696
+ }
1697
+ return actor;
1698
+ }
1699
+ async getAndWaitForActor(actorId, generation) {
1700
+ const actor = this.getActor(actorId, generation);
1701
+ if (!actor) return;
1702
+ await actor.actorStartPromise.promise;
1703
+ return actor;
1704
+ }
1705
+ hasActor(actorId, generation) {
1706
+ const actor = this.#actors.get(actorId);
1707
+ return !!actor && (generation === void 0 || actor.generation === generation);
1708
+ }
1709
+ get actors() {
1710
+ return this.#actors;
1711
+ }
1712
+ // IMPORTANT: Make sure to call stopActiveRequests if calling #removeActor
1713
+ #removeActor(actorId, generation) {
1714
+ var _a, _b, _c;
1715
+ const actor = this.#actors.get(actorId);
1716
+ if (!actor) {
1717
+ (_a = this.log) == null ? void 0 : _a.error({
1718
+ msg: "actor not found for removal",
1719
+ actorId
1720
+ });
1721
+ return void 0;
1722
+ }
1723
+ if (generation !== void 0 && actor.generation !== generation) {
1724
+ (_b = this.log) == null ? void 0 : _b.error({
1725
+ msg: "actor generation mismatch",
1726
+ actorId,
1727
+ generation
1728
+ });
1729
+ return void 0;
1730
+ }
1731
+ this.#actors.delete(actorId);
1732
+ (_c = this.log) == null ? void 0 : _c.info({
1733
+ msg: "removed actor",
1734
+ actorId,
1735
+ actors: this.#actors.size
1736
+ });
1737
+ return actor;
1738
+ }
1739
+ // MARK: Start
1740
+ async start() {
1741
+ var _a, _b;
1742
+ if (this.#started) throw new Error("Cannot call runner.start twice");
1743
+ this.#started = true;
1744
+ (_a = this.log) == null ? void 0 : _a.info({ msg: "starting runner" });
1745
+ this.#tunnel = new Tunnel(this);
1746
+ this.#tunnel.start();
1747
+ try {
1748
+ await this.#openPegboardWebSocket();
1749
+ } catch (error) {
1750
+ this.#started = false;
1751
+ throw error;
1752
+ }
1753
+ if (!this.#config.noAutoShutdown) {
1754
+ if (!SIGNAL_HANDLERS.length) {
1755
+ process.on("SIGTERM", async () => {
1756
+ var _a2;
1757
+ (_a2 = this.log) == null ? void 0 : _a2.debug("received SIGTERM");
1758
+ for (const handler of SIGNAL_HANDLERS) {
1759
+ await handler();
1760
+ }
1761
+ });
1762
+ process.on("SIGINT", async () => {
1763
+ var _a2;
1764
+ (_a2 = this.log) == null ? void 0 : _a2.debug("received SIGINT");
1765
+ for (const handler of SIGNAL_HANDLERS) {
1766
+ await handler();
1767
+ }
1768
+ });
1769
+ (_b = this.log) == null ? void 0 : _b.debug({
1770
+ msg: "added SIGTERM listeners"
1771
+ });
1772
+ }
1773
+ SIGNAL_HANDLERS.push(async () => {
1774
+ var _a2;
1775
+ const weak = new WeakRef(this);
1776
+ await ((_a2 = weak.deref()) == null ? void 0 : _a2.shutdown(false, false));
1777
+ });
1778
+ }
1779
+ }
1780
+ // MARK: Shutdown
1781
+ async shutdown(immediate, exit = false) {
1782
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1783
+ if (this.#shutdown) {
1784
+ (_a = this.log) == null ? void 0 : _a.debug({
1785
+ msg: "shutdown already in progress, ignoring"
1786
+ });
1787
+ return;
1788
+ }
1789
+ this.#shutdown = true;
1790
+ this.#draining = !immediate;
1791
+ (_b = this.log) == null ? void 0 : _b.info({
1792
+ msg: "starting shutdown",
1793
+ immediate,
1794
+ exit
1795
+ });
1796
+ if (this.#reconnectTimeout) {
1797
+ clearTimeout(this.#reconnectTimeout);
1798
+ this.#reconnectTimeout = void 0;
1799
+ }
1800
+ if (this.#runnerLostTimeout) {
1801
+ clearTimeout(this.#runnerLostTimeout);
1802
+ this.#runnerLostTimeout = void 0;
1803
+ }
1804
+ if (this.#ackInterval) {
1805
+ clearInterval(this.#ackInterval);
1806
+ this.#ackInterval = void 0;
1807
+ }
1808
+ if (this.#kvCleanupInterval) {
1809
+ clearInterval(this.#kvCleanupInterval);
1810
+ this.#kvCleanupInterval = void 0;
1811
+ }
1812
+ for (const request of this.#kvRequests.values()) {
1813
+ request.reject(
1814
+ new Error("WebSocket connection closed during shutdown")
1815
+ );
1816
+ }
1817
+ this.#kvRequests.clear();
1818
+ const pegboardWebSocket = this.#pegboardWebSocket;
1819
+ const readyState = pegboardWebSocket == null ? void 0 : pegboardWebSocket.readyState;
1820
+ const isOpen = readyState === 1;
1821
+ const isConnecting = readyState === 0;
1822
+ if (pegboardWebSocket && (isOpen || isConnecting)) {
1823
+ if (immediate || isConnecting) {
1824
+ pegboardWebSocket.close(1e3, "pegboard.runner_shutdown");
1825
+ } else {
1826
+ try {
1827
+ (_c = this.log) == null ? void 0 : _c.info({
1828
+ msg: "sending stopping message",
1829
+ readyState: pegboardWebSocket.readyState
1830
+ });
1831
+ this.__sendToServer({
1832
+ tag: "ToServerStopping",
1833
+ val: null
1834
+ });
1835
+ const closePromise = new Promise((resolve) => {
1836
+ if (!pegboardWebSocket)
1837
+ throw new Error("missing pegboardWebSocket");
1838
+ pegboardWebSocket.addEventListener("close", (ev) => {
1839
+ var _a2;
1840
+ (_a2 = this.log) == null ? void 0 : _a2.info({
1841
+ msg: "connection closed",
1842
+ code: ev.code,
1843
+ reason: ev.reason.toString()
1844
+ });
1845
+ resolve();
1846
+ });
1847
+ });
1848
+ await this.#waitForActorsToStop(pegboardWebSocket);
1849
+ (_d = this.log) == null ? void 0 : _d.info({
1850
+ msg: "closing WebSocket"
1851
+ });
1852
+ pegboardWebSocket.close(1e3, "pegboard.runner_shutdown");
1853
+ await closePromise;
1854
+ (_e = this.log) == null ? void 0 : _e.info({
1855
+ msg: "websocket shutdown completed"
1856
+ });
1857
+ } catch (error) {
1858
+ (_f = this.log) == null ? void 0 : _f.error({
1859
+ msg: "error during websocket shutdown:",
1860
+ error
1861
+ });
1862
+ pegboardWebSocket.close();
1863
+ }
1864
+ }
1865
+ } else {
1866
+ (_h = this.log) == null ? void 0 : _h.debug({
1867
+ msg: "no runner WebSocket to shutdown or already closed",
1868
+ readyState: (_g = this.#pegboardWebSocket) == null ? void 0 : _g.readyState
1869
+ });
1870
+ }
1871
+ if (this.#tunnel) {
1872
+ this.#tunnel.shutdown();
1873
+ this.#tunnel = void 0;
1874
+ }
1875
+ this.#config.onShutdown();
1876
+ if (exit) process.exit(0);
1877
+ }
1878
+ /**
1879
+ * Wait for all actors to stop before proceeding with shutdown.
1880
+ *
1881
+ * This method polls every 100ms to check if all actors have been stopped.
1882
+ *
1883
+ * It will resolve early if:
1884
+ * - All actors are stopped
1885
+ * - The WebSocket connection is closed
1886
+ * - The shutdown timeout is reached (120 seconds)
1887
+ *
1888
+ * When changing this timeout, update
1889
+ * website/src/content/docs/actors/versions.mdx (SIGTERM Handling section).
1890
+ */
1891
+ async #waitForActorsToStop(ws) {
1892
+ const shutdownTimeout = 12e4;
1893
+ const shutdownCheckInterval = 100;
1894
+ const progressLogInterval = 5e3;
1895
+ const shutdownStartTs = Date.now();
1896
+ let lastProgressLogTs = 0;
1897
+ return new Promise((resolve) => {
1898
+ var _a, _b;
1899
+ const checkActors = () => {
1900
+ var _a2, _b2, _c, _d;
1901
+ const now = Date.now();
1902
+ const elapsed = now - shutdownStartTs;
1903
+ const wsIsClosed = ws.readyState === 2 || ws.readyState === 3;
1904
+ if (this.#actors.size === 0) {
1905
+ (_a2 = this.log) == null ? void 0 : _a2.info({
1906
+ msg: "all actors stopped",
1907
+ elapsed
1908
+ });
1909
+ return true;
1910
+ } else if (wsIsClosed) {
1911
+ (_b2 = this.log) == null ? void 0 : _b2.warn({
1912
+ msg: "websocket closed before all actors stopped",
1913
+ remainingActors: this.#actors.size,
1914
+ elapsed
1915
+ });
1916
+ return true;
1917
+ } else if (elapsed >= shutdownTimeout) {
1918
+ (_c = this.log) == null ? void 0 : _c.warn({
1919
+ msg: "shutdown timeout reached, forcing close",
1920
+ remainingActors: this.#actors.size,
1921
+ elapsed
1922
+ });
1923
+ return true;
1924
+ } else {
1925
+ if (now - lastProgressLogTs >= progressLogInterval) {
1926
+ (_d = this.log) == null ? void 0 : _d.info({
1927
+ msg: "waiting for actors to stop",
1928
+ remainingActors: this.#actors.size,
1929
+ elapsed
1930
+ });
1931
+ lastProgressLogTs = now;
1932
+ }
1933
+ return false;
1934
+ }
1935
+ };
1936
+ if (checkActors()) {
1937
+ (_a = this.log) == null ? void 0 : _a.debug({
1938
+ msg: "actors check completed immediately"
1939
+ });
1940
+ resolve();
1941
+ return;
1942
+ }
1943
+ (_b = this.log) == null ? void 0 : _b.debug({
1944
+ msg: "starting actor wait interval",
1945
+ checkInterval: shutdownCheckInterval
1946
+ });
1947
+ const interval = setInterval(() => {
1948
+ var _a2, _b2;
1949
+ (_a2 = this.log) == null ? void 0 : _a2.debug({
1950
+ msg: "actor wait interval tick",
1951
+ actorCount: this.#actors.size
1952
+ });
1953
+ if (checkActors()) {
1954
+ (_b2 = this.log) == null ? void 0 : _b2.debug({
1955
+ msg: "actors check completed, clearing interval"
1956
+ });
1957
+ clearInterval(interval);
1958
+ resolve();
1959
+ }
1960
+ }, shutdownCheckInterval);
1961
+ });
1962
+ }
1963
+ // MARK: Networking
1964
+ get pegboardEndpoint() {
1965
+ return this.#config.pegboardEndpoint || this.#config.endpoint;
1966
+ }
1967
+ get pegboardUrl() {
1968
+ const wsEndpoint = this.pegboardEndpoint.replace("http://", "ws://").replace("https://", "wss://");
1969
+ const baseUrl = wsEndpoint.endsWith("/") ? wsEndpoint.slice(0, -1) : wsEndpoint;
1970
+ return `${baseUrl}/runners/connect?protocol_version=${PROTOCOL_VERSION}&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${encodeURIComponent(this.#runnerKey)}`;
1971
+ }
1972
+ // MARK: Runner protocol
1973
+ async #openPegboardWebSocket() {
1974
+ var _a, _b;
1975
+ const protocols = ["rivet"];
1976
+ if (this.config.token)
1977
+ protocols.push(`rivet_token.${this.config.token}`);
1978
+ const WS = await importWebSocket();
1979
+ if (this.#pegboardWebSocket && (this.#pegboardWebSocket.readyState === WS.CONNECTING || this.#pegboardWebSocket.readyState === WS.OPEN)) {
1980
+ (_a = this.log) == null ? void 0 : _a.error(
1981
+ "found duplicate pegboardWebSocket, closing previous"
1982
+ );
1983
+ this.#pegboardWebSocket.close(1e3, "duplicate_websocket");
1984
+ }
1985
+ const ws = new WS(this.pegboardUrl, protocols);
1986
+ this.#pegboardWebSocket = ws;
1987
+ (_b = this.log) == null ? void 0 : _b.info({
1988
+ msg: "connecting",
1989
+ endpoint: this.pegboardEndpoint,
1990
+ namespace: this.#config.namespace,
1991
+ runnerKey: this.#runnerKey,
1992
+ hasToken: !!this.config.token
1993
+ });
1994
+ ws.addEventListener("open", () => {
1995
+ var _a2, _b2;
1996
+ if (this.#reconnectAttempt > 0) {
1997
+ (_a2 = this.log) == null ? void 0 : _a2.info({
1998
+ msg: "runner reconnected",
1999
+ namespace: this.#config.namespace,
2000
+ runnerName: this.#config.runnerName,
2001
+ reconnectAttempt: this.#reconnectAttempt
2002
+ });
2003
+ } else {
2004
+ (_b2 = this.log) == null ? void 0 : _b2.debug({
2005
+ msg: "runner connected",
2006
+ namespace: this.#config.namespace,
2007
+ runnerName: this.#config.runnerName
2008
+ });
2009
+ }
2010
+ this.#reconnectAttempt = 0;
2011
+ if (this.#reconnectTimeout) {
2012
+ clearTimeout(this.#reconnectTimeout);
2013
+ this.#reconnectTimeout = void 0;
2014
+ }
2015
+ if (this.#runnerLostTimeout) {
2016
+ clearTimeout(this.#runnerLostTimeout);
2017
+ this.#runnerLostTimeout = void 0;
2018
+ }
2019
+ const init = {
2020
+ name: this.#config.runnerName,
2021
+ version: this.#config.version,
2022
+ totalSlots: this.#config.totalSlots,
2023
+ prepopulateActorNames: new Map(
2024
+ Object.entries(this.#config.prepopulateActorNames).map(
2025
+ ([name, data]) => [
2026
+ name,
2027
+ { metadata: JSON.stringify(data.metadata) }
2028
+ ]
2029
+ )
2030
+ ),
2031
+ metadata: JSON.stringify(this.#config.metadata)
2032
+ };
2033
+ this.__sendToServer({
2034
+ tag: "ToServerInit",
2035
+ val: init
2036
+ });
2037
+ const ackInterval = 5 * 60 * 1e3;
2038
+ const ackLoop = setInterval(() => {
2039
+ var _a3, _b3;
2040
+ try {
2041
+ if (ws.readyState === 1) {
2042
+ this.#sendCommandAcknowledgment();
2043
+ } else {
2044
+ clearInterval(ackLoop);
2045
+ (_a3 = this.log) == null ? void 0 : _a3.info({
2046
+ msg: "WebSocket not open, stopping ack loop"
2047
+ });
2048
+ }
2049
+ } catch (err) {
2050
+ (_b3 = this.log) == null ? void 0 : _b3.error({
2051
+ msg: "error in command acknowledgment loop",
2052
+ error: stringifyError(err)
2053
+ });
2054
+ }
2055
+ }, ackInterval);
2056
+ this.#ackInterval = ackLoop;
2057
+ });
2058
+ ws.addEventListener("message", async (ev) => {
2059
+ var _a2, _b2, _c, _d;
2060
+ let buf;
2061
+ if (ev.data instanceof Blob) {
2062
+ buf = new Uint8Array(await ev.data.arrayBuffer());
2063
+ } else if (Buffer.isBuffer(ev.data)) {
2064
+ buf = new Uint8Array(ev.data);
2065
+ } else {
2066
+ throw new Error(`expected binary data, got ${typeof ev.data}`);
2067
+ }
2068
+ await this.#injectLatency();
2069
+ const message = protocol.decodeToClient(buf);
2070
+ (_a2 = this.log) == null ? void 0 : _a2.debug({
2071
+ msg: "received runner message",
2072
+ data: stringifyToClient(message)
2073
+ });
2074
+ if (message.tag === "ToClientInit") {
2075
+ const init = message.val;
2076
+ if (this.runnerId !== init.runnerId) {
2077
+ this.runnerId = init.runnerId;
2078
+ this.#logCached = void 0;
2079
+ this.#stopAllActors();
2080
+ }
2081
+ this.#protocolMetadata = init.metadata;
2082
+ (_b2 = this.log) == null ? void 0 : _b2.info({
2083
+ msg: "received init",
2084
+ protocolMetadata: this.#protocolMetadata
2085
+ });
2086
+ this.#processUnsentKvRequests();
2087
+ this.#resendUnacknowledgedEvents();
2088
+ (_c = this.#tunnel) == null ? void 0 : _c.resendBufferedEvents();
2089
+ this.#config.onConnected();
2090
+ } else if (message.tag === "ToClientCommands") {
2091
+ const commands = message.val;
2092
+ this.#handleCommands(commands);
2093
+ } else if (message.tag === "ToClientAckEvents") {
2094
+ this.#handleAckEvents(message.val);
2095
+ } else if (message.tag === "ToClientKvResponse") {
2096
+ const kvResponse = message.val;
2097
+ this.#handleKvResponse(kvResponse);
2098
+ } else if (message.tag === "ToClientTunnelMessage") {
2099
+ (_d = this.#tunnel) == null ? void 0 : _d.handleTunnelMessage(message.val).catch((err) => {
2100
+ var _a3;
2101
+ (_a3 = this.log) == null ? void 0 : _a3.error({
2102
+ msg: "error handling tunnel message",
2103
+ error: stringifyError(err)
2104
+ });
2105
+ });
2106
+ } else if (message.tag === "ToClientPing") {
2107
+ this.__sendToServer({
2108
+ tag: "ToServerPong",
2109
+ val: {
2110
+ ts: message.val.ts
2111
+ }
2112
+ });
2113
+ } else {
2114
+ unreachable(message);
2115
+ }
2116
+ });
2117
+ ws.addEventListener("error", (ev) => {
2118
+ var _a2;
2119
+ (_a2 = this.log) == null ? void 0 : _a2.error({
2120
+ msg: `WebSocket error: ${stringifyError(ev.error)}`
2121
+ });
2122
+ if (!this.#shutdown) {
2123
+ this.#startRunnerLostTimeout();
2124
+ this.#scheduleReconnect();
2125
+ }
2126
+ });
2127
+ ws.addEventListener("close", async (ev) => {
2128
+ var _a2, _b2, _c;
2129
+ if (!this.#shutdown) {
2130
+ const closeError = parseWebSocketCloseReason(ev.reason);
2131
+ if ((closeError == null ? void 0 : closeError.group) === "ws" && (closeError == null ? void 0 : closeError.error) === "eviction") {
2132
+ (_a2 = this.log) == null ? void 0 : _a2.info("runner websocket evicted");
2133
+ this.#config.onDisconnected(ev.code, ev.reason);
2134
+ await this.shutdown(true);
2135
+ } else {
2136
+ (_b2 = this.log) == null ? void 0 : _b2.warn({
2137
+ msg: "runner disconnected",
2138
+ code: ev.code,
2139
+ reason: ev.reason.toString(),
2140
+ closeError
2141
+ });
2142
+ this.#config.onDisconnected(ev.code, ev.reason);
2143
+ }
2144
+ if (this.#ackInterval) {
2145
+ clearInterval(this.#ackInterval);
2146
+ this.#ackInterval = void 0;
2147
+ }
2148
+ this.#startRunnerLostTimeout();
2149
+ this.#scheduleReconnect();
2150
+ } else {
2151
+ (_c = this.log) == null ? void 0 : _c.info("websocket closed");
2152
+ this.#config.onDisconnected(ev.code, ev.reason);
2153
+ }
2154
+ });
2155
+ }
2156
+ #startRunnerLostTimeout() {
2157
+ var _a;
2158
+ if (!this.#runnerLostTimeout && this.#protocolMetadata && this.#protocolMetadata.runnerLostThreshold > 0) {
2159
+ (_a = this.log) == null ? void 0 : _a.info({
2160
+ msg: "starting runner lost timeout",
2161
+ seconds: this.#protocolMetadata.runnerLostThreshold / 1000n
2162
+ });
2163
+ this.#runnerLostTimeout = setTimeout(() => {
2164
+ var _a2;
2165
+ try {
2166
+ this.#handleLost();
2167
+ } catch (err) {
2168
+ (_a2 = this.log) == null ? void 0 : _a2.error({
2169
+ msg: "error handling runner lost",
2170
+ error: stringifyError(err)
2171
+ });
2172
+ }
2173
+ }, Number(this.#protocolMetadata.runnerLostThreshold));
2174
+ }
2175
+ }
2176
+ #handleCommands(commands) {
2177
+ var _a;
2178
+ (_a = this.log) == null ? void 0 : _a.info({
2179
+ msg: "received commands",
2180
+ commandCount: commands.length
2181
+ });
2182
+ for (const commandWrapper of commands) {
2183
+ if (commandWrapper.inner.tag === "CommandStartActor") {
2184
+ this.#handleCommandStartActor(commandWrapper).catch((err) => {
2185
+ var _a2;
2186
+ (_a2 = this.log) == null ? void 0 : _a2.error({
2187
+ msg: "error handling start actor command",
2188
+ actorId: commandWrapper.checkpoint.actorId,
2189
+ error: stringifyError(err)
2190
+ });
2191
+ });
2192
+ const actor = this.getActor(
2193
+ commandWrapper.checkpoint.actorId,
2194
+ commandWrapper.checkpoint.generation
2195
+ );
2196
+ if (actor)
2197
+ actor.lastCommandIdx = commandWrapper.checkpoint.index;
2198
+ } else if (commandWrapper.inner.tag === "CommandStopActor") {
2199
+ this.#handleCommandStopActor(commandWrapper).catch((err) => {
2200
+ var _a2;
2201
+ (_a2 = this.log) == null ? void 0 : _a2.error({
2202
+ msg: "error handling stop actor command",
2203
+ actorId: commandWrapper.checkpoint.actorId,
2204
+ error: stringifyError(err)
2205
+ });
2206
+ });
2207
+ } else {
2208
+ unreachable(commandWrapper.inner);
2209
+ }
2210
+ }
2211
+ }
2212
+ #handleAckEvents(ack) {
2213
+ var _a;
2214
+ const originalTotalEvents = Array.from(this.#actors).reduce(
2215
+ (s, [_, actor]) => s + actor.eventHistory.length,
2216
+ 0
2217
+ );
2218
+ for (const [_, actor] of this.#actors) {
2219
+ const checkpoint = ack.lastEventCheckpoints.find(
2220
+ (x) => x.actorId === actor.actorId
2221
+ );
2222
+ if (checkpoint) actor.handleAckEvents(checkpoint.index);
2223
+ }
2224
+ const totalEvents = Array.from(this.#actors).reduce(
2225
+ (s, [_, actor]) => s + actor.eventHistory.length,
2226
+ 0
2227
+ );
2228
+ const prunedCount = originalTotalEvents - totalEvents;
2229
+ if (prunedCount > 0) {
2230
+ (_a = this.log) == null ? void 0 : _a.info({
2231
+ msg: "pruned acknowledged events",
2232
+ prunedCount
2233
+ });
2234
+ }
2235
+ if (totalEvents <= EVENT_BACKLOG_WARN_THRESHOLD) {
2236
+ this.#eventBacklogWarned = false;
2237
+ }
2238
+ }
2239
+ /** Track events to send to the server in case we need to resend it on disconnect. */
2240
+ #recordEvent(eventWrapper) {
2241
+ var _a;
2242
+ const actor = this.getActor(eventWrapper.checkpoint.actorId);
2243
+ if (!actor) return;
2244
+ actor.recordEvent(eventWrapper);
2245
+ const totalEvents = Array.from(this.#actors).reduce(
2246
+ (s, [_, actor2]) => s + actor2.eventHistory.length,
2247
+ 0
2248
+ );
2249
+ if (totalEvents > EVENT_BACKLOG_WARN_THRESHOLD && !this.#eventBacklogWarned) {
2250
+ this.#eventBacklogWarned = true;
2251
+ (_a = this.log) == null ? void 0 : _a.warn({
2252
+ msg: "unacknowledged event backlog exceeds threshold",
2253
+ backlogSize: totalEvents,
2254
+ threshold: EVENT_BACKLOG_WARN_THRESHOLD
2255
+ });
2256
+ }
2257
+ }
2258
+ async #handleCommandStartActor(commandWrapper) {
2259
+ var _a, _b, _c, _d;
2260
+ if (!this.#tunnel) throw new Error("missing tunnel on actor start");
2261
+ const startCommand = commandWrapper.inner.val;
2262
+ const actorId = commandWrapper.checkpoint.actorId;
2263
+ const generation = commandWrapper.checkpoint.generation;
2264
+ const config = startCommand.config;
2265
+ const actorConfig = {
2266
+ name: config.name,
2267
+ key: config.key,
2268
+ createTs: config.createTs,
2269
+ input: config.input ? new Uint8Array(config.input) : null
2270
+ };
2271
+ const instance = new RunnerActor(
2272
+ actorId,
2273
+ generation,
2274
+ actorConfig,
2275
+ startCommand.hibernatingRequests
2276
+ );
2277
+ const existingActor = this.#actors.get(actorId);
2278
+ if (existingActor) {
2279
+ (_a = this.log) == null ? void 0 : _a.warn({
2280
+ msg: "replacing existing actor in actors map",
2281
+ actorId,
2282
+ existingGeneration: existingActor.generation,
2283
+ newGeneration: generation,
2284
+ existingPendingRequests: existingActor.pendingRequests.length
2285
+ });
2286
+ }
2287
+ this.#actors.set(actorId, instance);
2288
+ for (const hr of startCommand.hibernatingRequests) {
2289
+ this.#tunnel.addRequestToActor(hr.gatewayId, hr.requestId, actorId);
2290
+ }
2291
+ (_b = this.log) == null ? void 0 : _b.info({
2292
+ msg: "created actor",
2293
+ actors: this.#actors.size,
2294
+ actorId,
2295
+ name: config.name,
2296
+ key: config.key,
2297
+ generation,
2298
+ hibernatingRequests: startCommand.hibernatingRequests.length
2299
+ });
2300
+ this.#sendActorStateUpdate(actorId, generation, "running");
2301
+ try {
2302
+ (_c = this.log) == null ? void 0 : _c.debug({
2303
+ msg: "calling onActorStart",
2304
+ actorId,
2305
+ generation
2306
+ });
2307
+ await this.#config.onActorStart(actorId, generation, actorConfig);
2308
+ instance.actorStartPromise.resolve();
2309
+ } catch (error) {
2310
+ (_d = this.log) == null ? void 0 : _d.error({
2311
+ msg: "error starting runner actor",
2312
+ actorId,
2313
+ error
2314
+ });
2315
+ instance.actorStartPromise.reject(error);
2316
+ await this.forceStopActor(actorId, generation);
2317
+ }
2318
+ }
2319
+ async #handleCommandStopActor(commandWrapper) {
2320
+ const _stopCommand = commandWrapper.inner.val;
2321
+ const actorId = commandWrapper.checkpoint.actorId;
2322
+ const generation = commandWrapper.checkpoint.generation;
2323
+ await this.forceStopActor(actorId, generation);
2324
+ }
2325
+ #sendActorIntent(actorId, generation, intentType) {
2326
+ const actor = this.getActor(actorId, generation);
2327
+ if (!actor) return;
2328
+ let actorIntent;
2329
+ if (intentType === "sleep") {
2330
+ actorIntent = { tag: "ActorIntentSleep", val: null };
2331
+ } else if (intentType === "stop") {
2332
+ actorIntent = {
2333
+ tag: "ActorIntentStop",
2334
+ val: null
2335
+ };
2336
+ } else {
2337
+ unreachable(intentType);
2338
+ }
2339
+ const intentEvent = {
2340
+ intent: actorIntent
2341
+ };
2342
+ const eventWrapper = {
2343
+ checkpoint: {
2344
+ actorId,
2345
+ generation,
2346
+ index: actor.nextEventIdx++
2347
+ },
2348
+ inner: {
2349
+ tag: "EventActorIntent",
2350
+ val: intentEvent
2351
+ }
2352
+ };
2353
+ this.#recordEvent(eventWrapper);
2354
+ this.__sendToServer({
2355
+ tag: "ToServerEvents",
2356
+ val: [eventWrapper]
2357
+ });
2358
+ }
2359
+ #sendActorStateUpdate(actorId, generation, stateType) {
2360
+ const actor = this.getActor(actorId, generation);
2361
+ if (!actor) return;
2362
+ let actorState;
2363
+ if (stateType === "running") {
2364
+ actorState = { tag: "ActorStateRunning", val: null };
2365
+ } else if (stateType === "stopped") {
2366
+ actorState = {
2367
+ tag: "ActorStateStopped",
2368
+ val: {
2369
+ code: actor.stopIntentSent || this.#draining ? protocol.StopCode.Ok : protocol.StopCode.Error,
2370
+ message: null
2371
+ }
2372
+ };
2373
+ } else {
2374
+ unreachable(stateType);
2375
+ }
2376
+ const stateUpdateEvent = {
2377
+ state: actorState
2378
+ };
2379
+ const eventWrapper = {
2380
+ checkpoint: {
2381
+ actorId,
2382
+ generation,
2383
+ index: actor.nextEventIdx++
2384
+ },
2385
+ inner: {
2386
+ tag: "EventActorStateUpdate",
2387
+ val: stateUpdateEvent
2388
+ }
2389
+ };
2390
+ this.#recordEvent(eventWrapper);
2391
+ this.__sendToServer({
2392
+ tag: "ToServerEvents",
2393
+ val: [eventWrapper]
2394
+ });
2395
+ }
2396
+ #sendCommandAcknowledgment() {
2397
+ const lastCommandCheckpoints = [];
2398
+ for (const [_, actor] of this.#actors) {
2399
+ if (actor.lastCommandIdx < 0) {
2400
+ continue;
2401
+ }
2402
+ lastCommandCheckpoints.push({
2403
+ actorId: actor.actorId,
2404
+ generation: actor.generation,
2405
+ index: actor.lastCommandIdx
2406
+ });
2407
+ }
2408
+ this.__sendToServer({
2409
+ tag: "ToServerAckCommands",
2410
+ val: {
2411
+ lastCommandCheckpoints
2412
+ }
2413
+ });
2414
+ }
2415
+ #handleKvResponse(response) {
2416
+ var _a;
2417
+ const requestId = response.requestId;
2418
+ const request = this.#kvRequests.get(requestId);
2419
+ if (!request) {
2420
+ (_a = this.log) == null ? void 0 : _a.error({
2421
+ msg: "received kv response for unknown request id",
2422
+ requestId
2423
+ });
2424
+ return;
2425
+ }
2426
+ this.#kvRequests.delete(requestId);
2427
+ if (response.data.tag === "KvErrorResponse") {
2428
+ request.reject(
2429
+ new Error(response.data.val.message || "Unknown KV error")
2430
+ );
2431
+ } else {
2432
+ request.resolve(response.data.val);
2433
+ }
2434
+ }
2435
+ #parseGetResponseSimple(response, requestedKeys) {
2436
+ const responseKeys = [];
2437
+ const responseValues = [];
2438
+ for (const key of response.keys) {
2439
+ responseKeys.push(new Uint8Array(key));
2440
+ }
2441
+ for (const value of response.values) {
2442
+ responseValues.push(new Uint8Array(value));
2443
+ }
2444
+ const result = [];
2445
+ for (const requestedKey of requestedKeys) {
2446
+ let found = false;
2447
+ for (let i = 0; i < responseKeys.length; i++) {
2448
+ if (this.#keysEqual(requestedKey, responseKeys[i])) {
2449
+ result.push(responseValues[i]);
2450
+ found = true;
2451
+ break;
2452
+ }
2453
+ }
2454
+ if (!found) {
2455
+ result.push(null);
2456
+ }
2457
+ }
2458
+ return result;
2459
+ }
2460
+ #keysEqual(key1, key2) {
2461
+ if (key1.length !== key2.length) return false;
2462
+ for (let i = 0; i < key1.length; i++) {
2463
+ if (key1[i] !== key2[i]) return false;
2464
+ }
2465
+ return true;
2466
+ }
2467
+ //#parseGetResponse(response: protocol.KvGetResponse) {
2468
+ // const keys: string[] = [];
2469
+ // const values: Uint8Array[] = [];
2470
+ // const metadata: { version: Uint8Array; createTs: bigint }[] = [];
2471
+ //
2472
+ // for (const key of response.keys) {
2473
+ // keys.push(new TextDecoder().decode(key));
2474
+ // }
2475
+ //
2476
+ // for (const value of response.values) {
2477
+ // values.push(new Uint8Array(value));
2478
+ // }
2479
+ //
2480
+ // for (const meta of response.metadata) {
2481
+ // metadata.push({
2482
+ // version: new Uint8Array(meta.version),
2483
+ // createTs: meta.createTs,
2484
+ // });
2485
+ // }
2486
+ //
2487
+ // return { keys, values, metadata };
2488
+ //}
2489
+ #parseListResponseSimple(response) {
2490
+ const result = [];
2491
+ for (let i = 0; i < response.keys.length; i++) {
2492
+ const key = response.keys[i];
2493
+ const value = response.values[i];
2494
+ if (key && value) {
2495
+ const keyBytes = new Uint8Array(key);
2496
+ const valueBytes = new Uint8Array(value);
2497
+ result.push([keyBytes, valueBytes]);
2498
+ }
2499
+ }
2500
+ return result;
2501
+ }
2502
+ //#parseListResponse(response: protocol.KvListResponse) {
2503
+ // const keys: string[] = [];
2504
+ // const values: Uint8Array[] = [];
2505
+ // const metadata: { version: Uint8Array; createTs: bigint }[] = [];
2506
+ //
2507
+ // for (const key of response.keys) {
2508
+ // keys.push(new TextDecoder().decode(key));
2509
+ // }
2510
+ //
2511
+ // for (const value of response.values) {
2512
+ // values.push(new Uint8Array(value));
2513
+ // }
2514
+ //
2515
+ // for (const meta of response.metadata) {
2516
+ // metadata.push({
2517
+ // version: new Uint8Array(meta.version),
2518
+ // createTs: meta.createTs,
2519
+ // });
2520
+ // }
2521
+ //
2522
+ // return { keys, values, metadata };
2523
+ //}
2524
+ // MARK: KV Operations
2525
+ async kvGet(actorId, keys) {
2526
+ const kvKeys = keys.map(
2527
+ (key) => key.buffer.slice(
2528
+ key.byteOffset,
2529
+ key.byteOffset + key.byteLength
2530
+ )
2531
+ );
2532
+ const requestData = {
2533
+ tag: "KvGetRequest",
2534
+ val: { keys: kvKeys }
2535
+ };
2536
+ const response = await this.#sendKvRequest(actorId, requestData);
2537
+ return this.#parseGetResponseSimple(response, keys);
2538
+ }
2539
+ async kvListAll(actorId, options) {
2540
+ const requestData = {
2541
+ tag: "KvListRequest",
2542
+ val: {
2543
+ query: { tag: "KvListAllQuery", val: null },
2544
+ reverse: (options == null ? void 0 : options.reverse) || null,
2545
+ limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
2546
+ }
2547
+ };
2548
+ const response = await this.#sendKvRequest(actorId, requestData);
2549
+ return this.#parseListResponseSimple(response);
2550
+ }
2551
+ async kvListRange(actorId, start, end, exclusive, options) {
2552
+ const startKey = start.buffer.slice(
2553
+ start.byteOffset,
2554
+ start.byteOffset + start.byteLength
2555
+ );
2556
+ const endKey = end.buffer.slice(
2557
+ end.byteOffset,
2558
+ end.byteOffset + end.byteLength
2559
+ );
2560
+ const requestData = {
2561
+ tag: "KvListRequest",
2562
+ val: {
2563
+ query: {
2564
+ tag: "KvListRangeQuery",
2565
+ val: {
2566
+ start: startKey,
2567
+ end: endKey,
2568
+ exclusive: exclusive || false
2569
+ }
2570
+ },
2571
+ reverse: (options == null ? void 0 : options.reverse) || null,
2572
+ limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
2573
+ }
2574
+ };
2575
+ const response = await this.#sendKvRequest(actorId, requestData);
2576
+ return this.#parseListResponseSimple(response);
2577
+ }
2578
+ async kvListPrefix(actorId, prefix, options) {
2579
+ const prefixKey = prefix.buffer.slice(
2580
+ prefix.byteOffset,
2581
+ prefix.byteOffset + prefix.byteLength
2582
+ );
2583
+ const requestData = {
2584
+ tag: "KvListRequest",
2585
+ val: {
2586
+ query: {
2587
+ tag: "KvListPrefixQuery",
2588
+ val: { key: prefixKey }
2589
+ },
2590
+ reverse: (options == null ? void 0 : options.reverse) || null,
2591
+ limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
2592
+ }
2593
+ };
2594
+ const response = await this.#sendKvRequest(actorId, requestData);
2595
+ return this.#parseListResponseSimple(response);
2596
+ }
2597
+ async kvPut(actorId, entries) {
2598
+ const keys = entries.map(
2599
+ ([key, _value]) => key.buffer.slice(
2600
+ key.byteOffset,
2601
+ key.byteOffset + key.byteLength
2602
+ )
2603
+ );
2604
+ const values = entries.map(
2605
+ ([_key, value]) => value.buffer.slice(
2606
+ value.byteOffset,
2607
+ value.byteOffset + value.byteLength
2608
+ )
2609
+ );
2610
+ const requestData = {
2611
+ tag: "KvPutRequest",
2612
+ val: { keys, values }
2613
+ };
2614
+ await this.#sendKvRequest(actorId, requestData);
2615
+ }
2616
+ async kvDelete(actorId, keys) {
2617
+ const kvKeys = keys.map(
2618
+ (key) => key.buffer.slice(
2619
+ key.byteOffset,
2620
+ key.byteOffset + key.byteLength
2621
+ )
2622
+ );
2623
+ const requestData = {
2624
+ tag: "KvDeleteRequest",
2625
+ val: { keys: kvKeys }
2626
+ };
2627
+ await this.#sendKvRequest(actorId, requestData);
2628
+ }
2629
+ async kvDeleteRange(actorId, start, end) {
2630
+ const startKey = start.buffer.slice(
2631
+ start.byteOffset,
2632
+ start.byteOffset + start.byteLength
2633
+ );
2634
+ const endKey = end.buffer.slice(
2635
+ end.byteOffset,
2636
+ end.byteOffset + end.byteLength
2637
+ );
2638
+ const requestData = {
2639
+ tag: "KvDeleteRangeRequest",
2640
+ val: {
2641
+ start: startKey,
2642
+ end: endKey
2643
+ }
2644
+ };
2645
+ await this.#sendKvRequest(actorId, requestData);
2646
+ }
2647
+ async kvDrop(actorId) {
2648
+ const requestData = {
2649
+ tag: "KvDropRequest",
2650
+ val: null
2651
+ };
2652
+ await this.#sendKvRequest(actorId, requestData);
2653
+ }
2654
+ // MARK: Alarm Operations
2655
+ setAlarm(actorId, alarmTs, generation) {
2656
+ const actor = this.getActor(actorId, generation);
2657
+ if (!actor) return;
2658
+ const alarmEvent = {
2659
+ alarmTs: alarmTs !== null ? BigInt(alarmTs) : null
2660
+ };
2661
+ const eventWrapper = {
2662
+ checkpoint: {
2663
+ actorId,
2664
+ generation: actor.generation,
2665
+ index: actor.nextEventIdx++
2666
+ },
2667
+ inner: {
2668
+ tag: "EventActorSetAlarm",
2669
+ val: alarmEvent
2670
+ }
2671
+ };
2672
+ this.#recordEvent(eventWrapper);
2673
+ this.__sendToServer({
2674
+ tag: "ToServerEvents",
2675
+ val: [eventWrapper]
2676
+ });
2677
+ }
2678
+ clearAlarm(actorId, generation) {
2679
+ this.setAlarm(actorId, null, generation);
2680
+ }
2681
+ #sendKvRequest(actorId, requestData) {
2682
+ return new Promise((resolve, reject) => {
2683
+ const requestId = this.#nextKvRequestId++;
2684
+ const requestEntry = {
2685
+ actorId,
2686
+ data: requestData,
2687
+ resolve,
2688
+ reject,
2689
+ sent: false,
2690
+ timestamp: Date.now()
2691
+ };
2692
+ this.#kvRequests.set(requestId, requestEntry);
2693
+ if (this.getPegboardWebSocketIfReady()) {
2694
+ this.#sendSingleKvRequest(requestId);
2695
+ }
2696
+ });
2697
+ }
2698
+ #sendSingleKvRequest(requestId) {
2699
+ const request = this.#kvRequests.get(requestId);
2700
+ if (!request || request.sent) return;
2701
+ try {
2702
+ const kvRequest = {
2703
+ actorId: request.actorId,
2704
+ requestId,
2705
+ data: request.data
2706
+ };
2707
+ this.__sendToServer({
2708
+ tag: "ToServerKvRequest",
2709
+ val: kvRequest
2710
+ });
2711
+ request.sent = true;
2712
+ request.timestamp = Date.now();
2713
+ } catch (error) {
2714
+ this.#kvRequests.delete(requestId);
2715
+ request.reject(error);
2716
+ }
2717
+ }
2718
+ #processUnsentKvRequests() {
2719
+ if (!this.getPegboardWebSocketIfReady()) {
2720
+ return;
2721
+ }
2722
+ let processedCount = 0;
2723
+ for (const [requestId, request] of this.#kvRequests.entries()) {
2724
+ if (!request.sent) {
2725
+ this.#sendSingleKvRequest(requestId);
2726
+ processedCount++;
2727
+ }
2728
+ }
2729
+ if (processedCount > 0) {
2730
+ }
2731
+ }
2732
+ /** Resolves after the configured debug latency, or immediately if none. */
2733
+ #injectLatency() {
2734
+ const ms = this.#config.debugLatencyMs;
2735
+ if (!ms) return Promise.resolve();
2736
+ return new Promise((resolve) => setTimeout(resolve, ms));
2737
+ }
2738
+ /** Asserts WebSocket exists and is ready. */
2739
+ getPegboardWebSocketIfReady() {
2740
+ if (!!this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === 1) {
2741
+ return this.#pegboardWebSocket;
2742
+ } else {
2743
+ return void 0;
2744
+ }
2745
+ }
2746
+ __sendToServer(message) {
2747
+ var _a;
2748
+ (_a = this.log) == null ? void 0 : _a.debug({
2749
+ msg: "sending runner message",
2750
+ data: stringifyToServer(message)
2751
+ });
2752
+ const encoded = protocol.encodeToServer(message);
2753
+ this.#injectLatency().then(() => {
2754
+ var _a2;
2755
+ const pegboardWebSocket = this.getPegboardWebSocketIfReady();
2756
+ if (pegboardWebSocket) {
2757
+ pegboardWebSocket.send(encoded);
2758
+ } else {
2759
+ (_a2 = this.log) == null ? void 0 : _a2.error({
2760
+ msg: "WebSocket not available or not open for sending data"
2761
+ });
2762
+ }
2763
+ });
2764
+ }
2765
+ sendHibernatableWebSocketMessageAck(gatewayId, requestId, index) {
2766
+ if (!this.#tunnel)
2767
+ throw new Error("missing tunnel to send message ack");
2768
+ this.#tunnel.sendHibernatableWebSocketMessageAck(
2769
+ gatewayId,
2770
+ requestId,
2771
+ index
2772
+ );
2773
+ }
2774
+ /**
2775
+ * Restores hibernatable WebSocket connections for an actor.
2776
+ *
2777
+ * This method should be called at the end of `onActorStart` after the
2778
+ * actor instance is fully initialized.
2779
+ *
2780
+ * This method will:
2781
+ * - Restore all provided hibernatable WebSocket connections
2782
+ * - Attach event listeners to the restored WebSockets
2783
+ * - Close any WebSocket connections that failed to restore
2784
+ *
2785
+ * The provided metadata list should include all hibernatable WebSockets
2786
+ * that were persisted for this actor. The gateway will automatically
2787
+ * close any connections that are not restored (i.e., not included in
2788
+ * this list).
2789
+ *
2790
+ * **Important:** This method must be called after `onActorStart` completes
2791
+ * and before marking the actor as "ready" to ensure all hibernatable
2792
+ * connections are fully restored.
2793
+ *
2794
+ * @param actorId - The ID of the actor to restore connections for
2795
+ * @param metaEntries - Array of hibernatable WebSocket metadata to restore
2796
+ */
2797
+ async restoreHibernatingRequests(actorId, metaEntries) {
2798
+ if (!this.#tunnel)
2799
+ throw new Error("missing tunnel to restore hibernating requests");
2800
+ await this.#tunnel.restoreHibernatingRequests(actorId, metaEntries);
2801
+ }
2802
+ getServerlessInitPacket() {
2803
+ if (!this.runnerId) return void 0;
2804
+ const data = protocol.encodeToServerlessServer({
2805
+ tag: "ToServerlessServerInit",
2806
+ val: {
2807
+ runnerId: this.runnerId,
2808
+ runnerProtocolVersion: PROTOCOL_VERSION
2809
+ }
2810
+ });
2811
+ const buffer = Buffer.alloc(data.length + 2);
2812
+ buffer.writeUInt16LE(PROTOCOL_VERSION, 0);
2813
+ Buffer.from(data).copy(buffer, 2);
2814
+ return buffer.toString("base64");
2815
+ }
2816
+ #scheduleReconnect() {
2817
+ var _a, _b, _c;
2818
+ if (this.#shutdown) {
2819
+ (_a = this.log) == null ? void 0 : _a.debug({
2820
+ msg: "Runner is shut down, not attempting reconnect"
2821
+ });
2822
+ return;
2823
+ }
2824
+ const delay = calculateBackoff(this.#reconnectAttempt, {
2825
+ initialDelay: 1e3,
2826
+ maxDelay: 3e4,
2827
+ multiplier: 2,
2828
+ jitter: true
2829
+ });
2830
+ (_b = this.log) == null ? void 0 : _b.debug({
2831
+ msg: `Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`
2832
+ });
2833
+ if (this.#reconnectTimeout) {
2834
+ (_c = this.log) == null ? void 0 : _c.info(
2835
+ "clearing previous reconnect timeout in schedule reconnect"
2836
+ );
2837
+ clearTimeout(this.#reconnectTimeout);
2838
+ }
2839
+ this.#reconnectTimeout = setTimeout(() => {
2840
+ var _a2;
2841
+ if (!this.#shutdown) {
2842
+ this.#reconnectAttempt++;
2843
+ (_a2 = this.log) == null ? void 0 : _a2.debug({
2844
+ msg: `Attempting to reconnect (attempt ${this.#reconnectAttempt})...`
2845
+ });
2846
+ this.#openPegboardWebSocket().catch((err) => {
2847
+ var _a3;
2848
+ (_a3 = this.log) == null ? void 0 : _a3.error({
2849
+ msg: "error during websocket reconnection",
2850
+ error: stringifyError(err)
2851
+ });
2852
+ });
2853
+ }
2854
+ }, delay);
2855
+ }
2856
+ #resendUnacknowledgedEvents() {
2857
+ var _a;
2858
+ const eventsToResend = [];
2859
+ for (const [_, actor] of this.#actors) {
2860
+ eventsToResend.push(...actor.eventHistory);
2861
+ }
2862
+ if (eventsToResend.length === 0) return;
2863
+ (_a = this.log) == null ? void 0 : _a.info({
2864
+ msg: "resending unacknowledged events",
2865
+ count: eventsToResend.length
2866
+ });
2867
+ this.__sendToServer({
2868
+ tag: "ToServerEvents",
2869
+ val: eventsToResend
2870
+ });
2871
+ }
2872
+ #cleanupOldKvRequests() {
2873
+ const thirtySecondsAgo = Date.now() - KV_EXPIRE;
2874
+ const toDelete = [];
2875
+ for (const [requestId, request] of this.#kvRequests.entries()) {
2876
+ if (request.timestamp < thirtySecondsAgo) {
2877
+ request.reject(
2878
+ new Error(
2879
+ "KV request timed out waiting for WebSocket connection"
2880
+ )
2881
+ );
2882
+ toDelete.push(requestId);
2883
+ }
2884
+ }
2885
+ for (const requestId of toDelete) {
2886
+ this.#kvRequests.delete(requestId);
2887
+ }
2888
+ if (toDelete.length > 0) {
2889
+ }
2890
+ }
2891
+ getProtocolMetadata() {
2892
+ return this.#protocolMetadata;
2893
+ }
2894
+ };
2895
+
2896
+
2897
+
2898
+
2899
+
2900
+ exports.Runner = Runner; exports.RunnerActor = RunnerActor; exports.RunnerShutdownError = RunnerShutdownError; exports.idToStr = idToStr;
2901
+ //# sourceMappingURL=mod.cjs.map