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
6 changes: 3 additions & 3 deletions examples/next-js/src/lib/rivet-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { createClient, createRivetKit } from "@rivetkit/next-js/client";
import type { registry } from "@/rivet/registry";

// TODO: Auto-trigger start by sending request to health endpoint

const client = createClient<typeof registry>();
const client = createClient<typeof registry>(
process.env.NEXT_RIVET_ENDPOINT ?? "http://localhost:3000/api/rivet",
);
export const { useActor } = createRivetKit(client);
21 changes: 13 additions & 8 deletions packages/next-js/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ export const toNextHandler = (
inputConfig.disableDefaultServer = true;

// Configure serverless
const publicUrl =
process.env.NEXT_PUBLIC_SITE_URL ??
process.env.NEXT_PUBLIC_VERCEL_URL ??
`http://127.0.0.1:${process.env.PORT ?? 3000}`;
inputConfig.runnerKind = "serverless";
inputConfig.runEngine = true;
inputConfig.autoConfigureServerless = {
url: `${publicUrl}/api/rivet/start`,
};

// Auto-configure serverless runner if not in prod
if (process.env.NODE_ENV !== "production") {
const publicUrl =
process.env.NEXT_PUBLIC_SITE_URL ??
process.env.NEXT_PUBLIC_VERCEL_URL ??
`http://127.0.0.1:${process.env.PORT ?? 3000}`;

inputConfig.runEngine = true;
inputConfig.autoConfigureServerless = {
url: `${publicUrl}/api/rivet/start`,
};
}

// Next logs this on every request
inputConfig.noWelcome = true;
Expand Down
6 changes: 5 additions & 1 deletion packages/rivetkit/scripts/dump-openapi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from "node:fs/promises";
import { resolve } from "node:path";
import { ClientConfigSchema } from "@/client/config";
import { createFileSystemOrMemoryDriver } from "@/drivers/file-system/mod";
import type { ManagerDriver } from "@/manager/driver";
import { createManagerRouter } from "@/manager/router";
Expand Down Expand Up @@ -39,7 +40,10 @@ function main() {
getOrCreateInspectorAccessToken: unimplemented,
};

const client = createClientWithDriver(managerDriver);
const client = createClientWithDriver(
managerDriver,
ClientConfigSchema.parse({}),
);

const { openapi } = createManagerRouter(
registryConfig,
Expand Down
11 changes: 0 additions & 11 deletions packages/rivetkit/src/actor/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,6 @@ export class ActorError extends Error {
// Force stringify to return the message
return this.message;
}

/**
* Serialize error for HTTP response
*/
serializeForHttp() {
return {
type: this.code,
message: this.message,
metadata: this.metadata,
};
}
}

export class InternalError extends ActorError {
Expand Down
12 changes: 4 additions & 8 deletions packages/rivetkit/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,12 @@ export class ClientRaw {

/**
* Creates an instance of Client.
*
* @param {string} managerEndpoint - The manager endpoint. See {@link https://rivet.dev/docs/setup|Initial Setup} for instructions on getting the manager endpoint.
* @param {ClientConfig} [opts] - Options for configuring the client.
* @see {@link https://rivet.dev/docs/setup|Initial Setup}
*/
public constructor(driver: ManagerDriver, opts?: ClientConfig) {
public constructor(driver: ManagerDriver, config: ClientConfig) {
this.#driver = driver;

this.#encodingKind = opts?.encoding ?? "bare";
this[TRANSPORT_SYMBOL] = opts?.transport ?? "websocket";
this.#encodingKind = config.encoding ?? "bare";
this[TRANSPORT_SYMBOL] = config.transport ?? "websocket";
}

/**
Expand Down Expand Up @@ -406,7 +402,7 @@ export type AnyClient = Client<Registry<any>>;

export function createClientWithDriver<A extends Registry<any>>(
driver: ManagerDriver,
config?: ClientConfig,
config: ClientConfig,
): Client<A> {
const client = new ClientRaw(driver, config);

Expand Down
3 changes: 3 additions & 0 deletions packages/rivetkit/src/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export const ClientConfigSchema = z.object({

// See RunConfig.getUpgradeWebSocket
getUpgradeWebSocket: z.custom<GetUpgradeWebSocket>().optional(),

/** Whether to automatically perform health checks when the client is created. */
disableHealthCheck: z.boolean().optional().default(false),
});

export type ClientConfig = z.infer<typeof ClientConfigSchema>;
Expand Down
11 changes: 8 additions & 3 deletions packages/rivetkit/src/client/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HTTP_RESPONSE_ERROR_VERSIONED } from "@/schemas/client-protocol/version
import {
contentTypeForEncoding,
deserializeWithEncoding,
encodingIsBinary,
serializeWithEncoding,
} from "@/serde";
import { httpUserAgent } from "@/utils";
Expand Down Expand Up @@ -120,14 +121,18 @@ export async function sendHttpRequest<
);
}

// Decode metadata based on encoding - only binary encodings have CBOR-encoded metadata
let decodedMetadata: unknown;
if (responseData.metadata && encodingIsBinary(opts.encoding)) {
decodedMetadata = cbor.decode(new Uint8Array(responseData.metadata));
}

// Throw structured error
throw new ActorError(
responseData.group,
responseData.code,
responseData.message,
responseData.metadata
? cbor.decode(new Uint8Array(responseData.metadata))
: undefined,
decodedMetadata,
);
}

