Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.

Commit c3716fa

Browse files
committed
chore(core): add metadata endpoint & client endpoint redirects (#1373)
1 parent 9a3d850 commit c3716fa

File tree

16 files changed

+294
-107
lines changed

16 files changed

+294
-107
lines changed

examples/next-js/src/lib/rivet-client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { createClient, createRivetKit } from "@rivetkit/next-js/client";
33
import type { registry } from "@/rivet/registry";
44

5-
// TODO: Auto-trigger start by sending request to health endpoint
6-
7-
const client = createClient<typeof registry>();
5+
const client = createClient<typeof registry>(
6+
process.env.NEXT_RIVET_ENDPOINT ?? "http://localhost:3000/api/rivet",
7+
);
88
export const { useActor } = createRivetKit(client);

packages/next-js/src/mod.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@ export const toNextHandler = (
88
inputConfig.disableDefaultServer = true;
99

1010
// Configure serverless
11-
const publicUrl =
12-
process.env.NEXT_PUBLIC_SITE_URL ??
13-
process.env.NEXT_PUBLIC_VERCEL_URL ??
14-
`http://127.0.0.1:${process.env.PORT ?? 3000}`;
1511
inputConfig.runnerKind = "serverless";
16-
inputConfig.runEngine = true;
17-
inputConfig.autoConfigureServerless = {
18-
url: `${publicUrl}/api/rivet/start`,
19-
};
12+
13+
// Auto-configure serverless runner if not in prod
14+
if (process.env.NODE_ENV !== "production") {
15+
const publicUrl =
16+
process.env.NEXT_PUBLIC_SITE_URL ??
17+
process.env.NEXT_PUBLIC_VERCEL_URL ??
18+
`http://127.0.0.1:${process.env.PORT ?? 3000}`;
19+
20+
inputConfig.runEngine = true;
21+
inputConfig.autoConfigureServerless = {
22+
url: `${publicUrl}/api/rivet/start`,
23+
};
24+
}
2025

2126
// Next logs this on every request
2227
inputConfig.noWelcome = true;

packages/rivetkit/scripts/dump-openapi.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as fs from "node:fs/promises";
22
import { resolve } from "node:path";
3+
import { ClientConfigSchema } from "@/client/config";
34
import { createFileSystemOrMemoryDriver } from "@/drivers/file-system/mod";
45
import type { ManagerDriver } from "@/manager/driver";
56
import { createManagerRouter } from "@/manager/router";
@@ -39,7 +40,10 @@ function main() {
3940
getOrCreateInspectorAccessToken: unimplemented,
4041
};
4142

42-
const client = createClientWithDriver(managerDriver);
43+
const client = createClientWithDriver(
44+
managerDriver,
45+
ClientConfigSchema.parse({}),
46+
);
4347

4448
const { openapi } = createManagerRouter(
4549
registryConfig,

packages/rivetkit/src/actor/errors.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,6 @@ export class ActorError extends Error {
5454
// Force stringify to return the message
5555
return this.message;
5656
}
57-
58-
/**
59-
* Serialize error for HTTP response
60-
*/
61-
serializeForHttp() {
62-
return {
63-
type: this.code,
64-
message: this.message,
65-
metadata: this.metadata,
66-
};
67-
}
6857
}
6958

7059
export class InternalError extends ActorError {

packages/rivetkit/src/client/client.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,12 @@ export class ClientRaw {
168168

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

179-
this.#encodingKind = opts?.encoding ?? "bare";
180-
this[TRANSPORT_SYMBOL] = opts?.transport ?? "websocket";
175+
this.#encodingKind = config.encoding ?? "bare";
176+
this[TRANSPORT_SYMBOL] = config.transport ?? "websocket";
181177
}
182178

183179
/**
@@ -406,7 +402,7 @@ export type AnyClient = Client<Registry<any>>;
406402

407403
export function createClientWithDriver<A extends Registry<any>>(
408404
driver: ManagerDriver,
409-
config?: ClientConfig,
405+
config: ClientConfig,
410406
): Client<A> {
411407
const client = new ClientRaw(driver, config);
412408

packages/rivetkit/src/client/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export const ClientConfigSchema = z.object({
3939

4040
// See RunConfig.getUpgradeWebSocket
4141
getUpgradeWebSocket: z.custom<GetUpgradeWebSocket>().optional(),
42+
43+
/** Whether to automatically perform health checks when the client is created. */
44+
disableHealthCheck: z.boolean().optional().default(false),
4245
});
4346

4447
export type ClientConfig = z.infer<typeof ClientConfigSchema>;

packages/rivetkit/src/client/utils.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { HTTP_RESPONSE_ERROR_VERSIONED } from "@/schemas/client-protocol/version
88
import {
99
contentTypeForEncoding,
1010
deserializeWithEncoding,
11+
encodingIsBinary,
1112
serializeWithEncoding,
1213
} from "@/serde";
1314
import { httpUserAgent } from "@/utils";
@@ -120,14 +121,18 @@ export async function sendHttpRequest<
120121
);
121122
}
122123

