rivetkit 3.0.0-preview.3 → 3.0.0-preview.5
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/browser/client.js +49 -28
- package/dist/browser/client.js.map +1 -1
- package/dist/browser/inspector/client.js +1 -1
- package/dist/browser/inspector/client.js.map +1 -1
- package/dist/tsup/{chunk-KCWRKXTY.js → chunk-AW7C2XT5.js} +2 -2
- package/dist/tsup/{chunk-JWI6YJEA.js → chunk-BAXQGOBL.js} +2 -2
- package/dist/tsup/{chunk-4WDX5ZI7.cjs → chunk-CFGRIV62.cjs} +2 -2
- package/dist/tsup/{chunk-4WDX5ZI7.cjs.map → chunk-CFGRIV62.cjs.map} +1 -1
- package/dist/tsup/{chunk-7KES6QNE.cjs → chunk-CLY73DEM.cjs} +3 -3
- package/dist/tsup/{chunk-7KES6QNE.cjs.map → chunk-CLY73DEM.cjs.map} +1 -1
- package/dist/tsup/{chunk-KMYBA4P5.cjs → chunk-EON5X6B7.cjs} +3 -3
- package/dist/tsup/{chunk-KMYBA4P5.cjs.map → chunk-EON5X6B7.cjs.map} +1 -1
- package/dist/tsup/{chunk-2VXPVVNW.js → chunk-EOUH7MP7.js} +54 -33
- package/dist/tsup/chunk-EOUH7MP7.js.map +1 -0
- package/dist/tsup/{chunk-BCX5V7NI.cjs → chunk-GJUWI47D.cjs} +191 -170
- package/dist/tsup/chunk-GJUWI47D.cjs.map +1 -0
- package/dist/tsup/{chunk-PJ4PORWE.cjs → chunk-KEHBPXZE.cjs} +3 -3
- package/dist/tsup/{chunk-PJ4PORWE.cjs.map → chunk-KEHBPXZE.cjs.map} +1 -1
- package/dist/tsup/{chunk-HDQQVBOG.js → chunk-KJM23DKG.js} +2 -2
- package/dist/tsup/{chunk-JXO5QVDX.js → chunk-KKSIGBUN.js} +2 -2
- package/dist/tsup/{chunk-JXO5QVDX.js.map → chunk-KKSIGBUN.js.map} +1 -1
- package/dist/tsup/{chunk-HRBZ4VW5.cjs → chunk-MVDTFTCY.cjs} +7 -7
- package/dist/tsup/{chunk-HRBZ4VW5.cjs.map → chunk-MVDTFTCY.cjs.map} +1 -1
- package/dist/tsup/{chunk-B6U5V6WE.cjs → chunk-PHFXQQGE.cjs} +4 -4
- package/dist/tsup/{chunk-B6U5V6WE.cjs.map → chunk-PHFXQQGE.cjs.map} +1 -1
- package/dist/tsup/{chunk-A447S5YG.js → chunk-SJUGJ5DT.js} +2 -2
- package/dist/tsup/{chunk-AGQNNAZ2.js → chunk-W4CGRSYM.js} +3 -3
- package/dist/tsup/{chunk-IJGFSNGA.js → chunk-W5DDGJBO.js} +4 -4
- package/dist/tsup/{chunk-7Y4GHUAT.cjs → chunk-ZOFNA4SR.cjs} +10 -10
- package/dist/tsup/{chunk-7Y4GHUAT.cjs.map → chunk-ZOFNA4SR.cjs.map} +1 -1
- package/dist/tsup/client/mod.cjs +5 -5
- 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/db/drizzle.cjs +2 -2
- package/dist/tsup/db/drizzle.js +1 -1
- package/dist/tsup/db/mod.cjs +3 -3
- package/dist/tsup/db/mod.js +2 -2
- package/dist/tsup/inspector/mod.cjs +5 -5
- package/dist/tsup/inspector/mod.js +4 -4
- package/dist/tsup/inspector/workflow.cjs +4 -4
- package/dist/tsup/inspector/workflow.js +3 -3
- package/dist/tsup/mod.cjs +94 -90
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.js +15 -11
- package/dist/tsup/mod.js.map +1 -1
- package/dist/tsup/test/mod.cjs +6 -6
- package/dist/tsup/test/mod.js +4 -4
- package/dist/tsup/utils.cjs +2 -2
- package/dist/tsup/utils.js +1 -1
- package/dist/tsup/workflow/mod.cjs +7 -7
- package/dist/tsup/workflow/mod.cjs.map +1 -1
- package/dist/tsup/workflow/mod.js +4 -4
- package/package.json +9 -9
- package/src/client/actor-conn.ts +59 -33
- package/src/client/lifecycle-errors.ts +13 -0
- package/src/registry/config/index.ts +2 -1
- package/src/registry/index.ts +16 -3
- package/dist/tsup/chunk-2VXPVVNW.js.map +0 -1
- package/dist/tsup/chunk-BCX5V7NI.cjs.map +0 -1
- /package/dist/tsup/{chunk-KCWRKXTY.js.map → chunk-AW7C2XT5.js.map} +0 -0
- /package/dist/tsup/{chunk-JWI6YJEA.js.map → chunk-BAXQGOBL.js.map} +0 -0
- /package/dist/tsup/{chunk-HDQQVBOG.js.map → chunk-KJM23DKG.js.map} +0 -0
- /package/dist/tsup/{chunk-A447S5YG.js.map → chunk-SJUGJ5DT.js.map} +0 -0
- /package/dist/tsup/{chunk-AGQNNAZ2.js.map → chunk-W4CGRSYM.js.map} +0 -0
- /package/dist/tsup/{chunk-IJGFSNGA.js.map → chunk-W5DDGJBO.js.map} +0 -0
package/src/client/actor-conn.ts
CHANGED
|
@@ -50,9 +50,7 @@ import {
|
|
|
50
50
|
import { ACTOR_CONNS_SYMBOL, type ClientRaw } from "./client";
|
|
51
51
|
import * as errors from "./errors";
|
|
52
52
|
import { isRetryableLifecycleReconnectSignal } from "./lifecycle-errors";
|
|
53
|
-
import { retryAfterMs } from "./retry-budget";
|
|
54
53
|
import { logger } from "./log";
|
|
55
|
-
import { isInvalidToken } from "./token-provider";
|
|
56
54
|
import { outboundTelemetryHeaders } from "./outbound-telemetry";
|
|
57
55
|
import {
|
|
58
56
|
createQueueSender,
|
|
@@ -61,6 +59,8 @@ import {
|
|
|
61
59
|
type QueueSendResult,
|
|
62
60
|
type QueueSendWaitOptions,
|
|
63
61
|
} from "./queue";
|
|
62
|
+
import { retryAfterMs } from "./retry-budget";
|
|
63
|
+
import { isInvalidToken } from "./token-provider";
|
|
64
64
|
import {
|
|
65
65
|
type WebSocketMessage as ConnMessage,
|
|
66
66
|
messageLength,
|
|
@@ -160,6 +160,8 @@ export class ActorConnRaw {
|
|
|
160
160
|
#abortController = new AbortController();
|
|
161
161
|
|
|
162
162
|
#connStatus: ActorConnStatus = "idle";
|
|
163
|
+
/** The opening failure that stopped retries. Later actions must fail instead of queueing. */
|
|
164
|
+
#connectionOpenError?: errors.ActorError;
|
|
163
165
|
|
|
164
166
|
#actorId?: string;
|
|
165
167
|
#connId?: string;
|
|
@@ -356,6 +358,8 @@ export class ActorConnRaw {
|
|
|
356
358
|
`Invalid action call: expected an options object { name, args }, got ${typeof opts}. Use conn.actionName(...args) for the shorthand API.`,
|
|
357
359
|
);
|
|
358
360
|
}
|
|
361
|
+
if (this.#disposed) throw new errors.ActorConnDisposed();
|
|
362
|
+
if (this.#connectionOpenError) throw this.#connectionOpenError;
|
|
359
363
|
logger().debug({ msg: "action", name: opts.name, args: opts.args });
|
|
360
364
|
|
|
361
365
|
// If we have an active connection, use the websockactionId
|
|
@@ -466,6 +470,7 @@ export class ActorConnRaw {
|
|
|
466
470
|
}
|
|
467
471
|
|
|
468
472
|
#connectWithRetry() {
|
|
473
|
+
this.#connectionOpenError = undefined;
|
|
469
474
|
this.#setConnStatus("connecting");
|
|
470
475
|
|
|
471
476
|
// Attempt to reconnect indefinitely
|
|
@@ -523,6 +528,11 @@ export class ActorConnRaw {
|
|
|
523
528
|
// Wait for result
|
|
524
529
|
await this.#waitForOpen(this.#onOpenPromise.promise);
|
|
525
530
|
} catch (error) {
|
|
531
|
+
this.#abandonCurrentSocket(
|
|
532
|
+
isConnectionOpenTimeout(error)
|
|
533
|
+
? "Connect timeout"
|
|
534
|
+
: "Connection opening failed",
|
|
535
|
+
);
|
|
526
536
|
if (this.#shouldRetryConnectionOpenError(error)) {
|
|
527
537
|
throw error;
|
|
528
538
|
}
|
|
@@ -537,7 +547,7 @@ export class ActorConnRaw {
|
|
|
537
547
|
{ error: stringifyError(error) },
|
|
538
548
|
);
|
|
539
549
|
|
|
540
|
-
this.#
|
|
550
|
+
this.#connectionOpenError = actorError;
|
|
541
551
|
this.#rejectPendingPromises(actorError, false);
|
|
542
552
|
this.#dispatchActorError(actorError);
|
|
543
553
|
this.#setConnStatus("idle");
|
|
@@ -573,29 +583,24 @@ export class ActorConnRaw {
|
|
|
573
583
|
});
|
|
574
584
|
try {
|
|
575
585
|
await Promise.race([open, timedOut]);
|
|
576
|
-
} catch (error) {
|
|
577
|
-
if (isConnectionOpenTimeout(error)) {
|
|
578
|
-
this.#abandonCurrentSocket();
|
|
579
|
-
}
|
|
580
|
-
throw error;
|
|
581
586
|
} finally {
|
|
582
587
|
clearTimeout(timer);
|
|
583
588
|
}
|
|
584
589
|
}
|
|
585
590
|
|
|
586
|
-
#abandonCurrentSocket() {
|
|
591
|
+
#abandonCurrentSocket(reason: string) {
|
|
587
592
|
const ws = this.#websocket;
|
|
588
593
|
if (!ws) return;
|
|
589
594
|
this.#websocket = undefined;
|
|
590
595
|
this.#abandonedSockets.add(ws);
|
|
591
|
-
logger().
|
|
592
|
-
msg: "
|
|
596
|
+
logger().debug({
|
|
597
|
+
msg: "closing failed connection attempt",
|
|
593
598
|
connId: this.#connId,
|
|
594
599
|
readyState: ws.readyState,
|
|
595
|
-
|
|
600
|
+
reason,
|
|
596
601
|
});
|
|
597
602
|
try {
|
|
598
|
-
ws.close(1000,
|
|
603
|
+
ws.close(1000, reason);
|
|
599
604
|
} catch (err) {
|
|
600
605
|
logger().debug({
|
|
601
606
|
msg: "failed to close abandoned websocket",
|
|
@@ -611,6 +616,13 @@ export class ActorConnRaw {
|
|
|
611
616
|
if (isConnectionOpenTimeout(error)) {
|
|
612
617
|
return true;
|
|
613
618
|
}
|
|
619
|
+
if (
|
|
620
|
+
error instanceof errors.ActorError &&
|
|
621
|
+
error.group === "client" &&
|
|
622
|
+
error.code === "connection_open_interrupted"
|
|
623
|
+
) {
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
614
626
|
if (
|
|
615
627
|
error instanceof errors.ActorError &&
|
|
616
628
|
isInvalidToken(error.group, error.code) &&
|
|
@@ -692,16 +704,18 @@ export class ActorConnRaw {
|
|
|
692
704
|
});
|
|
693
705
|
this.#websocket = ws;
|
|
694
706
|
ws.addEventListener("open", () => {
|
|
695
|
-
if (this.#abandonedSockets.has(ws))
|
|
707
|
+
if (this.#websocket !== ws || this.#abandonedSockets.has(ws))
|
|
708
|
+
return;
|
|
696
709
|
logger().debug({
|
|
697
710
|
msg: "client websocket open",
|
|
698
711
|
connId: this.#connId,
|
|
699
712
|
});
|
|
700
713
|
});
|
|
701
714
|
ws.addEventListener("message", async (ev: { data: ConnMessage }) => {
|
|
702
|
-
if (this.#abandonedSockets.has(ws))
|
|
715
|
+
if (this.#websocket !== ws || this.#abandonedSockets.has(ws))
|
|
716
|
+
return;
|
|
703
717
|
try {
|
|
704
|
-
await this.#handleOnMessage(ev.data);
|
|
718
|
+
await this.#handleOnMessage(ev.data, ws);
|
|
705
719
|
} catch (err) {
|
|
706
720
|
logger().error({
|
|
707
721
|
msg: "error in websocket message handler",
|
|
@@ -710,7 +724,8 @@ export class ActorConnRaw {
|
|
|
710
724
|
}
|
|
711
725
|
});
|
|
712
726
|
ws.addEventListener("close", async (ev: Event | CloseEventLike) => {
|
|
713
|
-
if (this.#abandonedSockets.has(ws))
|
|
727
|
+
if (this.#websocket !== ws || this.#abandonedSockets.has(ws))
|
|
728
|
+
return;
|
|
714
729
|
try {
|
|
715
730
|
await this.#handleOnClose(ev);
|
|
716
731
|
} catch (err) {
|
|
@@ -721,7 +736,8 @@ export class ActorConnRaw {
|
|
|
721
736
|
}
|
|
722
737
|
});
|
|
723
738
|
ws.addEventListener("error", () => {
|
|
724
|
-
if (this.#abandonedSockets.has(ws))
|
|
739
|
+
if (this.#websocket !== ws || this.#abandonedSockets.has(ws))
|
|
740
|
+
return;
|
|
725
741
|
try {
|
|
726
742
|
this.#handleOnError();
|
|
727
743
|
} catch (err) {
|
|
@@ -803,7 +819,7 @@ export class ActorConnRaw {
|
|
|
803
819
|
}
|
|
804
820
|
|
|
805
821
|
/** Called by the onmessage event from drivers. */
|
|
806
|
-
async #handleOnMessage(data: any) {
|
|
822
|
+
async #handleOnMessage(data: any, socket: UniversalWebSocket) {
|
|
807
823
|
logger().trace({
|
|
808
824
|
msg: "received message",
|
|
809
825
|
dataType: typeof data,
|
|
@@ -812,6 +828,10 @@ export class ActorConnRaw {
|
|
|
812
828
|
});
|
|
813
829
|
|
|
814
830
|
const response = await this.#parseMessage(data as ConnMessage);
|
|
831
|
+
// Decoding can yield while this socket closes and another connects. Apply
|
|
832
|
+
// the message only while its socket still owns the connection.
|
|
833
|
+
if (this.#websocket !== socket || this.#abandonedSockets.has(socket))
|
|
834
|
+
return;
|
|
815
835
|
logger().trace(
|
|
816
836
|
getLogMessage()
|
|
817
837
|
? {
|
|
@@ -897,23 +917,19 @@ export class ActorConnRaw {
|
|
|
897
917
|
this.#actorResolutionState,
|
|
898
918
|
this.#driver,
|
|
899
919
|
);
|
|
920
|
+
if (
|
|
921
|
+
this.#websocket !== socket ||
|
|
922
|
+
this.#abandonedSockets.has(socket)
|
|
923
|
+
) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
900
926
|
if (schedulingError) {
|
|
901
927
|
errorToThrow = schedulingError;
|
|
902
928
|
}
|
|
903
929
|
}
|
|
904
930
|
|
|
905
|
-
// If we have an onOpenPromise, reject it with the error
|
|
906
|
-
if (this.#onOpenPromise) {
|
|
907
|
-
this.#onOpenPromise.reject(errorToThrow);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
931
|
this.#invalidateActorIfStale(group, code);
|
|
911
|
-
|
|
912
|
-
// Reject any in-flight requests
|
|
913
|
-
for (const [id, inFlight] of this.#actionsInFlight.entries()) {
|
|
914
|
-
inFlight.reject(errorToThrow);
|
|
915
|
-
this.#actionsInFlight.delete(id);
|
|
916
|
-
}
|
|
932
|
+
this.#rejectPendingPromises(errorToThrow, false);
|
|
917
933
|
|
|
918
934
|
this.#dispatchActorError(errorToThrow);
|
|
919
935
|
}
|
|
@@ -1047,9 +1063,17 @@ export class ActorConnRaw {
|
|
|
1047
1063
|
this.#invalidateActorIfStale(group, code);
|
|
1048
1064
|
} else {
|
|
1049
1065
|
// Default error for non-structured close reasons
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1066
|
+
const message = `${wasClean ? "Connection closed" : "Connection lost"} (code: ${closeEvent.code}, reason: ${reason})`;
|
|
1067
|
+
// A transport loss during the handshake can outlast an Engine
|
|
1068
|
+
// restart. Retry opening without replaying actions with unknown outcomes.
|
|
1069
|
+
error =
|
|
1070
|
+
!wasConnected && closeEvent.code === 1006
|
|
1071
|
+
? new errors.ActorError(
|
|
1072
|
+
"client",
|
|
1073
|
+
"connection_open_interrupted",
|
|
1074
|
+
message,
|
|
1075
|
+
)
|
|
1076
|
+
: new Error(message);
|
|
1053
1077
|
}
|
|
1054
1078
|
|
|
1055
1079
|
this.#rejectPendingPromises(error, false);
|
|
@@ -1071,6 +1095,8 @@ export class ActorConnRaw {
|
|
|
1071
1095
|
}
|
|
1072
1096
|
|
|
1073
1097
|
#rejectPendingPromises(error: Error, suppressUnhandled: boolean) {
|
|
1098
|
+
// Rejected actions must not survive in the queue and execute after a reconnect.
|
|
1099
|
+
this.#clearQueuedMessages();
|
|
1074
1100
|
if (this.#onOpenPromise) {
|
|
1075
1101
|
if (suppressUnhandled) {
|
|
1076
1102
|
this.#onOpenPromise.promise.catch(() => {});
|
|
@@ -77,6 +77,19 @@ function classifyActorError(
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
if (
|
|
81
|
+
(error.group === "ups" && error.code === "consumer_closed") ||
|
|
82
|
+
(error.group === "depot" && error.code === "request_deadline_exceeded")
|
|
83
|
+
) {
|
|
84
|
+
// The connection can recover, but an interrupted action may already have run.
|
|
85
|
+
return buildLifecycleBoundaryInfo(
|
|
86
|
+
"reconnect_only",
|
|
87
|
+
"actor_error",
|
|
88
|
+
error.message,
|
|
89
|
+
{ group: error.group, code: error.code },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
if (
|
|
81
94
|
error.group === "guard" &&
|
|
82
95
|
isRetryableGuardGatewayHttpError(error.code)
|
|
@@ -291,7 +291,8 @@ export const RegistryConfigSchema = z
|
|
|
291
291
|
* after calling `CoreRegistry::shutdown()`. Defaults to the
|
|
292
292
|
* engine-provided actor stop threshold once the node connects. A
|
|
293
293
|
* signal-driven shutdown of a managed local development engine waits
|
|
294
|
-
*
|
|
294
|
+
* four seconds by default so actors can acknowledge stopping before
|
|
295
|
+
* a file watcher restarts the process.
|
|
295
296
|
*
|
|
296
297
|
* Set a longer period when a complete graceful node drain is required.
|
|
297
298
|
*/
|
package/src/registry/index.ts
CHANGED
|
@@ -29,6 +29,13 @@ import type {
|
|
|
29
29
|
type ShutdownSignal = "SIGINT" | "SIGTERM";
|
|
30
30
|
|
|
31
31
|
const REGISTRY_READY_TIMEOUT_MS = 30_000;
|
|
32
|
+
// A managed Engine restart can wait for the old process to release storage and
|
|
33
|
+
// for its worker lease to expire before the replacement can register.
|
|
34
|
+
const LOCAL_ENGINE_READY_TIMEOUT_MS = 60_000;
|
|
35
|
+
// Core gives local actors three seconds to drain and acknowledge their stopped
|
|
36
|
+
// reports. Leave time to close the node connection after that, while staying
|
|
37
|
+
// below the five-second force-kill ceiling used by tsx watch.
|
|
38
|
+
const LOCAL_DEV_SHUTDOWN_GRACE_PERIOD_MS = 4_000;
|
|
32
39
|
|
|
33
40
|
// Serverless is disabled for v3; keep the implementation for a later release.
|
|
34
41
|
function rejectServerless(): void {
|
|
@@ -789,7 +796,7 @@ export class Registry<A extends RegistryActors> {
|
|
|
789
796
|
let gracePeriodMs = config.shutdown?.gracePeriodMs;
|
|
790
797
|
if (gracePeriodMs === undefined) {
|
|
791
798
|
gracePeriodMs = localDevSignal
|
|
792
|
-
?
|
|
799
|
+
? LOCAL_DEV_SHUTDOWN_GRACE_PERIOD_MS
|
|
793
800
|
: ((await this.#actorStopThresholdMs(
|
|
794
801
|
modeAPromise ?? modeBPromise,
|
|
795
802
|
)) ?? 30 * 60 * 1000);
|
|
@@ -942,16 +949,22 @@ export class Registry<A extends RegistryActors> {
|
|
|
942
949
|
throw new Error("registry envoy startup did not initialize");
|
|
943
950
|
}
|
|
944
951
|
|
|
952
|
+
const readyTimeoutMs =
|
|
953
|
+
config.engineSpawn &&
|
|
954
|
+
config.endpoint !== undefined &&
|
|
955
|
+
isLocalEngineEndpoint(config.endpoint)
|
|
956
|
+
? LOCAL_ENGINE_READY_TIMEOUT_MS
|
|
957
|
+
: REGISTRY_READY_TIMEOUT_MS;
|
|
945
958
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
946
959
|
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
947
960
|
timeout = setTimeout(
|
|
948
961
|
() =>
|
|
949
962
|
reject(
|
|
950
963
|
new Error(
|
|
951
|
-
`RivetKit registry did not register with the Engine within ${
|
|
964
|
+
`RivetKit registry did not register with the Engine within ${readyTimeoutMs}ms`,
|
|
952
965
|
),
|
|
953
966
|
),
|
|
954
|
-
|
|
967
|
+
readyTimeoutMs,
|
|
955
968
|
);
|
|
956
969
|
timeout.unref?.();
|
|
957
970
|
});
|