clear-router 2.7.6 → 2.8.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.
Files changed (116) hide show
  1. package/dist/ClearRequest.cjs +23 -0
  2. package/dist/ClearRequest.d.cts +28 -0
  3. package/dist/ClearRequest.d.mts +28 -0
  4. package/dist/ClearRequest.mjs +22 -0
  5. package/dist/Contracts.d.cts +12 -0
  6. package/dist/Contracts.d.mts +12 -0
  7. package/dist/Controller.cjs +12 -0
  8. package/dist/Controller.d.cts +14 -0
  9. package/dist/Controller.d.mts +14 -0
  10. package/dist/Controller.mjs +11 -0
  11. package/dist/{Request-DKXwa_W0.d.mts → Request-Ci0UQ-Vl.d.mts} +32 -5
  12. package/dist/ResourceRouteSelection.cjs +26 -0
  13. package/dist/ResourceRouteSelection.d.cts +20 -0
  14. package/dist/ResourceRouteSelection.d.mts +20 -0
  15. package/dist/ResourceRouteSelection.mjs +25 -0
  16. package/dist/ResourceRoutes.cjs +60 -0
  17. package/dist/ResourceRoutes.d.cts +37 -0
  18. package/dist/ResourceRoutes.d.mts +37 -0
  19. package/dist/ResourceRoutes.mjs +60 -0
  20. package/dist/Route.cjs +90 -0
  21. package/dist/Route.d.cts +62 -0
  22. package/dist/Route.d.mts +62 -0
  23. package/dist/Route.mjs +89 -0
  24. package/dist/core/Request.cjs +35 -0
  25. package/dist/core/Request.d.cts +25 -0
  26. package/dist/core/Request.d.mts +25 -0
  27. package/dist/core/Request.mjs +35 -0
  28. package/dist/core/Response.cjs +59 -0
  29. package/dist/core/Response.d.cts +24 -0
  30. package/dist/core/Response.d.mts +24 -0
  31. package/dist/core/Response.mjs +58 -0
  32. package/dist/{bindings-CLsZjOEy.cjs → core/bindings.cjs} +10 -160
  33. package/dist/{bindings-CNL7bpz5.d.mts → core/bindings.d.cts} +1 -1
  34. package/dist/{bindings-CxvtC8XS.d.cts → core/bindings.d.mts} +1 -1
  35. package/dist/{bindings-XLDXFpHZ.mjs → core/bindings.mjs} +3 -110
  36. package/dist/core/index.cjs +7 -17
  37. package/dist/core/index.d.cts +4 -1
  38. package/dist/core/index.d.mts +4 -1
  39. package/dist/core/index.mjs +4 -14
  40. package/dist/core/plugins.cjs +14 -0
  41. package/dist/core/plugins.d.cts +109 -0
  42. package/dist/core/plugins.d.mts +109 -0
  43. package/dist/core/plugins.mjs +13 -0
  44. package/dist/{responses-Bvnk0uvc.cjs → core/responses.cjs} +5 -20
  45. package/dist/{responses-BvETUeDL.mjs → core/responses.mjs} +2 -2
  46. package/dist/{router-C6W-k6sS.cjs → core/router.cjs} +67 -72
  47. package/dist/core/router.d.cts +286 -0
  48. package/dist/core/router.d.mts +286 -0
  49. package/dist/{router-Dc9w86Wn.mjs → core/router.mjs} +62 -62
  50. package/dist/decorators/index.cjs +1 -1
  51. package/dist/decorators/index.d.cts +1 -1
  52. package/dist/decorators/index.d.mts +1 -1
  53. package/dist/decorators/index.mjs +1 -1
  54. package/dist/decorators/setup.cjs +2 -2
  55. package/dist/decorators/setup.d.cts +1 -1
  56. package/dist/decorators/setup.d.mts +1 -2
  57. package/dist/decorators/setup.mjs +2 -2
  58. package/dist/express/index.cjs +2 -265
  59. package/dist/express/index.d.cts +1 -127
  60. package/dist/express/index.d.mts +1 -127
  61. package/dist/express/index.mjs +1 -264
  62. package/dist/express/router.cjs +265 -0
  63. package/dist/express/router.d.cts +132 -0
  64. package/dist/express/router.d.mts +132 -0
  65. package/dist/express/router.mjs +265 -0
  66. package/dist/fastify/index.cjs +2 -254
  67. package/dist/fastify/index.d.cts +1 -125
  68. package/dist/fastify/index.d.mts +1 -125
  69. package/dist/fastify/index.mjs +1 -253
  70. package/dist/fastify/router.cjs +254 -0
  71. package/dist/fastify/router.d.cts +130 -0
  72. package/dist/fastify/router.d.mts +130 -0
  73. package/dist/fastify/router.mjs +254 -0
  74. package/dist/h3/index.cjs +2 -260
  75. package/dist/h3/index.d.cts +1 -128
  76. package/dist/h3/index.d.mts +1 -128
  77. package/dist/h3/index.mjs +1 -259
  78. package/dist/h3/router.cjs +260 -0
  79. package/dist/h3/router.d.cts +133 -0
  80. package/dist/h3/router.d.mts +133 -0
  81. package/dist/h3/router.mjs +260 -0
  82. package/dist/hono/index.cjs +2 -251
  83. package/dist/hono/index.d.cts +1 -130
  84. package/dist/hono/index.d.mts +1 -130
  85. package/dist/hono/index.mjs +1 -250
  86. package/dist/hono/router.cjs +251 -0
  87. package/dist/hono/router.d.cts +135 -0
  88. package/dist/hono/router.d.mts +135 -0
  89. package/dist/hono/router.mjs +251 -0
  90. package/dist/index.cjs +16 -1097
  91. package/dist/index.d.cts +9 -563
  92. package/dist/index.d.mts +9 -563
  93. package/dist/index.mjs +8 -1089
  94. package/dist/koa/index.cjs +2 -261
  95. package/dist/koa/index.d.cts +1 -131
  96. package/dist/koa/index.d.mts +1 -131
  97. package/dist/koa/index.mjs +1 -260
  98. package/dist/koa/router.cjs +261 -0
  99. package/dist/koa/router.d.cts +136 -0
  100. package/dist/koa/router.d.mts +136 -0
  101. package/dist/koa/router.mjs +261 -0
  102. package/dist/types/basic.d.cts +53 -0
  103. package/dist/types/basic.d.mts +11 -1
  104. package/dist/types/express.d.cts +48 -0
  105. package/dist/types/express.d.mts +10 -4
  106. package/dist/types/fastify.d.cts +24 -0
  107. package/dist/types/fastify.d.mts +7 -4
  108. package/dist/types/h3.d.cts +46 -0
  109. package/dist/types/h3.d.mts +10 -5
  110. package/dist/types/hono.d.cts +22 -0
  111. package/dist/types/hono.d.mts +8 -6
  112. package/dist/types/koa.d.cts +26 -0
  113. package/dist/types/koa.d.mts +7 -5
  114. package/package.json +1 -1
  115. package/dist/router-BEgAxp5A.d.cts +0 -649
  116. package/dist/router-DKKYx23P.d.mts +0 -649
