Skip to content

Commit 3a0f736

Browse files
committed
feat: add runtime agnostic error handler
Adds a new option `onError` at the root level configuration.
1 parent 37c4e87 commit 3a0f736

File tree

5 files changed

+46
-0
lines changed

5 files changed

+46
-0
lines changed

docs/1.guide/5.options.md

+23
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,29 @@ serve({
9494
> - Use environment variables or secure secret management for production deployments
9595
> - Consider using automatic certificate management (e.g., Let's Encrypt) for production
9696
97+
### `onError`
98+
99+
Runtime agnostic error handler.
100+
101+
> [!NOTE]
102+
>
103+
> This handler will take precedence over runtime specific error handlers.
104+
105+
**Example:**
106+
107+
```js
108+
import { serve } from "srvx";
109+
110+
serve({
111+
onError(error) {
112+
return new Response(`<pre>${error}\n${error.stack}</pre>`, {
113+
headers: { "Content-Type": "text/html" },
114+
});
115+
},
116+
fetch: () => new Response("👋 Hello there!"),
117+
});
118+
```
119+
97120
## Runtime specific options
98121

99122
### Node.js

src/_plugin.ts

+4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ export function wrapFetch(server: Server, fetchHandler: ServerHandler) {
6666
}
6767
}
6868

69+
if (resPromise && server.options.onError) {
70+
resPromise = resPromise.catch(server.options.onError);
71+
}
72+
6973
// Response hooks
7074
if (hasResponseHooks) {
7175
for (const resHook of responseHooks) {

src/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ export interface ServerOptions {
9595
passphrase?: string;
9696
};
9797

98+
/**
99+
* Runtime agnostic error handler (optional).
100+
*
101+
* @note This handler will take precedence over runtime specific error handlers.
102+
*/
103+
onError?: (error: Error) => MaybePromise<Response>;
104+
98105
/**
99106
* Node.js server options.
100107
*/

test/_fixture.ts

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export const server = serve({
2929
},
3030
})) satisfies ServerPlugin,
3131
),
32+
async onError(err) {
33+
return new Response(`onError: ${err.message}`, { status: 500 });
34+
},
3235
async fetch(req) {
3336
const Response =
3437
(globalThis as any).TEST_RESPONSE_CTOR || globalThis.Response;
@@ -70,6 +73,9 @@ export const server = serve({
7073
case "/req-headers-instanceof": {
7174
return new Response(req.headers instanceof Headers ? "yes" : "no");
7275
}
76+
case "/error": {
77+
throw new Error("test error");
78+
}
7379
}
7480
return new Response("404", { status: 404 });
7581
},

test/_tests.ts

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ export function addTests(
6363
expect(await response.text()).toMatch(/ip: ::1|ip: 127.0.0.1/);
6464
});
6565

66+
test("runtime agnostic error handler (onError)", async () => {
67+
const response = await fetch(url("/error"));
68+
expect(response.status).toBe(500);
69+
expect(await response.text()).toBe("onError: test error");
70+
});
71+
6672
describe("plugin", () => {
6773
for (const hook of ["req", "res"]) {
6874
for (const type of ["async", "sync"]) {

0 commit comments

Comments
 (0)