rivetkit 2.0.37 → 2.0.39

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 (104) hide show
  1. package/dist/tsup/{chunk-G4N7FZMM.cjs → chunk-7E3RWMR6.cjs} +195 -93
  2. package/dist/tsup/chunk-7E3RWMR6.cjs.map +1 -0
  3. package/dist/tsup/{chunk-J6TX5EFW.js → chunk-BQ36VTSB.js} +108 -6
  4. package/dist/tsup/chunk-BQ36VTSB.js.map +1 -0
  5. package/dist/tsup/{chunk-4V7MS7SO.cjs → chunk-C64FV764.cjs} +3 -3
  6. package/dist/tsup/{chunk-4V7MS7SO.cjs.map → chunk-C64FV764.cjs.map} +1 -1
  7. package/dist/tsup/{chunk-XI335ZED.js → chunk-CDK6DRO2.js} +6 -4
  8. package/dist/tsup/chunk-CDK6DRO2.js.map +1 -0
  9. package/dist/tsup/{chunk-LYYTV7DN.cjs → chunk-DY4H3ASE.cjs} +50 -46
  10. package/dist/tsup/chunk-DY4H3ASE.cjs.map +1 -0
  11. package/dist/tsup/{chunk-B6BP74X3.cjs → chunk-KMYFL3OL.cjs} +318 -92
  12. package/dist/tsup/chunk-KMYFL3OL.cjs.map +1 -0
  13. package/dist/tsup/{chunk-22NKW7F5.cjs → chunk-MZPYVTVG.cjs} +9 -9
  14. package/dist/tsup/{chunk-22NKW7F5.cjs.map → chunk-MZPYVTVG.cjs.map} +1 -1
  15. package/dist/tsup/{chunk-RBA5AQTB.js → chunk-OJZRCEIA.js} +5 -5
  16. package/dist/tsup/{chunk-RXA3ZMCL.js → chunk-PHCD25XO.js} +2 -2
  17. package/dist/tsup/{chunk-5XGZXH74.js → chunk-PVKUXMOA.js} +264 -38
  18. package/dist/tsup/chunk-PVKUXMOA.js.map +1 -0
  19. package/dist/tsup/{chunk-FIUSIG6J.js → chunk-T7IPDBWH.js} +8 -4
  20. package/dist/tsup/{chunk-FIUSIG6J.js.map → chunk-T7IPDBWH.js.map} +1 -1
  21. package/dist/tsup/{chunk-5VVIFC6M.cjs → chunk-UAX5E3EU.cjs} +443 -369
  22. package/dist/tsup/chunk-UAX5E3EU.cjs.map +1 -0
  23. package/dist/tsup/{chunk-X5IX3YPO.cjs → chunk-X72X7I7T.cjs} +6 -4
  24. package/dist/tsup/chunk-X72X7I7T.cjs.map +1 -0
  25. package/dist/tsup/{chunk-ZQBSQ6H3.js → chunk-XU74APB4.js} +208 -134
  26. package/dist/tsup/chunk-XU74APB4.js.map +1 -0
  27. package/dist/tsup/client/mod.cjs +5 -5
  28. package/dist/tsup/client/mod.d.cts +3 -3
  29. package/dist/tsup/client/mod.d.ts +3 -3
  30. package/dist/tsup/client/mod.js +4 -4
  31. package/dist/tsup/common/log.cjs +2 -2
  32. package/dist/tsup/common/log.js +1 -1
  33. package/dist/tsup/common/websocket.cjs +3 -3
  34. package/dist/tsup/common/websocket.js +2 -2
  35. package/dist/tsup/{config--NjwiYlS.d.cts → config-BuBlMs6C.d.cts} +238 -60
  36. package/dist/tsup/{config-CRuzI6n4.d.ts → config-CBwo4ooA.d.ts} +238 -60
  37. package/dist/tsup/{driver-yKjYx9Yy.d.cts → driver-CPXmh8f8.d.cts} +1 -1
  38. package/dist/tsup/{driver-BcmckRaF.d.ts → driver-DxWa6HUO.d.ts} +1 -1
  39. package/dist/tsup/driver-helpers/mod.cjs +3 -3
  40. package/dist/tsup/driver-helpers/mod.d.cts +2 -2
  41. package/dist/tsup/driver-helpers/mod.d.ts +2 -2
  42. package/dist/tsup/driver-helpers/mod.js +2 -2
  43. package/dist/tsup/driver-test-suite/mod.cjs +81 -35
  44. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  45. package/dist/tsup/driver-test-suite/mod.d.cts +2 -2
  46. package/dist/tsup/driver-test-suite/mod.d.ts +2 -2
  47. package/dist/tsup/driver-test-suite/mod.js +407 -361
  48. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  49. package/dist/tsup/{kv-CTM8sCvx.d.cts → keys-Chhy4ylv.d.cts} +1 -0
  50. package/dist/tsup/{kv-CTM8sCvx.d.ts → keys-Chhy4ylv.d.ts} +1 -0
  51. package/dist/tsup/mod.cjs +19 -7
  52. package/dist/tsup/mod.cjs.map +1 -1
  53. package/dist/tsup/mod.d.cts +5 -5
  54. package/dist/tsup/mod.d.ts +5 -5
  55. package/dist/tsup/mod.js +18 -6
  56. package/dist/tsup/test/mod.cjs +7 -7
  57. package/dist/tsup/test/mod.d.cts +1 -1
  58. package/dist/tsup/test/mod.d.ts +1 -1
  59. package/dist/tsup/test/mod.js +6 -6
  60. package/dist/tsup/utils.cjs +2 -2
  61. package/dist/tsup/utils.js +1 -1
  62. package/package.json +6 -4
  63. package/src/actor/config.ts +198 -2
  64. package/src/actor/contexts/base/actor.ts +12 -0
  65. package/src/actor/instance/connection-manager.ts +1 -1
  66. package/src/actor/instance/keys.ts +29 -0
  67. package/src/actor/instance/kv.ts +240 -14
  68. package/src/actor/instance/mod.ts +5 -4
  69. package/src/actor/instance/state-manager.ts +1 -1
  70. package/src/actor/mod.ts +2 -1
  71. package/src/actor/router-websocket-endpoints.ts +2 -1
  72. package/src/client/actor-conn.ts +70 -81
  73. package/src/client/actor-handle.ts +35 -15
  74. package/src/client/actor-query.ts +47 -0
  75. package/src/client/errors.ts +22 -58
  76. package/src/client/mod.ts +1 -1
  77. package/src/client/utils.ts +33 -0
  78. package/src/driver-helpers/utils.ts +1 -1
  79. package/src/driver-test-suite/mod.ts +3 -0
  80. package/src/driver-test-suite/test-inline-client-driver.ts +3 -0
  81. package/src/driver-test-suite/tests/actor-kv.ts +44 -0
  82. package/src/driver-test-suite/utils.ts +4 -0
  83. package/src/drivers/engine/actor-driver.ts +3 -3
  84. package/src/drivers/file-system/manager.ts +5 -0
  85. package/src/manager/driver.ts +8 -3
  86. package/src/manager-api/actors.ts +1 -20
  87. package/src/registry/config/index.ts +68 -0
  88. package/src/remote-manager-driver/actor-http-client.ts +5 -3
  89. package/src/remote-manager-driver/actor-websocket-client.ts +18 -7
  90. package/src/remote-manager-driver/mod.ts +21 -1
  91. package/src/serverless/router.test.ts +166 -0
  92. package/src/serverless/router.ts +58 -5
  93. package/src/utils/env-vars.ts +4 -1
  94. package/dist/tsup/chunk-5VVIFC6M.cjs.map +0 -1
  95. package/dist/tsup/chunk-5XGZXH74.js.map +0 -1
  96. package/dist/tsup/chunk-B6BP74X3.cjs.map +0 -1
  97. package/dist/tsup/chunk-G4N7FZMM.cjs.map +0 -1
  98. package/dist/tsup/chunk-J6TX5EFW.js.map +0 -1
  99. package/dist/tsup/chunk-LYYTV7DN.cjs.map +0 -1
  100. package/dist/tsup/chunk-X5IX3YPO.cjs.map +0 -1
  101. package/dist/tsup/chunk-XI335ZED.js.map +0 -1
  102. package/dist/tsup/chunk-ZQBSQ6H3.js.map +0 -1
  103. /package/dist/tsup/{chunk-RBA5AQTB.js.map → chunk-OJZRCEIA.js.map} +0 -0
  104. /package/dist/tsup/{chunk-RXA3ZMCL.js.map → chunk-PHCD25XO.js.map} +0 -0
