Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 4696a81

Browse files
committed
Improve error handling
1 parent e974bc5 commit 4696a81

File tree

3 files changed

+47
-36
lines changed

3 files changed

+47
-36
lines changed

server/error.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1+
export type ErrorCallback = {
2+
(
3+
error: unknown,
4+
cause: {
5+
by: "route-api" | "ssr" | "transplie" | "fs" | "middleware";
6+
url: string;
7+
context?: Record<string, unknown>;
8+
},
9+
): Response | void;
10+
};
11+
112
const regStackLoc = /(http:\/\/localhost:60\d{2}\/.+)(:\d+:\d+)/;
213

3-
export const errorHtml = (message: string, type?: string): string => {
14+
export const generateErrorHtml = (message: string, type?: string): string => {
415
const formatMessage = message.split("\n").map((line, i) => {
516
const ret = line.match(regStackLoc);
617
if (ret) {

server/mod.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { RouteRecord } from "../framework/core/route.ts";
66
import log, { LevelName } from "../lib/log.ts";
77
import { getContentType } from "../lib/mime.ts";
88
import util from "../lib/util.ts";
9-
import { errorHtml } from "./error.ts";
9+
import { ErrorCallback, generateErrorHtml } from "./error.ts";
1010
import { DependencyGraph } from "./graph.ts";
1111
import { getDeploymentId, initModuleLoaders, loadImportMap, loadJSXConfig, regFullVersion } from "./helpers.ts";
1212
import { type HTMLRewriterHandlers, loadAndFixIndexHtml } from "./html.ts";
@@ -24,13 +24,7 @@ export type ServerOptions = Omit<ServeInit, "onError"> & {
2424
middlewares?: Middleware[];
2525
fetch?: FetchHandler;
2626
ssr?: SSR;
27-
onError?: (
28-
error: unknown,
29-
cause: {
30-
by: "route-api" | "ssr" | "transplie" | "fs" | "middleware";
31-
url: string;
32-
},
33-
) => Response | void;
27+
onError?: ErrorCallback;
3428
} & AlephConfig;
3529

3630
export const serve = (options: ServerOptions = {}) => {
@@ -73,7 +67,7 @@ export const serve = (options: ServerOptions = {}) => {
7367
if (!(err instanceof Deno.errors.NotFound)) {
7468
log.error(err);
7569
return onError?.(err, { by: "transplie", url: req.url }) ??
76-
new Response(errorHtml(err.stack ?? err.message), {
70+
new Response(generateErrorHtml(err.stack ?? err.message), {
7771
status: 500,
7872
headers: [["Content-Type", "text/html"]],
7973
});
@@ -102,7 +96,7 @@ export const serve = (options: ServerOptions = {}) => {
10296
if (!(err instanceof Deno.errors.NotFound)) {
10397
log.error(err);
10498
return onError?.(err, { by: "transplie", url: req.url }) ??
105-
new Response(errorHtml(err.stack ?? err.message), {
99+
new Response(generateErrorHtml(err.stack ?? err.message), {
106100
status: 500,
107101
headers: [["Content-Type", "text/html"]],
108102
});
@@ -149,7 +143,7 @@ export const serve = (options: ServerOptions = {}) => {
149143
if (!(err instanceof Deno.errors.NotFound)) {
150144
log.error(err);
151145
return onError?.(err, { by: "fs", url: req.url }) ??
152-
new Response(errorHtml(err.stack ?? err.message), {
146+
new Response(generateErrorHtml(err.stack ?? err.message), {
153147
status: 500,
154148
headers: [["Content-Type", "text/html"]],
155149
});
@@ -209,25 +203,27 @@ export const serve = (options: ServerOptions = {}) => {
209203
return new Response(null, { status: code || 302, headers });
210204
},
211205
json: (data: unknown, init?: ResponseInit): Response => {
212-
let hasCustomHeaders = false;
213-
const headers = new Headers(init?.headers);
206+
let headers: Headers | null = null;
214207
ctx.headers.forEach((value, name) => {
208+
if (!headers) {
209+
headers = new Headers(init?.headers);
210+
}
215211
headers.set(name, value);
216-
hasCustomHeaders = true;
217212
});
218-
if (!hasCustomHeaders) {
213+
if (!headers) {
219214
return json(data, init);
220215
}
221216
return json(data, { ...init, headers });
222217
},
223218
content: (body: BodyInit, init?: ResponseInit): Response => {
224-
let hasCustomHeaders = false;
225-
const headers = new Headers(init?.headers);
219+
let headers: Headers | null = null;
226220
ctx.headers.forEach((value, name) => {
221+
if (!headers) {
222+
headers = new Headers(init?.headers);
223+
}
227224
headers.set(name, value);
228-
hasCustomHeaders = true;
229225
});
230-
if (!hasCustomHeaders) {
226+
if (!headers) {
231227
return content(body, init);
232228
}
233229
return content(body, { ...init, headers });
@@ -252,8 +248,8 @@ export const serve = (options: ServerOptions = {}) => {
252248
}
253249
}
254250
} catch (err) {
255-
return onError?.(err, { by: "middleware", url: req.url }) ??
256-
new Response(errorHtml(err.stack ?? err.message), {
251+
return onError?.(err, { by: "middleware", url: req.url, context: ctx }) ??
252+
new Response(generateErrorHtml(err.stack ?? err.message), {
257253
status: 500,
258254
headers: [["Content-Type", "text/html"]],
259255
});
@@ -295,20 +291,20 @@ export const serve = (options: ServerOptions = {}) => {
295291
if (
296292
typeof res === "string" || res instanceof ArrayBuffer || res instanceof ReadableStream
297293
) {
298-
return new Response(res);
294+
return ctx.content(res);
299295
}
300296
if (res instanceof Blob || res instanceof File) {
301-
return new Response(res, { headers: { "Content-Type": res.type } });
297+
return ctx.content(res, { headers: { "Content-Type": res.type } });
302298
}
303299
if (util.isPlainObject(res) || Array.isArray(res) || res === null) {
304-
return json(res);
300+
return ctx.json(res);
305301
}
306-
return new Response(null);
302+
return new Response(null, { headers: ctx.headers });
307303
}
308304
return new Response("Method not allowed", { status: 405 });
309305
}
310306
} catch (err) {
311-
const res = onError?.(err, { by: "route-api", url: req.url });
307+
const res = onError?.(err, { by: "route-api", url: req.url, context: ctx });
312308
if (res instanceof Response) {
313309
return res;
314310
}
@@ -354,7 +350,7 @@ export const serve = (options: ServerOptions = {}) => {
354350
} else {
355351
log.error("read index.html:", err);
356352
return onError?.(err, { by: "fs", url: req.url }) ??
357-
new Response(errorHtml(err.stack ?? err.message), {
353+
new Response(generateErrorHtml(err.stack ?? err.message), {
358354
status: 500,
359355
headers: [["Content-Type", "text/html"]],
360356
});

server/renderer.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { RouteModule, RouteRecord } from "../framework/core/route.ts";
33
import { matchRoutes } from "../framework/core/route.ts";
44
import log from "../lib/log.ts";
55
import util from "../lib/util.ts";
6-
import { errorHtml } from "./error.ts";
6+
import { type ErrorCallback, generateErrorHtml } from "./error.ts";
77
import type { DependencyGraph, Module } from "./graph.ts";
88
import { builtinModuleExts, getDeploymentId, getUnoGenerator } from "./helpers.ts";
99
import type { Element, HTMLRewriterHandlers } from "./html.ts";
@@ -21,17 +21,21 @@ export type SSRContext = {
2121
readonly onError?: (error: unknown) => void;
2222
};
2323

24+
export type SSRFn = {
25+
(ssr: SSRContext): Promise<ReadableStream> | ReadableStream;
26+
};
27+
2428
export type SSR = {
2529
suspense: true;
2630
cacheControl?: "private" | "public";
2731
csp?: string | string[];
28-
render(ssr: SSRContext): Promise<ReadableStream> | ReadableStream;
32+
render: SSRFn;
2933
} | {
3034
suspense?: false;
3135
cacheControl?: "private" | "public";
3236
csp?: string | string[];
33-
render(ssr: SSRContext): Promise<string | ReadableStream> | string | ReadableStream;
34-
} | ((ssr: SSRContext) => Promise<string | ReadableStream> | string | ReadableStream);
37+
render: SSRFn;
38+
} | SSRFn;
3539

3640
export type SSRResult = {
3741
context: SSRContext;
@@ -46,7 +50,7 @@ export type RenderOptions = {
4650
customHTMLRewriter: Map<string, HTMLRewriterHandlers>;
4751
isDev: boolean;
4852
ssr?: SSR;
49-
onError?: (error: unknown, cause: { by: "ssr"; url: string }) => Response | void;
53+
onError?: ErrorCallback;
5054
};
5155

5256
/** The virtual `bootstrapScript` to mark the ssr streaming initial UI is ready */
@@ -153,7 +157,7 @@ export default {
153157
}
154158
headers.append("Cache-Control", `${cc}, max-age=0, must-revalidate`);
155159
headers.append("Content-Type", "text/html; charset=utf-8");
156-
return new Response(errorHtml(message, "SSR"), { headers });
160+
return new Response(generateErrorHtml(message, "SSR"), { headers });
157161
}
158162
} else {
159163
const deployId = getDeploymentId();
@@ -329,7 +333,7 @@ async function initSSR(
329333
ctx: Record<string, unknown>,
330334
routes: RouteRecord,
331335
suspense: boolean,
332-
onError?: (error: unknown, cause: { by: "ssr"; url: string }) => Response | void,
336+
onError?: ErrorCallback,
333337
): Promise<
334338
[
335339
url: URL,
@@ -377,7 +381,7 @@ async function initSSR(
377381
res = await res;
378382
}
379383
} catch (error) {
380-
res = onError?.(error, { by: "ssr", url: req.url });
384+
res = onError?.(error, { by: "ssr", url: req.url, context: ctx });
381385
if (res instanceof Response) {
382386
throw res;
383387
}

0 commit comments

Comments
 (0)