diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts b/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts index 4fa5de09fe..00c8f7f449 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts @@ -21,6 +21,7 @@ import { getRivetNamespace, getRivetRunEngine, getRivetRunEngineVersion, + getRivetRuntimeMode, getRivetToken, isDev, } from "@/utils/env-vars"; @@ -151,6 +152,8 @@ export const RegistryConfigSchema = z * @experimental * * Runtime mode to use when `registry.start()` is called. + * + * Can also be set via RIVET_RUNTIME_MODE. */ mode: RuntimeModeSchema.optional(), /** @@ -225,11 +228,31 @@ export const RegistryConfigSchema = z const isDevEnv = isDev(); const isProductionEnv = getNodeEnv() === "production"; const envStartEngine = getRivetRunEngine(); + const envRuntimeMode = + config.mode === undefined ? getRivetRuntimeMode() : undefined; + const parsedEnvRuntimeMode = + envRuntimeMode === undefined + ? undefined + : RuntimeModeSchema.safeParse(envRuntimeMode); + const configuredRuntimeMode = + config.mode ?? + (parsedEnvRuntimeMode?.success + ? parsedEnvRuntimeMode.data + : undefined); const explicitStartEngine = config.startEngine !== undefined || envStartEngine; let startEngine = true; let runtimeMode: RuntimeMode = "envoy"; + if (parsedEnvRuntimeMode && !parsedEnvRuntimeMode.success) { + ctx.addIssue({ + code: "custom", + message: + "RIVET_RUNTIME_MODE must be either envoy or serverless", + path: ["mode"], + }); + } + // Parse endpoint string (env var fallback is applied via transform above) const parsedEndpoint = config.endpoint ? tryParseEndpoint(ctx, { @@ -250,10 +273,10 @@ export const RegistryConfigSchema = z runtimeMode = "serverless"; } - if (config.mode === "envoy") { + if (configuredRuntimeMode === "envoy") { startEngine = false; runtimeMode = "envoy"; - } else if (config.mode === "serverless") { + } else if (configuredRuntimeMode === "serverless") { startEngine = false; runtimeMode = "serverless"; } @@ -580,7 +603,7 @@ export const DocRegistryConfigSchema = z .optional() .describe("Host to bind the local HTTP server to."), mode: RuntimeModeSchema.optional().describe( - "Runtime mode for registry.start(). Defaults to 'envoy' for local development and 'serverless' when a Rivet endpoint or production environment is configured.", + "Runtime mode for registry.start(). Can also be set via RIVET_RUNTIME_MODE. Defaults to 'envoy' for local development and 'serverless' when a Rivet endpoint or production environment is configured.", ), startEngine: z .boolean() diff --git a/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts b/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts index 7a813c7fa5..fc6402bcb8 100644 --- a/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts +++ b/rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts @@ -24,6 +24,8 @@ export const getRivetRunEngine = (): boolean => getEnvUniversal("RIVET_RUN_ENGINE") === "1"; export const getRivetRunEngineVersion = (): string | undefined => getEnvUniversal("RIVET_RUN_ENGINE_VERSION"); +export const getRivetRuntimeMode = (): string | undefined => + getEnvUniversal("RIVET_RUNTIME_MODE"); export const getRivetEnvoyKind = (): string | undefined => getEnvUniversal("RIVET_ENVOY_KIND"); export const getRivetEnvoyVersion = (): number | undefined => { diff --git a/rivetkit-typescript/packages/rivetkit/tests/registry-start-mode.test.ts b/rivetkit-typescript/packages/rivetkit/tests/registry-start-mode.test.ts index 96154032f8..dfbd46523b 100644 --- a/rivetkit-typescript/packages/rivetkit/tests/registry-start-mode.test.ts +++ b/rivetkit-typescript/packages/rivetkit/tests/registry-start-mode.test.ts @@ -8,6 +8,7 @@ const ENV_KEYS = [ "RIVET_ENGINE", "RIVET_ENVOY_VERSION", "RIVET_RUN_ENGINE", + "RIVET_RUNTIME_MODE", ] as const; type BunGlobal = typeof globalThis & { @@ -129,6 +130,39 @@ describe("registry start mode config", () => { expect(config.endpoint).toBe("https://api.rivet.dev/"); }); + test("RIVET_RUNTIME_MODE overrides endpoint inference", () => { + process.env.RIVET_ENDPOINT = "https://api.rivet.dev"; + process.env.RIVET_RUNTIME_MODE = "envoy"; + + const config = parseRegistryConfig(); + + expect(config.startEngine).toBe(false); + expect(config.runtimeMode).toBe("envoy"); + expect(config.endpoint).toBe("https://api.rivet.dev/"); + }); + + test("explicit mode overrides RIVET_RUNTIME_MODE", () => { + process.env.RIVET_ENDPOINT = "https://api.rivet.dev"; + process.env.RIVET_RUNTIME_MODE = "local"; + + const config = parseRegistryConfig({ + mode: "envoy", + }); + + expect(config.startEngine).toBe(false); + expect(config.runtimeMode).toBe("envoy"); + expect(config.endpoint).toBe("https://api.rivet.dev/"); + }); + + test("invalid RIVET_RUNTIME_MODE is rejected", () => { + process.env.RIVET_ENDPOINT = "https://api.rivet.dev"; + process.env.RIVET_RUNTIME_MODE = "local"; + + expect(() => parseRegistryConfig()).toThrow( + /RIVET_RUNTIME_MODE must be either envoy or serverless/, + ); + }); + test("explicit envoy mode disables local engine startup", () => { const config = parseRegistryConfig({ endpoint: "https://api.rivet.dev", diff --git a/website/src/content/docs/general/environment-variables.mdx b/website/src/content/docs/general/environment-variables.mdx index ee2d6f651b..a191b58bc2 100644 --- a/website/src/content/docs/general/environment-variables.mdx +++ b/website/src/content/docs/general/environment-variables.mdx @@ -34,6 +34,7 @@ These variables configure how clients connect to your actors. | Environment Variable | Description | |---------------------|-------------| +| `RIVET_RUNTIME_MODE` | Runtime mode for `registry.start()`: `envoy` or `serverless`. | | `RIVET_RUN_ENGINE` | Set to `1` to spawn the engine process | | `RIVET_RUN_ENGINE_VERSION` | Version of engine to download |