Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import * as fs from "node:fs/promises";
import { resolve } from "node:path";
import { z } from "zod";
import { createFileSystemOrMemoryDriver } from "@/drivers/file-system/mod";
import type {
ManagerDriver,
} from "@/manager/driver";
import type { ManagerDriver } from "@/manager/driver";
import { buildManagerRouter } from "@/manager/router";
import { type RegistryConfig, RegistryConfigSchema } from "@/registry/config";
import { VERSION } from "@/utils";
Expand All @@ -22,19 +20,20 @@ async function main() {
// const registry = setup(registryConfig);

const managerDriver: ManagerDriver = {
getForId: unimplemented,
getWithKey: unimplemented,
getOrCreateWithKey: unimplemented,
createActor: unimplemented,
listActors: unimplemented,
sendRequest: unimplemented,
openWebSocket: unimplemented,
proxyRequest: unimplemented,
proxyWebSocket: unimplemented,
displayInformation: unimplemented,
setGetUpgradeWebSocket: unimplemented,
kvGet: unimplemented,
};
getForId: unimplemented,
getWithKey: unimplemented,
getOrCreateWithKey: unimplemented,
createActor: unimplemented,
listActors: unimplemented,
sendRequest: unimplemented,
openWebSocket: unimplemented,
proxyRequest: unimplemented,
proxyWebSocket: unimplemented,
displayInformation: unimplemented,
setGetUpgradeWebSocket: unimplemented,
buildGatewayUrl: unimplemented,
kvGet: unimplemented,
};

// const client = createClientWithDriver(
// managerDriver,
Expand Down Expand Up @@ -207,4 +206,5 @@ function unimplemented(): never {
throw new Error("UNIMPLEMENTED");
}

// biome-ignore lint/nursery/noFloatingPromises: main
main();
217 changes: 183 additions & 34 deletions rivetkit-typescript/packages/rivetkit/src/actor/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,14 @@ export const ActorConfigSchema = z
// This must have only one or the other or else TState will not be able to be inferred
//
// Data returned from this handler will be available on `c.state`.
type CreateState<TState, TConnParams, TConnState, TVars, TInput, TDatabase extends AnyDatabaseProvider> =
type CreateState<
TState,
TConnParams,
TConnState,
TVars,
TInput,
TDatabase extends AnyDatabaseProvider,
> =
| { state: TState }
| {
createState: (
Expand Down Expand Up @@ -458,7 +465,7 @@ interface BaseActorConfig<
websocket: UniversalWebSocket,
) => void | Promise<void>;

actions: TActions;
actions?: TActions;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line changes 'actions' from required to optional, which may cause issues if other parts of the codebase expect actions to always be defined. Ensure this change is intentional and that code using this property handles the undefined case properly. Additionally, the formatting changes throughout the file should be applied using Biome instead of manually to ensure consistency with project standards.

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

}

type ActorDatabaseConfig<TDatabase extends AnyDatabaseProvider> =
Expand Down Expand Up @@ -620,43 +627,185 @@ export function test<