package/dist/index.mjs CHANGED
@@ -1,1091 +1,10 @@
1
- import { createRequire } from "node:module";
2
- import { AsyncLocalStorage } from "node:async_hooks";
1
+ import { ClearRequest } from "./ClearRequest.mjs";
2
+ import { Controller } from "./Controller.mjs";
3
+ import { Route } from "./Route.mjs";
4
+ import { Request } from "./core/Request.mjs";
5
+ import { Response } from "./core/Response.mjs";
6
+ import { definePlugin } from "./core/plugins.mjs";
7
+ import { CoreRouter } from "./core/router.mjs";
8
+ import "./core/index.mjs";
3
9
 
4
- //#region src/ClearRequest.ts
5
- var ClearRequest = class {
6
- /**
7
- * @param body - Parsed request body
8
- */
9
- body;
10
- /**
11
- * @param query - Parsed query parameters
12
- */
13
- query;
14
- /**
15
- * @param params - Parsed route parameters
16
- */
17
- params;
18
- route;
19
- constructor(init) {
20
- Object.assign(this, init);
21
- }
22
- };
23
-
24
- //#endregion
25
- //#region src/Controller.ts
26
- var Controller = class {
27
- ctx;
28
- body;
29
- query;
30
- params;
31
- clearRequest;
32
- };
33
-
34
- //#endregion
35
- //#region src/Route.ts
36
- var Route = class {
37
- ctx;
38
- body = {};
39
- query = {};
40
- params = {};
41
- clearRequest;
42
- methods;
43
- path;
44
- registrationPaths;
45
- parameters;
46
- routeName;
47
- handler;
48
- middlewares;
49
- controllerName;
50
- actionName;
51
- handlerType;
52
- middlewareCount;
53
- constructor(methods, path, handler, middlewares = [], options = {}) {
54
- this.methods = methods;
55
- this.path = path;
56
- this.registrationPaths = options.registrationPaths || [path];
57
- this.parameters = options.parameters || [];
58
- this.handler = handler;
59
- this.middlewares = middlewares;
60
- this.handlerType = Array.isArray(handler) ? "controller" : "function";
61
- this.middlewareCount = middlewares.length;
62
- this.controllerName = Array.isArray(handler) ? handler[0]?.name : void 0;
63
- this.actionName = Array.isArray(handler) ? handler[1] : typeof handler === "function" ? handler.constructor.name ?? handler.name : void 0;
64
- this.onName = options.onName;
65
- }
66
- onName;
67
- name(name) {
68
- const previousName = this.routeName;
69
- this.routeName = name;
70
- this.onName?.(name, this, previousName);
71
- return this;
72
- }
73
- toPath(params = {}) {
74
- return this.path.replace(/\/?\{([^{}]+)\}/g, (segment, raw) => {
75
- const optional = raw.endsWith("?");
76
- const [rawName, rawField] = (optional ? raw.slice(0, -1) : raw).split(":", 2);
77
- const name = rawName.trim();
78
- const field = rawField?.trim();
79
- const value = params[name];
80
- const resolved = field && value && typeof value === "object" ? value[field] : value;
81
- if (typeof resolved === "undefined" || resolved === null || resolved === "") {
82
- if (optional) return "";
83
- throw new Error(`Missing required route parameter: ${name}`);
84
- }
85
- return `${segment.startsWith("/") ? "/" : ""}${encodeURIComponent(String(resolved))}`;
86
- }) || "/";
87
- }
88
- };
89
-
90
- //#endregion
91
- //#region src/core/Request.ts
92
- var Request = class extends ClearRequest {
93
- original;
94
- method = "GET";
95
- path = "/";
96
- url = "/";
97
- headers = {};
98
- constructor(init) {
99
- super(init);
100
- Object.assign(this, init);
101
- }
102
- getBody() {
103
- return this.body ?? {};
104
- }
105
- header(name) {
106
- if (typeof this.headers.get === "function") return this.headers.get(name) || "";
107
- const headers = this.headers;
108
- const value = headers[name] ?? headers[name.toLowerCase()];
109
- return Array.isArray(value) ? String(value[0] ?? "") : String(value ?? "");
110
- }
111
- param(name) {
112
- return this.params?.[name];
113
- }
114
- input(name) {
115
- return this.body?.[name] ?? this.query?.[name] ?? this.params?.[name];
116
- }
117
- is(method) {
118
- return this.method.toLowerCase() === String(method).toLowerCase();
119
- }
120
- };
121
-
122
- //#endregion
123
- //#region src/core/Response.ts
124
- var Response = class {
125
- body;
126
- headers = new Headers();
127
- sent = false;
128
- statusCode = 200;
129
- statusText = "OK";
130
- constructor(init) {
131
- const { status: _, ...rest } = init ?? {};
132
- Object.assign(this, rest);
133
- if (init?.headers && !(init.headers instanceof Headers)) this.headers = new Headers(init.headers);
134
- if (init?.status && typeof init.status === "number") this.statusCode = init?.status;
135
- }
136
- status(code) {
137
- this.statusCode = code;
138
- return this;
139
- }
140
- setStatusText(text) {
141
- this.statusText = text;
142
- return this;
143
- }
144
- code(code) {
145
- return this.status(code);
146
- }
147
- setHeader(name, value) {
148
- this.headers.set(name, value);
149
- return this;
150
- }
151
- header(name, value) {
152
- return this.setHeader(name, value);
153
- }
154
- set(name, value) {
155
- return this.setHeader(name, value);
156
- }
157
- type(contentType) {
158
- return this.setHeader("Content-Type", contentType);
159
- }
160
- send(body) {
161
- this.body = body;
162
- this.sent = true;
163
- return this;
164
- }
165
- json(body) {
166
- return this.type("application/json; charset=utf-8").send(body);
167
- }
168
- html(body) {
169
- return this.type("text/html; charset=utf-8").send(body);
170
- }
171
- text(body) {
172
- return this.type("text/plain; charset=utf-8").send(body);
173
- }
174
- noContent() {
175
- return this.status(204).send(null);
176
- }
177
- };
178
-
179
- //#endregion
180
- //#region src/core/plugins.ts
181
- /**
182
- * Creates a new plugin
183
- *
184
- * @param plugin
185
- * @returns
186
- */
187
- function definePlugin(plugin) {
188
- return plugin;
189
- }
190
-
191
- //#endregion
192
- //#region src/core/bindings.ts
193
- const metadataKey = Symbol.for("clear-router:binding-metadata");
194
- const bindings = /* @__PURE__ */ new WeakMap();
195
- var Container = class {
196
- static registry = /* @__PURE__ */ new Map();
197
- static bind(token, value) {
198
- this.registry.set(token, value);
199
- }
200
- static unbind(token) {
201
- this.registry.delete(token);
202
- }
203
- static clear() {
204
- this.registry.clear();
205
- }
206
- static has(token) {
207
- return this.registry.has(token) || Boolean(this.findEquivalentToken(token));
208
- }
209
- static bindings() {
210
- return Object.fromEntries(this.registry.entries());
211
- }
212
- static async resolve(token, ctx, autoDiscover = false) {
213
- if (token === Request) return ctx.clearRequest;
214
- if (token === Response) return ctx.clearResponse;
215
- const binding = this.getBinding(token);
216
- if (binding) return this.resolveBinding(binding, ctx, autoDiscover);
217
- if (autoDiscover && typeof token === "function") return new token();
218
- }
219
- static getBinding(token) {
220
- if (this.registry.has(token)) return this.registry.get(token);
221
- const equivalent = this.findEquivalentToken(token);
222
- return equivalent ? this.registry.get(equivalent) : void 0;
223
- }
224
- static findEquivalentToken(token) {
225
- const name = token.name;
226
- if (!name) return;
227
- const tokenParent = Object.getPrototypeOf(token);
228
- const tokenProps = this.getComparableStaticProps(token);
229
- for (const registered of this.registry.keys()) {
230
- if (registered === token) continue;
231
- if (registered.name !== name) continue;
232
- const registeredParent = Object.getPrototypeOf(registered);
233
- if (tokenParent && registeredParent && tokenParent.name !== registeredParent.name) continue;
234
- const registeredProps = this.getComparableStaticProps(registered);
235
- if (!this.staticPropsMatch(token, registered, tokenProps, registeredProps)) continue;
236
- return registered;
237
- }
238
- }
239
- static getComparableStaticProps(token) {
240
- return Object.getOwnPropertyNames(token).filter((prop) => {
241
- return ![
242
- "length",
243
- "name",
244
- "prototype",
245
- "arguments",
246
- "caller"
247
- ].includes(prop);
248
- });
249
- }
250
- static staticPropsMatch(token, registered, tokenProps, registeredProps) {
251
- if (tokenProps.length !== registeredProps.length) return false;
252
- for (const prop of tokenProps) {
253
- if (!registeredProps.includes(prop)) return false;
254
- if (Reflect.get(token, prop) !== Reflect.get(registered, prop)) return false;
255
- }
256
- return true;
257
- }
258
- static async resolveBinding(binding, ctx, autoDiscover) {
259
- if (!binding) return void 0;
260
- if (typeof binding !== "function") return binding;
261
- if (isClass(binding)) return new binding();
262
- const resolved = await binding(ctx);
263
- if (typeof resolved === "function" && autoDiscover && isClass(resolved)) return new resolved();
264
- return resolved;
265
- }
266
- };
267
- function getStandardMetadata(metadata, propertyKey) {
268
- const store = metadata && metadata[metadataKey];
269
- if (!store) return void 0;
270
- return propertyKey ? store[propertyKey] : void 0;
271
- }
272
- function getBindingMetadataFromTargets(targets) {
273
- for (const { target, propertyKey } of targets) {
274
- if (!target) continue;
275
- const metadata = getBindingMetadata(target, propertyKey);
276
- if (metadata) return metadata;
277
- const standardMetadata = getStandardMetadata(target[Symbol.metadata], propertyKey);
278
- if (standardMetadata) return standardMetadata;
279
- }
280
- }
281
- function getBindingMetadata(target, propertyKey) {
282
- if (propertyKey) return bindings.get(target)?.get(propertyKey);
283
- return bindings.get(target)?.get("__route_handler__");
284
- }
285
- function getDesignParamTypes(target, propertyKey) {
286
- return Reflect.getMetadata?.("design:paramtypes", target, propertyKey) ?? [];
287
- }
288
- function isClass(value) {
289
- return typeof value === "function" && /^class\s/.test(Function.prototype.toString.call(value));
290
- }
291
-
292
- //#endregion
293
- //#region src/core/router.ts
294
- /**
295
- * @class clear-router CoreRouter
296
- * @description Core routing logic for clear-router, shared between all supported adapters (Express.js, H3, etc.)
297
- * @author 3m1n3nc3
298
- * @repository https://github.com/arkstack-tmp/clear-router
299
- */
300
- var CoreRouter = class {
301
- static routerStateNamespace = "clear-router:core";
302
- static stateStoreKey = Symbol.for("clear-router:router-state");
303
- static stateBoundKey = Symbol.for("clear-router:router-state-bound");
304
- static defaultConfigKey = Symbol.for("clear-router:default-config");
305
- static pluginStoreKey = Symbol.for("clear-router:plugins");
306
- static pluginPendingKey = Symbol.for("clear-router:plugin-promises");
307
- static pluginHttpCtxResolversKey = Symbol.for("clear-router:plugin-http-ctx");
308
- static pluginArgumentResolversKey = Symbol.for("clear-router:plugin-argument-resolvers");
309
- static requestProvider;
310
- static responseProvider;
311
- static config = {
312
- inferParamName: false,
313
- methodOverride: {
314
- enabled: true,
315
- bodyKeys: ["_method"],
316
- headerKeys: ["x-http-method"]
317
- },
318
- container: {
319
- enabled: false,
320
- autoDiscover: false
321
- }
322
- };
323
- static groupContext = new AsyncLocalStorage();
324
- static pluginRequestContext = new AsyncLocalStorage();
325
- static routes = /* @__PURE__ */ new Set([]);
326
- static routesByPathMethod = /* @__PURE__ */ new Map();
327
- static routesByMethod = /* @__PURE__ */ new Map();
328
- static routesByName = /* @__PURE__ */ new Map();
329
- static prefix = "";
330
- static groupMiddlewares = [];
331
- static globalMiddlewares = [];
332
- /**
333
- * Resets the router to it's default state
334
- */
335
- static reset() {
336
- this.routes.clear();
337
- this.prefix = "";
338
- this.groupMiddlewares = [];
339
- this.globalMiddlewares = [];
340
- this.routesByPathMethod.clear();
341
- this.routesByMethod.clear();
342
- this.routesByName.clear();
343
- return this;
344
- }
345
- static createBaseConfig() {
346
- return {
347
- inferParamName: false,
348
- methodOverride: {
349
- enabled: true,
350
- bodyKeys: ["_method"],
351
- headerKeys: ["x-http-method"]
352
- },
353
- container: {
354
- enabled: false,
355
- autoDiscover: false
356
- }
357
- };
358
- }
359
- static mergeConfig(target, source) {
360
- if (!source) return target;
361
- if (source.methodOverride) target.methodOverride = {
362
- ...target.methodOverride || {},
363
- ...source.methodOverride
364
- };
365
- if (source.container) target.container = {
366
- ...target.container || {},
367
- ...source.container
368
- };
369
- return target;
370
- }
371
- static getDefaultConfig() {
372
- const g = globalThis;
373
- if (!g[this.defaultConfigKey]) g[this.defaultConfigKey] = this.createBaseConfig();
374
- return {
375
- inferParamName: g[this.defaultConfigKey].inferParamName,
376
- methodOverride: { ...g[this.defaultConfigKey].methodOverride },
377
- container: { ...g[this.defaultConfigKey].container }
378
- };
379
- }
380
- static resolveStateNamespace() {
381
- return String(this.routerStateNamespace || this.name || "clear-router:core");
382
- }
383
- static getStateStore() {
384
- const g = globalThis;
385
- if (!g[this.stateStoreKey]) g[this.stateStoreKey] = Object.create(null);
386
- return g[this.stateStoreKey];
387
- }
388
- static getPluginStore() {
389
- const g = globalThis;
390
- if (!g[this.pluginStoreKey]) g[this.pluginStoreKey] = /* @__PURE__ */ new Set();
391
- return g[this.pluginStoreKey];
392
- }
393
- static getPluginPendingStore() {
394
- const g = globalThis;
395
- if (!g[this.pluginPendingKey]) g[this.pluginPendingKey] = /* @__PURE__ */ new Set();
396
- return g[this.pluginPendingKey];
397
- }
398
- static getPluginArgumentResolvers() {
399
- const g = globalThis;
400
- if (!g[this.pluginArgumentResolversKey]) g[this.pluginArgumentResolversKey] = /* @__PURE__ */ new Set();
401
- return g[this.pluginArgumentResolversKey];
402
- }
403
- static getPluginHttpCtxResolvers() {
404
- const g = globalThis;
405
- if (!g[this.pluginHttpCtxResolversKey]) g[this.pluginHttpCtxResolversKey] = /* @__PURE__ */ new Set();
406
- return g[this.pluginHttpCtxResolversKey];
407
- }
408
- static createDefaultState() {
409
- return {
410
- config: this.getDefaultConfig(),
411
- groupContext: new AsyncLocalStorage(),
412
- routes: /* @__PURE__ */ new Set([]),
413
- routesByPathMethod: /* @__PURE__ */ new Map(),
414
- routesByMethod: /* @__PURE__ */ new Map(),
415
- routesByName: /* @__PURE__ */ new Map(),
416
- prefix: "",
417
- groupMiddlewares: [],
418
- globalMiddlewares: []
419
- };
420
- }
421
- static bindStateAccessors() {
422
- if (Object.prototype.hasOwnProperty.call(this, this.stateBoundKey)) return;
423
- const namespace = this.resolveStateNamespace();
424
- const store = this.getStateStore();
425
- if (!store[namespace]) store[namespace] = this.createDefaultState();
426
- for (const key of [
427
- "config",
428
- "groupContext",
429
- "routes",
430
- "routesByPathMethod",
431
- "routesByMethod",
432
- "routesByName",
433
- "prefix",
434
- "groupMiddlewares",
435
- "globalMiddlewares"
436
- ]) Object.defineProperty(this, key, {
437
- get() {
438
- const ns = this.resolveStateNamespace();
439
- const registry = this.getStateStore();
440
- if (!registry[ns]) registry[ns] = this.createDefaultState();
441
- return registry[ns][key];
442
- },
443
- set(value) {
444
- const ns = this.resolveStateNamespace();
445
- const registry = this.getStateStore();
446
- if (!registry[ns]) registry[ns] = this.createDefaultState();
447
- registry[ns][key] = value;
448
- },
449
- configurable: true,
450
- enumerable: true
451
- });
452
- Object.defineProperty(this, this.stateBoundKey, {
453
- value: true,
454
- configurable: false,
455
- enumerable: false,
456
- writable: false
457
- });
458
- }
459
- static createDefaultOptionsHandler() {
460
- return (ctx) => {
461
- const allow = "GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD";
462
- if (ctx?.header && ctx?.status && ctx?.body) {
463
- ctx.header("Allow", allow);
464
- ctx.status(204);
465
- return ctx.body(null);
466
- }
467
- if (ctx?.res?.headers?.set) {
468
- ctx.res.headers.set("Allow", allow);
469
- ctx.res.status = 204;
470
- return;
471
- }
472
- if (ctx?.res?.set) {
473
- ctx.res.set("Allow", allow);
474
- ctx.res.sendStatus(204);
475
- return;
476
- }
477
- if (ctx?.reply?.header) {
478
- ctx.reply.header("Allow", allow);
479
- ctx.reply.code(204).send();
480
- return;
481
- }
482
- if (ctx?.set && "status" in ctx) {
483
- ctx.set("Allow", allow);
484
- ctx.status = 204;
485
- ctx.body = null;
486
- }
487
- };
488
- }
489
- /**
490
- * Default configuration used for everytime the router is reset
491
- *
492
- * @param options
493
- */
494
- static configureDefaults(options) {
495
- const g = globalThis;
496
- const defaults = this.mergeConfig(g[this.defaultConfigKey] || this.createBaseConfig(), options);
497
- g[this.defaultConfigKey] = defaults;
498
- const store = this.getStateStore();
499
- for (const state of Object.values(store)) state.config = this.mergeConfig(state.config || this.createBaseConfig(), options);
500
- }
501
- /**
502
- * Use a registered plugin
503
- *
504
- * @param this
505
- * @param plugin
506
- * @param options
507
- * @returns
508
- */
509
- static async use(plugin, options) {
510
- const name = typeof plugin === "function" ? plugin.name : plugin.name;
511
- const store = this.getPluginStore();
512
- if (name && store.has(name)) return;
513
- if (name) store.add(name);
514
- const setup = async () => {
515
- const ctx = {
516
- container: Container,
517
- bind: this.createPluginBind(),
518
- resolveArguments: (resolver) => {
519
- this.getPluginArgumentResolvers().add(resolver);
520
- },
521
- useHttpContext: (resolver) => {
522
- this.getPluginHttpCtxResolvers().add(resolver);
523
- },
524
- bindings: Container.bindings(),
525
- configure: this.configure.bind(this),
526
- configureDefaults: this.configureDefaults.bind(this),
527
- get request() {
528
- return this.getRequest();
529
- },
530
- get response() {
531
- return this.getResponse();
532
- },
533
- getRequest: () => this.getCurrentPluginRequestContext()?.request,
534
- getResponse: () => this.getCurrentPluginRequestContext()?.response,
535
- options
536
- };
537
- if (typeof plugin === "function") await plugin(ctx);
538
- else await plugin.setup(ctx);
539
- };
540
- const pending = this.getPluginPendingStore();
541
- const promise = setup();
542
- pending.add(promise);
543
- try {
544
- await promise;
545
- } catch (error) {
546
- if (name) store.delete(name);
547
- throw error;
548
- } finally {
549
- pending.delete(promise);
550
- }
551
- }
552
- static async pluginsReady() {
553
- const pending = Array.from(this.getPluginPendingStore());
554
- if (!pending.length) return;
555
- await Promise.all(pending);
556
- }
557
- static getCurrentPluginRequestContext() {
558
- return this.pluginRequestContext.getStore();
559
- }
560
- static createPluginRequestContext(ctx) {
561
- const request = ctx.clearRequest;
562
- const response = ctx.clearResponse;
563
- return {
564
- ...ctx,
565
- ctx,
566
- request,
567
- response,
568
- getBindings: () => Container.bindings()
569
- };
570
- }
571
- static createPluginBind() {
572
- const bind = (token, value) => {
573
- if (typeof value === "function" && !isClass(value)) {
574
- const factory = value;
575
- Container.bind(token, (ctx) => factory(this.createPluginRequestContext(ctx)));
576
- return;
577
- }
578
- Container.bind(token, value);
579
- };
580
- return bind;
581
- }
582
- static async resolvePluginArguments(ctx, routeContext) {
583
- const resolvers = Array.from(this.getPluginArgumentResolvers());
584
- if (!resolvers.length) return void 0;
585
- const pluginContext = {
586
- ...this.createPluginRequestContext(ctx),
587
- ...routeContext
588
- };
589
- for (const resolver of resolvers) {
590
- const args = await resolver(pluginContext);
591
- if (Array.isArray(args)) return args;
592
- }
593
- }
594
- static async resolvePluginHttpCtx(ctx) {
595
- const resolvers = Array.from(this.getPluginHttpCtxResolvers());
596
- if (!resolvers.length) return void 0;
597
- const pluginContext = this.createPluginRequestContext(ctx);
598
- for (const resolver of resolvers) await resolver(pluginContext);
599
- }
600
- static ensureState() {
601
- this.bindStateAccessors();
602
- if (!this.config) this.config = { methodOverride: {
603
- enabled: true,
604
- bodyKeys: ["_method"],
605
- headerKeys: ["x-http-method"]
606
- } };
607
- if (!this.groupContext) this.groupContext = new AsyncLocalStorage();
608
- if (!this.routes || Array.isArray(this.routes)) this.routes = new Set(this.routes ?? []);
609
- if (!this.routesByPathMethod) this.routesByPathMethod = /* @__PURE__ */ new Map();
610
- if (!this.routesByMethod) this.routesByMethod = /* @__PURE__ */ new Map();
611
- if (!this.routesByName) this.routesByName = /* @__PURE__ */ new Map();
612
- if (typeof this.prefix !== "string") this.prefix = "";
613
- if (!Array.isArray(this.groupMiddlewares)) this.groupMiddlewares = [];
614
- if (!Array.isArray(this.globalMiddlewares)) this.globalMiddlewares = [];
615
- }
616
- /**
617
- * Normalizes a path by ensuring it starts with a single slash and does not have trailing
618
- * slashes, while preserving dynamic segments and parameters.
619
- *
620
- * @param path The path to normalize.
621
- * @returns The normalized path.
622
- */
623
- static normalizePath(path) {
624
- return "/" + path.split("/").filter(Boolean).join("/");
625
- }
626
- static parseRouteParameters(path) {
627
- const parameters = [];
628
- const seen = /* @__PURE__ */ new Set();
629
- const pattern = /\{([^{}]+)\}/g;
630
- let match;
631
- while ((match = pattern.exec(path)) !== null) {
632
- const raw = match[1].trim();
633
- const optional = raw.endsWith("?");
634
- const [name, field] = (optional ? raw.slice(0, -1) : raw).split(":", 2).map((part) => part.trim());
635
- if (!name || seen.has(name)) continue;
636
- seen.add(name);
637
- parameters.push({
638
- name,
639
- field: field || void 0,
640
- optional
641
- });
642
- }
643
- return parameters;
644
- }
645
- static expandRoutePath(path) {
646
- let paths = [""];
647
- const segments = this.normalizePath(path).split("/").filter(Boolean);
648
- for (const segment of segments) {
649
- const match = segment.match(/^\{([^{}]+)\}$/);
650
- if (!match) {
651
- paths = paths.map((current) => `${current}/${segment}`);
652
- continue;
653
- }
654
- const raw = match[1].trim();
655
- const optional = raw.endsWith("?");
656
- const [rawName] = (optional ? raw.slice(0, -1) : raw).split(":", 2);
657
- const name = rawName.trim();
658
- if (!name) continue;
659
- const parameterSegment = `/:${name}`;
660
- paths = optional ? paths.flatMap((current) => [current, `${current}${parameterSegment}`]) : paths.map((current) => `${current}${parameterSegment}`);
661
- }
662
- return paths.map((path) => path || "/");
663
- }
664
- static routeRegistrationPaths(path) {
665
- return this.expandRoutePath(path);
666
- }
667
- /**
668
- * Configures the router with the given options, such as method override settings.
669
- *
670
- * @param this
671
- * @param options
672
- * @returns
673
- */
674
- static configure(options) {
675
- this.ensureState();
676
- this.config = this.mergeConfig(this.getDefaultConfig(), this.config);
677
- const container = options?.container;
678
- if (container) {
679
- if (typeof container.enabled === "boolean") this.config.container.enabled = container.enabled;
680
- if (typeof container.autoDiscover === "boolean") this.config.container.autoDiscover = container.autoDiscover;
681
- }
682
- if (options?.inferParamName) this.config.inferParamName = options?.inferParamName;
683
- const override = options?.methodOverride;
684
- if (override) {
685
- if (typeof override.enabled === "boolean") this.config.methodOverride.enabled = override.enabled;
686
- const bodyKeys = override.bodyKeys;
687
- if (typeof bodyKeys !== "undefined") this.config.methodOverride.bodyKeys = (Array.isArray(bodyKeys) ? bodyKeys : [bodyKeys]).map((e) => String(e).trim()).filter(Boolean);
688
- const headerKeys = override.headerKeys;
689
- if (typeof headerKeys !== "undefined") this.config.methodOverride.headerKeys = (Array.isArray(headerKeys) ? headerKeys : [headerKeys]).map((e) => String(e).trim().toLowerCase()).filter(Boolean);
690
- }
691
- }
692
- static resolveMethodOverride(method, headers, body) {
693
- this.ensureState();
694
- if (!this.config.methodOverride?.enabled || method.toLowerCase() !== "post") return null;
695
- let override;
696
- const headerValueFor = (key) => {
697
- if (typeof headers.get === "function") return headers.get(key);
698
- const value = headers?.[key];
699
- return Array.isArray(value) ? value[0] : value;
700
- };
701
- for (const key of this.config.methodOverride?.headerKeys || []) {
702
- const value = headerValueFor(key);
703
- if (value) {
704
- override = value;
705
- break;
706
- }
707
- }
708
- if (!override && body && typeof body === "object") for (const key of this.config.methodOverride?.bodyKeys || []) {
709
- const value = body[key];
710
- if (typeof value !== "undefined" && value !== null && value !== "") {
711
- override = value;
712
- break;
713
- }
714
- }
715
- const normalized = String(override || "").trim().toLowerCase();
716
- if (!normalized) return null;
717
- if ([
718
- "put",
719
- "patch",
720
- "delete",
721
- "post"
722
- ].includes(normalized)) return normalized;
723
- return null;
724
- }
725
- /**
726
- * Adds a new route to the router.
727
- *
728
- * @param this
729
- * @param methods
730
- * @param path
731
- * @param handler
732
- * @param middlewares
733
- */
734
- static add(methods, path, handler, middlewares) {
735
- this.ensureState();
736
- const context = this.groupContext.getStore();
737
- const activePrefix = context?.prefix ?? this.prefix;
738
- const activeGroupMiddlewares = context?.groupMiddlewares ?? this.groupMiddlewares;
739
- methods = Array.isArray(methods) ? methods : [methods];
740
- middlewares = middlewares ? Array.isArray(middlewares) ? middlewares : [middlewares] : void 0;
741
- const fullPath = this.normalizePath(`${activePrefix}/${path}`);
742
- const registrationPaths = this.routeRegistrationPaths(fullPath);
743
- const parameters = this.parseRouteParameters(fullPath);
744
- const route = new Route(methods.includes("options") ? methods : methods.concat("options"), fullPath, handler, [
745
- ...this.globalMiddlewares,
746
- ...activeGroupMiddlewares,
747
- ...middlewares || []
748
- ], {
749
- registrationPaths,
750
- parameters,
751
- onName: (name, route, previousName) => {
752
- if (previousName && this.routesByName.get(previousName) === route) this.routesByName.delete(previousName);
753
- this.routesByName.set(name, route);
754
- }
755
- });
756
- if (!methods.includes("options") && !this.routesByPathMethod.get(`OPTIONS ${fullPath}`)) this.options(path, this.createDefaultOptionsHandler());
757
- this.routes.add(route);
758
- for (const method of methods.map((m) => m.toUpperCase())) {
759
- this.routesByPathMethod.set(`${method} ${fullPath}`, route);
760
- if (!this.routesByMethod.has(method)) this.routesByMethod.set(method, []);
761
- this.routesByMethod.get(method)?.push(route);
762
- }
763
- return route;
764
- }
765
- /**
766
- * Define a resourceful API controller with standard CRUD routes.
767
- *
768
- * @param this
769
- * @param basePath
770
- * @param controller
771
- * @param options
772
- */
773
- static apiResource(basePath, controller, options) {
774
- let paramName = "id";
775
- if (!!this.config.inferParamName && this.hasPackageInstalled("@h3ravel/support")) {
776
- const { str } = createRequire(import.meta.url)("@h3ravel/support");
777
- paramName = str(basePath).singular().afterLast("/").toString();
778
- }
779
- const actions = {
780
- index: {
781
- method: "get",
782
- path: "/"
783
- },
784
- show: {
785
- method: "get",
786
- path: `/:${paramName}`
787
- },
788
- create: {
789
- method: "post",
790
- path: "/"
791
- },
792
- update: {
793
- method: "put",
794
- path: `/:${paramName}`
795
- },
796
- destroy: {
797
- method: "delete",
798
- path: `/:${paramName}`
799
- }
800
- };
801
- const only = options?.only || Object.keys(actions);
802
- const except = options?.except || [];
803
- const preController = typeof controller === "function" ? new controller() : controller;
804
- for (const action of only) {
805
- if (except.includes(action)) continue;
806
- if (typeof preController[action] === "function") {
807
- const { method, path } = actions[action];
808
- const actionMiddlewares = typeof options?.middlewares === "object" && !Array.isArray(options.middlewares) ? options.middlewares[action] : options?.middlewares;
809
- const name = `${basePath}${path}`.replace(/\/:[^/]+|\/\{[^}]+\}/g, "").replace(/\{(\w+):[^}]+\}/g, "$1").replace(/\/|:|[{}]/g, ".").replace(/\.{2,}/g, ".").replace(/^\.|\.$/g, "");
810
- this.add(method, `${basePath}${path}`, [controller, action], Array.isArray(actionMiddlewares) ? actionMiddlewares : actionMiddlewares ? [actionMiddlewares] : void 0).name(name + "." + action.toLowerCase());
811
- }
812
- }
813
- }
814
- /**
815
- * Adds a new GET route to the router.
816
- *
817
- * @param this The router instance.
818
- * @param path The path for the GET route.
819
- * @param handler The handler function for the GET route.
820
- * @param middlewares Optional middlewares to apply to the GET route.
821
- */
822
- static get(path, handler, middlewares) {
823
- return this.add("get", path, handler, middlewares);
824
- }
825
- /**
826
- * Adds a new POST route to the router.
827
- *
828
- * @param this
829
- * @param path
830
- * @param handler
831
- * @param middlewares
832
- */
833
- static post(path, handler, middlewares) {
834
- return this.add("post", path, handler, middlewares);
835
- }
836
- /**
837
- * Adds a new PUT route to the router.
838
- *
839
- * @param this
840
- * @param path
841
- * @param handler
842
- * @param middlewares
843
- */
844
- static put(path, handler, middlewares) {
845
- return this.add("put", path, handler, middlewares);
846
- }
847
- /**
848
- * Adds a new DELETE route to the router.
849
- *
850
- * @param this
851
- * @param path
852
- * @param handler
853
- * @param middlewares
854
- */
855
- static delete(path, handler, middlewares) {
856
- return this.add("delete", path, handler, middlewares);
857
- }
858
- /**
859
- * Adds a new PATCH route to the router.
860
- *
861
- * @param this
862
- * @param path
863
- * @param handler
864
- * @param middlewares
865
- */
866
- static patch(path, handler, middlewares) {
867
- return this.add("patch", path, handler, middlewares);
868
- }
869
- /**
870
- * Adds a new OPTIONS route to the router.
871
- *
872
- * @param this
873
- * @param path
874
- * @param handler
875
- * @param middlewares
876
- */
877
- static options(path, handler, middlewares) {
878
- return this.add("options", path, handler, middlewares);
879
- }
880
- /**
881
- * Adds a new HEAD route to the router.
882
- *
883
- * @param this
884
- * @param path
885
- * @param handler
886
- * @param middlewares
887
- */
888
- static head(path, handler, middlewares) {
889
- return this.add("head", path, handler, middlewares);
890
- }
891
- /**
892
- * Defines a group of routes with a common prefix.
893
- *
894
- * @param this
895
- * @param prefix
896
- * @param callback
897
- * @param middlewares
898
- */
899
- static async group(prefix, callback, middlewares) {
900
- this.ensureState();
901
- const context = this.groupContext.getStore();
902
- const previousPrefix = context?.prefix ?? this.prefix;
903
- const previousMiddlewares = context?.groupMiddlewares ?? this.groupMiddlewares;
904
- const fullPrefix = [previousPrefix, prefix].filter(Boolean).join("/");
905
- const nextContext = {
906
- prefix: this.normalizePath(fullPrefix),
907
- groupMiddlewares: [...previousMiddlewares, ...middlewares || []]
908
- };
909
- await this.groupContext.run(nextContext, async () => {
910
- await Promise.resolve(callback());
911
- });
912
- }
913
- /**
914
- * Adds global middlewares to the router, which will be applied to all routes.
915
- *
916
- * @param this
917
- * @param middlewares
918
- * @param callback
919
- */
920
- static middleware(middlewares, callback) {
921
- this.ensureState();
922
- const prevMiddlewares = this.globalMiddlewares;
923
- this.globalMiddlewares = [...prevMiddlewares, ...middlewares || []];
924
- callback();
925
- this.globalMiddlewares = prevMiddlewares;
926
- }
927
- static allRoutes(type) {
928
- this.ensureState();
929
- if (type === "method") return Object.fromEntries(this.routesByMethod.entries());
930
- if (type === "path") return Object.fromEntries(this.routesByPathMethod.entries());
931
- if (type === "name") return Object.fromEntries(this.routesByName.entries());
932
- return Array.from(this.routes).filter((e) => e.methods.length > 1 || e.methods[0] !== "options");
933
- }
934
- static route(name) {
935
- this.ensureState();
936
- return this.routesByName.get(name);
937
- }
938
- static url(name, params) {
939
- return this.route(name)?.toPath(params);
940
- }
941
- /**
942
- * Provide a class that will overide the base Request instance
943
- *
944
- * @param provider
945
- */
946
- static setRequestProvider(provider) {
947
- this.requestProvider = provider;
948
- }
949
- /**
950
- * Provide a class that will overide the base Response instance
951
- *
952
- * @param provider
953
- */
954
- static setResponseProvider(provider) {
955
- this.responseProvider = provider;
956
- }
957
- static hasPackageInstalled(name) {
958
- try {
959
- createRequire(import.meta.url).resolve(name, { paths: [process.cwd()] });
960
- return true;
961
- } catch {
962
- return false;
963
- }
964
- }
965
- static initializeInstance(provider, args) {
966
- const isRequest = [
967
- "CoreRequest",
968
- "Request",
969
- "ClearRequest"
970
- ].includes(provider.name);
971
- const isResponse = [
972
- "CoreResponse",
973
- "Response",
974
- "ClearResponse"
975
- ].includes(provider.name);
976
- if (isRequest && this.requestProvider) return new this.requestProvider(args);
977
- else if (isResponse && this.responseProvider) return new this.responseProvider(args);
978
- return new provider(args);
979
- }
980
- static resolveHandler(route) {
981
- let handlerFunction;
982
- let instance = null;
983
- let bindingTarget;
984
- let bindingMethod;
985
- let bindingHandler;
986
- let bindingMetadata;
987
- if (typeof route.handler === "function") {
988
- handlerFunction = route.handler.bind(route);
989
- bindingTarget = route.handler;
990
- bindingHandler = route.handler;
991
- } else if (Array.isArray(route.handler) && route.handler.length === 2) {
992
- const [ControllerType, method] = route.handler;
993
- if (["function", "object"].includes(typeof ControllerType) && typeof ControllerType[method] === "function") {
994
- instance = ControllerType;
995
- handlerFunction = ControllerType[method].bind(ControllerType);
996
- bindingTarget = ControllerType;
997
- bindingMethod = method;
998
- bindingHandler = ControllerType[method];
999
- bindingMetadata = ControllerType[Symbol.metadata];
1000
- } else if (typeof ControllerType === "function") {
1001
- instance = new ControllerType();
1002
- if (typeof instance[method] === "function") {
1003
- handlerFunction = instance[method].bind(instance);
1004
- bindingTarget = ControllerType.prototype;
1005
- bindingMethod = method;
1006
- bindingHandler = instance[method];
1007
- bindingMetadata = ControllerType[Symbol.metadata];
1008
- } else throw new Error(`Method "${method}" not found in controller instance "${ControllerType.name}"`);
1009
- } else throw new Error(`Invalid controller type for route: ${route.path}`);
1010
- } else throw new Error(`Invalid handler format for route: ${route.path}`);
1011
- return {
1012
- handlerFunction,
1013
- instance,
1014
- bindingTarget,
1015
- bindingMethod,
1016
- bindingHandler,
1017
- bindingMetadata
1018
- };
1019
- }
1020
- static async callHandler(handlerFunction, ctx, bindingTarget, bindingMethod, bindingHandler, bindingMetadata) {
1021
- return this.pluginRequestContext.run(this.createPluginRequestContext(ctx), async () => {
1022
- await this.pluginsReady();
1023
- await this.resolvePluginHttpCtx(ctx);
1024
- if (!this.config.container?.enabled) return handlerFunction(ctx, ctx.clearRequest);
1025
- const designTokens = [...bindingTarget ? getDesignParamTypes(bindingTarget, bindingMethod) : [], ...bindingHandler ? getDesignParamTypes(bindingHandler) : []];
1026
- const metadata = getBindingMetadataFromTargets([
1027
- {
1028
- target: bindingTarget,
1029
- propertyKey: bindingMethod
1030
- },
1031
- { target: bindingHandler },
1032
- {
1033
- target: bindingTarget,
1034
- propertyKey: "__class__"
1035
- }
1036
- ]) ?? getStandardMetadata(bindingMetadata, bindingMethod) ?? getStandardMetadata(bindingMetadata, "__class__");
1037
- const tokens = metadata?.tokens?.length ? metadata.tokens : designTokens;
1038
- const pluginArgs = await this.resolvePluginArguments(ctx, {
1039
- target: bindingTarget,
1040
- method: bindingMethod,
1041
- handler: bindingHandler,
1042
- metadata: bindingMetadata,
1043
- tokens,
1044
- designTokens
1045
- });
1046
- if (pluginArgs?.length) return handlerFunction(...pluginArgs);
1047
- if (!metadata || !tokens.length) return handlerFunction(ctx, ctx.clearRequest);
1048
- const args = [];
1049
- for (const token of tokens) {
1050
- const resolved = await Container.resolve(token, ctx, Boolean(this.config.container?.autoDiscover));
1051
- if (typeof resolved === "undefined") return handlerFunction(ctx, ctx.clearRequest);
1052
- args.push(resolved);
1053
- }
1054
- return handlerFunction(...args);
1055
- });
1056
- }
1057
- static bindRequestToInstance(ctx, instance, route, payload) {
1058
- const clearRequest = ctx.clearRequest instanceof Request ? ctx.clearRequest : this.initializeInstance(Request, {
1059
- ctx,
1060
- route,
1061
- body: payload.body,
1062
- query: payload.query,
1063
- params: payload.params,
1064
- method: String(payload.method || ctx.req?.method || ctx.method || "GET").toUpperCase(),
1065
- path: String(ctx.path || ctx.req?.path || ctx.req?.url || route.path),
1066
- url: String(ctx.url || ctx.req?.url || ctx.req?.originalUrl || route.path),
1067
- headers: ctx.req?.headers || ctx.headers || {},
1068
- original: ctx.req || ctx.request || ctx
1069
- });
1070
- clearRequest.ctx = ctx;
1071
- clearRequest.route = route;
1072
- clearRequest.body = payload.body;
1073
- clearRequest.query = payload.query;
1074
- clearRequest.params = payload.params;
1075
- ctx.clearRequest = clearRequest;
1076
- Container.bind(Request, ctx.clearRequest);
1077
- if (!(ctx.clearResponse instanceof Response)) {
1078
- ctx.clearResponse = this.initializeInstance(Response, ctx.response ?? ctx.reply ?? ctx.res);
1079
- Container.bind(Response, ctx.clearResponse);
1080
- }
1081
- if (!instance) return;
1082
- instance.ctx = ctx;
1083
- instance.body = payload.body;
1084
- instance.query = payload.query;
1085
- instance.params = payload.params;
1086
- instance.clearRequest = clearRequest;
1087
- }
1088
- };
1089
-
1090
- //#endregion
1091
10
  export { ClearRequest, Controller, CoreRouter, Request, Response, Route, definePlugin };