Skip to content

Commit 631249d

Browse files
authored
feat: support universal { fetch } handlers (#1210)
1 parent c272021 commit 631249d

File tree

7 files changed

+94
-45
lines changed

7 files changed

+94
-45
lines changed

src/h3.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import { callMiddleware, normalizeMiddleware } from "./middleware.ts";
55

66
import type { ServerRequest } from "srvx";
77
import type { RouterContext, MatchedRoute } from "rou3";
8-
import type { FetchHandler, H3Config, H3Plugin } from "./types/h3.ts";
8+
import type { RouteHandler, H3Config, H3Plugin } from "./types/h3.ts";
99
import type { H3EventContext } from "./types/context.ts";
10-
import type { EventHandler, Middleware } from "./types/handler.ts";
10+
import type {
11+
EventHandler,
12+
FetchableObject,
13+
FetchHandler,
14+
Middleware,
15+
} from "./types/handler.ts";
1116
import type {
1217
H3Route,
1318
HTTPMethod,
@@ -110,7 +115,7 @@ export const H3Core = /* @__PURE__ */ (() => {
110115

111116
mount(
112117
base: string,
113-
input: FetchHandler | { fetch: FetchHandler } | H3Type,
118+
input: FetchHandler | FetchableObject | H3Type,
114119
): H3Type {
115120
if ("handler" in input) {
116121
if (input._middleware.length > 0) {
@@ -144,15 +149,22 @@ export const H3Core = /* @__PURE__ */ (() => {
144149
on(
145150
method: HTTPMethod | Lowercase<HTTPMethod> | "",
146151
route: string,
147-
handler: EventHandler,
152+
handler: RouteHandler,
148153
opts?: RouteOptions,
149154
): H3Type {
150155
const _method = (method || "").toUpperCase();
151156
route = new URL(route, "http://_").pathname;
157+
if (
158+
typeof handler === "object" &&
159+
typeof (handler as FetchableObject)?.fetch === "function"
160+
) {
161+
const _fetchHandler = (handler as FetchableObject).fetch.bind(handler);
162+
handler = (event) => _fetchHandler(event.req);
163+
}
152164
this._addRoute({
153165
method: _method as HTTPMethod,
154166
route,
155-
handler,
167+
handler: handler as EventHandler,
156168
middleware: opts?.middleware,
157169
meta: { ...(handler as EventHandler).meta, ...opts?.meta },
158170
});

src/handler.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,24 @@ export function defineHandler<
3030
Res = EventHandlerResponse,
3131
>(def: EventHandlerObject<Req, Res>): EventHandlerWithFetch<Req, Res>;
3232

33-
export function defineHandler(arg1: unknown): EventHandlerWithFetch {
34-
if (typeof arg1 === "function") {
35-
return handlerWithFetch(arg1 as EventHandler);
33+
export function defineHandler(
34+
input: EventHandler | EventHandlerObject,
35+
): EventHandlerWithFetch {
36+
if (typeof input === "function") {
37+
return handlerWithFetch(input as EventHandler);
3638
}
37-
const { middleware, handler, meta } = arg1 as EventHandlerObject;
38-
const _handler = handlerWithFetch(
39-
middleware?.length
40-
? (event) => callMiddleware(event, middleware, handler)
41-
: handler,
39+
const handler: EventHandler =
40+
input.handler ||
41+
(input.fetch ? (event) => input.fetch!(event.req) : () => {});
42+
43+
return Object.assign(
44+
handlerWithFetch(
45+
input.middleware?.length
46+
? (event) => callMiddleware(event, input.middleware!, handler)
47+
: handler,
48+
),
49+
input,
4250
);
43-
_handler.meta = meta;
44-
return _handler;
4551
}
4652

4753
type StringHeaders<T> = {
@@ -100,6 +106,9 @@ function handlerWithFetch<
100106
Req extends EventHandlerRequest = EventHandlerRequest,
101107
Res = EventHandlerResponse,
102108
>(handler: EventHandler<Req, Res>): EventHandlerWithFetch<Req, Res> {
109+
if ("fetch" in handler) {
110+
return handler as EventHandlerWithFetch<Req, Res>;
111+
}
103112
return Object.assign(handler, {
104113
fetch: (req: ServerRequest | URL | string): Promise<Response> => {
105114
if (typeof req === "string") {

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export type {
99
PreparedResponse,
1010
RouteOptions,
1111
MiddlewareOptions,
12-
FetchHandler,
12+
RouteHandler,
1313
} from "./types/h3.ts";
1414

1515
export { definePlugin } from "./types/h3.ts";
@@ -38,6 +38,8 @@ export type {
3838
LazyEventHandler,
3939
Middleware,
4040
EventHandlerObject,
41+
FetchHandler,
42+
FetchableObject,
4143
} from "./types/handler.ts";
4244

4345
export {

src/types/h3.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { H3EventContext } from "./context.ts";
2-
import type { EventHandler, Middleware } from "./handler.ts";
2+
import type { EventHandler, FetchableObject, Middleware } from "./handler.ts";
33
import type { HTTPError } from "../error.ts";
44
import type { MaybePromise } from "./_utils.ts";
5-
import type { ServerRequest } from "srvx";
5+
import type { FetchHandler, ServerRequest } from "srvx";
66
import type { MatchedRoute } from "rou3";
77
import type { H3Event } from "../event.ts";
88

@@ -44,7 +44,7 @@ export interface H3Route {
4444
handler: EventHandler;
4545
}
4646

47-
// --- H3 Pluins ---
47+
// --- H3 Plugins ---
4848

4949
export type H3Plugin = (h3: H3) => void;
5050

@@ -56,7 +56,7 @@ export function definePlugin<T = unknown>(
5656

5757
// --- H3 App ---
5858

59-
export type FetchHandler = (req: ServerRequest) => Response | Promise<Response>;
59+
export type RouteHandler = EventHandler | FetchableObject;
6060

6161
export type RouteOptions = {
6262
middleware?: Middleware[];
@@ -152,22 +152,22 @@ export declare class H3 {
152152
on(
153153
method: HTTPMethod | Lowercase<HTTPMethod> | "",
154154
route: string,
155-
handler: EventHandler,
155+
handler: RouteHandler,
156156
opts?: RouteOptions,
157157
): this;
158158

159159
/**
160160
* Register a route handler for all HTTP methods.
161161
*/
162-
all(route: string, handler: EventHandler, opts?: RouteOptions): this;
163-
164-
get(route: string, handler: EventHandler, opts?: RouteOptions): this;
165-
post(route: string, handler: EventHandler, opts?: RouteOptions): this;
166-
put(route: string, handler: EventHandler, opts?: RouteOptions): this;
167-
delete(route: string, handler: EventHandler, opts?: RouteOptions): this;
168-
patch(route: string, handler: EventHandler, opts?: RouteOptions): this;
169-
head(route: string, handler: EventHandler, opts?: RouteOptions): this;
170-
options(route: string, handler: EventHandler, opts?: RouteOptions): this;
171-
connect(route: string, handler: EventHandler, opts?: RouteOptions): this;
172-
trace(route: string, handler: EventHandler, opts?: RouteOptions): this;
162+
all(route: string, handler: RouteHandler, opts?: RouteOptions): this;
163+
164+
get(route: string, handler: RouteHandler, opts?: RouteOptions): this;
165+
post(route: string, handler: RouteHandler, opts?: RouteOptions): this;
166+
put(route: string, handler: RouteHandler, opts?: RouteOptions): this;
167+
delete(route: string, handler: RouteHandler, opts?: RouteOptions): this;
168+
patch(route: string, handler: RouteHandler, opts?: RouteOptions): this;
169+
head(route: string, handler: RouteHandler, opts?: RouteOptions): this;
170+
options(route: string, handler: RouteHandler, opts?: RouteOptions): this;
171+
connect(route: string, handler: RouteHandler, opts?: RouteOptions): this;
172+
trace(route: string, handler: RouteHandler, opts?: RouteOptions): this;
173173
}

src/types/handler.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,12 @@ export interface EventHandler<
1414
meta?: H3RouteMeta;
1515
}
1616

17-
export type EventHandlerFetch<T extends Response | TypedResponse = Response> = (
18-
req: ServerRequest | URL | string,
19-
) => Promise<T>;
20-
2117
export interface EventHandlerObject<
2218
_RequestT extends EventHandlerRequest = EventHandlerRequest,
2319
_ResponseT extends EventHandlerResponse = EventHandlerResponse,
2420
> {
25-
handler: EventHandler<_RequestT, _ResponseT>;
21+
handler?: EventHandler<_RequestT, _ResponseT>;
22+
fetch?: FetchHandler;
2623
middleware?: Middleware[];
2724
meta?: H3RouteMeta;
2825
}
@@ -35,13 +32,6 @@ export interface EventHandlerRequest {
3532

3633
export type EventHandlerResponse<T = unknown> = T | Promise<T>;
3734

38-
export type EventHandlerWithFetch<
39-
_RequestT extends EventHandlerRequest = EventHandlerRequest,
40-
_ResponseT extends EventHandlerResponse = EventHandlerResponse,
41-
> = EventHandler<_RequestT, _ResponseT> & {
42-
fetch: EventHandlerFetch<TypedResponse<_ResponseT, ResponseHeaderMap>>;
43-
};
44-
4535
export type TypedServerRequest<
4636
_RequestT extends EventHandlerRequest = EventHandlerRequest,
4737
> = Omit<ServerRequest, "json" | "headers" | "clone"> &
@@ -53,6 +43,22 @@ export type TypedServerRequest<
5343
"json" | "headers" | "clone"
5444
>;
5545

46+
// --- fetchable ---
47+
48+
export type FetchHandler = (req: ServerRequest) => Response | Promise<Response>;
49+
export type FetchableObject = { fetch: FetchHandler };
50+
51+
export type EventHandlerWithFetch<
52+
_RequestT extends EventHandlerRequest = EventHandlerRequest,
53+
_ResponseT extends EventHandlerResponse = EventHandlerResponse,
54+
> = EventHandler<_RequestT, _ResponseT> & {
55+
fetch: EventHandlerFetch<TypedResponse<_ResponseT, ResponseHeaderMap>>;
56+
};
57+
58+
export type EventHandlerFetch<T extends Response | TypedResponse = Response> = (
59+
req: ServerRequest | URL | string,
60+
) => Promise<T>;
61+
5662
// --- middleware ---
5763

5864
export type Middleware = (

test/app.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@ describeMatrix("app", (t, { it, expect }) => {
230230
expect(await res.text()).toBe("42");
231231
});
232232

233+
it("can use fetchable routes", async () => {
234+
t.app.get("/fetchable", {
235+
fetch: async () => {
236+
return new Response("fetchable");
237+
},
238+
});
239+
const res = await t.fetch("/fetchable");
240+
expect(await res.text()).toBe("fetchable");
241+
});
242+
233243
it("handles next() call with no routes matching", async () => {
234244
t.app.use(() => {});
235245
t.app.use(() => {});

test/handler.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,24 @@ describe("handler.ts", () => {
1818
expect(eventHandler).toBe(handler);
1919
});
2020

21-
it("object syntax", () => {
21+
it("object syntax (h3 handler)", () => {
2222
const handler = vi.fn();
2323
const middleware = [vi.fn()];
2424
const eventHandler = defineHandler({ handler, middleware });
2525
eventHandler({} as H3Event);
2626
expect(middleware[0]).toHaveBeenCalled();
2727
expect(handler).toHaveBeenCalled();
2828
});
29+
30+
it("object syntax (fetchable)", () => {
31+
const fetchHandler = vi.fn();
32+
const middleware = [vi.fn()];
33+
const eventHandler = defineHandler({ fetch: fetchHandler, middleware });
34+
eventHandler({} as H3Event);
35+
expect(eventHandler.fetch).toBe(fetchHandler);
36+
expect(middleware[0]).toHaveBeenCalled();
37+
expect(fetchHandler).toHaveBeenCalled();
38+
});
2939
});
3040

3141
describe("dynamicEventHandler", () => {

0 commit comments

Comments
 (0)