diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/native.ts b/rivetkit-typescript/packages/rivetkit/src/registry/native.ts index cee301a9e2..4d3a55b45e 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/native.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/native.ts @@ -224,10 +224,36 @@ export async function loadAutoRuntime( return (await loaders.loadWasm(config.wasm)).runtime; } + let nativeError: unknown; try { return (await loaders.loadNative()).runtime; - } catch { + } catch (error) { + nativeError = error; + // Native is the expected runtime on a node-like host, so this is the + // actionable error even when the wasm fallback goes on to succeed. + // Discarding it hides causes such as a platform binding that npm + // silently skipped, which then resurfaces as an unrelated wasm error. + logger().warn({ + msg: "native runtime failed to load; falling back to wasm", + error: stringifyError(error), + }); + } + + try { return (await loaders.loadWasm(config.wasm)).runtime; + } catch (wasmError) { + // Report both, native first. The wasm failure on a node-like host is + // usually just its loader fetching over `file://`, which says nothing + // about why native was unavailable. + throw new RivetError( + "config", + "runtime_unavailable", + `RivetKit could not load a core runtime. Native runtime: ${stringifyError(nativeError)} Wasm runtime: ${stringifyError(wasmError)}`, + { + public: true, + statusCode: 500, + }, + ); } } diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/runtime.test.ts b/rivetkit-typescript/packages/rivetkit/src/registry/runtime.test.ts index 1921809190..2d11a51818 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/runtime.test.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/runtime.test.ts @@ -1,5 +1,8 @@ import { describe, expect, test } from "vitest"; +import type { RegistryConfig } from "./config"; +import { loadAutoRuntime, type RuntimeLoaders } from "./native"; import { + type CoreRuntime, normalizeRuntimeSqlExecuteResult, type RuntimeSqlBindParam, type RuntimeSqlBindParams, @@ -48,3 +51,74 @@ describe("runtime SQL boundary", () => { expect(normalizeRuntimeSqlExecuteResult(base)).toEqual(base); }); }); + +describe("loadAutoRuntime failure reporting", () => { + const wasmRuntime = { kind: "wasm" } as unknown as CoreRuntime; + const nativeRuntime = { kind: "napi" } as unknown as CoreRuntime; + const config = {} as RegistryConfig; + + function loaders(overrides: Partial): RuntimeLoaders { + return { + detectHost: () => "node-like", + loadNative: async () => ({ runtime: nativeRuntime }) as never, + loadWasm: async () => ({ runtime: wasmRuntime }) as never, + ...overrides, + }; + } + + test("prefers native when it loads", async () => { + const runtime = await loadAutoRuntime(config, loaders({})); + expect(runtime).toBe(nativeRuntime); + }); + + test("falls back to wasm when native fails", async () => { + const runtime = await loadAutoRuntime( + config, + loaders({ + loadNative: async () => { + throw new Error("missing platform binding"); + }, + }), + ); + expect(runtime).toBe(wasmRuntime); + }); + + test("reports the native cause when both runtimes fail", async () => { + // The native failure is the actionable one. Before this was reported, + // a skipped platform binding surfaced only as the wasm loader's + // unrelated `file://` fetch error. + const promise = loadAutoRuntime( + config, + loaders({ + loadNative: async () => { + throw new Error( + "Cannot find module '@rivetkit/rivetkit-napi-linux-x64-musl'", + ); + }, + loadWasm: async () => { + throw new Error("fetch failed"); + }, + }), + ); + await expect(promise).rejects.toThrow(/rivetkit-napi-linux-x64-musl/); + await expect(promise).rejects.toThrow(/fetch failed/); + }); + + test("uses wasm directly on an edge-like host without touching native", async () => { + let nativeCalls = 0; + const runtime = await loadAutoRuntime( + config, + loaders({ + detectHost: () => "edge-like", + loadNative: async () => { + nativeCalls += 1; + throw new Error( + "native must not be attempted on edge hosts", + ); + }, + }), + ); + expect(runtime).toBe(wasmRuntime); + expect(nativeCalls).toBe(0); + }); +});