Skip to content
Open
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
74 changes: 43 additions & 31 deletions rivetkit-typescript/packages/rivetkit/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,27 @@ async function ensureLocalRunnerConfig(config: RegistryConfig): Promise<void> {
const clientConfig = convertRegistryConfigToClientConfig(config);
const dcsRes = await getDatacenters(clientConfig);

await updateRunnerConfig(clientConfig, config.envoy.poolName, {
datacenters: Object.fromEntries(
dcsRes.datacenters.map((dc) => [
dc.name,
{
normal: {},
drain_on_version_upgrade: true,
},
]),
),
});
const datacenters = Object.fromEntries(
dcsRes.datacenters.map((dc) => [
dc.name,
{
normal: {},
drain_on_version_upgrade: true,
},
]),
);

// Register the main pool as well as the native database pool. The native
// database envoy (used by any actor that opens `rivetkit/db`) registers
// under `${poolName}-native-db` via EngineActorDriver, so the engine must
// have a runner config for that pool or envoy registration fails with
// `no_runner_config`.
await Promise.all([
updateRunnerConfig(clientConfig, config.envoy.poolName, { datacenters }),
updateRunnerConfig(clientConfig, `${config.envoy.poolName}-native-db`, {
datacenters,
}),
]);
}

export class Runtime<A extends RegistryActors> {
Expand All @@ -62,7 +72,7 @@ export class Runtime<A extends RegistryActors> {
#actorDriver?: EngineActorDriver;
#startKind?: StartKind;

managerPort?: number;
httpPort?: number;
#serverlessRouter?: ReturnType<typeof buildServerlessRouter>["router"];

get config() {
Expand All @@ -77,12 +87,12 @@ export class Runtime<A extends RegistryActors> {
registry: Registry<A>,
config: RegistryConfig,
engineClient: EngineControlClient,
managerPort?: number,
httpPort?: number,
) {
this.#registry = registry;
this.#config = config;
this.#engineClient = engineClient;
this.managerPort = managerPort;
this.httpPort = httpPort;
}

static async create<A extends RegistryActors>(
Expand All @@ -99,7 +109,7 @@ export class Runtime<A extends RegistryActors> {
}

const shouldSpawnEngine =
config.serverless.spawnEngine || (config.serveManager && !config.endpoint);
config.serverless.spawnEngine || (config.serveHttp && !config.endpoint);
if (shouldSpawnEngine) {
config.endpoint = ENGINE_ENDPOINT;

Expand All @@ -117,9 +127,9 @@ export class Runtime<A extends RegistryActors> {
);
await ensureLocalRunnerConfig(config);

let managerPort: number | undefined;
if (config.serveManager) {
const configuredManagerPort = config.managerPort;
let httpPort: number | undefined;
if (config.serveHttp) {
const configuredHttpPort = config.httpPort;
const serveRuntime = detectRuntime();
let upgradeWebSocket: any;
const getUpgradeWebSocket: GetUpgradeWebSocket = () =>
Expand All @@ -133,27 +143,27 @@ export class Runtime<A extends RegistryActors> {
serveRuntime,
);

managerPort = await findFreePort(config.managerPort);
httpPort = await findFreePort(config.httpPort);

if (managerPort !== configuredManagerPort) {
if (httpPort !== configuredHttpPort) {
logger().warn({
msg: `port ${configuredManagerPort} is in use, using ${managerPort}`,
msg: `port ${configuredHttpPort} is in use, using ${httpPort}`,
});
}

logger().debug({
msg: "serving runtime router",
port: managerPort,
msg: "serving HTTP router",
port: httpPort,
});

if (
config.publicEndpoint ===
`http://127.0.0.1:${configuredManagerPort}`
`http://127.0.0.1:${configuredHttpPort}`
) {
config.publicEndpoint = `http://127.0.0.1:${managerPort}`;
config.publicEndpoint = `http://127.0.0.1:${httpPort}`;
config.serverless.publicEndpoint = config.publicEndpoint;
}
config.managerPort = managerPort;
config.httpPort = httpPort;

let serverApp = runtimeRouter;
if (config.publicDir) {
Expand Down Expand Up @@ -181,7 +191,7 @@ export class Runtime<A extends RegistryActors> {

const out = await crossPlatformServe(
config,
managerPort,
httpPort,
serverApp,
serveRuntime,
);
Expand All @@ -196,7 +206,7 @@ export class Runtime<A extends RegistryActors> {
}
}

const runtime = new Runtime(registry, config, engineClient, managerPort);
const runtime = new Runtime(registry, config, engineClient, httpPort);

logger().info({
msg: "rivetkit ready",
Expand Down Expand Up @@ -247,9 +257,11 @@ export class Runtime<A extends RegistryActors> {
#printWelcome(): void {
if (this.#config.noWelcome) return;

const inspectorUrl = this.managerPort
? getInspectorUrl(this.#config, this.managerPort)
: undefined;
// Inspector URL falls back through getInspectorUrl's own chain
// (inspector.defaultEndpoint → config.endpoint → HTTP port). In
// envoy mode there is no HTTP port, but the engine serves `/ui/`
// on its endpoint so the inspector stays reachable.
const inspectorUrl = getInspectorUrl(this.#config, this.httpPort ?? 0);

console.log();
console.log(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ export async function actorGateway(
// Strip basePath from the request path
let strippedPath = c.req.path;
if (
config.managerBasePath &&
strippedPath.startsWith(config.managerBasePath)
config.httpBasePath &&
strippedPath.startsWith(config.httpBasePath)
) {
strippedPath = strippedPath.slice(config.managerBasePath.length);
strippedPath = strippedPath.slice(config.httpBasePath.length);
// Ensure the path starts with /
if (!strippedPath.startsWith("/")) {
strippedPath = `/${strippedPath}`;
Expand Down
2 changes: 1 addition & 1 deletion rivetkit-typescript/packages/rivetkit/src/actor/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ export class InvalidCanPublishResponse extends ActorError {
}
}

// Manager-specific errors
// HTTP server specific errors
export class MissingActorHeader extends ActorError {
constructor() {
super(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const HEADER_ACTOR_ID = "x-rivet-actor";

export const HEADER_RIVET_TOKEN = "x-rivet-token";

// MARK: Manager Gateway Headers
// MARK: HTTP Server Gateway Headers
export const HEADER_RIVET_TARGET = "x-rivet-target";
export const HEADER_RIVET_ACTOR = "x-rivet-actor";
export const HEADER_RIVET_NAMESPACE = "x-rivet-namespace";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export async function importWebSocket(): Promise<typeof WebSocket> {
// Node.js environment
try {
const moduleName = "ws";
const ws = await import(/* webpackIgnore: true */ moduleName);
const ws = await import(
/* webpackIgnore: true */ /* @vite-ignore */ moduleName
);
_WebSocket = ws.default as unknown as typeof WebSocket;
logger().debug("using websocket from npm");
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ async function readAfterSleepCycle<T extends SleepSnapshot>(
// `getCounts`
//
// To fix this, we need to imeplment some event system to be able to check for
// when an actor has slept. OR we can expose an HTTP endpoint on the manager
// when an actor has slept. OR we can expose an HTTP endpoint on the HTTP router
// for `.test` that checks if na actor is sleeping that we can poll.
export function runActorSleepTests(driverTestConfig: DriverTestConfig) {
const describeSleepTests = driverTestConfig.skip?.sleep
Expand Down
22 changes: 22 additions & 0 deletions rivetkit-typescript/packages/rivetkit/src/engine-process/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,28 @@ export async function ensureEngineProcess(
stderrStream.end();
});

// Terminate child when parent exits so tsx --watch restarts and
// Ctrl+C don't leave orphaned engine processes holding port 6420.
let cleanedUp = false;
const terminateChild = (parentSignal?: NodeJS.Signals) => {
if (cleanedUp) return;
cleanedUp = true;
try {
if (child.exitCode === null && !child.killed) {
child.kill(parentSignal ?? "SIGTERM");
}
} catch {
// Best effort; child may already be gone.
}
};
process.once("exit", () => terminateChild());
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
process.once(sig, () => {
terminateChild(sig);
process.exit(sig === "SIGINT" ? 130 : sig === "SIGTERM" ? 143 : 129);
});
}

// Wait for engine to be ready
await waitForEngineHealth();

Expand Down
11 changes: 9 additions & 2 deletions rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,18 @@ export const secureInspector = (config: RegistryConfig) =>

export function getInspectorUrl(
config: RegistryConfig,
managerPort: number,
httpPort: number,
): string | undefined {
if (!config.inspector.enabled) return undefined;

// Prefer the engine endpoint for the inspector URL when we know the engine
// serves the UI locally. The engine always hosts `/ui/` on its own port
// (6420 by default) so pointing users at the engine URL keeps the
// inspector discoverable at the standard Rivet port even when the
// local RivetKit HTTP server runs on a different port (e.g. 8080).
const base =
config.inspector.defaultEndpoint ?? `http://127.0.0.1:${managerPort}`;
config.inspector.defaultEndpoint ??
config.endpoint ??
`http://127.0.0.1:${httpPort}`;
return new URL("/ui/", base).href;
}
Loading
Loading