@rivetkit/engine-runner 0.0.0-pr.4600.32b0fc8

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