rivetkit 2.0.5 → 2.0.7-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.
Files changed (178) hide show
  1. package/dist/schemas/actor-persist/v1.ts +0 -6
  2. package/dist/tsup/actor-router-consts-B3Lu87yJ.d.cts +28 -0
  3. package/dist/tsup/actor-router-consts-B3Lu87yJ.d.ts +28 -0
  4. package/dist/tsup/{chunk-5YTI25C3.cjs → chunk-3MBP4WNC.cjs} +7 -7
  5. package/dist/tsup/{chunk-5YTI25C3.cjs.map → chunk-3MBP4WNC.cjs.map} +1 -1
  6. package/dist/tsup/chunk-3Y45CIF4.cjs +3726 -0
  7. package/dist/tsup/chunk-3Y45CIF4.cjs.map +1 -0
  8. package/dist/tsup/chunk-4GP7BZSR.js +102 -0
  9. package/dist/tsup/chunk-4GP7BZSR.js.map +1 -0
  10. package/dist/tsup/chunk-5ZOHIKWG.cjs +4071 -0
  11. package/dist/tsup/chunk-5ZOHIKWG.cjs.map +1 -0
  12. package/dist/tsup/{chunk-WADSS5X4.cjs → chunk-6EUWRXLT.cjs} +21 -7
  13. package/dist/tsup/chunk-6EUWRXLT.cjs.map +1 -0
  14. package/dist/tsup/{chunk-D7NWUCRK.cjs → chunk-6OVKCDSH.cjs} +6 -6
  15. package/dist/tsup/{chunk-D7NWUCRK.cjs.map → chunk-6OVKCDSH.cjs.map} +1 -1
  16. package/dist/tsup/{chunk-I5VTWPHW.js → chunk-7N56ZUC7.js} +3 -3
  17. package/dist/tsup/{chunk-LZIBTLEY.cjs → chunk-B3TLRM4Q.cjs} +13 -25
  18. package/dist/tsup/chunk-B3TLRM4Q.cjs.map +1 -0
  19. package/dist/tsup/chunk-BW5DPM6Z.js +4071 -0
  20. package/dist/tsup/chunk-BW5DPM6Z.js.map +1 -0
  21. package/dist/tsup/chunk-DFS77KAA.cjs +1046 -0
  22. package/dist/tsup/chunk-DFS77KAA.cjs.map +1 -0
  23. package/dist/tsup/{chunk-PG3K2LI7.js → chunk-E4UVJKSV.js} +2 -2
  24. package/dist/tsup/chunk-G4ABMAQY.cjs +102 -0
  25. package/dist/tsup/chunk-G4ABMAQY.cjs.map +1 -0
  26. package/dist/tsup/{chunk-CKA54YQN.js → chunk-GZVBFXBI.js} +3 -15
  27. package/dist/tsup/chunk-GZVBFXBI.js.map +1 -0
  28. package/dist/tsup/chunk-HPT3I7UU.js +3726 -0
  29. package/dist/tsup/chunk-HPT3I7UU.js.map +1 -0
  30. package/dist/tsup/chunk-JD54PXWP.js +1046 -0
  31. package/dist/tsup/chunk-JD54PXWP.js.map +1 -0
  32. package/dist/tsup/{chunk-PHSQJ6QI.cjs → chunk-K4ENQCC4.cjs} +3 -3
  33. package/dist/tsup/{chunk-PHSQJ6QI.cjs.map → chunk-K4ENQCC4.cjs.map} +1 -1
  34. package/dist/tsup/{chunk-WNGOBAA7.js → chunk-PUSQNDJG.js} +2 -2
  35. package/dist/tsup/{chunk-CFFKMUYH.js → chunk-RVP5RUSC.js} +20 -6
  36. package/dist/tsup/chunk-RVP5RUSC.js.map +1 -0
  37. package/dist/tsup/chunk-SAZCNSVY.cjs +259 -0
  38. package/dist/tsup/chunk-SAZCNSVY.cjs.map +1 -0
  39. package/dist/tsup/{chunk-YW6Y6VNE.js → chunk-SBKRVQS2.js} +9 -5
  40. package/dist/tsup/chunk-SBKRVQS2.js.map +1 -0
  41. package/dist/tsup/{chunk-FGFT4FVX.cjs → chunk-TZGUSEIJ.cjs} +14 -10
  42. package/dist/tsup/chunk-TZGUSEIJ.cjs.map +1 -0
  43. package/dist/tsup/chunk-YQ4XQYPM.js +259 -0
  44. package/dist/tsup/chunk-YQ4XQYPM.js.map +1 -0
  45. package/dist/tsup/client/mod.cjs +9 -9
  46. package/dist/tsup/client/mod.d.cts +7 -8
  47. package/dist/tsup/client/mod.d.ts +7 -8
  48. package/dist/tsup/client/mod.js +8 -8
  49. package/dist/tsup/common/log.cjs +3 -3
  50. package/dist/tsup/common/log.js +2 -2
  51. package/dist/tsup/common/websocket.cjs +4 -4
  52. package/dist/tsup/common/websocket.js +3 -3
  53. package/dist/tsup/{connection-BvE-Oq7t.d.ts → conn-DCSQgIlw.d.ts} +1605 -1353
  54. package/dist/tsup/{connection-DTzmWwU5.d.cts → conn-DdzHTm2E.d.cts} +1605 -1353
  55. package/dist/tsup/driver-helpers/mod.cjs +31 -5
  56. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  57. package/dist/tsup/driver-helpers/mod.d.cts +7 -8
  58. package/dist/tsup/driver-helpers/mod.d.ts +7 -8
  59. package/dist/tsup/driver-helpers/mod.js +33 -7
  60. package/dist/tsup/driver-test-suite/mod.cjs +319 -216
  61. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  62. package/dist/tsup/driver-test-suite/mod.d.cts +7 -7
  63. package/dist/tsup/driver-test-suite/mod.d.ts +7 -7
  64. package/dist/tsup/driver-test-suite/mod.js +588 -485
  65. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  66. package/dist/tsup/inspector/mod.cjs +17 -5
  67. package/dist/tsup/inspector/mod.cjs.map +1 -1
  68. package/dist/tsup/inspector/mod.d.cts +34 -7
  69. package/dist/tsup/inspector/mod.d.ts +34 -7
  70. package/dist/tsup/inspector/mod.js +20 -8
  71. package/dist/tsup/mod.cjs +10 -17
  72. package/dist/tsup/mod.cjs.map +1 -1
  73. package/dist/tsup/mod.d.cts +56 -9
  74. package/dist/tsup/mod.d.ts +56 -9
  75. package/dist/tsup/mod.js +17 -24
  76. package/dist/tsup/test/mod.cjs +11 -9
  77. package/dist/tsup/test/mod.cjs.map +1 -1
  78. package/dist/tsup/test/mod.d.cts +6 -7
  79. package/dist/tsup/test/mod.d.ts +6 -7
  80. package/dist/tsup/test/mod.js +10 -8
  81. package/dist/tsup/utils.cjs +4 -2
  82. package/dist/tsup/utils.cjs.map +1 -1
  83. package/dist/tsup/utils.d.cts +11 -1
  84. package/dist/tsup/utils.d.ts +11 -1
  85. package/dist/tsup/utils.js +3 -1
  86. package/package.json +8 -4
  87. package/src/actor/action.ts +1 -1
  88. package/src/actor/config.ts +1 -1
  89. package/src/actor/conn-drivers.ts +205 -0
  90. package/src/actor/conn-socket.ts +6 -0
  91. package/src/actor/{connection.ts → conn.ts} +78 -84
  92. package/src/actor/context.ts +1 -1
  93. package/src/actor/driver.ts +4 -43
  94. package/src/actor/instance.ts +162 -86
  95. package/src/actor/mod.ts +6 -14
  96. package/src/actor/persisted.ts +2 -5
  97. package/src/actor/protocol/old.ts +1 -1
  98. package/src/actor/router-endpoints.ts +147 -138
  99. package/src/actor/router.ts +89 -52
  100. package/src/actor/utils.ts +5 -1
  101. package/src/client/actor-conn.ts +163 -31
  102. package/src/client/actor-handle.ts +0 -1
  103. package/src/client/client.ts +2 -2
  104. package/src/client/config.ts +7 -0
  105. package/src/client/raw-utils.ts +1 -1
  106. package/src/client/utils.ts +1 -1
  107. package/src/common/actor-router-consts.ts +59 -0
  108. package/src/common/router.ts +2 -1
  109. package/src/common/versioned-data.ts +5 -5
  110. package/src/driver-helpers/mod.ts +15 -2
  111. package/src/driver-test-suite/mod.ts +11 -2
  112. package/src/driver-test-suite/test-inline-client-driver.ts +40 -22
  113. package/src/driver-test-suite/tests/actor-conn-state.ts +66 -22
  114. package/src/driver-test-suite/tests/actor-conn.ts +65 -126
  115. package/src/driver-test-suite/tests/actor-reconnect.ts +160 -0
  116. package/src/driver-test-suite/tests/actor-sleep.ts +0 -1
  117. package/src/driver-test-suite/tests/raw-websocket.ts +0 -35
  118. package/src/driver-test-suite/utils.ts +8 -3
  119. package/src/drivers/default.ts +8 -7
  120. package/src/drivers/engine/actor-driver.ts +67 -44
  121. package/src/drivers/engine/config.ts +4 -0
  122. package/src/drivers/file-system/actor.ts +0 -6
  123. package/src/drivers/file-system/global-state.ts +3 -14
  124. package/src/drivers/file-system/manager.ts +12 -8
  125. package/src/inspector/actor.ts +4 -3
  126. package/src/inspector/config.ts +10 -1
  127. package/src/inspector/mod.ts +1 -0
  128. package/src/inspector/utils.ts +23 -4
  129. package/src/manager/driver.ts +12 -2
  130. package/src/manager/gateway.ts +407 -0
  131. package/src/manager/protocol/query.ts +1 -1
  132. package/src/manager/router.ts +269 -468
  133. package/src/manager-api/actors.ts +61 -0
  134. package/src/manager-api/common.ts +4 -0
  135. package/src/mod.ts +1 -1
  136. package/src/registry/mod.ts +126 -12
  137. package/src/registry/serve.ts +8 -3
  138. package/src/remote-manager-driver/actor-http-client.ts +30 -19
  139. package/src/remote-manager-driver/actor-websocket-client.ts +45 -18
  140. package/src/remote-manager-driver/api-endpoints.ts +19 -21
  141. package/src/remote-manager-driver/api-utils.ts +10 -1
  142. package/src/remote-manager-driver/mod.ts +53 -53
  143. package/src/remote-manager-driver/ws-proxy.ts +2 -9
  144. package/src/test/mod.ts +6 -2
  145. package/src/utils.ts +21 -2
  146. package/dist/tsup/chunk-2MD57QF4.js +0 -1794
  147. package/dist/tsup/chunk-2MD57QF4.js.map +0 -1
  148. package/dist/tsup/chunk-B2QGJGZQ.js +0 -338
  149. package/dist/tsup/chunk-B2QGJGZQ.js.map +0 -1
  150. package/dist/tsup/chunk-CFFKMUYH.js.map +0 -1
  151. package/dist/tsup/chunk-CKA54YQN.js.map +0 -1
  152. package/dist/tsup/chunk-FGFT4FVX.cjs.map +0 -1
  153. package/dist/tsup/chunk-IRMBWX36.cjs +0 -1794
  154. package/dist/tsup/chunk-IRMBWX36.cjs.map +0 -1
  155. package/dist/tsup/chunk-L7QRXNWP.js +0 -6562
  156. package/dist/tsup/chunk-L7QRXNWP.js.map +0 -1
  157. package/dist/tsup/chunk-LZIBTLEY.cjs.map +0 -1
  158. package/dist/tsup/chunk-MRZS2J4X.cjs +0 -6562
  159. package/dist/tsup/chunk-MRZS2J4X.cjs.map +0 -1
  160. package/dist/tsup/chunk-RM2SVURR.cjs +0 -338
  161. package/dist/tsup/chunk-RM2SVURR.cjs.map +0 -1
  162. package/dist/tsup/chunk-WADSS5X4.cjs.map +0 -1
  163. package/dist/tsup/chunk-YW6Y6VNE.js.map +0 -1
  164. package/dist/tsup/common-CXCe7s6i.d.cts +0 -218
  165. package/dist/tsup/common-CXCe7s6i.d.ts +0 -218
  166. package/dist/tsup/router-endpoints-CctffZNL.d.cts +0 -65
  167. package/dist/tsup/router-endpoints-DFm1BglJ.d.ts +0 -65
  168. package/src/actor/generic-conn-driver.ts +0 -246
  169. package/src/common/fake-event-source.ts +0 -267
  170. package/src/manager-api/routes/actors-create.ts +0 -16
  171. package/src/manager-api/routes/actors-delete.ts +0 -4
  172. package/src/manager-api/routes/actors-get-by-id.ts +0 -7
  173. package/src/manager-api/routes/actors-get-or-create-by-id.ts +0 -29
  174. package/src/manager-api/routes/actors-get.ts +0 -7
  175. package/src/manager-api/routes/common.ts +0 -18
  176. /package/dist/tsup/{chunk-I5VTWPHW.js.map → chunk-7N56ZUC7.js.map} +0 -0
  177. /package/dist/tsup/{chunk-PG3K2LI7.js.map → chunk-E4UVJKSV.js.map} +0 -0
  178. /package/dist/tsup/{chunk-WNGOBAA7.js.map → chunk-PUSQNDJG.js.map} +0 -0
