clear-router 2.6.2 → 2.6.4

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,4 +1,5 @@
1
1
  import { a as getStandardMetadata, c as Request, i as getDesignParamTypes, n as Container, o as isClass, r as getBindingMetadataFromTargets, s as Response } from "./bindings-XLDXFpHZ.mjs";
2
+ import { createRequire } from "node:module";
2
3
  import { AsyncLocalStorage } from "node:async_hooks";
3
4
 
4
5
  //#region src/Route.ts
@@ -74,21 +75,43 @@ var CoreRouter = class {
74
75
  static pluginArgumentResolversKey = Symbol.for("clear-router:plugin-argument-resolvers");
75
76
  static requestProvider;
76
77
  static responseProvider;
78
+ static config = {
79
+ inferParamName: false,
80
+ methodOverride: {
81
+ enabled: true,
82
+ bodyKeys: ["_method"],
83
+ headerKeys: ["x-http-method"]
84
+ },
85
+ container: {
86
+ enabled: false,
87
+ autoDiscover: false
88
+ }
89
+ };
90
+ static groupContext = new AsyncLocalStorage();
91
+ static pluginRequestContext = new AsyncLocalStorage();
92
+ static routes = /* @__PURE__ */ new Set([]);
93
+ static routesByPathMethod = /* @__PURE__ */ new Map();
94
+ static routesByMethod = /* @__PURE__ */ new Map();
95
+ static routesByName = /* @__PURE__ */ new Map();
96
+ static prefix = "";
97
+ static groupMiddlewares = [];
98
+ static globalMiddlewares = [];
77
99
  /**
78
100
  * Resets the router to it's default state
79
101
  */
80
102
  static reset() {
81
- this.routes = [];
103
+ this.routes.clear();
82
104
  this.prefix = "";
83
105
  this.groupMiddlewares = [];
84
106
  this.globalMiddlewares = [];
85
- this.routesByPathMethod = {};
86
- this.routesByMethod = {};
87
- this.routesByName = {};
107
+ this.routesByPathMethod.clear();
108
+ this.routesByMethod.clear();
109
+ this.routesByName.clear();
88
110
  return this;
89
111
  }
90
112
  static createBaseConfig() {
91
113
  return {
114
+ inferParamName: false,
92
115
  methodOverride: {
93
116
  enabled: true,
94
117
  bodyKeys: ["_method"],
@@ -116,6 +139,7 @@ var CoreRouter = class {
116
139
  const g = globalThis;
117
140
  if (!g[this.defaultConfigKey]) g[this.defaultConfigKey] = this.createBaseConfig();
118
141
  return {
142
+ inferParamName: g[this.defaultConfigKey].inferParamName,
119
143
  methodOverride: { ...g[this.defaultConfigKey].methodOverride },
120
144
  container: { ...g[this.defaultConfigKey].container }
121
145
  };
@@ -147,10 +171,10 @@ var CoreRouter = class {
147
171
  return {
148
172
  config: this.getDefaultConfig(),
149
173
  groupContext: new AsyncLocalStorage(),
150
- routes: [],
151
- routesByPathMethod: {},
152
- routesByMethod: {},
153
- routesByName: {},
174
+ routes: /* @__PURE__ */ new Set([]),
175
+ routesByPathMethod: /* @__PURE__ */ new Map(),
176
+ routesByMethod: /* @__PURE__ */ new Map(),
177
+ routesByName: /* @__PURE__ */ new Map(),
154
178
  prefix: "",
155
179
  groupMiddlewares: [],
156
180
  globalMiddlewares: []
@@ -224,17 +248,6 @@ var CoreRouter = class {
224
248
  }
225
249
  };
226
250
  }
227
- static config = {
228
- methodOverride: {
229
- enabled: true,
230
- bodyKeys: ["_method"],
231
- headerKeys: ["x-http-method"]
232
- },
233
- container: {
234
- enabled: false,
235
- autoDiscover: false
236
- }
237
- };
238
251
  static configureDefaults(options) {
239
252
  const g = globalThis;
240
253
  const defaults = this.mergeConfig(g[this.defaultConfigKey] || this.createBaseConfig(), options);
@@ -295,15 +308,6 @@ var CoreRouter = class {
295
308
  if (!pending.length) return;
296
309
  await Promise.all(pending);
297
310
  }
298
- static groupContext = new AsyncLocalStorage();
299
- static pluginRequestContext = new AsyncLocalStorage();
300
- static routes = [];
301
- static routesByPathMethod = {};
302
- static routesByMethod = {};
303
- static routesByName = {};
304
- static prefix = "";
305
- static groupMiddlewares = [];
306
- static globalMiddlewares = [];
307
311
  static getCurrentPluginRequestContext() {
308
312
  return this.pluginRequestContext.getStore();
309
313
  }
@@ -349,10 +353,10 @@ var CoreRouter = class {
349
353
  headerKeys: ["x-http-method"]
350
354
  } };
351
355
  if (!this.groupContext) this.groupContext = new AsyncLocalStorage();
352
- if (!Array.isArray(this.routes)) this.routes = [];
353
- if (!this.routesByPathMethod || typeof this.routesByPathMethod !== "object") this.routesByPathMethod = {};
354
- if (!this.routesByMethod || typeof this.routesByMethod !== "object") this.routesByMethod = {};
355
- if (!this.routesByName || typeof this.routesByName !== "object") this.routesByName = {};
356
+ if (!this.routes || Array.isArray(this.routes)) this.routes = new Set(this.routes ?? []);
357
+ if (!this.routesByPathMethod) this.routesByPathMethod = /* @__PURE__ */ new Map();
358
+ if (!this.routesByMethod) this.routesByMethod = /* @__PURE__ */ new Map();
359
+ if (!this.routesByName) this.routesByName = /* @__PURE__ */ new Map();
356
360
  if (typeof this.prefix !== "string") this.prefix = "";
357
361
  if (!Array.isArray(this.groupMiddlewares)) this.groupMiddlewares = [];
358
362
  if (!Array.isArray(this.globalMiddlewares)) this.globalMiddlewares = [];
@@ -423,13 +427,15 @@ var CoreRouter = class {
423
427
  if (typeof container.enabled === "boolean") this.config.container.enabled = container.enabled;
424
428
  if (typeof container.autoDiscover === "boolean") this.config.container.autoDiscover = container.autoDiscover;
425
429
  }
430
+ if (options?.inferParamName) this.config.inferParamName = options?.inferParamName;
426
431
  const override = options?.methodOverride;
427
- if (!override) return;
428
- if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
429
- const bodyKeys = override.bodyKeys;
430
- if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
431
- const headerKeys = override.headerKeys;
432
- if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
432
+ if (override) {
433
+ if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
434
+ const bodyKeys = override.bodyKeys;
435
+ if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
436
+ const headerKeys = override.headerKeys;
437
+ if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
438
+ }
433
439
  }
434
440
  static resolveMethodOverride(method, headers, body) {
435
441
  this.ensureState();
@@ -491,16 +497,16 @@ var CoreRouter = class {
491
497
  registrationPaths,
492
498
  parameters,
493
499
  onName: (name, route, previousName) => {
494
- if (previousName && this.routesByName[previousName] === route) delete this.routesByName[previousName];
495
- this.routesByName[name] = route;
500
+ if (previousName && this.routesByName.get(previousName) === route) this.routesByName.delete(previousName);
501
+ this.routesByName.set(name, route);
496
502
  }
497
503
  });
498
- if (!methods.includes("options") && !this.routesByPathMethod[`OPTIONS ${fullPath}`]) this.options(path, this.createDefaultOptionsHandler());
499
- this.routes.push(route);
504
+ if (!methods.includes("options") && !this.routesByPathMethod.get(`OPTIONS ${fullPath}`)) this.options(path, this.createDefaultOptionsHandler());
505
+ this.routes.add(route);
500
506
  for (const method of methods.map((m) => m.toUpperCase())) {
501
- this.routesByPathMethod[`${method} ${fullPath}`] = route;
502
- if (!this.routesByMethod[method]) this.routesByMethod[method] = [];
503
- this.routesByMethod[method].push(route);
507
+ this.routesByPathMethod.set(`${method} ${fullPath}`, route);
508
+ if (!this.routesByMethod.has(method)) this.routesByMethod.set(method, []);
509
+ this.routesByMethod.get(method)?.push(route);
504
510
  }
505
511
  return route;
506
512
  }
@@ -513,6 +519,11 @@ var CoreRouter = class {
513
519
  * @param options
514
520
  */
515
521
  static apiResource(basePath, controller, options) {
522
+ let paramName = "id";
523
+ if (!!this.config.inferParamName && this.hasPackageInstalled("@h3ravel/support")) {
524
+ const { str } = createRequire(import.meta.url)("@h3ravel/support");
525
+ paramName = str(basePath).singular().afterLast("/").toString();
526
+ }
516
527
  const actions = {
517
528
  index: {
518
529
  method: "get",
@@ -520,7 +531,7 @@ var CoreRouter = class {
520
531
  },
521
532
  show: {
522
533
  method: "get",
523
- path: "/:id"
534
+ path: `/:${paramName}`
524
535
  },
525
536
  create: {
526
537
  method: "post",
@@ -528,11 +539,11 @@ var CoreRouter = class {
528
539
  },
529
540
  update: {
530
541
  method: "put",
531
- path: "/:id"
542
+ path: `/:${paramName}`
532
543
  },
533
544
  destroy: {
534
545
  method: "delete",
535
- path: "/:id"
546
+ path: `/:${paramName}`
536
547
  }
537
548
  };
538
549
  const only = options?.only || Object.keys(actions);
@@ -543,7 +554,8 @@ var CoreRouter = class {
543
554
  if (typeof preController[action] === "function") {
544
555
  const { method, path } = actions[action];
545
556
  const actionMiddlewares = typeof options?.middlewares === "object" && !Array.isArray(options.middlewares) ? options.middlewares[action] : options?.middlewares;
546
- this.add(method, `${basePath}${path}`, [controller, action], Array.isArray(actionMiddlewares) ? actionMiddlewares : actionMiddlewares ? [actionMiddlewares] : void 0);
557
+ const name = `${basePath}${path}`.replace(/\/:[^/]+|\/\{[^}]+\}/g, "").replace(/\{(\w+):[^}]+\}/g, "$1").replace(/\/|:|[{}]/g, ".").replace(/\.{2,}/g, ".").replace(/^\.|\.$/g, "");
558
+ this.add(method, `${basePath}${path}`, [controller, action], Array.isArray(actionMiddlewares) ? actionMiddlewares : actionMiddlewares ? [actionMiddlewares] : void 0).name(name + "." + action.toLowerCase());
547
559
  }
548
560
  }
549
561
  }
@@ -662,14 +674,14 @@ var CoreRouter = class {
662
674
  }
663
675
  static allRoutes(type) {
664
676
  this.ensureState();
665
- if (type === "method") return this.routesByMethod;
666
- if (type === "path") return this.routesByPathMethod;
667
- if (type === "name") return this.routesByName;
668
- return this.routes.filter((e) => e.methods.length > 1 || e.methods[0] !== "options");
677
+ if (type === "method") return Object.fromEntries(this.routesByMethod.entries());
678
+ if (type === "path") return Object.fromEntries(this.routesByPathMethod.entries());
679
+ if (type === "name") return Object.fromEntries(this.routesByName.entries());
680
+ return Array.from(this.routes).filter((e) => e.methods.length > 1 || e.methods[0] !== "options");
669
681
  }
670
682
  static route(name) {
671
683
  this.ensureState();
672
- return this.routesByName[name];
684
+ return this.routesByName.get(name);
673
685
  }
674
686
  static url(name, params) {
675
687
  return this.route(name)?.toPath(params);
@@ -690,6 +702,14 @@ var CoreRouter = class {
690
702
  static setResponseProvider(provider) {
691
703
  this.responseProvider = provider;
692
704
  }
705
+ static hasPackageInstalled(name) {
706
+ try {
707
+ createRequire(import.meta.url).resolve(name, { paths: [process.cwd()] });
708
+ return true;
709
+ } catch {
710
+ return false;
711
+ }
712
+ }
693
713
  static initializeInstance(provider, args) {
694
714
  const isRequest = [
695
715
  "CoreRequest",
@@ -49,6 +49,11 @@ type ControllerAction = 'index' | 'show' | 'create' | 'update' | 'destroy';
49
49
  type RequestData = Record<string, any>;
50
50
  type ApiResourceMiddleware<M = any> = M | M[] | { [K in ControllerAction]?: M | M[] };
51
51
  interface RouterConfig {
52
+ /**
53
+ * When enabled, API param name will be infered from the route path.
54
+ * So instead of getting /api/users/:id, we will now get /api/users/:user
55
+ */
56
+ inferParamName?: boolean;
52
57
  /**
53
58
  * Configuration for method override functionality, allowing clients to use a
54
59
  * specific header or body parameter to override the HTTP method.
@@ -326,6 +331,19 @@ declare abstract class CoreRouter {
326
331
  private static readonly pluginArgumentResolversKey;
327
332
  private static requestProvider?;
328
333
  private static responseProvider?;
334
+ static config: RouterConfig;
335
+ protected static groupContext: AsyncLocalStorage<{
336
+ prefix: string;
337
+ groupMiddlewares: any[];
338
+ }>;
339
+ protected static pluginRequestContext: AsyncLocalStorage<ClearRouterPluginRequestContext>;
340
+ static routes: Set<Route<any, any, any>>;
341
+ static routesByPathMethod: Map<string, Route<any, any, any>>;
342
+ static routesByMethod: Map<"GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD", Route<any, any, any>[]>;
343
+ static routesByName: Map<string, Route<any, any, any>>;
344
+ static prefix: string;
345
+ static groupMiddlewares: any[];
346
+ static globalMiddlewares: any[];
329
347
  /**
330
348
  * Resets the router to it's default state
331
349
  */
@@ -344,17 +362,16 @@ declare abstract class CoreRouter {
344
362
  prefix: string;
345
363
  groupMiddlewares: any[];
346
364
  }>;
347
- routes: Array<Route<any, any, any>>;
348
- routesByPathMethod: Record<string, Route<any, any, any>>;
349
- routesByMethod: { [method in Uppercase<HttpMethod>]?: Array<Route<any, any, any>> };
350
- routesByName: Record<string, Route<any, any, any>>;
365
+ routes: Set<never>;
366
+ routesByPathMethod: Map<any, any>;
367
+ routesByMethod: Map<any, any>;
368
+ routesByName: Map<any, any>;
351
369
  prefix: string;
352
370
  groupMiddlewares: any[];
353
371
  globalMiddlewares: any[];
354
372
  };
355
373
  protected static bindStateAccessors(): void;
356
374
  protected static createDefaultOptionsHandler(): any;
357
- static config: RouterConfig;
358
375
  static configureDefaults(options?: RouterConfig): void;
359
376
  /**
360
377
  * Use a registered plugin
@@ -366,18 +383,6 @@ declare abstract class CoreRouter {
366
383
  */
367
384
  static use<Options = any>(plugin: ClearRouterPluginInput<Options>, options?: Options): Promise<void>;
368
385
  protected static pluginsReady(): Promise<void>;
369
- protected static groupContext: AsyncLocalStorage<{
370
- prefix: string;
371
- groupMiddlewares: any[];
372
- }>;
373
- protected static pluginRequestContext: AsyncLocalStorage<ClearRouterPluginRequestContext>;
374
- static routes: Array<Route<any, any, any>>;
375
- static routesByPathMethod: Record<string, Route<any, any, any>>;
376
- static routesByMethod: { [method in Uppercase<HttpMethod>]?: Array<Route<any, any, any>> };
377
- static routesByName: Record<string, Route<any, any, any>>;
378
- static prefix: string;
379
- static groupMiddlewares: any[];
380
- static globalMiddlewares: any[];
381
386
  protected static getCurrentPluginRequestContext(): ClearRouterPluginRequestContext | undefined;
382
387
  protected static createPluginRequestContext(ctx: any): ClearRouterPluginRequestContext;
383
388
  protected static createPluginBind(): PluginBind;
@@ -542,6 +547,7 @@ declare abstract class CoreRouter {
542
547
  * @param provider
543
548
  */
544
549
  static setResponseProvider(provider: typeof Response$2): void;
550
+ private static hasPackageInstalled;
545
551
  /**
546
552
  * Provide a class that will overide the base Response instance
547
553
  *
@@ -1,5 +1,6 @@
1
1
  const require_bindings = require('./bindings-CLsZjOEy.cjs');
2
2
  let node_async_hooks = require("node:async_hooks");
3
+ let node_module = require("node:module");
3
4
 
4
5
  //#region src/Route.ts
5
6
  var Route = class {
@@ -74,21 +75,43 @@ var CoreRouter = class {
74
75
  static pluginArgumentResolversKey = Symbol.for("clear-router:plugin-argument-resolvers");
75
76
  static requestProvider;
76
77
  static responseProvider;
78
+ static config = {
79
+ inferParamName: false,
80
+ methodOverride: {
81
+ enabled: true,
82
+ bodyKeys: ["_method"],
83
+ headerKeys: ["x-http-method"]
84
+ },
85
+ container: {
86
+ enabled: false,
87
+ autoDiscover: false
88
+ }
89
+ };
90
+ static groupContext = new node_async_hooks.AsyncLocalStorage();
91
+ static pluginRequestContext = new node_async_hooks.AsyncLocalStorage();
92
+ static routes = /* @__PURE__ */ new Set([]);
93
+ static routesByPathMethod = /* @__PURE__ */ new Map();
94
+ static routesByMethod = /* @__PURE__ */ new Map();
95
+ static routesByName = /* @__PURE__ */ new Map();
96
+ static prefix = "";
97
+ static groupMiddlewares = [];
98
+ static globalMiddlewares = [];
77
99
  /**
78
100
  * Resets the router to it's default state
79
101
  */
80
102
  static reset() {
81
- this.routes = [];
103
+ this.routes.clear();
82
104
  this.prefix = "";
83
105
  this.groupMiddlewares = [];
84
106
  this.globalMiddlewares = [];
85
- this.routesByPathMethod = {};
86
- this.routesByMethod = {};
87
- this.routesByName = {};
107
+ this.routesByPathMethod.clear();
108
+ this.routesByMethod.clear();
109
+ this.routesByName.clear();
88
110
  return this;
89
111
  }
90
112
  static createBaseConfig() {
91
113
  return {
114
+ inferParamName: false,
92
115
  methodOverride: {
93
116
  enabled: true,
94
117
  bodyKeys: ["_method"],
@@ -116,6 +139,7 @@ var CoreRouter = class {
116
139
  const g = globalThis;
117
140
  if (!g[this.defaultConfigKey]) g[this.defaultConfigKey] = this.createBaseConfig();
118
141
  return {
142
+ inferParamName: g[this.defaultConfigKey].inferParamName,
119
143
  methodOverride: { ...g[this.defaultConfigKey].methodOverride },
120
144
  container: { ...g[this.defaultConfigKey].container }
121
145
  };
@@ -147,10 +171,10 @@ var CoreRouter = class {
147
171
  return {
148
172
  config: this.getDefaultConfig(),
149
173
  groupContext: new node_async_hooks.AsyncLocalStorage(),
150
- routes: [],
151
- routesByPathMethod: {},
152
- routesByMethod: {},
153
- routesByName: {},
174
+ routes: /* @__PURE__ */ new Set([]),
175
+ routesByPathMethod: /* @__PURE__ */ new Map(),
176
+ routesByMethod: /* @__PURE__ */ new Map(),
177
+ routesByName: /* @__PURE__ */ new Map(),
154
178
  prefix: "",
155
179
  groupMiddlewares: [],
156
180
  globalMiddlewares: []
@@ -224,17 +248,6 @@ var CoreRouter = class {
224
248
  }
225
249
  };
226
250
  }
227
- static config = {
228
- methodOverride: {
229
- enabled: true,
230
- bodyKeys: ["_method"],
231
- headerKeys: ["x-http-method"]
232
- },
233
- container: {
234
- enabled: false,
235
- autoDiscover: false
236
- }
237
- };
238
251
  static configureDefaults(options) {
239
252
  const g = globalThis;
240
253
  const defaults = this.mergeConfig(g[this.defaultConfigKey] || this.createBaseConfig(), options);
@@ -295,15 +308,6 @@ var CoreRouter = class {
295
308
  if (!pending.length) return;
296
309
  await Promise.all(pending);
297
310
  }
298
- static groupContext = new node_async_hooks.AsyncLocalStorage();
299
- static pluginRequestContext = new node_async_hooks.AsyncLocalStorage();
300
- static routes = [];
301
- static routesByPathMethod = {};
302
- static routesByMethod = {};
303
- static routesByName = {};
304
- static prefix = "";
305
- static groupMiddlewares = [];
306
- static globalMiddlewares = [];
307
311
  static getCurrentPluginRequestContext() {
308
312
  return this.pluginRequestContext.getStore();
309
313
  }
@@ -349,10 +353,10 @@ var CoreRouter = class {
349
353
  headerKeys: ["x-http-method"]
350
354
  } };
351
355
  if (!this.groupContext) this.groupContext = new node_async_hooks.AsyncLocalStorage();
352
- if (!Array.isArray(this.routes)) this.routes = [];
353
- if (!this.routesByPathMethod || typeof this.routesByPathMethod !== "object") this.routesByPathMethod = {};
354
- if (!this.routesByMethod || typeof this.routesByMethod !== "object") this.routesByMethod = {};
355
- if (!this.routesByName || typeof this.routesByName !== "object") this.routesByName = {};
356
+ if (!this.routes || Array.isArray(this.routes)) this.routes = new Set(this.routes ?? []);
357
+ if (!this.routesByPathMethod) this.routesByPathMethod = /* @__PURE__ */ new Map();
358
+ if (!this.routesByMethod) this.routesByMethod = /* @__PURE__ */ new Map();
359
+ if (!this.routesByName) this.routesByName = /* @__PURE__ */ new Map();
356
360
  if (typeof this.prefix !== "string") this.prefix = "";
357
361
  if (!Array.isArray(this.groupMiddlewares)) this.groupMiddlewares = [];
358
362
  if (!Array.isArray(this.globalMiddlewares)) this.globalMiddlewares = [];
@@ -423,13 +427,15 @@ var CoreRouter = class {
423
427
  if (typeof container.enabled === "boolean") this.config.container.enabled = container.enabled;
424
428
  if (typeof container.autoDiscover === "boolean") this.config.container.autoDiscover = container.autoDiscover;
425
429
  }
430
+ if (options?.inferParamName) this.config.inferParamName = options?.inferParamName;
426
431
  const override = options?.methodOverride;
427
- if (!override) return;
428
- if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
429
- const bodyKeys = override.bodyKeys;
430
- if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
431
- const headerKeys = override.headerKeys;
432
- if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
432
+ if (override) {
433
+ if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
434
+ const bodyKeys = override.bodyKeys;
435
+ if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
436
+ const headerKeys = override.headerKeys;
437
+ if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
438
+ }
433
439
  }
434
440
  static resolveMethodOverride(method, headers, body) {
435
441
  this.ensureState();
@@ -491,16 +497,16 @@ var CoreRouter = class {
491
497
  registrationPaths,
492
498
  parameters,
493
499
  onName: (name, route, previousName) => {
494
- if (previousName && this.routesByName[previousName] === route) delete this.routesByName[previousName];
495
- this.routesByName[name] = route;
500
+ if (previousName && this.routesByName.get(previousName) === route) this.routesByName.delete(previousName);
501
+ this.routesByName.set(name, route);
496
502
  }
497
503
  });
498
- if (!methods.includes("options") && !this.routesByPathMethod[`OPTIONS ${fullPath}`]) this.options(path, this.createDefaultOptionsHandler());
499
- this.routes.push(route);
504
+ if (!methods.includes("options") && !this.routesByPathMethod.get(`OPTIONS ${fullPath}`)) this.options(path, this.createDefaultOptionsHandler());
505
+ this.routes.add(route);
500
506
  for (const method of methods.map((m) => m.toUpperCase())) {
501
- this.routesByPathMethod[`${method} ${fullPath}`] = route;
502
- if (!this.routesByMethod[method]) this.routesByMethod[method] = [];
503
- this.routesByMethod[method].push(route);
507
+ this.routesByPathMethod.set(`${method} ${fullPath}`, route);
508
+ if (!this.routesByMethod.has(method)) this.routesByMethod.set(method, []);
509
+ this.routesByMethod.get(method)?.push(route);
504
510
  }
505
511
  return route;
506
512
  }
@@ -513,6 +519,11 @@ var CoreRouter = class {
513
519
  * @param options
514
520
  */
515
521
  static apiResource(basePath, controller, options) {
522
+ let paramName = "id";
523
+ if (!!this.config.inferParamName && this.hasPackageInstalled("@h3ravel/support")) {
524
+ const { str } = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("@h3ravel/support");
525
+ paramName = str(basePath).singular().afterLast("/").toString();
526
+ }
516
527
  const actions = {
517
528
  index: {
518
529
  method: "get",
@@ -520,7 +531,7 @@ var CoreRouter = class {
520
531
  },
521
532
  show: {
522
533
  method: "get",
523
- path: "/:id"
534
+ path: `/:${paramName}`
524
535
  },
525
536
  create: {
526
537
  method: "post",
@@ -528,11 +539,11 @@ var CoreRouter = class {
528
539
  },
529
540
  update: {
530
541
  method: "put",
531
- path: "/:id"
542
+ path: `/:${paramName}`
532
543
  },
533
544
  destroy: {
534
545
  method: "delete",
535
- path: "/:id"
546
+ path: `/:${paramName}`
536
547
  }
537
548
  };
538
549
  const only = options?.only || Object.keys(actions);
@@ -543,7 +554,8 @@ var CoreRouter = class {
543
554
  if (typeof preController[action] === "function") {
544
555
  const { method, path } = actions[action];
545
556
  const actionMiddlewares = typeof options?.middlewares === "object" && !Array.isArray(options.middlewares) ? options.middlewares[action] : options?.middlewares;
546
- this.add(method, `${basePath}${path}`, [controller, action], Array.isArray(actionMiddlewares) ? actionMiddlewares : actionMiddlewares ? [actionMiddlewares] : void 0);
557
+ const name = `${basePath}${path}`.replace(/\/:[^/]+|\/\{[^}]+\}/g, "").replace(/\{(\w+):[^}]+\}/g, "$1").replace(/\/|:|[{}]/g, ".").replace(/\.{2,}/g, ".").replace(/^\.|\.$/g, "");
558
+ this.add(method, `${basePath}${path}`, [controller, action], Array.isArray(actionMiddlewares) ? actionMiddlewares : actionMiddlewares ? [actionMiddlewares] : void 0).name(name + "." + action.toLowerCase());
547
559
  }
548
560
  }
549
561
  }
@@ -662,14 +674,14 @@ var CoreRouter = class {
662
674
  }
663
675
  static allRoutes(type) {
664
676
  this.ensureState();
665
- if (type === "method") return this.routesByMethod;
666
- if (type === "path") return this.routesByPathMethod;
667
- if (type === "name") return this.routesByName;
668
- return this.routes.filter((e) => e.methods.length > 1 || e.methods[0] !== "options");
677
+ if (type === "method") return Object.fromEntries(this.routesByMethod.entries());
678
+ if (type === "path") return Object.fromEntries(this.routesByPathMethod.entries());
679
+ if (type === "name") return Object.fromEntries(this.routesByName.entries());
680
+ return Array.from(this.routes).filter((e) => e.methods.length > 1 || e.methods[0] !== "options");
669
681
  }
670
682
  static route(name) {
671
683
  this.ensureState();
672
- return this.routesByName[name];
684
+ return this.routesByName.get(name);
673
685
  }
674
686
  static url(name, params) {
675
687
  return this.route(name)?.toPath(params);
@@ -690,6 +702,14 @@ var CoreRouter = class {
690
702
  static setResponseProvider(provider) {
691
703
  this.responseProvider = provider;
692
704
  }
705
+ static hasPackageInstalled(name) {
706
+ try {
707
+ (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href).resolve(name, { paths: [process.cwd()] });
708
+ return true;
709
+ } catch {
710
+ return false;
711
+ }
712
+ }
693
713
  static initializeInstance(provider, args) {
694
714
  const isRequest = [
695
715
  "CoreRequest",
@@ -17,6 +17,11 @@ type ControllerAction = 'index' | 'show' | 'create' | 'update' | 'destroy';
17
17
  type RequestData = Record<string, any>;
18
18
  type ApiResourceMiddleware<M = any> = M | M[] | { [K in ControllerAction]?: M | M[] };
19
19
  interface RouterConfig {
20
+ /**
21
+ * When enabled, API param name will be infered from the route path.
22
+ * So instead of getting /api/users/:id, we will now get /api/users/:user
23
+ */
24
+ inferParamName?: boolean;
20
25
  /**
21
26
  * Configuration for method override functionality, allowing clients to use a
22
27
  * specific header or body parameter to override the HTTP method.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-router",
3
- "version": "2.6.2",
3
+ "version": "2.6.4",
4
4
  "description": "Laravel-style routing for Node.js with support for Express, H3, Fastify, Hono, and Koa, including CommonJS, ESM, and TypeScript support.",
5
5
  "keywords": [
6
6
  "h3",
@@ -88,6 +88,7 @@
88
88
  "h3": "^2.0.1-rc.16",
89
89
  "hono": "^4.0.0",
90
90
  "koa": "^3.0.0",
91
+ "@h3ravel/support": "^0.15.11",
91
92
  "reflect-metadata": "^0.2.2"
92
93
  },
93
94
  "peerDependenciesMeta": {
@@ -109,6 +110,9 @@
109
110
  "koa": {
110
111
  "optional": true
111
112
  },
113
+ "@h3ravel/support": {
114
+ "optional": true
115
+ },
112
116
  "reflect-metadata": {
113
117
  "optional": true
114
118
  }