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.
- package/dist/tsup/{chunk-G4N7FZMM.cjs → chunk-7E3RWMR6.cjs} +195 -93
- package/dist/tsup/chunk-7E3RWMR6.cjs.map +1 -0
- package/dist/tsup/{chunk-J6TX5EFW.js → chunk-BQ36VTSB.js} +108 -6
- package/dist/tsup/chunk-BQ36VTSB.js.map +1 -0
- package/dist/tsup/{chunk-4V7MS7SO.cjs → chunk-C64FV764.cjs} +3 -3
- package/dist/tsup/{chunk-4V7MS7SO.cjs.map → chunk-C64FV764.cjs.map} +1 -1
- package/dist/tsup/{chunk-XI335ZED.js → chunk-CDK6DRO2.js} +6 -4
- package/dist/tsup/chunk-CDK6DRO2.js.map +1 -0
- package/dist/tsup/{chunk-LYYTV7DN.cjs → chunk-DY4H3ASE.cjs} +50 -46
- package/dist/tsup/chunk-DY4H3ASE.cjs.map +1 -0
- package/dist/tsup/{chunk-B6BP74X3.cjs → chunk-KMYFL3OL.cjs} +318 -92
- package/dist/tsup/chunk-KMYFL3OL.cjs.map +1 -0
- package/dist/tsup/{chunk-22NKW7F5.cjs → chunk-MZPYVTVG.cjs} +9 -9
- package/dist/tsup/{chunk-22NKW7F5.cjs.map → chunk-MZPYVTVG.cjs.map} +1 -1
- package/dist/tsup/{chunk-RBA5AQTB.js → chunk-OJZRCEIA.js} +5 -5
- package/dist/tsup/{chunk-RXA3ZMCL.js → chunk-PHCD25XO.js} +2 -2
- package/dist/tsup/{chunk-5XGZXH74.js → chunk-PVKUXMOA.js} +264 -38
- package/dist/tsup/chunk-PVKUXMOA.js.map +1 -0
- package/dist/tsup/{chunk-FIUSIG6J.js → chunk-T7IPDBWH.js} +8 -4
- package/dist/tsup/{chunk-FIUSIG6J.js.map → chunk-T7IPDBWH.js.map} +1 -1
- package/dist/tsup/{chunk-5VVIFC6M.cjs → chunk-UAX5E3EU.cjs} +443 -369
- package/dist/tsup/chunk-UAX5E3EU.cjs.map +1 -0
- package/dist/tsup/{chunk-X5IX3YPO.cjs → chunk-X72X7I7T.cjs} +6 -4
- package/dist/tsup/chunk-X72X7I7T.cjs.map +1 -0
- package/dist/tsup/{chunk-ZQBSQ6H3.js → chunk-XU74APB4.js} +208 -134
- package/dist/tsup/chunk-XU74APB4.js.map +1 -0
- package/dist/tsup/client/mod.cjs +5 -5
- package/dist/tsup/client/mod.d.cts +3 -3
- package/dist/tsup/client/mod.d.ts +3 -3
- package/dist/tsup/client/mod.js +4 -4
- package/dist/tsup/common/log.cjs +2 -2
- package/dist/tsup/common/log.js +1 -1
- package/dist/tsup/common/websocket.cjs +3 -3
- package/dist/tsup/common/websocket.js +2 -2
- package/dist/tsup/{config--NjwiYlS.d.cts → config-BuBlMs6C.d.cts} +238 -60
- package/dist/tsup/{config-CRuzI6n4.d.ts → config-CBwo4ooA.d.ts} +238 -60
- package/dist/tsup/{driver-yKjYx9Yy.d.cts → driver-CPXmh8f8.d.cts} +1 -1
- package/dist/tsup/{driver-BcmckRaF.d.ts → driver-DxWa6HUO.d.ts} +1 -1
- package/dist/tsup/driver-helpers/mod.cjs +3 -3
- package/dist/tsup/driver-helpers/mod.d.cts +2 -2
- package/dist/tsup/driver-helpers/mod.d.ts +2 -2
- package/dist/tsup/driver-helpers/mod.js +2 -2
- package/dist/tsup/driver-test-suite/mod.cjs +81 -35
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +2 -2
- package/dist/tsup/driver-test-suite/mod.d.ts +2 -2
- package/dist/tsup/driver-test-suite/mod.js +407 -361
- package/dist/tsup/driver-test-suite/mod.js.map +1 -1
- package/dist/tsup/{kv-CTM8sCvx.d.cts → keys-Chhy4ylv.d.cts} +1 -0
- package/dist/tsup/{kv-CTM8sCvx.d.ts → keys-Chhy4ylv.d.ts} +1 -0
- package/dist/tsup/mod.cjs +19 -7
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +5 -5
- package/dist/tsup/mod.d.ts +5 -5
- package/dist/tsup/mod.js +18 -6
- package/dist/tsup/test/mod.cjs +7 -7
- package/dist/tsup/test/mod.d.cts +1 -1
- package/dist/tsup/test/mod.d.ts +1 -1
- package/dist/tsup/test/mod.js +6 -6
- package/dist/tsup/utils.cjs +2 -2
- package/dist/tsup/utils.js +1 -1
- package/package.json +6 -4
- package/src/actor/config.ts +198 -2
- package/src/actor/contexts/base/actor.ts +12 -0
- package/src/actor/instance/connection-manager.ts +1 -1
- package/src/actor/instance/keys.ts +29 -0
- package/src/actor/instance/kv.ts +240 -14
- package/src/actor/instance/mod.ts +5 -4
- package/src/actor/instance/state-manager.ts +1 -1
- package/src/actor/mod.ts +2 -1
- package/src/actor/router-websocket-endpoints.ts +2 -1
- package/src/client/actor-conn.ts +70 -81
- package/src/client/actor-handle.ts +35 -15
- package/src/client/actor-query.ts +47 -0
- package/src/client/errors.ts +22 -58
- package/src/client/mod.ts +1 -1
- package/src/client/utils.ts +33 -0
- package/src/driver-helpers/utils.ts +1 -1
- package/src/driver-test-suite/mod.ts +3 -0
- package/src/driver-test-suite/test-inline-client-driver.ts +3 -0
- package/src/driver-test-suite/tests/actor-kv.ts +44 -0
- package/src/driver-test-suite/utils.ts +4 -0
- package/src/drivers/engine/actor-driver.ts +3 -3
- package/src/drivers/file-system/manager.ts +5 -0
- package/src/manager/driver.ts +8 -3
- package/src/manager-api/actors.ts +1 -20
- package/src/registry/config/index.ts +68 -0
- package/src/remote-manager-driver/actor-http-client.ts +5 -3
- package/src/remote-manager-driver/actor-websocket-client.ts +18 -7
- package/src/remote-manager-driver/mod.ts +21 -1
- package/src/serverless/router.test.ts +166 -0
- package/src/serverless/router.ts +58 -5
- package/src/utils/env-vars.ts +4 -1
- package/dist/tsup/chunk-5VVIFC6M.cjs.map +0 -1
- package/dist/tsup/chunk-5XGZXH74.js.map +0 -1
- package/dist/tsup/chunk-B6BP74X3.cjs.map +0 -1
- package/dist/tsup/chunk-G4N7FZMM.cjs.map +0 -1
- package/dist/tsup/chunk-J6TX5EFW.js.map +0 -1
- package/dist/tsup/chunk-LYYTV7DN.cjs.map +0 -1
- package/dist/tsup/chunk-X5IX3YPO.cjs.map +0 -1
- package/dist/tsup/chunk-XI335ZED.js.map +0 -1
- package/dist/tsup/chunk-ZQBSQ6H3.js.map +0 -1
- /package/dist/tsup/{chunk-RBA5AQTB.js.map → chunk-OJZRCEIA.js.map} +0 -0
- /package/dist/tsup/{chunk-RXA3ZMCL.js.map → chunk-PHCD25XO.js.map} +0 -0
package/src/actor/instance/kv.ts
CHANGED
|
@@ -1,15 +1,241 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 "./
|
|
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
|
-
|
|
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 =
|
|
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 "./
|
|
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/
|
|
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
|
-
|
|
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 };
|
package/src/client/actor-conn.ts
CHANGED
|
@@ -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
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
) {
|
|
530
|
-
|
|
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
|
-
|
|
541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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
|
+
}
|