@@ -1,64 +1,70 @@
1
1
  import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
2
2
  import * as cbor from "cbor-x";
3
- import { Hono } from "hono";
3
+ import {
4
+ Hono,
5
+ Context as HonoContext,
6
+ type MiddlewareHandler,
7
+ Next,
8
+ } from "hono";
4
9
  import { cors as corsMiddleware } from "hono/cors";
5
10
  import { createMiddleware } from "hono/factory";
6
- import type { WSContext } from "hono/ws";
11
+ import { streamSSE } from "hono/streaming";
7
12
  import invariant from "invariant";
8
13
  import { z } from "zod";
14
+ import { ActorNotFound, Unsupported } from "@/actor/errors";
15
+ import { serializeActorKey } from "@/actor/keys";
16
+ import type { Encoding, Transport } from "@/client/mod";
9
17
  import {
10
- ActorNotFound,
11
- FeatureNotImplemented,
12
- MissingActorHeader,
13
- Unsupported,
14
- WebSocketsNotEnabled,
15
- } from "@/actor/errors";
16
- import type { Encoding } from "@/client/mod";
18
+ WS_PROTOCOL_ACTOR,
19
+ WS_PROTOCOL_CONN_ID,
20
+ WS_PROTOCOL_CONN_PARAMS,
21
+ WS_PROTOCOL_CONN_TOKEN,
22
+ WS_PROTOCOL_ENCODING,
23
+ WS_PROTOCOL_PATH,
24
+ WS_PROTOCOL_TRANSPORT,
25
+ } from "@/common/actor-router-consts";
17
26
  import {
18
27
  handleRouteError,
19
28
  handleRouteNotFound,
20
29
  loggerMiddleware,
21
30
  } from "@/common/router";
