@rivetkit/engine-runner 25.7.1-rc.1 → 25.7.2-rc.1
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/.turbo/turbo-build.log +10 -10
- package/LICENSE +203 -0
- package/dist/mod.cjs +68 -289
- package/dist/mod.cjs.map +1 -1
- package/dist/mod.d.cts +3 -0
- package/dist/mod.d.ts +3 -0
- package/dist/mod.js +67 -288
- package/dist/mod.js.map +1 -1
- package/package.json +36 -37
- package/src/mod.ts +42 -65
- package/src/tunnel.ts +53 -298
package/package.json
CHANGED
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
2
|
+
"name": "@rivetkit/engine-runner",
|
|
3
|
+
"version": "25.7.2-rc.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
"import": {
|
|
7
|
+
"types": "./dist/mod.d.ts",
|
|
8
|
+
"default": "./dist/mod.js"
|
|
9
|
+
},
|
|
10
|
+
"require": {
|
|
11
|
+
"types": "./dist/mod.d.cts",
|
|
12
|
+
"default": "./dist/mod.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"uuid": "^12.0.0",
|
|
17
|
+
"pino": "^9.9.5",
|
|
18
|
+
"ws": "^8.18.3",
|
|
19
|
+
"@rivetkit/engine-runner-protocol": "25.7.2-rc.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.13.9",
|
|
23
|
+
"@types/ws": "^8.18.1",
|
|
24
|
+
"tinybench": "^5.0.0",
|
|
25
|
+
"tsup": "^8.5.0",
|
|
26
|
+
"tsx": "^4.19.3",
|
|
27
|
+
"typescript": "^5.3.3",
|
|
28
|
+
"vitest": "^1.6.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup src/mod.ts",
|
|
32
|
+
"check-types": "tsc --noEmit",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"bench": "tsx benches/actor-lifecycle.bench.ts"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/mod.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import WebSocket from "ws";
|
|
1
|
+
import type WebSocket from "ws";
|
|
2
2
|
import { importWebSocket } from "./websocket.js";
|
|
3
3
|
import * as protocol from "@rivetkit/engine-runner-protocol";
|
|
4
4
|
import { unreachable, calculateBackoff } from "./utils";
|
|
5
5
|
import { Tunnel } from "./tunnel";
|
|
6
|
-
import { WebSocketTunnelAdapter } from "./websocket-tunnel-adapter";
|
|
6
|
+
import type { WebSocketTunnelAdapter } from "./websocket-tunnel-adapter";
|
|
7
7
|
import type { Logger } from "pino";
|
|
8
8
|
import { setLogger, logger } from "./log.js";
|
|
9
9
|
|
|
@@ -102,12 +102,14 @@ export class Runner {
|
|
|
102
102
|
#kvCleanupInterval?: NodeJS.Timeout;
|
|
103
103
|
|
|
104
104
|
// Tunnel for HTTP/WebSocket forwarding
|
|
105
|
-
#tunnel
|
|
105
|
+
#tunnel: Tunnel;
|
|
106
106
|
|
|
107
107
|
constructor(config: RunnerConfig) {
|
|
108
108
|
this.#config = config;
|
|
109
109
|
if (this.#config.logger) setLogger(this.#config.logger);
|
|
110
110
|
|
|
111
|
+
this.#tunnel = new Tunnel(this);
|
|
112
|
+
|
|
111
113
|
// TODO(RVT-4986): Prune when server acks events
|
|
112
114
|
// Start pruning old events every minute
|
|
113
115
|
this.#eventPruneInterval = setInterval(() => {
|
|
@@ -137,9 +139,7 @@ export class Runner {
|
|
|
137
139
|
if (!actor) return;
|
|
138
140
|
|
|
139
141
|
// Unregister actor from tunnel
|
|
140
|
-
|
|
141
|
-
this.#tunnel.unregisterActor(actor);
|
|
142
|
-
}
|
|
142
|
+
this.#tunnel.unregisterActor(actor);
|
|
143
143
|
|
|
144
144
|
// If onActorStop times out, Pegboard will handle this timeout with ACTOR_STOP_THRESHOLD_DURATION_MS
|
|
145
145
|
try {
|
|
@@ -244,10 +244,9 @@ export class Runner {
|
|
|
244
244
|
|
|
245
245
|
logger()?.info("starting runner");
|
|
246
246
|
|
|
247
|
+
this.#tunnel.start();
|
|
248
|
+
|
|
247
249
|
try {
|
|
248
|
-
// Connect tunnel first and wait for it to be ready before connecting runner WebSocket
|
|
249
|
-
// This prevents a race condition where the runner appears ready but can't accept network requests
|
|
250
|
-
await this.#openTunnelAndWait();
|
|
251
250
|
await this.#openPegboardWebSocket();
|
|
252
251
|
} catch (error) {
|
|
253
252
|
this.#started = false;
|
|
@@ -312,7 +311,7 @@ export class Runner {
|
|
|
312
311
|
// Close WebSocket
|
|
313
312
|
if (
|
|
314
313
|
this.#pegboardWebSocket &&
|
|
315
|
-
this.#pegboardWebSocket.readyState ===
|
|
314
|
+
this.#pegboardWebSocket.readyState === 1
|
|
316
315
|
) {
|
|
317
316
|
const pegboardWebSocket = this.#pegboardWebSocket;
|
|
318
317
|
if (immediate) {
|
|
@@ -334,7 +333,7 @@ export class Runner {
|
|
|
334
333
|
});
|
|
335
334
|
if (
|
|
336
335
|
this.#pegboardWebSocket &&
|
|
337
|
-
this.#pegboardWebSocket.readyState ===
|
|
336
|
+
this.#pegboardWebSocket.readyState === 1
|
|
338
337
|
) {
|
|
339
338
|
this.#pegboardWebSocket.send(encoded);
|
|
340
339
|
} else {
|
|
@@ -405,42 +404,7 @@ export class Runner {
|
|
|
405
404
|
const wsEndpoint = endpoint
|
|
406
405
|
.replace("http://", "ws://")
|
|
407
406
|
.replace("https://", "wss://");
|
|
408
|
-
return `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${this.#config.runnerKey}`;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
async #openTunnelAndWait(): Promise<void> {
|
|
412
|
-
return new Promise((resolve, reject) => {
|
|
413
|
-
const url = this.pegboardTunnelUrl;
|
|
414
|
-
logger()?.info({ msg: "opening tunnel to:", url });
|
|
415
|
-
logger()?.info({
|
|
416
|
-
msg: "current runner id:",
|
|
417
|
-
runnerId: this.runnerId || "none",
|
|
418
|
-
});
|
|
419
|
-
logger()?.info({
|
|
420
|
-
msg: "active actors count:",
|
|
421
|
-
actors: this.#actors.size,
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
let connected = false;
|
|
425
|
-
|
|
426
|
-
this.#tunnel = new Tunnel(this, url, {
|
|
427
|
-
onConnected: () => {
|
|
428
|
-
if (!connected) {
|
|
429
|
-
connected = true;
|
|
430
|
-
logger()?.info("tunnel connected");
|
|
431
|
-
resolve();
|
|
432
|
-
}
|
|
433
|
-
},
|
|
434
|
-
onDisconnected: () => {
|
|
435
|
-
if (!connected) {
|
|
436
|
-
// First connection attempt failed
|
|
437
|
-
reject(new Error("Tunnel connection failed"));
|
|
438
|
-
}
|
|
439
|
-
// If already connected, tunnel will handle reconnection automatically
|
|
440
|
-
},
|
|
441
|
-
});
|
|
442
|
-
this.#tunnel.start();
|
|
443
|
-
});
|
|
407
|
+
return `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_name=${encodeURIComponent(this.#config.runnerName)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;
|
|
444
408
|
}
|
|
445
409
|
|
|
446
410
|
// MARK: Runner protocol
|
|
@@ -448,7 +412,7 @@ export class Runner {
|
|
|
448
412
|
const WS = await importWebSocket();
|
|
449
413
|
const ws = new WS(this.pegboardUrl, {
|
|
450
414
|
headers: {
|
|
451
|
-
"x-rivet-target": "runner
|
|
415
|
+
"x-rivet-target": "runner",
|
|
452
416
|
},
|
|
453
417
|
}) as any as WebSocket;
|
|
454
418
|
this.#pegboardWebSocket = ws;
|
|
@@ -491,7 +455,7 @@ export class Runner {
|
|
|
491
455
|
metadata: JSON.stringify(this.#config.metadata),
|
|
492
456
|
};
|
|
493
457
|
|
|
494
|
-
this
|
|
458
|
+
this.__sendToServer({
|
|
495
459
|
tag: "ToServerInit",
|
|
496
460
|
val: init,
|
|
497
461
|
});
|
|
@@ -502,8 +466,8 @@ export class Runner {
|
|
|
502
466
|
// Start ping interval
|
|
503
467
|
const pingInterval = 1000;
|
|
504
468
|
const pingLoop = setInterval(() => {
|
|
505
|
-
if (ws.readyState ===
|
|
506
|
-
this
|
|
469
|
+
if (ws.readyState === 1) {
|
|
470
|
+
this.__sendToServer({
|
|
507
471
|
tag: "ToServerPing",
|
|
508
472
|
val: {
|
|
509
473
|
ts: BigInt(Date.now()),
|
|
@@ -519,7 +483,7 @@ export class Runner {
|
|
|
519
483
|
// Start command acknowledgment interval (5 minutes)
|
|
520
484
|
const ackInterval = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
521
485
|
const ackLoop = setInterval(() => {
|
|
522
|
-
if (ws.readyState ===
|
|
486
|
+
if (ws.readyState === 1) {
|
|
523
487
|
this.#sendCommandAcknowledgment();
|
|
524
488
|
} else {
|
|
525
489
|
clearInterval(ackLoop);
|
|
@@ -530,13 +494,13 @@ export class Runner {
|
|
|
530
494
|
});
|
|
531
495
|
|
|
532
496
|
ws.addEventListener("message", async (ev) => {
|
|
533
|
-
let buf;
|
|
497
|
+
let buf: Uint8Array;
|
|
534
498
|
if (ev.data instanceof Blob) {
|
|
535
499
|
buf = new Uint8Array(await ev.data.arrayBuffer());
|
|
536
500
|
} else if (Buffer.isBuffer(ev.data)) {
|
|
537
501
|
buf = new Uint8Array(ev.data);
|
|
538
502
|
} else {
|
|
539
|
-
throw new Error(
|
|
503
|
+
throw new Error(`expected binary data, got ${typeof ev.data}`);
|
|
540
504
|
}
|
|
541
505
|
|
|
542
506
|
// Parse message
|
|
@@ -545,7 +509,6 @@ export class Runner {
|
|
|
545
509
|
// Handle message
|
|
546
510
|
if (message.tag === "ToClientInit") {
|
|
547
511
|
const init = message.val;
|
|
548
|
-
const hadRunnerId = !!this.runnerId;
|
|
549
512
|
this.runnerId = init.runnerId;
|
|
550
513
|
|
|
551
514
|
// Store the runner lost threshold from metadata
|
|
@@ -572,6 +535,12 @@ export class Runner {
|
|
|
572
535
|
} else if (message.tag === "ToClientKvResponse") {
|
|
573
536
|
const kvResponse = message.val;
|
|
574
537
|
this.#handleKvResponse(kvResponse);
|
|
538
|
+
} else if (message.tag === "ToClientTunnelMessage") {
|
|
539
|
+
this.#tunnel?.handleTunnelMessage(message.val);
|
|
540
|
+
} else if (message.tag === "ToClientClose") {
|
|
541
|
+
// TODO: Close ws
|
|
542
|
+
} else {
|
|
543
|
+
unreachable(message);
|
|
575
544
|
}
|
|
576
545
|
});
|
|
577
546
|
|
|
@@ -633,6 +602,8 @@ export class Runner {
|
|
|
633
602
|
this.#handleCommandStartActor(commandWrapper);
|
|
634
603
|
} else if (commandWrapper.inner.tag === "CommandStopActor") {
|
|
635
604
|
this.#handleCommandStopActor(commandWrapper);
|
|
605
|
+
} else {
|
|
606
|
+
unreachable(commandWrapper.inner);
|
|
636
607
|
}
|
|
637
608
|
|
|
638
609
|
this.#lastCommandIdx = Number(commandWrapper.index);
|
|
@@ -743,7 +714,7 @@ export class Runner {
|
|
|
743
714
|
val: eventWrapper.inner.val,
|
|
744
715
|
});
|
|
745
716
|
|
|
746
|
-
this
|
|
717
|
+
this.__sendToServer({
|
|
747
718
|
tag: "ToServerEvents",
|
|
748
719
|
val: [eventWrapper],
|
|
749
720
|
});
|
|
@@ -804,7 +775,7 @@ export class Runner {
|
|
|
804
775
|
val: eventWrapper.inner.val,
|
|
805
776
|
});
|
|
806
777
|
|
|
807
|
-
this
|
|
778
|
+
this.__sendToServer({
|
|
808
779
|
tag: "ToServerEvents",
|
|
809
780
|
val: [eventWrapper],
|
|
810
781
|
});
|
|
@@ -825,7 +796,7 @@ export class Runner {
|
|
|
825
796
|
|
|
826
797
|
//logger()?.log("Sending command acknowledgment", this.#lastCommandIdx);
|
|
827
798
|
|
|
828
|
-
this
|
|
799
|
+
this.__sendToServer({
|
|
829
800
|
tag: "ToServerAckCommands",
|
|
830
801
|
val: {
|
|
831
802
|
lastCommandIdx: BigInt(this.#lastCommandIdx),
|
|
@@ -1154,7 +1125,7 @@ export class Runner {
|
|
|
1154
1125
|
timestamp: Date.now(),
|
|
1155
1126
|
});
|
|
1156
1127
|
|
|
1157
|
-
this
|
|
1128
|
+
this.__sendToServer({
|
|
1158
1129
|
tag: "ToServerEvents",
|
|
1159
1130
|
val: [eventWrapper],
|
|
1160
1131
|
});
|
|
@@ -1177,7 +1148,7 @@ export class Runner {
|
|
|
1177
1148
|
const requestId = this.#nextRequestId++;
|
|
1178
1149
|
const isConnected =
|
|
1179
1150
|
this.#pegboardWebSocket &&
|
|
1180
|
-
this.#pegboardWebSocket.readyState ===
|
|
1151
|
+
this.#pegboardWebSocket.readyState === 1;
|
|
1181
1152
|
|
|
1182
1153
|
// Store the request
|
|
1183
1154
|
const requestEntry = {
|
|
@@ -1209,7 +1180,7 @@ export class Runner {
|
|
|
1209
1180
|
data: request.data,
|
|
1210
1181
|
};
|
|
1211
1182
|
|
|
1212
|
-
this
|
|
1183
|
+
this.__sendToServer({
|
|
1213
1184
|
tag: "ToServerKvRequest",
|
|
1214
1185
|
val: kvRequest,
|
|
1215
1186
|
});
|
|
@@ -1226,7 +1197,7 @@ export class Runner {
|
|
|
1226
1197
|
#processUnsentKvRequests() {
|
|
1227
1198
|
if (
|
|
1228
1199
|
!this.#pegboardWebSocket ||
|
|
1229
|
-
this.#pegboardWebSocket.readyState !==
|
|
1200
|
+
this.#pegboardWebSocket.readyState !== 1
|
|
1230
1201
|
) {
|
|
1231
1202
|
return;
|
|
1232
1203
|
}
|
|
@@ -1244,7 +1215,13 @@ export class Runner {
|
|
|
1244
1215
|
}
|
|
1245
1216
|
}
|
|
1246
1217
|
|
|
1247
|
-
|
|
1218
|
+
__webSocketReady(): boolean {
|
|
1219
|
+
return this.#pegboardWebSocket
|
|
1220
|
+
? this.#pegboardWebSocket.readyState === 1
|
|
1221
|
+
: false;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
__sendToServer(message: protocol.ToServer) {
|
|
1248
1225
|
if (this.#shutdown) {
|
|
1249
1226
|
logger()?.warn(
|
|
1250
1227
|
"Runner is shut down, cannot send message to server",
|
|
@@ -1255,7 +1232,7 @@ export class Runner {
|
|
|
1255
1232
|
const encoded = protocol.encodeToServer(message);
|
|
1256
1233
|
if (
|
|
1257
1234
|
this.#pegboardWebSocket &&
|
|
1258
|
-
this.#pegboardWebSocket.readyState ===
|
|
1235
|
+
this.#pegboardWebSocket.readyState === 1
|
|
1259
1236
|
) {
|
|
1260
1237
|
this.#pegboardWebSocket.send(encoded);
|
|
1261
1238
|
} else {
|
|
@@ -1306,7 +1283,7 @@ export class Runner {
|
|
|
1306
1283
|
|
|
1307
1284
|
// Resend events in batches
|
|
1308
1285
|
const events = eventsToResend.map((item) => item.event);
|
|
1309
|
-
this
|
|
1286
|
+
this.__sendToServer({
|
|
1310
1287
|
tag: "ToServerEvents",
|
|
1311
1288
|
val: events,
|
|
1312
1289
|
});
|