Skip to content

Commit 9820a82

Browse files
committed
feat(react): support connecting to actors by id in useActor
1 parent bef6088 commit 9820a82

3 files changed

Lines changed: 142 additions & 54 deletions

File tree

rivetkit-typescript/packages/framework-base/src/mod.ts

Lines changed: 113 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,44 @@ export type AnyActorRegistry = Registry<any>;
1313

1414
export type { ActorConnStatus };
1515

16+
/**
17+
* Registry-erased form of {@link ActorOptions} stored on {@link ActorStateReference}.
18+
* Mirrors the public discriminated union so an actor is identified by either
19+
* `key` or `id`, never both at once.
20+
*/
21+
type StoredActorOptions =
22+
| {
23+
name: string;
24+
/**
25+
* Unique key for the actor instance.
26+
* This can be a string or an array of strings to create multiple instances.
27+
* @example "abc" or ["abc", "def"]
28+
*/
29+
key: string | string[];
30+
/** Parameters for the actor. */
31+
params?: unknown;
32+
/** Region to create the actor in if it doesn't exist. */
33+
createInRegion?: string;
34+
/** Input data to pass to the actor. */
35+
createWithInput?: unknown;
36+
/** Whether the actor is enabled. Defaults to true. */
37+
enabled?: boolean;
38+
/**
39+
* If true, only gets the actor if it already exists. Does not create it.
40+
* Defaults to false.
41+
*/
42+
noCreate?: boolean;
43+
}
44+
| {
45+
name: string;
46+
/** The raw ID of an existing actor instance. */
47+
id: string;
48+
/** Parameters for the actor. */
49+
params?: unknown;
50+
/** Whether the actor is enabled. Defaults to true. */
51+
enabled?: boolean;
52+
};
53+
1654
interface ActorStateReference {
1755
/**
1856
* The unique identifier for the actor.
@@ -40,47 +78,20 @@ interface ActorStateReference {
4078
*/
4179
error: Error | null;
4280
/**
43-
* Options for the actor, including its name, key, parameters, and whether it is enabled.
81+
* Options for the actor, including its name, key or id, parameters, and whether
82+
* it is enabled.
4483
*/
45-
opts: {
46-
name: string;
47-
/**
48-
* Unique key for the actor instance.
49-
* This can be a string or an array of strings to create multiple instances.
50-
* @example "abc" or ["abc", "def"]
51-
*/
52-
key: string | string[];
53-
/**
54-
* Parameters for the actor.
55-
* These are additional options that can be passed to the actor.
56-
*/
57-
params?: unknown;
58-
/** Region to create the actor in if it doesn't exist. */
59-
createInRegion?: string;
60-
/** Input data to pass to the actor. */
61-
createWithInput?: unknown;
62-
/**
63-
* Whether the actor is enabled.
64-
* Defaults to true.
65-
*/
66-
enabled?: boolean;
67-
/**
68-
* If true, only gets the actor if it already exists. Does not create the actor.
69-
* Throws an error if the actor is not found.
70-
* Defaults to false.
71-
*/
72-
noCreate?: boolean;
73-
};
84+
opts: StoredActorOptions;
7485
}
7586

7687
interface InternalRivetKitStore {
7788
actors: Record<string, ActorStateReference>;
7889
}
7990

8091
/**
81-
* Options for configuring a actor in RivetKit.
92+
* Options shared by every way of identifying an actor.
8293
*/
83-
export interface ActorOptions<
94+
interface ActorOptionsBase<
8495
Registry extends AnyActorRegistry,
8596
ActorName extends keyof ExtractActorsFromRegistry<Registry> & string,
8697
> {
@@ -90,25 +101,34 @@ export interface ActorOptions<
90101
* @example "chatRoom"
91102
*/
92103
name: ActorName;
104+
/**
105+
* Parameters for the actor.
106+
*/
107+
params?: ExtractActorsFromRegistry<Registry>[ActorName]["params"];
108+
/**
109+
* Whether the actor is enabled.
110+
* Defaults to true.
111+
*/
112+
enabled?: boolean;
113+
}
114+
115+
/**
116+
* Options for identifying an actor by its name and key.
117+
*/
118+
export interface ActorByKeyOptions<
119+
Registry extends AnyActorRegistry,
120+
ActorName extends keyof ExtractActorsFromRegistry<Registry> & string,
121+
> extends ActorOptionsBase<Registry, ActorName> {
93122
/**
94123
* Unique key for the actor instance.
95124
* This can be a string or an array of strings to create multiple instances.
96125
* @example "abc" or ["abc", "def"]
97126
*/
98127
key: string | string[];
99-
/**
100-
* Parameters for the actor.
101-
*/
102-
params?: ExtractActorsFromRegistry<Registry>[ActorName]["params"];
103128
/** Region to create the actor in if it doesn't exist. */
104129
createInRegion?: string;
105130
/** Input data to pass to the actor. */
106131
createWithInput?: unknown;
107-
/**
108-
* Whether the actor is enabled.
109-
* Defaults to true.
110-
*/
111-
enabled?: boolean;
112132
/**
113133
* If true, only gets the actor if it already exists. Does not create the actor.
114134
* Throws an error if the actor is not found.
@@ -117,6 +137,33 @@ export interface ActorOptions<
117137
noCreate?: boolean;
118138
}
119139

140+
/**
141+
* Options for identifying an existing actor by its raw actor ID.
142+
* Unlike key-based options, this always resolves an existing actor and never
143+
* creates one, so create-only options do not apply.
144+
*/
145+
export interface ActorByIdOptions<
146+
Registry extends AnyActorRegistry,
147+
ActorName extends keyof ExtractActorsFromRegistry<Registry> & string,
148+
> extends ActorOptionsBase<Registry, ActorName> {
149+
/**
150+
* The raw ID of an existing actor instance.
151+
* @example "a1b2c3d4-..."
152+
*/
153+
id: string;
154+
}
155+
156+
/**
157+
* Options for configuring a actor in RivetKit. An actor is identified either by
158+
* its name and key or by its raw actor id.
159+
*/
160+
export type ActorOptions<
161+
Registry extends AnyActorRegistry,
162+
ActorName extends keyof ExtractActorsFromRegistry<Registry> & string,
163+
> =
164+
| ActorByKeyOptions<Registry, ActorName>
165+
| ActorByIdOptions<Registry, ActorName>;
166+
120167
export type ActorsStateDerived<
121168
Registry extends AnyActorRegistry,
122169
WorkerName extends keyof ExtractActorsFromRegistry<Registry> & string,
@@ -419,15 +466,22 @@ function create<Registry extends AnyActorRegistry>(
419466
});
420467

