@rivetkit/engine-runner 2.1.5 → 2.1.6-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivetkit/engine-runner",
3
- "version": "2.1.5",
3
+ "version": "2.1.6-rc.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -22,7 +22,7 @@
22
22
  "pino": "^9.9.5",
23
23
  "ws": "^8.18.3",
24
24
  "@rivetkit/virtual-websocket": "2.0.33",
25
- "@rivetkit/engine-runner-protocol": "2.1.5"
25
+ "@rivetkit/engine-runner-protocol": "2.1.6-rc.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.18.1",
package/src/mod.ts CHANGED
@@ -12,13 +12,16 @@ import {
12
12
  unreachable,
13
13
  } from "./utils";
14
14
  import { importWebSocket } from "./websocket.js";
15
+ import {
16
+ v4 as uuidv4,
17
+ } from "uuid";
15
18
 
16
19
  export type { HibernatingWebSocketMetadata };
17
20
  export { RunnerActor, type ActorConfig };
18
21
  export { idToStr } from "./utils";
19
22
 
20
23
  const KV_EXPIRE: number = 30_000;
21
- const PROTOCOL_VERSION: number = 6;
24
+ const PROTOCOL_VERSION: number = 7;
22
25
 
23
26
  /** Warn once the backlog significantly exceeds the server's ack batch size. */
24
27
  const EVENT_BACKLOG_WARN_THRESHOLD = 10_000;