22
- import { deconstructError, noopNext } from "@/common/utils";
23
- import { HEADER_ACTOR_ID } from "@/driver-helpers/mod";
31
+ import { deconstructError, noopNext, stringifyError } from "@/common/utils";
32
+ import { type ActorDriver, HEADER_ACTOR_ID } from "@/driver-helpers/mod";
24
33
  import type {
25
34
  TestInlineDriverCallRequest,
26
35
  TestInlineDriverCallResponse,
27
36
  } from "@/driver-test-suite/test-inline-client-driver";
28
37
  import { createManagerInspectorRouter } from "@/inspector/manager";
29
- import { secureInspector } from "@/inspector/utils";
38
+ import { isInspectorEnabled, secureInspector } from "@/inspector/utils";
30
39
  import {
31
40
  type ActorsCreateRequest,
32
41
  ActorsCreateRequestSchema,
42
+ type ActorsCreateResponse,
33
43
  ActorsCreateResponseSchema,
34
- } from "@/manager-api/routes/actors-create";
35
- import { ActorsDeleteResponseSchema } from "@/manager-api/routes/actors-delete";
36
- import { ActorsGetResponseSchema } from "@/manager-api/routes/actors-get";
37
- import { ActorsGetByIdResponseSchema } from "@/manager-api/routes/actors-get-by-id";
38
- import {
39
- type ActorsGetOrCreateByIdRequest,
40
- ActorsGetOrCreateByIdRequestSchema,
41
- ActorsGetOrCreateByIdResponseSchema,
42
- } from "@/manager-api/routes/actors-get-or-create-by-id";
43
- import { RivetIdSchema } from "@/manager-api/routes/common";
44
- import type { UniversalWebSocket, UpgradeWebSocketArgs } from "@/mod";
44
+ type ActorsGetOrCreateRequest,
45
+ ActorsGetOrCreateRequestSchema,
46
+ type ActorsGetOrCreateResponse,
47
+ ActorsGetOrCreateResponseSchema,
48
+ type ActorsListResponse,
49
+ ActorsListResponseSchema,
50
+ type Actor as ApiActor,
51
+ } from "@/manager-api/actors";
52
+ import { RivetIdSchema } from "@/manager-api/common";
45
53
  import type { RegistryConfig } from "@/registry/config";
