From 63556f0325da3365b5777d4dc062f28767d8eefe Mon Sep 17 00:00:00 2001 From: Adrian Pascu Date: Tue, 28 Jul 2026 17:39:28 +0200 Subject: [PATCH 1/3] feat: allow a server function error to be observed and replaced before serialization --- .changeset/great-hooks-observe.md | 5 ++ packages/start/src/fns/handler.spec.ts | 71 ++++++++++++++++++++++++++ packages/start/src/fns/handler.ts | 4 ++ packages/start/src/internal.d.ts | 6 +++ 4 files changed, 86 insertions(+) create mode 100644 .changeset/great-hooks-observe.md diff --git a/.changeset/great-hooks-observe.md b/.changeset/great-hooks-observe.md new file mode 100644 index 000000000..cc0a1c454 --- /dev/null +++ b/.changeset/great-hooks-observe.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Allow a server-function error to be observed and replaced before it is serialized into the response diff --git a/packages/start/src/fns/handler.spec.ts b/packages/start/src/fns/handler.spec.ts index 16d2726cc..5d771133b 100644 --- a/packages/start/src/fns/handler.spec.ts +++ b/packages/start/src/fns/handler.spec.ts @@ -1,6 +1,8 @@ import { describe, expect, it, vi, beforeEach } from "vitest"; import { parseCookies } from "h3"; import type { FetchEvent } from "../server/types.ts"; +import { getFetchEvent } from "../server/fetchEvent.ts"; +import { getServerFunction } from "./registration.ts"; vi.mock("h3", () => ({ parseCookies: vi.fn(() => ({})), @@ -25,6 +27,16 @@ vi.mock("../server/fetchEvent.ts", () => ({ mergeResponseHeaders: vi.fn(), })); +vi.mock("./registration.ts", () => ({ + getServerFunction: vi.fn(), + hasServerFunction: vi.fn(() => true), +})); + +vi.mock("./serialization.ts", () => ({ + serializeToJSONStream: vi.fn(() => "serialized"), + serializeToJSStream: vi.fn(() => "serialized"), +})); + function createMockFetchEvent( headers: Record = {}, setCookies: string[] = [], @@ -158,3 +170,62 @@ describe("createSingleFlightHeaders", () => { expect(() => createSingleFlightHeaders(sourceEvent, { some: "value" })).not.toThrow(); }); }); + +describe("handleServerFunction error hook", () => { + const callThrowing = async (thrown: unknown) => { + const request = new Request("http://localhost/_server", { + method: "POST", + headers: { "X-Server-Id": "fn", "X-Server-Instance": "server-fn:1" }, + }); + const h3Event = { res: { headers: new Headers(), status: 200 } }; + vi.mocked(getFetchEvent).mockReturnValue({ + request, + response: { headers: { getSetCookie: () => [] } }, + nativeEvent: h3Event, + locals: {}, + } as unknown as FetchEvent); + vi.mocked(getServerFunction).mockReturnValue(() => { + throw thrown; + }); + const { handleServerFunction } = await import("./handler.ts"); + await handleServerFunction(h3Event as never); + return h3Event; + }; + + beforeEach(() => { + vi.clearAllMocks(); + globalThis.__transformServerFnError = undefined; + }); + + it("passes the thrown value to the hook", async () => { + const thrown = new Error("boom"); + const hook = vi.fn(() => undefined); + globalThis.__transformServerFnError = hook; + + await callThrowing(thrown); + + expect(hook).toHaveBeenCalledWith(thrown); + }); + + it("serializes the replacement the hook returns", async () => { + globalThis.__transformServerFnError = () => new Error("replaced"); + + const h3Event = await callThrowing(new Error("boom")); + + expect(h3Event.res.headers.get("X-Error")).toBe("replaced"); + }); + + it("keeps the original error when the hook returns nothing", async () => { + globalThis.__transformServerFnError = () => undefined; + + const h3Event = await callThrowing(new Error("boom")); + + expect(h3Event.res.headers.get("X-Error")).toBe("boom"); + }); + + it("leaves the response untouched when no hook is registered", async () => { + const h3Event = await callThrowing(new Error("boom")); + + expect(h3Event.res.headers.get("X-Error")).toBe("boom"); + }); +}); diff --git a/packages/start/src/fns/handler.ts b/packages/start/src/fns/handler.ts index 2683087a9..29f647e75 100644 --- a/packages/start/src/fns/handler.ts +++ b/packages/start/src/fns/handler.ts @@ -126,6 +126,10 @@ export async function handleServerFunction(h3Event: H3Event) { } return serializeToJSONStream(result); } catch (x) { + // Nothing else observes a server function failure: it is caught here and + // written straight to the response, so monitoring never sees the crash and + // the thrown value reaches the client as-is. + x = globalThis.__transformServerFnError?.(x) ?? x; if (x instanceof Response) { if (singleFlight && instance) { x = await handleSingleFlight(event, x); diff --git a/packages/start/src/internal.d.ts b/packages/start/src/internal.d.ts index dd4ef0e40..df7f344ba 100644 --- a/packages/start/src/internal.d.ts +++ b/packages/start/src/internal.d.ts @@ -11,4 +11,10 @@ import type { Rollup } from "vite"; declare global { var START_CLIENT_BUNDLE: Rollup.OutputBundle; var USING_SOLID_START_DEV_SERVER: boolean | undefined; + /** + * Called with whatever a server function threw, before it is serialized into + * the response. Return a replacement value to send that instead, or + * `undefined` to leave the original untouched. + */ + var __transformServerFnError: ((thrown: unknown) => unknown) | undefined; } From ce68392c437c25076e386a5190a75b5c06d1e639 Mon Sep 17 00:00:00 2001 From: Adrian Pascu Date: Tue, 28 Jul 2026 18:30:20 +0200 Subject: [PATCH 2/3] refactor: register the error handler through an export instead of globalThis --- .changeset/great-hooks-observe.md | 2 +- packages/start/src/fns/error-handler.ts | 31 ++++++++++++++++++++ packages/start/src/fns/handler.spec.ts | 38 +++++++++++++++++-------- packages/start/src/fns/handler.ts | 6 ++-- packages/start/src/fns/server.ts | 2 ++ packages/start/src/internal.d.ts | 6 ---- 6 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 packages/start/src/fns/error-handler.ts diff --git a/.changeset/great-hooks-observe.md b/.changeset/great-hooks-observe.md index cc0a1c454..d3f75e86f 100644 --- a/.changeset/great-hooks-observe.md +++ b/.changeset/great-hooks-observe.md @@ -1,5 +1,5 @@ --- -"@solidjs/start": patch +"@solidjs/start": minor --- Allow a server-function error to be observed and replaced before it is serialized into the response diff --git a/packages/start/src/fns/error-handler.ts b/packages/start/src/fns/error-handler.ts new file mode 100644 index 000000000..c97853bbe --- /dev/null +++ b/packages/start/src/fns/error-handler.ts @@ -0,0 +1,31 @@ +export type ServerFunctionErrorHandler = (thrown: unknown) => unknown; + +let errorHandler: ServerFunctionErrorHandler | undefined; + +/** + * Registers a handler for errors thrown by server functions, called before the + * error is serialized into the response. + * + * Return a value to send it in place of what was thrown, or `undefined` to + * send the original. Pass `undefined` to unregister. + * + * @example + * ```ts + * import { setServerFunctionErrorHandler } from "@solidjs/start/fns/server"; + * + * setServerFunctionErrorHandler(error => { + * reportToMonitoring(error); + * return new Error("Internal server error"); + * }); + * ``` + */ +export function setServerFunctionErrorHandler( + handler: ServerFunctionErrorHandler | undefined, +): void { + errorHandler = handler; +} + +/** @internal */ +export function applyServerFunctionErrorHandler(thrown: unknown): unknown { + return errorHandler?.(thrown) ?? thrown; +} diff --git a/packages/start/src/fns/handler.spec.ts b/packages/start/src/fns/handler.spec.ts index 5d771133b..01fbc06b6 100644 --- a/packages/start/src/fns/handler.spec.ts +++ b/packages/start/src/fns/handler.spec.ts @@ -171,7 +171,7 @@ describe("createSingleFlightHeaders", () => { }); }); -describe("handleServerFunction error hook", () => { +describe("setServerFunctionErrorHandler", () => { const callThrowing = async (thrown: unknown) => { const request = new Request("http://localhost/_server", { method: "POST", @@ -192,38 +192,52 @@ describe("handleServerFunction error hook", () => { return h3Event; }; - beforeEach(() => { + const register = async (handler: ((thrown: unknown) => unknown) | undefined) => { + const { setServerFunctionErrorHandler } = await import("./error-handler.ts"); + setServerFunctionErrorHandler(handler); + }; + + beforeEach(async () => { vi.clearAllMocks(); - globalThis.__transformServerFnError = undefined; + await register(undefined); }); - it("passes the thrown value to the hook", async () => { + it("passes the thrown value to the registered handler", async () => { const thrown = new Error("boom"); - const hook = vi.fn(() => undefined); - globalThis.__transformServerFnError = hook; + const handler = vi.fn(() => undefined); + await register(handler); await callThrowing(thrown); - expect(hook).toHaveBeenCalledWith(thrown); + expect(handler).toHaveBeenCalledWith(thrown); }); - it("serializes the replacement the hook returns", async () => { - globalThis.__transformServerFnError = () => new Error("replaced"); + it("serializes the replacement the handler returns", async () => { + await register(() => new Error("replaced")); const h3Event = await callThrowing(new Error("boom")); expect(h3Event.res.headers.get("X-Error")).toBe("replaced"); }); - it("keeps the original error when the hook returns nothing", async () => { - globalThis.__transformServerFnError = () => undefined; + it("treats a Response the handler returns as control flow", async () => { + await register(() => new Response(null, { status: 403 })); + + const h3Event = await callThrowing(new Error("boom")); + + expect(h3Event.res.status).toBe(403); + expect(h3Event.res.headers.get("X-Error")).toBe("true"); + }); + + it("keeps the original error when the handler returns nothing", async () => { + await register(() => undefined); const h3Event = await callThrowing(new Error("boom")); expect(h3Event.res.headers.get("X-Error")).toBe("boom"); }); - it("leaves the response untouched when no hook is registered", async () => { + it("leaves the response untouched when no handler is registered", async () => { const h3Event = await callThrowing(new Error("boom")); expect(h3Event.res.headers.get("X-Error")).toBe("boom"); diff --git a/packages/start/src/fns/handler.ts b/packages/start/src/fns/handler.ts index 29f647e75..135ce497c 100644 --- a/packages/start/src/fns/handler.ts +++ b/packages/start/src/fns/handler.ts @@ -15,6 +15,7 @@ import { BODY_FORMAT_KEY, BodyFormat, extractBody, getHeadersAndBody } from "./s import "solid-start:server-fn-manifest"; import { getServerFunction, hasServerFunction } from "./registration.ts"; +import { applyServerFunctionErrorHandler } from "./error-handler.ts"; import type { FetchEvent, PageEvent } from "../server/types.ts"; import { getExpectedRedirectStatus } from "../server/util.ts"; @@ -126,10 +127,7 @@ export async function handleServerFunction(h3Event: H3Event) { } return serializeToJSONStream(result); } catch (x) { - // Nothing else observes a server function failure: it is caught here and - // written straight to the response, so monitoring never sees the crash and - // the thrown value reaches the client as-is. - x = globalThis.__transformServerFnError?.(x) ?? x; + x = applyServerFunctionErrorHandler(x); if (x instanceof Response) { if (singleFlight && instance) { x = await handleSingleFlight(event, x); diff --git a/packages/start/src/fns/server.ts b/packages/start/src/fns/server.ts index de6ecd8a8..2a8643099 100644 --- a/packages/start/src/fns/server.ts +++ b/packages/start/src/fns/server.ts @@ -2,6 +2,8 @@ import { getRequestEvent } from "solid-js/web"; import { provideRequestEvent } from "solid-js/web/storage"; import { registerServerFunction } from "./registration.ts"; +export { setServerFunctionErrorHandler, type ServerFunctionErrorHandler } from "./error-handler.ts"; + interface Registration { id: string; fn: (...args: T) => Promise; diff --git a/packages/start/src/internal.d.ts b/packages/start/src/internal.d.ts index df7f344ba..dd4ef0e40 100644 --- a/packages/start/src/internal.d.ts +++ b/packages/start/src/internal.d.ts @@ -11,10 +11,4 @@ import type { Rollup } from "vite"; declare global { var START_CLIENT_BUNDLE: Rollup.OutputBundle; var USING_SOLID_START_DEV_SERVER: boolean | undefined; - /** - * Called with whatever a server function threw, before it is serialized into - * the response. Return a replacement value to send that instead, or - * `undefined` to leave the original untouched. - */ - var __transformServerFnError: ((thrown: unknown) => unknown) | undefined; } From 38078e8b440b4547c1b37d52fe8f62aa367a8068 Mon Sep 17 00:00:00 2001 From: Adrian Pascu Date: Tue, 28 Jul 2026 20:00:08 +0200 Subject: [PATCH 3/3] refactor: configure the error handler through serverFunctions.onError --- .changeset/great-hooks-observe.md | 2 +- apps/tests/src/e2e/server-function.test.ts | 13 +++++++ .../src/routes/server-function-on-error.tsx | 30 ++++++++++++++++ apps/tests/src/server-fn-error.ts | 17 +++++++++ apps/tests/vite.config.ts | 3 ++ packages/start/src/config/constants.ts | 1 + packages/start/src/config/index.ts | 19 +++++++++- packages/start/src/config/manifest.ts | 7 ++++ packages/start/src/fns/error-handler.ts | 29 ++------------- packages/start/src/fns/handler.spec.ts | 36 ++++++++++--------- packages/start/src/fns/server.ts | 2 -- packages/start/src/server/index.tsx | 1 + packages/start/src/virtual.d.ts | 6 ++++ 13 files changed, 120 insertions(+), 46 deletions(-) create mode 100644 apps/tests/src/routes/server-function-on-error.tsx create mode 100644 apps/tests/src/server-fn-error.ts diff --git a/.changeset/great-hooks-observe.md b/.changeset/great-hooks-observe.md index d3f75e86f..bd974d890 100644 --- a/.changeset/great-hooks-observe.md +++ b/.changeset/great-hooks-observe.md @@ -2,4 +2,4 @@ "@solidjs/start": minor --- -Allow a server-function error to be observed and replaced before it is serialized into the response +Add a `serverFunctions.onError` option naming a module that observes and replaces what a server function threw, before it is serialized into the response diff --git a/apps/tests/src/e2e/server-function.test.ts b/apps/tests/src/e2e/server-function.test.ts index 672092e62..abb9861f7 100644 --- a/apps/tests/src/e2e/server-function.test.ts +++ b/apps/tests/src/e2e/server-function.test.ts @@ -155,4 +155,17 @@ test.describe("server-function", () => { ); }).toPass({ timeout: 15000 }); }); + + test("should send the error the onError module returns in place of the thrown one", async ({ + page, + }) => { + await page.goto("http://localhost:3000/server-function-on-error"); + // Retry the click until it registers post-hydration (clicks aren't auto-retried). + await expect(async () => { + await page.locator("#server-fn-test").click(); + await expect(page.locator("#server-fn-test")).toContainText("replaced by onError", { + timeout: 1000, + }); + }).toPass({ timeout: 15000 }); + }); }); diff --git a/apps/tests/src/routes/server-function-on-error.tsx b/apps/tests/src/routes/server-function-on-error.tsx new file mode 100644 index 000000000..d74125a32 --- /dev/null +++ b/apps/tests/src/routes/server-function-on-error.tsx @@ -0,0 +1,30 @@ +async function serverFnThrowsReplaceable() { + "use server"; + + // `src/server-fn-error.ts` swaps this one out for a different error before it + // is serialized, which is what the client below should end up displaying. + throw new Error("replace me"); +} + +export default function App() { + return ( +
+ { + const el = evt.target as HTMLElement; + serverFnThrowsReplaceable().then( + () => { + el.textContent = "no error"; + }, + err => { + el.textContent = err instanceof Error ? err.message : String(err); + }, + ); + }} + > + Click me + +
+ ); +} diff --git a/apps/tests/src/server-fn-error.ts b/apps/tests/src/server-fn-error.ts new file mode 100644 index 000000000..25d08c3ff --- /dev/null +++ b/apps/tests/src/server-fn-error.ts @@ -0,0 +1,17 @@ +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. + * + * Returning `undefined` sends whatever was thrown, which is what leaves the + * other server-function error tests in this app seeing their own errors. + */ +const onServerFunctionError: ServerFunctionErrorHandler = thrown => { + if (thrown instanceof Error && thrown.message === "replace me") { + return new Error("replaced by onError"); + } + return undefined; +}; + +export default onServerFunctionError; diff --git a/apps/tests/vite.config.ts b/apps/tests/vite.config.ts index dfb3367fe..e7baed41b 100644 --- a/apps/tests/vite.config.ts +++ b/apps/tests/vite.config.ts @@ -11,6 +11,9 @@ export default defineConfig({ serialization: { plugins: "src/seroval-plugins.ts", }, + serverFunctions: { + onError: "src/server-fn-error.ts", + }, env: { server: { load() { diff --git a/packages/start/src/config/constants.ts b/packages/start/src/config/constants.ts index 33bdd12a2..c81847133 100644 --- a/packages/start/src/config/constants.ts +++ b/packages/start/src/config/constants.ts @@ -9,6 +9,7 @@ export const VIRTUAL_MODULES = { middleware: "solid-start:middleware", serovalPlugins: "solid-start:seroval-plugins", serverFnManifest: "solid-start:server-fn-manifest", + serverFnErrorHandler: "solid-start:server-fn-error-handler", clientEntry: "solid-start:client-entry", serverEntry: "solid-start:server-entry", app: "solid-start:app", diff --git a/packages/start/src/config/index.ts b/packages/start/src/config/index.ts index 73527bf8d..b29609f2c 100644 --- a/packages/start/src/config/index.ts +++ b/packages/start/src/config/index.ts @@ -144,7 +144,24 @@ export interface SolidStartOptions { * Options controlling which files are processed as server functions * (inclusion / exclusion filters for the `"use server"` transform). */ - serverFunctions?: Pick; + 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. + * + * 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. + * + * 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" + */ + onError?: string; + }; } const absolute = (path: string, root: string) => diff --git a/packages/start/src/config/manifest.ts b/packages/start/src/config/manifest.ts index 5ffa2041b..6f90c2f54 100644 --- a/packages/start/src/config/manifest.ts +++ b/packages/start/src/config/manifest.ts @@ -41,6 +41,11 @@ export function manifest(start: SolidStartOptions): PluginOption { if (plugins) return await this.resolve(plugins); return `\0${VIRTUAL_MODULES.serovalPlugins}`; } + if (id === VIRTUAL_MODULES.serverFnErrorHandler) { + const onError = start.serverFunctions?.onError; + if (onError) return await this.resolve(onError); + return `\0${VIRTUAL_MODULES.serverFnErrorHandler}`; + } }, async load(id) { if (id === `\0${VIRTUAL_MODULES.clientViteManifest}`) { @@ -94,6 +99,8 @@ export function manifest(start: SolidStartOptions): PluginOption { return `export const clientViteManifest = ${JSON.stringify(clientViteManifest)};`; } else if (id === `\0${VIRTUAL_MODULES.middleware}`) return "export default {};"; else if (id === `\0${VIRTUAL_MODULES.serovalPlugins}`) return "export default [];"; + else if (id === `\0${VIRTUAL_MODULES.serverFnErrorHandler}`) + return "export default undefined;"; else if (id.startsWith("/@manifest")) { if (this.environment.mode !== "dev") throw new Error("@manifest queries are only allowed in dev"); diff --git a/packages/start/src/fns/error-handler.ts b/packages/start/src/fns/error-handler.ts index c97853bbe..ce98ee4e2 100644 --- a/packages/start/src/fns/error-handler.ts +++ b/packages/start/src/fns/error-handler.ts @@ -1,31 +1,8 @@ -export type ServerFunctionErrorHandler = (thrown: unknown) => unknown; - -let errorHandler: ServerFunctionErrorHandler | undefined; +import onServerFunctionError from "solid-start:server-fn-error-handler"; -/** - * Registers a handler for errors thrown by server functions, called before the - * error is serialized into the response. - * - * Return a value to send it in place of what was thrown, or `undefined` to - * send the original. Pass `undefined` to unregister. - * - * @example - * ```ts - * import { setServerFunctionErrorHandler } from "@solidjs/start/fns/server"; - * - * setServerFunctionErrorHandler(error => { - * reportToMonitoring(error); - * return new Error("Internal server error"); - * }); - * ``` - */ -export function setServerFunctionErrorHandler( - handler: ServerFunctionErrorHandler | undefined, -): void { - errorHandler = handler; -} +export type ServerFunctionErrorHandler = (thrown: unknown) => unknown; /** @internal */ export function applyServerFunctionErrorHandler(thrown: unknown): unknown { - return errorHandler?.(thrown) ?? thrown; + return onServerFunctionError?.(thrown) ?? thrown; } diff --git a/packages/start/src/fns/handler.spec.ts b/packages/start/src/fns/handler.spec.ts index 01fbc06b6..f4a44324d 100644 --- a/packages/start/src/fns/handler.spec.ts +++ b/packages/start/src/fns/handler.spec.ts @@ -18,6 +18,16 @@ vi.mock("solid-js/web/storage", () => ({ vi.mock("solid-start:server-fn-manifest", () => ({})); +const configuredErrorHandler = vi.hoisted(() => ({ + current: undefined as ((thrown: unknown) => unknown) | undefined, +})); + +vi.mock("solid-start:server-fn-error-handler", () => ({ + get default() { + return configuredErrorHandler.current; + }, +})); + vi.mock("../server/handler.ts", () => ({ createPageEvent: vi.fn(), })); @@ -171,7 +181,7 @@ describe("createSingleFlightHeaders", () => { }); }); -describe("setServerFunctionErrorHandler", () => { +describe("the configured server function error handler", () => { const callThrowing = async (thrown: unknown) => { const request = new Request("http://localhost/_server", { method: "POST", @@ -192,28 +202,22 @@ describe("setServerFunctionErrorHandler", () => { return h3Event; }; - const register = async (handler: ((thrown: unknown) => unknown) | undefined) => { - const { setServerFunctionErrorHandler } = await import("./error-handler.ts"); - setServerFunctionErrorHandler(handler); - }; - - beforeEach(async () => { + beforeEach(() => { vi.clearAllMocks(); - await register(undefined); + configuredErrorHandler.current = undefined; }); - it("passes the thrown value to the registered handler", async () => { + it("passes the thrown value to the handler", async () => { const thrown = new Error("boom"); - const handler = vi.fn(() => undefined); - await register(handler); + configuredErrorHandler.current = vi.fn(() => undefined); await callThrowing(thrown); - expect(handler).toHaveBeenCalledWith(thrown); + expect(configuredErrorHandler.current).toHaveBeenCalledWith(thrown); }); it("serializes the replacement the handler returns", async () => { - await register(() => new Error("replaced")); + configuredErrorHandler.current = () => new Error("replaced"); const h3Event = await callThrowing(new Error("boom")); @@ -221,7 +225,7 @@ describe("setServerFunctionErrorHandler", () => { }); it("treats a Response the handler returns as control flow", async () => { - await register(() => new Response(null, { status: 403 })); + configuredErrorHandler.current = () => new Response(null, { status: 403 }); const h3Event = await callThrowing(new Error("boom")); @@ -230,14 +234,14 @@ describe("setServerFunctionErrorHandler", () => { }); it("keeps the original error when the handler returns nothing", async () => { - await register(() => undefined); + configuredErrorHandler.current = () => undefined; const h3Event = await callThrowing(new Error("boom")); expect(h3Event.res.headers.get("X-Error")).toBe("boom"); }); - it("leaves the response untouched when no handler is registered", async () => { + it("leaves the response untouched when no handler is configured", async () => { const h3Event = await callThrowing(new Error("boom")); expect(h3Event.res.headers.get("X-Error")).toBe("boom"); diff --git a/packages/start/src/fns/server.ts b/packages/start/src/fns/server.ts index 2a8643099..de6ecd8a8 100644 --- a/packages/start/src/fns/server.ts +++ b/packages/start/src/fns/server.ts @@ -2,8 +2,6 @@ import { getRequestEvent } from "solid-js/web"; import { provideRequestEvent } from "solid-js/web/storage"; import { registerServerFunction } from "./registration.ts"; -export { setServerFunctionErrorHandler, type ServerFunctionErrorHandler } from "./error-handler.ts"; - interface Registration { id: string; fn: (...args: T) => Promise; diff --git a/packages/start/src/server/index.tsx b/packages/start/src/server/index.tsx index f7b1be5e3..1f904de6e 100644 --- a/packages/start/src/server/index.tsx +++ b/packages/start/src/server/index.tsx @@ -3,6 +3,7 @@ export { getServerFunctionMeta } from "../shared/serverFunction.ts"; export { StartServer } from "./StartServer.tsx"; export { decorateHandler, decorateMiddleware } from "./fetchEvent.ts"; export { createHandler } from "./handler.ts"; +export type { ServerFunctionErrorHandler } from "../fns/error-handler.ts"; export type { APIEvent, diff --git a/packages/start/src/virtual.d.ts b/packages/start/src/virtual.d.ts index d62ced0cd..c7b55d783 100644 --- a/packages/start/src/virtual.d.ts +++ b/packages/start/src/virtual.d.ts @@ -29,3 +29,9 @@ declare module "solid-start:middleware" { declare module "solid-start:seroval-plugins" { export default SerovalPlugins as import("seroval").Plugin[]; } + +declare module "solid-start:server-fn-error-handler" { + export default ServerFunctionErrorHandler as + | import("./fns/error-handler.ts").ServerFunctionErrorHandler + | undefined; +}