rivetkit 2.0.24-rc.1 → 2.0.24
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/schemas/actor-persist/v2.ts +3 -3
- package/dist/schemas/actor-persist/v3.ts +274 -0
- package/dist/schemas/client-protocol/v2.ts +432 -0
- package/dist/schemas/file-system-driver/v2.ts +136 -0
- package/dist/tsup/actor/errors.cjs +2 -4
- package/dist/tsup/actor/errors.cjs.map +1 -1
- package/dist/tsup/actor/errors.d.cts +7 -10
- package/dist/tsup/actor/errors.d.ts +7 -10
- package/dist/tsup/actor/errors.js +9 -11
- package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.cts → actor-router-consts-DzI2szci.d.cts} +5 -9
- package/dist/tsup/{actor-router-consts-B3Lu87yJ.d.ts → actor-router-consts-DzI2szci.d.ts} +5 -9
- package/dist/tsup/{chunk-HHFKKVLR.cjs → chunk-3543NCSN.cjs} +45 -57
- package/dist/tsup/chunk-3543NCSN.cjs.map +1 -0
- package/dist/tsup/chunk-4SHILYS5.cjs +5694 -0
- package/dist/tsup/chunk-4SHILYS5.cjs.map +1 -0
- package/dist/tsup/{chunk-ZTH3KYFH.cjs → chunk-5BZO5XPS.cjs} +3 -3
- package/dist/tsup/{chunk-ZTH3KYFH.cjs.map → chunk-5BZO5XPS.cjs.map} +1 -1
- package/dist/tsup/{chunk-PLUN2NQT.js → chunk-BAIGSF64.js} +189 -187
- package/dist/tsup/chunk-BAIGSF64.js.map +1 -0
- package/dist/tsup/{chunk-SHVX2QUR.cjs → chunk-CHLZBSI2.cjs} +17 -17
- package/dist/tsup/chunk-CHLZBSI2.cjs.map +1 -0
- package/dist/tsup/chunk-D3SLADUD.cjs +512 -0
- package/dist/tsup/chunk-D3SLADUD.cjs.map +1 -0
- package/dist/tsup/{chunk-KSRXX3Z4.cjs → chunk-D6762AOA.cjs} +20 -25
- package/dist/tsup/chunk-D6762AOA.cjs.map +1 -0
- package/dist/tsup/{chunk-7L65NNWP.cjs → chunk-DLK5YCTN.cjs} +187 -185
- package/dist/tsup/chunk-DLK5YCTN.cjs.map +1 -0
- package/dist/tsup/{chunk-YBG6R7LX.js → chunk-DUJQWGYD.js} +3 -7
- package/dist/tsup/chunk-DUJQWGYD.js.map +1 -0
- package/dist/tsup/{chunk-CD33GT6Z.js → chunk-EIPANQMF.js} +2 -2
- package/dist/tsup/{chunk-2JYPS5YM.cjs → chunk-ESMTDP7G.cjs} +6 -6
- package/dist/tsup/chunk-ESMTDP7G.cjs.map +1 -0
- package/dist/tsup/{chunk-VHGY7PU5.cjs → chunk-FVAKREFB.cjs} +1900 -1737
- package/dist/tsup/chunk-FVAKREFB.cjs.map +1 -0
- package/dist/tsup/{chunk-BLK27ES3.js → chunk-I3XT7WOF.js} +44 -56
- package/dist/tsup/chunk-I3XT7WOF.js.map +1 -0
- package/dist/tsup/{chunk-YBHYXIP6.js → chunk-IMDS5T42.js} +3 -3
- package/dist/tsup/chunk-IMDS5T42.js.map +1 -0
- package/dist/tsup/{chunk-INNFK746.cjs → chunk-J3HZJF2P.cjs} +10 -14
- package/dist/tsup/chunk-J3HZJF2P.cjs.map +1 -0
- package/dist/tsup/{chunk-BYMKMOBS.js → chunk-MBBJUHSP.js} +1844 -1681
- package/dist/tsup/chunk-MBBJUHSP.js.map +1 -0
- package/dist/tsup/{chunk-BOMZS2TJ.js → chunk-MO5CB6MD.js} +9 -9
- package/dist/tsup/chunk-MO5CB6MD.js.map +1 -0
- package/dist/tsup/chunk-OFOTPKAH.js +512 -0
- package/dist/tsup/chunk-OFOTPKAH.js.map +1 -0
- package/dist/tsup/{chunk-G64QUEDJ.js → chunk-W6RDS6NW.js} +23 -28
- package/dist/tsup/chunk-W6RDS6NW.js.map +1 -0
- package/dist/tsup/{chunk-36JJ4IQB.cjs → chunk-YC5DUHPM.cjs} +4 -8
- package/dist/tsup/chunk-YC5DUHPM.cjs.map +1 -0
- package/dist/tsup/{chunk-FX7TWFQR.js → chunk-YC7YPM2T.js} +2 -6
- package/dist/tsup/chunk-YC7YPM2T.js.map +1 -0
- package/dist/tsup/{chunk-227FEWMB.js → chunk-ZSPU5R4C.js} +3322 -2251
- package/dist/tsup/chunk-ZSPU5R4C.js.map +1 -0
- package/dist/tsup/client/mod.cjs +9 -9
- package/dist/tsup/client/mod.d.cts +5 -7
- package/dist/tsup/client/mod.d.ts +5 -7
- package/dist/tsup/client/mod.js +8 -8
- package/dist/tsup/common/log.cjs +3 -3
- package/dist/tsup/common/log.js +2 -2
- package/dist/tsup/common/websocket.cjs +4 -4
- package/dist/tsup/common/websocket.js +3 -3
- package/dist/tsup/{conn-B3Vhbgnd.d.ts → config-BRDYDraU.d.cts} +1119 -1047
- package/dist/tsup/{conn-DJWL3nGx.d.cts → config-Bo-blHpJ.d.ts} +1119 -1047
- package/dist/tsup/driver-helpers/mod.cjs +5 -13
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
- package/dist/tsup/driver-helpers/mod.d.cts +11 -9
- package/dist/tsup/driver-helpers/mod.d.ts +11 -9
- package/dist/tsup/driver-helpers/mod.js +14 -22
- package/dist/tsup/driver-test-suite/mod.cjs +474 -303
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +6 -9
- package/dist/tsup/driver-test-suite/mod.d.ts +6 -9
- package/dist/tsup/driver-test-suite/mod.js +1085 -914
- package/dist/tsup/driver-test-suite/mod.js.map +1 -1
- package/dist/tsup/inspector/mod.cjs +6 -6
- package/dist/tsup/inspector/mod.d.cts +5 -7
- package/dist/tsup/inspector/mod.d.ts +5 -7
- package/dist/tsup/inspector/mod.js +5 -5
- package/dist/tsup/mod.cjs +10 -16
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +23 -25
- package/dist/tsup/mod.d.ts +23 -25
- package/dist/tsup/mod.js +17 -23
- package/dist/tsup/test/mod.cjs +11 -11
- package/dist/tsup/test/mod.d.cts +4 -6
- package/dist/tsup/test/mod.d.ts +4 -6
- package/dist/tsup/test/mod.js +10 -10
- package/dist/tsup/utils.cjs +3 -5
- package/dist/tsup/utils.cjs.map +1 -1
- package/dist/tsup/utils.d.cts +1 -2
- package/dist/tsup/utils.d.ts +1 -2
- package/dist/tsup/utils.js +2 -4
- package/package.json +13 -6
- package/src/actor/config.ts +56 -44
- package/src/actor/conn/driver.ts +61 -0
- package/src/actor/conn/drivers/http.ts +17 -0
- package/src/actor/conn/drivers/raw-request.ts +24 -0
- package/src/actor/conn/drivers/raw-websocket.ts +65 -0
- package/src/actor/conn/drivers/websocket.ts +129 -0
- package/src/actor/conn/mod.ts +232 -0
- package/src/actor/conn/persisted.ts +81 -0
- package/src/actor/conn/state-manager.ts +196 -0
- package/src/actor/contexts/action.ts +23 -0
- package/src/actor/{context.ts → contexts/actor.ts} +19 -8
- package/src/actor/contexts/conn-init.ts +31 -0
- package/src/actor/contexts/conn.ts +48 -0
- package/src/actor/contexts/create-conn-state.ts +13 -0
- package/src/actor/contexts/on-before-connect.ts +13 -0
- package/src/actor/contexts/on-connect.ts +22 -0
- package/src/actor/contexts/request.ts +48 -0
- package/src/actor/contexts/websocket.ts +48 -0
- package/src/actor/definition.ts +3 -3
- package/src/actor/driver.ts +36 -5
- package/src/actor/errors.ts +19 -24
- package/src/actor/instance/connection-manager.ts +465 -0
- package/src/actor/instance/event-manager.ts +292 -0
- package/src/actor/instance/kv.ts +15 -0
- package/src/actor/instance/mod.ts +1107 -0
- package/src/actor/instance/persisted.ts +67 -0
- package/src/actor/instance/schedule-manager.ts +349 -0
- package/src/actor/instance/state-manager.ts +502 -0
- package/src/actor/mod.ts +13 -16
- package/src/actor/protocol/old.ts +131 -43
- package/src/actor/protocol/serde.ts +19 -4
- package/src/actor/router-endpoints.ts +61 -586
- package/src/actor/router-websocket-endpoints.ts +408 -0
- package/src/actor/router.ts +63 -197
- package/src/actor/schedule.ts +1 -1
- package/src/client/actor-conn.ts +183 -249
- package/src/client/actor-handle.ts +29 -6
- package/src/client/client.ts +0 -4
- package/src/client/config.ts +1 -4
- package/src/client/mod.ts +0 -1
- package/src/client/raw-utils.ts +3 -3
- package/src/client/utils.ts +85 -39
- package/src/common/actor-router-consts.ts +5 -12
- package/src/common/{inline-websocket-adapter2.ts → inline-websocket-adapter.ts} +26 -48
- package/src/common/log.ts +1 -1
- package/src/common/router.ts +28 -17
- package/src/common/utils.ts +2 -0
- package/src/driver-helpers/mod.ts +7 -10
- package/src/driver-helpers/utils.ts +18 -9
- package/src/driver-test-suite/mod.ts +26 -50
- package/src/driver-test-suite/test-inline-client-driver.ts +27 -51
- package/src/driver-test-suite/tests/actor-conn-hibernation.ts +150 -0
- package/src/driver-test-suite/tests/actor-conn-state.ts +1 -4
- package/src/driver-test-suite/tests/actor-conn.ts +5 -9
- package/src/driver-test-suite/tests/actor-destroy.ts +294 -0
- package/src/driver-test-suite/tests/actor-driver.ts +0 -7
- package/src/driver-test-suite/tests/actor-handle.ts +12 -12
- package/src/driver-test-suite/tests/actor-metadata.ts +1 -1
- package/src/driver-test-suite/tests/manager-driver.ts +1 -1
- package/src/driver-test-suite/tests/raw-http-direct-registry.ts +8 -8
- package/src/driver-test-suite/tests/raw-http-request-properties.ts +6 -5
- package/src/driver-test-suite/tests/raw-http.ts +5 -5
- package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +7 -7
- package/src/driver-test-suite/tests/request-access.ts +4 -4
- package/src/driver-test-suite/utils.ts +6 -10
- package/src/drivers/engine/actor-driver.ts +614 -424
- package/src/drivers/engine/mod.ts +0 -1
- package/src/drivers/file-system/actor.ts +24 -12
- package/src/drivers/file-system/global-state.ts +427 -37
- package/src/drivers/file-system/manager.ts +71 -83
- package/src/drivers/file-system/mod.ts +3 -0
- package/src/drivers/file-system/utils.ts +18 -8
- package/src/engine-process/mod.ts +38 -38
- package/src/inspector/utils.ts +7 -5
- package/src/manager/driver.ts +11 -4
- package/src/manager/gateway.ts +4 -29
- package/src/manager/protocol/mod.ts +0 -2
- package/src/manager/protocol/query.ts +0 -4
- package/src/manager/router.ts +67 -64
- package/src/manager-api/actors.ts +13 -0
- package/src/mod.ts +1 -3
- package/src/registry/mod.ts +20 -20
- package/src/registry/serve.ts +9 -14
- package/src/remote-manager-driver/actor-websocket-client.ts +1 -16
- package/src/remote-manager-driver/api-endpoints.ts +13 -1
- package/src/remote-manager-driver/api-utils.ts +8 -0
- package/src/remote-manager-driver/metadata.ts +58 -0
- package/src/remote-manager-driver/mod.ts +47 -62
- package/src/remote-manager-driver/ws-proxy.ts +1 -1
- package/src/schemas/actor-persist/mod.ts +1 -1
- package/src/schemas/actor-persist/versioned.ts +56 -31
- package/src/schemas/client-protocol/mod.ts +1 -1
- package/src/schemas/client-protocol/versioned.ts +41 -21
- package/src/schemas/client-protocol-zod/mod.ts +103 -0
- package/src/schemas/file-system-driver/mod.ts +1 -1
- package/src/schemas/file-system-driver/versioned.ts +42 -19
- package/src/serde.ts +33 -11
- package/src/test/mod.ts +7 -3
- package/src/utils/node.ts +173 -0
- package/src/utils.ts +0 -4
- package/dist/tsup/chunk-227FEWMB.js.map +0 -1
- package/dist/tsup/chunk-2JYPS5YM.cjs.map +0 -1
- package/dist/tsup/chunk-36JJ4IQB.cjs.map +0 -1
- package/dist/tsup/chunk-7L65NNWP.cjs.map +0 -1
- package/dist/tsup/chunk-BLK27ES3.js.map +0 -1
- package/dist/tsup/chunk-BOMZS2TJ.js.map +0 -1
- package/dist/tsup/chunk-BYMKMOBS.js.map +0 -1
- package/dist/tsup/chunk-FX7TWFQR.js.map +0 -1
- package/dist/tsup/chunk-G64QUEDJ.js.map +0 -1
- package/dist/tsup/chunk-HHFKKVLR.cjs.map +0 -1
- package/dist/tsup/chunk-INNFK746.cjs.map +0 -1
- package/dist/tsup/chunk-KSRXX3Z4.cjs.map +0 -1
- package/dist/tsup/chunk-O44LFKSB.cjs +0 -4623
- package/dist/tsup/chunk-O44LFKSB.cjs.map +0 -1
- package/dist/tsup/chunk-PLUN2NQT.js.map +0 -1
- package/dist/tsup/chunk-S4UJG7ZE.js +0 -1119
- package/dist/tsup/chunk-S4UJG7ZE.js.map +0 -1
- package/dist/tsup/chunk-SHVX2QUR.cjs.map +0 -1
- package/dist/tsup/chunk-VFB23BYZ.cjs +0 -1119
- package/dist/tsup/chunk-VFB23BYZ.cjs.map +0 -1
- package/dist/tsup/chunk-VHGY7PU5.cjs.map +0 -1
- package/dist/tsup/chunk-YBG6R7LX.js.map +0 -1
- package/dist/tsup/chunk-YBHYXIP6.js.map +0 -1
- package/src/actor/action.ts +0 -178
- package/src/actor/conn-drivers.ts +0 -216
- package/src/actor/conn-socket.ts +0 -8
- package/src/actor/conn.ts +0 -272
- package/src/actor/instance.ts +0 -2336
- package/src/actor/persisted.ts +0 -49
- package/src/actor/unstable-react.ts +0 -110
- package/src/driver-test-suite/tests/actor-reconnect.ts +0 -170
- package/src/drivers/engine/kv.ts +0 -3
- package/src/manager/hono-websocket-adapter.ts +0 -393
- /package/dist/tsup/{chunk-CD33GT6Z.js.map → chunk-EIPANQMF.js.map} +0 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { idToStr } from "@rivetkit/engine-runner";
|
|
2
|
+
import onChange from "on-change";
|
|
3
|
+
import { isCborSerializable, stringifyError } from "@/common/utils";
|
|
4
|
+
import {
|
|
5
|
+
ACTOR_VERSIONED,
|
|
6
|
+
CONN_VERSIONED,
|
|
7
|
+
} from "@/schemas/actor-persist/versioned";
|
|
8
|
+
import { promiseWithResolvers, SinglePromiseQueue } from "@/utils";
|
|
9
|
+
import { type AnyConn, CONN_STATE_MANAGER_SYMBOL } from "../conn/mod";
|
|
10
|
+
import { convertConnToBarePersistedConn } from "../conn/persisted";
|
|
11
|
+
import type { ActorDriver } from "../driver";
|
|
12
|
+
import * as errors from "../errors";
|
|
13
|
+
import { isConnStatePath, isStatePath } from "../utils";
|
|
14
|
+
import { KEYS, makeConnKey } from "./kv";
|
|
15
|
+
import type { ActorInstance } from "./mod";
|
|
16
|
+
import { convertActorToBarePersisted, type PersistedActor } from "./persisted";
|
|
17
|
+
|
|
18
|
+
export interface SaveStateOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Forces the state to be saved immediately. This function will return when the state has saved successfully.
|
|
21
|
+
*/
|
|
22
|
+
immediate?: boolean;
|
|
23
|
+
/** Bypass ready check for stopping. */
|
|
24
|
+
allowStoppingState?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum time in milliseconds to wait before forcing a save.
|
|
27
|
+
*
|
|
28
|
+
* If a save is already scheduled to occur later than this deadline, it will be rescheduled earlier.
|
|
29
|
+
*/
|
|
30
|
+
maxWait?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Manages actor state persistence, proxying, and synchronization.
|
|
35
|
+
* Handles automatic state change detection and throttled persistence to KV storage.
|
|
36
|
+
*/
|
|
37
|
+
export class StateManager<S, CP, CS, I> {
|
|
38
|
+
#actor: ActorInstance<S, CP, CS, any, I, any>;
|
|
39
|
+
#actorDriver: ActorDriver;
|
|
40
|
+
|
|
41
|
+
// State tracking
|
|
42
|
+
#persist!: PersistedActor<S, I>;
|
|
43
|
+
#persistRaw!: PersistedActor<S, I>;
|
|
44
|
+
#persistChanged = false;
|
|
45
|
+
#isInOnStateChange = false;
|
|
46
|
+
|
|
47
|
+
// Save management
|
|
48
|
+
#persistWriteQueue = new SinglePromiseQueue();
|
|
49
|
+
#lastSaveTime = 0;
|
|
50
|
+
#pendingSaveTimeout?: NodeJS.Timeout;
|
|
51
|
+
#pendingSaveScheduledTimestamp?: number;
|
|
52
|
+
#onPersistSavedPromise?: ReturnType<typeof promiseWithResolvers<void>>;
|
|
53
|
+
|
|
54
|
+
// Configuration
|
|
55
|
+
#config: any; // ActorConfig type
|
|
56
|
+
#stateSaveInterval: number;
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
actor: ActorInstance<S, CP, CS, any, I, any>,
|
|
60
|
+
actorDriver: ActorDriver,
|
|
61
|
+
config: any,
|
|
62
|
+
) {
|
|
63
|
+
this.#actor = actor;
|
|
64
|
+
this.#actorDriver = actorDriver;
|
|
65
|
+
this.#config = config;
|
|
66
|
+
this.#stateSaveInterval = config.options.stateSaveInterval || 100;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// MARK: - Public API
|
|
70
|
+
|
|
71
|
+
get persist(): PersistedActor<S, I> {
|
|
72
|
+
return this.#persist;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get persistRaw(): PersistedActor<S, I> {
|
|
76
|
+
return this.#persistRaw;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get persistChanged(): boolean {
|
|
80
|
+
return this.#persistChanged;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get state(): S {
|
|
84
|
+
this.#validateStateEnabled();
|
|
85
|
+
return this.#persist.state;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
set state(value: S) {
|
|
89
|
+
this.#validateStateEnabled();
|
|
90
|
+
this.#persist.state = value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get stateEnabled(): boolean {
|
|
94
|
+
return "createState" in this.#config || "state" in this.#config;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// MARK: - Initialization
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Initializes state from persisted data or creates new state.
|
|
101
|
+
*/
|
|
102
|
+
async initializeState(persistData: PersistedActor<S, I>): Promise<void> {
|
|
103
|
+
if (!persistData.hasInitialized) {
|
|
104
|
+
// Create initial state
|
|
105
|
+
let stateData: unknown;
|
|
106
|
+
if (this.stateEnabled) {
|
|
107
|
+
this.#actor.rLog.info({ msg: "actor state initializing" });
|
|
108
|
+
|
|
109
|
+
if ("createState" in this.#config) {
|
|
110
|
+
stateData = await this.#config.createState(
|
|
111
|
+
this.#actor.actorContext,
|
|
112
|
+
persistData.input!,
|
|
113
|
+
);
|
|
114
|
+
} else if ("state" in this.#config) {
|
|
115
|
+
stateData = structuredClone(this.#config.state);
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Both 'createState' or 'state' were not defined",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
this.#actor.rLog.debug({ msg: "state not enabled" });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Update persisted data
|
|
126
|
+
persistData.state = stateData as S;
|
|
127
|
+
persistData.hasInitialized = true;
|
|
128
|
+
|
|
129
|
+
// Save initial state
|
|
130
|
+
//
|
|
131
|
+
// We don't use #savePersistInner because the actor is not fully
|
|
132
|
+
// initialized yet
|
|
133
|
+
const bareData = convertActorToBarePersisted<S, I>(persistData);
|
|
134
|
+
await this.#actorDriver.kvBatchPut(this.#actor.id, [
|
|
135
|
+
[
|
|
136
|
+
KEYS.PERSIST_DATA,
|
|
137
|
+
ACTOR_VERSIONED.serializeWithEmbeddedVersion(bareData),
|
|
138
|
+
],
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Initialize proxy
|
|
143
|
+
this.initPersistProxy(persistData);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Creates proxy for persist object that handles automatic state change detection.
|
|
148
|
+
*/
|
|
149
|
+
initPersistProxy(target: PersistedActor<S, I>) {
|
|
150
|
+
// Set raw persist object
|
|
151
|
+
this.#persistRaw = target;
|
|
152
|
+
|
|
153
|
+
// Validate serializability
|
|
154
|
+
if (target === null || typeof target !== "object") {
|
|
155
|
+
let invalidPath = "";
|
|
156
|
+
if (
|
|
157
|
+
!isCborSerializable(
|
|
158
|
+
target,
|
|
159
|
+
(path) => {
|
|
160
|
+
invalidPath = path;
|
|
161
|
+
},
|
|
162
|
+
"",
|
|
163
|
+
)
|
|
164
|
+
) {
|
|
165
|
+
throw new errors.InvalidStateType({ path: invalidPath });
|
|
166
|
+
}
|
|
167
|
+
return target;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Unsubscribe from old state
|
|
171
|
+
if (this.#persist) {
|
|
172
|
+
onChange.unsubscribe(this.#persist);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Listen for changes to automatically write state
|
|
176
|
+
this.#persist = onChange(
|
|
177
|
+
target,
|
|
178
|
+
(
|
|
179
|
+
path: string,
|
|
180
|
+
value: any,
|
|
181
|
+
_previousValue: any,
|
|
182
|
+
_applyData: any,
|
|
183
|
+
) => {
|
|
184
|
+
this.#handleStateChange(path, value);
|
|
185
|
+
},
|
|
186
|
+
{ ignoreDetached: true },
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// MARK: - State Persistence
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Forces the state to get saved.
|
|
194
|
+
*/
|
|
195
|
+
async saveState(opts: SaveStateOptions): Promise<void> {
|
|
196
|
+
this.#actor.assertReady(opts.allowStoppingState);
|
|
197
|
+
|
|
198
|
+
if (this.#persistChanged) {
|
|
199
|
+
if (opts.immediate) {
|
|
200
|
+
await this.#savePersistInner();
|
|
201
|
+
} else {
|
|
202
|
+
// Create promise for waiting
|
|
203
|
+
if (!this.#onPersistSavedPromise) {
|
|
204
|
+
this.#onPersistSavedPromise = promiseWithResolvers();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Save throttled
|
|
208
|
+
this.savePersistThrottled(opts.maxWait);
|
|
209
|
+
|
|
210
|
+
// Wait for save
|
|
211
|
+
await this.#onPersistSavedPromise?.promise;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Throttled save state method. Used to write to KV at a reasonable cadence.
|
|
218
|
+
*
|
|
219
|
+
* Passing a maxWait will override the stateSaveInterval with the min
|
|
220
|
+
* between that and the maxWait.
|
|
221
|
+
*/
|
|
222
|
+
savePersistThrottled(maxWait?: number) {
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
const timeSinceLastSave = now - this.#lastSaveTime;
|
|
225
|
+
|
|
226
|
+
// Calculate when the save should happen based on throttle interval
|
|
227
|
+
let saveDelay = Math.max(
|
|
228
|
+
0,
|
|
229
|
+
this.#stateSaveInterval - timeSinceLastSave,
|
|
230
|
+
);
|
|
231
|
+
if (maxWait !== undefined) {
|
|
232
|
+
saveDelay = Math.min(saveDelay, maxWait);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check if we need to reschedule the same timeout
|
|
236
|
+
if (
|
|
237
|
+
this.#pendingSaveTimeout !== undefined &&
|
|
238
|
+
this.#pendingSaveScheduledTimestamp !== undefined
|
|
239
|
+
) {
|
|
240
|
+
// Check if we have an earlier save deadline
|
|
241
|
+
const newScheduledTimestamp = now + saveDelay;
|
|
242
|
+
if (newScheduledTimestamp < this.#pendingSaveScheduledTimestamp) {
|
|
243
|
+
// Cancel existing timeout and reschedule
|
|
244
|
+
clearTimeout(this.#pendingSaveTimeout);
|
|
245
|
+
this.#pendingSaveTimeout = undefined;
|
|
246
|
+
this.#pendingSaveScheduledTimestamp = undefined;
|
|
247
|
+
} else {
|
|
248
|
+
// Current schedule is fine, don't reschedule
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (saveDelay > 0) {
|
|
254
|
+
// Schedule save
|
|
255
|
+
this.#pendingSaveScheduledTimestamp = now + saveDelay;
|
|
256
|
+
this.#pendingSaveTimeout = setTimeout(() => {
|
|
257
|
+
this.#pendingSaveTimeout = undefined;
|
|
258
|
+
this.#pendingSaveScheduledTimestamp = undefined;
|
|
259
|
+
this.#savePersistInner();
|
|
260
|
+
}, saveDelay);
|
|
261
|
+
} else {
|
|
262
|
+
// Save immediately
|
|
263
|
+
this.#savePersistInner();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Clears any pending save timeout.
|
|
269
|
+
*/
|
|
270
|
+
clearPendingSaveTimeout() {
|
|
271
|
+
if (this.#pendingSaveTimeout) {
|
|
272
|
+
clearTimeout(this.#pendingSaveTimeout);
|
|
273
|
+
this.#pendingSaveTimeout = undefined;
|
|
274
|
+
this.#pendingSaveScheduledTimestamp = undefined;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Waits for any pending write operations to complete.
|
|
280
|
+
*/
|
|
281
|
+
async waitForPendingWrites(): Promise<void> {
|
|
282
|
+
if (this.#persistWriteQueue.runningDrainLoop) {
|
|
283
|
+
await this.#persistWriteQueue.runningDrainLoop;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// MARK: - Private Helpers
|
|
288
|
+
|
|
289
|
+
#validateStateEnabled() {
|
|
290
|
+
if (!this.stateEnabled) {
|
|
291
|
+
throw new errors.StateNotEnabled();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#handleStateChange(path: string, value: any) {
|
|
296
|
+
const actorStatePath = isStatePath(path);
|
|
297
|
+
const connStatePath = isConnStatePath(path);
|
|
298
|
+
|
|
299
|
+
// Validate CBOR serializability
|
|
300
|
+
if (actorStatePath || connStatePath) {
|
|
301
|
+
let invalidPath = "";
|
|
302
|
+
if (
|
|
303
|
+
!isCborSerializable(
|
|
304
|
+
value,
|
|
305
|
+
(invalidPathPart) => {
|
|
306
|
+
invalidPath = invalidPathPart;
|
|
307
|
+
},
|
|
308
|
+
"",
|
|
309
|
+
)
|
|
310
|
+
) {
|
|
311
|
+
throw new errors.InvalidStateType({
|
|
312
|
+
path: path + (invalidPath ? `.${invalidPath}` : ""),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.#actor.rLog.debug({
|
|
318
|
+
msg: "onChange triggered, setting persistChanged=true",
|
|
319
|
+
path,
|
|
320
|
+
});
|
|
321
|
+
this.#persistChanged = true;
|
|
322
|
+
|
|
323
|
+
// Inform inspector about state changes
|
|
324
|
+
if (actorStatePath) {
|
|
325
|
+
this.#actor.inspector.emitter.emit(
|
|
326
|
+
"stateUpdated",
|
|
327
|
+
this.#persist.state,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Call onStateChange lifecycle hook
|
|
332
|
+
if (
|
|
333
|
+
actorStatePath &&
|
|
334
|
+
this.#config.onStateChange &&
|
|
335
|
+
this.#actor.isReady() &&
|
|
336
|
+
!this.#isInOnStateChange
|
|
337
|
+
) {
|
|
338
|
+
try {
|
|
339
|
+
this.#isInOnStateChange = true;
|
|
340
|
+
this.#config.onStateChange(
|
|
341
|
+
this.#actor.actorContext,
|
|
342
|
+
this.#persistRaw.state,
|
|
343
|
+
);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
this.#actor.rLog.error({
|
|
346
|
+
msg: "error in `_onStateChange`",
|
|
347
|
+
error: stringifyError(error),
|
|
348
|
+
});
|
|
349
|
+
} finally {
|
|
350
|
+
this.#isInOnStateChange = false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async #savePersistInner() {
|
|
356
|
+
this.#actor.rLog.info({
|
|
357
|
+
msg: "savePersistInner called",
|
|
358
|
+
persistChanged: this.#persistChanged,
|
|
359
|
+
connsWithPersistChangedSize:
|
|
360
|
+
this.#actor.connectionManager.connsWithPersistChanged.size,
|
|
361
|
+
connsWithPersistChangedIds: Array.from(
|
|
362
|
+
this.#actor.connectionManager.connsWithPersistChanged,
|
|
363
|
+
),
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
this.#lastSaveTime = Date.now();
|
|
368
|
+
|
|
369
|
+
// Check if either actor state or connections have changed
|
|
370
|
+
const hasChanges =
|
|
371
|
+
this.#persistChanged ||
|
|
372
|
+
this.#actor.connectionManager.connsWithPersistChanged.size > 0;
|
|
373
|
+
|
|
374
|
+
if (hasChanges) {
|
|
375
|
+
await this.#persistWriteQueue.enqueue(async () => {
|
|
376
|
+
this.#actor.rLog.debug({
|
|
377
|
+
msg: "saving persist",
|
|
378
|
+
actorChanged: this.#persistChanged,
|
|
379
|
+
connectionsChanged:
|
|
380
|
+
this.#actor.connectionManager
|
|
381
|
+
.connsWithPersistChanged.size,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const entries: Array<[Uint8Array, Uint8Array]> = [];
|
|
385
|
+
|
|
386
|
+
// Build actor entries
|
|
387
|
+
if (this.#persistChanged) {
|
|
388
|
+
this.#persistChanged = false;
|
|
389
|
+
const bareData = convertActorToBarePersisted<S, I>(
|
|
390
|
+
this.#persistRaw,
|
|
391
|
+
);
|
|
392
|
+
entries.push([
|
|
393
|
+
KEYS.PERSIST_DATA,
|
|
394
|
+
ACTOR_VERSIONED.serializeWithEmbeddedVersion(
|
|
395
|
+
bareData,
|
|
396
|
+
),
|
|
397
|
+
]);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Build connection entries
|
|
401
|
+
const connections: Array<AnyConn> = [];
|
|
402
|
+
for (const connId of this.#actor.connectionManager
|
|
403
|
+
.connsWithPersistChanged) {
|
|
404
|
+
const conn = this.#actor.conns.get(connId);
|
|
405
|
+
if (!conn) {
|
|
406
|
+
this.#actor.rLog.warn({
|
|
407
|
+
msg: "connection not found in conns map",
|
|
408
|
+
connId,
|
|
409
|
+
});
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const connStateManager =
|
|
414
|
+
conn[CONN_STATE_MANAGER_SYMBOL];
|
|
415
|
+
const hibernatableDataRaw =
|
|
416
|
+
connStateManager.hibernatableDataRaw;
|
|
417
|
+
if (!hibernatableDataRaw) {
|
|
418
|
+
this.#actor.log.warn({
|
|
419
|
+
msg: "missing raw hibernatable data for conn in getChangedConnectionsData",
|
|
420
|
+
connId: conn.id,
|
|
421
|
+
});
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.#actor.rLog.info({
|
|
426
|
+
msg: "persisting connection",
|
|
427
|
+
connId,
|
|
428
|
+
gatewayId: idToStr(hibernatableDataRaw.requestId),
|
|
429
|
+
requestId: idToStr(hibernatableDataRaw.requestId),
|
|
430
|
+
serverMessageIndex:
|
|
431
|
+
hibernatableDataRaw.serverMessageIndex,
|
|
432
|
+
clientMessageIndex:
|
|
433
|
+
hibernatableDataRaw.clientMessageIndex,
|
|
434
|
+
hasState: hibernatableDataRaw.state !== undefined,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const bareData = convertConnToBarePersistedConn<CP, CS>(
|
|
438
|
+
hibernatableDataRaw,
|
|
439
|
+
);
|
|
440
|
+
const connData =
|
|
441
|
+
CONN_VERSIONED.serializeWithEmbeddedVersion(
|
|
442
|
+
bareData,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
entries.push([makeConnKey(connId), connData]);
|
|
446
|
+
connections.push(conn);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
this.#actor.rLog.info({
|
|
450
|
+
msg: "prepared entries for kvBatchPut",
|
|
451
|
+
totalEntries: entries.length,
|
|
452
|
+
connectionEntries: connections.length,
|
|
453
|
+
connectionIds: connections.map((c) => c.id),
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Notify driver before persisting connections
|
|
457
|
+
if (this.#actorDriver.onBeforePersistConn) {
|
|
458
|
+
for (const conn of connections) {
|
|
459
|
+
this.#actorDriver.onBeforePersistConn(conn);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Clear changed connections
|
|
464
|
+
this.#actor.connectionManager.clearConnWithPersistChanged();
|
|
465
|
+
|
|
466
|
+
// Write data
|
|
467
|
+
this.#actor.rLog.info({
|
|
468
|
+
msg: "calling kvBatchPut",
|
|
469
|
+
actorId: this.#actor.id,
|
|
470
|
+
entriesCount: entries.length,
|
|
471
|
+
});
|
|
472
|
+
await this.#actorDriver.kvBatchPut(this.#actor.id, entries);
|
|
473
|
+
this.#actor.rLog.info({
|
|
474
|
+
msg: "kvBatchPut completed successfully",
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Notify driver after persisting connections
|
|
478
|
+
if (this.#actorDriver.onAfterPersistConn) {
|
|
479
|
+
for (const conn of connections) {
|
|
480
|
+
this.#actorDriver.onAfterPersistConn(conn);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
this.#actor.rLog.debug({ msg: "persist saved" });
|
|
485
|
+
});
|
|
486
|
+
} else {
|
|
487
|
+
this.#actor.rLog.info({
|
|
488
|
+
msg: "savePersistInner skipped - no changes",
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
this.#onPersistSavedPromise?.resolve();
|
|
493
|
+
} catch (error) {
|
|
494
|
+
this.#actor.rLog.error({
|
|
495
|
+
msg: "error saving persist",
|
|
496
|
+
error: stringifyError(error),
|
|
497
|
+
});
|
|
498
|
+
this.#onPersistSavedPromise?.reject(error);
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
package/src/actor/mod.ts
CHANGED
|
@@ -55,8 +55,8 @@ export function actor<
|
|
|
55
55
|
export type { Encoding } from "@/actor/protocol/serde";
|
|
56
56
|
export {
|
|
57
57
|
ALLOWED_PUBLIC_HEADERS,
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
PATH_CONNECT,
|
|
59
|
+
PATH_WEBSOCKET_PREFIX,
|
|
60
60
|
} from "@/common/actor-router-consts";
|
|
61
61
|
export type {
|
|
62
62
|
UniversalErrorEvent,
|
|
@@ -64,7 +64,6 @@ export type {
|
|
|
64
64
|
UniversalEventSource,
|
|
65
65
|
UniversalMessageEvent,
|
|
66
66
|
} from "@/common/eventsource-interface";
|
|
67
|
-
export type { UpgradeWebSocketArgs } from "@/common/inline-websocket-adapter2";
|
|
68
67
|
export type {
|
|
69
68
|
RivetCloseEvent,
|
|
70
69
|
RivetEvent,
|
|
@@ -72,15 +71,16 @@ export type {
|
|
|
72
71
|
UniversalWebSocket,
|
|
73
72
|
} from "@/common/websocket-interface";
|
|
74
73
|
export type { ActorKey } from "@/manager/protocol/query";
|
|
75
|
-
export type { ActionContext } from "./action";
|
|
76
74
|
export type * from "./config";
|
|
77
|
-
export type {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} from "./
|
|
83
|
-
export type {
|
|
75
|
+
export type { AnyConn, Conn } from "./conn/mod";
|
|
76
|
+
export type { ActionContext } from "./contexts/action";
|
|
77
|
+
export type { ActorContext } from "./contexts/actor";
|
|
78
|
+
export type { ConnInitContext } from "./contexts/conn-init";
|
|
79
|
+
export type { CreateConnStateContext } from "./contexts/create-conn-state";
|
|
80
|
+
export type { OnBeforeConnectContext } from "./contexts/on-before-connect";
|
|
81
|
+
export type { OnConnectContext } from "./contexts/on-connect";
|
|
82
|
+
export type { RequestContext } from "./contexts/request";
|
|
83
|
+
export type { WebSocketContext } from "./contexts/websocket";
|
|
84
84
|
export type {
|
|
85
85
|
ActionContextOf,
|
|
86
86
|
ActorContextOf,
|
|
@@ -89,12 +89,9 @@ export type {
|
|
|
89
89
|
} from "./definition";
|
|
90
90
|
export { lookupInRegistry } from "./definition";
|
|
91
91
|
export { UserError, type UserErrorOptions } from "./errors";
|
|
92
|
-
export type { AnyActorInstance } from "./instance";
|
|
92
|
+
export type { AnyActorInstance } from "./instance/mod";
|
|
93
93
|
export {
|
|
94
94
|
type ActorRouter,
|
|
95
95
|
createActorRouter,
|
|
96
96
|
} from "./router";
|
|
97
|
-
export {
|
|
98
|
-
handleRawWebSocketHandler,
|
|
99
|
-
handleWebSocketConnect,
|
|
100
|
-
} from "./router-endpoints";
|
|
97
|
+
export { routeWebSocket } from "./router-websocket-endpoints";
|