421468
try {
422-
const handle = actor.opts.noCreate
423-
? client.get(actor.opts.name as string, actor.opts.key, {
424-
params: actor.opts.params,
425-
})
426-
: client.getOrCreate(actor.opts.name as string, actor.opts.key, {
427-
params: actor.opts.params,
428-
createInRegion: actor.opts.createInRegion,
429-
createWithInput: actor.opts.createWithInput,
430-
});
469+
let handle: ActorHandle<AnyActorDefinition>;
470+
if ("id" in actor.opts) {
471+
handle = client.getForId(actor.opts.name as string, actor.opts.id, {
472+
params: actor.opts.params,
473+
});
474+
} else if (actor.opts.noCreate) {
475+
handle = client.get(actor.opts.name as string, actor.opts.key, {
476+
params: actor.opts.params,
477+
});
478+
} else {
479+
handle = client.getOrCreate(actor.opts.name as string, actor.opts.key, {
480+
params: actor.opts.params,
481+
createInRegion: actor.opts.createInRegion,
482+
createWithInput: actor.opts.createWithInput,
483+
});
484+
}
431485

432486
const connection = handle.connect();
433487
const storedHandle = handle as ActorHandle<AnyActorDefinition>;
@@ -495,10 +549,16 @@ function create<Registry extends AnyActorRegistry>(
495549
}
496550
}
497551

498-
function defaultHashFunction({ name, key, params, noCreate }: AnyActorOptions) {
499-
return JSON.stringify({ name, key, params, noCreate });
552+
function defaultHashFunction(opts: AnyActorOptions) {
553+
const key = "key" in opts ? opts.key : undefined;
554+
const id = "id" in opts ? opts.id : undefined;
555+
const noCreate = "noCreate" in opts ? opts.noCreate : undefined;
556+
return JSON.stringify({ name: opts.name, key, id, params: opts.params, noCreate });
500557
}
501558

502-
function optsEqual(a: AnyActorOptions, b: AnyActorOptions) {
559+
function optsEqual(
560+
a: ActorStateReference["opts"],
561+
b: ActorStateReference["opts"],
562+
) {
503563
return equal(a, b);
504564
}

rivetkit-typescript/packages/react/src/mod.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ export function createRivetKitWithClient<Registry extends AnyActorRegistry>(
6464
* Hook to connect to a actor and retrieve its state. Using this hook with the same options
6565
* will return the same actor instance. This simplifies passing around the actor state in your components.
6666
* It also provides a method to listen for events emitted by the actor.
67-
* @param opts - Options for the actor, including its name, key, and parameters.
67+
*
68+
* The actor is identified either by its `name` and `key`, or by its `name` and raw
69+
* actor `id`. The id form always resolves an existing actor and never creates one.
70+
* @param opts - Options for the actor, including its name, key or id, and parameters.
6871
* @returns An object containing the actor's state and a method to listen for events.
6972
*/
7073
function useActor<

rivetkit-typescript/packages/react/src/mod.typecheck.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,28 @@ const connectionTypeCheck: Assert<
7373
IsEqual<ActualConnection, ExpectedConnection>
7474
> = true;
7575
void connectionTypeCheck;
76+
77+
// An actor may also be identified by its raw id. The typed surface (actions,
78+
// events, params) must match the key-based form.
79+
const idActorState = rivet.useActor({
80+
name: "counter",
81+
id: "a1b2c3d4-typecheck",
82+
});
83+
84+
if (idActorState.connection) {
85+
void idActorState.connection.increment(1);
86+
}
87+
88+
idActorState.useEvent("updated", (payload) => {
89+
const count: number = payload.count;
90+
void count;
91+
});
92+
93+
type IdActualConnection = typeof idActorState.connection;
94+
const idConnectionTypeCheck: Assert<
95+
IsEqual<IdActualConnection, ExpectedConnection>
96+
> = true;
97+
void idConnectionTypeCheck;
98+
99+
// @ts-expect-error must provide either a key or an id
100+
rivet.useActor({ name: "counter" });

0 commit comments

Comments
 (0)