@@ -1,15 +1,241 @@
1
- export const KEYS = {
2
- PERSIST_DATA: Uint8Array.from([1]),
3
- CONN_PREFIX: Uint8Array.from([2]), // Prefix for connection keys
4
- INSPECTOR_TOKEN: Uint8Array.from([3]), // Inspector token key
5
- };
6
-
7
- // Helper to create a connection key
8
- export function makeConnKey(connId: string): Uint8Array {
9
- const encoder = new TextEncoder();
10
- const connIdBytes = encoder.encode(connId);
11
- const key = new Uint8Array(KEYS.CONN_PREFIX.length + connIdBytes.length);
12
- key.set(KEYS.CONN_PREFIX, 0);
13
- key.set(connIdBytes, KEYS.CONN_PREFIX.length);
14
- return key;
1
+ import type { ActorDriver } from "../driver";
2
+ import { makePrefixedKey, removePrefixFromKey } from "./keys";
3
+
4
+ /**
5
+ * User-facing KV storage interface exposed on ActorContext.
6
+ */
7
+ type KvValueType = "text" | "arrayBuffer" | "binary";
8
+ type KvKeyType = "text" | "binary";
9
+ type KvKey = Uint8Array | string;
10
+
11
+ type KvValueTypeMap = {
12
+ text: string;
13
+ arrayBuffer: ArrayBuffer;
14
+ binary: Uint8Array;
15
+ };
16
+
17
+ type KvKeyTypeMap = {
18
+ text: string;
19
+ binary: Uint8Array;
20
+ };
21
+
22
+ type KvValueOptions<T extends KvValueType = "text"> = {
23
+ type?: T;
24
+ };
25
+
26
+ type KvListOptions<
27
+ T extends KvValueType = "text",
28
+ K extends KvKeyType = "text",
29
+ > = KvValueOptions<T> & {
30
+ keyType?: K;
31
+ };
32
+
33
+ const textEncoder = new TextEncoder();
34
+ const textDecoder = new TextDecoder();
35
+
36
+ function encodeKey<K extends KvKeyType = KvKeyType>(
37
+ key: KvKeyTypeMap[K],
38
+ keyType?: K,
39
+ ): Uint8Array {
40
+ if (key instanceof Uint8Array) {
41
+ return key;
42
+ }
43
+ const resolvedKeyType = keyType ?? "text";
44
+ if (resolvedKeyType === "binary") {
45
+ throw new TypeError("Expected a Uint8Array when keyType is binary");
46
+ }
47
+ return textEncoder.encode(key);
48
+ }
49
+
50
+ function decodeKey<K extends KvKeyType = "text">(
51
+ key: Uint8Array,
52
+ keyType?: K,
53
+ ): KvKeyTypeMap[K] {
54
+ const resolvedKeyType = keyType ?? "text";
55
+ switch (resolvedKeyType) {
56
+ case "text":
57
+ return textDecoder.decode(key) as KvKeyTypeMap[K];
58
+ case "binary":
59
+ return key as KvKeyTypeMap[K];
60
+ default:
61
+ throw new TypeError("Invalid kv key type");
62
+ }
63
+ }
64
+
65
+ function resolveValueType(
66
+ value: string | Uint8Array | ArrayBuffer,
67
+ ): KvValueType {
68
+ if (typeof value === "string") {
69
+ return "text";
70
+ }
71
+ if (value instanceof Uint8Array) {
72
+ return "binary";
73
+ }
74
+ if (value instanceof ArrayBuffer) {
75
+ return "arrayBuffer";
76
+ }
77
+ throw new TypeError("Invalid kv value");
78
+ }
79
+
80
+ function encodeValue<T extends KvValueType = KvValueType>(
81
+ value: KvValueTypeMap[T],
82
+ options?: KvValueOptions<T>,
83
+ ): Uint8Array {
84
+ const type =
85
+ options?.type ??
86
+ resolveValueType(value as string | Uint8Array | ArrayBuffer);
87
+ switch (type) {
88
+ case "text":
89
+ if (typeof value !== "string") {
90
+ throw new TypeError("Expected a string when type is text");
91
+ }
92
+ return textEncoder.encode(value);
93
+ case "arrayBuffer":
94
+ if (!(value instanceof ArrayBuffer)) {
95
+ throw new TypeError("Expected an ArrayBuffer when type is arrayBuffer");
96
+ }
97
+ return new Uint8Array(value);
98
+ case "binary":
99
+ if (!(value instanceof Uint8Array)) {
100
+ throw new TypeError("Expected a Uint8Array when type is binary");
101
+ }
102
+ return value;
103
+ default:
104
+ throw new TypeError("Invalid kv value type");
105
+ }
106
+ }
107
+
108
+ function decodeValue<T extends KvValueType = "text">(
109
+ value: Uint8Array,
110
+ options?: KvValueOptions<T>,
111
+ ): KvValueTypeMap[T] {
112
+ const type = options?.type ?? "text";
113
+ switch (type) {
114
+ case "text":
115
+ return textDecoder.decode(value) as KvValueTypeMap[T];
116
+ case "arrayBuffer": {
117
+ const copy = new Uint8Array(value.byteLength);
118
+ copy.set(value);
119
+ return copy.buffer as KvValueTypeMap[T];
120
+ }
121
+ case "binary":
122
+ return value as KvValueTypeMap[T];
123
+ default:
124
+ throw new TypeError("Invalid kv value type");
125
+ }
126
+ }
127
+
128
+ export class ActorKv {
129
+ #driver: ActorDriver;
130
+ #actorId: string;
131
+
132
+ constructor(driver: ActorDriver, actorId: string) {
133
+ this.#driver = driver;
134
+ this.#actorId = actorId;
135
+ }
136
+
137
+ /**
138
+ * Get a single value by key.
139
+ */
140
+ async get<T extends KvValueType = "text">(
141
+ key: KvKey,
142
+ options?: KvValueOptions<T>,
143
+ ): Promise<KvValueTypeMap[T] | null> {
144
+ const results = await this.#driver.kvBatchGet(this.#actorId, [
145
+ makePrefixedKey(encodeKey(key)),
146
+ ]);
147
+ const result = results[0];
148
+ if (!result) {
149
+ return null;
150
+ }
151
+ return decodeValue(result, options);
152
+ }
153
+
154
+ /**
155
+ * Get multiple values by keys.
156
+ */
157
+ async getBatch<T extends KvValueType = "text">(
158
+ keys: KvKey[],
159
+ options?: KvValueOptions<T>,
160
+ ): Promise<(KvValueTypeMap[T] | null)[]> {
161
+ const prefixedKeys = keys.map((key) =>
162
+ makePrefixedKey(encodeKey(key)),
163
+ );
164
+ const results = await this.#driver.kvBatchGet(
165
+ this.#actorId,
166
+ prefixedKeys,
167
+ );
168
+ return results.map((result) =>
169
+ result ? decodeValue(result, options) : null,
170
+ );
171
+ }
172
+
173
+ /**
174
+ * Put a single key-value pair.
175
+ */
176
+ async put<T extends KvValueType = KvValueType>(
177
+ key: KvKey,
178
+ value: KvValueTypeMap[T],
179
+ options?: KvValueOptions<T>,
180
+ ): Promise<void> {
181
+ await this.#driver.kvBatchPut(this.#actorId, [
182
+ [makePrefixedKey(encodeKey(key)), encodeValue(value, options)],
183
+ ]);
184
+ }
185
+
186
+ /**
187
+ * Put multiple key-value pairs.
188
+ */
189
+ async putBatch<T extends KvValueType = KvValueType>(
190
+ entries: [KvKey, KvValueTypeMap[T]][],
191
+ options?: KvValueOptions<T>,
192
+ ): Promise<void> {
193
+ const prefixedEntries: [Uint8Array, Uint8Array][] = entries.map(
194
+ ([key, value]) => [
195
+ makePrefixedKey(encodeKey(key)),
196
+ encodeValue(value, options),
197
+ ],
198
+ );
199
+ await this.#driver.kvBatchPut(this.#actorId, prefixedEntries);
200
+ }
201
+
202
+ /**
203
+ * Delete a single key.
204
+ */
205
+ async delete(key: KvKey): Promise<void> {
206
+ await this.#driver.kvBatchDelete(this.#actorId, [
207
+ makePrefixedKey(encodeKey(key)),
208
+ ]);
209
+ }
210
+
211
+ /**
212
+ * Delete multiple keys.
213
+ */
214
+ async deleteBatch(keys: KvKey[]): Promise<void> {
215
+ const prefixedKeys = keys.map((key) =>
216
+ makePrefixedKey(encodeKey(key)),
217
+ );
218
+ await this.#driver.kvBatchDelete(this.#actorId, prefixedKeys);
219
+ }
220
+
221
+ /**
222
+ * List all keys with a given prefix.
223
+ * Returns key-value pairs where keys have the user prefix removed.
224
+ */
225
+ async list<T extends KvValueType = "text", K extends KvKeyType = "text">(
226
+ prefix: KvKeyTypeMap[K],
227
+ options?: KvListOptions<T, K>,
228
+ ): Promise<[KvKeyTypeMap[K], KvValueTypeMap[T]][]> {
229
+ const prefixedPrefix = makePrefixedKey(
230
+ encodeKey(prefix, options?.keyType),
231
+ );
232
+ const results = await this.#driver.kvListPrefix(
233
+ this.#actorId,
234
+ prefixedPrefix,
235
+ );
236
+ return results.map(([key, value]) => [
237
+ decodeKey<K>(removePrefixFromKey(key), options?.keyType),
238
+ decodeValue<T>(value, options),
239
+ ]);
240
+ }
15
241
  }
