rivetkit 2.0.2 → 2.0.4

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 (246) hide show
  1. package/README.md +3 -5
  2. package/dist/schemas/actor-persist/v1.ts +225 -0
  3. package/dist/schemas/client-protocol/v1.ts +435 -0
  4. package/dist/schemas/file-system-driver/v1.ts +102 -0
  5. package/dist/tsup/actor/errors.cjs +77 -0
  6. package/dist/tsup/actor/errors.cjs.map +1 -0
  7. package/dist/tsup/actor/errors.d.cts +156 -0
  8. package/dist/tsup/actor/errors.d.ts +156 -0
  9. package/dist/tsup/actor/errors.js +77 -0
  10. package/dist/tsup/actor/errors.js.map +1 -0
  11. package/dist/tsup/chunk-3F2YSRJL.js +117 -0
  12. package/dist/tsup/chunk-3F2YSRJL.js.map +1 -0
  13. package/dist/tsup/chunk-4CXBCT26.cjs +250 -0
  14. package/dist/tsup/chunk-4CXBCT26.cjs.map +1 -0
  15. package/dist/tsup/chunk-4R73YDN3.cjs +20 -0
  16. package/dist/tsup/chunk-4R73YDN3.cjs.map +1 -0
  17. package/dist/tsup/chunk-6LJT3QRL.cjs +539 -0
  18. package/dist/tsup/chunk-6LJT3QRL.cjs.map +1 -0
  19. package/dist/tsup/chunk-GICQ3YCU.cjs +1792 -0
  20. package/dist/tsup/chunk-GICQ3YCU.cjs.map +1 -0
  21. package/dist/tsup/chunk-H26RP6GD.js +251 -0
  22. package/dist/tsup/chunk-H26RP6GD.js.map +1 -0
  23. package/dist/tsup/chunk-HI3HWJRC.js +20 -0
  24. package/dist/tsup/chunk-HI3HWJRC.js.map +1 -0
  25. package/dist/tsup/chunk-HLLF4B4Q.js +1792 -0
  26. package/dist/tsup/chunk-HLLF4B4Q.js.map +1 -0
  27. package/dist/tsup/chunk-IH6CKNDW.cjs +117 -0
  28. package/dist/tsup/chunk-IH6CKNDW.cjs.map +1 -0
  29. package/dist/tsup/chunk-LV2S3OU3.js +250 -0
  30. package/dist/tsup/chunk-LV2S3OU3.js.map +1 -0
  31. package/dist/tsup/chunk-LWNKVZG5.cjs +251 -0
  32. package/dist/tsup/chunk-LWNKVZG5.cjs.map +1 -0
  33. package/dist/tsup/chunk-NFU2BBT5.js +374 -0
  34. package/dist/tsup/chunk-NFU2BBT5.js.map +1 -0
  35. package/dist/tsup/chunk-PQY7KKTL.js +539 -0
  36. package/dist/tsup/chunk-PQY7KKTL.js.map +1 -0
  37. package/dist/tsup/chunk-QK72M5JB.js +45 -0
  38. package/dist/tsup/chunk-QK72M5JB.js.map +1 -0
  39. package/dist/tsup/chunk-QNNXFOQV.cjs +45 -0
  40. package/dist/tsup/chunk-QNNXFOQV.cjs.map +1 -0
  41. package/dist/tsup/chunk-SBHHJ6QS.cjs +374 -0
  42. package/dist/tsup/chunk-SBHHJ6QS.cjs.map +1 -0
  43. package/dist/tsup/chunk-TQ62L3X7.js +325 -0
  44. package/dist/tsup/chunk-TQ62L3X7.js.map +1 -0
  45. package/dist/tsup/chunk-VO7ZRVVD.cjs +6293 -0
  46. package/dist/tsup/chunk-VO7ZRVVD.cjs.map +1 -0
  47. package/dist/tsup/chunk-WHBPJNGW.cjs +325 -0
  48. package/dist/tsup/chunk-WHBPJNGW.cjs.map +1 -0
  49. package/dist/tsup/chunk-XJQHKJ4P.js +6293 -0
  50. package/dist/tsup/chunk-XJQHKJ4P.js.map +1 -0
  51. package/dist/tsup/client/mod.cjs +32 -0
  52. package/dist/tsup/client/mod.cjs.map +1 -0
  53. package/dist/tsup/client/mod.d.cts +20 -0
  54. package/dist/tsup/client/mod.d.ts +20 -0
  55. package/dist/tsup/client/mod.js +32 -0
  56. package/dist/tsup/client/mod.js.map +1 -0
  57. package/dist/tsup/common/log.cjs +21 -0
  58. package/dist/tsup/common/log.cjs.map +1 -0
  59. package/dist/tsup/common/log.d.cts +26 -0
  60. package/dist/tsup/common/log.d.ts +26 -0
  61. package/dist/tsup/common/log.js +21 -0
  62. package/dist/tsup/common/log.js.map +1 -0
  63. package/dist/tsup/common/websocket.cjs +10 -0
  64. package/dist/tsup/common/websocket.cjs.map +1 -0
  65. package/dist/tsup/common/websocket.d.cts +3 -0
  66. package/dist/tsup/common/websocket.d.ts +3 -0
  67. package/dist/tsup/common/websocket.js +10 -0
  68. package/dist/tsup/common/websocket.js.map +1 -0
  69. package/dist/tsup/common-CXCe7s6i.d.cts +218 -0
  70. package/dist/tsup/common-CXCe7s6i.d.ts +218 -0
  71. package/dist/tsup/connection-BI-6UIBJ.d.ts +2087 -0
  72. package/dist/tsup/connection-Dyd4NLGW.d.cts +2087 -0
  73. package/dist/tsup/driver-helpers/mod.cjs +30 -0
  74. package/dist/tsup/driver-helpers/mod.cjs.map +1 -0
  75. package/dist/tsup/driver-helpers/mod.d.cts +17 -0
  76. package/dist/tsup/driver-helpers/mod.d.ts +17 -0
  77. package/dist/tsup/driver-helpers/mod.js +30 -0
  78. package/dist/tsup/driver-helpers/mod.js.map +1 -0
  79. package/dist/tsup/driver-test-suite/mod.cjs +3411 -0
  80. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -0
  81. package/dist/tsup/driver-test-suite/mod.d.cts +63 -0
  82. package/dist/tsup/driver-test-suite/mod.d.ts +63 -0
  83. package/dist/tsup/driver-test-suite/mod.js +3411 -0
  84. package/dist/tsup/driver-test-suite/mod.js.map +1 -0
  85. package/dist/tsup/inspector/mod.cjs +51 -0
  86. package/dist/tsup/inspector/mod.cjs.map +1 -0
  87. package/dist/tsup/inspector/mod.d.cts +408 -0
  88. package/dist/tsup/inspector/mod.d.ts +408 -0
  89. package/dist/tsup/inspector/mod.js +51 -0
  90. package/dist/tsup/inspector/mod.js.map +1 -0
  91. package/dist/tsup/mod.cjs +67 -0
  92. package/dist/tsup/mod.cjs.map +1 -0
  93. package/dist/tsup/mod.d.cts +105 -0
  94. package/dist/tsup/mod.d.ts +105 -0
  95. package/dist/tsup/mod.js +67 -0
  96. package/dist/tsup/mod.js.map +1 -0
  97. package/dist/tsup/router-endpoints-BTe_Rsdn.d.cts +65 -0
  98. package/dist/tsup/router-endpoints-CBSrKHmo.d.ts +65 -0
  99. package/dist/tsup/test/mod.cjs +17 -0
  100. package/dist/tsup/test/mod.cjs.map +1 -0
  101. package/dist/tsup/test/mod.d.cts +26 -0
  102. package/dist/tsup/test/mod.d.ts +26 -0
  103. package/dist/tsup/test/mod.js +17 -0
  104. package/dist/tsup/test/mod.js.map +1 -0
  105. package/dist/tsup/utils-fwx3o3K9.d.cts +18 -0
  106. package/dist/tsup/utils-fwx3o3K9.d.ts +18 -0
  107. package/dist/tsup/utils.cjs +26 -0
  108. package/dist/tsup/utils.cjs.map +1 -0
  109. package/dist/tsup/utils.d.cts +36 -0
  110. package/dist/tsup/utils.d.ts +36 -0
  111. package/dist/tsup/utils.js +26 -0
  112. package/dist/tsup/utils.js.map +1 -0
  113. package/package.json +208 -5
  114. package/src/actor/action.ts +178 -0
  115. package/src/actor/config.ts +497 -0
  116. package/src/actor/connection.ts +257 -0
  117. package/src/actor/context.ts +168 -0
  118. package/src/actor/database.ts +23 -0
  119. package/src/actor/definition.ts +82 -0
  120. package/src/actor/driver.ts +84 -0
  121. package/src/actor/errors.ts +422 -0
  122. package/src/actor/generic-conn-driver.ts +246 -0
  123. package/src/actor/instance.ts +1844 -0
  124. package/src/actor/keys.test.ts +266 -0
  125. package/src/actor/keys.ts +89 -0
  126. package/src/actor/log.ts +6 -0
  127. package/src/actor/mod.ts +108 -0
  128. package/src/actor/persisted.ts +42 -0
  129. package/src/actor/protocol/old.ts +297 -0
  130. package/src/actor/protocol/serde.ts +131 -0
  131. package/src/actor/router-endpoints.ts +688 -0
  132. package/src/actor/router.ts +265 -0
  133. package/src/actor/schedule.ts +17 -0
  134. package/src/actor/unstable-react.ts +110 -0
  135. package/src/actor/utils.ts +102 -0
  136. package/src/client/actor-common.ts +30 -0
  137. package/src/client/actor-conn.ts +865 -0
  138. package/src/client/actor-handle.ts +268 -0
  139. package/src/client/actor-query.ts +65 -0
  140. package/src/client/client.ts +554 -0
  141. package/src/client/config.ts +44 -0
  142. package/src/client/errors.ts +42 -0
  143. package/src/client/log.ts +5 -0
  144. package/src/client/mod.ts +60 -0
  145. package/src/client/raw-utils.ts +149 -0
  146. package/src/client/test.ts +44 -0
  147. package/src/client/utils.ts +152 -0
  148. package/src/common/eventsource-interface.ts +47 -0
  149. package/src/common/eventsource.ts +80 -0
  150. package/src/common/fake-event-source.ts +267 -0
  151. package/src/common/inline-websocket-adapter2.ts +454 -0
  152. package/src/common/log-levels.ts +27 -0
  153. package/src/common/log.ts +214 -0
  154. package/src/common/logfmt.ts +219 -0
  155. package/src/common/network.ts +2 -0
  156. package/src/common/router.ts +80 -0
  157. package/src/common/utils.ts +336 -0
  158. package/src/common/versioned-data.ts +95 -0
  159. package/src/common/websocket-interface.ts +49 -0
  160. package/src/common/websocket.ts +42 -0
  161. package/src/driver-helpers/mod.ts +22 -0
  162. package/src/driver-helpers/utils.ts +17 -0
  163. package/src/driver-test-suite/log.ts +5 -0
  164. package/src/driver-test-suite/mod.ts +239 -0
  165. package/src/driver-test-suite/tests/action-features.ts +136 -0
  166. package/src/driver-test-suite/tests/actor-conn-state.ts +249 -0
  167. package/src/driver-test-suite/tests/actor-conn.ts +349 -0
  168. package/src/driver-test-suite/tests/actor-driver.ts +25 -0
  169. package/src/driver-test-suite/tests/actor-error-handling.ts +158 -0
  170. package/src/driver-test-suite/tests/actor-handle.ts +292 -0
  171. package/src/driver-test-suite/tests/actor-inline-client.ts +152 -0
  172. package/src/driver-test-suite/tests/actor-inspector.ts +570 -0
  173. package/src/driver-test-suite/tests/actor-metadata.ts +116 -0
  174. package/src/driver-test-suite/tests/actor-onstatechange.ts +95 -0
  175. package/src/driver-test-suite/tests/actor-schedule.ts +108 -0
  176. package/src/driver-test-suite/tests/actor-sleep.ts +413 -0
  177. package/src/driver-test-suite/tests/actor-state.ts +54 -0
  178. package/src/driver-test-suite/tests/actor-vars.ts +93 -0
  179. package/src/driver-test-suite/tests/manager-driver.ts +367 -0
  180. package/src/driver-test-suite/tests/raw-http-direct-registry.ts +227 -0
  181. package/src/driver-test-suite/tests/raw-http-request-properties.ts +414 -0
  182. package/src/driver-test-suite/tests/raw-http.ts +347 -0
  183. package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +393 -0
  184. package/src/driver-test-suite/tests/raw-websocket.ts +484 -0
  185. package/src/driver-test-suite/tests/request-access.ts +230 -0
  186. package/src/driver-test-suite/utils.ts +71 -0
  187. package/src/drivers/default.ts +34 -0
  188. package/src/drivers/engine/actor-driver.ts +369 -0
  189. package/src/drivers/engine/config.ts +31 -0
  190. package/src/drivers/engine/kv.ts +3 -0
  191. package/src/drivers/engine/log.ts +5 -0
  192. package/src/drivers/engine/mod.ts +35 -0
  193. package/src/drivers/file-system/actor.ts +91 -0
  194. package/src/drivers/file-system/global-state.ts +686 -0
  195. package/src/drivers/file-system/log.ts +5 -0
  196. package/src/drivers/file-system/manager.ts +329 -0
  197. package/src/drivers/file-system/mod.ts +48 -0
  198. package/src/drivers/file-system/utils.ts +109 -0
  199. package/src/globals.d.ts +6 -0
  200. package/src/inspector/actor.ts +298 -0
  201. package/src/inspector/config.ts +88 -0
  202. package/src/inspector/log.ts +5 -0
  203. package/src/inspector/manager.ts +86 -0
  204. package/src/inspector/mod.ts +2 -0
  205. package/src/inspector/protocol/actor.ts +10 -0
  206. package/src/inspector/protocol/common.ts +196 -0
  207. package/src/inspector/protocol/manager.ts +10 -0
  208. package/src/inspector/protocol/mod.ts +2 -0
  209. package/src/inspector/utils.ts +76 -0
  210. package/src/manager/driver.ts +88 -0
  211. package/src/manager/hono-websocket-adapter.ts +342 -0
  212. package/src/manager/log.ts +5 -0
  213. package/src/manager/mod.ts +2 -0
  214. package/src/manager/protocol/mod.ts +24 -0
  215. package/src/manager/protocol/query.ts +89 -0
  216. package/src/manager/router.ts +412 -0
  217. package/src/manager-api/routes/actors-create.ts +16 -0
  218. package/src/manager-api/routes/actors-delete.ts +4 -0
  219. package/src/manager-api/routes/actors-get-by-id.ts +7 -0
  220. package/src/manager-api/routes/actors-get-or-create-by-id.ts +29 -0
  221. package/src/manager-api/routes/actors-get.ts +7 -0
  222. package/src/manager-api/routes/common.ts +18 -0
  223. package/src/mod.ts +18 -0
  224. package/src/registry/config.ts +32 -0
  225. package/src/registry/log.ts +5 -0
  226. package/src/registry/mod.ts +157 -0
  227. package/src/registry/run-config.ts +52 -0
  228. package/src/registry/serve.ts +52 -0
  229. package/src/remote-manager-driver/actor-http-client.ts +72 -0
  230. package/src/remote-manager-driver/actor-websocket-client.ts +63 -0
  231. package/src/remote-manager-driver/api-endpoints.ts +79 -0
  232. package/src/remote-manager-driver/api-utils.ts +43 -0
  233. package/src/remote-manager-driver/log.ts +5 -0
  234. package/src/remote-manager-driver/mod.ts +274 -0
  235. package/src/remote-manager-driver/ws-proxy.ts +180 -0
  236. package/src/schemas/actor-persist/mod.ts +1 -0
  237. package/src/schemas/actor-persist/versioned.ts +25 -0
  238. package/src/schemas/client-protocol/mod.ts +1 -0
  239. package/src/schemas/client-protocol/versioned.ts +63 -0
  240. package/src/schemas/file-system-driver/mod.ts +1 -0
  241. package/src/schemas/file-system-driver/versioned.ts +28 -0
  242. package/src/serde.ts +90 -0
  243. package/src/test/config.ts +16 -0
  244. package/src/test/log.ts +5 -0
  245. package/src/test/mod.ts +154 -0
  246. package/src/utils.ts +172 -0
