clear-router 2.1.11 → 2.2.0

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.
@@ -1,291 +1,140 @@
1
- import { n as ClearRequest, t as Route } from "../Route-BbPXcDGX.mjs";
2
- import { AsyncLocalStorage } from "node:async_hooks";
1
+ import { t as CoreRouter } from "../router-BiCuy5TZ.mjs";
3
2
 
4
3
  //#region src/express/router.ts
5
4
  /**
6
- * @class clear-router
5
+ * @class clear-router Express Router
7
6
  * @description Laravel-style routing system for Express.js and H3 with support for CommonJS, ESM, and TypeScript
8
7
  * @author Refkinscallv
9
8
  * @author 3m1n3nc3
10
9
  * @repository https://github.com/toneflix/clear-router
11
10
  */
12
- var Router = class Router {
13
- static config = { methodOverride: {
14
- enabled: true,
15
- bodyKeys: ["_method"],
16
- headerKeys: ["x-http-method"]
17
- } };
18
- static groupContext = new AsyncLocalStorage();
19
- /**
20
- * All registered routes
21
- */
22
- static routes = [];
23
- /**
24
- * Mapping of routes by path and method for quick lookup.
25
- */
26
- static routesByPathMethod = {};
27
- /**
28
- * Mapping of routes by method for quick lookup.
29
- */
30
- static routesByMethod = {};
31
- /**
32
- * Current route prefix
33
- */
34
- static prefix = "";
35
- /**
36
- * Group-level middlewares
37
- */
38
- static groupMiddlewares = [];
39
- /**
40
- * Global-level middlewares
41
- */
42
- static globalMiddlewares = [];
43
- /**
44
- * Normalize path by removing duplicate slashes and ensuring leading slash
45
- * @param path - Path to normalize
46
- * @returns Normalized path
47
- */
48
- static normalizePath(path) {
49
- return "/" + path.split("/").filter(Boolean).join("/");
11
+ var Router = class Router extends CoreRouter {
12
+ static ensureRequestBodyAccessor(req) {
13
+ if (typeof req.getBody !== "function") req.getBody = () => req.body ?? {};
50
14
  }
51
15
  /**
52
- * Configure router settings to modify behavior.
16
+ * Adds a new route to the router with the specified HTTP methods, path, handler, and optional middlewares.
53
17
  *
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
- /**
99
- * Add a route with specified HTTP methods, path, handler, and middlewares
100
- * @param methods - HTTP method(s) for the route
101
- * @param path - Route path
102
- * @param handler - Route handler function or controller reference
103
- * @param middlewares - Array of middleware functions
18
+ * @param methods
19
+ * @param path
20
+ * @param handler
21
+ * @param middlewares
104
22
  */
105
23
  static add(methods, path, handler, middlewares) {
106
- const context = this.groupContext.getStore();
107
- const activePrefix = context?.prefix ?? this.prefix;
108
- const activeGroupMiddlewares = context?.groupMiddlewares ?? this.groupMiddlewares;
109
- methods = Array.isArray(methods) ? methods : [methods];
110
- middlewares = middlewares ? Array.isArray(middlewares) ? middlewares : [middlewares] : void 0;
111
- const fullPath = this.normalizePath(`${activePrefix}/${path}`);
112
- const route = new Route(methods.includes("options") ? methods : methods.concat("options"), fullPath, handler, [
113
- ...this.globalMiddlewares,
114
- ...activeGroupMiddlewares,
115
- ...middlewares || []
116
- ]);
117
- if (!methods.includes("options") && !this.routesByPathMethod[`OPTIONS ${fullPath}`]) this.options(path, ({ res }) => {
118
- res.set("Allow", "GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD");
119
- res.sendStatus(204);
120
- });
121
- this.routes.push(route);
122
- for (const method of methods.map((m) => m.toUpperCase())) {
123
- this.routesByPathMethod[`${method.toUpperCase()} ${fullPath}`] = route;
124
- if (!this.routesByMethod[method]) this.routesByMethod[method] = [];
125
- this.routesByMethod[method].push(route);
126
- }
24
+ super.add(methods, path, handler, middlewares);
127
25
  }
128
26
  /**
129
- * Register RESTful API resource routes for a controller with optional action filtering
27
+ * Adds a new API resource route to the router for the specified base path and controller, with
28
+ * optional configuration for included/excluded actions and middlewares.
130
29
  *
131
- * @param basePath - Base path for the resource
132
- * @param controller - Controller object containing action methods
133
- * @param options - Optional filtering options for actions
30
+ * @param basePath
31
+ * @param controller
32
+ * @param options
134
33
  */
135
34
  static apiResource(basePath, controller, options) {
136
- const actions = {
137
- index: {
138
- method: "get",
139
- path: "/"
140
- },
141
- show: {
142
- method: "get",
143
- path: "/:id"
144
- },
145
- create: {
146
- method: "post",
147
- path: "/"
148
- },
149
- update: {
150
- method: "put",
151
- path: "/:id"
152
- },
153
- destroy: {
154
- method: "delete",
155
- path: "/:id"
156
- }
157
- };
158
- const only = options?.only || Object.keys(actions);
159
- const except = options?.except || [];
160
- const preController = typeof controller === "function" ? new controller() : controller;
161
- for (const action of only) {
162
- if (except.includes(action)) continue;
163
- if (typeof preController[action] === "function") {
164
- const { method, path } = actions[action];
165
- const actionMiddlewares = typeof options?.middlewares === "object" && !Array.isArray(options.middlewares) ? options.middlewares[action] : options?.middlewares;
166
- this.add(method, `${basePath}${path}`, [controller, action], Array.isArray(actionMiddlewares) ? actionMiddlewares : actionMiddlewares ? [actionMiddlewares] : void 0);
167
- }
168
- }
35
+ super.apiResource(basePath, controller, options);
169
36
  }
170
37
  /**
171
- * Register a GET route
172
- * @param path - Route path
173
- * @param handler - Route handler
174
- * @param middlewares - Middleware functions
38
+ * Adds a new GET route to the router with the specified path, handler, and optional middlewares.
39
+ *
40
+ * @param path
41
+ * @param handler
42
+ * @param middlewares
175
43
  */
176
44
  static get(path, handler, middlewares) {
177
- this.add("get", path, handler, middlewares);
45
+ super.get(path, handler, middlewares);
178
46
  }
179
47
  /**
180
- * Register a POST route
181
- * @param path - Route path
182
- * @param handler - Route handler
183
- * @param middlewares - Middleware functions
48
+ * Adds a new POST route to the router with the specified path, handler, and optional middlewares.
49
+ *
50
+ * @param path
51
+ * @param handler
52
+ * @param middlewares
184
53
  */
185
54
  static post(path, handler, middlewares) {
186
- this.add("post", path, handler, middlewares);
55
+ super.post(path, handler, middlewares);
187
56
  }
188
57
  /**
189
- * Register a PUT route
190
- * @param path - Route path
191
- * @param handler - Route handler
192
- * @param middlewares - Middleware functions
58
+ * Adds a new PUT route to the router with the specified path, handler, and optional middlewares.
59
+ *
60
+ * @param path
61
+ * @param handler
62
+ * @param middlewares
193
63
  */
194
64
  static put(path, handler, middlewares) {
195
- this.add("put", path, handler, middlewares);
65
+ super.put(path, handler, middlewares);
196
66
  }
197
67
  /**
198
- * Register a DELETE route
199
- * @param path - Route path
200
- * @param handler - Route handler
201
- * @param middlewares - Middleware functions
68
+ * Adds a new DELETE route to the router with the specified path, handler, and optional middlewares.
69
+ *
70
+ * @param path
71
+ * @param handler
72
+ * @param middlewares
202
73
  */
203
74
  static delete(path, handler, middlewares) {
204
- this.add("delete", path, handler, middlewares);
75
+ super.delete(path, handler, middlewares);
205
76
  }
206
77
  /**
207
- * Register a PATCH route
208
- * @param path - Route path
209
- * @param handler - Route handler
210
- * @param middlewares - Middleware functions
78
+ * Adds a new PATCH route to the router with the specified path, handler, and optional middlewares.
79
+ *
80
+ * @param path
81
+ * @param handler
82
+ * @param middlewares
211
83
  */
212
84
  static patch(path, handler, middlewares) {
213
- this.add("patch", path, handler, middlewares);
85
+ super.patch(path, handler, middlewares);
214
86
  }
215
87
  /**
216
- * Register an OPTIONS route
217
- * @param path - Route path
218
- * @param handler - Route handler
219
- * @param middlewares - Middleware functions
88
+ * Adds a new OPTIONS route to the router with the specified path, handler, and optional middlewares.
89
+ *
90
+ * @param path
91
+ * @param handler
92
+ * @param middlewares
220
93
  */
221
94
  static options(path, handler, middlewares) {
222
- this.add("options", path, handler, middlewares);
95
+ super.options(path, handler, middlewares);
223
96
  }
224
97
  /**
225
- * Register a HEAD route
226
- * @param path - Route path
227
- * @param handler - Route handler
228
- * @param middlewares - Middleware functions
98
+ * Adds a new HEAD route to the router with the specified path, handler, and optional middlewares.
99
+ *
100
+ * @param path
101
+ * @param handler
102
+ * @param middlewares
229
103
  */
230
104
  static head(path, handler, middlewares) {
231
- this.add("head", path, handler, middlewares);
105
+ super.head(path, handler, middlewares);
232
106
  }
233
107
  /**
234
- * Group routes with a common prefix and middlewares
235
- * @param prefix - URL prefix for grouped routes
236
- * @param callback - Function containing route definitions
237
- * @param middlewares - Middleware functions applied to all routes in group
108
+ * Defines a group of routes with a common prefix and optional middlewares, allowing for better
109
+ * organization and reuse of route configurations.
110
+ *
111
+ * @param prefix
112
+ * @param callback
113
+ * @param middlewares
238
114
  */
239
115
  static async group(prefix, callback, middlewares) {
240
- const context = this.groupContext.getStore();
241
- const previousPrefix = context?.prefix ?? this.prefix;
242
- const previousMiddlewares = context?.groupMiddlewares ?? this.groupMiddlewares;
243
- const fullPrefix = [previousPrefix, prefix].filter(Boolean).join("/");
244
- const nextContext = {
245
- prefix: this.normalizePath(fullPrefix),
246
- groupMiddlewares: [...previousMiddlewares, ...middlewares || []]
247
- };
248
- await this.groupContext.run(nextContext, async () => {
249
- await Promise.resolve(callback());
250
- });
116
+ await super.group(prefix, callback, middlewares);
251
117
  }
252
118
  /**
253
- * Apply global middlewares for the duration of the callback
254
- * @param middlewares - Middleware functions
255
- * @param callback - Function containing route definitions
119
+ * Adds global middlewares to the router, which will be applied to all routes.
120
+ *
121
+ * @param middlewares
122
+ * @param callback
256
123
  */
257
124
  static middleware(middlewares, callback) {
258
- const prevMiddlewares = this.globalMiddlewares;
259
- this.globalMiddlewares = [...prevMiddlewares, ...middlewares || []];
260
- callback();
261
- this.globalMiddlewares = prevMiddlewares;
125
+ super.middleware(middlewares, callback);
262
126
  }
263
127
  static allRoutes(type) {
264
- if (type === "method") return this.routesByMethod;
265
- if (type === "path") return this.routesByPathMethod;
266
- return this.routes.filter((e) => e.methods.length > 1 || e.methods[0] !== "options");
128
+ return super.allRoutes(type);
267
129
  }
268
130
  static async apply(router) {
269
131
  for (const route of this.routes) {
270
132
  let handlerFunction = null;
271
133
  let instance = null;
272
134
  try {
273
- if (typeof route.handler === "function")
274
- /**
275
- * Since we do not have a controller instance, we will call the handler function directly and the route instance will be the this argument. This allows for both controller-based and function-based handlers to work seamlessly.
276
- */
277
- handlerFunction = route.handler.bind(route);
278
- else if (Array.isArray(route.handler) && route.handler.length === 2) {
279
- const [Controller, method] = route.handler;
280
- if (["function", "object"].includes(typeof Controller) && typeof Controller[method] === "function") {
281
- instance = Controller;
282
- handlerFunction = Controller[method].bind(Controller);
283
- } else if (typeof Controller === "function") {
284
- instance = new Controller();
285
- if (typeof instance[method] === "function") handlerFunction = instance[method].bind(instance);
286
- else throw new Error(`Method "${method}" not found in controller instance "${Controller.name}"`);
287
- } else throw new Error(`Invalid controller type for route: ${route.path}`);
288
- } else throw new Error(`Invalid handler format for route: ${route.path}`);
135
+ const resolved = this.resolveHandler(route);
136
+ handlerFunction = resolved.handlerFunction;
137
+ instance = resolved.instance;
289
138
  } catch (error) {
290
139
  console.error(`[ROUTES] Error setting up route ${route.path}:`, error.message);
291
140
  throw error;
@@ -307,19 +156,25 @@ var Router = class Router {
307
156
  console.error("[ROUTES]", error.message);
308
157
  throw error;
309
158
  }
310
- router[method](route.path, (req, res, next) => {
159
+ router[method](route.path, (req, _res, next) => {
160
+ Router.ensureRequestBodyAccessor(req);
311
161
  const override = Router.resolveMethodOverride(req.method, req.headers, req.body);
312
162
  if (method === "post" && override && override !== "post") return next("route");
313
163
  return next();
314
164
  }, ...route.middlewares || [], async (req, res, next) => {
315
165
  try {
166
+ Router.ensureRequestBodyAccessor(req);
316
167
  const ctx = {
317
168
  req,
318
169
  res,
319
170
  next
320
171
  };
321
172
  const inst = instance ?? route;
322
- await Router.bindRequestToInstance(ctx, inst, route);
173
+ Router.bindRequestToInstance(ctx, inst, route, {
174
+ body: ctx.req.getBody(),
175
+ query: ctx.req.query,
176
+ params: ctx.req.params
177
+ });
323
178
  const result = handlerFunction(ctx, inst.clearRequest);
324
179
  await Promise.resolve(result);
325
180
  } catch (error) {
@@ -330,19 +185,25 @@ var Router = class Router {
330
185
  "put",
331
186
  "patch",
332
187
  "delete"
333
- ].includes(method)) router.post(route.path, (req, res, next) => {
188
+ ].includes(method)) router.post(route.path, (req, _res, next) => {
189
+ Router.ensureRequestBodyAccessor(req);
334
190
  if (Router.resolveMethodOverride(req.method, req.headers, req.body) !== method) return next("route");
335
191
  req.method = method.toUpperCase();
336
192
  return next();
337
193
  }, ...route.middlewares || [], async (req, res, next) => {
338
194
  try {
195
+ Router.ensureRequestBodyAccessor(req);
339
196
  const ctx = {
340
197
  req,
341
198
  res,
342
199
  next
343
200
  };
344
201
  const inst = instance ?? route;
345
- await Router.bindRequestToInstance(ctx, inst, route);
202
+ Router.bindRequestToInstance(ctx, inst, route, {
203
+ body: ctx.req.getBody(),
204
+ query: ctx.req.query,
205
+ params: ctx.req.params
206
+ });
346
207
  const result = handlerFunction(ctx, inst.clearRequest);
347
208
  await Promise.resolve(result);
348
209
  } catch (error) {
@@ -352,20 +213,6 @@ var Router = class Router {
352
213
  }
353
214
  }
354
215
  }
355
- static async bindRequestToInstance(ctx, instance, route) {
356
- if (!instance) return;
357
- instance.ctx = ctx;
358
- instance.body = ctx.req.body;
359
- instance.query = ctx.req.query;
360
- instance.params = ctx.req.params;
361
- instance.clearRequest = new ClearRequest({
362
- ctx,
363
- route,
364
- body: instance.body,
365
- query: instance.query,
366
- params: instance.params
367
- });
368
- }
369
216
  };
370
217
 
371
218
  //#endregion