diff --git a/CLAUDE.md b/CLAUDE.md index d2cfba7046..65a9008371 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,7 @@ Design constraints, invariants, and reference commands for the Rivet monorepo. F - Avoid raw `f64` fields in vbare protocol schemas that use hashable maps; generated Rust derives `Eq`/`Hash`, so encode floats as fixed bytes or an ordered wrapper. - Version converters must manually map fields between versions; never use serialize-deserialize round trips such as `transcode_version` or `serde_bare::to_vec` plus `from_slice`. +- RivetKit client/server protocol compatibility assumes the server/runtime is newer than the client; clients send their latest request protocol version, and servers handle older-client compatibility and negotiation. When talking about "Rivet Actors" make sure to capitalize "Rivet Actor" as a proper noun and lowercase "actor" as a generic noun. diff --git a/rivetkit-asyncapi/asyncapi.json b/rivetkit-asyncapi/asyncapi.json index 1d8dd34c40..3e28f2f6c4 100644 --- a/rivetkit-asyncapi/asyncapi.json +++ b/rivetkit-asyncapi/asyncapi.json @@ -123,6 +123,33 @@ "type": "null" } ] + }, + "actor": { + "type": "object", + "properties": { + "actorId": { + "type": "string" + }, + "generation": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "integer", + "format": "int64" + } + ] + }, + "key": { + "type": "string" + } + }, + "required": [ + "actorId", + "generation" + ], + "additionalProperties": false } }, "required": [ @@ -421,6 +448,33 @@ "type": "null" } ] + }, + "actor": { + "type": "object", + "properties": { + "actorId": { + "type": "string" + }, + "generation": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "integer", + "format": "int64" + } + ] + }, + "key": { + "type": "string" + } + }, + "required": [ + "actorId", + "generation" + ], + "additionalProperties": false } }, "required": [ diff --git a/rivetkit-typescript/packages/rivetkit/src/client/actor-conn.ts b/rivetkit-typescript/packages/rivetkit/src/client/actor-conn.ts index ff3454b7ed..a2edfc66c1 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/actor-conn.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/actor-conn.ts @@ -216,6 +216,9 @@ export class ActorConnRaw { this.#encoding = encoding; this.#actorResolutionState = actorResolutionState; this.#gatewayOptions = resolveActorGatewayOptions(gatewayOptions); + if ("getForId" in actorResolutionState) { + this.#actorId = actorResolutionState.getForId.actorId; + } this.#readyPromise = promiseWithResolvers((reason) => logger().warn({ msg: "unhandled ready promise rejection", diff --git a/rivetkit-typescript/packages/rivetkit/tests/driver/actor-conn.test.ts b/rivetkit-typescript/packages/rivetkit/tests/driver/actor-conn.test.ts index b9491051ea..e65153d14a 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/driver/actor-conn.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/driver/actor-conn.test.ts @@ -100,6 +100,20 @@ describeDriverMatrix("Actor Conn", (driverTestConfig) => { await connection.dispose(); }); + test("should expose actorId after connection init", async (c) => { + const { client } = await setupDriverTest(c, driverTestConfig); + + const handle = client.counter.getOrCreate([ + "test-conn-actor-id", + ]); + const connection = handle.connect(); + + await connection.ready; + expect(connection.actorId).toBe(await handle.resolve()); + + await connection.dispose(); + }); + test("should connect using (await create()).connect()", async (c) => { const { client } = await setupDriverTest(c, driverTestConfig);