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
@@ -0,0 +1,4071 @@
1
+ import {
2
+ ColumnsSchema,
3
+ ForeignKeysSchema,
4
+ PatchSchema,
5
+ TablesSchema
6
+ } from "./chunk-GZVBFXBI.js";
7
+ import {
8
+ importWebSocket,
9
+ logger
10
+ } from "./chunk-E4UVJKSV.js";
11
+ import {
12
+ HTTP_ACTION_REQUEST_VERSIONED,
13
+ HTTP_ACTION_RESPONSE_VERSIONED,
14
+ HTTP_RESPONSE_ERROR_VERSIONED,
15
+ PERSISTED_ACTOR_VERSIONED,
16
+ TO_CLIENT_VERSIONED,
17
+ TO_SERVER_VERSIONED,
18
+ inputDataToBuffer,
19
+ processMessage
20
+ } from "./chunk-JD54PXWP.js";
21
+ import {
22
+ CachedSerializer,
23
+ DeadlineError,
24
+ HEADER_CONN_ID,
25
+ HEADER_CONN_PARAMS,
26
+ HEADER_CONN_TOKEN,
27
+ HEADER_ENCODING,
28
+ HEADER_RIVET_ACTOR,
29
+ HEADER_RIVET_TARGET,
30
+ HEADER_RIVET_TOKEN,
31
+ PATH_CONNECT_WEBSOCKET,
32
+ PATH_RAW_WEBSOCKET_PREFIX,
33
+ WS_PROTOCOL_ACTOR,
34
+ WS_PROTOCOL_CONN_ID,
35
+ WS_PROTOCOL_CONN_PARAMS,
36
+ WS_PROTOCOL_CONN_TOKEN,
37
+ WS_PROTOCOL_ENCODING,
38
+ WS_PROTOCOL_STANDARD,
39
+ WS_PROTOCOL_TARGET,
40
+ WS_PROTOCOL_TOKEN,
41
+ assertUnreachable as assertUnreachable2,
42
+ contentTypeForEncoding,
43
+ deadline,
44
+ deserializeWithEncoding,
45
+ encodeDataToString,
46
+ encodingIsBinary,
47
+ generateRandomString,
48
+ generateSecureToken,
49
+ jsonStringifyCompat,
50
+ serializeWithEncoding
51
+ } from "./chunk-YQ4XQYPM.js";
52
+ import {
53
+ getBaseLogger,
54
+ getIncludeTarget,
55
+ getLogger
56
+ } from "./chunk-PUSQNDJG.js";
57
+ import {
58
+ SinglePromiseQueue,
59
+ assertUnreachable,
60
+ bufferToArrayBuffer,
61
+ combineUrlPath,
62
+ deconstructError,
63
+ getEnvUniversal,
64
+ httpUserAgent,
65
+ isCborSerializable,
66
+ noopNext,
67
+ promiseWithResolvers,
68
+ stringifyError
69
+ } from "./chunk-RVP5RUSC.js";
70
+ import {
71
+ ActionNotFound,
72
+ ActionTimedOut,
73
+ ActorNotFound,
74
+ ConnStateNotEnabled,
75
+ DatabaseNotEnabled,
76
+ FetchHandlerNotDefined,
77
+ InternalError,
78
+ InvalidFetchResponse,
79
+ InvalidRequest,
80
+ InvalidStateType,
81
+ StateNotEnabled,
82
+ VarsNotEnabled
83
+ } from "./chunk-YPZFLUO6.js";
84
+
85
+ // src/actor/conn.ts
86
+ import * as cbor from "cbor-x";
87
+
88
+ // src/actor/conn-drivers.ts
89
+ var WEBSOCKET_DRIVER = {
90
+ sendMessage: (actor, _conn, state, message) => {
91
+ const serialized = message.serialize(state.encoding);
92
+ actor.rLog.debug({
93
+ msg: "sending websocket message",
94
+ encoding: state.encoding,
95
+ dataType: typeof serialized,
96
+ isUint8Array: serialized instanceof Uint8Array,
97
+ isArrayBuffer: serialized instanceof ArrayBuffer,
98
+ dataLength: serialized.byteLength || serialized.length
99
+ });
100
+ if (serialized instanceof Uint8Array) {
101
+ const buffer = serialized.buffer.slice(
102
+ serialized.byteOffset,
103
+ serialized.byteOffset + serialized.byteLength
104
+ );
105
+ if (buffer instanceof SharedArrayBuffer) {
106
+ const arrayBuffer = new ArrayBuffer(buffer.byteLength);
107
+ new Uint8Array(arrayBuffer).set(new Uint8Array(buffer));
108
+ actor.rLog.debug({
109
+ msg: "converted SharedArrayBuffer to ArrayBuffer",
110
+ byteLength: arrayBuffer.byteLength
111
+ });
112
+ state.websocket.send(arrayBuffer);
113
+ } else {
114
+ actor.rLog.debug({
115
+ msg: "sending ArrayBuffer",
116
+ byteLength: buffer.byteLength
117
+ });
118
+ state.websocket.send(buffer);
119
+ }
120
+ } else {
121
+ actor.rLog.debug({
122
+ msg: "sending string data",
123
+ length: serialized.length
124
+ });
125
+ state.websocket.send(serialized);
126
+ }
127
+ },
128
+ disconnect: async (_actor, _conn, state, reason) => {
129
+ state.websocket.close(1e3, reason);
130
+ await state.closePromise.promise;
131
+ },
132
+ getConnectionReadyState: (_actor, _conn, state) => {
133
+ return state.websocket.readyState;
134
+ }
135
+ };
136
+ var SSE_DRIVER = {
137
+ sendMessage: (_actor, _conn, state, message) => {
138
+ state.stream.writeSSE({
139
+ data: encodeDataToString(message.serialize(state.encoding))
140
+ });
141
+ },
142
+ disconnect: async (_actor, _conn, state, _reason) => {
143
+ state.stream.close();
144
+ },
145
+ getConnectionReadyState: (_actor, _conn, state) => {
146
+ if (state.stream.aborted || state.stream.closed) {
147
+ return 3 /* CLOSED */;
148
+ }
149
+ return 1 /* OPEN */;
150
+ }
151
+ };
152
+ var HTTP_DRIVER = {
153
+ getConnectionReadyState(_actor, _conn) {
154
+ return 1 /* OPEN */;
155
+ },
156
+ disconnect: async () => {
157
+ }
158
+ };
159
+ var CONN_DRIVERS = {
160
+ [0 /* WEBSOCKET */]: WEBSOCKET_DRIVER,
161
+ [1 /* SSE */]: SSE_DRIVER,
162
+ [2 /* HTTP */]: HTTP_DRIVER
163
+ };
164
+ function getConnDriverKindFromState(state) {
165
+ if (0 /* WEBSOCKET */ in state) return 0 /* WEBSOCKET */;
166
+ else if (1 /* SSE */ in state) return 1 /* SSE */;
167
+ else if (2 /* HTTP */ in state) return 2 /* HTTP */;
168
+ else assertUnreachable(state);
169
+ }
170
+
171
+ // src/actor/conn.ts
172
+ function generateConnId() {
173
+ return crypto.randomUUID();
174
+ }
175
+ function generateConnToken() {
176
+ return generateSecureToken(32);
177
+ }
178
+ function generateConnSocketId() {
179
+ return crypto.randomUUID();
180
+ }
181
+ var Conn = class {
182
+ subscriptions = /* @__PURE__ */ new Set();
183
+ // TODO: Remove this cyclical reference
184
+ #actor;
185
+ /**
186
+ * The proxied state that notifies of changes automatically.
187
+ *
188
+ * Any data that should be stored indefinitely should be held within this object.
189
+ */
190
+ __persist;
191
+ get __driverState() {
192
+ var _a;
193
+ return (_a = this.__socket) == null ? void 0 : _a.driverState;
194
+ }
195
+ /**
196
+ * Socket connected to this connection.
197
+ *
198
+ * If undefined, then nothing is connected to this.
199
+ */
200
+ __socket;
201
+ get __status() {
202
+ if (this.__socket) {
203
+ return "connected";
204
+ } else {
205
+ return "reconnecting";
206
+ }
207
+ }
208
+ get params() {
209
+ return this.__persist.params;
210
+ }
211
+ get __stateEnabled() {
212
+ return this.#actor.connStateEnabled;
213
+ }
214
+ /**
215
+ * Gets the current state of the connection.
216
+ *
217
+ * Throws an error if the state is not enabled.
218
+ */
219
+ get state() {
220
+ this.#validateStateEnabled();
221
+ if (!this.__persist.state) throw new Error("state should exists");
222
+ return this.__persist.state;
223
+ }
224
+ /**
225
+ * Sets the state of the connection.
226
+ *
227
+ * Throws an error if the state is not enabled.
228
+ */
229
+ set state(value) {
230
+ this.#validateStateEnabled();
231
+ this.__persist.state = value;
232
+ }
233
+ /**
234
+ * Unique identifier for the connection.
235
+ */
236
+ get id() {
237
+ return this.__persist.connId;
238
+ }
239
+ /**
240
+ * Token used to authenticate this request.
241
+ */
242
+ get _token() {
243
+ return this.__persist.token;
244
+ }
245
+ /**
246
+ * Status of the connection.
247
+ */
248
+ get status() {
249
+ return this.__status;
250
+ }
251
+ /**
252
+ * Timestamp of the last time the connection was seen, i.e. the last time the connection was active and checked for liveness.
253
+ */
254
+ get lastSeen() {
255
+ return this.__persist.lastSeen;
256
+ }
257
+ /**
258
+ * Initializes a new instance of the Connection class.
259
+ *
260
+ * This should only be constructed by {@link Actor}.
261
+ *
262
+ * @protected
263
+ */
264
+ constructor(actor, persist) {
265
+ this.#actor = actor;
266
+ this.__persist = persist;
267
+ }
268
+ #validateStateEnabled() {
269
+ if (!this.__stateEnabled) {
270
+ throw new ConnStateNotEnabled();
271
+ }
272
+ }
273
+ /**
274
+ * Sends a WebSocket message to the client.
275
+ *
276
+ * @param message - The message to send.
277
+ *
278
+ * @protected
279
+ */
280
+ _sendMessage(message) {
281
+ if (this.__driverState) {
282
+ const driverKind = getConnDriverKindFromState(this.__driverState);
283
+ const driver = CONN_DRIVERS[driverKind];
284
+ if (driver.sendMessage) {
285
+ driver.sendMessage(
286
+ this.#actor,
287
+ this,
288
+ this.__driverState[driverKind],
289
+ message
290
+ );
291
+ } else {
292
+ this.#actor.rLog.debug({
293
+ msg: "conn driver does not support sending messages",
294
+ conn: this.id
295
+ });
296
+ }
297
+ } else {
298
+ this.#actor.rLog.warn({
299
+ msg: "missing connection driver state for send message",
300
+ conn: this.id
301
+ });
302
+ }
303
+ }
304
+ /**
305
+ * Sends an event with arguments to the client.
306
+ *
307
+ * @param eventName - The name of the event.
308
+ * @param args - The arguments for the event.
309
+ * @see {@link https://rivet.dev/docs/events|Events Documentation}
310
+ */
311
+ send(eventName, ...args) {
312
+ this.#actor.inspector.emitter.emit("eventFired", {
313
+ type: "event",
314
+ eventName,
315
+ args,
316
+ connId: this.id
317
+ });
318
+ this._sendMessage(
319
+ new CachedSerializer(
320
+ {
321
+ body: {
322
+ tag: "Event",
323
+ val: {
324
+ name: eventName,
325
+ args: bufferToArrayBuffer(cbor.encode(args))
326
+ }
327
+ }
328
+ },
329
+ TO_CLIENT_VERSIONED
330
+ )
331
+ );
332
+ }
333
+ /**
334
+ * Disconnects the client with an optional reason.
335
+ *
336
+ * @param reason - The reason for disconnection.
337
+ */
338
+ async disconnect(reason) {
339
+ if (this.__socket && this.__driverState) {
340
+ const driverKind = getConnDriverKindFromState(this.__driverState);
341
+ const driver = CONN_DRIVERS[driverKind];
342
+ if (driver.disconnect) {
343
+ driver.disconnect(
344
+ this.#actor,
345
+ this,
346
+ this.__driverState[driverKind],
347
+ reason
348
+ );
349
+ } else {
350
+ this.#actor.rLog.debug({
351
+ msg: "no disconnect handler for conn driver",
352
+ conn: this.id
353
+ });
354
+ }
355
+ this.#actor.__connDisconnected(this, true, this.__socket.socketId);
356
+ } else {
357
+ this.#actor.rLog.warn({
358
+ msg: "missing connection driver state for disconnect",
359
+ conn: this.id
360
+ });
361
+ }
362
+ this.__socket = void 0;
363
+ }
364
+ };
365
+
366
+ // src/actor/instance.ts
367
+ import * as cbor2 from "cbor-x";
368
+ import invariant from "invariant";
369
+ import onChange from "on-change";
370
+
371
+ // src/inspector/actor.ts
372
+ import { sValidator } from "@hono/standard-validator";
373
+ import jsonPatch from "@rivetkit/fast-json-patch";
374
+ import { Hono } from "hono";
375
+ import { streamSSE } from "hono/streaming";
376
+ import { createNanoEvents } from "nanoevents";
377
+ import z from "zod/v4";
378
+ function createActorInspectorRouter() {
379
+ return new Hono().get("/ping", (c) => {
380
+ return c.json({ message: "pong" }, 200);
381
+ }).get("/state", async (c) => {
382
+ if (await c.var.inspector.accessors.isStateEnabled()) {
383
+ return c.json(
384
+ {
385
+ enabled: true,
386
+ state: await c.var.inspector.accessors.getState()
387
+ },
388
+ 200
389
+ );
390
+ }
391
+ return c.json({ enabled: false, state: null }, 200);
392
+ }).patch(
393
+ "/state",
394
+ sValidator(
395
+ "json",
396
+ z.object({ patch: PatchSchema }).or(z.object({ replace: z.any() }))
397
+ ),
398
+ async (c) => {
399
+ if (!await c.var.inspector.accessors.isStateEnabled()) {
400
+ return c.json({ enabled: false }, 200);
401
+ }
402
+ const body = c.req.valid("json");
403
+ if ("replace" in body) {
404
+ await c.var.inspector.accessors.setState(body.replace);
405
+ return c.json(
406
+ {
407
+ enabled: true,
408
+ state: await c.var.inspector.accessors.getState()
409
+ },
410
+ 200
411
+ );
412
+ }
413
+ const state = await c.var.inspector.accessors.getState();
414
+ const { newDocument: newState } = jsonPatch.applyPatch(
415
+ state,
416
+ body.patch
417
+ );
418
+ await c.var.inspector.accessors.setState(newState);
419
+ return c.json(
420
+ { enabled: true, state: await c.var.inspector.accessors.getState() },
421
+ 200
422
+ );
423
+ }
424
+ ).get("/state/stream", async (c) => {
425
+ if (!await c.var.inspector.accessors.isStateEnabled()) {
426
+ return c.json({ enabled: false }, 200);
427
+ }
428
+ let id = 0;
429
+ let unsub;
430
+ return streamSSE(
431
+ c,
432
+ async (stream) => {
433
+ unsub = c.var.inspector.emitter.on("stateUpdated", async (state) => {
434
+ stream.writeSSE({
435
+ data: JSON.stringify(state) || "",
436
+ event: "state-update",
437
+ id: String(id++)
438
+ });
439
+ });
440
+ const { promise } = promiseWithResolvers();
441
+ return promise;
442
+ },
443
+ async () => {
444
+ unsub == null ? void 0 : unsub();
445
+ }
446
+ );
447
+ }).get("/connections", async (c) => {
448
+ const connections = await c.var.inspector.accessors.getConnections();
449
+ return c.json({ connections }, 200);
450
+ }).get("/connections/stream", async (c) => {
451
+ let id = 0;
452
+ let unsub;
453
+ return streamSSE(
454
+ c,
455
+ async (stream) => {
456
+ unsub = c.var.inspector.emitter.on("connectionUpdated", async () => {
457
+ stream.writeSSE({
458
+ data: JSON.stringify(
459
+ await c.var.inspector.accessors.getConnections()
460
+ ),
461
+ event: "connection-update",
462
+ id: String(id++)
463
+ });
464
+ });
465
+ const { promise } = promiseWithResolvers();
466
+ return promise;
467
+ },
468
+ async () => {
469
+ unsub == null ? void 0 : unsub();
470
+ }
471
+ );
472
+ }).get("/events", async (c) => {
473
+ const events = c.var.inspector.lastRealtimeEvents;
474
+ return c.json({ events }, 200);
475
+ }).post("/events/clear", async (c) => {
476
+ c.var.inspector.lastRealtimeEvents.length = 0;
477
+ return c.json({ message: "Events cleared" }, 200);
478
+ }).get("/events/stream", async (c) => {
479
+ let id = 0;
480
+ let unsub;
481
+ return streamSSE(
482
+ c,
483
+ async (stream) => {
484
+ unsub = c.var.inspector.emitter.on("eventFired", () => {
485
+ stream.writeSSE({
486
+ data: JSON.stringify(c.var.inspector.lastRealtimeEvents),
487
+ event: "realtime-event",
488
+ id: String(id++)
489
+ });
490
+ });
491
+ const { promise } = promiseWithResolvers();
492
+ return promise;
493
+ },
494
+ async () => {
495
+ unsub == null ? void 0 : unsub();
496
+ }
497
+ );
498
+ }).get("/rpcs", async (c) => {
499
+ const rpcs = await c.var.inspector.accessors.getRpcs();
500
+ return c.json({ rpcs }, 200);
501
+ }).get("/db", async (c) => {
502
+ if (!await c.var.inspector.accessors.isDbEnabled()) {
503
+ return c.json({ enabled: false, db: null }, 200);
504
+ }
505
+ const db = await c.var.inspector.accessors.getDb();
506
+ const rows = await db.execute(`PRAGMA table_list`);
507
+ const tables = TablesSchema.parse(rows).filter(
508
+ (table) => table.schema !== "temp" && !table.name.startsWith("sqlite_")
509
+ );
510
+ const tablesInfo = await Promise.all(
511
+ tables.map((table) => db.execute(`PRAGMA table_info(${table.name})`))
512
+ );
513
+ const columns = tablesInfo.map((def) => ColumnsSchema.parse(def));
514
+ const foreignKeysList = await Promise.all(
515
+ tables.map(
516
+ (table) => db.execute(`PRAGMA foreign_key_list(${table.name})`)
517
+ )
518
+ );
519
+ const foreignKeys = foreignKeysList.map(
520
+ (def) => ForeignKeysSchema.parse(def)
521
+ );
522
+ const countInfo = await Promise.all(
523
+ tables.map(
524
+ (table) => db.execute(`SELECT COUNT(*) as count FROM ${table.name}`)
525
+ )
526
+ );
527
+ const counts = countInfo.map((def) => {
528
+ return def[0].count || 0;
529
+ });
530
+ return c.json(
531
+ {
532
+ enabled: true,
533
+ db: tablesInfo.map((_, index) => {
534
+ return {
535
+ table: tables[index],
536
+ columns: columns[index],
537
+ foreignKeys: foreignKeys[index],
538
+ records: counts[index]
539
+ };
540
+ })
541
+ },
542
+ 200
543
+ );
544
+ }).post(
545
+ "/db",
546
+ sValidator(
547
+ "json",
548
+ z.object({ query: z.string(), params: z.array(z.any()).optional() })
549
+ ),
550
+ async (c) => {
551
+ if (!await c.var.inspector.accessors.isDbEnabled()) {
552
+ return c.json({ enabled: false }, 200);
553
+ }
554
+ const db = await c.var.inspector.accessors.getDb();
555
+ try {
556
+ const result = await db.execute(
557
+ c.req.valid("json").query,
558
+ ...c.req.valid("json").params || []
559
+ );
560
+ return c.json({ result }, 200);
561
+ } catch (error) {
562
+ c;
563
+ return c.json({ error: error.message }, 500);
564
+ }
565
+ }
566
+ );
567
+ }
568
+ var ActorInspector = class {
569
+ accessors;
570
+ emitter = createNanoEvents();
571
+ #lastRealtimeEvents = [];
572
+ get lastRealtimeEvents() {
573
+ return this.#lastRealtimeEvents;
574
+ }
575
+ constructor(accessors) {
576
+ this.accessors = accessors();
577
+ this.emitter.on("eventFired", (event) => {
578
+ this.#lastRealtimeEvents.push({
579
+ id: crypto.randomUUID(),
580
+ timestamp: Date.now(),
581
+ ...event
582
+ });
583
+ if (this.#lastRealtimeEvents.length > 100) {
584
+ this.#lastRealtimeEvents = this.#lastRealtimeEvents.slice(-100);
585
+ }
586
+ });
587
+ }
588
+ };
589
+
590
+ // src/actor/context.ts
591
+ var ActorContext = class {
592
+ #actor;
593
+ constructor(actor) {
594
+ this.#actor = actor;
595
+ }
596
+ /**
597
+ * Get the actor state
598
+ */
599
+ get state() {
600
+ return this.#actor.state;
601
+ }
602
+ /**
603
+ * Get the actor variables
604
+ */
605
+ get vars() {
606
+ return this.#actor.vars;
607
+ }
608
+ /**
609
+ * Broadcasts an event to all connected clients.
610
+ * @param name - The name of the event.
611
+ * @param args - The arguments to send with the event.
612
+ */
613
+ broadcast(name, ...args) {
614
+ this.#actor._broadcast(name, ...args);
615
+ return;
616
+ }
617
+ /**
618
+ * Gets the logger instance.
619
+ */
620
+ get log() {
621
+ return this.#actor.log;
622
+ }
623
+ /**
624
+ * Gets actor ID.
625
+ */
626
+ get actorId() {
627
+ return this.#actor.id;
628
+ }
629
+ /**
630
+ * Gets the actor name.
631
+ */
632
+ get name() {
633
+ return this.#actor.name;
634
+ }
635
+ /**
636
+ * Gets the actor key.
637
+ */
638
+ get key() {
639
+ return this.#actor.key;
640
+ }
641
+ /**
642
+ * Gets the region.
643
+ */
644
+ get region() {
645
+ return this.#actor.region;
646
+ }
647
+ /**
648
+ * Gets the scheduler.
649
+ */
650
+ get schedule() {
651
+ return this.#actor.schedule;
652
+ }
653
+ /**
654
+ * Gets the map of connections.
655
+ */
656
+ get conns() {
657
+ return this.#actor.conns;
658
+ }
659
+ /**
660
+ * Returns the client for the given registry.
661
+ */
662
+ client() {
663
+ return this.#actor.inlineClient;
664
+ }
665
+ /**
666
+ * Gets the database.
667
+ * @experimental
668
+ * @throws {DatabaseNotEnabled} If the database is not enabled.
669
+ */
670
+ get db() {
671
+ return this.#actor.db;
672
+ }
673
+ /**
674
+ * Forces the state to get saved.
675
+ *
676
+ * @param opts - Options for saving the state.
677
+ */
678
+ async saveState(opts) {
679
+ return this.#actor.saveState(opts);
680
+ }
681
+ /**
682
+ * Prevents the actor from sleeping until promise is complete.
683
+ */
684
+ waitUntil(promise) {
685
+ this.#actor._waitUntil(promise);
686
+ }
687
+ /**
688
+ * AbortSignal that fires when the actor is stopping.
689
+ */
690
+ get abortSignal() {
691
+ return this.#actor.abortSignal;
692
+ }
693
+ /**
694
+ * Forces the actor to sleep.
695
+ *
696
+ * Not supported on all drivers.
697
+ *
698
+ * @experimental
699
+ */
700
+ sleep() {
701
+ this.#actor._sleep();
702
+ }
703
+ };
704
+
705
+ // src/actor/keys.ts
706
+ var EMPTY_KEY = "/";
707
+ var KEY_SEPARATOR = "/";
708
+ function serializeActorKey(key) {
709
+ if (key.length === 0) {
710
+ return EMPTY_KEY;
711
+ }
712
+ const escapedParts = key.map((part) => {
713
+ if (part === "") {
714
+ return "\\0";
715
+ }
716
+ let escaped = part.replace(/\\/g, "\\\\");
717
+ escaped = escaped.replace(/\//g, `\\${KEY_SEPARATOR}`);
718
+ return escaped;
719
+ });
720
+ return escapedParts.join(KEY_SEPARATOR);
721
+ }
722
+ function deserializeActorKey(keyString) {
723
+ if (keyString === void 0 || keyString === null || keyString === EMPTY_KEY) {
724
+ return [];
725
+ }
726
+ const parts = [];
727
+ let currentPart = "";
728
+ let escaping = false;
729
+ let isEmptyStringMarker = false;
730
+ for (let i = 0; i < keyString.length; i++) {
731
+ const char = keyString[i];
732
+ if (escaping) {
733
+ if (char === "0") {
734
+ isEmptyStringMarker = true;
735
+ } else {
736
+ currentPart += char;
737
+ }
738
+ escaping = false;
739
+ } else if (char === "\\") {
740
+ escaping = true;
741
+ } else if (char === KEY_SEPARATOR) {
742
+ if (isEmptyStringMarker) {
743
+ parts.push("");
744
+ isEmptyStringMarker = false;
745
+ } else {
746
+ parts.push(currentPart);
747
+ }
748
+ currentPart = "";
749
+ } else {
750
+ currentPart += char;
751
+ }
752
+ }
753
+ if (escaping) {
754
+ parts.push(currentPart + "\\");
755
+ } else if (isEmptyStringMarker) {
756
+ parts.push("");
757
+ } else if (currentPart !== "" || parts.length > 0) {
758
+ parts.push(currentPart);
759
+ }
760
+ return parts;
761
+ }
762
+
763
+ // src/actor/schedule.ts
764
+ var Schedule = class {
765
+ #actor;
766
+ constructor(actor) {
767
+ this.#actor = actor;
768
+ }
769
+ async after(duration, fn, ...args) {
770
+ await this.#actor.scheduleEvent(Date.now() + duration, fn, args);
771
+ }
772
+ async at(timestamp, fn, ...args) {
773
+ await this.#actor.scheduleEvent(timestamp, fn, args);
774
+ }
775
+ };
776
+
777
+ // src/actor/instance.ts
778
+ var ActorInstance = class {
779
+ // Shared actor context for this instance
780
+ actorContext;
781
+ /** Actor log, intended for the user to call */
782
+ #log;
783
+ /** Runtime log, intended for internal actor logs */
784
+ #rLog;
785
+ #sleepCalled = false;
786
+ #stopCalled = false;
787
+ get isStopping() {
788
+ return this.#stopCalled || this.#sleepCalled;
789
+ }
790
+ #persistChanged = false;
791
+ #isInOnStateChange = false;
792
+ /**
793
+ * The proxied state that notifies of changes automatically.
794
+ *
795
+ * Any data that should be stored indefinitely should be held within this object.
796
+ */
797
+ #persist;
798
+ /** Raw state without the proxy wrapper */
799
+ #persistRaw;
800
+ #persistWriteQueue = new SinglePromiseQueue();
801
+ #alarmWriteQueue = new SinglePromiseQueue();
802
+ #lastSaveTime = 0;
803
+ #pendingSaveTimeout;
804
+ #vars;
805
+ #backgroundPromises = [];
806
+ #abortController = new AbortController();
807
+ #config;
808
+ #actorDriver;
809
+ #inlineClient;
810
+ #actorId;
811
+ #name;
812
+ #key;
813
+ #region;
814
+ #ready = false;
815
+ #connections = /* @__PURE__ */ new Map();
816
+ #subscriptionIndex = /* @__PURE__ */ new Map();
817
+ #checkConnLivenessInterval;
818
+ #sleepTimeout;
819
+ // Track active raw requests so sleep logic can account for them
820
+ #activeRawFetchCount = 0;
821
+ #activeRawWebSockets = /* @__PURE__ */ new Set();
822
+ #schedule;
823
+ #db;
824
+ #inspector = new ActorInspector(() => {
825
+ return {
826
+ isDbEnabled: async () => {
827
+ return this.#db !== void 0;
828
+ },
829
+ getDb: async () => {
830
+ return this.db;
831
+ },
832
+ isStateEnabled: async () => {
833
+ return this.stateEnabled;
834
+ },
835
+ getState: async () => {
836
+ this.#validateStateEnabled();
837
+ return this.#persistRaw.state;
838
+ },
839
+ getRpcs: async () => {
840
+ return Object.keys(this.#config.actions);
841
+ },
842
+ getConnections: async () => {
843
+ return Array.from(this.#connections.entries()).map(([id, conn]) => ({
844
+ id,
845
+ stateEnabled: conn.__stateEnabled,
846
+ params: conn.params,
847
+ state: conn.__stateEnabled ? conn.state : void 0
848
+ }));
849
+ },
850
+ setState: async (state) => {
851
+ this.#validateStateEnabled();
852
+ this.#persist.state = { ...state };
853
+ await this.saveState({ immediate: true });
854
+ }
855
+ };
856
+ });
857
+ get id() {
858
+ return this.#actorId;
859
+ }
860
+ get inlineClient() {
861
+ return this.#inlineClient;
862
+ }
863
+ get inspector() {
864
+ return this.#inspector;
865
+ }
866
+ get #sleepingSupported() {
867
+ return this.#actorDriver.sleep !== void 0;
868
+ }
869
+ /**
870
+ * This constructor should never be used directly.
871
+ *
872
+ * Constructed in {@link ActorInstance.start}.
873
+ *
874
+ * @private
875
+ */
876
+ constructor(config) {
877
+ this.#config = config;
878
+ this.actorContext = new ActorContext(this);
879
+ }
880
+ async start(actorDriver, inlineClient, actorId, name, key, region) {
881
+ var _a, _b;
882
+ const logParams = {
883
+ actor: name,
884
+ key: serializeActorKey(key),
885
+ actorId
886
+ };
887
+ this.#log = getBaseLogger().child(
888
+ Object.assign(getIncludeTarget() ? { target: "actor" } : {}, logParams)
889
+ );
890
+ this.#rLog = getBaseLogger().child(
891
+ Object.assign(
892
+ getIncludeTarget() ? { target: "actor-runtime" } : {},
893
+ logParams
894
+ )
895
+ );
896
+ this.#actorDriver = actorDriver;
897
+ this.#inlineClient = inlineClient;
898
+ this.#actorId = actorId;
899
+ this.#name = name;
900
+ this.#key = key;
901
+ this.#region = region;
902
+ this.#schedule = new Schedule(this);
903
+ await this.#initialize();
904
+ if (this.#varsEnabled) {
905
+ let vars;
906
+ if ("createVars" in this.#config) {
907
+ const dataOrPromise = this.#config.createVars(
908
+ this.actorContext,
909
+ this.#actorDriver.getContext(this.#actorId)
910
+ );
911
+ if (dataOrPromise instanceof Promise) {
912
+ vars = await deadline(
913
+ dataOrPromise,
914
+ this.#config.options.createVarsTimeout
915
+ );
916
+ } else {
917
+ vars = dataOrPromise;
918
+ }
919
+ } else if ("vars" in this.#config) {
920
+ vars = structuredClone(this.#config.vars);
921
+ } else {
922
+ throw new Error("Could not variables from 'createVars' or 'vars'");
923
+ }
924
+ this.#vars = vars;
925
+ }
926
+ this.#rLog.info({ msg: "actor starting" });
927
+ if (this.#config.onStart) {
928
+ const result = this.#config.onStart(this.actorContext);
929
+ if (result instanceof Promise) {
930
+ await result;
931
+ }
932
+ }
933
+ if ("db" in this.#config && this.#config.db) {
934
+ const client = await this.#config.db.createClient({
935
+ getDatabase: () => actorDriver.getDatabase(this.#actorId)
936
+ });
937
+ this.#rLog.info({ msg: "database migration starting" });
938
+ await ((_b = (_a = this.#config.db).onMigrate) == null ? void 0 : _b.call(_a, client));
939
+ this.#rLog.info({ msg: "database migration complete" });
940
+ this.#db = client;
941
+ }
942
+ if (this.#persist.scheduledEvents.length > 0) {
943
+ await this.#queueSetAlarm(this.#persist.scheduledEvents[0].timestamp);
944
+ }
945
+ this.#rLog.info({ msg: "actor ready" });
946
+ this.#ready = true;
947
+ this.#resetSleepTimer();
948
+ this.#checkConnLivenessInterval = setInterval(
949
+ this.#checkConnectionsLiveness.bind(this),
950
+ this.#config.options.connectionLivenessInterval
951
+ );
952
+ this.#checkConnectionsLiveness();
953
+ }
954
+ async #scheduleEventInner(newEvent) {
955
+ this.actorContext.log.info({ msg: "scheduling event", ...newEvent });
956
+ const insertIndex = this.#persist.scheduledEvents.findIndex(
957
+ (x) => x.timestamp > newEvent.timestamp
958
+ );
959
+ if (insertIndex === -1) {
960
+ this.#persist.scheduledEvents.push(newEvent);
961
+ } else {
962
+ this.#persist.scheduledEvents.splice(insertIndex, 0, newEvent);
963
+ }
964
+ if (insertIndex === 0 || this.#persist.scheduledEvents.length === 1) {
965
+ this.actorContext.log.info({
966
+ msg: "setting alarm",
967
+ timestamp: newEvent.timestamp,
968
+ eventCount: this.#persist.scheduledEvents.length
969
+ });
970
+ await this.#queueSetAlarm(newEvent.timestamp);
971
+ }
972
+ }
973
+ async _onAlarm() {
974
+ const now = Date.now();
975
+ this.actorContext.log.debug({
976
+ msg: "alarm triggered",
977
+ now,
978
+ events: this.#persist.scheduledEvents.length
979
+ });
980
+ this.#resetSleepTimer();
981
+ const runIndex = this.#persist.scheduledEvents.findIndex(
982
+ (x) => x.timestamp <= now
983
+ );
984
+ if (runIndex === -1) {
985
+ this.#rLog.warn({ msg: "no events are due yet, time may have broken" });
986
+ if (this.#persist.scheduledEvents.length > 0) {
987
+ const nextTs = this.#persist.scheduledEvents[0].timestamp;
988
+ this.actorContext.log.warn({
989
+ msg: "alarm fired early, rescheduling for next event",
990
+ now,
991
+ nextTs,
992
+ delta: nextTs - now
993
+ });
994
+ await this.#queueSetAlarm(nextTs);
995
+ }
996
+ this.actorContext.log.debug({ msg: "no events to run", now });
997
+ return;
998
+ }
999
+ const scheduleEvents = this.#persist.scheduledEvents.splice(
1000
+ 0,
1001
+ runIndex + 1
1002
+ );
1003
+ this.actorContext.log.debug({
1004
+ msg: "running events",
1005
+ count: scheduleEvents.length
1006
+ });
1007
+ if (this.#persist.scheduledEvents.length > 0) {
1008
+ const nextTs = this.#persist.scheduledEvents[0].timestamp;
1009
+ this.actorContext.log.info({
1010
+ msg: "setting next alarm",
1011
+ nextTs,
1012
+ remainingEvents: this.#persist.scheduledEvents.length
1013
+ });
1014
+ await this.#queueSetAlarm(nextTs);
1015
+ }
1016
+ for (const event of scheduleEvents) {
1017
+ try {
1018
+ this.actorContext.log.info({
1019
+ msg: "running action for event",
1020
+ event: event.eventId,
1021
+ timestamp: event.timestamp,
1022
+ action: event.kind.generic.actionName
1023
+ });
1024
+ const fn = this.#config.actions[event.kind.generic.actionName];
1025
+ if (!fn)
1026
+ throw new Error(
1027
+ `Missing action for alarm ${event.kind.generic.actionName}`
1028
+ );
1029
+ if (typeof fn !== "function")
1030
+ throw new Error(
1031
+ `Alarm function lookup for ${event.kind.generic.actionName} returned ${typeof fn}`
1032
+ );
1033
+ try {
1034
+ const args = event.kind.generic.args ? cbor2.decode(new Uint8Array(event.kind.generic.args)) : [];
1035
+ await fn.call(void 0, this.actorContext, ...args);
1036
+ } catch (error) {
1037
+ this.actorContext.log.error({
1038
+ msg: "error while running event",
1039
+ error: stringifyError(error),
1040
+ event: event.eventId,
1041
+ timestamp: event.timestamp,
1042
+ action: event.kind.generic.actionName
1043
+ });
1044
+ }
1045
+ } catch (error) {
1046
+ this.actorContext.log.error({
1047
+ msg: "internal error while running event",
1048
+ error: stringifyError(error),
1049
+ ...event
1050
+ });
1051
+ }
1052
+ }
1053
+ }
1054
+ async scheduleEvent(timestamp, action, args) {
1055
+ return this.#scheduleEventInner({
1056
+ eventId: crypto.randomUUID(),
1057
+ timestamp,
1058
+ kind: {
1059
+ generic: {
1060
+ actionName: action,
1061
+ args: bufferToArrayBuffer(cbor2.encode(args))
1062
+ }
1063
+ }
1064
+ });
1065
+ }
1066
+ get stateEnabled() {
1067
+ return "createState" in this.#config || "state" in this.#config;
1068
+ }
1069
+ #validateStateEnabled() {
1070
+ if (!this.stateEnabled) {
1071
+ throw new StateNotEnabled();
1072
+ }
1073
+ }
1074
+ get connStateEnabled() {
1075
+ return "createConnState" in this.#config || "connState" in this.#config;
1076
+ }
1077
+ get #varsEnabled() {
1078
+ return "createVars" in this.#config || "vars" in this.#config;
1079
+ }
1080
+ #validateVarsEnabled() {
1081
+ if (!this.#varsEnabled) {
1082
+ throw new VarsNotEnabled();
1083
+ }
1084
+ }
1085
+ /** Promise used to wait for a save to complete. This is required since you cannot await `#saveStateThrottled`. */
1086
+ #onPersistSavedPromise;
1087
+ /** Throttled save state method. Used to write to KV at a reasonable cadence. */
1088
+ #savePersistThrottled() {
1089
+ const now = Date.now();
1090
+ const timeSinceLastSave = now - this.#lastSaveTime;
1091
+ const saveInterval = this.#config.options.stateSaveInterval;
1092
+ if (timeSinceLastSave < saveInterval) {
1093
+ if (this.#pendingSaveTimeout === void 0) {
1094
+ this.#pendingSaveTimeout = setTimeout(() => {
1095
+ this.#pendingSaveTimeout = void 0;
1096
+ this.#savePersistInner();
1097
+ }, saveInterval - timeSinceLastSave);
1098
+ }
1099
+ } else {
1100
+ this.#savePersistInner();
1101
+ }
1102
+ }
1103
+ /** Saves the state to KV. You probably want to use #saveStateThrottled instead except for a few edge cases. */
1104
+ async #savePersistInner() {
1105
+ var _a, _b;
1106
+ try {
1107
+ this.#lastSaveTime = Date.now();
1108
+ if (this.#persistChanged) {
1109
+ const finished = this.#persistWriteQueue.enqueue(async () => {
1110
+ this.#rLog.debug({ msg: "saving persist" });
1111
+ this.#persistChanged = false;
1112
+ const bareData = this.#convertToBarePersisted(this.#persistRaw);
1113
+ await this.#actorDriver.writePersistedData(
1114
+ this.#actorId,
1115
+ PERSISTED_ACTOR_VERSIONED.serializeWithEmbeddedVersion(bareData)
1116
+ );
1117
+ this.#rLog.debug({ msg: "persist saved" });
1118
+ });
1119
+ await finished;
1120
+ }
1121
+ (_a = this.#onPersistSavedPromise) == null ? void 0 : _a.resolve();
1122
+ } catch (error) {
1123
+ (_b = this.#onPersistSavedPromise) == null ? void 0 : _b.reject(error);
1124
+ throw error;
1125
+ }
1126
+ }
1127
+ async #queueSetAlarm(timestamp) {
1128
+ await this.#alarmWriteQueue.enqueue(async () => {
1129
+ await this.#actorDriver.setAlarm(this, timestamp);
1130
+ });
1131
+ }
1132
+ /**
1133
+ * Creates proxy for `#persist` that handles automatically flagging when state needs to be updated.
1134
+ */
1135
+ #setPersist(target) {
1136
+ this.#persistRaw = target;
1137
+ if (target === null || typeof target !== "object") {
1138
+ let invalidPath = "";
1139
+ if (!isCborSerializable(
1140
+ target,
1141
+ (path) => {
1142
+ invalidPath = path;
1143
+ },
1144
+ ""
1145
+ )) {
1146
+ throw new InvalidStateType({ path: invalidPath });
1147
+ }
1148
+ return target;
1149
+ }
1150
+ if (this.#persist) {
1151
+ onChange.unsubscribe(this.#persist);
1152
+ }
1153
+ this.#persist = onChange(
1154
+ target,
1155
+ // biome-ignore lint/suspicious/noExplicitAny: Don't know types in proxy
1156
+ (path, value, _previousValue, _applyData) => {
1157
+ if (path !== "state" && !path.startsWith("state.")) {
1158
+ return;
1159
+ }
1160
+ let invalidPath = "";
1161
+ if (!isCborSerializable(
1162
+ value,
1163
+ (invalidPathPart) => {
1164
+ invalidPath = invalidPathPart;
1165
+ },
1166
+ ""
1167
+ )) {
1168
+ throw new InvalidStateType({
1169
+ path: path + (invalidPath ? `.${invalidPath}` : "")
1170
+ });
1171
+ }
1172
+ this.#persistChanged = true;
1173
+ this.inspector.emitter.emit("stateUpdated", this.#persist.state);
1174
+ if (this.#config.onStateChange && this.#ready && !this.#isInOnStateChange) {
1175
+ try {
1176
+ this.#isInOnStateChange = true;
1177
+ this.#config.onStateChange(
1178
+ this.actorContext,
1179
+ this.#persistRaw.state
1180
+ );
1181
+ } catch (error) {
1182
+ this.#rLog.error({
1183
+ msg: "error in `_onStateChange`",
1184
+ error: stringifyError(error)
1185
+ });
1186
+ } finally {
1187
+ this.#isInOnStateChange = false;
1188
+ }
1189
+ }
1190
+ },
1191
+ { ignoreDetached: true }
1192
+ );
1193
+ }
1194
+ async #initialize() {
1195
+ const persistDataBuffer = await this.#actorDriver.readPersistedData(
1196
+ this.#actorId
1197
+ );
1198
+ invariant(
1199
+ persistDataBuffer !== void 0,
1200
+ "persist data has not been set, it should be set when initialized"
1201
+ );
1202
+ const bareData = PERSISTED_ACTOR_VERSIONED.deserializeWithEmbeddedVersion(
1203
+ persistDataBuffer
1204
+ );
1205
+ const persistData = this.#convertFromBarePersisted(bareData);
1206
+ if (persistData.hasInitiated) {
1207
+ this.#rLog.info({
1208
+ msg: "actor restoring",
1209
+ connections: persistData.connections.length
1210
+ });
1211
+ this.#setPersist(persistData);
1212
+ for (const connPersist of this.#persist.connections) {
1213
+ const conn = new Conn(this, connPersist);
1214
+ this.#connections.set(conn.id, conn);
1215
+ for (const sub of connPersist.subscriptions) {
1216
+ this.#addSubscription(sub.eventName, conn, true);
1217
+ }
1218
+ }
1219
+ } else {
1220
+ this.#rLog.info({ msg: "actor creating" });
1221
+ let stateData;
1222
+ if (this.stateEnabled) {
1223
+ this.#rLog.info({ msg: "actor state initializing" });
1224
+ if ("createState" in this.#config) {
1225
+ this.#config.createState;
1226
+ stateData = await this.#config.createState(
1227
+ this.actorContext,
1228
+ persistData.input
1229
+ );
1230
+ } else if ("state" in this.#config) {
1231
+ stateData = structuredClone(this.#config.state);
1232
+ } else {
1233
+ throw new Error("Both 'createState' or 'state' were not defined");
1234
+ }
1235
+ } else {
1236
+ this.#rLog.debug({ msg: "state not enabled" });
1237
+ }
1238
+ persistData.state = stateData;
1239
+ persistData.hasInitiated = true;
1240
+ this.#rLog.debug({ msg: "writing state" });
1241
+ const bareData2 = this.#convertToBarePersisted(persistData);
1242
+ await this.#actorDriver.writePersistedData(
1243
+ this.#actorId,
1244
+ PERSISTED_ACTOR_VERSIONED.serializeWithEmbeddedVersion(bareData2)
1245
+ );
1246
+ this.#setPersist(persistData);
1247
+ if (this.#config.onCreate) {
1248
+ await this.#config.onCreate(this.actorContext, persistData.input);
1249
+ }
1250
+ }
1251
+ }
1252
+ __getConnForId(id) {
1253
+ return this.#connections.get(id);
1254
+ }
1255
+ /**
1256
+ * Connection disconnected.
1257
+ *
1258
+ * If a clean diconnect, will be removed immediately.
1259
+ *
1260
+ * If not a clean disconnect, will keep the connection alive for a given interval to wait for reconnect.
1261
+ */
1262
+ __connDisconnected(conn, wasClean, socketId) {
1263
+ if (socketId && conn.__socket && socketId !== conn.__socket.socketId) {
1264
+ this.rLog.debug({
1265
+ msg: "ignoring stale disconnect event",
1266
+ connId: conn.id,
1267
+ eventSocketId: socketId,
1268
+ currentSocketId: conn.__socket.socketId
1269
+ });
1270
+ return;
1271
+ }
1272
+ if (wasClean) {
1273
+ this.#removeConn(conn);
1274
+ } else {
1275
+ if (!conn.__driverState) {
1276
+ this.rLog.warn("called conn disconnected without driver state");
1277
+ }
1278
+ conn.__persist.lastSeen = Date.now();
1279
+ conn.__socket = void 0;
1280
+ }
1281
+ }
1282
+ /**
1283
+ * Removes a connection and cleans up its resources.
1284
+ */
1285
+ #removeConn(conn) {
1286
+ const connIdx = this.#persist.connections.findIndex(
1287
+ (c) => c.connId === conn.id
1288
+ );
1289
+ if (connIdx !== -1) {
1290
+ this.#persist.connections.splice(connIdx, 1);
1291
+ this.saveState({ immediate: true, allowStoppingState: true });
1292
+ } else {
1293
+ this.#rLog.warn({
1294
+ msg: "could not find persisted connection to remove",
1295
+ connId: conn.id
1296
+ });
1297
+ }
1298
+ this.#connections.delete(conn.id);
1299
+ for (const eventName of [...conn.subscriptions.values()]) {
1300
+ this.#removeSubscription(eventName, conn, true);
1301
+ }
1302
+ this.inspector.emitter.emit("connectionUpdated");
1303
+ if (this.#config.onDisconnect) {
1304
+ try {
1305
+ const result = this.#config.onDisconnect(this.actorContext, conn);
1306
+ if (result instanceof Promise) {
1307
+ result.catch((error) => {
1308
+ this.#rLog.error({
1309
+ msg: "error in `onDisconnect`",
1310
+ error: stringifyError(error)
1311
+ });
1312
+ });
1313
+ }
1314
+ } catch (error) {
1315
+ this.#rLog.error({
1316
+ msg: "error in `onDisconnect`",
1317
+ error: stringifyError(error)
1318
+ });
1319
+ }
1320
+ }
1321
+ this.#resetSleepTimer();
1322
+ }
1323
+ /**
1324
+ * Called to create a new connection or reconnect an existing one.
1325
+ */
1326
+ async createConn(socket, params, request, connectionId, connectionToken) {
1327
+ this.#assertReady();
1328
+ if (connectionId && connectionToken) {
1329
+ this.rLog.debug({
1330
+ msg: "checking for existing connection",
1331
+ connectionId
1332
+ });
1333
+ const existingConn = this.#connections.get(connectionId);
1334
+ if (existingConn && existingConn._token === connectionToken) {
1335
+ this.rLog.debug({
1336
+ msg: "reconnecting existing connection",
1337
+ connectionId
1338
+ });
1339
+ if (existingConn.__driverState) {
1340
+ const driverKind = getConnDriverKindFromState(
1341
+ existingConn.__driverState
1342
+ );
1343
+ const driver = CONN_DRIVERS[driverKind];
1344
+ if (driver.disconnect) {
1345
+ driver.disconnect(
1346
+ this,
1347
+ existingConn,
1348
+ existingConn.__driverState[driverKind],
1349
+ "Reconnecting with new driver state"
1350
+ );
1351
+ }
1352
+ }
1353
+ existingConn.__socket = socket;
1354
+ existingConn.__persist.lastSeen = Date.now();
1355
+ this.#resetSleepTimer();
1356
+ this.inspector.emitter.emit("connectionUpdated");
1357
+ existingConn._sendMessage(
1358
+ new CachedSerializer(
1359
+ {
1360
+ body: {
1361
+ tag: "Init",
1362
+ val: {
1363
+ actorId: this.id,
1364
+ connectionId: existingConn.id,
1365
+ connectionToken: existingConn._token
1366
+ }
1367
+ }
1368
+ },
1369
+ TO_CLIENT_VERSIONED
1370
+ )
1371
+ );
1372
+ return existingConn;
1373
+ }
1374
+ this.rLog.debug({
1375
+ msg: "connection not found or token mismatch, creating new connection",
1376
+ connectionId
1377
+ });
1378
+ }
1379
+ const newConnId = generateConnId();
1380
+ const newConnToken = generateConnToken();
1381
+ if (this.#connections.has(newConnId)) {
1382
+ throw new Error(`Connection already exists: ${newConnId}`);
1383
+ }
1384
+ let connState;
1385
+ const onBeforeConnectOpts = {
1386
+ request
1387
+ };
1388
+ if (this.#config.onBeforeConnect) {
1389
+ await this.#config.onBeforeConnect(
1390
+ this.actorContext,
1391
+ onBeforeConnectOpts,
1392
+ params
1393
+ );
1394
+ }
1395
+ if (this.connStateEnabled) {
1396
+ if ("createConnState" in this.#config) {
1397
+ const dataOrPromise = this.#config.createConnState(
1398
+ this.actorContext,
1399
+ onBeforeConnectOpts,
1400
+ params
1401
+ );
1402
+ if (dataOrPromise instanceof Promise) {
1403
+ connState = await deadline(
1404
+ dataOrPromise,
1405
+ this.#config.options.createConnStateTimeout
1406
+ );
1407
+ } else {
1408
+ connState = dataOrPromise;
1409
+ }
1410
+ } else if ("connState" in this.#config) {
1411
+ connState = structuredClone(this.#config.connState);
1412
+ } else {
1413
+ throw new Error(
1414
+ "Could not create connection state from 'createConnState' or 'connState'"
1415
+ );
1416
+ }
1417
+ }
1418
+ const persist = {
1419
+ connId: newConnId,
1420
+ token: newConnToken,
1421
+ params,
1422
+ state: connState,
1423
+ lastSeen: Date.now(),
1424
+ subscriptions: []
1425
+ };
1426
+ const conn = new Conn(this, persist);
1427
+ conn.__socket = socket;
1428
+ this.#connections.set(conn.id, conn);
1429
+ this.#resetSleepTimer();
1430
+ this.#persist.connections.push(persist);
1431
+ this.saveState({ immediate: true });
1432
+ if (this.#config.onConnect) {
1433
+ try {
1434
+ const result = this.#config.onConnect(this.actorContext, conn);
1435
+ if (result instanceof Promise) {
1436
+ deadline(result, this.#config.options.onConnectTimeout).catch(
1437
+ (error) => {
1438
+ this.#rLog.error({
1439
+ msg: "error in `onConnect`, closing socket",
1440
+ error
1441
+ });
1442
+ conn == null ? void 0 : conn.disconnect("`onConnect` failed");
1443
+ }
1444
+ );
1445
+ }
1446
+ } catch (error) {
1447
+ this.#rLog.error({
1448
+ msg: "error in `onConnect`",
1449
+ error: stringifyError(error)
1450
+ });
1451
+ conn == null ? void 0 : conn.disconnect("`onConnect` failed");
1452
+ }
1453
+ }
1454
+ this.inspector.emitter.emit("connectionUpdated");
1455
+ conn._sendMessage(
1456
+ new CachedSerializer(
1457
+ {
1458
+ body: {
1459
+ tag: "Init",
1460
+ val: {
1461
+ actorId: this.id,
1462
+ connectionId: conn.id,
1463
+ connectionToken: conn._token
1464
+ }
1465
+ }
1466
+ },
1467
+ TO_CLIENT_VERSIONED
1468
+ )
1469
+ );
1470
+ return conn;
1471
+ }
1472
+ // MARK: Messages
1473
+ async processMessage(message, conn) {
1474
+ await processMessage(message, this, conn, {
1475
+ onExecuteAction: async (ctx, name, args) => {
1476
+ this.inspector.emitter.emit("eventFired", {
1477
+ type: "action",
1478
+ name,
1479
+ args,
1480
+ connId: conn.id
1481
+ });
1482
+ return await this.executeAction(ctx, name, args);
1483
+ },
1484
+ onSubscribe: async (eventName, conn2) => {
1485
+ this.inspector.emitter.emit("eventFired", {
1486
+ type: "subscribe",
1487
+ eventName,
1488
+ connId: conn2.id
1489
+ });
1490
+ this.#addSubscription(eventName, conn2, false);
1491
+ },
1492
+ onUnsubscribe: async (eventName, conn2) => {
1493
+ this.inspector.emitter.emit("eventFired", {
1494
+ type: "unsubscribe",
1495
+ eventName,
1496
+ connId: conn2.id
1497
+ });
1498
+ this.#removeSubscription(eventName, conn2, false);
1499
+ }
1500
+ });
1501
+ }
1502
+ // MARK: Events
1503
+ #addSubscription(eventName, connection, fromPersist) {
1504
+ if (connection.subscriptions.has(eventName)) {
1505
+ this.#rLog.debug({
1506
+ msg: "connection already has subscription",
1507
+ eventName
1508
+ });
1509
+ return;
1510
+ }
1511
+ if (!fromPersist) {
1512
+ connection.__persist.subscriptions.push({ eventName });
1513
+ this.saveState({ immediate: true });
1514
+ }
1515
+ connection.subscriptions.add(eventName);
1516
+ let subscribers = this.#subscriptionIndex.get(eventName);
1517
+ if (!subscribers) {
1518
+ subscribers = /* @__PURE__ */ new Set();
1519
+ this.#subscriptionIndex.set(eventName, subscribers);
1520
+ }
1521
+ subscribers.add(connection);
1522
+ }
1523
+ #removeSubscription(eventName, connection, fromRemoveConn) {
1524
+ if (!connection.subscriptions.has(eventName)) {
1525
+ this.#rLog.warn({
1526
+ msg: "connection does not have subscription",
1527
+ eventName
1528
+ });
1529
+ return;
1530
+ }
1531
+ if (!fromRemoveConn) {
1532
+ connection.subscriptions.delete(eventName);
1533
+ const subIdx = connection.__persist.subscriptions.findIndex(
1534
+ (s) => s.eventName === eventName
1535
+ );
1536
+ if (subIdx !== -1) {
1537
+ connection.__persist.subscriptions.splice(subIdx, 1);
1538
+ } else {
1539
+ this.#rLog.warn({
1540
+ msg: "subscription does not exist with name",
1541
+ eventName
1542
+ });
1543
+ }
1544
+ this.saveState({ immediate: true });
1545
+ }
1546
+ const subscribers = this.#subscriptionIndex.get(eventName);
1547
+ if (subscribers) {
1548
+ subscribers.delete(connection);
1549
+ if (subscribers.size === 0) {
1550
+ this.#subscriptionIndex.delete(eventName);
1551
+ }
1552
+ }
1553
+ }
1554
+ #assertReady(allowStoppingState = false) {
1555
+ if (!this.#ready) throw new InternalError("Actor not ready");
1556
+ if (!allowStoppingState && this.#sleepCalled)
1557
+ throw new InternalError("Actor is going to sleep");
1558
+ if (!allowStoppingState && this.#stopCalled)
1559
+ throw new InternalError("Actor is stopping");
1560
+ }
1561
+ /**
1562
+ * Check the liveness of all connections.
1563
+ * Sets up a recurring check based on the configured interval.
1564
+ */
1565
+ #checkConnectionsLiveness() {
1566
+ this.#rLog.debug({ msg: "checking connections liveness" });
1567
+ for (const conn of this.#connections.values()) {
1568
+ if (conn.__status === "connected") {
1569
+ this.#rLog.debug({ msg: "connection is alive", connId: conn.id });
1570
+ } else {
1571
+ const lastSeen = conn.__persist.lastSeen;
1572
+ const sinceLastSeen = Date.now() - lastSeen;
1573
+ if (sinceLastSeen < this.#config.options.connectionLivenessTimeout) {
1574
+ this.#rLog.debug({
1575
+ msg: "connection might be alive, will check later",
1576
+ connId: conn.id,
1577
+ lastSeen,
1578
+ sinceLastSeen
1579
+ });
1580
+ continue;
1581
+ }
1582
+ this.#rLog.warn({
1583
+ msg: "connection is dead, removing",
1584
+ connId: conn.id,
1585
+ lastSeen
1586
+ });
1587
+ this.#removeConn(conn);
1588
+ }
1589
+ }
1590
+ }
1591
+ /**
1592
+ * Check if the actor is ready to handle requests.
1593
+ */
1594
+ isReady() {
1595
+ return this.#ready;
1596
+ }
1597
+ /**
1598
+ * Execute an action call from a client.
1599
+ *
1600
+ * This method handles:
1601
+ * 1. Validating the action name
1602
+ * 2. Executing the action function
1603
+ * 3. Processing the result through onBeforeActionResponse (if configured)
1604
+ * 4. Handling timeouts and errors
1605
+ * 5. Saving state changes
1606
+ *
1607
+ * @param ctx The action context
1608
+ * @param actionName The name of the action being called
1609
+ * @param args The arguments passed to the action
1610
+ * @returns The result of the action call
1611
+ * @throws {ActionNotFound} If the action doesn't exist
1612
+ * @throws {ActionTimedOut} If the action times out
1613
+ * @internal
1614
+ */
1615
+ async executeAction(ctx, actionName, args) {
1616
+ invariant(this.#ready, "executing action before ready");
1617
+ if (!(actionName in this.#config.actions)) {
1618
+ this.#rLog.warn({ msg: "action does not exist", actionName });
1619
+ throw new ActionNotFound(actionName);
1620
+ }
1621
+ const actionFunction = this.#config.actions[actionName];
1622
+ if (typeof actionFunction !== "function") {
1623
+ this.#rLog.warn({
1624
+ msg: "action is not a function",
1625
+ actionName,
1626
+ type: typeof actionFunction
1627
+ });
1628
+ throw new ActionNotFound(actionName);
1629
+ }
1630
+ try {
1631
+ this.#rLog.debug({
1632
+ msg: "executing action",
1633
+ actionName,
1634
+ args
1635
+ });
1636
+ const outputOrPromise = actionFunction.call(void 0, ctx, ...args);
1637
+ let output;
1638
+ if (outputOrPromise instanceof Promise) {
1639
+ this.#rLog.debug({
1640
+ msg: "awaiting async action",
1641
+ actionName
1642
+ });
1643
+ output = await deadline(
1644
+ outputOrPromise,
1645
+ this.#config.options.actionTimeout
1646
+ );
1647
+ this.#rLog.debug({
1648
+ msg: "async action completed",
1649
+ actionName
1650
+ });
1651
+ } else {
1652
+ output = outputOrPromise;
1653
+ }
1654
+ if (this.#config.onBeforeActionResponse) {
1655
+ try {
1656
+ const processedOutput = this.#config.onBeforeActionResponse(
1657
+ this.actorContext,
1658
+ actionName,
1659
+ args,
1660
+ output
1661
+ );
1662
+ if (processedOutput instanceof Promise) {
1663
+ this.#rLog.debug({
1664
+ msg: "awaiting onBeforeActionResponse",
1665
+ actionName
1666
+ });
1667
+ output = await processedOutput;
1668
+ this.#rLog.debug({
1669
+ msg: "onBeforeActionResponse completed",
1670
+ actionName
1671
+ });
1672
+ } else {
1673
+ output = processedOutput;
1674
+ }
1675
+ } catch (error) {
1676
+ this.#rLog.error({
1677
+ msg: "error in `onBeforeActionResponse`",
1678
+ error: stringifyError(error)
1679
+ });
1680
+ }
1681
+ }
1682
+ this.#rLog.debug({
1683
+ msg: "action completed",
1684
+ actionName,
1685
+ outputType: typeof output,
1686
+ isPromise: output instanceof Promise
1687
+ });
1688
+ return output;
1689
+ } catch (error) {
1690
+ if (error instanceof DeadlineError) {
1691
+ throw new ActionTimedOut();
1692
+ }
1693
+ this.#rLog.error({
1694
+ msg: "action error",
1695
+ actionName,
1696
+ error: stringifyError(error)
1697
+ });
1698
+ throw error;
1699
+ } finally {
1700
+ this.#savePersistThrottled();
1701
+ }
1702
+ }
1703
+ /**
1704
+ * Returns a list of action methods available on this actor.
1705
+ */
1706
+ get actions() {
1707
+ return Object.keys(this.#config.actions);
1708
+ }
1709
+ /**
1710
+ * Handles raw HTTP requests to the actor.
1711
+ */
1712
+ async handleFetch(request, opts) {
1713
+ this.#assertReady();
1714
+ if (!this.#config.onFetch) {
1715
+ throw new FetchHandlerNotDefined();
1716
+ }
1717
+ this.#activeRawFetchCount++;
1718
+ this.#resetSleepTimer();
1719
+ try {
1720
+ const response = await this.#config.onFetch(
1721
+ this.actorContext,
1722
+ request,
1723
+ opts
1724
+ );
1725
+ if (!response) {
1726
+ throw new InvalidFetchResponse();
1727
+ }
1728
+ return response;
1729
+ } catch (error) {
1730
+ this.#rLog.error({ msg: "onFetch error", error: stringifyError(error) });
1731
+ throw error;
1732
+ } finally {
1733
+ this.#activeRawFetchCount = Math.max(0, this.#activeRawFetchCount - 1);
1734
+ this.#resetSleepTimer();
1735
+ this.#savePersistThrottled();
1736
+ }
1737
+ }
1738
+ /**
1739
+ * Handles raw WebSocket connections to the actor.
1740
+ */
1741
+ async handleWebSocket(websocket, opts) {
1742
+ this.#assertReady();
1743
+ if (!this.#config.onWebSocket) {
1744
+ throw new InternalError("onWebSocket handler not defined");
1745
+ }
1746
+ try {
1747
+ const stateBeforeHandler = this.#persistChanged;
1748
+ this.#activeRawWebSockets.add(websocket);
1749
+ this.#resetSleepTimer();
1750
+ const onSocketClosed = () => {
1751
+ try {
1752
+ websocket.removeEventListener("close", onSocketClosed);
1753
+ websocket.removeEventListener("error", onSocketClosed);
1754
+ } catch {
1755
+ }
1756
+ this.#activeRawWebSockets.delete(websocket);
1757
+ this.#resetSleepTimer();
1758
+ };
1759
+ try {
1760
+ websocket.addEventListener("close", onSocketClosed);
1761
+ websocket.addEventListener("error", onSocketClosed);
1762
+ } catch {
1763
+ }
1764
+ await this.#config.onWebSocket(this.actorContext, websocket, opts);
1765
+ if (this.#persistChanged && !stateBeforeHandler) {
1766
+ await this.saveState({ immediate: true });
1767
+ }
1768
+ } catch (error) {
1769
+ this.#rLog.error({
1770
+ msg: "onWebSocket error",
1771
+ error: stringifyError(error)
1772
+ });
1773
+ throw error;
1774
+ } finally {
1775
+ this.#savePersistThrottled();
1776
+ }
1777
+ }
1778
+ // MARK: Lifecycle hooks
1779
+ // MARK: Exposed methods
1780
+ get log() {
1781
+ invariant(this.#log, "log not configured");
1782
+ return this.#log;
1783
+ }
1784
+ get rLog() {
1785
+ invariant(this.#rLog, "log not configured");
1786
+ return this.#rLog;
1787
+ }
1788
+ /**
1789
+ * Gets the name.
1790
+ */
1791
+ get name() {
1792
+ return this.#name;
1793
+ }
1794
+ /**
1795
+ * Gets the key.
1796
+ */
1797
+ get key() {
1798
+ return this.#key;
1799
+ }
1800
+ /**
1801
+ * Gets the region.
1802
+ */
1803
+ get region() {
1804
+ return this.#region;
1805
+ }
1806
+ /**
1807
+ * Gets the scheduler.
1808
+ */
1809
+ get schedule() {
1810
+ return this.#schedule;
1811
+ }
1812
+ /**
1813
+ * Gets the map of connections.
1814
+ */
1815
+ get conns() {
1816
+ return this.#connections;
1817
+ }
1818
+ /**
1819
+ * Gets the current state.
1820
+ *
1821
+ * Changing properties of this value will automatically be persisted.
1822
+ */
1823
+ get state() {
1824
+ this.#validateStateEnabled();
1825
+ return this.#persist.state;
1826
+ }
1827
+ /**
1828
+ * Gets the database.
1829
+ * @experimental
1830
+ * @throws {DatabaseNotEnabled} If the database is not enabled.
1831
+ */
1832
+ get db() {
1833
+ if (!this.#db) {
1834
+ throw new DatabaseNotEnabled();
1835
+ }
1836
+ return this.#db;
1837
+ }
1838
+ /**
1839
+ * Sets the current state.
1840
+ *
1841
+ * This property will automatically be persisted.
1842
+ */
1843
+ set state(value) {
1844
+ this.#validateStateEnabled();
1845
+ this.#persist.state = value;
1846
+ }
1847
+ get vars() {
1848
+ this.#validateVarsEnabled();
1849
+ invariant(this.#vars !== void 0, "vars not enabled");
1850
+ return this.#vars;
1851
+ }
1852
+ /**
1853
+ * Broadcasts an event to all connected clients.
1854
+ * @param name - The name of the event.
1855
+ * @param args - The arguments to send with the event.
1856
+ */
1857
+ _broadcast(name, ...args) {
1858
+ this.#assertReady();
1859
+ this.inspector.emitter.emit("eventFired", {
1860
+ type: "broadcast",
1861
+ eventName: name,
1862
+ args
1863
+ });
1864
+ const subscriptions = this.#subscriptionIndex.get(name);
1865
+ if (!subscriptions) return;
1866
+ const toClientSerializer = new CachedSerializer(
1867
+ {
1868
+ body: {
1869
+ tag: "Event",
1870
+ val: {
1871
+ name,
1872
+ args: bufferToArrayBuffer(cbor2.encode(args))
1873
+ }
1874
+ }
1875
+ },
1876
+ TO_CLIENT_VERSIONED
1877
+ );
1878
+ for (const connection of subscriptions) {
1879
+ connection._sendMessage(toClientSerializer);
1880
+ }
1881
+ }
1882
+ /**
1883
+ * Prevents the actor from sleeping until promise is complete.
1884
+ *
1885
+ * This allows the actor runtime to ensure that a promise completes while
1886
+ * returning from an action request early.
1887
+ *
1888
+ * @param promise - The promise to run in the background.
1889
+ */
1890
+ _waitUntil(promise) {
1891
+ this.#assertReady();
1892
+ const nonfailablePromise = promise.then(() => {
1893
+ this.#rLog.debug({ msg: "wait until promise complete" });
1894
+ }).catch((error) => {
1895
+ this.#rLog.error({
1896
+ msg: "wait until promise failed",
1897
+ error: stringifyError(error)
1898
+ });
1899
+ });
1900
+ this.#backgroundPromises.push(nonfailablePromise);
1901
+ }
1902
+ /**
1903
+ * Forces the state to get saved.
1904
+ *
1905
+ * This is helpful if running a long task that may fail later or when
1906
+ * running a background job that updates the state.
1907
+ *
1908
+ * @param opts - Options for saving the state.
1909
+ */
1910
+ async saveState(opts) {
1911
+ this.#assertReady(opts.allowStoppingState);
1912
+ if (this.#persistChanged) {
1913
+ if (opts.immediate) {
1914
+ await this.#savePersistInner();
1915
+ } else {
1916
+ if (!this.#onPersistSavedPromise) {
1917
+ this.#onPersistSavedPromise = promiseWithResolvers();
1918
+ }
1919
+ this.#savePersistThrottled();
1920
+ await this.#onPersistSavedPromise.promise;
1921
+ }
1922
+ }
1923
+ }
1924
+ // MARK: Sleep
1925
+ /**
1926
+ * Reset timer from the last actor interaction that allows it to be put to sleep.
1927
+ *
1928
+ * This should be called any time a sleep-related event happens:
1929
+ * - Connection opens (will clear timer)
1930
+ * - Connection closes (will schedule timer if there are no open connections)
1931
+ * - Alarm triggers (will reset timer)
1932
+ *
1933
+ * We don't need to call this on events like individual action calls, since there will always be a connection open for these.
1934
+ **/
1935
+ #resetSleepTimer() {
1936
+ if (this.#config.options.noSleep || !this.#sleepingSupported) return;
1937
+ const canSleep = this.#canSleep();
1938
+ this.#rLog.debug({
1939
+ msg: "resetting sleep timer",
1940
+ canSleep,
1941
+ existingTimeout: !!this.#sleepTimeout
1942
+ });
1943
+ if (this.#sleepTimeout) {
1944
+ clearTimeout(this.#sleepTimeout);
1945
+ this.#sleepTimeout = void 0;
1946
+ }
1947
+ if (this.#sleepCalled) return;
1948
+ if (canSleep) {
1949
+ this.#sleepTimeout = setTimeout(() => {
1950
+ this._sleep().catch((error) => {
1951
+ this.#rLog.error({
1952
+ msg: "error during sleep",
1953
+ error: stringifyError(error)
1954
+ });
1955
+ });
1956
+ }, this.#config.options.sleepTimeout);
1957
+ }
1958
+ }
1959
+ /** If this actor can be put in a sleeping state. */
1960
+ #canSleep() {
1961
+ if (!this.#ready) return false;
1962
+ for (const conn of this.#connections.values()) {
1963
+ if (conn.status === "connected") return false;
1964
+ }
1965
+ if (this.#activeRawFetchCount > 0) return false;
1966
+ if (this.#activeRawWebSockets.size > 0) return false;
1967
+ return true;
1968
+ }
1969
+ /** Puts an actor to sleep. This should just start the sleep sequence, most shutdown logic should be in _stop (which is called by the ActorDriver when sleeping). */
1970
+ async _sleep() {
1971
+ var _a;
1972
+ const sleep = (_a = this.#actorDriver.sleep) == null ? void 0 : _a.bind(
1973
+ this.#actorDriver,
1974
+ this.#actorId
1975
+ );
1976
+ invariant(this.#sleepingSupported, "sleeping not supported");
1977
+ invariant(sleep, "no sleep on driver");
1978
+ if (this.#sleepCalled) {
1979
+ this.#rLog.warn({ msg: "already sleeping actor" });
1980
+ return;
1981
+ }
1982
+ this.#sleepCalled = true;
1983
+ this.#rLog.info({ msg: "actor sleeping" });
1984
+ setImmediate(async () => {
1985
+ await sleep();
1986
+ });
1987
+ }
1988
+ // MARK: Stop
1989
+ async _stop() {
1990
+ if (this.#stopCalled) {
1991
+ this.#rLog.warn({ msg: "already stopping actor" });
1992
+ return;
1993
+ }
1994
+ this.#stopCalled = true;
1995
+ this.#rLog.info({ msg: "actor stopping" });
1996
+ try {
1997
+ this.#abortController.abort();
1998
+ } catch {
1999
+ }
2000
+ if (this.#config.onStop) {
2001
+ try {
2002
+ this.#rLog.debug({ msg: "calling onStop" });
2003
+ const result = this.#config.onStop(this.actorContext);
2004
+ if (result instanceof Promise) {
2005
+ await deadline(result, this.#config.options.onStopTimeout);
2006
+ }
2007
+ this.#rLog.debug({ msg: "onStop completed" });
2008
+ } catch (error) {
2009
+ if (error instanceof DeadlineError) {
2010
+ this.#rLog.error({ msg: "onStop timed out" });
2011
+ } else {
2012
+ this.#rLog.error({
2013
+ msg: "error in onStop",
2014
+ error: stringifyError(error)
2015
+ });
2016
+ }
2017
+ }
2018
+ }
2019
+ const promises = [];
2020
+ for (const connection of this.#connections.values()) {
2021
+ promises.push(connection.disconnect());
2022
+ }
2023
+ await this.#waitBackgroundPromises(this.#config.options.waitUntilTimeout);
2024
+ if (this.#pendingSaveTimeout) clearTimeout(this.#pendingSaveTimeout);
2025
+ if (this.#sleepTimeout) clearTimeout(this.#sleepTimeout);
2026
+ if (this.#checkConnLivenessInterval)
2027
+ clearInterval(this.#checkConnLivenessInterval);
2028
+ await this.saveState({ immediate: true, allowStoppingState: true });
2029
+ const res = Promise.race([
2030
+ Promise.all(promises).then(() => false),
2031
+ new Promise(
2032
+ (res2) => globalThis.setTimeout(() => res2(true), 1500)
2033
+ )
2034
+ ]);
2035
+ if (await res) {
2036
+ this.#rLog.warn({
2037
+ msg: "timed out waiting for connections to close, shutting down anyway"
2038
+ });
2039
+ }
2040
+ if (this.#persistWriteQueue.runningDrainLoop)
2041
+ await this.#persistWriteQueue.runningDrainLoop;
2042
+ if (this.#alarmWriteQueue.runningDrainLoop)
2043
+ await this.#alarmWriteQueue.runningDrainLoop;
2044
+ }
2045
+ /** Abort signal that fires when the actor is stopping. */
2046
+ get abortSignal() {
2047
+ return this.#abortController.signal;
2048
+ }
2049
+ /** Wait for background waitUntil promises with a timeout. */
2050
+ async #waitBackgroundPromises(timeoutMs) {
2051
+ const pending = this.#backgroundPromises;
2052
+ if (pending.length === 0) {
2053
+ this.#rLog.debug({ msg: "no background promises" });
2054
+ return;
2055
+ }
2056
+ const timedOut = await Promise.race([
2057
+ Promise.allSettled(pending).then(() => false),
2058
+ new Promise(
2059
+ (resolve) => setTimeout(() => resolve(true), timeoutMs)
2060
+ )
2061
+ ]);
2062
+ if (timedOut) {
2063
+ this.#rLog.error({
2064
+ msg: "timed out waiting for background tasks, background promises may have leaked",
2065
+ count: pending.length,
2066
+ timeoutMs
2067
+ });
2068
+ } else {
2069
+ this.#rLog.debug({ msg: "background promises finished" });
2070
+ }
2071
+ }
2072
+ // MARK: BARE Conversion Helpers
2073
+ #convertToBarePersisted(persist) {
2074
+ return {
2075
+ input: persist.input !== void 0 ? bufferToArrayBuffer(cbor2.encode(persist.input)) : null,
2076
+ hasInitialized: persist.hasInitiated,
2077
+ state: bufferToArrayBuffer(cbor2.encode(persist.state)),
2078
+ connections: persist.connections.map((conn) => ({
2079
+ id: conn.connId,
2080
+ token: conn.token,
2081
+ parameters: bufferToArrayBuffer(cbor2.encode(conn.params || {})),
2082
+ state: bufferToArrayBuffer(cbor2.encode(conn.state || {})),
2083
+ subscriptions: conn.subscriptions.map((sub) => ({
2084
+ eventName: sub.eventName
2085
+ })),
2086
+ lastSeen: BigInt(conn.lastSeen)
2087
+ })),
2088
+ scheduledEvents: persist.scheduledEvents.map((event) => ({
2089
+ eventId: event.eventId,
2090
+ timestamp: BigInt(event.timestamp),
2091
+ kind: {
2092
+ tag: "GenericPersistedScheduleEvent",
2093
+ val: {
2094
+ action: event.kind.generic.actionName,
2095
+ args: event.kind.generic.args ?? null
2096
+ }
2097
+ }
2098
+ }))
2099
+ };
2100
+ }
2101
+ #convertFromBarePersisted(bareData) {
2102
+ return {
2103
+ input: bareData.input ? cbor2.decode(new Uint8Array(bareData.input)) : void 0,
2104
+ hasInitiated: bareData.hasInitialized,
2105
+ state: cbor2.decode(new Uint8Array(bareData.state)),
2106
+ connections: bareData.connections.map((conn) => ({
2107
+ connId: conn.id,
2108
+ token: conn.token,
2109
+ params: cbor2.decode(new Uint8Array(conn.parameters)),
2110
+ state: cbor2.decode(new Uint8Array(conn.state)),
2111
+ subscriptions: conn.subscriptions.map((sub) => ({
2112
+ eventName: sub.eventName
2113
+ })),
2114
+ lastSeen: Number(conn.lastSeen)
2115
+ })),
2116
+ scheduledEvents: bareData.scheduledEvents.map((event) => ({
2117
+ eventId: event.eventId,
2118
+ timestamp: Number(event.timestamp),
2119
+ kind: {
2120
+ generic: {
2121
+ actionName: event.kind.val.action,
2122
+ args: event.kind.val.args
2123
+ }
2124
+ }
2125
+ }))
2126
+ };
2127
+ }
2128
+ };
2129
+
2130
+ // src/actor/definition.ts
2131
+ var ActorDefinition = class {
2132
+ #config;
2133
+ constructor(config) {
2134
+ this.#config = config;
2135
+ }
2136
+ get config() {
2137
+ return this.#config;
2138
+ }
2139
+ instantiate() {
2140
+ return new ActorInstance(this.#config);
2141
+ }
2142
+ };
2143
+ function lookupInRegistry(registryConfig, name) {
2144
+ const definition = registryConfig.use[name];
2145
+ if (!definition) throw new Error(`no actor in registry for name ${name}`);
2146
+ return definition;
2147
+ }
2148
+
2149
+ // src/client/errors.ts
2150
+ var ActorClientError = class extends Error {
2151
+ };
2152
+ var InternalError2 = class extends ActorClientError {
2153
+ };
2154
+ var ManagerError = class extends ActorClientError {
2155
+ constructor(error, opts) {
2156
+ super(`Manager error: ${error}`, opts);
2157
+ }
2158
+ };
2159
+ var MalformedResponseMessage = class extends ActorClientError {
2160
+ constructor(cause) {
2161
+ super(`Malformed response message: ${cause}`, { cause });
2162
+ }
2163
+ };
2164
+ var ActorError = class extends ActorClientError {
2165
+ constructor(group, code, message, metadata) {
2166
+ super(message);
2167
+ this.group = group;
2168
+ this.code = code;
2169
+ this.metadata = metadata;
2170
+ }
2171
+ __type = "ActorError";
2172
+ };
2173
+ var HttpRequestError = class extends ActorClientError {
2174
+ constructor(message, opts) {
2175
+ super(`HTTP request error: ${message}`, { cause: opts == null ? void 0 : opts.cause });
2176
+ }
2177
+ };
2178
+ var ActorConnDisposed = class extends ActorClientError {
2179
+ constructor() {
2180
+ super("Attempting to interact with a disposed actor connection.");
2181
+ }
2182
+ };
2183
+
2184
+ // src/client/actor-conn.ts
2185
+ import * as cbor5 from "cbor-x";
2186
+ import invariant5 from "invariant";
2187
+ import pRetry from "p-retry";
2188
+
2189
+ // src/common/eventsource.ts
2190
+ var eventSourcePromise = null;
2191
+ async function importEventSource() {
2192
+ if (eventSourcePromise !== null) {
2193
+ return eventSourcePromise;
2194
+ }
2195
+ eventSourcePromise = (async () => {
2196
+ let _EventSource;
2197
+ try {
2198
+ const es = await import("eventsource");
2199
+ _EventSource = es.EventSource;
2200
+ logger().debug("using eventsource from npm");
2201
+ } catch (err) {
2202
+ _EventSource = class MockEventSource {
2203
+ constructor() {
2204
+ throw new Error(
2205
+ 'EventSource support requires installing the "eventsource" peer dependency.'
2206
+ );
2207
+ }
2208
+ };
2209
+ logger().debug("using mock eventsource");
2210
+ }
2211
+ return _EventSource;
2212
+ })();
2213
+ return eventSourcePromise;
2214
+ }
2215
+
2216
+ // src/client/actor-query.ts
2217
+ async function queryActor(c, query, managerDriver) {
2218
+ logger().debug({ msg: "querying actor", query: JSON.stringify(query) });
2219
+ let actorOutput;
2220
+ if ("getForId" in query) {
2221
+ const output = await managerDriver.getForId({
2222
+ c,
2223
+ name: query.getForId.name,
2224
+ actorId: query.getForId.actorId
2225
+ });
2226
+ if (!output) throw new ActorNotFound(query.getForId.actorId);
2227
+ actorOutput = output;
2228
+ } else if ("getForKey" in query) {
2229
+ const existingActor = await managerDriver.getWithKey({
2230
+ c,
2231
+ name: query.getForKey.name,
2232
+ key: query.getForKey.key
2233
+ });
2234
+ if (!existingActor) {
2235
+ throw new ActorNotFound(
2236
+ `${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`
2237
+ );
2238
+ }
2239
+ actorOutput = existingActor;
2240
+ } else if ("getOrCreateForKey" in query) {
2241
+ const getOrCreateOutput = await managerDriver.getOrCreateWithKey({
2242
+ c,
2243
+ name: query.getOrCreateForKey.name,
2244
+ key: query.getOrCreateForKey.key,
2245
+ input: query.getOrCreateForKey.input,
2246
+ region: query.getOrCreateForKey.region
2247
+ });
2248
+ actorOutput = {
2249
+ actorId: getOrCreateOutput.actorId
2250
+ };
2251
+ } else if ("create" in query) {
2252
+ const createOutput = await managerDriver.createActor({
2253
+ c,
2254
+ name: query.create.name,
2255
+ key: query.create.key,
2256
+ input: query.create.input,
2257
+ region: query.create.region
2258
+ });
2259
+ actorOutput = {
2260
+ actorId: createOutput.actorId
2261
+ };
2262
+ } else {
2263
+ throw new InvalidRequest("Invalid query format");
2264
+ }
2265
+ logger().debug({ msg: "actor query result", actorId: actorOutput.actorId });
2266
+ return { actorId: actorOutput.actorId };
2267
+ }
2268
+
2269
+ // src/client/actor-handle.ts
2270
+ import * as cbor4 from "cbor-x";
2271
+ import invariant4 from "invariant";
2272
+
2273
+ // src/client/raw-utils.ts
2274
+ import invariant2 from "invariant";
2275
+ async function rawHttpFetch(driver, actorQuery, params, input, init) {
2276
+ let path;
2277
+ let mergedInit = init || {};
2278
+ if (typeof input === "string") {
2279
+ path = input;
2280
+ } else if (input instanceof URL) {
2281
+ path = input.pathname + input.search;
2282
+ } else if (input instanceof Request) {
2283
+ const url = new URL(input.url);
2284
+ path = url.pathname + url.search;
2285
+ const requestHeaders = new Headers(input.headers);
2286
+ const initHeaders = new Headers((init == null ? void 0 : init.headers) || {});
2287
+ const mergedHeaders = new Headers(requestHeaders);
2288
+ for (const [key, value] of initHeaders) {
2289
+ mergedHeaders.set(key, value);
2290
+ }
2291
+ mergedInit = {
2292
+ method: input.method,
2293
+ body: input.body,
2294
+ mode: input.mode,
2295
+ credentials: input.credentials,
2296
+ redirect: input.redirect,
2297
+ referrer: input.referrer,
2298
+ referrerPolicy: input.referrerPolicy,
2299
+ integrity: input.integrity,
2300
+ keepalive: input.keepalive,
2301
+ signal: input.signal,
2302
+ ...mergedInit,
2303
+ // init overrides Request properties
2304
+ headers: mergedHeaders
2305
+ // headers must be set after spread to ensure proper merge
2306
+ };
2307
+ if (mergedInit.body) {
2308
+ mergedInit.duplex = "half";
2309
+ }
2310
+ } else {
2311
+ throw new TypeError("Invalid input type for fetch");
2312
+ }
2313
+ try {
2314
+ const { actorId } = await queryActor(void 0, actorQuery, driver);
2315
+ logger().debug({ msg: "found actor for raw http", actorId });
2316
+ invariant2(actorId, "Missing actor ID");
2317
+ const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
2318
+ const url = new URL(`http://actor/raw/http/${normalizedPath}`);
2319
+ const proxyRequestHeaders = new Headers(mergedInit.headers);
2320
+ if (params) {
2321
+ proxyRequestHeaders.set(HEADER_CONN_PARAMS, JSON.stringify(params));
2322
+ }
2323
+ const proxyRequest = new Request(url, {
2324
+ ...mergedInit,
2325
+ headers: proxyRequestHeaders
2326
+ });
2327
+ return driver.sendRequest(actorId, proxyRequest);
2328
+ } catch (err) {
2329
+ const { group, code, message, metadata } = deconstructError(
2330
+ err,
2331
+ logger(),
2332
+ {},
2333
+ true
2334
+ );
2335
+ throw new ActorError(group, code, message, metadata);
2336
+ }
2337
+ }
2338
+ async function rawWebSocket(driver, actorQuery, params, path, protocols) {
2339
+ const encoding = "bare";
2340
+ const { actorId } = await queryActor(void 0, actorQuery, driver);
2341
+ logger().debug({ msg: "found actor for action", actorId });
2342
+ invariant2(actorId, "Missing actor ID");
2343
+ let pathPortion = "";
2344
+ let queryPortion = "";
2345
+ if (path) {
2346
+ const queryIndex = path.indexOf("?");
2347
+ if (queryIndex !== -1) {
2348
+ pathPortion = path.substring(0, queryIndex);
2349
+ queryPortion = path.substring(queryIndex);
2350
+ } else {
2351
+ pathPortion = path;
2352
+ }
2353
+ if (pathPortion.startsWith("/")) {
2354
+ pathPortion = pathPortion.slice(1);
2355
+ }
2356
+ }
2357
+ const fullPath = `${PATH_RAW_WEBSOCKET_PREFIX}${pathPortion}${queryPortion}`;
2358
+ logger().debug({
2359
+ msg: "opening websocket",
2360
+ actorId,
2361
+ encoding,
2362
+ path: fullPath
2363
+ });
2364
+ const ws = await driver.openWebSocket(fullPath, actorId, encoding, params);
2365
+ return ws;
2366
+ }
2367
+
2368
+ // src/client/utils.ts
2369
+ import * as cbor3 from "cbor-x";
2370
+ import invariant3 from "invariant";
2371
+ function messageLength(message) {
2372
+ if (message instanceof Blob) {
2373
+ return message.size;
2374
+ }
2375
+ if (message instanceof ArrayBuffer) {
2376
+ return message.byteLength;
2377
+ }
2378
+ if (message instanceof Uint8Array) {
2379
+ return message.byteLength;
2380
+ }
2381
+ if (typeof message === "string") {
2382
+ return message.length;
2383
+ }
2384
+ assertUnreachable(message);
2385
+ }
2386
+ async function sendHttpRequest(opts) {
2387
+ logger().debug({
2388
+ msg: "sending http request",
2389
+ url: opts.url,
2390
+ encoding: opts.encoding
2391
+ });
2392
+ let contentType;
2393
+ let bodyData;
2394
+ if (opts.method === "POST" || opts.method === "PUT") {
2395
+ invariant3(opts.body !== void 0, "missing body");
2396
+ contentType = contentTypeForEncoding(opts.encoding);
2397
+ bodyData = serializeWithEncoding(
2398
+ opts.encoding,
2399
+ opts.body,
2400
+ opts.requestVersionedDataHandler
2401
+ );
2402
+ }
2403
+ let response;
2404
+ try {
2405
+ response = await (opts.customFetch ?? fetch)(
2406
+ new Request(opts.url, {
2407
+ method: opts.method,
2408
+ headers: {
2409
+ ...opts.headers,
2410
+ ...contentType ? {
2411
+ "Content-Type": contentType
2412
+ } : {},
2413
+ "User-Agent": httpUserAgent()
2414
+ },
2415
+ body: bodyData,
2416
+ credentials: "include",
2417
+ signal: opts.signal
2418
+ })
2419
+ );
2420
+ } catch (error) {
2421
+ throw new HttpRequestError(`Request failed: ${error}`, {
2422
+ cause: error
2423
+ });
2424
+ }
2425
+ if (!response.ok) {
2426
+ const bufferResponse = await response.arrayBuffer();
2427
+ let responseData;
2428
+ try {
2429
+ responseData = deserializeWithEncoding(
2430
+ opts.encoding,
2431
+ new Uint8Array(bufferResponse),
2432
+ HTTP_RESPONSE_ERROR_VERSIONED
2433
+ );
2434
+ } catch (error) {
2435
+ const textResponse = new TextDecoder("utf-8", { fatal: false }).decode(
2436
+ bufferResponse
2437
+ );
2438
+ throw new HttpRequestError(
2439
+ `${response.statusText} (${response.status}):
2440
+ ${textResponse}`
2441
+ );
2442
+ }
2443
+ throw new ActorError(
2444
+ responseData.group,
2445
+ responseData.code,
2446
+ responseData.message,
2447
+ responseData.metadata ? cbor3.decode(new Uint8Array(responseData.metadata)) : void 0
2448
+ );
2449
+ }
2450
+ if (opts.skipParseResponse) {
2451
+ return void 0;
2452
+ }
2453
+ try {
2454
+ const buffer = new Uint8Array(await response.arrayBuffer());
2455
+ return deserializeWithEncoding(
2456
+ opts.encoding,
2457
+ buffer,
2458
+ opts.responseVersionedDataHandler
2459
+ );
2460
+ } catch (error) {
2461
+ throw new HttpRequestError(`Failed to parse response: ${error}`, {
2462
+ cause: error
2463
+ });
2464
+ }
2465
+ }
2466
+
2467
+ // src/client/actor-handle.ts
2468
+ var ActorHandleRaw = class {
2469
+ #client;
2470
+ #driver;
2471
+ #encoding;
2472
+ #actorQuery;
2473
+ #params;
2474
+ /**
2475
+ * Do not call this directly.
2476
+ *
2477
+ * Creates an instance of ActorHandleRaw.
2478
+ *
2479
+ * @protected
2480
+ */
2481
+ constructor(client, driver, params, encoding, actorQuery) {
2482
+ this.#client = client;
2483
+ this.#driver = driver;
2484
+ this.#encoding = encoding;
2485
+ this.#actorQuery = actorQuery;
2486
+ this.#params = params;
2487
+ }
2488
+ /**
2489
+ * Call a raw action. This method sends an HTTP request to invoke the named action.
2490
+ *
2491
+ * @see {@link ActorHandle}
2492
+ * @template Args - The type of arguments to pass to the action function.
2493
+ * @template Response - The type of the response returned by the action function.
2494
+ */
2495
+ async action(opts) {
2496
+ try {
2497
+ const { actorId } = await queryActor(
2498
+ void 0,
2499
+ this.#actorQuery,
2500
+ this.#driver
2501
+ );
2502
+ logger().debug({ msg: "found actor for action", actorId });
2503
+ invariant4(actorId, "Missing actor ID");
2504
+ logger().debug({
2505
+ msg: "handling action",
2506
+ name: opts.name,
2507
+ encoding: this.#encoding
2508
+ });
2509
+ const responseData = await sendHttpRequest({
2510
+ url: `http://actor/action/${encodeURIComponent(opts.name)}`,
2511
+ method: "POST",
2512
+ headers: {
2513
+ [HEADER_ENCODING]: this.#encoding,
2514
+ ...this.#params !== void 0 ? { [HEADER_CONN_PARAMS]: JSON.stringify(this.#params) } : {}
2515
+ },
2516
+ body: {
2517
+ args: bufferToArrayBuffer(cbor4.encode(opts.args))
2518
+ },
2519
+ encoding: this.#encoding,
2520
+ customFetch: this.#driver.sendRequest.bind(this.#driver, actorId),
2521
+ signal: opts == null ? void 0 : opts.signal,
2522
+ requestVersionedDataHandler: HTTP_ACTION_REQUEST_VERSIONED,
2523
+ responseVersionedDataHandler: HTTP_ACTION_RESPONSE_VERSIONED
2524
+ });
2525
+ return cbor4.decode(new Uint8Array(responseData.output));
2526
+ } catch (err) {
2527
+ const { group, code, message, metadata } = deconstructError(
2528
+ err,
2529
+ logger(),
2530
+ {},
2531
+ true
2532
+ );
2533
+ throw new ActorError(group, code, message, metadata);
2534
+ }
2535
+ }
2536
+ /**
2537
+ * Establishes a persistent connection to the actor.
2538
+ *
2539
+ * @template AD The actor class that this connection is for.
2540
+ * @returns {ActorConn<AD>} A connection to the actor.
2541
+ */
2542
+ connect() {
2543
+ logger().debug({
2544
+ msg: "establishing connection from handle",
2545
+ query: this.#actorQuery
2546
+ });
2547
+ const conn = new ActorConnRaw(
2548
+ this.#client,
2549
+ this.#driver,
2550
+ this.#params,
2551
+ this.#encoding,
2552
+ this.#actorQuery
2553
+ );
2554
+ return this.#client[CREATE_ACTOR_CONN_PROXY](
2555
+ conn
2556
+ );
2557
+ }
2558
+ /**
2559
+ * Makes a raw HTTP request to the actor.
2560
+ *
2561
+ * @param input - The URL, path, or Request object
2562
+ * @param init - Standard fetch RequestInit options
2563
+ * @returns Promise<Response> - The raw HTTP response
2564
+ */
2565
+ async fetch(input, init) {
2566
+ return rawHttpFetch(
2567
+ this.#driver,
2568
+ this.#actorQuery,
2569
+ this.#params,
2570
+ input,
2571
+ init
2572
+ );
2573
+ }
2574
+ /**
2575
+ * Creates a raw WebSocket connection to the actor.
2576
+ *
2577
+ * @param path - The path for the WebSocket connection (e.g., "stream")
2578
+ * @param protocols - Optional WebSocket subprotocols
2579
+ * @returns WebSocket - A raw WebSocket connection
2580
+ */
2581
+ async websocket(path, protocols) {
2582
+ return rawWebSocket(
2583
+ this.#driver,
2584
+ this.#actorQuery,
2585
+ this.#params,
2586
+ path,
2587
+ protocols
2588
+ );
2589
+ }
2590
+ /**
2591
+ * Resolves the actor to get its unique actor ID
2592
+ *
2593
+ * @returns {Promise<string>} - A promise that resolves to the actor's ID
2594
+ */
2595
+ async resolve({ signal } = {}) {
2596
+ if ("getForKey" in this.#actorQuery || "getOrCreateForKey" in this.#actorQuery) {
2597
+ let name;
2598
+ if ("getForKey" in this.#actorQuery) {
2599
+ name = this.#actorQuery.getForKey.name;
2600
+ } else if ("getOrCreateForKey" in this.#actorQuery) {
2601
+ name = this.#actorQuery.getOrCreateForKey.name;
2602
+ } else {
2603
+ assertUnreachable2(this.#actorQuery);
2604
+ }
2605
+ const { actorId } = await queryActor(
2606
+ void 0,
2607
+ this.#actorQuery,
2608
+ this.#driver
2609
+ );
2610
+ this.#actorQuery = { getForId: { actorId, name } };
2611
+ return actorId;
2612
+ } else if ("getForId" in this.#actorQuery) {
2613
+ return this.#actorQuery.getForId.actorId;
2614
+ } else if ("create" in this.#actorQuery) {
2615
+ invariant4(false, "actorQuery cannot be create");
2616
+ } else {
2617
+ assertUnreachable2(this.#actorQuery);
2618
+ }
2619
+ }
2620
+ };
2621
+
2622
+ // src/client/client.ts
2623
+ var ACTOR_CONNS_SYMBOL = Symbol("actorConns");
2624
+ var CREATE_ACTOR_CONN_PROXY = Symbol("createActorConnProxy");
2625
+ var TRANSPORT_SYMBOL = Symbol("transport");
2626
+ var ClientRaw = class {
2627
+ #disposed = false;
2628
+ [ACTOR_CONNS_SYMBOL] = /* @__PURE__ */ new Set();
2629
+ #driver;
2630
+ #encodingKind;
2631
+ [TRANSPORT_SYMBOL];
2632
+ /**
2633
+ * Creates an instance of Client.
2634
+ *
2635
+ * @param {string} managerEndpoint - The manager endpoint. See {@link https://rivet.dev/docs/setup|Initial Setup} for instructions on getting the manager endpoint.
2636
+ * @param {ClientConfig} [opts] - Options for configuring the client.
2637
+ * @see {@link https://rivet.dev/docs/setup|Initial Setup}
2638
+ */
2639
+ constructor(driver, opts) {
2640
+ this.#driver = driver;
2641
+ this.#encodingKind = (opts == null ? void 0 : opts.encoding) ?? "bare";
2642
+ this[TRANSPORT_SYMBOL] = (opts == null ? void 0 : opts.transport) ?? "websocket";
2643
+ }
2644
+ /**
2645
+ * Gets a stateless handle to a actor by its ID.
2646
+ *
2647
+ * @template AD The actor class that this handle is for.
2648
+ * @param {string} name - The name of the actor.
2649
+ * @param {string} actorId - The ID of the actor.
2650
+ * @param {GetWithIdOptions} [opts] - Options for getting the actor.
2651
+ * @returns {ActorHandle<AD>} - A handle to the actor.
2652
+ */
2653
+ getForId(name, actorId, opts) {
2654
+ logger().debug({
2655
+ msg: "get handle to actor with id",
2656
+ name,
2657
+ actorId,
2658
+ params: opts == null ? void 0 : opts.params
2659
+ });
2660
+ const actorQuery = {
2661
+ getForId: {
2662
+ name,
2663
+ actorId
2664
+ }
2665
+ };
2666
+ const handle = this.#createHandle(opts == null ? void 0 : opts.params, actorQuery);
2667
+ return createActorProxy(handle);
2668
+ }
2669
+ /**
2670
+ * Gets a stateless handle to a actor by its key, but does not create the actor if it doesn't exist.
2671
+ *
2672
+ * @template AD The actor class that this handle is for.
2673
+ * @param {string} name - The name of the actor.
2674
+ * @param {string | string[]} [key=[]] - The key to identify the actor. Can be a single string or an array of strings.
2675
+ * @param {GetWithIdOptions} [opts] - Options for getting the actor.
2676
+ * @returns {ActorHandle<AD>} - A handle to the actor.
2677
+ */
2678
+ get(name, key, opts) {
2679
+ const keyArray = typeof key === "string" ? [key] : key || [];
2680
+ logger().debug({
2681
+ msg: "get handle to actor",
2682
+ name,
2683
+ key: keyArray,
2684
+ parameters: opts == null ? void 0 : opts.params
2685
+ });
2686
+ const actorQuery = {
2687
+ getForKey: {
2688
+ name,
2689
+ key: keyArray
2690
+ }
2691
+ };
2692
+ const handle = this.#createHandle(opts == null ? void 0 : opts.params, actorQuery);
2693
+ return createActorProxy(handle);
2694
+ }
2695
+ /**
2696
+ * Gets a stateless handle to a actor by its key, creating it if necessary.
2697
+ *
2698
+ * @template AD The actor class that this handle is for.
2699
+ * @param {string} name - The name of the actor.
2700
+ * @param {string | string[]} [key=[]] - The key to identify the actor. Can be a single string or an array of strings.
2701
+ * @param {GetOptions} [opts] - Options for getting the actor.
2702
+ * @returns {ActorHandle<AD>} - A handle to the actor.
2703
+ */
2704
+ getOrCreate(name, key, opts) {
2705
+ const keyArray = typeof key === "string" ? [key] : key || [];
2706
+ logger().debug({
2707
+ msg: "get or create handle to actor",
2708
+ name,
2709
+ key: keyArray,
2710
+ parameters: opts == null ? void 0 : opts.params,
2711
+ createInRegion: opts == null ? void 0 : opts.createInRegion
2712
+ });
2713
+ const actorQuery = {
2714
+ getOrCreateForKey: {
2715
+ name,
2716
+ key: keyArray,
2717
+ input: opts == null ? void 0 : opts.createWithInput,
2718
+ region: opts == null ? void 0 : opts.createInRegion
2719
+ }
2720
+ };
2721
+ const handle = this.#createHandle(opts == null ? void 0 : opts.params, actorQuery);
2722
+ return createActorProxy(handle);
2723
+ }
2724
+ /**
2725
+ * Creates a new actor with the provided key and returns a stateless handle to it.
2726
+ * Resolves the actor ID and returns a handle with getForId query.
2727
+ *
2728
+ * @template AD The actor class that this handle is for.
2729
+ * @param {string} name - The name of the actor.
2730
+ * @param {string | string[]} key - The key to identify the actor. Can be a single string or an array of strings.
2731
+ * @param {CreateOptions} [opts] - Options for creating the actor (excluding name and key).
2732
+ * @returns {Promise<ActorHandle<AD>>} - A promise that resolves to a handle to the actor.
2733
+ */
2734
+ async create(name, key, opts) {
2735
+ const keyArray = typeof key === "string" ? [key] : key || [];
2736
+ const createQuery = {
2737
+ create: {
2738
+ ...opts,
2739
+ // Do these last to override `opts`
2740
+ name,
2741
+ key: keyArray
2742
+ }
2743
+ };
2744
+ logger().debug({
2745
+ msg: "create actor handle",
2746
+ name,
2747
+ key: keyArray,
2748
+ parameters: opts == null ? void 0 : opts.params,
2749
+ create: createQuery.create
2750
+ });
2751
+ const { actorId } = await queryActor(void 0, createQuery, this.#driver);
2752
+ logger().debug({
2753
+ msg: "created actor with ID",
2754
+ name,
2755
+ key: keyArray,
2756
+ actorId
2757
+ });
2758
+ const getForIdQuery = {
2759
+ getForId: {
2760
+ name,
2761
+ actorId
2762
+ }
2763
+ };
2764
+ const handle = this.#createHandle(opts == null ? void 0 : opts.params, getForIdQuery);
2765
+ const proxy = createActorProxy(handle);
2766
+ return proxy;
2767
+ }
2768
+ #createHandle(params, actorQuery) {
2769
+ return new ActorHandleRaw(
2770
+ this,
2771
+ this.#driver,
2772
+ params,
2773
+ this.#encodingKind,
2774
+ actorQuery
2775
+ );
2776
+ }
2777
+ [CREATE_ACTOR_CONN_PROXY](conn) {
2778
+ this[ACTOR_CONNS_SYMBOL].add(conn);
2779
+ conn[CONNECT_SYMBOL]();
2780
+ return createActorProxy(conn);
2781
+ }
2782
+ /**
2783
+ * Disconnects from all actors.
2784
+ *
2785
+ * @returns {Promise<void>} A promise that resolves when all connections are closed.
2786
+ */
2787
+ async dispose() {
2788
+ if (this.#disposed) {
2789
+ logger().warn({ msg: "client already disconnected" });
2790
+ return;
2791
+ }
2792
+ this.#disposed = true;
2793
+ logger().debug({ msg: "disposing client" });
2794
+ const disposePromises = [];
2795
+ for (const conn of this[ACTOR_CONNS_SYMBOL].values()) {
2796
+ disposePromises.push(conn.dispose());
2797
+ }
2798
+ await Promise.all(disposePromises);
2799
+ }
2800
+ };
2801
+ function createClientWithDriver(driver, config) {
2802
+ const client = new ClientRaw(driver, config);
2803
+ return new Proxy(client, {
2804
+ get: (target, prop, receiver) => {
2805
+ if (typeof prop === "symbol" || prop in target) {
2806
+ const value = Reflect.get(target, prop, receiver);
2807
+ if (typeof value === "function") {
2808
+ return value.bind(target);
2809
+ }
2810
+ return value;
2811
+ }
2812
+ if (typeof prop === "string") {
2813
+ return {
2814
+ // Handle methods (stateless action)
2815
+ get: (key, opts) => {
2816
+ return target.get(
2817
+ prop,
2818
+ key,
2819
+ opts
2820
+ );
2821
+ },
2822
+ getOrCreate: (key, opts) => {
2823
+ return target.getOrCreate(prop, key, opts);
2824
+ },
2825
+ getForId: (actorId, opts) => {
2826
+ return target.getForId(
2827
+ prop,
2828
+ actorId,
2829
+ opts
2830
+ );
2831
+ },
2832
+ create: async (key, opts = {}) => {
2833
+ return await target.create(prop, key, opts);
2834
+ }
2835
+ };
2836
+ }
2837
+ return void 0;
2838
+ }
2839
+ });
2840
+ }
2841
+ function createActorProxy(handle) {
2842
+ const methodCache = /* @__PURE__ */ new Map();
2843
+ return new Proxy(handle, {
2844
+ get(target, prop, receiver) {
2845
+ if (typeof prop === "symbol") {
2846
+ return Reflect.get(target, prop, receiver);
2847
+ }
2848
+ if (prop === "constructor" || prop in target) {
2849
+ const value = Reflect.get(target, prop, target);
2850
+ if (typeof value === "function") {
2851
+ return value.bind(target);
2852
+ }
2853
+ return value;
2854
+ }
2855
+ if (typeof prop === "string") {
2856
+ if (prop === "then") return void 0;
2857
+ let method = methodCache.get(prop);
2858
+ if (!method) {
2859
+ method = (...args) => target.action({ name: prop, args });
2860
+ methodCache.set(prop, method);
2861
+ }
2862
+ return method;
2863
+ }
2864
+ },
2865
+ // Support for 'in' operator
2866
+ has(target, prop) {
2867
+ if (typeof prop === "string") {
2868
+ return true;
2869
+ }
2870
+ return Reflect.has(target, prop);
2871
+ },
2872
+ // Support instanceof checks
2873
+ getPrototypeOf(target) {
2874
+ return Reflect.getPrototypeOf(target);
2875
+ },
2876
+ // Prevent property enumeration of non-existent action methods
2877
+ ownKeys(target) {
2878
+ return Reflect.ownKeys(target);
2879
+ },
2880
+ // Support proper property descriptors
2881
+ getOwnPropertyDescriptor(target, prop) {
2882
+ const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
2883
+ if (targetDescriptor) {
2884
+ return targetDescriptor;
2885
+ }
2886
+ if (typeof prop === "string") {
2887
+ return {
2888
+ configurable: true,
2889
+ enumerable: false,
2890
+ writable: false,
2891
+ value: (...args) => target.action({ name: prop, args })
2892
+ };
2893
+ }
2894
+ return void 0;
2895
+ }
2896
+ });
2897
+ }
2898
+
2899
+ // src/client/actor-conn.ts
2900
+ var CONNECT_SYMBOL = Symbol("connect");
2901
+ var ActorConnRaw = class {
2902
+ #disposed = false;
2903
+ /* Will be aborted on dispose. */
2904
+ #abortController = new AbortController();
2905
+ /** If attempting to connect. Helpful for knowing if in a retry loop when reconnecting. */
2906
+ #connecting = false;
2907
+ // Connection info, used for reconnection and HTTP requests
2908
+ #actorId;
2909
+ #connectionId;
2910
+ #connectionToken;
2911
+ #transport;
2912
+ #messageQueue = [];
2913
+ #actionsInFlight = /* @__PURE__ */ new Map();
2914
+ // biome-ignore lint/suspicious/noExplicitAny: Unknown subscription type
2915
+ #eventSubscriptions = /* @__PURE__ */ new Map();
2916
+ #errorHandlers = /* @__PURE__ */ new Set();
2917
+ #actionIdCounter = 0;
2918
+ /**
2919
+ * Interval that keeps the NodeJS process alive if this is the only thing running.
2920
+ *
2921
+ * See ttps://github.com/nodejs/node/issues/22088
2922
+ */
2923
+ #keepNodeAliveInterval;
2924
+ /** Promise used to indicate the socket has connected successfully. This will be rejected if the connection fails. */
2925
+ #onOpenPromise;
2926
+ #client;
2927
+ #driver;
2928
+ #params;
2929
+ #encoding;
2930
+ #actorQuery;
2931
+ // TODO: ws message queue
2932
+ /**
2933
+ * Do not call this directly.
2934
+ *
2935
+ * Creates an instance of ActorConnRaw.
2936
+ *
2937
+ * @protected
2938
+ */
2939
+ constructor(client, driver, params, encoding, actorQuery) {
2940
+ this.#client = client;
2941
+ this.#driver = driver;
2942
+ this.#params = params;
2943
+ this.#encoding = encoding;
2944
+ this.#actorQuery = actorQuery;
2945
+ this.#keepNodeAliveInterval = setInterval(() => 6e4);
2946
+ }
2947
+ /**
2948
+ * Call a raw action connection. See {@link ActorConn} for type-safe action calls.
2949
+ *
2950
+ * @see {@link ActorConn}
2951
+ * @template Args - The type of arguments to pass to the action function.
2952
+ * @template Response - The type of the response returned by the action function.
2953
+ * @param {string} name - The name of the action function to call.
2954
+ * @param {...Args} args - The arguments to pass to the action function.
2955
+ * @returns {Promise<Response>} - A promise that resolves to the response of the action function.
2956
+ */
2957
+ async action(opts) {
2958
+ logger().debug({ msg: "action", name: opts.name, args: opts.args });
2959
+ const actionId = this.#actionIdCounter;
2960
+ this.#actionIdCounter += 1;
2961
+ const { promise, resolve, reject } = promiseWithResolvers();
2962
+ this.#actionsInFlight.set(actionId, { name: opts.name, resolve, reject });
2963
+ this.#sendMessage({
2964
+ body: {
2965
+ tag: "ActionRequest",
2966
+ val: {
2967
+ id: BigInt(actionId),
2968
+ name: opts.name,
2969
+ args: bufferToArrayBuffer(cbor5.encode(opts.args))
2970
+ }
2971
+ }
2972
+ });
2973
+ const { id: responseId, output } = await promise;
2974
+ if (responseId !== BigInt(actionId))
2975
+ throw new Error(
2976
+ `Request ID ${actionId} does not match response ID ${responseId}`
2977
+ );
2978
+ return cbor5.decode(new Uint8Array(output));
2979
+ }
2980
+ /**
2981
+ * Do not call this directly.
2982
+ enc
2983
+ * Establishes a connection to the server using the specified endpoint & encoding & driver.
2984
+ *
2985
+ * @protected
2986
+ */
2987
+ [CONNECT_SYMBOL]() {
2988
+ this.#connectWithRetry();
2989
+ }
2990
+ async #connectWithRetry() {
2991
+ this.#connecting = true;
2992
+ try {
2993
+ await pRetry(this.#connectAndWait.bind(this), {
2994
+ forever: true,
2995
+ minTimeout: 250,
2996
+ maxTimeout: 3e4,
2997
+ onFailedAttempt: (error) => {
2998
+ logger().warn({
2999
+ msg: "failed to reconnect",
3000
+ attempt: error.attemptNumber,
3001
+ error: stringifyError(error)
3002
+ });
3003
+ },
3004
+ // Cancel retry if aborted
3005
+ signal: this.#abortController.signal
3006
+ });
3007
+ } catch (err) {
3008
+ if (err.name === "AbortError") {
3009
+ logger().info({ msg: "connection retry aborted" });
3010
+ return;
3011
+ } else {
3012
+ throw err;
3013
+ }
3014
+ }
3015
+ this.#connecting = false;
3016
+ }
3017
+ async #connectAndWait() {
3018
+ try {
3019
+ if (this.#onOpenPromise)
3020
+ throw new Error("#onOpenPromise already defined");
3021
+ this.#onOpenPromise = promiseWithResolvers();
3022
+ if (this.#client[TRANSPORT_SYMBOL] === "websocket") {
3023
+ await this.#connectWebSocket();
3024
+ } else if (this.#client[TRANSPORT_SYMBOL] === "sse") {
3025
+ await this.#connectSse();
3026
+ } else {
3027
+ assertUnreachable(this.#client[TRANSPORT_SYMBOL]);
3028
+ }
3029
+ await this.#onOpenPromise.promise;
3030
+ } finally {
3031
+ this.#onOpenPromise = void 0;
3032
+ }
3033
+ }
3034
+ async #connectWebSocket() {
3035
+ const { actorId } = await queryActor(
3036
+ void 0,
3037
+ this.#actorQuery,
3038
+ this.#driver
3039
+ );
3040
+ const isReconnection = this.#connectionId && this.#connectionToken;
3041
+ if (isReconnection) {
3042
+ logger().debug({
3043
+ msg: "attempting websocket reconnection",
3044
+ connectionId: this.#connectionId
3045
+ });
3046
+ }
3047
+ const ws = await this.#driver.openWebSocket(
3048
+ PATH_CONNECT_WEBSOCKET,
3049
+ actorId,
3050
+ this.#encoding,
3051
+ this.#params,
3052
+ // Pass connection ID and token for reconnection if available
3053
+ isReconnection ? this.#connectionId : void 0,
3054
+ isReconnection ? this.#connectionToken : void 0
3055
+ );
3056
+ logger().debug({
3057
+ msg: "transport set to new websocket",
3058
+ connectionId: this.#connectionId,
3059
+ readyState: ws.readyState,
3060
+ messageQueueLength: this.#messageQueue.length
3061
+ });
3062
+ this.#transport = { websocket: ws };
3063
+ ws.addEventListener("open", () => {
3064
+ logger().debug({
3065
+ msg: "client websocket open",
3066
+ connectionId: this.#connectionId
3067
+ });
3068
+ });
3069
+ ws.addEventListener("message", async (ev) => {
3070
+ this.#handleOnMessage(ev.data);
3071
+ });
3072
+ ws.addEventListener("close", (ev) => {
3073
+ this.#handleOnClose(ev);
3074
+ });
3075
+ ws.addEventListener("error", (_ev) => {
3076
+ this.#handleOnError();
3077
+ });
3078
+ }
3079
+ async #connectSse() {
3080
+ const EventSource = await importEventSource();
3081
+ const { actorId } = await queryActor(
3082
+ void 0,
3083
+ this.#actorQuery,
3084
+ this.#driver
3085
+ );
3086
+ logger().debug({ msg: "found actor for sse connection", actorId });
3087
+ invariant5(actorId, "Missing actor ID");
3088
+ logger().debug({
3089
+ msg: "opening sse connection",
3090
+ actorId,
3091
+ encoding: this.#encoding
3092
+ });
3093
+ const isReconnection = this.#connectionId && this.#connectionToken;
3094
+ const eventSource = new EventSource("http://actor/connect/sse", {
3095
+ fetch: (input, init) => {
3096
+ return this.#driver.sendRequest(
3097
+ actorId,
3098
+ new Request(input, {
3099
+ ...init,
3100
+ headers: {
3101
+ ...init == null ? void 0 : init.headers,
3102
+ "User-Agent": httpUserAgent(),
3103
+ [HEADER_ENCODING]: this.#encoding,
3104
+ ...this.#params !== void 0 ? { [HEADER_CONN_PARAMS]: JSON.stringify(this.#params) } : {},
3105
+ ...isReconnection ? {
3106
+ [HEADER_CONN_ID]: this.#connectionId,
3107
+ [HEADER_CONN_TOKEN]: this.#connectionToken
3108
+ } : {}
3109
+ }
3110
+ })
3111
+ );
3112
+ }
3113
+ });
3114
+ this.#transport = { sse: eventSource };
3115
+ eventSource.addEventListener("message", (ev) => {
3116
+ if (ev.type === "ping") return;
3117
+ this.#handleOnMessage(ev.data);
3118
+ });
3119
+ eventSource.addEventListener("error", (ev) => {
3120
+ this.#handleOnError();
3121
+ });
3122
+ }
3123
+ /** Called by the onopen event from drivers. */
3124
+ #handleOnOpen() {
3125
+ logger().debug({
3126
+ msg: "socket open",
3127
+ messageQueueLength: this.#messageQueue.length,
3128
+ connectionId: this.#connectionId
3129
+ });
3130
+ if (this.#onOpenPromise) {
3131
+ this.#onOpenPromise.resolve(void 0);
3132
+ } else {
3133
+ logger().warn({ msg: "#onOpenPromise is undefined" });
3134
+ }
3135
+ for (const eventName of this.#eventSubscriptions.keys()) {
3136
+ this.#sendSubscription(eventName, true);
3137
+ }
3138
+ const queue = this.#messageQueue;
3139
+ this.#messageQueue = [];
3140
+ logger().debug({
3141
+ msg: "flushing message queue",
3142
+ queueLength: queue.length
3143
+ });
3144
+ for (const msg of queue) {
3145
+ this.#sendMessage(msg);
3146
+ }
3147
+ }
3148
+ /** Called by the onmessage event from drivers. */
3149
+ async #handleOnMessage(data) {
3150
+ logger().trace({
3151
+ msg: "received message",
3152
+ dataType: typeof data,
3153
+ isBlob: data instanceof Blob,
3154
+ isArrayBuffer: data instanceof ArrayBuffer
3155
+ });
3156
+ const response = await this.#parseMessage(data);
3157
+ logger().trace(
3158
+ getEnvUniversal("_RIVETKIT_LOG_MESSAGE") ? {
3159
+ msg: "parsed message",
3160
+ message: jsonStringifyCompat(response).substring(0, 100) + "..."
3161
+ } : { msg: "parsed message" }
3162
+ );
3163
+ if (response.body.tag === "Init") {
3164
+ this.#actorId = response.body.val.actorId;
3165
+ this.#connectionId = response.body.val.connectionId;
3166
+ this.#connectionToken = response.body.val.connectionToken;
3167
+ logger().trace({
3168
+ msg: "received init message",
3169
+ actorId: this.#actorId,
3170
+ connectionId: this.#connectionId
3171
+ });
3172
+ this.#handleOnOpen();
3173
+ } else if (response.body.tag === "Error") {
3174
+ const { group, code, message, metadata, actionId } = response.body.val;
3175
+ if (actionId) {
3176
+ const inFlight = this.#takeActionInFlight(Number(actionId));
3177
+ logger().warn({
3178
+ msg: "action error",
3179
+ actionId,
3180
+ actionName: inFlight == null ? void 0 : inFlight.name,
3181
+ group,
3182
+ code,
3183
+ message,
3184
+ metadata
3185
+ });
3186
+ inFlight.reject(new ActorError(group, code, message, metadata));
3187
+ } else {
3188
+ logger().warn({
3189
+ msg: "connection error",
3190
+ group,
3191
+ code,
3192
+ message,
3193
+ metadata
3194
+ });
3195
+ const actorError = new ActorError(
3196
+ group,
3197
+ code,
3198
+ message,
3199
+ metadata
3200
+ );
3201
+ if (this.#onOpenPromise) {
3202
+ this.#onOpenPromise.reject(actorError);
3203
+ }
3204
+ for (const [id, inFlight] of this.#actionsInFlight.entries()) {
3205
+ inFlight.reject(actorError);
3206
+ this.#actionsInFlight.delete(id);
3207
+ }
3208
+ this.#dispatchActorError(actorError);
3209
+ }
3210
+ } else if (response.body.tag === "ActionResponse") {
3211
+ const { id: actionId } = response.body.val;
3212
+ logger().trace({
3213
+ msg: "received action response",
3214
+ actionId
3215
+ });
3216
+ const inFlight = this.#takeActionInFlight(Number(actionId));
3217
+ logger().trace({
3218
+ msg: "resolving action promise",
3219
+ actionId,
3220
+ actionName: inFlight == null ? void 0 : inFlight.name
3221
+ });
3222
+ inFlight.resolve(response.body.val);
3223
+ } else if (response.body.tag === "Event") {
3224
+ logger().trace({ msg: "received event", name: response.body.val.name });
3225
+ this.#dispatchEvent(response.body.val);
3226
+ } else {
3227
+ assertUnreachable(response.body);
3228
+ }
3229
+ }
3230
+ /** Called by the onclose event from drivers. */
3231
+ #handleOnClose(event) {
3232
+ if (this.#onOpenPromise) {
3233
+ this.#onOpenPromise.reject(new Error("Closed"));
3234
+ }
3235
+ const closeEvent = event;
3236
+ const wasClean = closeEvent.wasClean;
3237
+ logger().info({
3238
+ msg: "socket closed",
3239
+ code: closeEvent.code,
3240
+ reason: closeEvent.reason,
3241
+ wasClean,
3242
+ connectionId: this.#connectionId,
3243
+ messageQueueLength: this.#messageQueue.length,
3244
+ actionsInFlight: this.#actionsInFlight.size
3245
+ });
3246
+ if (this.#actionsInFlight.size > 0) {
3247
+ logger().debug({
3248
+ msg: "rejecting in-flight actions after disconnect",
3249
+ count: this.#actionsInFlight.size,
3250
+ connectionId: this.#connectionId,
3251
+ wasClean
3252
+ });
3253
+ const disconnectError = new Error(
3254
+ wasClean ? "Connection closed" : "Connection lost"
3255
+ );
3256
+ for (const actionInfo of this.#actionsInFlight.values()) {
3257
+ actionInfo.reject(disconnectError);
3258
+ }
3259
+ this.#actionsInFlight.clear();
3260
+ }
3261
+ this.#transport = void 0;
3262
+ if (!this.#disposed && !this.#connecting) {
3263
+ logger().debug({
3264
+ msg: "triggering reconnect",
3265
+ connectionId: this.#connectionId,
3266
+ messageQueueLength: this.#messageQueue.length
3267
+ });
3268
+ this.#connectWithRetry();
3269
+ }
3270
+ }
3271
+ /** Called by the onerror event from drivers. */
3272
+ #handleOnError() {
3273
+ if (this.#disposed) return;
3274
+ logger().warn("socket error");
3275
+ }
3276
+ #takeActionInFlight(id) {
3277
+ const inFlight = this.#actionsInFlight.get(id);
3278
+ if (!inFlight) {
3279
+ throw new InternalError2(`No in flight response for ${id}`);
3280
+ }
3281
+ this.#actionsInFlight.delete(id);
3282
+ return inFlight;
3283
+ }
3284
+ #dispatchEvent(event) {
3285
+ const { name, args: argsRaw } = event;
3286
+ const args = cbor5.decode(new Uint8Array(argsRaw));
3287
+ const listeners = this.#eventSubscriptions.get(name);
3288
+ if (!listeners) return;
3289
+ for (const listener of [...listeners]) {
3290
+ listener.callback(...args);
3291
+ if (listener.once) {
3292
+ listeners.delete(listener);
3293
+ }
3294
+ }
3295
+ if (listeners.size === 0) {
3296
+ this.#eventSubscriptions.delete(name);
3297
+ }
3298
+ }
3299
+ #dispatchActorError(error) {
3300
+ for (const handler of [...this.#errorHandlers]) {
3301
+ try {
3302
+ handler(error);
3303
+ } catch (err) {
3304
+ logger().error({
3305
+ msg: "error in connection error handler",
3306
+ error: stringifyError(err)
3307
+ });
3308
+ }
3309
+ }
3310
+ }
3311
+ #addEventSubscription(eventName, callback, once) {
3312
+ const listener = {
3313
+ callback,
3314
+ once
3315
+ };
3316
+ let subscriptionSet = this.#eventSubscriptions.get(eventName);
3317
+ if (subscriptionSet === void 0) {
3318
+ subscriptionSet = /* @__PURE__ */ new Set();
3319
+ this.#eventSubscriptions.set(eventName, subscriptionSet);
3320
+ this.#sendSubscription(eventName, true);
3321
+ }
3322
+ subscriptionSet.add(listener);
3323
+ return () => {
3324
+ const listeners = this.#eventSubscriptions.get(eventName);
3325
+ if (listeners) {
3326
+ listeners.delete(listener);
3327
+ if (listeners.size === 0) {
3328
+ this.#eventSubscriptions.delete(eventName);
3329
+ this.#sendSubscription(eventName, false);
3330
+ }
3331
+ }
3332
+ };
3333
+ }
3334
+ /**
3335
+ * Subscribes to an event that will happen repeatedly.
3336
+ *
3337
+ * @template Args - The type of arguments the event callback will receive.
3338
+ * @param {string} eventName - The name of the event to subscribe to.
3339
+ * @param {(...args: Args) => void} callback - The callback function to execute when the event is triggered.
3340
+ * @returns {EventUnsubscribe} - A function to unsubscribe from the event.
3341
+ * @see {@link https://rivet.dev/docs/events|Events Documentation}
3342
+ */
3343
+ on(eventName, callback) {
3344
+ return this.#addEventSubscription(eventName, callback, false);
3345
+ }
3346
+ /**
3347
+ * Subscribes to an event that will be triggered only once.
3348
+ *
3349
+ * @template Args - The type of arguments the event callback will receive.
3350
+ * @param {string} eventName - The name of the event to subscribe to.
3351
+ * @param {(...args: Args) => void} callback - The callback function to execute when the event is triggered.
3352
+ * @returns {EventUnsubscribe} - A function to unsubscribe from the event.
3353
+ * @see {@link https://rivet.dev/docs/events|Events Documentation}
3354
+ */
3355
+ once(eventName, callback) {
3356
+ return this.#addEventSubscription(eventName, callback, true);
3357
+ }
3358
+ /**
3359
+ * Subscribes to connection errors.
3360
+ *
3361
+ * @param {ActorErrorCallback} callback - The callback function to execute when a connection error occurs.
3362
+ * @returns {() => void} - A function to unsubscribe from the error handler.
3363
+ */
3364
+ onError(callback) {
3365
+ this.#errorHandlers.add(callback);
3366
+ return () => {
3367
+ this.#errorHandlers.delete(callback);
3368
+ };
3369
+ }
3370
+ #sendMessage(message, opts) {
3371
+ var _a, _b;
3372
+ if (this.#disposed) {
3373
+ throw new ActorConnDisposed();
3374
+ }
3375
+ let queueMessage = false;
3376
+ if (!this.#transport) {
3377
+ logger().debug({ msg: "no transport, queueing message" });
3378
+ queueMessage = true;
3379
+ } else if ("websocket" in this.#transport) {
3380
+ const readyState = this.#transport.websocket.readyState;
3381
+ logger().debug({
3382
+ msg: "websocket send attempt",
3383
+ readyState,
3384
+ readyStateString: readyState === 0 ? "CONNECTING" : readyState === 1 ? "OPEN" : readyState === 2 ? "CLOSING" : "CLOSED",
3385
+ connectionId: this.#connectionId,
3386
+ messageType: message.body.tag,
3387
+ actionName: (_a = message.body.val) == null ? void 0 : _a.name
3388
+ });
3389
+ if (readyState === 1) {
3390
+ try {
3391
+ const messageSerialized = serializeWithEncoding(
3392
+ this.#encoding,
3393
+ message,
3394
+ TO_SERVER_VERSIONED
3395
+ );
3396
+ this.#transport.websocket.send(messageSerialized);
3397
+ logger().trace({
3398
+ msg: "sent websocket message",
3399
+ len: messageLength(messageSerialized)
3400
+ });
3401
+ } catch (error) {
3402
+ logger().warn({
3403
+ msg: "failed to send message, added to queue",
3404
+ error,
3405
+ connectionId: this.#connectionId
3406
+ });
3407
+ queueMessage = true;
3408
+ }
3409
+ } else {
3410
+ logger().debug({
3411
+ msg: "websocket not open, queueing message",
3412
+ readyState
3413
+ });
3414
+ queueMessage = true;
3415
+ }
3416
+ } else if ("sse" in this.#transport) {
3417
+ if (this.#transport.sse.readyState === 1) {
3418
+ this.#sendHttpMessage(message, opts);
3419
+ } else {
3420
+ queueMessage = true;
3421
+ }
3422
+ } else {
3423
+ assertUnreachable(this.#transport);
3424
+ }
3425
+ if (!(opts == null ? void 0 : opts.ephemeral) && queueMessage) {
3426
+ this.#messageQueue.push(message);
3427
+ logger().debug({
3428
+ msg: "queued connection message",
3429
+ queueLength: this.#messageQueue.length,
3430
+ connectionId: this.#connectionId,
3431
+ messageType: message.body.tag,
3432
+ actionName: (_b = message.body.val) == null ? void 0 : _b.name
3433
+ });
3434
+ }
3435
+ }
3436
+ async #sendHttpMessage(message, opts) {
3437
+ try {
3438
+ if (!this.#actorId || !this.#connectionId || !this.#connectionToken)
3439
+ throw new InternalError2("Missing connection ID or token.");
3440
+ logger().trace(
3441
+ getEnvUniversal("_RIVETKIT_LOG_MESSAGE") ? {
3442
+ msg: "sent http message",
3443
+ message: `${jsonStringifyCompat(message).substring(0, 100)}...`
3444
+ } : { msg: "sent http message" }
3445
+ );
3446
+ logger().debug({
3447
+ msg: "sending http message",
3448
+ actorId: this.#actorId,
3449
+ connectionId: this.#connectionId
3450
+ });
3451
+ await sendHttpRequest({
3452
+ url: "http://actor/connections/message",
3453
+ method: "POST",
3454
+ headers: {
3455
+ [HEADER_ENCODING]: this.#encoding,
3456
+ [HEADER_CONN_ID]: this.#connectionId,
3457
+ [HEADER_CONN_TOKEN]: this.#connectionToken
3458
+ },
3459
+ body: message,
3460
+ encoding: this.#encoding,
3461
+ skipParseResponse: true,
3462
+ customFetch: this.#driver.sendRequest.bind(this.#driver, this.#actorId),
3463
+ requestVersionedDataHandler: TO_SERVER_VERSIONED,
3464
+ responseVersionedDataHandler: TO_CLIENT_VERSIONED
3465
+ });
3466
+ } catch (error) {
3467
+ logger().warn({ msg: "failed to send message, added to queue", error });
3468
+ if (!(opts == null ? void 0 : opts.ephemeral)) {
3469
+ this.#messageQueue.unshift(message);
3470
+ }
3471
+ }
3472
+ }
3473
+ async #parseMessage(data) {
3474
+ invariant5(this.#transport, "transport must be defined");
3475
+ if (encodingIsBinary(this.#encoding) && "sse" in this.#transport) {
3476
+ if (typeof data === "string") {
3477
+ const binaryString = atob(data);
3478
+ data = new Uint8Array(
3479
+ [...binaryString].map((char) => char.charCodeAt(0))
3480
+ );
3481
+ } else {
3482
+ throw new InternalError2(
3483
+ `Expected data to be a string for SSE, got ${data}.`
3484
+ );
3485
+ }
3486
+ }
3487
+ const buffer = await inputDataToBuffer(data);
3488
+ return deserializeWithEncoding(this.#encoding, buffer, TO_CLIENT_VERSIONED);
3489
+ }
3490
+ /**
3491
+ * Get the actor ID (for testing purposes).
3492
+ * @internal
3493
+ */
3494
+ get actorId() {
3495
+ return this.#actorId;
3496
+ }
3497
+ /**
3498
+ * Get the connection ID (for testing purposes).
3499
+ * @internal
3500
+ */
3501
+ get connectionId() {
3502
+ return this.#connectionId;
3503
+ }
3504
+ /**
3505
+ * Disconnects from the actor.
3506
+ *
3507
+ * @returns {Promise<void>} A promise that resolves when the socket is gracefully closed.
3508
+ */
3509
+ async dispose() {
3510
+ if (this.#disposed) {
3511
+ logger().warn({ msg: "connection already disconnected" });
3512
+ return;
3513
+ }
3514
+ this.#disposed = true;
3515
+ logger().debug({ msg: "disposing actor conn" });
3516
+ clearInterval(this.#keepNodeAliveInterval);
3517
+ this.#abortController.abort();
3518
+ this.#client[ACTOR_CONNS_SYMBOL].delete(this);
3519
+ if (!this.#transport) {
3520
+ } else if ("websocket" in this.#transport) {
3521
+ const ws = this.#transport.websocket;
3522
+ if (ws.readyState === 2 || ws.readyState === 3) {
3523
+ logger().debug({ msg: "ws already closed or closing" });
3524
+ } else {
3525
+ const { promise, resolve } = promiseWithResolvers();
3526
+ ws.addEventListener("close", () => {
3527
+ logger().debug({ msg: "ws closed" });
3528
+ resolve(void 0);
3529
+ });
3530
+ ws.close();
3531
+ await promise;
3532
+ }
3533
+ } else if ("sse" in this.#transport) {
3534
+ if (this.#connectionId && this.#connectionToken) {
3535
+ try {
3536
+ await sendHttpRequest({
3537
+ url: "http://actor/connections/close",
3538
+ method: "POST",
3539
+ headers: {
3540
+ [HEADER_CONN_ID]: this.#connectionId,
3541
+ [HEADER_CONN_TOKEN]: this.#connectionToken
3542
+ },
3543
+ encoding: this.#encoding,
3544
+ skipParseResponse: true,
3545
+ customFetch: this.#driver.sendRequest.bind(
3546
+ this.#driver,
3547
+ this.#actorId
3548
+ ),
3549
+ requestVersionedDataHandler: TO_SERVER_VERSIONED,
3550
+ responseVersionedDataHandler: TO_CLIENT_VERSIONED
3551
+ });
3552
+ } catch (error) {
3553
+ logger().warn({ msg: "failed to send close request", error });
3554
+ }
3555
+ }
3556
+ this.#transport.sse.close();
3557
+ } else {
3558
+ assertUnreachable(this.#transport);
3559
+ }
3560
+ this.#transport = void 0;
3561
+ }
3562
+ #sendSubscription(eventName, subscribe) {
3563
+ this.#sendMessage(
3564
+ {
3565
+ body: {
3566
+ tag: "SubscriptionRequest",
3567
+ val: {
3568
+ eventName,
3569
+ subscribe
3570
+ }
3571
+ }
3572
+ },
3573
+ { ephemeral: true }
3574
+ );
3575
+ }
3576
+ };
3577
+
3578
+ // src/remote-manager-driver/mod.ts
3579
+ import * as cbor6 from "cbor-x";
3580
+ import invariant6 from "invariant";
3581
+
3582
+ // src/remote-manager-driver/log.ts
3583
+ function logger2() {
3584
+ return getLogger("remote-manager-driver");
3585
+ }
3586
+
3587
+ // src/remote-manager-driver/api-utils.ts
3588
+ var EngineApiError = class extends Error {
3589
+ constructor(group, code, message) {
3590
+ super(message || `Engine API error: ${group}/${code}`);
3591
+ this.group = group;
3592
+ this.code = code;
3593
+ this.name = "EngineApiError";
3594
+ }
3595
+ };
3596
+ function getEndpoint(config) {
3597
+ return config.endpoint ?? "http://127.0.0.1:6420";
3598
+ }
3599
+ async function apiCall(config, method, path, body) {
3600
+ const endpoint = getEndpoint(config);
3601
+ const url = combineUrlPath(endpoint, path, {
3602
+ namespace: config.namespace
3603
+ });
3604
+ logger2().debug({ msg: "making api call", method, url });
3605
+ const headers = {
3606
+ ...config.headers
3607
+ };
3608
+ if (config.token) {
3609
+ headers.Authorization = `Bearer ${config.token}`;
3610
+ }
3611
+ return await sendHttpRequest({
3612
+ method,
3613
+ url,
3614
+ headers,
3615
+ body,
3616
+ encoding: "json",
3617
+ skipParseResponse: false,
3618
+ requestVersionedDataHandler: void 0,
3619
+ responseVersionedDataHandler: void 0
3620
+ });
3621
+ }
3622
+
3623
+ // src/remote-manager-driver/actor-http-client.ts
3624
+ async function sendHttpRequestToActor(runConfig, actorId, actorRequest) {
3625
+ const url = new URL(actorRequest.url);
3626
+ const endpoint = getEndpoint(runConfig);
3627
+ const guardUrl = combineUrlPath(endpoint, url.pathname + url.search);
3628
+ let bodyToSend = null;
3629
+ const guardHeaders = buildGuardHeadersForHttp(
3630
+ runConfig,
3631
+ actorRequest,
3632
+ actorId
3633
+ );
3634
+ if (actorRequest.method !== "GET" && actorRequest.method !== "HEAD") {
3635
+ if (actorRequest.bodyUsed) {
3636
+ throw new Error("Request body has already been consumed");
3637
+ }
3638
+ const reqBody = await actorRequest.arrayBuffer();
3639
+ if (reqBody.byteLength !== 0) {
3640
+ bodyToSend = reqBody;
3641
+ guardHeaders.delete("transfer-encoding");
3642
+ guardHeaders.set("content-length", String(bodyToSend.byteLength));
3643
+ }
3644
+ }
3645
+ const guardRequest = new Request(guardUrl, {
3646
+ method: actorRequest.method,
3647
+ headers: guardHeaders,
3648
+ body: bodyToSend,
3649
+ signal: actorRequest.signal
3650
+ });
3651
+ return mutableResponse(await fetch(guardRequest));
3652
+ }
3653
+ function mutableResponse(fetchRes) {
3654
+ return new Response(fetchRes.body, fetchRes);
3655
+ }
3656
+ function buildGuardHeadersForHttp(runConfig, actorRequest, actorId) {
3657
+ const headers = new Headers();
3658
+ for (const [key, value] of actorRequest.headers.entries()) {
3659
+ headers.set(key, value);
3660
+ }
3661
+ for (const [key, value] of Object.entries(runConfig.headers)) {
3662
+ headers.set(key, value);
3663
+ }
3664
+ headers.set(HEADER_RIVET_TARGET, "actor");
3665
+ headers.set(HEADER_RIVET_ACTOR, actorId);
3666
+ if (runConfig.token) {
3667
+ headers.set(HEADER_RIVET_TOKEN, runConfig.token);
3668
+ }
3669
+ return headers;
3670
+ }
3671
+
3672
+ // src/remote-manager-driver/actor-websocket-client.ts
3673
+ async function openWebSocketToActor(runConfig, path, actorId, encoding, params, connId, connToken) {
3674
+ const WebSocket2 = await importWebSocket();
3675
+ const endpoint = getEndpoint(runConfig);
3676
+ const guardUrl = combineUrlPath(endpoint, path);
3677
+ logger2().debug({
3678
+ msg: "opening websocket to actor via guard",
3679
+ actorId,
3680
+ path,
3681
+ guardUrl
3682
+ });
3683
+ const ws = new WebSocket2(
3684
+ guardUrl,
3685
+ buildWebSocketProtocols(
3686
+ runConfig,
3687
+ actorId,
3688
+ encoding,
3689
+ params,
3690
+ connId,
3691
+ connToken
3692
+ )
3693
+ );
3694
+ ws.binaryType = "arraybuffer";
3695
+ logger2().debug({ msg: "websocket connection opened", actorId });
3696
+ return ws;
3697
+ }
3698
+ function buildWebSocketProtocols(runConfig, actorId, encoding, params, connId, connToken) {
3699
+ const protocols = [];
3700
+ protocols.push(WS_PROTOCOL_STANDARD);
3701
+ protocols.push(`${WS_PROTOCOL_TARGET}actor`);
3702
+ protocols.push(`${WS_PROTOCOL_ACTOR}${actorId}`);
3703
+ protocols.push(`${WS_PROTOCOL_ENCODING}${encoding}`);
3704
+ if (runConfig.token) {
3705
+ protocols.push(`${WS_PROTOCOL_TOKEN}${runConfig.token}`);
3706
+ }
3707
+ if (params) {
3708
+ protocols.push(
3709
+ `${WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`
3710
+ );
3711
+ }
3712
+ if (connId) {
3713
+ protocols.push(`${WS_PROTOCOL_CONN_ID}${connId}`);
3714
+ }
3715
+ if (connToken) {
3716
+ protocols.push(`${WS_PROTOCOL_CONN_TOKEN}${connToken}`);
3717
+ }
3718
+ return protocols;
3719
+ }
3720
+
3721
+ // src/remote-manager-driver/api-endpoints.ts
3722
+ async function getActor(config, name, actorId) {
3723
+ return apiCall(
3724
+ config,
3725
+ "GET",
3726
+ `/actors?name=${name}&actor_ids=${encodeURIComponent(actorId)}`
3727
+ );
3728
+ }
3729
+ async function getActorByKey(config, name, key) {
3730
+ const serializedKey = serializeActorKey(key);
3731
+ return apiCall(
3732
+ config,
3733
+ "GET",
3734
+ `/actors?name=${encodeURIComponent(name)}&key=${encodeURIComponent(serializedKey)}`
3735
+ );
3736
+ }
3737
+ async function getOrCreateActor(config, request) {
3738
+ return apiCall(
3739
+ config,
3740
+ "PUT",
3741
+ `/actors`,
3742
+ request
3743
+ );
3744
+ }
3745
+ async function createActor(config, request) {
3746
+ return apiCall(
3747
+ config,
3748
+ "POST",
3749
+ `/actors`,
3750
+ request
3751
+ );
3752
+ }
3753
+ async function destroyActor(config, actorId) {
3754
+ return apiCall(
3755
+ config,
3756
+ "DELETE",
3757
+ `/actors/${encodeURIComponent(actorId)}`
3758
+ );
3759
+ }
3760
+
3761
+ // src/remote-manager-driver/ws-proxy.ts
3762
+ async function createWebSocketProxy(c, targetUrl, protocols) {
3763
+ const WebSocket2 = await importWebSocket();
3764
+ const state = {};
3765
+ return {
3766
+ onOpen: async (event, clientWs) => {
3767
+ logger2().debug({ msg: "client websocket connected", targetUrl });
3768
+ if (clientWs.readyState !== 1) {
3769
+ logger2().warn({
3770
+ msg: "client websocket not open on connection",
3771
+ targetUrl,
3772
+ readyState: clientWs.readyState
3773
+ });
3774
+ return;
3775
+ }
3776
+ const targetWs = new WebSocket2(targetUrl, protocols);
3777
+ state.targetWs = targetWs;
3778
+ state.connectPromise = new Promise((resolve, reject) => {
3779
+ targetWs.addEventListener("open", () => {
3780
+ logger2().debug({ msg: "target websocket connected", targetUrl });
3781
+ if (clientWs.readyState !== 1) {
3782
+ logger2().warn({
3783
+ msg: "client websocket closed before target connected",
3784
+ targetUrl,
3785
+ clientReadyState: clientWs.readyState
3786
+ });
3787
+ targetWs.close(1001, "Client disconnected");
3788
+ reject(new Error("Client disconnected"));
3789
+ return;
3790
+ }
3791
+ resolve();
3792
+ });
3793
+ targetWs.addEventListener("error", (error) => {
3794
+ logger2().warn({
3795
+ msg: "target websocket error during connection",
3796
+ targetUrl
3797
+ });
3798
+ reject(error);
3799
+ });
3800
+ });
3801
+ state.targetWs.addEventListener("message", (event2) => {
3802
+ if (typeof event2.data === "string" || event2.data instanceof ArrayBuffer) {
3803
+ clientWs.send(event2.data);
3804
+ } else if (event2.data instanceof Blob) {
3805
+ event2.data.arrayBuffer().then((buffer) => {
3806
+ clientWs.send(buffer);
3807
+ });
3808
+ }
3809
+ });
3810
+ state.targetWs.addEventListener("close", (event2) => {
3811
+ logger2().debug({
3812
+ msg: "target websocket closed",
3813
+ targetUrl,
3814
+ code: event2.code,
3815
+ reason: event2.reason
3816
+ });
3817
+ closeWebSocketIfOpen(clientWs, event2.code, event2.reason);
3818
+ });
3819
+ state.targetWs.addEventListener("error", (error) => {
3820
+ logger2().error({
3821
+ msg: "target websocket error",
3822
+ targetUrl,
3823
+ error: stringifyError(error)
3824
+ });
3825
+ closeWebSocketIfOpen(clientWs, 1011, "Target WebSocket error");
3826
+ });
3827
+ },
3828
+ onMessage: async (event, clientWs) => {
3829
+ if (!state.targetWs || !state.connectPromise) {
3830
+ logger2().error({ msg: "websocket state not initialized", targetUrl });
3831
+ return;
3832
+ }
3833
+ try {
3834
+ await state.connectPromise;
3835
+ if (state.targetWs.readyState === WebSocket2.OPEN) {
3836
+ state.targetWs.send(event.data);
3837
+ } else {
3838
+ logger2().warn({
3839
+ msg: "target websocket not open",
3840
+ targetUrl,
3841
+ readyState: state.targetWs.readyState
3842
+ });
3843
+ }
3844
+ } catch (error) {
3845
+ logger2().error({
3846
+ msg: "failed to connect to target websocket",
3847
+ targetUrl,
3848
+ error
3849
+ });
3850
+ closeWebSocketIfOpen(clientWs, 1011, "Failed to connect to target");
3851
+ }
3852
+ },
3853
+ onClose: (event, clientWs) => {
3854
+ logger2().debug({
3855
+ msg: "client websocket closed",
3856
+ targetUrl,
3857
+ code: event.code,
3858
+ reason: event.reason,
3859
+ wasClean: event.wasClean
3860
+ });
3861
+ if (state.targetWs) {
3862
+ if (state.targetWs.readyState === WebSocket2.OPEN || state.targetWs.readyState === WebSocket2.CONNECTING) {
3863
+ state.targetWs.close(1e3, event.reason || "Client disconnected");
3864
+ }
3865
+ }
3866
+ },
3867
+ onError: (event, clientWs) => {
3868
+ logger2().error({ msg: "client websocket error", targetUrl, event });
3869
+ if (state.targetWs) {
3870
+ if (state.targetWs.readyState === WebSocket2.OPEN) {
3871
+ state.targetWs.close(1011, "Client WebSocket error");
3872
+ } else if (state.targetWs.readyState === WebSocket2.CONNECTING) {
3873
+ state.targetWs.close();
3874
+ }
3875
+ }
3876
+ }
3877
+ };
3878
+ }
3879
+ function closeWebSocketIfOpen(ws, code, reason) {
3880
+ if (ws.readyState === 1) {
3881
+ ws.close(code, reason);
3882
+ } else if ("close" in ws && ws.readyState === WebSocket.OPEN) {
3883
+ ws.close(code, reason);
3884
+ }
3885
+ }
3886
+
3887
+ // src/remote-manager-driver/mod.ts
3888
+ var RemoteManagerDriver = class {
3889
+ #config;
3890
+ constructor(runConfig) {
3891
+ this.#config = runConfig;
3892
+ }
3893
+ async getForId({
3894
+ c,
3895
+ name,
3896
+ actorId
3897
+ }) {
3898
+ const response = await getActor(this.#config, name, actorId);
3899
+ const actor = response.actors[0];
3900
+ if (!actor) return void 0;
3901
+ if (actor.name !== name) {
3902
+ logger2().debug({
3903
+ msg: "actor name mismatch from api",
3904
+ actorId,
3905
+ apiName: actor.name,
3906
+ requestedName: name
3907
+ });
3908
+ return void 0;
3909
+ }
3910
+ const keyRaw = actor.key;
3911
+ invariant6(keyRaw, `actor ${actorId} should have key`);
3912
+ const key = deserializeActorKey(keyRaw);
3913
+ return {
3914
+ actorId,
3915
+ name,
3916
+ key
3917
+ };
3918
+ }
3919
+ async getWithKey({
3920
+ c,
3921
+ name,
3922
+ key
3923
+ }) {
3924
+ logger2().debug({ msg: "getWithKey: searching for actor", name, key });
3925
+ try {
3926
+ const response = await getActorByKey(this.#config, name, key);
3927
+ const actor = response.actors[0];
3928
+ if (!actor) return void 0;
3929
+ const actorId = actor.actor_id;
3930
+ logger2().debug({
3931
+ msg: "getWithKey: found actor via api",
3932
+ actorId,
3933
+ name,
3934
+ key
3935
+ });
3936
+ return {
3937
+ actorId,
3938
+ name,
3939
+ key
3940
+ };
3941
+ } catch (error) {
3942
+ if (error instanceof EngineApiError && error.group === "actor" && error.code === "not_found") {
3943
+ return void 0;
3944
+ }
3945
+ throw error;
3946
+ }
3947
+ }
3948
+ async getOrCreateWithKey(input) {
3949
+ const { c, name, key, input: actorInput, region } = input;
3950
+ logger2().info({
3951
+ msg: "getOrCreateWithKey: getting or creating actor via engine api",
3952
+ name,
3953
+ key
3954
+ });
3955
+ const { actor, created } = await getOrCreateActor(this.#config, {
3956
+ name,
3957
+ key: serializeActorKey(key),
3958
+ runner_name_selector: this.#config.runnerName,
3959
+ input: actorInput ? cbor6.encode(actorInput).toString("base64") : void 0,
3960
+ crash_policy: "sleep"
3961
+ });
3962
+ const actorId = actor.actor_id;
3963
+ logger2().info({
3964
+ msg: "getOrCreateWithKey: actor ready",
3965
+ actorId,
3966
+ name,
3967
+ key,
3968
+ created
3969
+ });
3970
+ return {
3971
+ actorId,
3972
+ name,
3973
+ key
3974
+ };
3975
+ }
3976
+ async createActor({
3977
+ c,
3978
+ name,
3979
+ key,
3980
+ input
3981
+ }) {
3982
+ logger2().info({ msg: "creating actor via engine api", name, key });
3983
+ const result = await createActor(this.#config, {
3984
+ name,
3985
+ runner_name_selector: this.#config.runnerName,
3986
+ key: serializeActorKey(key),
3987
+ input: input ? cbor6.encode(input).toString("base64") : null,
3988
+ crash_policy: "sleep"
3989
+ });
3990
+ const actorId = result.actor.actor_id;
3991
+ logger2().info({ msg: "actor created", actorId, name, key });
3992
+ return {
3993
+ actorId,
3994
+ name,
3995
+ key
3996
+ };
3997
+ }
3998
+ async destroyActor(actorId) {
3999
+ logger2().info({ msg: "destroying actor via engine api", actorId });
4000
+ await destroyActor(this.#config, actorId);
4001
+ logger2().info({ msg: "actor destroyed", actorId });
4002
+ }
4003
+ async sendRequest(actorId, actorRequest) {
4004
+ return await sendHttpRequestToActor(this.#config, actorId, actorRequest);
4005
+ }
4006
+ async openWebSocket(path, actorId, encoding, params, connId, connToken) {
4007
+ return await openWebSocketToActor(
4008
+ this.#config,
4009
+ path,
4010
+ actorId,
4011
+ encoding,
4012
+ params,
4013
+ connId,
4014
+ connToken
4015
+ );
4016
+ }
4017
+ async proxyRequest(_c, actorRequest, actorId) {
4018
+ return await sendHttpRequestToActor(this.#config, actorId, actorRequest);
4019
+ }
4020
+ async proxyWebSocket(c, path, actorId, encoding, params, connId, connToken) {
4021
+ var _a, _b;
4022
+ const upgradeWebSocket = (_b = (_a = this.#config).getUpgradeWebSocket) == null ? void 0 : _b.call(_a);
4023
+ invariant6(upgradeWebSocket, "missing getUpgradeWebSocket");
4024
+ const endpoint = getEndpoint(this.#config);
4025
+ const guardUrl = combineUrlPath(endpoint, path);
4026
+ const wsGuardUrl = guardUrl.replace("http://", "ws://");
4027
+ logger2().debug({
4028
+ msg: "forwarding websocket to actor via guard",
4029
+ actorId,
4030
+ path,
4031
+ guardUrl
4032
+ });
4033
+ const protocols = buildWebSocketProtocols(
4034
+ this.#config,
4035
+ actorId,
4036
+ encoding,
4037
+ params,
4038
+ connId,
4039
+ connToken
4040
+ );
4041
+ const args = await createWebSocketProxy(c, wsGuardUrl, protocols);
4042
+ return await upgradeWebSocket(() => args)(c, noopNext());
4043
+ }
4044
+ displayInformation() {
4045
+ return { name: "Remote", properties: {} };
4046
+ }
4047
+ getOrCreateInspectorAccessToken() {
4048
+ return generateRandomString();
4049
+ }
4050
+ };
4051
+
4052
+ export {
4053
+ generateConnId,
4054
+ generateConnToken,
4055
+ generateConnSocketId,
4056
+ createActorInspectorRouter,
4057
+ serializeActorKey,
4058
+ deserializeActorKey,
4059
+ ActorDefinition,
4060
+ lookupInRegistry,
4061
+ ActorClientError,
4062
+ InternalError2 as InternalError,
4063
+ ManagerError,
4064
+ MalformedResponseMessage,
4065
+ ActorError,
4066
+ ActorConnRaw,
4067
+ ActorHandleRaw,
4068
+ createClientWithDriver,
4069
+ RemoteManagerDriver
4070
+ };
4071
+ //# sourceMappingURL=chunk-BW5DPM6Z.js.map