Expand Down
51 changes: 50 additions & 1 deletion packages/rivetkit/src/common/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
getRequestEncoding,
getRequestExposeInternalError,
} from "@/actor/router-endpoints";
import type { RunnerConfig } from "@/registry/run-config";
import { getEndpoint } from "@/remote-manager-driver/api-utils";
import { HttpResponseError } from "@/schemas/client-protocol/mod";
import { HTTP_RESPONSE_ERROR_VERSIONED } from "@/schemas/client-protocol/versioned";
import { encodingIsBinary, serializeWithEncoding } from "@/serde";
import { bufferToArrayBuffer } from "@/utils";
import { bufferToArrayBuffer, VERSION } from "@/utils";
import { getLogger, type Logger } from "./log";
import { deconstructError, stringifyError } from "./utils";

Expand Down Expand Up @@ -79,3 +81,50 @@ export function handleRouteError(error: unknown, c: HonoContext) {
// TODO: Remove any
return c.body(output as any, { status: statusCode });
}

/**
* Metadata response interface for the /metadata endpoint
*/
export interface MetadataResponse {
runtime: string;
version: string;
runner?: {
kind:
| { serverless: Record<never, never> }
| { normal: Record<never, never> };
};
/**
* Endpoint that the client should connect to to access this runner.
*
* If defined, will override the endpoint the user has configured on startup.
*
* This is helpful if attempting to connect to a serverless runner, so the serverless runner can define where the main endpoint lives.
*
* This is also helpful for setting up clean redirects as needed.
**/
clientEndpoint?: string;
}

export function handleMetadataRequest(c: HonoContext, runConfig: RunnerConfig) {
const response: MetadataResponse = {
runtime: "rivetkit",
version: VERSION,
runner: {
kind:
runConfig.runnerKind === "serverless"
? { serverless: {} }
: { normal: {} },
},
clientEndpoint: getEndpoint(runConfig),
};

return c.json(response);
}

export function handleHealthRequest(c: HonoContext) {
return c.json({
status: "ok",
runtime: "rivetkit",
version: VERSION,
});
}
6 changes: 5 additions & 1 deletion packages/rivetkit/src/driver-test-suite/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createNodeWebSocket, type NodeWebSocket } from "@hono/node-ws";
import { bundleRequire } from "bundle-require";
import invariant from "invariant";
import { describe } from "vitest";
import { ClientConfigSchema } from "@/client/config";
import type { Encoding, Transport } from "@/client/mod";
import { configureInspectorAccessToken } from "@/inspector/utils";
import { createManagerRouter } from "@/manager/router";
Expand Down Expand Up @@ -230,7 +231,10 @@ export async function createTestRuntime(

// Create router
const managerDriver = driver.manager(registry.config, config);
const client = createClientWithDriver(managerDriver);
const client = createClientWithDriver(
managerDriver,
ClientConfigSchema.parse({}),
);
configureInspectorAccessToken(config, managerDriver);
const { router } = createManagerRouter(
registry.config,
Expand Down
6 changes: 0 additions & 6 deletions packages/rivetkit/src/drivers/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ export function chooseDefaultDriver(runConfig: RunnerConfig): DriverConfig {
);
}

if (runConfig.runnerKind === "serverless" && !runConfig.endpoint) {
throw new UserError(
"Cannot use 'serverless' runnerKind without the 'endpoint' config set.",
);
}

if (runConfig.driver) {
return runConfig.driver;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/rivetkit/src/drivers/file-system/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
handleWebSocketConnect,
} from "@/actor/router-endpoints";
import { createClientWithDriver } from "@/client/client";
import { ClientConfigSchema } from "@/client/config";
import { InlineWebSocketAdapter2 } from "@/common/inline-websocket-adapter2";
import { noopNext } from "@/common/utils";
import type {
Expand Down Expand Up @@ -116,7 +117,10 @@ export class FileSystemManagerDriver implements ManagerDriver {
}

// Actors run on the same node as the manager, so we create a dummy actor router that we route requests to
const inlineClient = createClientWithDriver(this);
const inlineClient = createClientWithDriver(
this,
ClientConfigSchema.parse({}),
);
this.#actorDriver = this.#driverConfig.actor(
registryConfig,
runConfig,
Expand Down
24 changes: 9 additions & 15 deletions packages/rivetkit/src/manager/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ import {
WS_PROTOCOL_TRANSPORT,
} from "@/common/actor-router-consts";
import {
handleHealthRequest,
handleMetadataRequest,
handleRouteError,
handleRouteNotFound,
loggerMiddleware,
type MetadataResponse,
} from "@/common/router";
import {
assertUnreachable,
Expand Down Expand Up @@ -207,13 +210,9 @@ function addServerlessRoutes(
return await actorDriver.serverlessHandleStart(c);
});

router.get("/health", (c) => {
return c.json({
status: "ok",
runtime: "rivetkit",
version: VERSION,
});
});
router.get("/health", (c) => handleHealthRequest(c));

router.get("/metadata", (c) => handleMetadataRequest(c, runConfig));
}

function addManagerRoutes(
Expand Down Expand Up @@ -642,14 +641,9 @@ function addManagerRoutes(
});
}

router.get("/health", (c) => {
return c.json({
status: "ok",
rivetkit: {
version: VERSION,
},
});
});
router.get("/health", (c) => handleHealthRequest(c, runConfig));

router.get("/metadata", (c) => handleMetadataRequest(c, runConfig));

managerDriver.modifyManagerRouter?.(
registryConfig,
Expand Down
Loading
Loading