124+
// Decode metadata based on encoding - only binary encodings have CBOR-encoded metadata
125+
let decodedMetadata: unknown;
126+
if (responseData.metadata && encodingIsBinary(opts.encoding)) {
127+
decodedMetadata = cbor.decode(new Uint8Array(responseData.metadata));
128+
}
129+
123130
// Throw structured error
124131
throw new ActorError(
125132
responseData.group,
126133
responseData.code,
127134
responseData.message,
128-
responseData.metadata
129-
? cbor.decode(new Uint8Array(responseData.metadata))
130-
: undefined,
135+
decodedMetadata,
131136
);
132137
}
133138

packages/rivetkit/src/common/router.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import {
55
getRequestEncoding,
66
getRequestExposeInternalError,
77
} from "@/actor/router-endpoints";
8+
import type { RunnerConfig } from "@/registry/run-config";
9+
import { getEndpoint } from "@/remote-manager-driver/api-utils";
810
import { HttpResponseError } from "@/schemas/client-protocol/mod";
911
import { HTTP_RESPONSE_ERROR_VERSIONED } from "@/schemas/client-protocol/versioned";
1012
import { encodingIsBinary, serializeWithEncoding } from "@/serde";
11-
import { bufferToArrayBuffer } from "@/utils";
13+
import { bufferToArrayBuffer, VERSION } from "@/utils";
1214
import { getLogger, type Logger } from "./log";
1315
import { deconstructError, stringifyError } from "./utils";
1416

@@ -79,3 +81,50 @@ export function handleRouteError(error: unknown, c: HonoContext) {
7981
// TODO: Remove any
8082
return c.body(output as any, { status: statusCode });
8183
}
84+
85+
/**
86+
* Metadata response interface for the /metadata endpoint
87+
*/
88+
export interface MetadataResponse {
89+
runtime: string;
90+
version: string;
91+
runner?: {
92+
kind:
93+
| { serverless: Record<never, never> }
94+
| { normal: Record<never, never> };
95+
};
96+
/**
97+
* Endpoint that the client should connect to to access this runner.
98+
*
99+
* If defined, will override the endpoint the user has configured on startup.
100+
*
101+
* This is helpful if attempting to connect to a serverless runner, so the serverless runner can define where the main endpoint lives.
102+
*
103+
* This is also helpful for setting up clean redirects as needed.
104+
**/
105+
clientEndpoint?: string;
106+
}
107+
108+
export function handleMetadataRequest(c: HonoContext, runConfig: RunnerConfig) {
109+
const response: MetadataResponse = {
110+
runtime: "rivetkit",
111+
version: VERSION,
112+
runner: {
113+
kind:
114+
runConfig.runnerKind === "serverless"
115+
? { serverless: {} }
116+
: { normal: {} },
117+
},
118+
clientEndpoint: getEndpoint(runConfig),
119+
};
120+
121+
return c.json(response);
122+
}
123+
124+
export function handleHealthRequest(c: HonoContext) {
125+
return c.json({
126+
status: "ok",
127+
runtime: "rivetkit",
128+
version: VERSION,
129+
});
130+
}

packages/rivetkit/src/driver-test-suite/mod.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createNodeWebSocket, type NodeWebSocket } from "@hono/node-ws";
33
import { bundleRequire } from "bundle-require";
44
import invariant from "invariant";
55
import { describe } from "vitest";
6+
import { ClientConfigSchema } from "@/client/config";
67
import type { Encoding, Transport } from "@/client/mod";
78
import { configureInspectorAccessToken } from "@/inspector/utils";
89
import { createManagerRouter } from "@/manager/router";
@@ -230,7 +231,10 @@ export async function createTestRuntime(
230231

231232
// Create router
232233
const managerDriver = driver.manager(registry.config, config);
233-
const client = createClientWithDriver(managerDriver);
234+
const client = createClientWithDriver(
235+
managerDriver,
236+
ClientConfigSchema.parse({}),
237+
);
234238
configureInspectorAccessToken(config, managerDriver);
235239
const { router } = createManagerRouter(
236240
registry.config,

packages/rivetkit/src/drivers/default.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ export function chooseDefaultDriver(runConfig: RunnerConfig): DriverConfig {
1414
);
1515
}
1616

17-
if (runConfig.runnerKind === "serverless" && !runConfig.endpoint) {
18-
throw new UserError(
19-
"Cannot use 'serverless' runnerKind without the 'endpoint' config set.",
20-
);
21-
}
22-
2317
if (runConfig.driver) {
2418
return runConfig.driver;
2519
}

0 commit comments

Comments
 (0)