@@ -0,0 +1,865 @@
1
+ import * as cbor from "cbor-x";
2
+ import invariant from "invariant";
3
+ import pRetry from "p-retry";
4
+ import type { CloseEvent } from "ws";
5
+ import type { AnyActorDefinition } from "@/actor/definition";
6
+ import { inputDataToBuffer } from "@/actor/protocol/old";
7
+ import { type Encoding, jsonStringifyCompat } from "@/actor/protocol/serde";
8
+ import { importEventSource } from "@/common/eventsource";
9
+ import type {
10
+ UniversalErrorEvent,
11
+ UniversalEventSource,
12
+ UniversalMessageEvent,
13
+ } from "@/common/eventsource-interface";
14
+ import { assertUnreachable, stringifyError } from "@/common/utils";
15
+ import {
16
+ HEADER_CONN_ID,
17
+ HEADER_CONN_PARAMS,
18
+ HEADER_CONN_TOKEN,
19
+ HEADER_ENCODING,
20
+ type ManagerDriver,
21
+ } from "@/driver-helpers/mod";
22
+ import type { ActorQuery } from "@/manager/protocol/query";
23
+ import { PATH_CONNECT_WEBSOCKET, type UniversalWebSocket } from "@/mod";
24
+ import type * as protocol from "@/schemas/client-protocol/mod";
25
+ import {
26
+ TO_CLIENT_VERSIONED,
27
+ TO_SERVER_VERSIONED,
28
+ } from "@/schemas/client-protocol/versioned";
29
+ import {
30
+ deserializeWithEncoding,
31
+ encodingIsBinary,
32
+ serializeWithEncoding,
33
+ } from "@/serde";
34
+ import { bufferToArrayBuffer, getEnvUniversal, httpUserAgent } from "@/utils";
35
+ import type { ActorDefinitionActions } from "./actor-common";
36
+ import { queryActor } from "./actor-query";
37
+ import { ACTOR_CONNS_SYMBOL, type ClientRaw, TRANSPORT_SYMBOL } from "./client";
38
+ import * as errors from "./errors";
39
+ import { logger } from "./log";
40
+ import {
41
+ type WebSocketMessage as ConnMessage,
42
+ messageLength,
43
+ sendHttpRequest,
44
+ } from "./utils";
45
+
46
+ interface ActionInFlight {
47
+ name: string;
48
+ resolve: (response: protocol.ActionResponse) => void;
49
+ reject: (error: Error) => void;
50
+ }
51
+
52
+ interface EventSubscriptions<Args extends Array<unknown>> {
53
+ callback: (...args: Args) => void;
54
+ once: boolean;
55
+ }
56
+
57
+ /**
58
+ * A function that unsubscribes from an event.
59
+ *
60
+ * @typedef {Function} EventUnsubscribe
61
+ */
62
+ export type EventUnsubscribe = () => void;
63
+
64
+ /**
65
+ * A function that handles connection errors.
66
+ *
67
+ * @typedef {Function} ActorErrorCallback
68
+ */
69
+ export type ActorErrorCallback = (error: errors.ActorError) => void;
70
+
71
+ export interface SendHttpMessageOpts {
72
+ ephemeral: boolean;
73
+ signal?: AbortSignal;
74
+ }
75
+
76
+ export type ConnTransport =
77
+ | { websocket: UniversalWebSocket }
78
+ | { sse: UniversalEventSource };
79
+
80
+ export const CONNECT_SYMBOL = Symbol("connect");
81
+
82
+ /**
83
+ * Provides underlying functions for {@link ActorConn}. See {@link ActorConn} for using type-safe remote procedure calls.
84
+ *
85
+ * @see {@link ActorConn}
86
+ */
87
+ export class ActorConnRaw {
88
+ #disposed = false;
89
+
90
+ /* Will be aborted on dispose. */
91
+ #abortController = new AbortController();
92
+
93
+ /** If attempting to connect. Helpful for knowing if in a retry loop when reconnecting. */
94
+ #connecting = false;
95
+
96
+ // These will only be set on SSE driver
97
+ #actorId?: string;
98
+ #connectionId?: string;
99
+ #connectionToken?: string;
100
+
101
+ #transport?: ConnTransport;
102
+
103
+ #messageQueue: protocol.ToServer[] = [];
104
+ #actionsInFlight = new Map<number, ActionInFlight>();
105
+
106
+ // biome-ignore lint/suspicious/noExplicitAny: Unknown subscription type
107
+ #eventSubscriptions = new Map<string, Set<EventSubscriptions<any[]>>>();
108
+
109
+ #errorHandlers = new Set<ActorErrorCallback>();
110
+
111
+ #actionIdCounter = 0;
112
+
113
+ /**
114
+ * Interval that keeps the NodeJS process alive if this is the only thing running.
115
+ *
116
+ * See ttps://github.com/nodejs/node/issues/22088
117
+ */
118
+ #keepNodeAliveInterval: NodeJS.Timeout;
119
+
120
+ /** Promise used to indicate the socket has connected successfully. This will be rejected if the connection fails. */
121
+ #onOpenPromise?: PromiseWithResolvers<undefined>;
122
+
123
+ #client: ClientRaw;
124
+ #driver: ManagerDriver;
125
+ #params: unknown;
126
+ #encoding: Encoding;
127
+ #actorQuery: ActorQuery;
128
+
129
+ // TODO: ws message queue
130
+
131
+ /**
132
+ * Do not call this directly.
133
+ *
134
+ * Creates an instance of ActorConnRaw.
135
+ *
136
+ * @protected
137
+ */
138
+ public constructor(
139
+ client: ClientRaw,
140
+ driver: ManagerDriver,
141
+ params: unknown,
142
+ encoding: Encoding,
143
+ actorQuery: ActorQuery,
144
+ ) {
145
+ this.#client = client;
146
+ this.#driver = driver;
147
+ this.#params = params;
148
+ this.#encoding = encoding;
149
+ this.#actorQuery = actorQuery;
150
+
151
+ this.#keepNodeAliveInterval = setInterval(() => 60_000);
152
+ }
153
+
154
+ /**
155
+ * Call a raw action connection. See {@link ActorConn} for type-safe action calls.
156
+ *
157
+ * @see {@link ActorConn}
158
+ * @template Args - The type of arguments to pass to the action function.
159
+ * @template Response - The type of the response returned by the action function.
160
+ * @param {string} name - The name of the action function to call.
161
+ * @param {...Args} args - The arguments to pass to the action function.
162
+ * @returns {Promise<Response>} - A promise that resolves to the response of the action function.
163
+ */
164
+ async action<
165
+ Args extends Array<unknown> = unknown[],
166
+ Response = unknown,
167
+ >(opts: {
168
+ name: string;
169
+ args: Args;
170
+ signal?: AbortSignal;
171
+ }): Promise<Response> {
172
+ logger().debug({ msg: "action", name: opts.name, args: opts.args });
173
+
174
+ // If we have an active connection, use the websockactionId
175
+ const actionId = this.#actionIdCounter;
176
+ this.#actionIdCounter += 1;
177
+
178
+ const { promise, resolve, reject } =
179
+ Promise.withResolvers<protocol.ActionResponse>();
180
+ this.#actionsInFlight.set(actionId, { name: opts.name, resolve, reject });
181
+
182
+ this.#sendMessage({
183
+ body: {
184
+ tag: "ActionRequest",
185
+ val: {
186
+ id: BigInt(actionId),
187
+ name: opts.name,
188
+ args: bufferToArrayBuffer(cbor.encode(opts.args)),
189
+ },
190
+ },
191
+ } satisfies protocol.ToServer);
192
+
193
+ // TODO: Throw error if disconnect is called
194
+
195
+ const { id: responseId, output } = await promise;
196
+ if (responseId !== BigInt(actionId))
197
+ throw new Error(
198
+ `Request ID ${actionId} does not match response ID ${responseId}`,
199
+ );
200
+
201
+ return cbor.decode(new Uint8Array(output)) as Response;
202
+ }
203
+
204
+ /**
205
+ * Do not call this directly.
206
+ enc
207
+ * Establishes a connection to the server using the specified endpoint & encoding & driver.
208
+ *
209
+ * @protected
210
+ */
211
+ public [CONNECT_SYMBOL]() {
212
+ this.#connectWithRetry();
213
+ }
214
+
215
+ async #connectWithRetry() {
216
+ this.#connecting = true;
217
+
218
+ // Attempt to reconnect indefinitely
219
+ try {
220
+ await pRetry(this.#connectAndWait.bind(this), {
221
+ forever: true,
222
+ minTimeout: 250,
223
+ maxTimeout: 30_000,
224
+
225
+ onFailedAttempt: (error) => {
226
+ logger().warn({
227
+ msg: "failed to reconnect",
228
+ attempt: error.attemptNumber,
229
+ error: stringifyError(error),
230
+ });
231
+ },
232
+
233
+ // Cancel retry if aborted
234
+ signal: this.#abortController.signal,
235
+ });
236
+ } catch (err) {
237
+ if ((err as Error).name === "AbortError") {
238
+ // Ignore abortions
239
+ logger().info({ msg: "connection retry aborted" });
240
+ return;
241
+ } else {
242
+ // Unknown error
243
+ throw err;
244
+ }
245
+ }
246
+
247
+ this.#connecting = false;
248
+ }
249
+
250
+ async #connectAndWait() {
251
+ try {
252
+ // Create promise for open
253
+ if (this.#onOpenPromise)
254
+ throw new Error("#onOpenPromise already defined");
255
+ this.#onOpenPromise = Promise.withResolvers();
256
+
257
+ // Connect transport
258
+ if (this.#client[TRANSPORT_SYMBOL] === "websocket") {
259
+ await this.#connectWebSocket();
260
+ } else if (this.#client[TRANSPORT_SYMBOL] === "sse") {
261
+ await this.#connectSse();
262
+ } else {
263
+ assertUnreachable(this.#client[TRANSPORT_SYMBOL]);
264
+ }
265
+
266
+ // Wait for result
267
+ await this.#onOpenPromise.promise;
268
+ } finally {
269
+ this.#onOpenPromise = undefined;
270
+ }
271
+ }
272
+
273
+ async #connectWebSocket() {
274
+ const { actorId } = await queryActor(
275
+ undefined,
276
+ this.#actorQuery,
277
+ this.#driver,
278
+ );
279
+ const ws = await this.#driver.openWebSocket(
280
+ PATH_CONNECT_WEBSOCKET,
281
+ actorId,
282
+ this.#encoding,
283
+ this.#params,
284
+ );
285
+ this.#transport = { websocket: ws };
286
+ ws.addEventListener("open", () => {
287
+ logger().debug({ msg: "websocket open" });
288
+ });
289
+ ws.addEventListener("message", async (ev) => {
290
+ this.#handleOnMessage(ev.data);
291
+ });
292
+ ws.addEventListener("close", (ev) => {
293
+ this.#handleOnClose(ev);
294
+ });
295
+ ws.addEventListener("error", (_ev) => {
296
+ this.#handleOnError();
297
+ });
298
+ }
299
+
300
+ async #connectSse() {
301
+ const EventSource = await importEventSource();
302
+
303
+ // Get the actor ID
304
+ const { actorId } = await queryActor(
305
+ undefined,
306
+ this.#actorQuery,
307
+ this.#driver,
308
+ );
309
+ logger().debug({ msg: "found actor for sse connection", actorId });
310
+ invariant(actorId, "Missing actor ID");
311
+
312
+ logger().debug({
313
+ msg: "opening sse connection",
314
+ actorId,
315
+ encoding: this.#encoding,
316
+ });
317
+
318
+ const eventSource = new EventSource("http://actor/connect/sse", {
319
+ fetch: (input, init) => {
320
+ return this.#driver.sendRequest(
321
+ actorId,
322
+ new Request(input, {
323
+ ...init,
324
+ headers: {
325
+ ...init?.headers,
326
+ "User-Agent": httpUserAgent(),
327
+ [HEADER_ENCODING]: this.#encoding,
328
+ ...(this.#params !== undefined
329
+ ? { [HEADER_CONN_PARAMS]: JSON.stringify(this.#params) }
330
+ : {}),
331
+ },
332
+ }),
333
+ );
334
+ },
335
+ }) as UniversalEventSource;
336
+
337
+ this.#transport = { sse: eventSource };
338
+
339
+ eventSource.addEventListener("message", (ev: UniversalMessageEvent) => {
340
+ this.#handleOnMessage(ev.data);
341
+ });
342
+
343
+ eventSource.addEventListener("error", (ev: UniversalErrorEvent) => {
344
+ this.#handleOnError();
345
+ });
346
+ }
347
+
348
+ /** Called by the onopen event from drivers. */
349
+ #handleOnOpen() {
350
+ logger().debug({
351
+ msg: "socket open",
352
+ messageQueueLength: this.#messageQueue.length,
353
+ });
354
+
355
+ // Resolve open promise
356
+ if (this.#onOpenPromise) {
357
+ this.#onOpenPromise.resolve(undefined);
358
+ } else {
359
+ logger().warn({ msg: "#onOpenPromise is undefined" });
360
+ }
361
+
362
+ // Resubscribe to all active events
363
+ for (const eventName of this.#eventSubscriptions.keys()) {
364
+ this.#sendSubscription(eventName, true);
365
+ }
366
+
367
+ // Flush queue
368
+ //
369
+ // If the message fails to send, the message will be re-queued
370
+ const queue = this.#messageQueue;
371
+ this.#messageQueue = [];
372
+ for (const msg of queue) {
373
+ this.#sendMessage(msg);
374
+ }
375
+ }
376
+
377
+ /** Called by the onmessage event from drivers. */
378
+ async #handleOnMessage(data: any) {
379
+ logger().trace({
380
+ msg: "received message",
381
+ dataType: typeof data,
382
+ isBlob: data instanceof Blob,
383
+ isArrayBuffer: data instanceof ArrayBuffer,
384
+ });
385
+
386
+ const response = await this.#parseMessage(data as ConnMessage);
387
+ logger().trace(
388
+ getEnvUniversal("_RIVETKIT_LOG_MESSAGE")
389
+ ? {
390
+ msg: "parsed message",
391
+ message: jsonStringifyCompat(response).substring(0, 100) + "...",
392
+ }
393
+ : { msg: "parsed message" },
394
+ );
395
+
396
+ if (response.body.tag === "Init") {
397
+ // This is only called for SSE
398
+ this.#actorId = response.body.val.actorId;
399
+ this.#connectionId = response.body.val.connectionId;
400
+ this.#connectionToken = response.body.val.connectionToken;
401
+ logger().trace({
402
+ msg: "received init message",
403
+ actorId: this.#actorId,
404
+ connectionId: this.#connectionId,
405
+ });
406
+ this.#handleOnOpen();
407
+ } else if (response.body.tag === "Error") {
408
+ // Connection error
409
+ const { group, code, message, metadata, actionId } = response.body.val;
410
+
411
+ if (actionId) {
412
+ const inFlight = this.#takeActionInFlight(Number(actionId));
413
+
414
+ logger().warn({
415
+ msg: "action error",
416
+ actionId: actionId,
417
+ actionName: inFlight?.name,
418
+ group,
419
+ code,
420
+ message,
421
+ metadata,
422
+ });
423
+
424
+ inFlight.reject(new errors.ActorError(group, code, message, metadata));
425
+ } else {
426
+ logger().warn({
427
+ msg: "connection error",
428
+ group,
429
+ code,
430
+ message,
431
+ metadata,
432
+ });
433
+
434
+ // Create a connection error
435
+ const actorError = new errors.ActorError(
436
+ group,
437
+ code,
438
+ message,
439
+ metadata,
440
+ );
441
+
442
+ // If we have an onOpenPromise, reject it with the error
443
+ if (this.#onOpenPromise) {
444
+ this.#onOpenPromise.reject(actorError);
445
+ }
446
+
447
+ // Reject any in-flight requests
448
+ for (const [id, inFlight] of this.#actionsInFlight.entries()) {
449
+ inFlight.reject(actorError);
450
+ this.#actionsInFlight.delete(id);
451
+ }
452
+
453
+ // Dispatch to error handler if registered
454
+ this.#dispatchActorError(actorError);
455
+ }
456
+ } else if (response.body.tag === "ActionResponse") {
457
+ // Action response OK
458
+ const { id: actionId } = response.body.val;
459
+ logger().trace({
460
+ msg: "received action response",
461
+ actionId,
462
+ });
463
+
464
+ const inFlight = this.#takeActionInFlight(Number(actionId));
465
+ logger().trace({
466
+ msg: "resolving action promise",
467
+ actionId,
468
+ actionName: inFlight?.name,
469
+ });
470
+ inFlight.resolve(response.body.val);
471
+ } else if (response.body.tag === "Event") {
472
+ logger().trace({ msg: "received event", name: response.body.val.name });
473
+ this.#dispatchEvent(response.body.val);
474
+ } else {
475
+ assertUnreachable(response.body);
476
+ }
477
+ }
478
+
479
+ /** Called by the onclose event from drivers. */
480
+ #handleOnClose(event: Event | CloseEvent) {
481
+ // TODO: Handle queue
482
+ // TODO: Reconnect with backoff
483
+
484
+ // Reject open promise
485
+ if (this.#onOpenPromise) {
486
+ this.#onOpenPromise.reject(new Error("Closed"));
487
+ }
488
+
489
+ // We can't use `event instanceof CloseEvent` because it's not defined in NodeJS
490
+ //
491
+ // These properties will be undefined
492
+ const closeEvent = event as CloseEvent;
493
+ if (closeEvent.wasClean) {
494
+ logger().info({
495
+ msg: "socket closed",
496
+ code: closeEvent.code,
497
+ reason: closeEvent.reason,
498
+ wasClean: closeEvent.wasClean,
499
+ });
500
+ } else {
501
+ logger().warn({
502
+ msg: "socket closed",
503
+ code: closeEvent.code,
504
+ reason: closeEvent.reason,
505
+ wasClean: closeEvent.wasClean,
506
+ });
507
+ }
508
+
509
+ this.#transport = undefined;
510
+
511
+ // Automatically reconnect. Skip if already attempting to connect.
512
+ if (!this.#disposed && !this.#connecting) {
513
+ // TODO: Fetch actor to check if it's destroyed
514
+ // TODO: Add backoff for reconnect
515
+ // TODO: Add a way of preserving connection ID for connection state
516
+
517
+ // Attempt to connect again
518
+ this.#connectWithRetry();
519
+ }
520
+ }
521
+
522
+ /** Called by the onerror event from drivers. */
523
+ #handleOnError() {
524
+ if (this.#disposed) return;
525
+
526
+ // More detailed information will be logged in onclose
527
+ logger().warn("socket error");
528
+ }
529
+
530
+ #takeActionInFlight(id: number): ActionInFlight {
531
+ const inFlight = this.#actionsInFlight.get(id);
532
+ if (!inFlight) {
533
+ throw new errors.InternalError(`No in flight response for ${id}`);
534
+ }
535
+ this.#actionsInFlight.delete(id);
536
+ return inFlight;
537
+ }
538
+
539
+ #dispatchEvent(event: protocol.Event) {
540
+ const { name, args: argsRaw } = event;
541
+ const args = cbor.decode(new Uint8Array(argsRaw));
542
+
543
+ const listeners = this.#eventSubscriptions.get(name);
544
+ if (!listeners) return;
545
+
546
+ // Create a new array to avoid issues with listeners being removed during iteration
547
+ for (const listener of [...listeners]) {
548
+ listener.callback(...args);
549
+
550
+ // Remove if this was a one-time listener
551
+ if (listener.once) {
552
+ listeners.delete(listener);
553
+ }
554
+ }
555
+
556
+ // Clean up empty listener sets
557
+ if (listeners.size === 0) {
558
+ this.#eventSubscriptions.delete(name);
559
+ }
560
+ }
561
+
562
+ #dispatchActorError(error: errors.ActorError) {
563
+ // Call all registered error handlers
564
+ for (const handler of [...this.#errorHandlers]) {
565
+ try {
566
+ handler(error);
567
+ } catch (err) {
568
+ logger().error({
569
+ msg: "error in connection error handler",
570
+ error: stringifyError(err),
571
+ });
572
+ }
573
+ }
574
+ }
575
+
576
+ #addEventSubscription<Args extends Array<unknown>>(
577
+ eventName: string,
578
+ callback: (...args: Args) => void,
579
+ once: boolean,
580
+ ): EventUnsubscribe {
581
+ const listener: EventSubscriptions<Args> = {
582
+ callback,
583
+ once,
584
+ };
585
+
586
+ let subscriptionSet = this.#eventSubscriptions.get(eventName);
587
+ if (subscriptionSet === undefined) {
588
+ subscriptionSet = new Set();
589
+ this.#eventSubscriptions.set(eventName, subscriptionSet);
590
+ this.#sendSubscription(eventName, true);
591
+ }
592
+ subscriptionSet.add(listener);
593
+
594
+ // Return unsubscribe function
595
+ return () => {
596
+ const listeners = this.#eventSubscriptions.get(eventName);
597
+ if (listeners) {
598
+ listeners.delete(listener);
599
+ if (listeners.size === 0) {
600
+ this.#eventSubscriptions.delete(eventName);
601
+ this.#sendSubscription(eventName, false);
602
+ }
603
+ }
604
+ };
605
+ }
606
+
607
+ /**
608
+ * Subscribes to an event that will happen repeatedly.
609
+ *
610
+ * @template Args - The type of arguments the event callback will receive.
611
+ * @param {string} eventName - The name of the event to subscribe to.
612
+ * @param {(...args: Args) => void} callback - The callback function to execute when the event is triggered.
613
+ * @returns {EventUnsubscribe} - A function to unsubscribe from the event.
614
+ * @see {@link https://rivet.dev/docs/events|Events Documentation}
615
+ */
616
+ on<Args extends Array<unknown> = unknown[]>(
617
+ eventName: string,
618
+ callback: (...args: Args) => void,
619
+ ): EventUnsubscribe {
620
+ return this.#addEventSubscription<Args>(eventName, callback, false);
621
+ }
622
+
623
+ /**
624
+ * Subscribes to an event that will be triggered only once.
625
+ *
626
+ * @template Args - The type of arguments the event callback will receive.
627
+ * @param {string} eventName - The name of the event to subscribe to.
628
+ * @param {(...args: Args) => void} callback - The callback function to execute when the event is triggered.
629
+ * @returns {EventUnsubscribe} - A function to unsubscribe from the event.
630
+ * @see {@link https://rivet.dev/docs/events|Events Documentation}
631
+ */
632
+ once<Args extends Array<unknown> = unknown[]>(
633
+ eventName: string,
634
+ callback: (...args: Args) => void,
635
+ ): EventUnsubscribe {
636
+ return this.#addEventSubscription<Args>(eventName, callback, true);
637
+ }
638
+
639
+ /**
640
+ * Subscribes to connection errors.
641
+ *
642
+ * @param {ActorErrorCallback} callback - The callback function to execute when a connection error occurs.
643
+ * @returns {() => void} - A function to unsubscribe from the error handler.
644
+ */
645
+ onError(callback: ActorErrorCallback): () => void {
646
+ this.#errorHandlers.add(callback);
647
+
648
+ // Return unsubscribe function
649
+ return () => {
650
+ this.#errorHandlers.delete(callback);
651
+ };
652
+ }
653
+
654
+ #sendMessage(message: protocol.ToServer, opts?: SendHttpMessageOpts) {
655
+ if (this.#disposed) {
656
+ throw new errors.ActorConnDisposed();
657
+ }
658
+
659
+ let queueMessage = false;
660
+ if (!this.#transport) {
661
+ // No transport connected yet
662
+ queueMessage = true;
663
+ } else if ("websocket" in this.#transport) {
664
+ if (this.#transport.websocket.readyState === 1) {
665
+ try {
666
+ const messageSerialized = serializeWithEncoding(
667
+ this.#encoding,
668
+ message,
669
+ TO_SERVER_VERSIONED,
670
+ );
671
+ this.#transport.websocket.send(messageSerialized);
672
+ logger().trace({
673
+ msg: "sent websocket message",
674
+ len: messageLength(messageSerialized),
675
+ });
676
+ } catch (error) {
677
+ logger().warn({
678
+ msg: "failed to send message, added to queue",
679
+ error,
680
+ });
681
+
682
+ // Assuming the socket is disconnected and will be reconnected soon
683
+ queueMessage = true;
684
+ }
685
+ } else {
686
+ queueMessage = true;
687
+ }
688
+ } else if ("sse" in this.#transport) {
689
+ if (this.#transport.sse.readyState === 1) {
690
+ // Spawn in background since #sendMessage cannot be async
691
+ this.#sendHttpMessage(message, opts);
692
+ } else {
693
+ queueMessage = true;
694
+ }
695
+ } else {
696
+ assertUnreachable(this.#transport);
697
+ }
698
+
699
+ if (!opts?.ephemeral && queueMessage) {
700
+ this.#messageQueue.push(message);
701
+ logger().debug({ msg: "queued connection message" });
702
+ }
703
+ }
704
+
705
+ async #sendHttpMessage(
706
+ message: protocol.ToServer,
707
+ opts?: SendHttpMessageOpts,
708
+ ) {
709
+ try {
710
+ if (!this.#actorId || !this.#connectionId || !this.#connectionToken)
711
+ throw new errors.InternalError("Missing connection ID or token.");
712
+
713
+ logger().trace(
714
+ getEnvUniversal("_RIVETKIT_LOG_MESSAGE")
715
+ ? {
716
+ msg: "sent http message",
717
+ message: `${jsonStringifyCompat(message).substring(0, 100)}...`,
718
+ }
719
+ : { msg: "sent http message" },
720
+ );
721
+
722
+ logger().debug({
723
+ msg: "sending http message",
724
+ actorId: this.#actorId,
725
+ connectionId: this.#connectionId,
726
+ });
727
+
728
+ // Send an HTTP request to the connections endpoint
729
+ await sendHttpRequest({
730
+ url: "http://actor/connections/message",
731
+ method: "POST",
732
+ headers: {
733
+ [HEADER_ENCODING]: this.#encoding,
734
+ [HEADER_CONN_ID]: this.#connectionId,
735
+ [HEADER_CONN_TOKEN]: this.#connectionToken,
736
+ },
737
+ body: message,
738
+ encoding: this.#encoding,
739
+ skipParseResponse: true,
740
+ customFetch: this.#driver.sendRequest.bind(this.#driver, this.#actorId),
741
+ requestVersionedDataHandler: TO_SERVER_VERSIONED,
742
+ responseVersionedDataHandler: TO_CLIENT_VERSIONED,
743
+ });
744
+ } catch (error) {
745
+ // TODO: This will not automatically trigger a re-broadcast of HTTP events since SSE is separate from the HTTP action
746
+
747
+ logger().warn({ msg: "failed to send message, added to queue", error });
748
+
749
+ // Assuming the socket is disconnected and will be reconnected soon
750
+ //
751
+ // Will attempt to resend soon
752
+ if (!opts?.ephemeral) {
753
+ this.#messageQueue.unshift(message);
754
+ }
755
+ }
756
+ }
757
+
758
+ async #parseMessage(data: ConnMessage): Promise<protocol.ToClient> {
759
+ invariant(this.#transport, "transport must be defined");
760
+
761
+ // Decode base64 since SSE sends raw strings
762
+ if (encodingIsBinary(this.#encoding) && "sse" in this.#transport) {
763
+ if (typeof data === "string") {
764
+ const binaryString = atob(data);
765
+ data = new Uint8Array(
766
+ [...binaryString].map((char) => char.charCodeAt(0)),
767
+ );
768
+ } else {
769
+ throw new errors.InternalError(
770
+ `Expected data to be a string for SSE, got ${data}.`,
771
+ );
772
+ }
773
+ }
774
+
775
+ const buffer = await inputDataToBuffer(data);
776
+
777
+ return deserializeWithEncoding(this.#encoding, buffer, TO_CLIENT_VERSIONED);
778
+ }
779
+
780
+ /**
781
+ * Disconnects from the actor.
782
+ *
783
+ * @returns {Promise<void>} A promise that resolves when the socket is gracefully closed.
784
+ */
785
+ async dispose(): Promise<void> {
786
+ // Internally, this "disposes" the connection
787
+
788
+ if (this.#disposed) {
789
+ logger().warn({ msg: "connection already disconnected" });
790
+ return;
791
+ }
792
+ this.#disposed = true;
793
+
794
+ logger().debug({ msg: "disposing actor conn" });
795
+
796
+ // Clear interval so NodeJS process can exit
797
+ clearInterval(this.#keepNodeAliveInterval);
798
+
799
+ // Abort
800
+ this.#abortController.abort();
801
+
802
+ // Remove from registry
803
+ this.#client[ACTOR_CONNS_SYMBOL].delete(this);
804
+
805
+ // Disconnect transport cleanly
806
+ if (!this.#transport) {
807
+ // Nothing to do
808
+ } else if ("websocket" in this.#transport) {
809
+ const ws = this.#transport.websocket;
810
+ // Check if WebSocket is already closed or closing
811
+ if (
812
+ ws.readyState === 2 /* CLOSING */ ||
813
+ ws.readyState === 3 /* CLOSED */
814
+ ) {
815
+ logger().debug({ msg: "ws already closed or closing" });
816
+ } else {
817
+ const { promise, resolve } = Promise.withResolvers();
818
+ ws.addEventListener("close", () => {
819
+ logger().debug({ msg: "ws closed" });
820
+ resolve(undefined);
821
+ });
822
+ ws.close();
823
+ await promise;
824
+ }
825
+ } else if ("sse" in this.#transport) {
826
+ this.#transport.sse.close();
827
+ } else {
828
+ assertUnreachable(this.#transport);
829
+ }
830
+ this.#transport = undefined;
831
+ }
832
+
833
+ #sendSubscription(eventName: string, subscribe: boolean) {
834
+ this.#sendMessage(
835
+ {
836
+ body: {
837
+ tag: "SubscriptionRequest",
838
+ val: {
839
+ eventName,
840
+ subscribe,
841
+ },
842
+ },
843
+ },
844
+ { ephemeral: true },
845
+ );
846
+ }
847
+ }
848
+
849
+ /**
850
+ * Connection to a actor. Allows calling actor's remote procedure calls with inferred types. See {@link ActorConnRaw} for underlying methods.
851
+ *
852
+ * @example
853
+ * ```
854
+ * const room = client.connect<ChatRoom>(...etc...);
855
+ * // This calls the action named `sendMessage` on the `ChatRoom` actor.
856
+ * await room.sendMessage('Hello, world!');
857
+ * ```
858
+ *
859
+ * Private methods (e.g. those starting with `_`) are automatically excluded.
860
+ *
861
+ * @template AD The actor class that this connection is for.
862
+ * @see {@link ActorConnRaw}
863
+ */
864
+ export type ActorConn<AD extends AnyActorDefinition> = ActorConnRaw &
865
+ ActorDefinitionActions<AD>;