diff --git a/apps/tests/src/server-fn-error.ts b/apps/tests/src/server-fn-error.ts index 25d08c3ff..3d3628dd5 100644 --- a/apps/tests/src/server-fn-error.ts +++ b/apps/tests/src/server-fn-error.ts @@ -1,8 +1,9 @@ import type { ServerFunctionErrorHandler } from "@solidjs/start/server"; /** - * Wired up through `serverFunctions.onError`. Unlike `seroval-plugins.ts` this - * module is bundled into the server only, so it may reach for server-only code. + * Wired up through `serverFunctions.onError`. Unlike `src/seroval-plugins.ts`, + * this module is bundled into the server only, so it may reach for server-only + * code. * * Returning `undefined` sends whatever was thrown, which is what leaves the * other server-function error tests in this app seeing their own errors. diff --git a/packages/start/src/config/index.ts b/packages/start/src/config/index.ts index b29609f2c..34e06da46 100644 --- a/packages/start/src/config/index.ts +++ b/packages/start/src/config/index.ts @@ -141,22 +141,45 @@ export interface SolidStartOptions { env?: EnvPluginOptions; /** - * Options controlling which files are processed as server functions - * (inclusion / exclusion filters for the `"use server"` transform). + * Options for server functions: which files the `"use server"` transform + * processes (inclusion / exclusion filters), and what happens when a server + * function throws. */ serverFunctions?: Pick & { /** - * Path to a module whose default export is called with whatever a server - * function threw, before it is serialized into the response. Return a - * value to send it in place of what was thrown, or `undefined` to send the - * original. + * Path to a module whose default export handles whatever a server function + * throws, before it reaches the client. Use it to report failures to a + * monitoring service, or to replace an error carrying internal detail with + * one that is safe to send. * - * Naming the module here rather than registering a handler at runtime - * keeps the app in sole control of it: no dependency can reach into the - * running server and take over reporting. + * Only server function calls made over the network run through it. A + * server function called during rendering runs in process and throws + * straight to its caller, and errors from API routes never reach it + * either. * - * The module is bundled into the server only, so it may import server-only - * code such as a monitoring SDK. + * The export is called synchronously with the thrown value, and what it + * returns decides what the client sees: + * + * - `undefined` (or `null`) sends what was thrown, unchanged. + * - A `Response` is passed through unchanged, which keeps a thrown + * `redirect()` working. + * - Any other value is sent in place of what was thrown, and the client + * call rejects with it. + * + * Control flow reaches the export the same way errors do, so a handler + * that replaces everything it sees turns redirects into errors. + * + * Whatever is sent gets serialized to the client along with its own + * properties, so an error meant to be safe to expose must not carry + * internal detail. + * + * The return value is not awaited, so make the export a plain function + * rather than an `async` one, and report failures with calls that do not + * need awaiting. + * + * Type the export as `ServerFunctionErrorHandler` from + * `@solidjs/start/server`. The module is bundled into the server only, so + * it may import server-only code such as a monitoring SDK. * * @example "src/server-fn-error.ts" */ diff --git a/packages/start/src/fns/error-handler.ts b/packages/start/src/fns/error-handler.ts index ce98ee4e2..6ba8d2ba8 100644 --- a/packages/start/src/fns/error-handler.ts +++ b/packages/start/src/fns/error-handler.ts @@ -1,8 +1,38 @@ import onServerFunctionError from "solid-start:server-fn-error-handler"; +/** + * Handles whatever a server function threw, before it reaches the client. Use + * it to type the default export of the module named by the + * `serverFunctions.onError` option in `vite.config.ts`. + * + * Called synchronously, and the value it returns is not awaited, so it cannot + * be an `async` function. + * + * @param thrown The value the server function threw. This is a `Response` when + * the server function threw control flow such as a `redirect()`. + * @returns `undefined` (or `null`) to send `thrown` unchanged, a `Response` to + * pass control flow through untouched, or any other value to send in place of + * `thrown`. + * + * @example + * ```ts + * // src/server-fn-error.ts + * import type { ServerFunctionErrorHandler } from "@solidjs/start/server"; + * import { captureException } from "./your-monitoring-client"; + * + * const onServerFunctionError: ServerFunctionErrorHandler = thrown => { + * // redirect() throws a Response, so returning it preserves that control flow. + * if (thrown instanceof Response) return thrown; + * + * captureException(thrown); // or console.error, or any reporter + * return new Error("Something went wrong"); + * }; + * + * export default onServerFunctionError; + * ``` + */ export type ServerFunctionErrorHandler = (thrown: unknown) => unknown; -/** @internal */ export function applyServerFunctionErrorHandler(thrown: unknown): unknown { return onServerFunctionError?.(thrown) ?? thrown; }