clear-router 2.1.9 → 2.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,6 +42,7 @@ yarn add clear-router express
42
42
 
43
43
  - Simple and clean route declarations (get, post, put, delete, patch, options, head)
44
44
  - Grouped routes with prefix
45
+ - Method override support via body or header keys (configurable)
45
46
  - Middleware stack: per-route and group-level
46
47
  - Controller-method pair as route handler
47
48
  - Supports HttpContext style handlers: { req, res, next }
@@ -115,5 +115,16 @@ type ControllerAction = 'index' | 'show' | 'create' | 'update' | 'destroy';
115
115
  */
116
116
  type RequestData = Record<string, any>;
117
117
  type ApiResourceMiddleware<M extends Middleware | Middleware$1> = M | M[] | { [K in ControllerAction]?: M | M[] };
118
+ interface RouterConfig {
119
+ /**
120
+ * Configuration for method override functionality, allowing clients to use a
121
+ * specific header or body parameter to override the HTTP method.
122
+ */
123
+ methodOverride?: {
124
+ /** Whether method override is enabled */enabled?: boolean; /** Keys in the request body to check for method override */
125
+ bodyKeys?: string[] | string; /** Keys in the request headers to check for method override */
126
+ headerKeys?: string[] | string;
127
+ };
128
+ }
118
129
  //#endregion
119
- export { HttpContext as a, H3App as c, Middleware$1 as d, Handler as i, Handler$1 as l, ControllerAction as n, Middleware as o, HttpMethod as r, Route as s, ApiResourceMiddleware as t, HttpContext$1 as u };
130
+ export { Handler as a, Route as c, HttpContext$1 as d, Middleware$1 as f, RouterConfig as i, H3App as l, ControllerAction as n, HttpContext as o, HttpMethod as r, Middleware as s, ApiResourceMiddleware as t, Handler$1 as u };
@@ -115,5 +115,16 @@ type ControllerAction = 'index' | 'show' | 'create' | 'update' | 'destroy';
115
115
  */
116
116
  type RequestData = Record<string, any>;
117
117
  type ApiResourceMiddleware<M extends Middleware | Middleware$1> = M | M[] | { [K in ControllerAction]?: M | M[] };
118
+ interface RouterConfig {
119
+ /**
120
+ * Configuration for method override functionality, allowing clients to use a
121
+ * specific header or body parameter to override the HTTP method.
122
+ */
123
+ methodOverride?: {
124
+ /** Whether method override is enabled */enabled?: boolean; /** Keys in the request body to check for method override */
125
+ bodyKeys?: string[] | string; /** Keys in the request headers to check for method override */
126
+ headerKeys?: string[] | string;
127
+ };
128
+ }
118
129
  //#endregion
119
- export { HttpContext as a, H3App as c, Middleware$1 as d, Handler as i, Handler$1 as l, ControllerAction as n, Middleware as o, HttpMethod as r, Route as s, ApiResourceMiddleware as t, HttpContext$1 as u };
130
+ export { Handler as a, Route as c, HttpContext$1 as d, Middleware$1 as f, RouterConfig as i, H3App as l, ControllerAction as n, HttpContext as o, HttpMethod as r, Middleware as s, ApiResourceMiddleware as t, Handler$1 as u };
@@ -11,6 +11,11 @@ let node_async_hooks = require("node:async_hooks");
11
11
  * @repository https://github.com/toneflix/clear-router
12
12
  */
