Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,6 @@ Always include a README.md. The `README.md` should always follow this structure:
Apache 2.0
```

## Test Notes

- Using setTimeout in tests & test actors will not work unless you call `await waitFor(driverTestConfig, <ts>)`
57 changes: 57 additions & 0 deletions packages/core/fixtures/driver-test-suite/inline-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { actor } from "@rivetkit/core";
import type { Registry } from "./registry";

export const inlineClientActor = actor({
onAuth: () => {},
state: { messages: [] as string[] },
actions: {
// Action that uses client to call another actor (stateless)
callCounterIncrement: async (c, amount: number) => {
const client = c.client<Registry>();
const result = await client.counter.getOrCreate(["inline-test"]).increment(amount);
c.state.messages.push(`Called counter.increment(${amount}), result: ${result}`);
return result;
},

// Action that uses client to get counter state (stateless)
getCounterState: async (c) => {
const client = c.client<Registry>();
const count = await client.counter.getOrCreate(["inline-test"]).getCount();
c.state.messages.push(`Got counter state: ${count}`);
return count;
},

// Action that uses client with .connect() for stateful communication
connectToCounterAndIncrement: async (c, amount: number) => {
const client = c.client<Registry>();
const handle = client.counter.getOrCreate(["inline-test-stateful"]);
const connection = handle.connect();

// Set up event listener
const events: number[] = [];
connection.on("newCount", (count: number) => {
events.push(count);
});

// Perform increments
const result1 = await connection.increment(amount);
const result2 = await connection.increment(amount * 2);

await connection.dispose();

c.state.messages.push(`Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}`);

return { result1, result2, events };
},

// Get all messages from this actor's state
getMessages: (c) => {
return c.state.messages;
},

// Clear messages
clearMessages: (c) => {
c.state.messages = [];
},
},
});
3 changes: 3 additions & 0 deletions packages/core/fixtures/driver-test-suite/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { connStateActor } from "./conn-state";
// Import actors from individual files
import { counter } from "./counter";
import { customTimeoutActor, errorHandlingActor } from "./error-handling";
import { inlineClientActor } from "./inline-client";
import { counterWithLifecycle } from "./lifecycle";
import { metadataActor } from "./metadata";
import { scheduled } from "./scheduled";
Expand All @@ -47,6 +48,8 @@ export const registry = setup({
// From error-handling.ts
errorHandlingActor,
customTimeoutActor,
// From inline-client.ts
inlineClientActor,
// From action-inputs.ts
inputActor,
// From action-timeout.ts
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/actor/action.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { Logger } from "@/common/log";
import type { ActorKey } from "@/actor/mod";
import type { Logger } from "@/common/log";
import type { Conn } from "./connection";
import type { ConnId } from "./connection";
import type { ActorContext } from "./context";
import type { SaveStateOptions } from "./instance";
import type { Schedule } from "./schedule";
import { Registry } from "@/registry/mod";
import { Client } from "@/client/client";

/**
* Context for a remote procedure call.
Expand Down Expand Up @@ -97,6 +99,13 @@ export class ActionContext<S, CP, CS, V, I, AD, DB> {
return this.#actorContext.conns;
}

/**
* Returns the client for the given registry.
*/
client<R extends Registry<any>>(): Client<R> {
return this.#actorContext.client<R>();
}

/**
* @experimental
*/
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/actor/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { Logger } from "@/common/log";
import type { ActorKey } from "@/actor/mod";
import type { Logger } from "@/common/log";
import type { Conn, ConnId } from "./connection";
import type { ActorInstance, SaveStateOptions } from "./instance";
import type { Schedule } from "./schedule";
import { Client } from "@/client/client";
import { Registry } from "@/registry/mod";

/**
* ActorContext class that provides access to actor methods and state
Expand Down Expand Up @@ -87,6 +89,13 @@ export class ActorContext<S, CP, CS, V, I, AD, DB> {
return this.#actor.conns;
}

/**
* Returns the client for the given registry.
*/
client<R extends Registry<any>>(): Client<R> {
return this.#actor.inlineClient as Client<R>;
}

/**
* Gets the database.
* @experimental
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/actor/instance.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { ActorKey } from "@/actor/mod";
import type * as wsToClient from "@/actor/protocol/message/to-client";
import type * as wsToServer from "@/actor/protocol/message/to-server";
import type { Client } from "@/client/client";
import type { Logger } from "@/common/log";
import type { ActorKey } from "@/actor/mod";
import { isJsonSerializable, stringifyError } from "@/common/utils";
import type { Registry } from "@/mod";
import invariant from "invariant";
import onChange from "on-change";
import type { ActionContext } from "./action";
Expand Down Expand Up @@ -136,6 +138,7 @@ export class ActorInstance<S, CP, CS, V, I, AD, DB> {
#config: ActorConfig<S, CP, CS, V, I, AD, DB>;
#connectionDrivers!: ConnDrivers;
#actorDriver!: ActorDriver;
#inlineClient!: Client<Registry<any>>;
#actorId!: string;
#name!: string;
#key!: ActorKey;
Expand All @@ -154,6 +157,10 @@ export class ActorInstance<S, CP, CS, V, I, AD, DB> {
return this.#actorId;
}

get inlineClient(): Client<Registry<any>> {
return this.#inlineClient;
}

/**
* This constructor should never be used directly.
*
Expand All @@ -169,13 +176,15 @@ export class ActorInstance<S, CP, CS, V, I, AD, DB> {
async start(
connectionDrivers: ConnDrivers,
actorDriver: ActorDriver,
inlineClient: Client<Registry<any>>,
actorId: string,
name: string,
key: ActorKey,
region: string,
) {
this.#connectionDrivers = connectionDrivers;
this.#actorDriver = actorDriver;
this.#inlineClient = inlineClient;
this.#actorId = actorId;
this.#name = name;
this.#key = key;
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/driver-test-suite/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { runActorConnStateTests } from "./tests/actor-conn-state";
import { runActorDriverTests } from "./tests/actor-driver";
import { runActorErrorHandlingTests } from "./tests/actor-error-handling";
import { runActorHandleTests } from "./tests/actor-handle";
import { runActorInlineClientTests } from "./tests/actor-inline-client";
import { runActorMetadataTests } from "./tests/actor-metadata";
import { runActorVarsTests } from "./tests/actor-vars";
import { runManagerDriverTests } from "./tests/manager-driver";
Expand Down Expand Up @@ -94,6 +95,8 @@ export function runDriverTests(
runActorErrorHandlingTests(driverTestConfig);

runActorAuthTests(driverTestConfig);

runActorInlineClientTests(driverTestConfig);
});
}
}
Expand Down
133 changes: 133 additions & 0 deletions packages/core/src/driver-test-suite/tests/actor-inline-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { describe, expect, test } from "vitest";
import type { DriverTestConfig } from "../mod";
import { setupDriverTest } from "../utils";

export function runActorInlineClientTests(driverTestConfig: DriverTestConfig) {
describe("Actor Inline Client Tests", () => {
describe("Stateless Client Calls", () => {
test("should make stateless calls to other actors", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);

// Create the inline client actor
const inlineClientHandle = client.inlineClientActor.getOrCreate(["inline-client-test"]);

// Test calling counter.increment via inline client
const result = await inlineClientHandle.callCounterIncrement(5);
expect(result).toBe(5);

// Verify the counter state was actually updated
const counterState = await inlineClientHandle.getCounterState();
expect(counterState).toBe(5);

// Check that messages were logged
const messages = await inlineClientHandle.getMessages();
expect(messages).toHaveLength(2);
expect(messages[0]).toContain("Called counter.increment(5), result: 5");
expect(messages[1]).toContain("Got counter state: 5");
});

test("should handle multiple stateless calls", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);

// Create the inline client actor
const inlineClientHandle = client.inlineClientActor.getOrCreate(["inline-client-multi"]);

// Clear any existing messages
await inlineClientHandle.clearMessages();

// Make multiple calls
const result1 = await inlineClientHandle.callCounterIncrement(3);
const result2 = await inlineClientHandle.callCounterIncrement(7);
const finalState = await inlineClientHandle.getCounterState();

expect(result1).toBe(3);
expect(result2).toBe(10); // 3 + 7
expect(finalState).toBe(10);

// Check messages
const messages = await inlineClientHandle.getMessages();
expect(messages).toHaveLength(3);
expect(messages[0]).toContain("Called counter.increment(3), result: 3");
expect(messages[1]).toContain("Called counter.increment(7), result: 10");
expect(messages[2]).toContain("Got counter state: 10");
});
});

describe("Stateful Client Calls", () => {
test("should connect to other actors and receive events", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);

// Create the inline client actor
const inlineClientHandle = client.inlineClientActor.getOrCreate(["inline-client-stateful"]);

// Clear any existing messages
await inlineClientHandle.clearMessages();

// Test stateful connection with events
const result = await inlineClientHandle.connectToCounterAndIncrement(4);

expect(result.result1).toBe(4);
expect(result.result2).toBe(12); // 4 + 8
expect(result.events).toEqual([4, 12]); // Should have received both events

// Check that message was logged
const messages = await inlineClientHandle.getMessages();
expect(messages).toHaveLength(1);
expect(messages[0]).toContain("Connected to counter, incremented by 4 and 8");
expect(messages[0]).toContain("results: 4, 12");
expect(messages[0]).toContain("events: [4,12]");
});

test("should handle stateful connection independently", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);

// Create the inline client actor
const inlineClientHandle = client.inlineClientActor.getOrCreate(["inline-client-independent"]);

// Clear any existing messages
await inlineClientHandle.clearMessages();

// Test with different increment values
const result = await inlineClientHandle.connectToCounterAndIncrement(2);

expect(result.result1).toBe(2);
expect(result.result2).toBe(6); // 2 + 4
expect(result.events).toEqual([2, 6]);

// Verify the state is independent from previous tests
const messages = await inlineClientHandle.getMessages();
expect(messages).toHaveLength(1);
expect(messages[0]).toContain("Connected to counter, incremented by 2 and 4");
});
});

describe("Mixed Client Usage", () => {
test("should handle both stateless and stateful calls", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);

// Create the inline client actor
const inlineClientHandle = client.inlineClientActor.getOrCreate(["inline-client-mixed"]);

// Clear any existing messages
await inlineClientHandle.clearMessages();

// Start with stateless calls
await inlineClientHandle.callCounterIncrement(1);
const statelessResult = await inlineClientHandle.getCounterState();
expect(statelessResult).toBe(1);

// Then do stateful call
const statefulResult = await inlineClientHandle.connectToCounterAndIncrement(3);
expect(statefulResult.result1).toBe(3);
expect(statefulResult.result2).toBe(9); // 3 + 6

// Check all messages were logged
const messages = await inlineClientHandle.getMessages();
expect(messages).toHaveLength(3);
expect(messages[0]).toContain("Called counter.increment(1), result: 1");
expect(messages[1]).toContain("Got counter state: 1");
expect(messages[2]).toContain("Connected to counter, incremented by 3 and 6");
});
});
});
}
8 changes: 8 additions & 0 deletions packages/core/src/topologies/coordinate/actor-peer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { ActorDriver } from "@/actor/driver";
import type { AnyActorInstance } from "@/actor/instance";
import type { ActorKey } from "@/actor/mod";
import type { Client } from "@/client/client";
import type { Registry } from "@/mod";
import type { RegistryConfig } from "@/registry/config";
import type { RunConfig } from "@/registry/run-config";
import type { GlobalState } from "@/topologies/coordinate/topology";
Expand All @@ -16,6 +18,7 @@ export class ActorPeer {
#runConfig: RunConfig;
#coordinateDriver: CoordinateDriver;
#actorDriver: ActorDriver;
#inlineClient: Client<Registry<any>>;
#globalState: GlobalState;
#actorId: string;
#actorName?: string;
Expand Down Expand Up @@ -47,13 +50,15 @@ export class ActorPeer {
runConfig: RunConfig,
CoordinateDriver: CoordinateDriver,
actorDriver: ActorDriver,
inlineClient: Client<Registry<any>>,
globalState: GlobalState,
actorId: string,
) {
this.#registryConfig = registryConfig;
this.#runConfig = runConfig;
this.#coordinateDriver = CoordinateDriver;
this.#actorDriver = actorDriver;
this.#inlineClient = inlineClient;
this.#globalState = globalState;
this.#actorId = actorId;
}
Expand All @@ -63,6 +68,7 @@ export class ActorPeer {
registryConfig: RegistryConfig,
runConfig: RunConfig,
actorDriver: ActorDriver,
inlineClient: Client<Registry<any>>,
CoordinateDriver: CoordinateDriver,
globalState: GlobalState,
actorId: string,
Expand All @@ -77,6 +83,7 @@ export class ActorPeer {
runConfig,
CoordinateDriver,
actorDriver,
inlineClient,
globalState,
actorId,
);
Expand Down Expand Up @@ -226,6 +233,7 @@ export class ActorPeer {
),
},
this.#actorDriver,
this.#inlineClient,
this.#actorId,
this.#actorName,
this.#actorKey,
Expand Down
Loading
Loading