46
54
  import type { RunConfig } from "@/registry/run-config";
47
- import { stringifyError } from "@/utils";
48
- import type { ManagerDriver } from "./driver";
55
+ import type { ActorOutput, ManagerDriver } from "./driver";
56
+ import { actorGateway, createTestWebSocketProxy } from "./gateway";
49
57
  import { logger } from "./log";
50
58
 
51
- function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
59
+ function buildOpenApiResponses<T>(schema: T) {
52
60
  return {
53
61
  200: {
54
62
  description: "Success",
55
- content: validateBody
56
- ? {
57
- "application/json": {
58
- schema,
59
- },
60
- }
61
- : {},
63
+ content: {
64
+ "application/json": {
65
+ schema,
66
+ },
67
+ },
62
68
  },
63
69
  400: {
64
70
  description: "User error",
@@ -73,8 +79,8 @@ export function createManagerRouter(
73
79
  registryConfig: RegistryConfig,
74
80
  runConfig: RunConfig,
75
81
  managerDriver: ManagerDriver,
76
- validateBody: boolean,
77
- ): { router: Hono; openapi: OpenAPIHono } {
82
+ serverlessActorDriverBuilder: (() => ActorDriver) | undefined,
83
+ ): { router: Hono; openapi: OpenAPIHono; cors: MiddlewareHandler } {
78
84
  const router = new OpenAPIHono({ strict: false }).basePath(
79
85
  runConfig.basePath,
80
86
  );
@@ -85,80 +91,55 @@ export function createManagerRouter(
85
91
  ? corsMiddleware(runConfig.cors)
86
92
  : createMiddleware((_c, next) => next());
87
93
 
88
- // Actor proxy middleware - intercept requests with x-rivet-target=actor
89
- router.use("*", cors, async (c, next) => {
90
- const target = c.req.header("x-rivet-target");
91
- const actorId = c.req.header("x-rivet-actor");
92
-
93
- if (target === "actor") {
94
- if (!actorId) {
95
- throw new MissingActorHeader();
96
- }
97
-
98
- logger().debug({
99
- msg: "proxying request to actor",
100
- actorId,
101
- path: c.req.path,
102
- method: c.req.method,
103
- });
104
-
105
- // Handle WebSocket upgrade
106
- if (c.req.header("upgrade") === "websocket") {
107
- const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
108
- if (!upgradeWebSocket) {
109
- throw new WebSocketsNotEnabled();
110
- }
111
-
112
- // For WebSocket, use the driver's proxyWebSocket method
113
- // Extract any additional headers that might be needed
114
- const encoding =
115
- c.req.header("X-RivetKit-Encoding") ||
116
- c.req.header("x-rivet-encoding") ||
117
- "json";
118
- const connParams =
119
- c.req.header("X-RivetKit-Conn-Params") ||
120
- c.req.header("x-rivet-conn-params");
121
- const authData =
122
- c.req.header("X-RivetKit-Auth-Data") ||
123
- c.req.header("x-rivet-auth-data");
124
-
125
- // Include query string if present
126
- const pathWithQuery = c.req.url.includes("?")
127
- ? c.req.path + c.req.url.substring(c.req.url.indexOf("?"))
128
- : c.req.path;
129
-
130
- return await managerDriver.proxyWebSocket(
131
- c,
132
- pathWithQuery,
133
- actorId,
134
- encoding as any, // Will be validated by driver
135
- connParams ? JSON.parse(connParams) : undefined,
136
- authData ? JSON.parse(authData) : undefined,
137
- );
138
- }
94
+ if (serverlessActorDriverBuilder) {
95
+ addServerlessRoutes(serverlessActorDriverBuilder, router, cors);
96
+ } else {
97
+ addManagerRoutes(registryConfig, runConfig, managerDriver, router, cors);
98
+ }
139
99
 
140
- // Handle regular HTTP requests
141
- // Preserve all headers except the routing headers
142
- const proxyHeaders = new Headers(c.req.raw.headers);
143
- proxyHeaders.delete("x-rivet-target");
144
- proxyHeaders.delete("x-rivet-actor");
100
+ // Error handling
101
+ router.notFound(handleRouteNotFound);
102
+ router.onError(handleRouteError);
145
103
 
146
- // Build the proxy request with the actor URL format
147
- const url = new URL(c.req.url);
148
- const proxyUrl = new URL(`http://actor${url.pathname}${url.search}`);
104
+ return { router: router as Hono, openapi: router, cors };
105
+ }
149
106
 
150
- const proxyRequest = new Request(proxyUrl, {
151
- method: c.req.raw.method,
152
- headers: proxyHeaders,
153
- body: c.req.raw.body,
154
- signal: c.req.raw.signal,
155
- });
107
+ function addServerlessRoutes(
108
+ serverlessActorDriverBuilder: () => ActorDriver,
109
+ router: OpenAPIHono,
110
+ cors: MiddlewareHandler,
111
+ ) {
112
+ // GET /
113
+ router.get("/", cors, (c) => {
114
+ return c.text(
115
+ "This is a RivetKit server.\n\nLearn more at https://rivetkit.org",
116
+ );
117
+ });
156
118
 
157
- return await managerDriver.proxyRequest(c, proxyRequest, actorId);
158
- }
119
+ // Serverless start endpoint
120
+ router.get("/start", cors, async (c) => {
121
+ const actorDriver = serverlessActorDriverBuilder();
122
+ invariant(
123
+ actorDriver.serverlessHandleStart,
124
+ "missing serverlessHandleStart on ActorDriver",
125
+ );
126
+ return await actorDriver.serverlessHandleStart(c);
127
+ });
159
128
 
160
- return next();
129
+ router.get("/health", cors, (c) => {
130
+ return c.text("ok");
161
131
  });
132
+ }
133
+
134
+ function addManagerRoutes(
135
+ registryConfig: RegistryConfig,
136
+ runConfig: RunConfig,
137
+ managerDriver: ManagerDriver,
138
+ router: OpenAPIHono,
139
+ cors: MiddlewareHandler,
140
+ ) {
141
+ // Actor gateway
142
+ router.use("*", cors, actorGateway.bind(undefined, runConfig, managerDriver));
162
143
 
163
144
  // GET /
164
145
  router.get("/", cors, (c) => {
@@ -167,72 +148,99 @@ export function createManagerRouter(
167
148
  );
168
149
  });
169
150
 
170
- // GET /actors/by-id
151
+ // GET /actors
171
152
  {
172
153
  const route = createRoute({
173
154
  middleware: [cors],
174
155
  method: "get",
175
- path: "/actors/by-id",
156
+ path: "/actors",
176
157
  request: {
177
158
  query: z.object({
178
159
  name: z.string(),
179
- key: z.string(),
160
+ actor_ids: z.string().optional(),
161
+ key: z.string().optional(),
180
162
  }),
181
163
  },
182
- responses: buildOpenApiResponses(
183
- ActorsGetByIdResponseSchema,
184
- validateBody,
185
- ),
164
+ responses: buildOpenApiResponses(ActorsListResponseSchema),
186
165
  });
187
166
 
188
167
  router.openapi(route, async (c) => {
189
- const { name, key } = c.req.valid("query");
168
+ const { name, actor_ids, key } = c.req.valid("query");
169
+
170
+ const actorIdsParsed = actor_ids
171
+ ? actor_ids
172
+ .split(",")
173
+ .map((id) => id.trim())
174
+ .filter((id) => id.length > 0)
175
+ : undefined;
176
+
177
+ const actors: ActorOutput[] = [];
178
+
179
+ if (actorIdsParsed) {
180
+ if (actorIdsParsed.length > 32) {
181
+ return c.json(
182
+ {
183
+ error: `Too many actor IDs. Maximum is 32, got ${actorIdsParsed.length}.`,
184
+ },
185
+ 400,
186
+ );
187
+ }
190
188
 
191
- // Get actor by key from the driver
192
- const actorOutput = await managerDriver.getWithKey({
193
- c,
194
- name,
195
- key: [key], // Convert string to ActorKey array
196
- });
189
+ if (actorIdsParsed.length === 0) {
190
+ return c.json<ActorsListResponse>({
191
+ actors: [],
192
+ });
193
+ }
194
+
195
+ for (const actorId of actorIdsParsed) {
196
+ if (name) {
197
+ const actorOutput = await managerDriver.getForId({
198
+ c,
199
+ name,
200
+ actorId,
201
+ });
202
+ if (actorOutput) {
203
+ actors.push(actorOutput);
204
+ }
205
+ }
206
+ }
207
+ } else if (key) {
208
+ const actorOutput = await managerDriver.getWithKey({
209
+ c,
210
+ name,
211
+ key: [key], // Convert string to ActorKey array
212
+ });
213
+ if (actorOutput) {
214
+ actors.push(actorOutput);
215
+ }
216
+ }
197
217
 
198
- return c.json({
199
- actor_id: actorOutput?.actorId || null,
218
+ return c.json<ActorsListResponse>({
219
+ actors: actors.map(createApiActor),
200
220
  });
201
221
  });
202
222
  }
203
223
 
204
- // PUT /actors/by-id
224
+ // PUT /actors
205
225
  {
206
226
  const route = createRoute({
207
- cors: [cors],
227
+ middleware: [cors],
208
228
  method: "put",
209
- path: "/actors/by-id",
229
+ path: "/actors",
210
230
  request: {
211
231
  body: {
212
- content: validateBody
213
- ? {
214
- "application/json": {
215
- schema: ActorsGetOrCreateByIdRequestSchema,
216
- },
217
- }
218
- : {},
232
+ content: {
233
+ "application/json": {
234
+ schema: ActorsGetOrCreateRequestSchema,
235
+ },
236
+ },
219
237
  },
220
238
  },
221
- responses: buildOpenApiResponses(
222
- ActorsGetOrCreateByIdResponseSchema,
223
- validateBody,
224
- ),
239
+ responses: buildOpenApiResponses(ActorsGetOrCreateResponseSchema),
225
240
  });
226
241
 
227
242
  router.openapi(route, async (c) => {
228
- const body = validateBody
229
- ? await c.req.json<ActorsGetOrCreateByIdRequest>()
230
- : await c.req.json();
231
-
232
- // Parse and validate the request body if validation is enabled
233
- if (validateBody) {
234
- ActorsGetOrCreateByIdRequestSchema.parse(body);
235
- }
243
+ const body = c.req.valid("json");
236
244
 
237
245
  // Check if actor already exists
238
246
  const existingActor = await managerDriver.getWithKey({
@@ -242,8 +250,8 @@ export function createManagerRouter(
242
250
  });
243
251
 
244
252
  if (existingActor) {
245
- return c.json({
246
- actor_id: existingActor.actorId,
253
+ return c.json<ActorsGetOrCreateResponse>({
254
+ actor: createApiActor(existingActor),
247
255
  created: false,
248
256
  });
249
257
  }
@@ -259,60 +267,13 @@ export function createManagerRouter(
259
267
  region: undefined, // Not provided in the request schema
260
268
  });
261
269
 
262
- return c.json({
263
- actor_id: newActor.actorId,
270
+ return c.json<ActorsGetOrCreateResponse>({
271
+ actor: createApiActor(newActor),
264
272
  created: true,
265
273
  });
266
274
  });
267
275
  }
268
276
 
269
- // GET /actors/{actor_id}
270
- {
271
- const route = createRoute({
272
- middleware: [cors],
273
- method: "get",
274
- path: "/actors/{actor_id}",
275
- request: {
276
- params: z.object({
277
- actor_id: RivetIdSchema,
278
- }),
279
- },
280
- responses: buildOpenApiResponses(ActorsGetResponseSchema, validateBody),
281
- });
282
-
283
- router.openapi(route, async (c) => {
284
- const { actor_id } = c.req.valid("param");
285
-
286
- // Get actor by ID from the driver
287
- const actorOutput = await managerDriver.getForId({
288
- c,
289
- name: "", // TODO: The API doesn't provide the name, this may need to be resolved
290
- actorId: actor_id,
291
- });
292
-
293
- if (!actorOutput) {
294
- throw new ActorNotFound(actor_id);
295
- }
296
-
297
- // Transform ActorOutput to match ActorSchema
298
- // Note: Some fields are not available from the driver and need defaults
299
- const actor = {
300
- actor_id: actorOutput.actorId,
301
- name: actorOutput.name,
302
- key: actorOutput.key,
303
- namespace_id: "default", // Assert default namespace
304
- runner_name_selector: "rivetkit", // Assert rivetkit runner
305
- create_ts: Date.now(), // Not available from driver
306
- connectable_ts: null,
307
- destroy_ts: null,
308
- sleep_ts: null,
309
- start_ts: null,
310
- };
311
-
312
- return c.json({ actor });
313
- });
314
- }
315
-
316
277
  // POST /actors
317
278
  {
318
279
  const route = createRoute({
@@ -321,30 +282,18 @@ export function createManagerRouter(
321
282
  path: "/actors",
322
283
  request: {
323
284
  body: {
324
- content: validateBody
325
- ? {
326
- "application/json": {
327
- schema: ActorsCreateRequestSchema,
328
- },
329
- }
330
- : {},
285
+ content: {
286
+ "application/json": {
287
+ schema: ActorsCreateRequestSchema,
288
+ },
289
+ },
331
290
  },
332
291
  },
333
- responses: buildOpenApiResponses(
334
- ActorsCreateResponseSchema,
335
- validateBody,
336
- ),
292
+ responses: buildOpenApiResponses(ActorsCreateResponseSchema),
337
293
  });
338
294
 
339
295
  router.openapi(route, async (c) => {
340
- const body = validateBody
341
- ? await c.req.json<ActorsCreateRequest>()
342
- : await c.req.json();
343
-
344
- // Parse and validate the request body if validation is enabled
345
- if (validateBody) {
346
- ActorsCreateRequestSchema.parse(body);
347
- }
296
+ const body = c.req.valid("json");
348
297
 
349
298
  // Create actor using the driver
350
299
  const actorOutput = await managerDriver.createActor({
@@ -358,20 +307,9 @@ export function createManagerRouter(
358
307
  });
359
308
 
360
309
  // Transform ActorOutput to match ActorSchema
361
- const actor = {
362
- actor_id: actorOutput.actorId,
363
- name: actorOutput.name,
364
- key: actorOutput.key,
365
- namespace_id: "default", // Assert default namespace
366
- runner_name_selector: "rivetkit", // Assert rivetkit runner
367
- create_ts: Date.now(),
368
- connectable_ts: null,
369
- destroy_ts: null,
370
- sleep_ts: null,
371
- start_ts: null,
372
- };
373
-
374
- return c.json({ actor });
310
+ const actor = createApiActor(actorOutput);
311
+
312
+ return c.json<ActorsCreateResponse>({ actor });
375
313
  });
376
314
  }
377
315
 
@@ -427,7 +365,8 @@ export function createManagerRouter(
427
365
  response = { err };
428
366
  }
429
367
 
430
- return c.body(cbor.encode(response));
368
+ // TODO: Remove any
369
+ return c.body(cbor.encode(response) as any);
431
370
  });
432
371
 
433
372
  router.get(".test/inline-driver/connect-websocket/*", async (c) => {
@@ -435,25 +374,52 @@ export function createManagerRouter(
435
374
  invariant(upgradeWebSocket, "websockets not supported on this platform");
436
375
 
437
376
  return upgradeWebSocket(async (c: any) => {
438
- const {
439
- path,
440
- actorId,
441
- params: paramsRaw,
442
- encodingKind,
443
- } = c.req.query() as {
444
- path: string;
445
- actorId: string;
446
- params?: string;
447
- encodingKind: Encoding;
448
- };
449
- const params =
450
- paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined;
377
+ // Extract information from sec-websocket-protocol header
378
+ const protocolHeader = c.req.header("sec-websocket-protocol") || "";
379
+ const protocols = protocolHeader.split(/,\s*/);
380
+
381
+ // Parse protocols to extract connection info
382
+ let actorId = "";
383
+ let encoding: Encoding = "bare";
384
+ let transport: Transport = "websocket";
385
+ let path = "";
386
+ let params: unknown;
387
+ let connId: string | undefined;
388
+ let connToken: string | undefined;
389
+
390
+ for (const protocol of protocols) {
391
+ if (protocol.startsWith(WS_PROTOCOL_ACTOR)) {
392
+ actorId = protocol.substring(WS_PROTOCOL_ACTOR.length);
393
+ } else if (protocol.startsWith(WS_PROTOCOL_ENCODING)) {
394
+ encoding = protocol.substring(
395
+ WS_PROTOCOL_ENCODING.length,
396
+ ) as Encoding;
397
+ } else if (protocol.startsWith(WS_PROTOCOL_TRANSPORT)) {
398
+ transport = protocol.substring(
399
+ WS_PROTOCOL_TRANSPORT.length,
400
+ ) as Transport;
401
+ } else if (protocol.startsWith(WS_PROTOCOL_PATH)) {
402
+ path = decodeURIComponent(
403
+ protocol.substring(WS_PROTOCOL_PATH.length),
404
+ );
405
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_PARAMS)) {
406
+ const paramsRaw = decodeURIComponent(
407
+ protocol.substring(WS_PROTOCOL_CONN_PARAMS.length),
408
+ );
409
+ params = JSON.parse(paramsRaw);
410
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_ID)) {
411
+ connId = protocol.substring(WS_PROTOCOL_CONN_ID.length);
412
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_TOKEN)) {
413
+ connToken = protocol.substring(WS_PROTOCOL_CONN_TOKEN.length);
414
+ }
415
+ }
451
416
 
452
417
  logger().debug({
453
418
  msg: "received test inline driver websocket",
454
419
  actorId,
455
420
  params,
456
- encodingKind,
421
+ encodingKind: encoding,
422
+ transport,
457
423
  path: path,
458
424
  });
459
425
 
@@ -461,11 +427,13 @@ export function createManagerRouter(
461
427
  const clientWsPromise = managerDriver.openWebSocket(
462
428
  path,
463
429
  actorId,
464
- encodingKind,
430
+ encoding,
465
431
  params,
432
+ connId,
433
+ connToken,
466
434
  );
467
435
 
468
- return await createTestWebSocketProxy(clientWsPromise, "standard");
436
+ return await createTestWebSocketProxy(clientWsPromise);
469
437
  })(c, noopNext());
470
438
  });
471
439
 
@@ -500,6 +468,7 @@ export function createManagerRouter(
500
468
  method: c.req.method,
501
469
  headers: c.req.raw.headers,
502
470
  body: c.req.raw.body,
471
+ duplex: "half",
503
472
  }),
504
473
  );
505
474
 
@@ -525,14 +494,60 @@ export function createManagerRouter(
525
494
  );
526
495
  }
527
496
  });
497
+
498
+ // Test endpoint to force disconnect a connection non-cleanly
499
+ router.post("/.test/force-disconnect", async (c) => {
500
+ const actorId = c.req.query("actor");
501
+ const connId = c.req.query("conn");
502
+
503
+ if (!actorId || !connId) {
504
+ return c.text("Missing actor or conn query parameters", 400);
505
+ }
506
+
507
+ logger().debug({
508
+ msg: "forcing unclean disconnect",
509
+ actorId,
510
+ connId,
511
+ });
512
+
513
+ try {
514
+ // Send a special request to the actor to force disconnect the connection
515
+ const response = await managerDriver.sendRequest(
516
+ actorId,
517
+ new Request(`http://actor/.test/force-disconnect?conn=${connId}`, {
518
+ method: "POST",
519
+ }),
520
+ );
521
+
522
+ if (!response.ok) {
523
+ const text = await response.text();
524
+ return c.text(
525
+ `Failed to force disconnect: ${text}`,
526
+ response.status as any,
527
+ );
528
+ }
529
+
530
+ return c.json({ success: true });
531
+ } catch (error) {
532
+ logger().error({
533
+ msg: "error forcing disconnect",
534
+ error: stringifyError(error),
535
+ });
536
+ return c.text(`Error: ${error}`, 500);
537
+ }
538
+ });
528
539
  }
529
540
 
541
+ router.get("/health", cors, (c) => {
542
+ return c.text("ok");
543
+ });
544
+
530
545
  managerDriver.modifyManagerRouter?.(
531
546
  registryConfig,
532
547
  router as unknown as Hono,
533
548
  );
534
549
 
535
- if (runConfig.inspector?.enabled) {
550
+ if (isInspectorEnabled(runConfig, "manager")) {
536
551
  if (!managerDriver.inspector) {
537
552
  throw new Unsupported("inspector");
538
553
  }
@@ -548,233 +563,19 @@ export function createManagerRouter(
548
563
  .route("/", createManagerInspectorRouter()),
549
564
  );
550
565
  }
551
-
552
- // Error handling
553
- router.notFound(handleRouteNotFound);
554
- router.onError(handleRouteError);
555
-
556
- return { router: router as Hono, openapi: router };
557
566
  }
558
- /**
559
- * Creates a WebSocket proxy for test endpoints that forwards messages between server and client WebSockets
560
- */
561
- async function createTestWebSocketProxy(
562
- clientWsPromise: Promise<UniversalWebSocket>,
563
- connectionType: string,
564
- ): Promise<UpgradeWebSocketArgs> {
565
- // Store a reference to the resolved WebSocket
566
- let clientWs: UniversalWebSocket | null = null;
567
- try {
568
- // Resolve the client WebSocket promise
569
- logger().debug({ msg: "awaiting client websocket promise" });
570
- const ws = await clientWsPromise;
571
- clientWs = ws;
572
- logger().debug({
573
- msg: "client websocket promise resolved",
574
- constructor: ws?.constructor.name,
575
- });
576
567
 
577
- // Wait for ws to open
578
- await new Promise<void>((resolve, reject) => {
579
- const onOpen = () => {
580
- logger().debug({ msg: "test websocket connection opened" });
581
- resolve();
582
- };
583
- const onError = (error: any) => {
584
- logger().error({ msg: "test websocket connection failed", error });
585
- reject(
586
- new Error(`Failed to open WebSocket: ${error.message || error}`),
587
- );
588
- };
589
- ws.addEventListener("open", onOpen);
590
- ws.addEventListener("error", onError);
591
- });
592
- } catch (error) {
593
- logger().error({
594
- msg: `failed to establish client ${connectionType} websocket connection`,
595
- error,
596
- });
597
- return {
598
- onOpen: (_evt, serverWs) => {
599
- serverWs.close(1011, "Failed to establish connection");
600
- },
601
- onMessage: () => {},
602
- onError: () => {},
603
- onClose: () => {},
604
- };
605
- }
606
-
607
- // Create WebSocket proxy handlers to relay messages between client and server
568
+ function createApiActor(actor: ActorOutput): ApiActor {
608
569
  return {
609
- onOpen: (_evt: any, serverWs: WSContext) => {
610
- logger().debug({
611
- msg: `test ${connectionType} websocket connection opened`,
612
- });
613
-
614
- // Check WebSocket type
615
- logger().debug({
616
- msg: "clientWs info",
617
- constructor: clientWs.constructor.name,
618
- hasAddEventListener: typeof clientWs.addEventListener === "function",
619
- readyState: clientWs.readyState,
620
- });
621
-
622
- // Add message handler to forward messages from client to server
623
- clientWs.addEventListener("message", (clientEvt: MessageEvent) => {
624
- logger().debug({
625
- msg: `test ${connectionType} websocket connection message from client`,
626
- dataType: typeof clientEvt.data,
627
- isBlob: clientEvt.data instanceof Blob,
628
- isArrayBuffer: clientEvt.data instanceof ArrayBuffer,
629
- dataConstructor: clientEvt.data?.constructor?.name,
630
- dataStr:
631
- typeof clientEvt.data === "string"
632
- ? clientEvt.data.substring(0, 100)
633
- : undefined,
634
- });
635
-
636
- if (serverWs.readyState === 1) {
637
- // OPEN
638
- // Handle Blob data
639
- if (clientEvt.data instanceof Blob) {
640
- clientEvt.data
641
- .arrayBuffer()
642
- .then((buffer) => {
643
- logger().debug({
644
- msg: "converted client blob to arraybuffer, sending to server",
645
- bufferSize: buffer.byteLength,
646
- });
647
- serverWs.send(buffer as any);
648
- })
649
- .catch((error) => {
650
- logger().error({
651
- msg: "failed to convert blob to arraybuffer",
652
- error,
653
- });
654
- });
655
- } else {
656
- logger().debug({
657
- msg: "sending client data directly to server",
658
- dataType: typeof clientEvt.data,
659
- dataLength:
660
- typeof clientEvt.data === "string"
661
- ? clientEvt.data.length
662
- : undefined,
663
- });
664
- serverWs.send(clientEvt.data as any);
665
- }
666
- }
667
- });
668
-
669
- // Add close handler to close server when client closes
670
- clientWs.addEventListener("close", (clientEvt: any) => {
671
- logger().debug({
672
- msg: `test ${connectionType} websocket connection closed`,
673
- });
674
-
675
- if (serverWs.readyState !== 3) {
676
- // Not CLOSED
677
- serverWs.close(clientEvt.code, clientEvt.reason);
678
- }
679
- });
680
-
681
- // Add error handler
682
- clientWs.addEventListener("error", () => {
683
- logger().debug({
684
- msg: `test ${connectionType} websocket connection error`,
685
- });
686
-
687
- if (serverWs.readyState !== 3) {
688
- // Not CLOSED
689
- serverWs.close(1011, "Error in client websocket");
690
- }
691
- });
692
- },
693
- onMessage: (evt: { data: any }) => {
694
- logger().debug({
695
- msg: "received message from server",
696
- dataType: typeof evt.data,
697
- isBlob: evt.data instanceof Blob,
698
- isArrayBuffer: evt.data instanceof ArrayBuffer,
699
- dataConstructor: evt.data?.constructor?.name,
700
- dataStr:
701
- typeof evt.data === "string" ? evt.data.substring(0, 100) : undefined,
702
- });
703
-
704
- // Forward messages from server websocket to client websocket
705
- if (clientWs.readyState === 1) {
706
- // OPEN
707
- // Handle Blob data
708
- if (evt.data instanceof Blob) {
709
- evt.data
710
- .arrayBuffer()
711
- .then((buffer) => {
712
- logger().debug({
713
- msg: "converted blob to arraybuffer, sending",
714
- bufferSize: buffer.byteLength,
715
- });
716
- clientWs.send(buffer);
717
- })
718
- .catch((error) => {
719
- logger().error({
720
- msg: "failed to convert blob to arraybuffer",
721
- error,
722
- });
723
- });
724
- } else {
725
- logger().debug({
726
- msg: "sending data directly",
727
- dataType: typeof evt.data,
728
- dataLength:
729
- typeof evt.data === "string" ? evt.data.length : undefined,
730
- });
731
- clientWs.send(evt.data);
732
- }
733
- }
734
- },
735
- onClose: (
736
- event: {
737
- wasClean: boolean;
738
- code: number;
739
- reason: string;
740
- },
741
- serverWs: WSContext,
742
- ) => {
743
- logger().debug({
744
- msg: `server ${connectionType} websocket closed`,
745
- wasClean: event.wasClean,
746
- code: event.code,
747
- reason: event.reason,
748
- });
749
-
750
- // HACK: Close socket in order to fix bug with Cloudflare leaving WS in closing state
751
- // https://github.com/cloudflare/workerd/issues/2569
752
- serverWs.close(1000, "hack_force_close");
753
-
754
- // Close the client websocket when the server websocket closes
755
- if (
756
- clientWs &&
757
- clientWs.readyState !== clientWs.CLOSED &&
758
- clientWs.readyState !== clientWs.CLOSING
759
- ) {
760
- // Don't pass code/message since this may affect how close events are triggered
761
- clientWs.close(1000, event.reason);
762
- }
763
- },
764
- onError: (error: unknown) => {
765
- logger().error({
766
- msg: `error in server ${connectionType} websocket`,
767
- error,
768
- });
769
-
770
- // Close the client websocket on error
771
- if (
772
- clientWs &&
773
- clientWs.readyState !== clientWs.CLOSED &&
774
- clientWs.readyState !== clientWs.CLOSING
775
- ) {
776
- clientWs.close(1011, "Error in server websocket");
777
- }
778
- },
570
+ actor_id: actor.actorId,
571
+ name: actor.name,
572
+ key: serializeActorKey(actor.key),
573
+ namespace_id: "default", // Assert default namespace
574
+ runner_name_selector: "rivetkit", // Assert rivetkit runner
575
+ create_ts: Date.now(),
576
+ connectable_ts: null,
577
+ destroy_ts: null,
578
+ sleep_ts: null,
579
+ start_ts: null,
779
580
  };
780
581
  }