13
13
  var Router = class Router {
14
+ static config = { methodOverride: {
15
+ enabled: true,
16
+ bodyKeys: ["_method"],
17
+ headerKeys: ["x-http-method"]
18
+ } };
14
19
  static groupContext = new node_async_hooks.AsyncLocalStorage();
15
20
  /**
16
21
  * All registered routes
@@ -45,6 +50,53 @@ var Router = class Router {
45
50
  return "/" + path.split("/").filter(Boolean).join("/");
46
51
  }
47
52
  /**
53
+ * Configure router settings to modify behavior.
54
+ *
55
+ * @param options - Configuration options for the router
56
+ * @returns
57
+ */
58
+ static configure(options) {
59
+ if (!this.config.methodOverride) this.config.methodOverride = {
60
+ enabled: true,
61
+ bodyKeys: ["_method"],
62
+ headerKeys: ["x-http-method"]
63
+ };
64
+ const override = options?.methodOverride;
65
+ if (!override) return;
66
+ if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
67
+ const bodyKeys = override.bodyKeys;
68
+ if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
69
+ const headerKeys = override.headerKeys;
70
+ if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
71
+ }
72
+ static resolveMethodOverride(method, headers, body) {
73
+ if (!this.config.methodOverride?.enabled || method.toLowerCase() !== "post") return null;
74
+ let override;
75
+ for (const key of this.config.methodOverride?.headerKeys || []) {
76
+ const value = headers?.[key];
77
+ if (Array.isArray(value) ? value[0] : value) {
78
+ override = Array.isArray(value) ? value[0] : value;
79
+ break;
80
+ }
81
+ }
82
+ if (!override && body && typeof body === "object") for (const key of this.config.methodOverride?.bodyKeys || []) {
83
+ const value = body[key];
84
+ if (typeof value !== "undefined" && value !== null && value !== "") {
85
+ override = value;
86
+ break;
87
+ }
88
+ }
89
+ const normalized = String(override || "").trim().toLowerCase();
90
+ if (!normalized) return null;
91
+ if ([
92
+ "put",
93
+ "patch",
94
+ "delete",
95
+ "post"
96
+ ].includes(normalized)) return normalized;
97
+ return null;
98
+ }
99
+ /**
48
100
  * Add a route with specified HTTP methods, path, handler, and middlewares
49
101
  * @param methods - HTTP method(s) for the route
50
102
  * @param path - Route path
@@ -256,7 +308,34 @@ var Router = class Router {
256
308
  console.error("[ROUTES]", error.message);
257
309
  throw error;
258
310
  }
259
- router[method](route.path, ...route.middlewares || [], async (req, res, next) => {
311
+ router[method](route.path, (req, res, next) => {
312
+ const override = Router.resolveMethodOverride(req.method, req.headers, req.body);
313
+ if (method === "post" && override && override !== "post") return next("route");
314
+ return next();
315
+ }, ...route.middlewares || [], async (req, res, next) => {
316
+ try {
317
+ const ctx = {
318
+ req,
319
+ res,
320
+ next
321
+ };
322
+ const inst = instance ?? route;
323
+ await Router.bindRequestToInstance(ctx, inst, route);
324
+ const result = handlerFunction(ctx, inst.clearRequest);
325
+ await Promise.resolve(result);
326
+ } catch (error) {
327
+ next(error);
328
+ }
329
+ });
330
+ if ([
331
+ "put",
332
+ "patch",
333
+ "delete"
334
+ ].includes(method)) router.post(route.path, (req, res, next) => {
335
+ if (Router.resolveMethodOverride(req.method, req.headers, req.body) !== method) return next("route");
336
+ req.method = method.toUpperCase();
337
+ return next();
338
+ }, ...route.middlewares || [], async (req, res, next) => {
260
339
  try {
261
340
  const ctx = {
262
341
  req,
@@ -1,4 +1,4 @@
1
- import { a as HttpContext, i as Handler, n as ControllerAction, o as Middleware, r as HttpMethod, s as Route, t as ApiResourceMiddleware } from "../basic-Chn8OGPD.cjs";
1
+ import { a as Handler, c as Route, i as RouterConfig, n as ControllerAction, o as HttpContext, r as HttpMethod, s as Middleware, t as ApiResourceMiddleware } from "../basic-DXbqD6cP.cjs";
2
2
  import { Router as Router$1 } from "express";
3
3
 
4
4
  //#region src/express/router.d.ts
@@ -10,6 +10,7 @@ import { Router as Router$1 } from "express";
10
10
  * @repository https://github.com/toneflix/clear-router
11
11
  */
12
12
  declare class Router {
13
+ static config: RouterConfig;
13
14
  private static readonly groupContext;
14
15
  /**
15
16
  * All registered routes
@@ -41,6 +42,14 @@ declare class Router {
41
42
  * @returns Normalized path
42
43
  */
43
44
  static normalizePath(path: string): string;
45
+ /**
46
+ * Configure router settings to modify behavior.
47
+ *
48
+ * @param options - Configuration options for the router
49
+ * @returns
50
+ */
51
+ static configure(options?: RouterConfig): void;
52
+ private static resolveMethodOverride;
44
53
  /**
45
54
  * Add a route with specified HTTP methods, path, handler, and middlewares
46
55
  * @param methods - HTTP method(s) for the route
@@ -127,9 +136,9 @@ declare class Router {
127
136
  * Get all registered routes with their information
128
137
  * @returns Array of route information objects
129
138
  */
130
- static allRoutes(type?: 'path'): Record<string, Route<HttpContext, Middleware>>;
131
- static allRoutes(type?: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
132
- static allRoutes(type?: 'method'): Array<Route<HttpContext, Middleware>>;
139
+ static allRoutes(): Array<Route<HttpContext, Middleware>>;
140
+ static allRoutes(type: 'path'): Record<string, Route<HttpContext, Middleware>>;
141
+ static allRoutes(type: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
133
142
  /**
134
143
  * Apply all registered routes to the provided Express Router instance
135
144
  * Handles controller-method binding and middleware application
@@ -1,4 +1,4 @@
1
- import { a as HttpContext, i as Handler, n as ControllerAction, o as Middleware, r as HttpMethod, s as Route, t as ApiResourceMiddleware } from "../basic-DJmmZq1h.mjs";
1
+ import { a as Handler, c as Route, i as RouterConfig, n as ControllerAction, o as HttpContext, r as HttpMethod, s as Middleware, t as ApiResourceMiddleware } from "../basic-vvrFwa_Y.mjs";
2
2
  import { Router as Router$1 } from "express";
3
3
 
4
4
  //#region src/express/router.d.ts
@@ -10,6 +10,7 @@ import { Router as Router$1 } from "express";
10
10
  * @repository https://github.com/toneflix/clear-router
11
11
  */
12
12
  declare class Router {
13
+ static config: RouterConfig;
13
14
  private static readonly groupContext;
14
15
  /**
15
16
  * All registered routes
@@ -41,6 +42,14 @@ declare class Router {
41
42
  * @returns Normalized path
42
43
  */
43
44
  static normalizePath(path: string): string;
45
+ /**
46
+ * Configure router settings to modify behavior.
47
+ *
48
+ * @param options - Configuration options for the router
49
+ * @returns
50
+ */
51
+ static configure(options?: RouterConfig): void;
52
+ private static resolveMethodOverride;
44
53
  /**
45
54
  * Add a route with specified HTTP methods, path, handler, and middlewares
46
55
  * @param methods - HTTP method(s) for the route
@@ -127,9 +136,9 @@ declare class Router {
127
136
  * Get all registered routes with their information
128
137
  * @returns Array of route information objects
129
138
  */
130
- static allRoutes(type?: 'path'): Record<string, Route<HttpContext, Middleware>>;
131
- static allRoutes(type?: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
132
- static allRoutes(type?: 'method'): Array<Route<HttpContext, Middleware>>;
139
+ static allRoutes(): Array<Route<HttpContext, Middleware>>;
140
+ static allRoutes(type: 'path'): Record<string, Route<HttpContext, Middleware>>;
141
+ static allRoutes(type: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
133
142
  /**
134
143
  * Apply all registered routes to the provided Express Router instance
135
144
  * Handles controller-method binding and middleware application
@@ -10,6 +10,11 @@ import { AsyncLocalStorage } from "node:async_hooks";
10
10
  * @repository https://github.com/toneflix/clear-router
11
11
  */
12
12
  var Router = class Router {
13
+ static config = { methodOverride: {
14
+ enabled: true,
15
+ bodyKeys: ["_method"],
16
+ headerKeys: ["x-http-method"]
17
+ } };
13
18
  static groupContext = new AsyncLocalStorage();
14
19
  /**
15
20
  * All registered routes
@@ -44,6 +49,53 @@ var Router = class Router {
44
49
  return "/" + path.split("/").filter(Boolean).join("/");
45
50
  }
46
51
  /**
52
+ * Configure router settings to modify behavior.
53
+ *
54
+ * @param options - Configuration options for the router
55
+ * @returns
56
+ */
57
+ static configure(options) {
58
+ if (!this.config.methodOverride) this.config.methodOverride = {
59
+ enabled: true,
60
+ bodyKeys: ["_method"],
61
+ headerKeys: ["x-http-method"]
62
+ };
63
+ const override = options?.methodOverride;
64
+ if (!override) return;
65
+ if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
66
+ const bodyKeys = override.bodyKeys;
67
+ if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
68
+ const headerKeys = override.headerKeys;
69
+ if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
70
+ }
71
+ static resolveMethodOverride(method, headers, body) {
72
+ if (!this.config.methodOverride?.enabled || method.toLowerCase() !== "post") return null;
73
+ let override;
74
+ for (const key of this.config.methodOverride?.headerKeys || []) {
75
+ const value = headers?.[key];
76
+ if (Array.isArray(value) ? value[0] : value) {
77
+ override = Array.isArray(value) ? value[0] : value;
78
+ break;
79
+ }
80
+ }
81
+ if (!override && body && typeof body === "object") for (const key of this.config.methodOverride?.bodyKeys || []) {
82
+ const value = body[key];
83
+ if (typeof value !== "undefined" && value !== null && value !== "") {
84
+ override = value;
85
+ break;
86
+ }
87
+ }
88
+ const normalized = String(override || "").trim().toLowerCase();
89
+ if (!normalized) return null;
90
+ if ([
91
+ "put",
92
+ "patch",
93
+ "delete",
94
+ "post"
95
+ ].includes(normalized)) return normalized;
96
+ return null;
97
+ }
98
+ /**
47
99
  * Add a route with specified HTTP methods, path, handler, and middlewares
48
100
  * @param methods - HTTP method(s) for the route
49
101
  * @param path - Route path
@@ -255,7 +307,34 @@ var Router = class Router {
255
307
  console.error("[ROUTES]", error.message);
256
308
  throw error;
257
309
  }
258
- router[method](route.path, ...route.middlewares || [], async (req, res, next) => {
310
+ router[method](route.path, (req, res, next) => {
311
+ const override = Router.resolveMethodOverride(req.method, req.headers, req.body);
312
+ if (method === "post" && override && override !== "post") return next("route");
313
+ return next();
314
+ }, ...route.middlewares || [], async (req, res, next) => {
315
+ try {
316
+ const ctx = {
317
+ req,
318
+ res,
319
+ next
320
+ };
321
+ const inst = instance ?? route;
322
+ await Router.bindRequestToInstance(ctx, inst, route);
323
+ const result = handlerFunction(ctx, inst.clearRequest);
324
+ await Promise.resolve(result);
325
+ } catch (error) {
326
+ next(error);
327
+ }
328
+ });
329
+ if ([
330
+ "put",
331
+ "patch",
332
+ "delete"
333
+ ].includes(method)) router.post(route.path, (req, res, next) => {
334
+ if (Router.resolveMethodOverride(req.method, req.headers, req.body) !== method) return next("route");
335
+ req.method = method.toUpperCase();
336
+ return next();
337
+ }, ...route.middlewares || [], async (req, res, next) => {
259
338
  try {
260
339
  const ctx = {
261
340
  req,
package/dist/h3/index.cjs CHANGED
@@ -11,6 +11,12 @@ let h3 = require("h3");
11
11
  * @repository https://github.com/toneflix/clear-router
12
12
  */
13
13
  var Router = class Router {
14
+ static config = { methodOverride: {
15
+ enabled: true,
16
+ bodyKeys: ["_method"],
17
+ headerKeys: ["x-http-method"]
18
+ } };
19
+ static bodyCache = /* @__PURE__ */ new WeakMap();
14
20
  static groupContext = new node_async_hooks.AsyncLocalStorage();
15
21
  /**
16
22
  * All registered routes
@@ -44,6 +50,53 @@ var Router = class Router {
44
50
  static normalizePath(path) {
45
51
  return "/" + path.split("/").filter(Boolean).join("/");
46
52
  }
53
+ static configure(options) {
54
+ if (!this.config.methodOverride) this.config.methodOverride = {
55
+ enabled: true,
56
+ bodyKeys: ["_method"],
57
+ headerKeys: ["x-http-method"]
58
+ };
59
+ const override = options?.methodOverride;
60
+ if (!override) return;
61
+ if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
62
+ const bodyKeys = override.bodyKeys;
63
+ if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
64
+ const headerKeys = override.headerKeys;
65
+ if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
66
+ }
67
+ static async readBodyCached(ctx) {
68
+ if (this.bodyCache.has(ctx)) return this.bodyCache.get(ctx);
69
+ const body = await (0, h3.readBody)(ctx) ?? {};
70
+ this.bodyCache.set(ctx, body);
71
+ return body;
72
+ }
73
+ static resolveMethodOverride(method, headers, body) {
74
+ if (!this.config.methodOverride?.enabled || method.toLowerCase() !== "post") return null;
75
+ let override;
76
+ for (const key of this.config.methodOverride?.headerKeys || []) {
77
+ const value = headers.get(key);
78
+ if (value) {
79
+ override = value;
80
+ break;
81
+ }
82
+ }
83
+ if (!override && body && typeof body === "object") for (const key of this.config.methodOverride?.bodyKeys || []) {
84
+ const value = body[key];
85
+ if (typeof value !== "undefined" && value !== null && value !== "") {
86
+ override = value;
87
+ break;
88
+ }
89
+ }
90
+ const normalized = String(override || "").trim().toLowerCase();
91
+ if (!normalized) return null;
92
+ if ([
93
+ "put",
94
+ "patch",
95
+ "delete",
96
+ "post"
97
+ ].includes(normalized)) return normalized;
98
+ return null;
99
+ }
47
100
  /**
48
101
  * Add a route with specified HTTP methods, path, handler, and middlewares
49
102
  * @param methods - HTTP method(s) for the route
@@ -266,8 +319,28 @@ var Router = class Router {
266
319
  app[method](route.path, async (event) => {
267
320
  try {
268
321
  const ctx = event;
322
+ const reqBody = await Router.readBodyCached(ctx);
323
+ const override = Router.resolveMethodOverride(ctx.req.method, ctx.req.headers, reqBody);
324
+ if (method === "post" && override && override !== "post") return;
325
+ const inst = instance ?? route;
326
+ await Router.bindRequestToInstance(ctx, inst, route, reqBody);
327
+ const result = handlerFunction(ctx, inst.clearRequest);
328
+ return await Promise.resolve(result);
329
+ } catch (error) {
330
+ return error;
331
+ }
332
+ }, { middleware: route.middlewares });
333
+ if ([
334
+ "put",
335
+ "patch",
336
+ "delete"
337
+ ].includes(method)) app.post(route.path, async (event) => {
338
+ try {
339
+ const ctx = event;
340
+ const reqBody = await Router.readBodyCached(ctx);
341
+ if (Router.resolveMethodOverride(ctx.req.method, ctx.req.headers, reqBody) !== method) return;
269
342
  const inst = instance ?? route;
270
- await Router.bindRequestToInstance(ctx, inst, route);
343
+ await Router.bindRequestToInstance(ctx, inst, route, reqBody);
271
344
  const result = handlerFunction(ctx, inst.clearRequest);
272
345
  return await Promise.resolve(result);
273
346
  } catch (error) {
@@ -278,10 +351,10 @@ var Router = class Router {
278
351
  }
279
352
  return app;
280
353
  }
281
- static async bindRequestToInstance(ctx, instance, route) {
354
+ static async bindRequestToInstance(ctx, instance, route, body) {
282
355
  if (!instance) return;
283
356
  instance.ctx = ctx;
284
- instance.body = await (0, h3.readBody)(ctx) ?? {};
357
+ instance.body = body ?? await Router.readBodyCached(ctx);
285
358
  instance.query = (0, h3.getQuery)(ctx);
286
359
  instance.params = (0, h3.getRouterParams)(ctx, { decode: true });
287
360
  instance.clearRequest = new require_Route.ClearRequest({
@@ -1,4 +1,4 @@
1
- import { c as H3App, d as Middleware, l as Handler, n as ControllerAction, r as HttpMethod, s as Route, t as ApiResourceMiddleware, u as HttpContext } from "../basic-Chn8OGPD.cjs";
1
+ import { c as Route, d as HttpContext, f as Middleware, i as RouterConfig, l as H3App, n as ControllerAction, r as HttpMethod, t as ApiResourceMiddleware, u as Handler } from "../basic-DXbqD6cP.cjs";
2
2
  import { H3 } from "h3";
3
3
 
4
4
  //#region src/h3/router.d.ts
@@ -9,6 +9,8 @@ import { H3 } from "h3";
9
9
  * @repository https://github.com/toneflix/clear-router
10
10
  */
11
11
  declare class Router {
12
+ static config: RouterConfig;
13
+ private static readonly bodyCache;
12
14
  private static readonly groupContext;
13
15
  /**
14
16
  * All registered routes
@@ -40,6 +42,9 @@ declare class Router {
40
42
  * @returns Normalized path
41
43
  */
42
44
  static normalizePath(path: string): string;
45
+ static configure(options?: RouterConfig): void;
46
+ private static readBodyCached;
47
+ private static resolveMethodOverride;
43
48
  /**
44
49
  * Add a route with specified HTTP methods, path, handler, and middlewares
45
50
  * @param methods - HTTP method(s) for the route
@@ -125,10 +130,10 @@ declare class Router {
125
130
  /**
126
131
  * Get all registered routes with their information
127
132
  * @returns Array of route information objects
128
- */
129
- static allRoutes(type?: 'path'): Record<string, Route<HttpContext, Middleware>>;
130
- static allRoutes(type?: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
131
- static allRoutes(type?: 'method'): Array<Route<HttpContext, Middleware>>;
133
+ */
134
+ static allRoutes(): Array<Route<HttpContext, Middleware>>;
135
+ static allRoutes(type: 'path'): Record<string, Route<HttpContext, Middleware>>;
136
+ static allRoutes(type: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
132
137
  /**
133
138
  * Apply all registered routes to the provided H3 Router instance
134
139
  * Handles controller-method binding and middleware application
@@ -1,4 +1,4 @@
1
- import { c as H3App, d as Middleware, l as Handler, n as ControllerAction, r as HttpMethod, s as Route, t as ApiResourceMiddleware, u as HttpContext } from "../basic-DJmmZq1h.mjs";
1
+ import { c as Route, d as HttpContext, f as Middleware, i as RouterConfig, l as H3App, n as ControllerAction, r as HttpMethod, t as ApiResourceMiddleware, u as Handler } from "../basic-vvrFwa_Y.mjs";
2
2
  import { H3 } from "h3";
3
3
 
4
4
  //#region src/h3/router.d.ts
@@ -9,6 +9,8 @@ import { H3 } from "h3";
9
9
  * @repository https://github.com/toneflix/clear-router
10
10
  */
11
11
  declare class Router {
12
+ static config: RouterConfig;
13
+ private static readonly bodyCache;
12
14
  private static readonly groupContext;
13
15
  /**
14
16
  * All registered routes
@@ -40,6 +42,9 @@ declare class Router {
40
42
  * @returns Normalized path
41
43
  */
42
44
  static normalizePath(path: string): string;
45
+ static configure(options?: RouterConfig): void;
46
+ private static readBodyCached;
47
+ private static resolveMethodOverride;
43
48
  /**
44
49
  * Add a route with specified HTTP methods, path, handler, and middlewares
45
50
  * @param methods - HTTP method(s) for the route
@@ -125,10 +130,10 @@ declare class Router {
125
130
  /**
126
131
  * Get all registered routes with their information
127
132
  * @returns Array of route information objects
128
- */
129
- static allRoutes(type?: 'path'): Record<string, Route<HttpContext, Middleware>>;
130
- static allRoutes(type?: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
131
- static allRoutes(type?: 'method'): Array<Route<HttpContext, Middleware>>;
133
+ */
134
+ static allRoutes(): Array<Route<HttpContext, Middleware>>;
135
+ static allRoutes(type: 'path'): Record<string, Route<HttpContext, Middleware>>;
136
+ static allRoutes(type: 'method'): { [method in Uppercase<HttpMethod>]?: Array<Route<HttpContext, Middleware>> };
132
137
  /**
133
138
  * Apply all registered routes to the provided H3 Router instance
134
139
  * Handles controller-method binding and middleware application
package/dist/h3/index.mjs CHANGED
@@ -10,6 +10,12 @@ import { getQuery, getRouterParams, readBody } from "h3";
10
10
  * @repository https://github.com/toneflix/clear-router
11
11
  */
12
12
  var Router = class Router {
13
+ static config = { methodOverride: {
14
+ enabled: true,
15
+ bodyKeys: ["_method"],
16
+ headerKeys: ["x-http-method"]
17
+ } };
18
+ static bodyCache = /* @__PURE__ */ new WeakMap();
13
19
  static groupContext = new AsyncLocalStorage();
14
20
  /**
15
21
  * All registered routes
@@ -43,6 +49,53 @@ var Router = class Router {
43
49
  static normalizePath(path) {
44
50
  return "/" + path.split("/").filter(Boolean).join("/");
45
51
  }
52
+ static configure(options) {
53
+ if (!this.config.methodOverride) this.config.methodOverride = {
54
+ enabled: true,
55
+ bodyKeys: ["_method"],
56
+ headerKeys: ["x-http-method"]
57
+ };
58
+ const override = options?.methodOverride;
59
+ if (!override) return;
60
+ if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
61
+ const bodyKeys = override.bodyKeys;
62
+ if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
63
+ const headerKeys = override.headerKeys;
64
+ if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
65
+ }
66
+ static async readBodyCached(ctx) {
67
+ if (this.bodyCache.has(ctx)) return this.bodyCache.get(ctx);
68
+ const body = await readBody(ctx) ?? {};
69
+ this.bodyCache.set(ctx, body);
70
+ return body;
71
+ }
72
+ static resolveMethodOverride(method, headers, body) {
73
+ if (!this.config.methodOverride?.enabled || method.toLowerCase() !== "post") return null;
74
+ let override;
75
+ for (const key of this.config.methodOverride?.headerKeys || []) {
76
+ const value = headers.get(key);
77
+ if (value) {
78
+ override = value;
79
+ break;
80
+ }
81
+ }
82
+ if (!override && body && typeof body === "object") for (const key of this.config.methodOverride?.bodyKeys || []) {
83
+ const value = body[key];
84
+ if (typeof value !== "undefined" && value !== null && value !== "") {
85
+ override = value;
86
+ break;
87
+ }
88
+ }
89
+ const normalized = String(override || "").trim().toLowerCase();
90
+ if (!normalized) return null;
91
+ if ([
92
+ "put",
93
+ "patch",
94
+ "delete",
95
+ "post"
96
+ ].includes(normalized)) return normalized;
97
+ return null;
98
+ }
46
99
  /**
47
100
  * Add a route with specified HTTP methods, path, handler, and middlewares
48
101
  * @param methods - HTTP method(s) for the route
@@ -265,8 +318,28 @@ var Router = class Router {
265
318
  app[method](route.path, async (event) => {
266
319
  try {
267
320
  const ctx = event;
321
+ const reqBody = await Router.readBodyCached(ctx);
322
+ const override = Router.resolveMethodOverride(ctx.req.method, ctx.req.headers, reqBody);
323
+ if (method === "post" && override && override !== "post") return;
324
+ const inst = instance ?? route;
325
+ await Router.bindRequestToInstance(ctx, inst, route, reqBody);
326
+ const result = handlerFunction(ctx, inst.clearRequest);
327
+ return await Promise.resolve(result);
328
+ } catch (error) {
329
+ return error;
330
+ }
331
+ }, { middleware: route.middlewares });
332
+ if ([
333
+ "put",
334
+ "patch",
335
+ "delete"
336
+ ].includes(method)) app.post(route.path, async (event) => {
337
+ try {
338
+ const ctx = event;
339
+ const reqBody = await Router.readBodyCached(ctx);
340
+ if (Router.resolveMethodOverride(ctx.req.method, ctx.req.headers, reqBody) !== method) return;
268
341
  const inst = instance ?? route;
269
- await Router.bindRequestToInstance(ctx, inst, route);
342
+ await Router.bindRequestToInstance(ctx, inst, route, reqBody);
270
343
  const result = handlerFunction(ctx, inst.clearRequest);
271
344
  return await Promise.resolve(result);
272
345
  } catch (error) {
@@ -277,10 +350,10 @@ var Router = class Router {
277
350
  }
278
351
  return app;
279
352
  }
280
- static async bindRequestToInstance(ctx, instance, route) {
353
+ static async bindRequestToInstance(ctx, instance, route, body) {
281
354
  if (!instance) return;
282
355
  instance.ctx = ctx;
283
- instance.body = await readBody(ctx) ?? {};
356
+ instance.body = body ?? await Router.readBodyCached(ctx);
284
357
  instance.query = getQuery(ctx);
285
358
  instance.params = getRouterParams(ctx, { decode: true });
286
359
  instance.clearRequest = new ClearRequest({
@@ -19,5 +19,16 @@ type ControllerAction = 'index' | 'show' | 'create' | 'update' | 'destroy';
19
19
  */
20
20
  type RequestData = Record<string, any>;
21
21
  type ApiResourceMiddleware<M extends Middleware$1 | Middleware> = M | M[] | { [K in ControllerAction]?: M | M[] };
22
+ interface RouterConfig {
23
+ /**
24
+ * Configuration for method override functionality, allowing clients to use a
25
+ * specific header or body parameter to override the HTTP method.
26
+ */
27
+ methodOverride?: {
28
+ /** Whether method override is enabled */enabled?: boolean; /** Keys in the request body to check for method override */
29
+ bodyKeys?: string[] | string; /** Keys in the request headers to check for method override */
30
+ headerKeys?: string[] | string;
31
+ };
32
+ }
22
33
  //#endregion
23
- export { ApiResourceMiddleware, ControllerAction, ControllerHandler, HttpMethod, RequestData };
34
+ export { ApiResourceMiddleware, ControllerAction, ControllerHandler, HttpMethod, RequestData, RouterConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-router",
3
- "version": "2.1.9",
3
+ "version": "2.1.11",
4
4
  "description": "Laravel-style routing system for Express.js and H3, with CommonJS, ESM, and TypeScript support",
5
5
  "keywords": [
6
6
  "h3",