@@ -40,7 +43,6 @@ export interface RunnerConfig {
40
43
  namespace: string;
41
44
  totalSlots: number;
42
45
  runnerName: string;
43
- runnerKey: string;
44
46
  prepopulateActorNames: Record<string, { metadata: Record<string, any> }>;
45
47
  metadata?: Record<string, any>;
46
48
  onConnected: () => void;
@@ -175,6 +177,15 @@ export interface RunnerConfig {
175
177
 
176
178
  onActorStop: (actorId: string, generation: number) => Promise<void>;
177
179
  noAutoShutdown?: boolean;
180
+
181
+ /**
182
+ * Debug option to inject artificial latency (in ms) into WebSocket
183
+ * communication. Messages are queued and delivered in order after the
184
+ * configured delay.
185
+ *
186
+ * @experimental For testing only.
187
+ */
188
+ debugLatencyMs?: number;
178
189
  }
179
190
 
180
191
  export interface KvListOptions {
@@ -193,6 +204,7 @@ interface KvRequestEntry {
193
204
 
194
205
  export class Runner {
195
206
  #config: RunnerConfig;
207
+ #runnerKey: string = uuidv4();
196
208
 
197
209
  get config(): RunnerConfig {
198
210
  return this.#config;
@@ -443,6 +455,8 @@ export class Runner {
443
455
  throw error;
444
456
  }
445
457
 
458
+ // When changing SIGTERM/shutdown behavior, update
459
+ // website/src/content/docs/actors/versions.mdx (SIGTERM Handling section).
446
460
  if (!this.#config.noAutoShutdown) {
447
461
  if (!SIGNAL_HANDLERS.length) {
448
462
  process.on("SIGTERM", async () => {
@@ -614,6 +628,9 @@ export class Runner {
614
628
  * - All actors are stopped
615
629
  * - The WebSocket connection is closed
616
630
  * - The shutdown timeout is reached (120 seconds)
631
+ *
632
+ * When changing this timeout, update
633
+ * website/src/content/docs/actors/versions.mdx (SIGTERM Handling section).
617
634
  */
618
635
  async #waitForActorsToStop(ws: WebSocket): Promise<void> {
619
636
  const shutdownTimeout = 120_000; // 120 seconds
@@ -705,7 +722,7 @@ export class Runner {
705
722
  const baseUrl = wsEndpoint.endsWith("/")
706
723
  ? wsEndpoint.slice(0, -1)
707
724
  : wsEndpoint;
708
- return `${baseUrl}/runners/connect?protocol_version=${PROTOCOL_VERSION}&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;
725
+ return `${baseUrl}/runners/connect?protocol_version=${PROTOCOL_VERSION}&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${encodeURIComponent(this.#runnerKey)}`;
709
726
  }
710
727
 
711
728
  // MARK: Runner protocol
@@ -735,7 +752,7 @@ export class Runner {
735
752
  msg: "connecting",
736
753
  endpoint: this.pegboardEndpoint,
737
754
  namespace: this.#config.namespace,
738
- runnerKey: this.#config.runnerKey,
755
+ runnerKey: this.#runnerKey,
739
756
  hasToken: !!this.config.token,
740
757
  });
741
758
 
@@ -823,6 +840,8 @@ export class Runner {
823
840
  throw new Error(`expected binary data, got ${typeof ev.data}`);
824
841
  }
825
842
 
843
+ await this.#injectLatency();
844
+
826
845
  // Parse message
827
846
  const message = protocol.decodeToClient(buf);
828
847
  this.log?.debug({
@@ -1551,6 +1570,31 @@ export class Runner {
1551
1570
  await this.#sendKvRequest(actorId, requestData);
1552
1571
  }
1553
1572
 
1573
+ async kvDeleteRange(
1574
+ actorId: string,
1575
+ start: Uint8Array,
1576
+ end: Uint8Array,
1577
+ ): Promise<void> {
1578
+ const startKey: protocol.KvKey = start.buffer.slice(
1579
+ start.byteOffset,
1580
+ start.byteOffset + start.byteLength,
1581
+ ) as ArrayBuffer;
1582
+ const endKey: protocol.KvKey = end.buffer.slice(
1583
+ end.byteOffset,
1584
+ end.byteOffset + end.byteLength,
1585
+ ) as ArrayBuffer;
1586
+
1587
+ const requestData: protocol.KvRequestData = {
1588
+ tag: "KvDeleteRangeRequest",
1589
+ val: {
1590
+ start: startKey,
1591
+ end: endKey,
1592
+ },
1593
+ };
1594
+
1595
+ await this.#sendKvRequest(actorId, requestData);
1596
+ }
1597
+
1554
1598
  async kvDrop(actorId: string): Promise<void> {
1555
1599
  const requestData: protocol.KvRequestData = {
1556
1600
  tag: "KvDropRequest",
@@ -1662,6 +1706,13 @@ export class Runner {
1662
1706
  }
1663
1707
  }
1664
1708
 
1709
+ /** Resolves after the configured debug latency, or immediately if none. */
1710
+ #injectLatency(): Promise<void> {
1711
+ const ms = this.#config.debugLatencyMs;
1712
+ if (!ms) return Promise.resolve();
1713
+ return new Promise((resolve) => setTimeout(resolve, ms));
1714
+ }
1715
+
1665
1716
  /** Asserts WebSocket exists and is ready. */
1666
1717
  getPegboardWebSocketIfReady(): WebSocket | undefined {
1667
1718
  if (
@@ -1681,14 +1732,19 @@ export class Runner {
1681
1732
  });
1682
1733
 
1683
1734
  const encoded = protocol.encodeToServer(message);
1684
- const pegboardWebSocket = this.getPegboardWebSocketIfReady();
1685
- if (pegboardWebSocket) {
1686
- pegboardWebSocket.send(encoded);
1687
- } else {
1688
- this.log?.error({
1689
- msg: "WebSocket not available or not open for sending data",
1690
- });
1691
- }
1735
+
1736
+ // Normally synchronous. When debugLatencyMs is set, the send is
1737
+ // deferred but message order is preserved.
1738
+ this.#injectLatency().then(() => {
1739
+ const pegboardWebSocket = this.getPegboardWebSocketIfReady();
1740
+ if (pegboardWebSocket) {
1741
+ pegboardWebSocket.send(encoded);
1742
+ } else {
1743
+ this.log?.error({
1744
+ msg: "WebSocket not available or not open for sending data",
1745
+ });
1746
+ }
1747
+ });
1692
1748
  }
1693
1749
 
1694
1750
  sendHibernatableWebSocketMessageAck(
package/src/stringify.ts CHANGED
@@ -303,6 +303,10 @@ function stringifyKvRequestData(data: protocol.KvRequestData): string {
303
303
  const { keys } = data.val;
304
304
  return `KvDeleteRequest{keys: ${keys.length}}`;
305
305
  }
306
+ case "KvDeleteRangeRequest": {
307
+ const { start, end } = data.val;
308
+ return `KvDeleteRangeRequest{start: ${stringifyArrayBuffer(start)}, end: ${stringifyArrayBuffer(end)}}`;
309
+ }
306
310
  case "KvDropRequest":
307
311
  return "KvDropRequest";
308
312
  }
package/src/tunnel.ts CHANGED
@@ -5,11 +5,6 @@ import type {
5
5
  RequestId,
6
6
  } from "@rivetkit/engine-runner-protocol";
7
7
  import type { Logger } from "pino";
8
- import {
9
- parse as uuidparse,
10
- stringify as uuidstringify,
11
- v4 as uuidv4,
12
- } from "uuid";
13
8
  import { type Runner, type RunnerActor, RunnerShutdownError } from "./mod";
14
9
  import {
15
10
  stringifyToClientTunnelMessageKind,