rivetkit 2.0.19 → 2.0.21

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.
Files changed (169) hide show
  1. package/dist/tsup/actor/errors.cjs.map +1 -1
  2. package/dist/tsup/{chunk-UGLCR467.js → chunk-2POQCWMA.js} +481 -100
  3. package/dist/tsup/chunk-2POQCWMA.js.map +1 -0
  4. package/dist/tsup/{chunk-GBVUP7IH.js → chunk-3UIGKLZW.js} +6 -6
  5. package/dist/tsup/chunk-3UIGKLZW.js.map +1 -0
  6. package/dist/tsup/{chunk-NCVKAD3U.js → chunk-4OINFQBR.js} +3 -3
  7. package/dist/tsup/{chunk-2N5T57W5.cjs → chunk-65SAIRRY.cjs} +12 -12
  8. package/dist/tsup/chunk-65SAIRRY.cjs.map +1 -0
  9. package/dist/tsup/{chunk-SO6CSZPF.js → chunk-6G76WIWL.js} +2 -2
  10. package/dist/tsup/{chunk-SO6CSZPF.js.map → chunk-6G76WIWL.js.map} +1 -1
  11. package/dist/tsup/{chunk-UBS2ARYU.js → chunk-D2LS4X6E.js} +11 -5
  12. package/dist/tsup/chunk-D2LS4X6E.js.map +1 -0
  13. package/dist/tsup/{chunk-FO4Q36GQ.js → chunk-DYA34FHW.js} +2 -2
  14. package/dist/tsup/{chunk-7RKGZXDH.cjs → chunk-ELDFBXDV.cjs} +8 -4
  15. package/dist/tsup/chunk-ELDFBXDV.cjs.map +1 -0
  16. package/dist/tsup/{chunk-F4PHLUIT.cjs → chunk-FDJ3AVNB.cjs} +32 -26
  17. package/dist/tsup/chunk-FDJ3AVNB.cjs.map +1 -0
  18. package/dist/tsup/{chunk-U3PO7PEY.js → chunk-FUX6U6TL.js} +2 -2
  19. package/dist/tsup/chunk-FUX6U6TL.js.map +1 -0
  20. package/dist/tsup/{chunk-AYNDGM4A.cjs → chunk-HN7UXCYQ.cjs} +7 -7
  21. package/dist/tsup/chunk-HN7UXCYQ.cjs.map +1 -0
  22. package/dist/tsup/{chunk-6MI3RWWC.js → chunk-HUGSRAGL.js} +8 -4
  23. package/dist/tsup/chunk-HUGSRAGL.js.map +1 -0
  24. package/dist/tsup/{chunk-7BTAYSZC.cjs → chunk-JKOUXDK6.cjs} +16 -10
  25. package/dist/tsup/chunk-JKOUXDK6.cjs.map +1 -0
  26. package/dist/tsup/{chunk-EXP6CQEI.cjs → chunk-JTIBPF7N.cjs} +14 -14
  27. package/dist/tsup/chunk-JTIBPF7N.cjs.map +1 -0
  28. package/dist/tsup/chunk-KSRXX3Z4.cjs.map +1 -1
  29. package/dist/tsup/{chunk-RIK4JNIG.cjs → chunk-LMJHBF26.cjs} +454 -288
  30. package/dist/tsup/chunk-LMJHBF26.cjs.map +1 -0
  31. package/dist/tsup/{chunk-DGXMPCNI.cjs → chunk-LWGCMELP.cjs} +3 -3
  32. package/dist/tsup/chunk-LWGCMELP.cjs.map +1 -0
  33. package/dist/tsup/{chunk-ZB3DP5IR.cjs → chunk-M5BHNJHB.cjs} +630 -249
  34. package/dist/tsup/chunk-M5BHNJHB.cjs.map +1 -0
  35. package/dist/tsup/{chunk-J5PFJTK3.cjs → chunk-O4GUKGK4.cjs} +6 -6
  36. package/dist/tsup/chunk-O4GUKGK4.cjs.map +1 -0
  37. package/dist/tsup/{chunk-LWQDW6VP.js → chunk-RZZDFDB6.js} +13 -7
  38. package/dist/tsup/chunk-RZZDFDB6.js.map +1 -0
  39. package/dist/tsup/{chunk-DAZ2YBCM.js → chunk-VLR3TDHT.js} +2 -2
  40. package/dist/tsup/{chunk-DAAQFFK3.js → chunk-Y2QONT7B.js} +262 -96
  41. package/dist/tsup/chunk-Y2QONT7B.js.map +1 -0
  42. package/dist/tsup/{chunk-AXQWQIUS.cjs → chunk-ZNWE3XBT.cjs} +3 -3
  43. package/dist/tsup/chunk-ZNWE3XBT.cjs.map +1 -0
  44. package/dist/tsup/client/mod.cjs +9 -9
  45. package/dist/tsup/client/mod.cjs.map +1 -1
  46. package/dist/tsup/client/mod.d.cts +2 -2
  47. package/dist/tsup/client/mod.d.ts +2 -2
  48. package/dist/tsup/client/mod.js +8 -8
  49. package/dist/tsup/common/log.cjs +3 -3
  50. package/dist/tsup/common/log.cjs.map +1 -1
  51. package/dist/tsup/common/log.js +2 -2
  52. package/dist/tsup/common/websocket.cjs +4 -4
  53. package/dist/tsup/common/websocket.cjs.map +1 -1
  54. package/dist/tsup/common/websocket.js +3 -3
  55. package/dist/tsup/{conn-DAXlyhVg.d.ts → conn-Clu655RU.d.ts} +1 -0
  56. package/dist/tsup/{conn--6rFdSfD.d.cts → conn-lUvFLo_q.d.cts} +1 -0
  57. package/dist/tsup/driver-helpers/mod.cjs +5 -5
  58. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  59. package/dist/tsup/driver-helpers/mod.d.cts +1 -1
  60. package/dist/tsup/driver-helpers/mod.d.ts +1 -1
  61. package/dist/tsup/driver-helpers/mod.js +4 -4
  62. package/dist/tsup/driver-test-suite/mod.cjs +603 -294
  63. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  64. package/dist/tsup/driver-test-suite/mod.d.cts +1 -1
  65. package/dist/tsup/driver-test-suite/mod.d.ts +1 -1
  66. package/dist/tsup/driver-test-suite/mod.js +574 -265
  67. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  68. package/dist/tsup/inspector/mod.cjs +6 -6
  69. package/dist/tsup/inspector/mod.cjs.map +1 -1
  70. package/dist/tsup/inspector/mod.d.cts +68 -7
  71. package/dist/tsup/inspector/mod.d.ts +68 -7
  72. package/dist/tsup/inspector/mod.js +5 -5
  73. package/dist/tsup/mod.cjs +10 -10
  74. package/dist/tsup/mod.cjs.map +1 -1
  75. package/dist/tsup/mod.d.cts +2 -2
  76. package/dist/tsup/mod.d.ts +2 -2
  77. package/dist/tsup/mod.js +9 -9
  78. package/dist/tsup/test/mod.cjs +11 -11
  79. package/dist/tsup/test/mod.cjs.map +1 -1
  80. package/dist/tsup/test/mod.d.cts +1 -1
  81. package/dist/tsup/test/mod.d.ts +1 -1
  82. package/dist/tsup/test/mod.js +10 -10
  83. package/dist/tsup/utils.cjs +2 -2
  84. package/dist/tsup/utils.cjs.map +1 -1
  85. package/dist/tsup/utils.js +1 -1
  86. package/package.json +4 -3
  87. package/src/actor/config.ts +108 -15
  88. package/src/actor/conn-drivers.ts +2 -1
  89. package/src/actor/instance.ts +119 -35
  90. package/src/actor/keys.test.ts +13 -4
  91. package/src/actor/protocol/old.ts +10 -3
  92. package/src/actor/router-endpoints.ts +26 -16
  93. package/src/actor/router.ts +41 -13
  94. package/src/actor/unstable-react.ts +1 -1
  95. package/src/client/actor-common.ts +3 -1
  96. package/src/client/actor-conn.ts +44 -12
  97. package/src/client/actor-handle.ts +4 -1
  98. package/src/client/client.ts +32 -18
  99. package/src/client/utils.ts +21 -8
  100. package/src/common/actor-router-consts.ts +2 -0
  101. package/src/common/inline-websocket-adapter2.ts +24 -6
  102. package/src/common/log.ts +6 -2
  103. package/src/common/logfmt.ts +3 -1
  104. package/src/common/router.ts +3 -1
  105. package/src/common/utils.ts +6 -2
  106. package/src/driver-helpers/utils.ts +4 -1
  107. package/src/driver-test-suite/mod.ts +15 -4
  108. package/src/driver-test-suite/test-inline-client-driver.ts +35 -13
  109. package/src/driver-test-suite/tests/action-features.ts +6 -2
  110. package/src/driver-test-suite/tests/actor-conn-state.ts +18 -8
  111. package/src/driver-test-suite/tests/actor-conn.ts +35 -13
  112. package/src/driver-test-suite/tests/actor-handle.ts +35 -15
  113. package/src/driver-test-suite/tests/actor-inline-client.ts +34 -23
  114. package/src/driver-test-suite/tests/actor-inspector.ts +241 -131
  115. package/src/driver-test-suite/tests/actor-reconnect.ts +14 -4
  116. package/src/driver-test-suite/tests/actor-schedule.ts +12 -3
  117. package/src/driver-test-suite/tests/actor-sleep.ts +6 -3
  118. package/src/driver-test-suite/tests/actor-vars.ts +6 -2
  119. package/src/driver-test-suite/tests/manager-driver.ts +16 -6
  120. package/src/driver-test-suite/tests/raw-http-request-properties.ts +64 -25
  121. package/src/driver-test-suite/tests/raw-http.ts +17 -5
  122. package/src/driver-test-suite/tests/raw-websocket.ts +36 -12
  123. package/src/driver-test-suite/tests/request-access.ts +18 -8
  124. package/src/drivers/engine/actor-driver.ts +46 -25
  125. package/src/drivers/engine/config.ts +2 -1
  126. package/src/drivers/file-system/global-state.ts +58 -16
  127. package/src/drivers/file-system/manager.ts +35 -12
  128. package/src/drivers/file-system/mod.ts +6 -1
  129. package/src/drivers/file-system/utils.ts +8 -2
  130. package/src/engine-process/mod.ts +15 -4
  131. package/src/inspector/actor.ts +63 -23
  132. package/src/inspector/config.ts +2 -1
  133. package/src/inspector/manager.ts +10 -3
  134. package/src/inspector/utils.ts +2 -1
  135. package/src/manager/driver.ts +4 -1
  136. package/src/manager/gateway.ts +278 -8
  137. package/src/manager/hono-websocket-adapter.ts +33 -10
  138. package/src/manager/router-schema.ts +4 -2
  139. package/src/manager/router.ts +78 -12
  140. package/src/manager-api/actors.ts +2 -0
  141. package/src/registry/mod.ts +31 -9
  142. package/src/registry/run-config.ts +3 -1
  143. package/src/remote-manager-driver/api-endpoints.ts +2 -2
  144. package/src/remote-manager-driver/mod.ts +22 -5
  145. package/src/remote-manager-driver/ws-proxy.ts +21 -5
  146. package/src/serde.ts +6 -2
  147. package/src/test/mod.ts +2 -1
  148. package/src/utils.ts +6 -2
  149. package/dist/tsup/chunk-2N5T57W5.cjs.map +0 -1
  150. package/dist/tsup/chunk-6MI3RWWC.js.map +0 -1
  151. package/dist/tsup/chunk-7BTAYSZC.cjs.map +0 -1
  152. package/dist/tsup/chunk-7RKGZXDH.cjs.map +0 -1
  153. package/dist/tsup/chunk-AXQWQIUS.cjs.map +0 -1
  154. package/dist/tsup/chunk-AYNDGM4A.cjs.map +0 -1
  155. package/dist/tsup/chunk-DAAQFFK3.js.map +0 -1
  156. package/dist/tsup/chunk-DGXMPCNI.cjs.map +0 -1
  157. package/dist/tsup/chunk-EXP6CQEI.cjs.map +0 -1
  158. package/dist/tsup/chunk-F4PHLUIT.cjs.map +0 -1
  159. package/dist/tsup/chunk-GBVUP7IH.js.map +0 -1
  160. package/dist/tsup/chunk-J5PFJTK3.cjs.map +0 -1
  161. package/dist/tsup/chunk-LWQDW6VP.js.map +0 -1
  162. package/dist/tsup/chunk-RIK4JNIG.cjs.map +0 -1
  163. package/dist/tsup/chunk-U3PO7PEY.js.map +0 -1
  164. package/dist/tsup/chunk-UBS2ARYU.js.map +0 -1
  165. package/dist/tsup/chunk-UGLCR467.js.map +0 -1
  166. package/dist/tsup/chunk-ZB3DP5IR.cjs.map +0 -1
  167. /package/dist/tsup/{chunk-NCVKAD3U.js.map → chunk-4OINFQBR.js.map} +0 -0
  168. /package/dist/tsup/{chunk-FO4Q36GQ.js.map → chunk-DYA34FHW.js.map} +0 -0
  169. /package/dist/tsup/{chunk-DAZ2YBCM.js.map → chunk-VLR3TDHT.js.map} +0 -0
