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
2 changes: 1 addition & 1 deletion packages/next-js/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const toNextHandler = (
inputConfig.disableServer = true;
inputConfig.disableActorDriver = true;

const { fetch } = registry.startServerless(inputConfig);
const { fetch } = registry.start(inputConfig);

const fetchWrapper = async (
request: Request,
Expand Down
2 changes: 0 additions & 2 deletions packages/rivetkit/src/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export const ClientConfigSchema = z.object({
.optional()
.transform((x) => x ?? getEnvUniversal("RIVET_TOKEN")),

totalSlots: z.number().optional(),

headers: z.record(z.string()).optional().default({}),

/** Endpoint to connect to the Rivet engine. Can be configured via RIVET_ENGINE env var. */
Expand Down
8 changes: 7 additions & 1 deletion packages/rivetkit/src/drivers/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import type { DriverConfig, RunConfig } from "@/registry/run-config";
export function chooseDefaultDriver(runConfig: RunConfig): DriverConfig {
if (runConfig.endpoint && runConfig.driver) {
throw new UserError(
"Cannot specify both 'engine' and 'driver' in configuration",
"Cannot specify both 'endpoint' and 'driver' in configuration",
);
}

if (runConfig.runnerKind === "serverless" && !runConfig.endpoint) {
throw new UserError(
"Cannot use 'serverless' runnerKind without the 'endpoint' config set. This condition should not happen, since the endpoint should be configured by default by Rivet when using serverless runners.",
);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/rivetkit/src/drivers/engine/actor-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ export class EngineActorDriver implements ActorDriver {
endpoint: config.endpoint,
token: runConfig.token ?? config.token,
pegboardEndpoint: config.pegboardEndpoint,
namespace: config.namespace,
namespace: runConfig.namespace ?? config.namespace,
totalSlots: runConfig.totalSlots ?? config.totalSlots,
runnerName: config.runnerName,
runnerName: runConfig.runnerName ?? config.runnerName,
runnerKey: config.runnerKey,
metadata: {
inspectorToken: this.#runConfig.inspector.token(),
Expand Down
23 changes: 14 additions & 9 deletions packages/rivetkit/src/manager/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
type Actor as ApiActor,
} from "@/manager-api/actors";
import { RivetIdSchema } from "@/manager-api/common";
import type { ServerlessActorDriverBuilder } from "@/mod";
import type { RegistryConfig } from "@/registry/config";
import type { RunConfig } from "@/registry/run-config";
import type { ActorOutput, ManagerDriver } from "./driver";
Expand Down Expand Up @@ -79,7 +80,7 @@ export function createManagerRouter(
registryConfig: RegistryConfig,
runConfig: RunConfig,
managerDriver: ManagerDriver,
serverlessActorDriverBuilder: (() => ActorDriver) | undefined,
serverlessActorDriverBuilder: ServerlessActorDriverBuilder | undefined,
): { router: Hono; openapi: OpenAPIHono } {
const router = new OpenAPIHono({ strict: false }).basePath(
runConfig.basePath,
Expand Down Expand Up @@ -121,10 +122,7 @@ export function createManagerRouter(

function addServerlessRoutes(
runConfig: RunConfig,
serverlessActorDriverBuilder: (
token: string | undefined,
totalSlots: number | undefined,
) => ActorDriver,
serverlessActorDriverBuilder: ServerlessActorDriverBuilder,
router: OpenAPIHono,
) {
// Apply CORS
Expand All @@ -141,11 +139,18 @@ function addServerlessRoutes(
router.get("/start", async (c) => {
const token = c.req.header("x-rivet-token");
let totalSlots: number | undefined = parseInt(
c.req.header("x-rivetkit-total-slots") as any,
c.req.header("x-rivet-total-slots") as any,
);
if (!Number.isFinite(totalSlots)) totalSlots = undefined;
const runnerName = c.req.header("x-rivet-runner-name");
const namespace = c.req.header("x-rivet-namespace-id");

const actorDriver = serverlessActorDriverBuilder(
token,
totalSlots,
runnerName,
namespace,
);
if (isNaN(totalSlots)) totalSlots = undefined;

const actorDriver = serverlessActorDriverBuilder(token, totalSlots);
invariant(
actorDriver.serverlessHandleStart,
"missing serverlessHandleStart on ActorDriver",
Expand Down
136 changes: 26 additions & 110 deletions packages/rivetkit/src/registry/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ import {
} from "./run-config";
import { crossPlatformServe } from "./serve";

export type ServerlessActorDriverBuilder = (
token?: string,
totalSlots?: number,
runnerName?: string,
namespace?: string,
) => ActorDriver;

interface ServerOutput<A extends Registry<any>> {
/** Client to communicate with the actors. */
client: Client<A>;
Expand Down Expand Up @@ -72,6 +79,9 @@ export class Registry<A extends RegistryActors> {
config.disableActorDriver = true;
config.noWelcome = true;
}
if (config.runnerKind === "serverless") {
config.disableActorDriver = true;
}

// Configure getUpgradeWebSocket lazily so we can assign it in crossPlatformServe
let upgradeWebSocket: any;
Expand Down Expand Up @@ -117,6 +127,7 @@ export class Registry<A extends RegistryActors> {
console.log();
}

let serverlessActorDriverBuilder: undefined | ServerlessActorDriverBuilder;
// HACK: We need to find a better way to let the driver itself decide when to start the actor driver
// Create runner
//
Expand All @@ -128,117 +139,22 @@ export class Registry<A extends RegistryActors> {
managerDriver,
client,
);
}

const { router: hono } = createManagerRouter(
this.#config,
config,
managerDriver,
undefined,
);

// Start server
if (!config.disableServer) {
(async () => {
const out = await crossPlatformServe(hono, undefined);
upgradeWebSocket = out.upgradeWebSocket;
})();
}

return {
client,
fetch: hono.fetch.bind(hono),
};
}

public startServerless(inputConfig?: RunConfigInput): ServerOutput<this> {
const config = RunConfigSchema.parse(inputConfig);

// Configure logger
if (config.logging?.baseLogger) {
// Use provided base logger
configureBaseLogger(config.logging.baseLogger);
} else {
// Configure default logger with log level from config
// getPinoLevel will handle env variable priority
configureDefaultLogger(config.logging?.level);
}

// Choose the driver based on configuration
const driver = chooseDefaultDriver(config);

// TODO: Find cleaner way of disabling by default
if (driver.name === "engine") {
config.inspector.enabled = false;
config.disableServer = true;
config.disableActorDriver = true;
}
if (driver.name === "cloudflare-workers") {
config.inspector.enabled = false;
config.disableServer = true;
config.disableActorDriver = true;
config.noWelcome = true;
}

// Configure getUpgradeWebSocket lazily so we can assign it in crossPlatformServe
let upgradeWebSocket: any;
if (!config.getUpgradeWebSocket) {
config.getUpgradeWebSocket = () => upgradeWebSocket!;
}

// Create router
const managerDriver = driver.manager(this.#config, config);

// Create client
const client = createClientWithDriver<this>(managerDriver, config);

const driverLog = managerDriver.extraStartupLog?.() ?? {};
logger().info({
msg: "rivetkit ready",
driver: driver.name,
definitions: Object.keys(this.#config.use).length,
...driverLog,
});
if (config.inspector?.enabled && managerDriver.inspector) {
logger().info({ msg: "inspector ready", url: getInspectorUrl(config) });
}

// Print welcome information
if (!config.noWelcome) {
const displayInfo = managerDriver.displayInformation();
console.log();
console.log(` RivetKit ${pkg.version} (${displayInfo.name})`);
console.log(` - Endpoint: http://127.0.0.1:6420`);
for (const [k, v] of Object.entries(displayInfo.properties)) {
const padding = " ".repeat(Math.max(0, 13 - k.length));
console.log(` - ${k}:${padding}${v}`);
}
if (config.inspector?.enabled && managerDriver.inspector) {
console.log(` - Inspector: ${getInspectorUrl(config)}`);
}
console.log();
}

let serverlessActorDriverBuilder:
| ((token?: string, totalSlots?: number) => ActorDriver)
| undefined = (
token: string | undefined,
totalSlots: number | undefined,
) => {
// Override config
if (token) config.token = token;
if (totalSlots) config.totalSlots = totalSlots;

return driver.actor(this.#config, config, managerDriver, client);
};

// HACK: We need to find a better way to let the driver itself decide when to start the actor driver
// Create runner
//
// Even though we do not use the return value, this is required to start the code that will handle incoming actors
if (!config.disableActorDriver) {
const _actorDriver = serverlessActorDriverBuilder();
serverlessActorDriverBuilder = undefined;
serverlessActorDriverBuilder = (
token,
totalSlots,
runnerName,
namespace,
) => {
// Override config
if (token) config.token = token;
if (totalSlots) config.totalSlots = totalSlots;
if (runnerName) config.runnerName = runnerName;
if (namespace) config.namespace = namespace;

// Create new actor driver with updated config
return driver.actor(this.#config, config, managerDriver, client);
};
}

const { router: hono } = createManagerRouter(
Expand Down
17 changes: 17 additions & 0 deletions packages/rivetkit/src/registry/run-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ClientConfigSchema } from "@/client/config";
import { LogLevelSchema } from "@/common/log";
import { InspectorConfigSchema } from "@/inspector/config";
import type { ManagerDriverBuilder } from "@/manager/driver";
import { getEnvUniversal } from "@/utils";

type CorsOptions = NonNullable<Parameters<typeof cors>[0]>;

Expand Down Expand Up @@ -35,6 +36,22 @@ export const RunConfigSchema = ClientConfigSchema.extend({
/** @experimental */
disableActorDriver: z.boolean().optional().default(false),

/**
* @experimental
*
* Whether to run runners normally or have them managed
* serverlessly (by the Rivet Engine for example).
*/
runnerKind: z
.enum(["serverless", "normal"])
.optional()
.default(() =>
getEnvUniversal("RIVET_RUNNER_KIND") === "serverless"
? "serverless"
: "normal",
),
totalSlots: z.number().optional(),

/**
* @experimental
*
Expand Down
Loading