export const DocActorOptionsSchema = z
.object({
createVarsTimeout: z.number().optional().describe("Timeout in ms for createVars handler. Default: 5000"),
createConnStateTimeout: z.number().optional().describe("Timeout in ms for createConnState handler. Default: 5000"),
onConnectTimeout: z.number().optional().describe("Timeout in ms for onConnect handler. Default: 5000"),
onSleepTimeout: z.number().optional().describe("Timeout in ms for onSleep handler. Must be less than ACTOR_STOP_THRESHOLD_MS. Default: 5000"),
onDestroyTimeout: z.number().optional().describe("Timeout in ms for onDestroy handler. Default: 5000"),
stateSaveInterval: z.number().optional().describe("Interval in ms between automatic state saves. Default: 10000"),
actionTimeout: z.number().optional().describe("Timeout in ms for action handlers. Default: 60000"),
waitUntilTimeout: z.number().optional().describe("Max time in ms to wait for waitUntil background promises during shutdown. Default: 15000"),
connectionLivenessTimeout: z.number().optional().describe("Timeout in ms for connection liveness checks. Default: 2500"),
connectionLivenessInterval: z.number().optional().describe("Interval in ms between connection liveness checks. Default: 5000"),
noSleep: z.boolean().optional().describe("If true, the actor will never sleep. Default: false"),
sleepTimeout: z.number().optional().describe("Time in ms of inactivity before the actor sleeps. Default: 30000"),
canHibernateWebSocket: z.boolean().optional().describe("Whether WebSockets using onWebSocket can be hibernated. WebSockets using actions/events are hibernatable by default. Default: false"),
createVarsTimeout: z
.number()
.optional()
.describe("Timeout in ms for createVars handler. Default: 5000"),
createConnStateTimeout: z
.number()
.optional()
.describe(
"Timeout in ms for createConnState handler. Default: 5000",
),
onConnectTimeout: z
.number()
.optional()
.describe("Timeout in ms for onConnect handler. Default: 5000"),
onSleepTimeout: z
.number()
.optional()
.describe(
"Timeout in ms for onSleep handler. Must be less than ACTOR_STOP_THRESHOLD_MS. Default: 5000",
),
onDestroyTimeout: z
.number()
.optional()
.describe("Timeout in ms for onDestroy handler. Default: 5000"),
stateSaveInterval: z
.number()
.optional()
.describe(
"Interval in ms between automatic state saves. Default: 10000",
),
actionTimeout: z
.number()
.optional()
.describe("Timeout in ms for action handlers. Default: 60000"),
waitUntilTimeout: z
.number()
.optional()
.describe(
"Max time in ms to wait for waitUntil background promises during shutdown. Default: 15000",
),
connectionLivenessTimeout: z
.number()
.optional()
.describe(
"Timeout in ms for connection liveness checks. Default: 2500",
),
connectionLivenessInterval: z
.number()
.optional()
.describe(
"Interval in ms between connection liveness checks. Default: 5000",
),
noSleep: z
.boolean()
.optional()
.describe("If true, the actor will never sleep. Default: false"),
sleepTimeout: z
.number()
.optional()
.describe(
"Time in ms of inactivity before the actor sleeps. Default: 30000",
),
canHibernateWebSocket: z
.boolean()
.optional()
.describe(
"Whether WebSockets using onWebSocket can be hibernated. WebSockets using actions/events are hibernatable by default. Default: false",
),
})
.describe("Actor options for timeouts and behavior configuration.");