@@ -50,7 +50,9 @@ export function createActorInspectorRouter() {
50
50
  "/state",
51
51
  sValidator(
52
52
  "json",
53
- z.object({ patch: PatchSchema }).or(z.object({ replace: z.any() })),
53
+ z
54
+ .object({ patch: PatchSchema })
55
+ .or(z.object({ replace: z.any() })),
54
56
  ),
55
57
  async (c) => {
56
58
  if (!(await c.var.inspector.accessors.isStateEnabled())) {
@@ -77,7 +79,10 @@ export function createActorInspectorRouter() {
77
79
  await c.var.inspector.accessors.setState(newState);
78
80
 
79
81
  return c.json(
80
- { enabled: true, state: await c.var.inspector.accessors.getState() },
82
+ {
83
+ enabled: true,
84
+ state: await c.var.inspector.accessors.getState(),
85
+ },
81
86
  200,
82
87
  );
83
88
  },
@@ -92,13 +97,16 @@ export function createActorInspectorRouter() {
92
97
  return streamSSE(
93
98
  c,
94
99
  async (stream) => {
95
- unsub = c.var.inspector.emitter.on("stateUpdated", async (state) => {
96
- stream.writeSSE({
97
- data: JSON.stringify(state) || "",
98
- event: "state-update",
99
- id: String(id++),
100
- });
101
- });
100
+ unsub = c.var.inspector.emitter.on(
101
+ "stateUpdated",
102
+ async (state) => {
103
+ stream.writeSSE({
104
+ data: JSON.stringify(state) || "",
105
+ event: "state-update",
106
+ id: String(id++),
107
+ });
108
+ },
109
+ );
102
110
 
103
111
  const { promise } = promiseWithResolvers<void>();
104
112
 
@@ -110,7 +118,8 @@ export function createActorInspectorRouter() {
110
118
  );
111
119
  })
112
120
  .get("/connections", async (c) => {
113
- const connections = await c.var.inspector.accessors.getConnections();
121
+ const connections =
122
+ await c.var.inspector.accessors.getConnections();
114
123
  return c.json({ connections }, 200);
115
124
  })
116
125
  .get("/connections/stream", async (c) => {
@@ -119,15 +128,18 @@ export function createActorInspectorRouter() {
119
128
  return streamSSE(
120
129
  c,
121
130
  async (stream) => {
122
- unsub = c.var.inspector.emitter.on("connectionUpdated", async () => {
123
- stream.writeSSE({
124
- data: JSON.stringify(
125
- await c.var.inspector.accessors.getConnections(),
126
- ),
127
- event: "connection-update",
128
- id: String(id++),
129
- });
130
- });
131
+ unsub = c.var.inspector.emitter.on(
132
+ "connectionUpdated",
133
+ async () => {
134
+ stream.writeSSE({
135
+ data: JSON.stringify(
136
+ await c.var.inspector.accessors.getConnections(),
137
+ ),
138
+ event: "connection-update",
139
+ id: String(id++),
140
+ });
141
+ },
142
+ );
131
143
 
132
144
  const { promise } = promiseWithResolvers<void>();
133
145
 
@@ -154,7 +166,9 @@ export function createActorInspectorRouter() {
154
166
  async (stream) => {
155
167
  unsub = c.var.inspector.emitter.on("eventFired", () => {
156
168
  stream.writeSSE({
157
- data: JSON.stringify(c.var.inspector.lastRealtimeEvents),
169
+ data: JSON.stringify(
170
+ c.var.inspector.lastRealtimeEvents,
171
+ ),
158
172
  event: "realtime-event",
159
173
  id: String(id++),
160
174
  });
@@ -184,11 +198,15 @@ export function createActorInspectorRouter() {
184
198
  // Get list of tables
185
199
  const rows = await db.execute(`PRAGMA table_list`);
186
200
  const tables = TablesSchema.parse(rows).filter(
187
- (table) => table.schema !== "temp" && !table.name.startsWith("sqlite_"),
201
+ (table) =>
202
+ table.schema !== "temp" &&
203
+ !table.name.startsWith("sqlite_"),
188
204
  );
189
205
  // Get columns for each table
190
206
  const tablesInfo = await Promise.all(
191
- tables.map((table) => db.execute(`PRAGMA table_info(${table.name})`)),
207
+ tables.map((table) =>
208
+ db.execute(`PRAGMA table_info(${table.name})`),
209
+ ),
192
210
  );
193
211
  const columns = tablesInfo.map((def) => ColumnsSchema.parse(def));
194
212
 
@@ -231,7 +249,10 @@ export function createActorInspectorRouter() {
231
249
  "/db",
232
250
  sValidator(
233
251
  "json",
234
- z.object({ query: z.string(), params: z.array(z.any()).optional() }),
252
+ z.object({
253
+ query: z.string(),
254
+ params: z.array(z.any()).optional(),
255
+ }),
235
256
  ),
236
257
  async (c) => {
237
258
  if (!(await c.var.inspector.accessors.isDbEnabled())) {
@@ -250,6 +271,24 @@ export function createActorInspectorRouter() {
250
271
  return c.json({ error: (error as Error).message }, 500);
251
272
  }
252
273
  },
274
+ )
275
+ .post(
276
+ "/action",
277
+ sValidator(
278
+ "json",
279
+ z.object({
280
+ name: z.string(),
281
+ params: z.array(z.any()).optional(),
282
+ }),
283
+ ),
284
+ async (c) => {
285
+ const { name, params } = c.req.valid("json");
286
+ const result = await c.var.inspector.accessors.executeAction(
287
+ name,
288
+ params,
289
+ );
290
+ return c.json({ result }, 200);
291
+ },
253
292
  );
254
293
  }
255
294
 
@@ -261,6 +300,7 @@ interface ActorInspectorAccessors {
261
300
  getDb: () => Promise<InferDatabaseClient<AnyDatabaseProvider>>;
262
301
  getRpcs: () => Promise<string[]>;
263
302
  getConnections: () => Promise<Connection[]>;
303
+ executeAction: (name: string, params?: unknown[]) => Promise<unknown>;
264
304
  }
265
305
 
266
306
  interface ActorInspectorEmitterEvents {
@@ -35,7 +35,8 @@ const defaultCors: CorsOptions = {
35
35
  origin: (origin) => {
36
36
  if (
37
37
  defaultInspectorOrigins.includes(origin) ||
38
- (origin.startsWith("https://") && origin.endsWith("rivet-dev.vercel.app"))
38
+ (origin.startsWith("https://") &&
39
+ origin.endsWith("rivet-dev.vercel.app"))
39
40
  ) {
40
41
  return origin;
41
42
  } else {
@@ -21,7 +21,8 @@ export function createManagerInspectorRouter() {
21
21
  return c.json({ message: "pong" }, 200);
22
22
  })
23
23
  .get("/actors", async (c) => {
24
- const limit = Number.parseInt(c.req.query("limit") ?? "") || undefined;
24
+ const limit =
25
+ Number.parseInt(c.req.query("limit") ?? "") || undefined;
25
26
  const cursor = c.req.query("cursor") || undefined;
26
27
 
27
28
  if (!limit || (limit && limit <= 0)) {
@@ -35,7 +36,10 @@ export function createManagerInspectorRouter() {
35
36
  });
36
37
  return c.json(actors, 200);
37
38
  } catch (error) {
38
- inspectorLogger().error({ msg: "Failed to fetch actors", error });
39
+ inspectorLogger().error({
40
+ msg: "Failed to fetch actors",
41
+ error,
42
+ });
39
43
  return c.json("Failed to fetch actors", 500);
40
44
  }
41
45
  })
@@ -67,7 +71,10 @@ export function createManagerInspectorRouter() {
67
71
  }
68
72
 
69
73
  interface ManagerInspectorAccessors {
70
- getAllActors: (param: { cursor?: string; limit: number }) => Promise<Actor[]>;
74
+ getAllActors: (param: {
75
+ cursor?: string;
76
+ limit: number;
77
+ }) => Promise<Actor[]>;
71
78
  getActorById: (id: string) => Promise<Actor | null>;
72
79
  getBuilds: () => Promise<Builds>;
73
80
  createActor: (input: CreateInput) => Promise<Actor | null>;
@@ -66,7 +66,8 @@ export function getInspectorUrl(runConfig: RunnerConfigInput | undefined) {
66
66
  url.searchParams.set("t", accessToken);
67
67
 
68
68
  const overrideDefaultEndpoint =
69
- runConfig?.inspector?.defaultEndpoint ?? runConfig.overrideServerAddress;
69
+ runConfig?.inspector?.defaultEndpoint ??
70
+ runConfig.overrideServerAddress;
70
71
  if (overrideDefaultEndpoint) {
71
72
  url.searchParams.set("u", overrideDefaultEndpoint);
72
73
  }
@@ -43,7 +43,10 @@ export interface ManagerDriver {
43
43
 
44
44
  extraStartupLog?: () => Record<string, unknown>;
45
45
 
46
- modifyManagerRouter?: (registryConfig: RegistryConfig, router: Hono) => void;
46
+ modifyManagerRouter?: (
47
+ registryConfig: RegistryConfig,
48
+ router: Hono,
49
+ ) => void;
47
50
 
48
51
  /**
49
52
  * @internal
@@ -4,6 +4,7 @@ import { MissingActorHeader, WebSocketsNotEnabled } from "@/actor/errors";
4
4
  import type { Encoding, Transport } from "@/client/mod";
5
5
  import {
6
6
  HEADER_RIVET_ACTOR,
7
+ HEADER_RIVET_NAMESPACE,
7
8
  HEADER_RIVET_TARGET,
8
9
  WS_PROTOCOL_ACTOR,
9
10
  WS_PROTOCOL_CONN_ID,
@@ -11,6 +12,7 @@ import {
11
12
  WS_PROTOCOL_CONN_TOKEN,
12
13
  WS_PROTOCOL_ENCODING,
13
14
  WS_PROTOCOL_TARGET,
15
+ WS_PROTOCOL_TOKEN,
14
16
  } from "@/common/actor-router-consts";
15
17
  import { deconstructError, noopNext } from "@/common/utils";
16
18
  import type { UniversalWebSocket, UpgradeWebSocketArgs } from "@/mod";
@@ -19,10 +21,123 @@ import { promiseWithResolvers, stringifyError } from "@/utils";
19
21
  import type { ManagerDriver } from "./driver";
20
22
  import { logger } from "./log";
21
23
 
24
+ interface ActorPathInfo {
25
+ actorId: string;
26
+ token?: string;
27
+ remainingPath: string;
28
+ }
29
+
30
+ /**
31
+ * Handle path-based WebSocket routing
32
+ */
33
+ async function handleWebSocketGatewayPathBased(
34
+ runConfig: RunnerConfig,
35
+ managerDriver: ManagerDriver,
36
+ c: HonoContext,
37
+ actorPathInfo: ActorPathInfo,
38
+ ): Promise<Response> {
39
+ const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
40
+ if (!upgradeWebSocket) {
41
+ throw new WebSocketsNotEnabled();
42
+ }
43
+
44
+ // NOTE: Token validation implemented in EE
45
+
46
+ // Parse additional configuration from Sec-WebSocket-Protocol header
47
+ const protocols = c.req.header("sec-websocket-protocol");
48
+ let encodingRaw: string | undefined;
49
+ let connParamsRaw: string | undefined;
50
+ let connIdRaw: string | undefined;
51
+ let connTokenRaw: string | undefined;
52
+
53
+ if (protocols) {
54
+ const protocolList = protocols.split(",").map((p) => p.trim());
55
+ for (const protocol of protocolList) {
56
+ if (protocol.startsWith(WS_PROTOCOL_ENCODING)) {
57
+ encodingRaw = protocol.substring(WS_PROTOCOL_ENCODING.length);
58
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_PARAMS)) {
59
+ connParamsRaw = decodeURIComponent(
60
+ protocol.substring(WS_PROTOCOL_CONN_PARAMS.length),
61
+ );
62
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_ID)) {
63
+ connIdRaw = protocol.substring(WS_PROTOCOL_CONN_ID.length);
64
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_TOKEN)) {
65
+ connTokenRaw = protocol.substring(
66
+ WS_PROTOCOL_CONN_TOKEN.length,
67
+ );
68
+ }
69
+ }
70
+ }
71
+
72
+ logger().debug({
73
+ msg: "proxying websocket to actor via path-based routing",
74
+ actorId: actorPathInfo.actorId,
75
+ path: actorPathInfo.remainingPath,
76
+ encoding: encodingRaw,
77
+ });
78
+
79
+ const encoding = encodingRaw || "json";
80
+ const connParams = connParamsRaw ? JSON.parse(connParamsRaw) : undefined;
81
+
82
+ return await managerDriver.proxyWebSocket(
83
+ c,
84
+ actorPathInfo.remainingPath,
85
+ actorPathInfo.actorId,
86
+ encoding as any, // Will be validated by driver
87
+ connParams,
88
+ connIdRaw,
89
+ connTokenRaw,
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Handle path-based HTTP routing
95
+ */
96
+ async function handleHttpGatewayPathBased(
97
+ managerDriver: ManagerDriver,
98
+ c: HonoContext,
99
+ actorPathInfo: ActorPathInfo,
100
+ ): Promise<Response> {
101
+ // NOTE: Token validation implemented in EE
102
+
103
+ logger().debug({
104
+ msg: "proxying request to actor via path-based routing",
105
+ actorId: actorPathInfo.actorId,
106
+ path: actorPathInfo.remainingPath,
107
+ method: c.req.method,
108
+ });
109
+
110
+ // Preserve all headers
111
+ const proxyHeaders = new Headers(c.req.raw.headers);
112
+
113
+ // Build the proxy request with the actor URL format
114
+ const proxyUrl = new URL(`http://actor${actorPathInfo.remainingPath}`);
115
+
116
+ const proxyRequest = new Request(proxyUrl, {
117
+ method: c.req.raw.method,
118
+ headers: proxyHeaders,
119
+ body: c.req.raw.body,
120
+ signal: c.req.raw.signal,
121
+ duplex: "half",
122
+ } as RequestInit);
123
+
124
+ return await managerDriver.proxyRequest(
125
+ c,
126
+ proxyRequest,
127
+ actorPathInfo.actorId,
128
+ );
129
+ }
130
+
22
131
  /**
23
132
  * Provides an endpoint to connect to individual actors.
24
133
  *
25
- * Routes requests based on the Upgrade header:
134
+ * Routes requests using either path-based routing or header-based routing:
135
+ *
136
+ * Path-based routing (checked first):
137
+ * - /gateway/actors/{actor_id}/tokens/{token}/route/{...path}
138
+ * - /gateway/actors/{actor_id}/route/{...path}
139
+ *
140
+ * Header-based routing (fallback):
26
141
  * - WebSocket requests: Uses sec-websocket-protocol for routing (target.actor, actor.{id})
27
142
  * - HTTP requests: Uses x-rivet-target and x-rivet-actor headers for routing
28
143
  */
@@ -47,6 +162,40 @@ export async function actorGateway(
47
162
  }
48
163
  }
49
164
 
165
+ // Include query string if present (needed for parseActorPath to preserve query params)
166
+ const pathWithQuery = c.req.url.includes("?")
167
+ ? strippedPath + c.req.url.substring(c.req.url.indexOf("?"))
168
+ : strippedPath;
169
+
170
+ // First, check if this is an actor path-based route
171
+ const actorPathInfo = parseActorPath(pathWithQuery);
172
+ if (actorPathInfo) {
173
+ logger().debug({
174
+ msg: "routing using path-based actor routing",
175
+ actorPathInfo,
176
+ });
177
+
178
+ // Check if this is a WebSocket upgrade request
179
+ const isWebSocket = c.req.header("upgrade") === "websocket";
180
+
181
+ if (isWebSocket) {
182
+ return await handleWebSocketGatewayPathBased(
183
+ runConfig,
184
+ managerDriver,
185
+ c,
186
+ actorPathInfo,
187
+ );
188
+ }
189
+
190
+ // Handle regular HTTP requests
191
+ return await handleHttpGatewayPathBased(
192
+ managerDriver,
193
+ c,
194
+ actorPathInfo,
195
+ );
196
+ }
197
+
198
+ // Fallback to header-based routing
50
199
  // Check if this is a WebSocket upgrade request
51
200
  if (c.req.header("upgrade") === "websocket") {
52
201
  return await handleWebSocketGateway(
@@ -100,7 +249,9 @@ async function handleWebSocketGateway(
100
249
  } else if (protocol.startsWith(WS_PROTOCOL_CONN_ID)) {
101
250
  connIdRaw = protocol.substring(WS_PROTOCOL_CONN_ID.length);
102
251
  } else if (protocol.startsWith(WS_PROTOCOL_CONN_TOKEN)) {
103
- connTokenRaw = protocol.substring(WS_PROTOCOL_CONN_TOKEN.length);
252
+ connTokenRaw = protocol.substring(
253
+ WS_PROTOCOL_CONN_TOKEN.length,
254
+ );
104
255
  }
105
256
  }
106
257
  }
@@ -186,6 +337,113 @@ async function handleHttpGateway(
186
337
  return await managerDriver.proxyRequest(c, proxyRequest, actorId);
187
338
  }
188
339
 
340
+ /**
341
+ * Parse actor routing information from path
342
+ * Matches patterns:
343
+ * - /gateway/actors/{actor_id}/tokens/{token}/route/{...path}
344
+ * - /gateway/actors/{actor_id}/route/{...path}
345
+ */
346
+ export function parseActorPath(path: string): ActorPathInfo | null {
347
+ // Find query string position (everything from ? onwards, but before fragment)
348
+ const queryPos = path.indexOf("?");
349
+ const fragmentPos = path.indexOf("#");
350
+
351
+ // Extract query string (excluding fragment)
352
+ let queryString = "";
353
+ if (queryPos !== -1) {
354
+ if (fragmentPos !== -1 && queryPos < fragmentPos) {
355
+ queryString = path.slice(queryPos, fragmentPos);
356
+ } else {
357
+ queryString = path.slice(queryPos);
358
+ }
359
+ }
360
+
361
+ // Extract base path (before query and fragment)
362
+ let basePath = path;
363
+ if (queryPos !== -1) {
364
+ basePath = path.slice(0, queryPos);
365
+ } else if (fragmentPos !== -1) {
366
+ basePath = path.slice(0, fragmentPos);
367
+ }
368
+
369
+ // Check for double slashes (invalid path)
370
+ if (basePath.includes("//")) {
371
+ return null;
372
+ }
373
+
374
+ // Split the path into segments
375
+ const segments = basePath.split("/").filter((s) => s.length > 0);
376
+
377
+ // Check minimum required segments: gateway, actors, {actor_id}, route
378
+ if (segments.length < 4) {
379
+ return null;
380
+ }
381
+
382
+ // Verify the fixed segments
383
+ if (segments[0] !== "gateway" || segments[1] !== "actors") {
384
+ return null;
385
+ }
386
+
387
+ // Check for empty actor_id
388
+ if (segments[2].length === 0) {
389
+ return null;
390
+ }
391
+
392
+ const actorId = segments[2];
393
+
394
+ // Check for token or direct route
395
+ let token: string | undefined;
396
+ let remainingPathStartIdx: number;
397
+
398
+ if (
399
+ segments.length >= 6 &&
400
+ segments[3] === "tokens" &&
401
+ segments[5] === "route"
402
+ ) {
403
+ // Pattern with token: /gateway/actors/{actor_id}/tokens/{token}/route/{...path}
404
+ // Check for empty token
405
+ if (segments[4].length === 0) {
406
+ return null;
407
+ }
408
+ token = segments[4];
409
+ remainingPathStartIdx = 6;
410
+ } else if (segments.length >= 4 && segments[3] === "route") {
411
+ // Pattern without token: /gateway/actors/{actor_id}/route/{...path}
412
+ token = undefined;
413
+ remainingPathStartIdx = 4;
414
+ } else {
415
+ return null;
416
+ }
417
+
418
+ // Calculate the position in the original path where remaining path starts
419
+ let prefixLen = 0;
420
+ for (let i = 0; i < remainingPathStartIdx; i++) {
421
+ prefixLen += 1 + segments[i].length; // +1 for the slash
422
+ }
423
+
424
+ // Extract the remaining path preserving trailing slashes
425
+ let remainingBase: string;
426
+ if (prefixLen < basePath.length) {
427
+ remainingBase = basePath.slice(prefixLen);
428
+ } else {
429
+ remainingBase = "/";
430
+ }
431
+
432
+ // Ensure remaining path starts with /
433
+ let remainingPath: string;
434
+ if (remainingBase.length === 0 || !remainingBase.startsWith("/")) {
435
+ remainingPath = `/${remainingBase}${queryString}`;
436
+ } else {
437
+ remainingPath = `${remainingBase}${queryString}`;
438
+ }
439
+
440
+ return {
441
+ actorId,
442
+ token,
443
+ remainingPath,
444
+ };
445
+ }
446
+
189
447
  /**
190
448
  * Creates a WebSocket proxy for test endpoints that forwards messages between server and client WebSockets
191
449
  */
@@ -212,13 +470,20 @@ export async function createTestWebSocketProxy(
212
470
  // Wait for ws to open
213
471
  await new Promise<void>((resolve, reject) => {
214
472
  const onOpen = () => {
215
- logger().debug({ msg: "test websocket connection to actor opened" });
473
+ logger().debug({
474
+ msg: "test websocket connection to actor opened",
475
+ });
216
476
  resolve();
217
477
  };
218
478
  const onError = (error: any) => {
219
- logger().error({ msg: "test websocket connection failed", error });
479
+ logger().error({
480
+ msg: "test websocket connection failed",
481
+ error,
482
+ });
220
483
  reject(
221
- new Error(`Failed to open WebSocket: ${error.message || error}`),
484
+ new Error(
485
+ `Failed to open WebSocket: ${error.message || error}`,
486
+ ),
222
487
  );
223
488
  serverWsReject();
224
489
  };
@@ -327,7 +592,8 @@ export async function createTestWebSocketProxy(
327
592
  logger().debug({
328
593
  msg: "clientWs info",
329
594
  constructor: clientWs.constructor.name,
330
- hasAddEventListener: typeof clientWs.addEventListener === "function",
595
+ hasAddEventListener:
596
+ typeof clientWs.addEventListener === "function",
331
597
  readyState: clientWs.readyState,
332
598
  });
333
599
 
@@ -341,7 +607,9 @@ export async function createTestWebSocketProxy(
341
607
  isArrayBuffer: evt.data instanceof ArrayBuffer,
342
608
  dataConstructor: evt.data?.constructor?.name,
343
609
  dataStr:
344
- typeof evt.data === "string" ? evt.data.substring(0, 100) : undefined,
610
+ typeof evt.data === "string"
611
+ ? evt.data.substring(0, 100)
612
+ : undefined,
345
613
  });
346
614
 
347
615
  // Forward messages from server websocket to client websocket
@@ -369,7 +637,9 @@ export async function createTestWebSocketProxy(
369
637
  msg: "sending data directly",
370
638
  dataType: typeof evt.data,
371
639
  dataLength:
372
- typeof evt.data === "string" ? evt.data.length : undefined,
640
+ typeof evt.data === "string"
641
+ ? evt.data.length
642
+ : undefined,
373
643
  });
374
644
  clientWs.send(evt.data);
375
645
  }
@@ -76,7 +76,9 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
76
76
  isString: typeof data === "string",
77
77
  isArrayBuffer: data instanceof ArrayBuffer,
78
78
  dataStr:
79
- typeof data === "string" ? data.substring(0, 100) : "<non-string>",
79
+ typeof data === "string"
80
+ ? data.substring(0, 100)
81
+ : "<non-string>",
80
82
  });
81
83
 
82
84
  if (typeof data === "string") {
@@ -99,8 +101,7 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
99
101
  }
100
102
  } else if (data instanceof Blob) {
101
103
  // Convert Blob to ArrayBuffer
102
- data
103
- .arrayBuffer()
104
+ data.arrayBuffer()
104
105
  .then((buffer) => {
105
106
  (this.#ws as any).send(buffer);
106
107
  })
@@ -109,7 +110,11 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
109
110
  msg: "failed to convert blob to arraybuffer",
110
111
  error,
111
112
  });
112
- this.#fireEvent("error", { type: "error", target: this, error });
113
+ this.#fireEvent("error", {
114
+ type: "error",
115
+ target: this,
116
+ error,
117
+ });
113
118
  });
114
119
  } else {
115
120
  // Try to convert to string as a fallback
@@ -128,7 +133,10 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
128
133
  }
129
134
 
130
135
  close(code = 1000, reason = ""): void {
131
- if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {
136
+ if (
137
+ this.readyState === this.CLOSING ||
138
+ this.readyState === this.CLOSED
139
+ ) {
132
140
  return;
133
141
  }
134
142
 
@@ -260,7 +268,10 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
260
268
  try {
261
269
  listener(event);
262
270
  } catch (error) {
263
- logger().error({ msg: `error in ${type} event listener`, error });
271
+ logger().error({
272
+ msg: `error in ${type} event listener`,
273
+ error,
274
+ });
264
275
  }
265
276
  }
266
277
  }
@@ -272,7 +283,10 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
272
283
  try {
273
284
  this.#onopen(event);
274
285
  } catch (error) {
275
- logger().error({ msg: "error in onopen handler", error });
286
+ logger().error({
287
+ msg: "error in onopen handler",
288
+ error,
289
+ });
276
290
  }
277
291
  }
278
292
  break;
@@ -281,7 +295,10 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
281
295
  try {
282
296
  this.#onclose(event);
283
297
  } catch (error) {
284
- logger().error({ msg: "error in onclose handler", error });
298
+ logger().error({
299
+ msg: "error in onclose handler",
300
+ error,
301
+ });
285
302
  }
286
303
  }
287
304
  break;
@@ -290,7 +307,10 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
290
307
  try {
291
308
  this.#onerror(event);
292
309
  } catch (error) {
293
- logger().error({ msg: "error in onerror handler", error });
310
+ logger().error({
311
+ msg: "error in onerror handler",
312
+ error,
313
+ });
294
314
  }
295
315
  }
296
316
  break;
@@ -299,7 +319,10 @@ export class HonoWebSocketAdapter implements UniversalWebSocket {
299
319
  try {
300
320
  this.#onmessage(event);
301
321
  } catch (error) {
302
- logger().error({ msg: "error in onmessage handler", error });
322
+ logger().error({
323
+ msg: "error in onmessage handler",
324
+ error,
325
+ });
303
326
  }
304
327
  }
305
328
  break;