Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export {
export { defineRoute } from "./utils/route.ts";
export type { RouteDefinition } from "./utils/route.ts";

// Middleware Route
export { defineMiddlewareRoute } from "./utils/middleware-route.ts";
export type { MiddlewareRouteDefinition } from "./utils/middleware-route.ts";

// Request
export {
getRequestHost,
Expand Down
14 changes: 14 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,30 @@ export function normalizeMiddleware(
opts: MiddlewareOptions & { route?: string } = {},
): Middleware {
const matcher = createMatcher(opts);
const hasMeta = opts.meta && Object.keys(opts.meta).length > 0;

if (
!matcher &&
!hasMeta &&
(input.length > 1 || input.constructor?.name === "AsyncFunction")
) {
return input; // Fast path: async or with explicit next() and no matcher filters
}

return (event, next) => {
if (matcher && !matcher(event)) {
return next();
}

// Add meta to event context if provided
if (hasMeta) {
event.context.matchedMiddleware = event.context.matchedMiddleware || [];
event.context.matchedMiddleware.push({
route: opts.route,
meta: opts.meta,
});
}

const res = input(event, next);
return res === undefined || res === kNotFound ? next() : res;
};
Expand Down
12 changes: 11 additions & 1 deletion src/types/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Session } from "../utils/session.ts";
import type { H3Route } from "./h3.ts";
import type { H3Route, H3RouteMeta } from "./h3.ts";

export interface H3EventContext extends Record<string, any> {
/* Matched router parameters */
Expand All @@ -15,6 +15,16 @@ export interface H3EventContext extends Record<string, any> {
*/
matchedRoute?: H3Route;

/**
* Matched middleware with their metadata
*
* @experimental The object structure may change in non-major version.
*/
matchedMiddleware?: Array<{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove it from this PR. We need runtime implementation and one PR per feature!

route?: string;
meta?: H3RouteMeta;
}>;

/* Cached session data */
sessions?: Record<string, Session>;

Expand Down
1 change: 1 addition & 0 deletions src/types/h3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export type RouteOptions = {
export type MiddlewareOptions = {
method?: string;
match?: (event: H3Event) => boolean;
meta?: H3RouteMeta;
};

export declare class H3 {
Expand Down
81 changes: 81 additions & 0 deletions src/utils/middleware-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { HTTPMethod } from "../types/h3.ts";
import type { Middleware } from "../types/handler.ts";
import type { H3Plugin, H3 } from "../types/h3.ts";
import type { H3Event } from "../event.ts";
import { defineMiddleware } from "../middleware.ts";

/**
* Middleware route definition options
*/
export interface MiddlewareRouteDefinition {
/**
* Path pattern for the middleware, e.g. '/api/**'
*/
path?: string;

/**
* HTTP methods to apply the middleware to
*/
methods?: HTTPMethod[];

/**
* Middleware handler function
*/
handler: Middleware;

/**
* Additional middleware metadata
*/
meta?: Record<string, unknown>;
}

/**
* Define a middleware route as a plugin that can be registered with app.register()
*
* @example
* ```js
* const authMiddleware = defineMiddlewareRoute({
* path: '/api/**',
* methods: ['GET', 'POST'],
* meta: {
* rateLimit: {
* interval: '1m',
* tokensPerInterval: 10,
* },
* },
* handler: async (event, next) => {
* console.log('Auth middleware running');
* // Check authentication
* if (!event.context.user) {
* return new Response('Unauthorized', { status: 401 });
* }
* return next();
* }
* });
*
* app.register(authMiddleware);
* ```
*/
export function defineMiddlewareRoute(
def: MiddlewareRouteDefinition,
): H3Plugin {
const middleware = defineMiddleware(def.handler);

return (h3: H3) => {
const options = {
...(def.methods && {
match: (event: H3Event) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.use already adds matching support using normalizeMiddleware internal (see here > here so we don't need to do it again here.

Also #1153 (comment), we could add this functionality to existing defineRoute utility.

const method = event.req.method.toUpperCase();
return def.methods!.includes(method as HTTPMethod);
},
}),
...(def.meta && { meta: def.meta }),
};

if (def.path) {
h3.use(def.path, middleware, options);
} else {
h3.use(middleware, options);
}
};
}
Loading