export const DocActorConfigSchema = z
.object({
state: z.unknown().optional().describe("Initial state value for the actor. Cannot be used with createState."),
createState: z.unknown().optional().describe("Function to create initial state. Receives context and input. Cannot be used with state."),
connState: z.unknown().optional().describe("Initial connection state value. Cannot be used with createConnState."),
createConnState: z.unknown().optional().describe("Function to create connection state. Receives context and connection params. Cannot be used with connState."),
vars: z.unknown().optional().describe("Initial ephemeral variables value. Cannot be used with createVars."),
createVars: z.unknown().optional().describe("Function to create ephemeral variables. Receives context and driver context. Cannot be used with vars."),
db: z.unknown().optional().describe("Database provider instance for the actor."),
onCreate: z.unknown().optional().describe("Called when the actor is first initialized. Use to initialize state."),
onDestroy: z.unknown().optional().describe("Called when the actor is destroyed."),
onWake: z.unknown().optional().describe("Called when the actor wakes up and is ready to receive connections and actions."),
onSleep: z.unknown().optional().describe("Called when the actor is stopping or sleeping. Use to clean up resources."),
onStateChange: z.unknown().optional().describe("Called when the actor's state changes. State changes within this hook won't trigger recursion."),
onBeforeConnect: z.unknown().optional().describe("Called before a client connects. Throw an error to reject the connection."),
onConnect: z.unknown().optional().describe("Called when a client successfully connects."),
onDisconnect: z.unknown().optional().describe("Called when a client disconnects."),
onBeforeActionResponse: z.unknown().optional().describe("Called before sending an action response. Use to transform output."),
onRequest: z.unknown().optional().describe("Called for raw HTTP requests to /actors/{name}/http/* endpoints."),
onWebSocket: z.unknown().optional().describe("Called for raw WebSocket connections to /actors/{name}/websocket/* endpoints."),
actions: z.record(z.string(), z.unknown()).optional().describe("Map of action name to handler function."),
state: z
.unknown()
.optional()
.describe(
"Initial state value for the actor. Cannot be used with createState.",
),
createState: z
.unknown()
.optional()
.describe(
"Function to create initial state. Receives context and input. Cannot be used with state.",
),
connState: z
.unknown()
.optional()
.describe(
"Initial connection state value. Cannot be used with createConnState.",
),
createConnState: z
.unknown()
.optional()
.describe(
"Function to create connection state. Receives context and connection params. Cannot be used with connState.",
),
vars: z
.unknown()
.optional()
.describe(
"Initial ephemeral variables value. Cannot be used with createVars.",
),
createVars: z
.unknown()
.optional()
.describe(
"Function to create ephemeral variables. Receives context and driver context. Cannot be used with vars.",
),
db: z
.unknown()
.optional()
.describe("Database provider instance for the actor."),
onCreate: z
.unknown()
.optional()
.describe(
"Called when the actor is first initialized. Use to initialize state.",
),
onDestroy: z
.unknown()
.optional()
.describe("Called when the actor is destroyed."),
onWake: z
.unknown()
.optional()
.describe(
"Called when the actor wakes up and is ready to receive connections and actions.",
),
onSleep: z
.unknown()
.optional()
.describe(
"Called when the actor is stopping or sleeping. Use to clean up resources.",
),
onStateChange: z
.unknown()
.optional()
.describe(
"Called when the actor's state changes. State changes within this hook won't trigger recursion.",
),
onBeforeConnect: z
.unknown()
.optional()
.describe(
"Called before a client connects. Throw an error to reject the connection.",
),
onConnect: z
.unknown()
.optional()
.describe("Called when a client successfully connects."),
onDisconnect: z
.unknown()
.optional()
.describe("Called when a client disconnects."),
onBeforeActionResponse: z
.unknown()
.optional()
.describe(
"Called before sending an action response. Use to transform output.",
),
onRequest: z
.unknown()
.optional()
.describe(
"Called for raw HTTP requests to /actors/{name}/http/* endpoints.",
),
onWebSocket: z
.unknown()
.optional()
.describe(
"Called for raw WebSocket connections to /actors/{name}/websocket/* endpoints.",
),
actions: z
.record(z.string(), z.unknown())
.optional()
.describe(
"Map of action name to handler function. Defaults to an empty object.",
),
options: DocActorOptionsSchema.optional(),
})
.describe("Actor configuration passed to the actor() function.");
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
}

get actions(): string[] {
return Object.keys(this.#config.actions);
return Object.keys(this.#config.actions ?? {});
}

get config(): ActorConfig<S, CP, CS, V, I, DB> {
Expand Down Expand Up @@ -516,12 +516,13 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
): Promise<unknown> {
this.assertReady();

if (!(actionName in this.#config.actions)) {
const actions = this.#config.actions ?? {};
if (!(actionName in actions)) {
this.#rLog.warn({ msg: "action does not exist", actionName });
throw new errors.ActionNotFound(actionName);
}

const actionFunction = this.#config.actions[actionName];
const actionFunction = actions[actionName];
if (typeof actionFunction !== "function") {
this.#rLog.warn({
msg: "action is not a function",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ export function createTestInlineClientDriver(
);
return upgradeWebSocket(() => wsHandler)(c, noopNext());
},
async buildGatewayUrl(actorId: string): Promise<string> {
return `${endpoint}/gateway/${actorId}`;
},
displayInformation(): ManagerDisplayInformation {
return { properties: {} };
},
Expand Down
Loading