@@ -44,7 +44,7 @@ import {
44
44
  } from "../utils";
45
45
  import { ConnectionManager } from "./connection-manager";
46
46
  import { EventManager } from "./event-manager";
47
- import { KEYS } from "./kv";
47
+ import { KEYS } from "./keys";
48
48
  import {
49
49
  convertActorFromBarePersisted,
50
50
  type PersistedActor,
@@ -203,7 +203,7 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
203
203
  }
204
204
 
205
205
  get actions(): string[] {
206
- return Object.keys(this.#config.actions);
206
+ return Object.keys(this.#config.actions ?? {});
207
207
  }
208
208
 
209
209
  get config(): ActorConfig<S, CP, CS, V, I, DB> {
@@ -516,12 +516,13 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
516
516
  ): Promise<unknown> {
517
517
  this.assertReady();
518
518
 
519
- if (!(actionName in this.#config.actions)) {
519
+ const actions = this.#config.actions ?? {};
520
+ if (!(actionName in actions)) {
520
521
  this.#rLog.warn({ msg: "action does not exist", actionName });
521
522
  throw new errors.ActionNotFound(actionName);
522
523
  }
523
524
 
524
- const actionFunction = this.#config.actions[actionName];
525
+ const actionFunction = actions[actionName];
525
526
  if (typeof actionFunction !== "function") {
526
527
  this.#rLog.warn({
527
528
  msg: "action is not a function",
@@ -12,7 +12,7 @@ import { convertConnToBarePersistedConn } from "../conn/persisted";
12
12
  import type { ActorDriver } from "../driver";
13
13
  import * as errors from "../errors";
14
14
  import { isConnStatePath, isStatePath } from "../utils";
15
- import { KEYS, makeConnKey } from "./kv";
15
+ import { KEYS, makeConnKey } from "./keys";
16
16
  import type { ActorInstance } from "./mod";
17
17
  import { convertActorToBarePersisted, type PersistedActor } from "./persisted";
18
18
 
package/src/actor/mod.ts CHANGED
@@ -76,7 +76,8 @@ export type { AnyConn, Conn } from "./conn/mod";
76
76
  export type { ActorDefinition, AnyActorDefinition } from "./definition";
77
77
  export { lookupInRegistry } from "./definition";
78
78
  export { UserError, type UserErrorOptions } from "./errors";
79
- export { KEYS as KV_KEYS } from "./instance/kv";
79
+ export { KEYS as KV_KEYS } from "./instance/keys";
80
+ export { ActorKv } from "./instance/kv";
80
81
  export type { AnyActorInstance } from "./instance/mod";
81
82
  export {
82
83
  type ActorRouter,
@@ -384,7 +384,8 @@ export function parseWebSocketProtocols(
384
384
  }
385
385
  }
386
386
 
387
- const encoding = EncodingSchema.parse(encodingRaw);
387
+ // Default to "json" encoding for raw WebSocket connections without subprotocols
388
+ const encoding = EncodingSchema.parse(encodingRaw ?? "json");
388
389
  const connParams = connParamsRaw ? JSON.parse(connParamsRaw) : undefined;
389
390
 
390
391
  return { encoding, connParams };
@@ -26,13 +26,14 @@ import { deserializeWithEncoding, serializeWithEncoding } from "@/serde";
26
26
  import { bufferToArrayBuffer, promiseWithResolvers } from "@/utils";
27
27
  import { getLogMessage } from "@/utils/env-vars";
28
28
  import type { ActorDefinitionActions } from "./actor-common";
29
- import { queryActor } from "./actor-query";
29
+ import { checkForSchedulingError, queryActor } from "./actor-query";
30
30
  import { ACTOR_CONNS_SYMBOL, type ClientRaw } from "./client";
31
31
  import * as errors from "./errors";
32
32
  import { logger } from "./log";
33
33
  import {
34
34
  type WebSocketMessage as ConnMessage,
35
35
  messageLength,
36
+ parseWebSocketCloseReason,
36
37
  sendHttpRequest,
37
38
  } from "./utils";
38
39
 
@@ -387,9 +388,9 @@ enc
387
388
  });
388
389
  }
389
390
  });
390
- ws.addEventListener("close", (ev) => {
391
+ ws.addEventListener("close", async (ev) => {
391
392
  try {
392
- this.#handleOnClose(ev);
393
+ await this.#handleOnClose(ev);
393
394
  } catch (err) {
394
395
  logger().error({
395
396
  msg: "error in websocket close handler",
@@ -519,27 +520,24 @@ enc
519
520
  metadata,
520
521
  });
521
522
 
522
- // Check if this is an actor scheduling error
523
- let errorToThrow: Error;
524
- if (
525
- group === "guard" &&
526
- (code === "actor_ready_timeout" ||
527
- code === "actor_runner_failed") &&
528
- this.#actorId
529
- ) {
530
- // Try to fetch actor details to get more specific error info
531
- const schedulingError =
532
- await this.#checkForSchedulingError();
533
- errorToThrow =
534
- schedulingError ??
535
- new errors.ActorError(group, code, message, metadata);
536
- } else {
537
- errorToThrow = new errors.ActorError(
523
+ // Check if this is an actor scheduling error and try to get more details
524
+ let errorToThrow = new errors.ActorError(
525
+ group,
526
+ code,
527
+ message,
528
+ metadata,
529
+ );
530
+ if (errors.isSchedulingError(group, code) && this.#actorId) {
531
+ const schedulingError = await checkForSchedulingError(
538
532
  group,
539
533
  code,
540
- message,
541
- metadata,
534
+ this.#actorId,
535
+ this.#actorQuery,
536
+ this.#driver,
542
537
  );
538
+ if (schedulingError) {
539
+ errorToThrow = schedulingError;
540
+ }
543
541
  }
544
542
 
545
543
  // If we have an onOpenPromise, reject it with the error
@@ -553,10 +551,7 @@ enc
553
551
  this.#actionsInFlight.delete(id);
554
552
  }
555
553
 
556
- // Dispatch to error handler if it's an ActorError
557
- if (errorToThrow instanceof errors.ActorError) {
558
- this.#dispatchActorError(errorToThrow);
559
- }
554
+ this.#dispatchActorError(errorToThrow);
560
555
  }
561
556
  } else if (response.body.tag === "ActionResponse") {
562
557
  // Action response OK
@@ -587,7 +582,7 @@ enc
587
582
  }
588
583
 
589
584
  /** Called by the onclose event from drivers. */
590
- #handleOnClose(event: Event | CloseEvent) {
585
+ async #handleOnClose(event: Event | CloseEvent) {
591
586
  // We can't use `event instanceof CloseEvent` because it's not defined in NodeJS
592
587
  const closeEvent = event as CloseEvent;
593
588
  const wasClean = closeEvent.wasClean;
@@ -609,12 +604,55 @@ enc
609
604
  this.#rejectPendingPromises(new errors.ActorConnDisposed(), true);
610
605
  } else {
611
606
  this.#setConnStatus("disconnected");
612
- this.#rejectPendingPromises(
613
- new Error(
614
- `${wasClean ? "Connection closed" : "Connection lost"} (code: ${closeEvent.code}, reason: ${closeEvent.reason})`,
615
- ),
616
- false,
617
- );
607
+
608
+ // Build error from close event
609
+ let error: Error;
610
+ const reason = closeEvent.reason || "";
611
+ const parsed = parseWebSocketCloseReason(reason);
612
+
613
+ if (parsed) {
614
+ const { group, code } = parsed;
615
+
616
+ // Check if this is a scheduling error
617
+ if (errors.isSchedulingError(group, code) && this.#actorId) {
618
+ const schedulingError = await checkForSchedulingError(
619
+ group,
620
+ code,
621
+ this.#actorId,
622
+ this.#actorQuery,
623
+ this.#driver,
624
+ );
625
+ if (schedulingError) {
626
+ error = schedulingError;
627
+ } else {
628
+ error = new errors.ActorError(
629
+ group,
630
+ code,
631
+ `Connection closed: ${reason}`,
632
+ undefined,
633
+ );
634
+ }
635
+ } else {
636
+ error = new errors.ActorError(
637
+ group,
638
+ code,
639
+ `Connection closed: ${reason}`,
640
+ undefined,
641
+ );
642
+ }
643
+ } else {
644
+ // Default error for non-structured close reasons
645
+ error = new Error(
646
+ `${wasClean ? "Connection closed" : "Connection lost"} (code: ${closeEvent.code}, reason: ${reason})`,
647
+ );
648
+ }
649
+
650
+ this.#rejectPendingPromises(error, false);
651
+
652
+ // Dispatch to error handler if it's an ActorError
653
+ if (error instanceof errors.ActorError) {
654
+ this.#dispatchActorError(error);
655
+ }
618
656
 
619
657
  // Automatically reconnect if we were connected
620
658
  if (wasConnected) {
@@ -712,55 +750,6 @@ enc
712
750
  }
713
751
  }
714
752
 
715
- async #checkForSchedulingError(): Promise<errors.ActorSchedulingError | null> {
716
- if (!this.#actorId) return null;
717
-
718
- // Extract name from the query (it's nested in one of the variant properties)
719
- const query = this.#actorQuery;
720
- const name =
721
- "getForId" in query
722
- ? query.getForId.name
723
- : "getForKey" in query
724
- ? query.getForKey.name
725
- : "getOrCreateForKey" in query
726
- ? query.getOrCreateForKey.name
727
- : "create" in query
728
- ? query.create.name
729
- : null;
730
-
731
- if (!name) return null;
732
-
733
- try {
734
- // Fetch actor details to check for scheduling errors
735
- const actor = await this.#driver.getForId({
736
- name,
737
- actorId: this.#actorId,
738
- });
739
-
740
- if (actor?.error) {
741
- logger().info({
742
- msg: "found actor scheduling error",
743
- actorId: this.#actorId,
744
- error: actor.error,
745
- });
746
- return new errors.ActorSchedulingError(
747
- this.#actorId,
748
- actor.error,
749
- );
750
- }
751
- } catch (err) {
752
- // If we can't fetch actor details, just return null
753
- // and fall back to the generic error
754
- logger().warn({
755
- msg: "failed to fetch actor details for scheduling error check",
756
- actorId: this.#actorId,
757
- error: stringifyError(err),
758
- });
759
- }
760
-
761
- return null;
762
- }
763
-
764
753
  #addEventSubscription<Args extends Array<unknown>>(
765
754
  eventName: string,
766
755
  callback: (...args: Args) => void,
@@ -25,9 +25,9 @@ import {
25
25
  import { bufferToArrayBuffer } from "@/utils";
26
26
  import type { ActorDefinitionActions } from "./actor-common";
27
27
  import { type ActorConn, ActorConnRaw } from "./actor-conn";
28
- import { queryActor } from "./actor-query";
28
+ import { checkForSchedulingError, queryActor } from "./actor-query";
29
29
  import { type ClientRaw, CREATE_ACTOR_CONN_PROXY } from "./client";
30
- import { ActorError } from "./errors";
30
+ import { ActorError, isSchedulingError } from "./errors";
31
31
  import { logger } from "./log";
32
32
  import { rawHttpFetch, rawWebSocket } from "./raw-utils";
33
33
  import { sendHttpRequest } from "./utils";
@@ -81,22 +81,17 @@ export class ActorHandleRaw {
81
81
  args: Args;
82
82
  signal?: AbortSignal;
83
83
  }): Promise<Response> {
84
- // return await this.#driver.action<Args, Response>(
85
- // undefined,
86
- // this.#actorQuery,
87
- // this.#encodingKind,
88
- // this.#params,
89
- // opts.name,
90
- // opts.args,
91
- // { signal: opts.signal },
92
- // );
84
+ // Track actorId for scheduling error lookups
85
+ let actorId: string | undefined;
86
+
93
87
  try {
94
88
  // Get the actor ID
95
- const { actorId } = await queryActor(
89
+ const result = await queryActor(
96
90
  undefined,
97
91
  this.#actorQuery,
98
92
  this.#driver,
99
93
  );
94
+ actorId = result.actorId;
100
95
  logger().debug({ msg: "found actor for action", actorId });
101
96
  invariant(actorId, "Missing actor ID");
102
97
 
@@ -159,6 +154,21 @@ export class ActorHandleRaw {
159
154
  {},
160
155
  true,
161
156
  );
157
+
158
+ // Check if this is a scheduling error and try to get more details
159
+ if (actorId && isSchedulingError(group, code)) {
160
+ const schedulingError = await checkForSchedulingError(
161
+ group,
162
+ code,
163
+ actorId,
164
+ this.#actorQuery,
165
+ this.#driver,
166
+ );
167
+ if (schedulingError) {
168
+ throw schedulingError;
169
+ }
170
+ }
171
+
162
172
  throw new ActorError(group, code, message, metadata);
163
173
  }
164
174
  }
@@ -229,9 +239,7 @@ export class ActorHandleRaw {
229
239
  }
230
240
 
231
241
  /**
232
- * Resolves the actor to get its unique actor ID
233
- *
234
- * @returns {Promise<string>} - A promise that resolves to the actor's ID
242
+ * Resolves the actor to get its unique actor ID.
235
243
  */
236
244
  async resolve({ signal }: { signal?: AbortSignal } = {}): Promise<string> {
237
245
  if (
@@ -267,6 +275,18 @@ export class ActorHandleRaw {
267
275
  assertUnreachable(this.#actorQuery);
268
276
  }
269
277
  }
278
+
279
+ /**
280
+ * Returns the raw URL for routing traffic to the actor.
281
+ */
282
+ async getGatewayUrl(): Promise<string> {
283
+ const { actorId } = await queryActor(
284
+ undefined,
285
+ this.#actorQuery,
286
+ this.#driver,
287
+ );
288
+ return await this.#driver.buildGatewayUrl(actorId);
289
+ }
270
290
  }
271
291
 
272
292
  /**
@@ -1,7 +1,9 @@
1
1
  import type { Context as HonoContext } from "hono";
2
2
  import * as errors from "@/actor/errors";
3
+ import { stringifyError } from "@/common/utils";
3
4
  import type { ManagerDriver } from "@/driver-helpers/mod";
4
5
  import type { ActorQuery } from "@/manager/protocol/query";
6
+ import { ActorSchedulingError } from "./errors";
5
7
  import { logger } from "./log";
6
8
 
7
9
  /**
@@ -63,3 +65,48 @@ export async function queryActor(
63
65
  logger().debug({ msg: "actor query result", actorId: actorOutput.actorId });
64
66
  return { actorId: actorOutput.actorId };
65
67
  }
68
+
69
+ /**
70
+ * Extract the actor name from a query.
71
+ */
72
+ export function getActorNameFromQuery(query: ActorQuery): string {
73
+ if ("getForId" in query) return query.getForId.name;
74
+ if ("getForKey" in query) return query.getForKey.name;
75
+ if ("getOrCreateForKey" in query) return query.getOrCreateForKey.name;
76
+ if ("create" in query) return query.create.name;
77
+ throw new errors.InvalidRequest("Invalid query format");
78
+ }
79
+
80
+ /**
81
+ * Fetch actor details and check for scheduling errors.
82
+ */
83
+ export async function checkForSchedulingError(
84
+ group: string,
85
+ code: string,
86
+ actorId: string,
87
+ query: ActorQuery,
88
+ driver: ManagerDriver,
89
+ ): Promise<ActorSchedulingError | null> {
90
+ const name = getActorNameFromQuery(query);
91
+
92
+ try {
93
+ const actor = await driver.getForId({ name, actorId });
94
+
95
+ if (actor?.error) {
96
+ logger().info({
97
+ msg: "found actor scheduling error",
98
+ actorId,
99
+ error: actor.error,
100
+ });
101
+ return new ActorSchedulingError(group, code, actorId, actor.error);
102
+ }
103
+ } catch (err) {
104
+ logger().warn({
105
+ msg: "failed to fetch actor details for scheduling error check",
106
+ actorId,
107
+ error: stringifyError(err),
108
+ });
109
+ }
110
+
111
+ return null;
112
+ }