@adonisjs/http-server 8.0.0-next.9 → 8.1.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.
- package/build/chunk-B2GA45YG.js +35 -0
- package/build/define_config-t7GL92UR.js +5470 -0
- package/build/factories/http_context.d.ts +10 -4
- package/build/factories/main.d.ts +2 -2
- package/build/factories/main.js +328 -345
- package/build/factories/request.d.ts +4 -4
- package/build/factories/response.d.ts +4 -4
- package/build/factories/router.d.ts +1 -1
- package/build/factories/server_factory.d.ts +1 -1
- package/build/factories/url_builder_factory.d.ts +3 -3
- package/build/helpers-XD4_pfkD.js +108 -0
- package/build/helpers-aa47NQc6.js +1426 -0
- package/build/index.d.ts +2 -2
- package/build/index.js +330 -373
- package/build/src/client/helpers.d.ts +37 -0
- package/build/src/client/types.d.ts +194 -0
- package/build/src/client/url_builder.d.ts +15 -0
- package/build/src/client/url_builder.js +131 -0
- package/build/src/cookies/client.d.ts +28 -5
- package/build/src/cookies/drivers/encrypted.d.ts +1 -1
- package/build/src/cookies/drivers/signed.d.ts +1 -1
- package/build/src/cookies/parser.d.ts +1 -1
- package/build/src/cookies/serializer.d.ts +2 -2
- package/build/src/debug.d.ts +14 -1
- package/build/src/define_config.d.ts +19 -1
- package/build/src/define_middleware.d.ts +19 -3
- package/build/src/errors.d.ts +60 -5
- package/build/src/exception_handler.d.ts +28 -8
- package/build/src/helpers.d.ts +5 -17
- package/build/src/helpers.js +4 -24
- package/build/src/http_context/main.d.ts +67 -17
- package/build/src/qs.d.ts +17 -3
- package/build/src/redirect.d.ts +22 -3
- package/build/src/request.d.ts +12 -5
- package/build/src/response.d.ts +5 -5
- package/build/src/response_status.d.ts +14 -0
- package/build/src/router/main.d.ts +6 -2
- package/build/src/router/route.d.ts +130 -32
- package/build/src/router/signed_url_builder.d.ts +1 -1
- package/build/src/server/main.d.ts +6 -6
- package/build/src/types/main.js +2 -0
- package/build/src/types/request.d.ts +2 -1
- package/build/src/types/response.d.ts +6 -1
- package/build/src/types/route.d.ts +3 -27
- package/build/src/types/url_builder.d.ts +2 -140
- package/build/src/utils.d.ts +71 -6
- package/package.json +57 -48
- package/build/chunk-CVZAIRWJ.js +0 -1222
- package/build/chunk-JD6QW4NQ.js +0 -4428
- package/build/src/router/url_builder.d.ts +0 -9
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
import { n as __exportAll } from "./chunk-B2GA45YG.js";
|
|
2
|
+
import { t as createURL } from "./helpers-XD4_pfkD.js";
|
|
3
|
+
import { InvalidArgumentsException, RuntimeException } from "@poppinss/utils/exception";
|
|
4
|
+
import { debuglog } from "node:util";
|
|
5
|
+
import { serialize } from "cookie-es";
|
|
6
|
+
import matchit from "@poppinss/matchit";
|
|
7
|
+
import string from "@poppinss/utils/string";
|
|
8
|
+
import { moduleCaller, moduleImporter, parseBindingReference } from "@adonisjs/fold";
|
|
9
|
+
import Cache from "tmp-cache";
|
|
10
|
+
import Macroable from "@poppinss/macroable";
|
|
11
|
+
import is from "@sindresorhus/is";
|
|
12
|
+
import Middleware from "@poppinss/middleware";
|
|
13
|
+
import diagnostics_channel from "node:diagnostics_channel";
|
|
14
|
+
import StringBuilder from "@poppinss/utils/string_builder";
|
|
15
|
+
import encodeUrl from "encodeurl";
|
|
16
|
+
import mime from "mime-types";
|
|
17
|
+
//#region src/debug.ts
|
|
18
|
+
/**
|
|
19
|
+
* Debug logger instance for the AdonisJS HTTP server package.
|
|
20
|
+
*
|
|
21
|
+
* This debug logger can be enabled by setting the NODE_DEBUG environment variable
|
|
22
|
+
* to include 'adonisjs:http'. When enabled, it will output detailed debugging
|
|
23
|
+
* information about HTTP server operations including route matching, middleware
|
|
24
|
+
* execution, and response generation.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```bash
|
|
28
|
+
* NODE_DEBUG=adonisjs:http node ace serve
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
var debug_default = debuglog("adonisjs:http");
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/router/factories/use_return_value.ts
|
|
34
|
+
/**
|
|
35
|
+
* Check if the value can be used to write the response body. Returns
|
|
36
|
+
* false when the response body has already been set
|
|
37
|
+
* @param value - The value to check
|
|
38
|
+
* @param ctx - The HTTP context instance
|
|
39
|
+
* @returns True if value can be used for response body
|
|
40
|
+
*/
|
|
41
|
+
function canWriteResponseBody(value, ctx) {
|
|
42
|
+
return value !== void 0 && !ctx.response.hasLazyBody && value !== ctx.response;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A factory function that uses the return value of the request
|
|
46
|
+
* pipeline as the response
|
|
47
|
+
* @param ctx - The HTTP context instance
|
|
48
|
+
* @returns Function that handles return values
|
|
49
|
+
*/
|
|
50
|
+
function useReturnValue(ctx) {
|
|
51
|
+
return function(value) {
|
|
52
|
+
if (canWriteResponseBody(value, ctx)) ctx.response.send(value);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/tracing_channels.ts
|
|
57
|
+
var tracing_channels_exports = /* @__PURE__ */ __exportAll({
|
|
58
|
+
httpExceptionHandler: () => httpExceptionHandler,
|
|
59
|
+
httpMiddleware: () => httpMiddleware,
|
|
60
|
+
httpRequest: () => httpRequest,
|
|
61
|
+
httpResponseSerializer: () => httpResponseSerializer,
|
|
62
|
+
httpRouteHandler: () => httpRouteHandler
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Traces every HTTP request handled by the {@link Server} class.
|
|
66
|
+
*/
|
|
67
|
+
const httpRequest = diagnostics_channel.tracingChannel("adonisjs.http.request");
|
|
68
|
+
/**
|
|
69
|
+
* Traces middleware executed during the HTTP request
|
|
70
|
+
*/
|
|
71
|
+
const httpMiddleware = diagnostics_channel.tracingChannel("adonisjs.http.middleware");
|
|
72
|
+
/**
|
|
73
|
+
* Traces the exception handler that converts errors into HTTP responses
|
|
74
|
+
*/
|
|
75
|
+
const httpExceptionHandler = diagnostics_channel.tracingChannel("adonisjs.http.exception.handler");
|
|
76
|
+
/**
|
|
77
|
+
* Traces route handler executed during the HTTP request
|
|
78
|
+
*/
|
|
79
|
+
const httpRouteHandler = diagnostics_channel.tracingChannel("adonisjs.http.route.handler");
|
|
80
|
+
/**
|
|
81
|
+
* Traces non-stream and non-file download responses written by the AdonisJS
|
|
82
|
+
* response class
|
|
83
|
+
*/
|
|
84
|
+
const httpResponseSerializer = diagnostics_channel.tracingChannel("adonisjs.http.response.serializer");
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/router/executor.ts
|
|
87
|
+
/**
|
|
88
|
+
* Executor to execute the route middleware pipeline the route
|
|
89
|
+
* handler
|
|
90
|
+
* @param route - The route JSON object containing route information
|
|
91
|
+
* @param resolver - Container resolver for dependency injection
|
|
92
|
+
* @param ctx - The HTTP context instance
|
|
93
|
+
* @param errorResponder - Error handler function for handling errors
|
|
94
|
+
*/
|
|
95
|
+
function execute(route, resolver, ctx, errorResponder) {
|
|
96
|
+
return route.middleware.runner().errorHandler((error) => errorResponder(error, ctx)).finalHandler(() => {
|
|
97
|
+
if (typeof route.handler === "function") return httpRouteHandler.tracePromise(($ctx) => Promise.resolve(route.handler($ctx)), httpRouteHandler.hasSubscribers ? { route } : void 0, void 0, ctx).then(useReturnValue(ctx));
|
|
98
|
+
return httpRouteHandler.tracePromise(route.handler.handle, httpRouteHandler.hasSubscribers ? { route } : void 0, void 0, resolver, ctx).then(useReturnValue(ctx));
|
|
99
|
+
}).run(async (middleware, next) => {
|
|
100
|
+
if (typeof middleware === "function") return httpMiddleware.tracePromise(middleware, httpMiddleware.hasSubscribers ? { middleware } : void 0, void 0, ctx, next);
|
|
101
|
+
return httpMiddleware.tracePromise(middleware.handle, httpMiddleware.hasSubscribers ? { middleware } : void 0, void 0, resolver, ctx, next, middleware.args);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/router/route.ts
|
|
106
|
+
/**
|
|
107
|
+
* The Route class provides a fluent API for constructing and configuring HTTP routes.
|
|
108
|
+
*
|
|
109
|
+
* Routes define how HTTP requests are handled by mapping URL patterns and HTTP methods
|
|
110
|
+
* to controller actions or inline handlers. This class supports middleware application,
|
|
111
|
+
* parameter validation, naming, and various other route-specific configurations.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const route = new Route(app, middleware, {
|
|
116
|
+
* pattern: '/users/:id',
|
|
117
|
+
* methods: ['GET'],
|
|
118
|
+
* handler: 'UsersController.show'
|
|
119
|
+
* })
|
|
120
|
+
*
|
|
121
|
+
* route
|
|
122
|
+
* .where('id', /^[0-9]+$/)
|
|
123
|
+
* .middleware(['auth'])
|
|
124
|
+
* .as('users.show')
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
var Route = class extends Macroable {
|
|
128
|
+
/**
|
|
129
|
+
* The URL pattern for this route. May contain dynamic parameters
|
|
130
|
+
* prefixed with a colon (e.g., '/users/:id').
|
|
131
|
+
*/
|
|
132
|
+
#pattern;
|
|
133
|
+
/**
|
|
134
|
+
* Array of HTTP methods this route responds to (e.g., ['GET', 'POST']).
|
|
135
|
+
*/
|
|
136
|
+
#methods;
|
|
137
|
+
/**
|
|
138
|
+
* A unique identifier for the route, used for URL generation and
|
|
139
|
+
* route referencing.
|
|
140
|
+
*/
|
|
141
|
+
#name;
|
|
142
|
+
/**
|
|
143
|
+
* Flag indicating whether the route should be excluded from registration
|
|
144
|
+
* in the route store. This must be set before calling Router.commit().
|
|
145
|
+
*/
|
|
146
|
+
#isDeleted = false;
|
|
147
|
+
/**
|
|
148
|
+
* The handler function or controller method that processes requests
|
|
149
|
+
* matching this route.
|
|
150
|
+
*/
|
|
151
|
+
#handler;
|
|
152
|
+
/**
|
|
153
|
+
* Route parameter matchers inherited from the global router configuration.
|
|
154
|
+
* These are applied to all routes unless overridden locally.
|
|
155
|
+
*/
|
|
156
|
+
#globalMatchers;
|
|
157
|
+
/**
|
|
158
|
+
* Reference to the AdonisJS application instance, used for module
|
|
159
|
+
* resolution and dependency injection.
|
|
160
|
+
*/
|
|
161
|
+
#app;
|
|
162
|
+
/**
|
|
163
|
+
* Global middleware registered on the router that applies to this route.
|
|
164
|
+
*/
|
|
165
|
+
#routerMiddleware;
|
|
166
|
+
/**
|
|
167
|
+
* The domain this route belongs to. Defaults to 'root' when no specific
|
|
168
|
+
* domain is configured.
|
|
169
|
+
*/
|
|
170
|
+
#routeDomain = "root";
|
|
171
|
+
/**
|
|
172
|
+
* Route-specific parameter matchers that validate dynamic segments.
|
|
173
|
+
* Populated via the where() method and takes precedence over global matchers.
|
|
174
|
+
*/
|
|
175
|
+
#matchers = {};
|
|
176
|
+
/**
|
|
177
|
+
* Stack of URL prefixes applied to this route, typically inherited from
|
|
178
|
+
* route groups. Prefixes are applied in reverse order during pattern computation.
|
|
179
|
+
*/
|
|
180
|
+
#prefixes = [];
|
|
181
|
+
/**
|
|
182
|
+
* Multi-dimensional array of middleware, where each nested array represents
|
|
183
|
+
* a layer in the middleware stack. This structure maintains the order of
|
|
184
|
+
* middleware application from groups and direct assignments.
|
|
185
|
+
*/
|
|
186
|
+
#middleware = [];
|
|
187
|
+
/**
|
|
188
|
+
* Creates a new Route instance
|
|
189
|
+
* @param app - The AdonisJS application instance
|
|
190
|
+
* @param routerMiddleware - Array of global middleware registered on the router
|
|
191
|
+
* @param options - Configuration options for the route
|
|
192
|
+
*/
|
|
193
|
+
constructor(app, routerMiddleware, options) {
|
|
194
|
+
super();
|
|
195
|
+
this.#app = app;
|
|
196
|
+
this.#routerMiddleware = routerMiddleware;
|
|
197
|
+
this.#pattern = options.pattern;
|
|
198
|
+
this.#methods = options.methods;
|
|
199
|
+
this.#globalMatchers = options.globalMatchers;
|
|
200
|
+
const { handler, routeName } = this.#resolveRouteHandle(options.handler);
|
|
201
|
+
this.#handler = handler;
|
|
202
|
+
this.#name = routeName;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Resolves the route handler from various input formats into a normalized
|
|
206
|
+
* StoreRouteHandler object. Supports string references, inline functions,
|
|
207
|
+
* class constructors, and lazy imports.
|
|
208
|
+
*
|
|
209
|
+
* @param handler - The handler in one of the supported formats:
|
|
210
|
+
* - String: 'Controller.method' or 'Controller' (defaults to 'handle')
|
|
211
|
+
* - Function: Inline route handler function
|
|
212
|
+
* - Tuple: [Controller class or lazy import, optional method name]
|
|
213
|
+
*/
|
|
214
|
+
#resolveRouteHandle(handler) {
|
|
215
|
+
/**
|
|
216
|
+
* Convert magic string to handle method call
|
|
217
|
+
*/
|
|
218
|
+
if (typeof handler === "string") {
|
|
219
|
+
const parts = handler.split(".");
|
|
220
|
+
const method = parts.length === 1 ? "handle" : parts.pop();
|
|
221
|
+
const moduleRefId = parts.join(".");
|
|
222
|
+
const controllerName = new StringBuilder(moduleRefId.split("/").pop()).removeSuffix("controller").snakeCase();
|
|
223
|
+
return {
|
|
224
|
+
handler: {
|
|
225
|
+
method,
|
|
226
|
+
reference: handler,
|
|
227
|
+
importExpression: moduleRefId,
|
|
228
|
+
...moduleImporter(() => this.#app.import(moduleRefId), method).toHandleMethod(),
|
|
229
|
+
name: handler
|
|
230
|
+
},
|
|
231
|
+
routeName: method === "handle" ? controllerName.toString() : `${controllerName}.${string.snakeCase(method)}`
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Using a lazily imported controller
|
|
236
|
+
*/
|
|
237
|
+
if (Array.isArray(handler)) {
|
|
238
|
+
const controller = handler[0];
|
|
239
|
+
const method = handler[1] ?? "handle";
|
|
240
|
+
/**
|
|
241
|
+
* The first item of the tuple is a class constructor
|
|
242
|
+
*/
|
|
243
|
+
if (is.class(controller)) {
|
|
244
|
+
const controllerName = new StringBuilder(controller.name).removeSuffix("controller").snakeCase();
|
|
245
|
+
return {
|
|
246
|
+
handler: {
|
|
247
|
+
method,
|
|
248
|
+
reference: handler,
|
|
249
|
+
importExpression: null,
|
|
250
|
+
...moduleCaller(controller, method).toHandleMethod()
|
|
251
|
+
},
|
|
252
|
+
routeName: method === "handle" ? controllerName.toString() : `${controllerName}.${string.snakeCase(method)}`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const controllerName = controller.name ? new StringBuilder(controller.name).removeSuffix("controller").snakeCase() : void 0;
|
|
256
|
+
/**
|
|
257
|
+
* The first item of the tuple is a function that lazily
|
|
258
|
+
* loads the controller
|
|
259
|
+
*/
|
|
260
|
+
return {
|
|
261
|
+
handler: {
|
|
262
|
+
method,
|
|
263
|
+
reference: handler,
|
|
264
|
+
importExpression: String(controller),
|
|
265
|
+
...moduleImporter(controller, method).toHandleMethod()
|
|
266
|
+
},
|
|
267
|
+
routeName: controllerName ? method === "handle" ? controllerName.toString() : `${controllerName}.${string.snakeCase(method)}` : void 0
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return { handler };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Merges global and route-specific parameter matchers into a single object.
|
|
274
|
+
* Local matchers take precedence over global matchers when conflicts occur.
|
|
275
|
+
*/
|
|
276
|
+
#getMatchers() {
|
|
277
|
+
return {
|
|
278
|
+
...this.#globalMatchers,
|
|
279
|
+
...this.#matchers
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Computes the final route pattern by applying all prefixes from route groups.
|
|
284
|
+
* Prefixes are applied in reverse order (innermost group first) and normalized
|
|
285
|
+
* to remove leading/trailing slashes.
|
|
286
|
+
*/
|
|
287
|
+
#computePattern() {
|
|
288
|
+
const pattern = dropSlash(this.#pattern);
|
|
289
|
+
const prefix = this.#prefixes.slice().reverse().map((one) => dropSlash(one)).join("");
|
|
290
|
+
return prefix ? `${prefix}${pattern === "/" ? "" : pattern}` : pattern;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Returns the route's handler configuration object.
|
|
294
|
+
*/
|
|
295
|
+
getHandler() {
|
|
296
|
+
return this.#handler;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Defines a validation matcher for a route parameter. Route-level matchers
|
|
300
|
+
* take precedence over group-level matchers to ensure routes can override
|
|
301
|
+
* group constraints.
|
|
302
|
+
*
|
|
303
|
+
* @param param - The name of the route parameter to validate
|
|
304
|
+
* @param matcher - The validation pattern as a string, RegExp, or RouteMatcher object
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* // Validate that 'id' is numeric
|
|
309
|
+
* route.where('id', /^[0-9]+$/)
|
|
310
|
+
*
|
|
311
|
+
* // Using a string pattern
|
|
312
|
+
* route.where('slug', '[a-z0-9-]+')
|
|
313
|
+
*
|
|
314
|
+
* // Route matcher takes precedence over group matcher
|
|
315
|
+
* Route.group(() => {
|
|
316
|
+
* Route.get('/:id', 'handler').where('id', /^[0-9]$/)
|
|
317
|
+
* }).where('id', /[^a-z$]/)
|
|
318
|
+
* // The route's /^[0-9]$/ wins over the group's matcher
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
where(param, matcher) {
|
|
322
|
+
if (this.#matchers[param]) return this;
|
|
323
|
+
if (typeof matcher === "string") this.#matchers[param] = { match: new RegExp(matcher) };
|
|
324
|
+
else if (is.regExp(matcher)) this.#matchers[param] = { match: matcher };
|
|
325
|
+
else this.#matchers[param] = matcher;
|
|
326
|
+
return this;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Adds a URL prefix to the route pattern. Multiple calls stack prefixes
|
|
330
|
+
* which are applied in reverse order during pattern computation.
|
|
331
|
+
*
|
|
332
|
+
* @param prefix - The URL prefix to prepend to the route pattern
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* route.prefix('/api').prefix('/v1')
|
|
337
|
+
* // Results in pattern: /v1/api/users (for original pattern /users)
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
prefix(prefix) {
|
|
341
|
+
this.#prefixes.push(prefix);
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Assigns a custom domain to the route. By default, routes belong to the
|
|
346
|
+
* 'root' domain. Once set, the domain is not overwritten unless the
|
|
347
|
+
* overwrite flag is true.
|
|
348
|
+
*
|
|
349
|
+
* @param domain - The domain identifier for this route
|
|
350
|
+
* @param overwrite - Whether to overwrite an existing non-root domain
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* route.domain('api.example.com')
|
|
355
|
+
*
|
|
356
|
+
* // Overwrite existing domain
|
|
357
|
+
* route.domain('new.example.com', true)
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
domain(domain, overwrite = false) {
|
|
361
|
+
if (this.#routeDomain === "root" || overwrite) this.#routeDomain = domain;
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Registers one or more middleware to execute before the route handler.
|
|
366
|
+
* Middleware can be inline functions or named middleware references registered
|
|
367
|
+
* with the router's middleware store.
|
|
368
|
+
*
|
|
369
|
+
* @param middleware - Single middleware or array of middleware to apply
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* // Single middleware
|
|
374
|
+
* route.use(async (ctx, next) => {
|
|
375
|
+
* console.log('Before handler')
|
|
376
|
+
* await next()
|
|
377
|
+
* })
|
|
378
|
+
*
|
|
379
|
+
* // Multiple middleware
|
|
380
|
+
* route.use(['auth', 'admin'])
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
use(middleware) {
|
|
384
|
+
this.#middleware.push(Array.isArray(middleware) ? middleware : [middleware]);
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Alias for the {@link Route.use} method.
|
|
389
|
+
*
|
|
390
|
+
* @param middleware - Single middleware or array of middleware to apply
|
|
391
|
+
*/
|
|
392
|
+
middleware(middleware) {
|
|
393
|
+
return this.use(middleware);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Assigns a unique name to the route for use in URL generation and route
|
|
397
|
+
* referencing. Assigning a new name replaces any existing name unless
|
|
398
|
+
* prepend is true.
|
|
399
|
+
*
|
|
400
|
+
* @param name - The route name to assign
|
|
401
|
+
* @param prepend - If true, prepends the name to the existing name with a dot separator
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* // Set route name
|
|
406
|
+
* route.as('users.show')
|
|
407
|
+
*
|
|
408
|
+
* // Prepend to existing name (typically used by route groups)
|
|
409
|
+
* route.as('admin', true) // Results in 'admin.users.show'
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
as(name, prepend = false) {
|
|
413
|
+
if (prepend) {
|
|
414
|
+
if (!this.#name) throw new RuntimeException(`Routes inside a group must have names before calling "router.group.as"`);
|
|
415
|
+
this.#name = `${name}.${this.#name}`;
|
|
416
|
+
return this;
|
|
417
|
+
}
|
|
418
|
+
this.#name = name;
|
|
419
|
+
return this;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Checks whether the route has been marked for deletion. Deleted routes
|
|
423
|
+
* are excluded from the route store during registration.
|
|
424
|
+
*/
|
|
425
|
+
isDeleted() {
|
|
426
|
+
return this.#isDeleted;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Marks the route for deletion. Deleted routes will not be registered
|
|
430
|
+
* with the route store when Router.commit() is called.
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* ```ts
|
|
434
|
+
* const route = Route.get('/admin', 'handler')
|
|
435
|
+
* route.markAsDeleted()
|
|
436
|
+
* // This route will not be registered
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
markAsDeleted() {
|
|
440
|
+
this.#isDeleted = true;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Returns the unique name assigned to the route, if any.
|
|
444
|
+
*/
|
|
445
|
+
getName() {
|
|
446
|
+
return this.#name;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Returns the route's URL pattern with dynamic parameters.
|
|
450
|
+
*/
|
|
451
|
+
getPattern() {
|
|
452
|
+
return this.#pattern;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Updates the route's URL pattern.
|
|
456
|
+
*
|
|
457
|
+
* @param pattern - The new URL pattern to assign to the route
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* route.setPattern('/users/:id/posts/:postId')
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
setPattern(pattern) {
|
|
465
|
+
this.#pattern = pattern;
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Returns the multi-dimensional middleware stack registered on this route.
|
|
470
|
+
* The returned value is shared by reference, not a copy.
|
|
471
|
+
*/
|
|
472
|
+
getMiddleware() {
|
|
473
|
+
return this.#middleware;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Constructs a frozen Middleware instance for storage. This combines global
|
|
477
|
+
* router middleware with route-specific middleware in the correct execution order.
|
|
478
|
+
* The middleware is frozen to prevent modifications after route registration.
|
|
479
|
+
*/
|
|
480
|
+
#getMiddlewareForStore() {
|
|
481
|
+
const middleware = new Middleware();
|
|
482
|
+
this.#routerMiddleware.forEach((one) => {
|
|
483
|
+
debug_default("adding global middleware to route %s, %O", this.#pattern, one);
|
|
484
|
+
middleware.add(one);
|
|
485
|
+
});
|
|
486
|
+
this.#middleware.flat().forEach((one) => {
|
|
487
|
+
debug_default("adding named middleware to route %s, %O", this.#pattern, one);
|
|
488
|
+
middleware.add(one);
|
|
489
|
+
});
|
|
490
|
+
middleware.freeze();
|
|
491
|
+
return middleware;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Serializes the route into a JSON representation suitable for storage and
|
|
495
|
+
* execution. This includes the computed pattern with prefixes, merged matchers,
|
|
496
|
+
* parsed route tokens, and frozen middleware stack.
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```ts
|
|
500
|
+
* const json = route.toJSON()
|
|
501
|
+
* console.log(json.pattern) // '/api/users/:id'
|
|
502
|
+
* console.log(json.methods) // ['GET']
|
|
503
|
+
* console.log(json.name) // 'users.show'
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
toJSON() {
|
|
507
|
+
const pattern = this.#computePattern();
|
|
508
|
+
const matchers = this.#getMatchers();
|
|
509
|
+
return {
|
|
510
|
+
domain: this.#routeDomain,
|
|
511
|
+
pattern,
|
|
512
|
+
matchers,
|
|
513
|
+
tokens: parseRoute(pattern, matchers),
|
|
514
|
+
meta: {},
|
|
515
|
+
name: this.#name,
|
|
516
|
+
handler: this.#handler,
|
|
517
|
+
methods: this.#methods,
|
|
518
|
+
middleware: this.#getMiddlewareForStore(),
|
|
519
|
+
execute
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/router/brisk.ts
|
|
525
|
+
/**
|
|
526
|
+
* Brisk routes exposes the API to configure the route handler by chaining
|
|
527
|
+
* one of the pre-defined methods.
|
|
528
|
+
*
|
|
529
|
+
* For example: Instead of defining the redirect logic as a callback, one can
|
|
530
|
+
* chain the `.redirect` method.
|
|
531
|
+
*
|
|
532
|
+
* Brisk routes are always registered under the `GET` HTTP method.
|
|
533
|
+
*/
|
|
534
|
+
var BriskRoute = class extends Macroable {
|
|
535
|
+
/**
|
|
536
|
+
* Route pattern
|
|
537
|
+
*/
|
|
538
|
+
#pattern;
|
|
539
|
+
/**
|
|
540
|
+
* Matchers inherited from the router
|
|
541
|
+
*/
|
|
542
|
+
#globalMatchers;
|
|
543
|
+
/**
|
|
544
|
+
* Reference to the AdonisJS application
|
|
545
|
+
*/
|
|
546
|
+
#app;
|
|
547
|
+
/**
|
|
548
|
+
* Middleware registered on the router
|
|
549
|
+
*/
|
|
550
|
+
#routerMiddleware;
|
|
551
|
+
/**
|
|
552
|
+
* Reference to route instance. Set after `setHandler` is called
|
|
553
|
+
*/
|
|
554
|
+
route = null;
|
|
555
|
+
/**
|
|
556
|
+
* Creates a new BriskRoute instance
|
|
557
|
+
* @param app - The AdonisJS application instance
|
|
558
|
+
* @param routerMiddleware - Array of global middleware registered on the router
|
|
559
|
+
* @param options - Configuration options for the brisk route
|
|
560
|
+
*/
|
|
561
|
+
constructor(app, routerMiddleware, options) {
|
|
562
|
+
super();
|
|
563
|
+
this.#app = app;
|
|
564
|
+
this.#routerMiddleware = routerMiddleware;
|
|
565
|
+
this.#pattern = options.pattern;
|
|
566
|
+
this.#globalMatchers = options.globalMatchers;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Set handler for the brisk route
|
|
570
|
+
* @param handler - The route handler function
|
|
571
|
+
* @returns The created route instance
|
|
572
|
+
*/
|
|
573
|
+
setHandler(handler) {
|
|
574
|
+
this.route = new Route(this.#app, this.#routerMiddleware, {
|
|
575
|
+
pattern: this.#pattern,
|
|
576
|
+
globalMatchers: this.#globalMatchers,
|
|
577
|
+
methods: ["GET", "HEAD"],
|
|
578
|
+
handler
|
|
579
|
+
});
|
|
580
|
+
return this.route;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Redirects to a given route. Params from the original request will
|
|
584
|
+
* be used when no custom params are defined.
|
|
585
|
+
* @param args - Route identifier, parameters, and options for building the redirect URL
|
|
586
|
+
* @returns The created route instance
|
|
587
|
+
*/
|
|
588
|
+
redirect(...args) {
|
|
589
|
+
const [identifier, params, options] = args;
|
|
590
|
+
function redirectsToRoute(ctx) {
|
|
591
|
+
const redirector = ctx.response.redirect();
|
|
592
|
+
if (options?.status) redirector.status(options.status);
|
|
593
|
+
return redirector.toRoute(identifier, params || ctx.params, options);
|
|
594
|
+
}
|
|
595
|
+
Object.defineProperty(redirectsToRoute, "listArgs", {
|
|
596
|
+
value: identifier,
|
|
597
|
+
writable: false
|
|
598
|
+
});
|
|
599
|
+
return this.setHandler(redirectsToRoute);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Redirect request to a fixed URL
|
|
603
|
+
* @param url - The URL to redirect to
|
|
604
|
+
* @param options - Optional redirect options including HTTP status code
|
|
605
|
+
* @returns The created route instance
|
|
606
|
+
*/
|
|
607
|
+
redirectToPath(url, options) {
|
|
608
|
+
function redirectsToPath(ctx) {
|
|
609
|
+
const redirector = ctx.response.redirect();
|
|
610
|
+
if (options?.status) redirector.status(options.status);
|
|
611
|
+
return redirector.toPath(url);
|
|
612
|
+
}
|
|
613
|
+
Object.defineProperty(redirectsToPath, "listArgs", {
|
|
614
|
+
value: url,
|
|
615
|
+
writable: false
|
|
616
|
+
});
|
|
617
|
+
return this.setHandler(redirectsToPath);
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/router/resource.ts
|
|
622
|
+
/**
|
|
623
|
+
* Route resource exposes the API to register multiple routes for a resource.
|
|
624
|
+
*/
|
|
625
|
+
var RouteResource = class extends Macroable {
|
|
626
|
+
/**
|
|
627
|
+
* Resource identifier. Nested resources are separated
|
|
628
|
+
* with a dot notation
|
|
629
|
+
*/
|
|
630
|
+
#resource;
|
|
631
|
+
/**
|
|
632
|
+
* The controller to handle resource routing requests
|
|
633
|
+
*/
|
|
634
|
+
#controller;
|
|
635
|
+
/**
|
|
636
|
+
* Is it a shallow resource? Shallow resources URLs do not have parent
|
|
637
|
+
* resource name and id once they can be identified with the id.
|
|
638
|
+
*/
|
|
639
|
+
#shallow = false;
|
|
640
|
+
/**
|
|
641
|
+
* Matchers inherited from the router
|
|
642
|
+
*/
|
|
643
|
+
#globalMatchers;
|
|
644
|
+
/**
|
|
645
|
+
* Reference to the AdonisJS application
|
|
646
|
+
*/
|
|
647
|
+
#app;
|
|
648
|
+
/**
|
|
649
|
+
* Middleware registered on the router
|
|
650
|
+
*/
|
|
651
|
+
#routerMiddleware;
|
|
652
|
+
/**
|
|
653
|
+
* Parameter names for the resources. Defaults to `id` for
|
|
654
|
+
* a singular resource and `resource_id` for nested
|
|
655
|
+
* resources.
|
|
656
|
+
*/
|
|
657
|
+
#params = {};
|
|
658
|
+
/**
|
|
659
|
+
* Base name for the routes. We suffix action names
|
|
660
|
+
* on top of the base name
|
|
661
|
+
*/
|
|
662
|
+
#routesBaseName;
|
|
663
|
+
/**
|
|
664
|
+
* A collection of routes instances that belongs to this resource
|
|
665
|
+
*/
|
|
666
|
+
routes = [];
|
|
667
|
+
/**
|
|
668
|
+
* Creates a new RouteResource instance
|
|
669
|
+
* @param app - The AdonisJS application instance
|
|
670
|
+
* @param routerMiddleware - Array of global middleware registered on the router
|
|
671
|
+
* @param options - Configuration options for the route resource
|
|
672
|
+
*/
|
|
673
|
+
constructor(app, routerMiddleware, options) {
|
|
674
|
+
super();
|
|
675
|
+
this.#validateResourceName(options.resource);
|
|
676
|
+
this.#app = app;
|
|
677
|
+
this.#shallow = options.shallow;
|
|
678
|
+
this.#routerMiddleware = routerMiddleware;
|
|
679
|
+
this.#controller = options.controller;
|
|
680
|
+
this.#globalMatchers = options.globalMatchers;
|
|
681
|
+
this.#resource = this.#normalizeResourceName(options.resource);
|
|
682
|
+
this.#routesBaseName = this.#getRoutesBaseName();
|
|
683
|
+
this.#buildRoutes();
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Normalizes the resource name to dropping leading and trailing
|
|
687
|
+
* slashes.
|
|
688
|
+
*/
|
|
689
|
+
#normalizeResourceName(resource) {
|
|
690
|
+
return resource.replace(/^\//, "").replace(/\/$/, "");
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Ensure resource name is not an empty string
|
|
694
|
+
*/
|
|
695
|
+
#validateResourceName(resource) {
|
|
696
|
+
if (!resource || resource === "/") throw new RuntimeException(`Invalid resource name "${resource}"`);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Converting segments of a resource to snake case to
|
|
700
|
+
* make the route name.
|
|
701
|
+
*/
|
|
702
|
+
#getRoutesBaseName() {
|
|
703
|
+
return this.#resource.split(".").map((token) => string.snakeCase(token)).join(".");
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Create a new route for the given pattern, methods and controller action
|
|
707
|
+
*/
|
|
708
|
+
#createRoute(pattern, methods, action) {
|
|
709
|
+
const route = new Route(this.#app, this.#routerMiddleware, {
|
|
710
|
+
pattern,
|
|
711
|
+
methods,
|
|
712
|
+
handler: typeof this.#controller === "string" ? `${this.#controller}.${action}` : [this.#controller, action],
|
|
713
|
+
globalMatchers: this.#globalMatchers
|
|
714
|
+
});
|
|
715
|
+
route.as(`${this.#routesBaseName}.${action}`);
|
|
716
|
+
this.routes.push(route);
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Returns the `resource_id` name for a given resource. The
|
|
720
|
+
* resource name is converted to singular form and
|
|
721
|
+
* transformed to snake case.
|
|
722
|
+
*
|
|
723
|
+
* photos becomes photo_id
|
|
724
|
+
* users becomes user_id
|
|
725
|
+
*/
|
|
726
|
+
#getResourceId(resource) {
|
|
727
|
+
return `${string.snakeCase(string.singular(resource))}_id`;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Build routes for the given resource
|
|
731
|
+
*/
|
|
732
|
+
#buildRoutes() {
|
|
733
|
+
const resources = this.#resource.split(".");
|
|
734
|
+
const mainResource = resources.pop();
|
|
735
|
+
this.#params[mainResource] = ":id";
|
|
736
|
+
const baseURI = `${resources.map((resource) => {
|
|
737
|
+
const paramName = `:${this.#getResourceId(resource)}`;
|
|
738
|
+
this.#params[resource] = paramName;
|
|
739
|
+
return `${resource}/${paramName}`;
|
|
740
|
+
}).join("/")}/${mainResource}`;
|
|
741
|
+
this.#createRoute(baseURI, ["GET", "HEAD"], "index");
|
|
742
|
+
this.#createRoute(`${baseURI}/create`, ["GET", "HEAD"], "create");
|
|
743
|
+
this.#createRoute(baseURI, ["POST"], "store");
|
|
744
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["GET", "HEAD"], "show");
|
|
745
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id/edit`, ["GET", "HEAD"], "edit");
|
|
746
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["PUT", "PATCH"], "update");
|
|
747
|
+
this.#createRoute(`${this.#shallow ? mainResource : baseURI}/:id`, ["DELETE"], "destroy");
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Filter the routes based on their partial names
|
|
751
|
+
*/
|
|
752
|
+
#filter(names, inverse) {
|
|
753
|
+
const actions = Array.isArray(names) ? names : [names];
|
|
754
|
+
return this.routes.filter((route) => {
|
|
755
|
+
const match = actions.find((name) => route.getName().endsWith(name));
|
|
756
|
+
return inverse ? !match : match;
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Register only given routes and remove others
|
|
761
|
+
* @param names - Array of action names to keep
|
|
762
|
+
* @returns Current RouteResource instance with filtered actions
|
|
763
|
+
*/
|
|
764
|
+
only(names) {
|
|
765
|
+
this.#filter(names, true).forEach((route) => route.markAsDeleted());
|
|
766
|
+
return this;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Register all routes, except the one's defined
|
|
770
|
+
* @param names - Array of action names to exclude
|
|
771
|
+
* @returns Current RouteResource instance with filtered actions
|
|
772
|
+
*/
|
|
773
|
+
except(names) {
|
|
774
|
+
this.#filter(names, false).forEach((route) => route.markAsDeleted());
|
|
775
|
+
return this;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Register api only routes. The `create` and `edit` routes, which
|
|
779
|
+
* are meant to show forms will not be registered
|
|
780
|
+
* @returns Current RouteResource instance without create and edit actions
|
|
781
|
+
*/
|
|
782
|
+
apiOnly() {
|
|
783
|
+
return this.except(["create", "edit"]);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Define matcher for params inside the resource
|
|
787
|
+
* @param key - The parameter name to match
|
|
788
|
+
* @param matcher - The matcher pattern (RegExp, string, or RouteMatcher)
|
|
789
|
+
* @returns Current RouteResource instance for method chaining
|
|
790
|
+
*/
|
|
791
|
+
where(key, matcher) {
|
|
792
|
+
this.routes.forEach((route) => {
|
|
793
|
+
route.where(key, matcher);
|
|
794
|
+
});
|
|
795
|
+
return this;
|
|
796
|
+
}
|
|
797
|
+
tap(actions, callback) {
|
|
798
|
+
if (typeof actions === "function") {
|
|
799
|
+
this.routes.forEach((route) => {
|
|
800
|
+
if (!route.isDeleted()) actions(route);
|
|
801
|
+
});
|
|
802
|
+
return this;
|
|
803
|
+
}
|
|
804
|
+
this.#filter(actions, false).forEach((route) => {
|
|
805
|
+
if (!route.isDeleted()) callback(route);
|
|
806
|
+
});
|
|
807
|
+
return this;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Set the param name for a given resource
|
|
811
|
+
* @param resources - Object mapping resource names to parameter names
|
|
812
|
+
* @returns Current RouteResource instance for method chaining
|
|
813
|
+
*/
|
|
814
|
+
params(resources) {
|
|
815
|
+
Object.keys(resources).forEach((resource) => {
|
|
816
|
+
const param = resources[resource];
|
|
817
|
+
const existingParam = this.#params[resource];
|
|
818
|
+
this.#params[resource] = `:${param}`;
|
|
819
|
+
this.routes.forEach((route) => {
|
|
820
|
+
route.setPattern(route.getPattern().replace(`${resource}/${existingParam}`, `${resource}/:${param}`));
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
return this;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Define one or more middleware on the routes created by
|
|
827
|
+
* the resource.
|
|
828
|
+
*
|
|
829
|
+
* Calling this method multiple times will append middleware
|
|
830
|
+
* to existing list.
|
|
831
|
+
* @param actions - Action name(s) or '*' for all actions
|
|
832
|
+
* @param middleware - Middleware function(s) to apply
|
|
833
|
+
* @returns Current RouteResource instance for method chaining
|
|
834
|
+
*/
|
|
835
|
+
use(actions, middleware) {
|
|
836
|
+
if (actions === "*") this.tap((route) => route.use(middleware));
|
|
837
|
+
else this.tap(actions, (route) => route.use(middleware));
|
|
838
|
+
return this;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Alias for {@link RouteResource.use}
|
|
842
|
+
* @param actions - Action name(s) or '*' for all actions
|
|
843
|
+
* @param middleware - Middleware function(s) to apply
|
|
844
|
+
* @returns Current RouteResource instance for method chaining
|
|
845
|
+
*/
|
|
846
|
+
middleware(actions, middleware) {
|
|
847
|
+
return this.use(actions, middleware);
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Prepend name to all the routes
|
|
851
|
+
* @param name - The name to prepend to all route names
|
|
852
|
+
* @param normalizeName - Whether to normalize the name to snake_case
|
|
853
|
+
* @returns Current RouteResource instance for method chaining
|
|
854
|
+
*/
|
|
855
|
+
as(name, normalizeName = true) {
|
|
856
|
+
name = normalizeName ? string.snakeCase(name) : name;
|
|
857
|
+
this.routes.forEach((route) => {
|
|
858
|
+
route.as(route.getName().replace(this.#routesBaseName, name), false);
|
|
859
|
+
});
|
|
860
|
+
this.#routesBaseName = name;
|
|
861
|
+
return this;
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region src/router/group.ts
|
|
866
|
+
/**
|
|
867
|
+
* Group class exposes the API to take action on a group of routes.
|
|
868
|
+
* The group routes must be pre-defined using the constructor.
|
|
869
|
+
*/
|
|
870
|
+
var RouteGroup = class RouteGroup extends Macroable {
|
|
871
|
+
/**
|
|
872
|
+
* Array of middleware registered on the group.
|
|
873
|
+
*/
|
|
874
|
+
#middleware = [];
|
|
875
|
+
/**
|
|
876
|
+
* Creates a new RouteGroup instance
|
|
877
|
+
* @param routes - Array of routes that belong to this group
|
|
878
|
+
*/
|
|
879
|
+
constructor(routes) {
|
|
880
|
+
super();
|
|
881
|
+
this.routes = routes;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Shares midldeware stack with the routes. The method is invoked recursively
|
|
885
|
+
* to only register middleware with the route class and not with the
|
|
886
|
+
* resource or the child group
|
|
887
|
+
*/
|
|
888
|
+
#shareMiddlewareStackWithRoutes(route) {
|
|
889
|
+
if (route instanceof RouteGroup) {
|
|
890
|
+
route.routes.forEach((child) => this.#shareMiddlewareStackWithRoutes(child));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if (route instanceof RouteResource) {
|
|
894
|
+
route.routes.forEach((child) => child.getMiddleware().unshift(this.#middleware));
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (route instanceof BriskRoute) {
|
|
898
|
+
route.route.getMiddleware().unshift(this.#middleware);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
route.getMiddleware().unshift(this.#middleware);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Updates the route name. The method is invoked recursively to only update
|
|
905
|
+
* the name with the route class and not with the resource or the child
|
|
906
|
+
* group.
|
|
907
|
+
*/
|
|
908
|
+
#updateRouteName(route, name) {
|
|
909
|
+
if (route instanceof RouteGroup) {
|
|
910
|
+
route.routes.forEach((child) => this.#updateRouteName(child, name));
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
if (route instanceof RouteResource) {
|
|
914
|
+
route.routes.forEach((child) => child.as(name, true));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (route instanceof BriskRoute) {
|
|
918
|
+
route.route.as(name, true);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
route.as(name, true);
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Sets prefix on the route. The method is invoked recursively to only set
|
|
925
|
+
* the prefix with the route class and not with the resource or the
|
|
926
|
+
* child group.
|
|
927
|
+
*/
|
|
928
|
+
#setRoutePrefix(route, prefix) {
|
|
929
|
+
if (route instanceof RouteGroup) {
|
|
930
|
+
route.routes.forEach((child) => this.#setRoutePrefix(child, prefix));
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (route instanceof RouteResource) {
|
|
934
|
+
route.routes.forEach((child) => child.prefix(prefix));
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (route instanceof BriskRoute) {
|
|
938
|
+
route.route.prefix(prefix);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
route.prefix(prefix);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Updates domain on the route. The method is invoked recursively to only update
|
|
945
|
+
* the domain with the route class and not with the resource or the child
|
|
946
|
+
* group.
|
|
947
|
+
*/
|
|
948
|
+
#updateRouteDomain(route, domain) {
|
|
949
|
+
if (route instanceof RouteGroup) {
|
|
950
|
+
route.routes.forEach((child) => this.#updateRouteDomain(child, domain));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (route instanceof RouteResource) {
|
|
954
|
+
route.routes.forEach((child) => child.domain(domain));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (route instanceof BriskRoute) {
|
|
958
|
+
route.route.domain(domain, false);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
route.domain(domain, false);
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Updates matchers on the route. The method is invoked recursively to only update
|
|
965
|
+
* the matchers with the route class and not with the resource or the child
|
|
966
|
+
* group.
|
|
967
|
+
*/
|
|
968
|
+
#updateRouteMatchers(route, param, matcher) {
|
|
969
|
+
if (route instanceof RouteGroup) {
|
|
970
|
+
route.routes.forEach((child) => this.#updateRouteMatchers(child, param, matcher));
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
if (route instanceof RouteResource) {
|
|
974
|
+
route.routes.forEach((child) => child.where(param, matcher));
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (route instanceof BriskRoute) {
|
|
978
|
+
route.route.where(param, matcher);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
route.where(param, matcher);
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Define route param matcher
|
|
985
|
+
*
|
|
986
|
+
* ```ts
|
|
987
|
+
* Route.group(() => {
|
|
988
|
+
* }).where('id', /^[0-9]+/)
|
|
989
|
+
* ```
|
|
990
|
+
* @param param - The parameter name to match
|
|
991
|
+
* @param matcher - The matcher pattern (RegExp, string, or RouteMatcher)
|
|
992
|
+
* @returns Current RouteGroup instance for method chaining
|
|
993
|
+
*/
|
|
994
|
+
where(param, matcher) {
|
|
995
|
+
this.routes.forEach((route) => this.#updateRouteMatchers(route, param, matcher));
|
|
996
|
+
return this;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Define prefix all the routes in the group.
|
|
1000
|
+
*
|
|
1001
|
+
* ```ts
|
|
1002
|
+
* Route.group(() => {
|
|
1003
|
+
* }).prefix('v1')
|
|
1004
|
+
* ```
|
|
1005
|
+
* @param prefix - The prefix to add to all routes in the group
|
|
1006
|
+
* @returns Current RouteGroup instance for method chaining
|
|
1007
|
+
*/
|
|
1008
|
+
prefix(prefix) {
|
|
1009
|
+
this.routes.forEach((route) => this.#setRoutePrefix(route, prefix));
|
|
1010
|
+
return this;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Define domain for all the routes.
|
|
1014
|
+
*
|
|
1015
|
+
* ```ts
|
|
1016
|
+
* Route.group(() => {
|
|
1017
|
+
* }).domain(':name.adonisjs.com')
|
|
1018
|
+
* ```
|
|
1019
|
+
* @param domain - The domain pattern for all routes in the group
|
|
1020
|
+
* @returns Current RouteGroup instance for method chaining
|
|
1021
|
+
*/
|
|
1022
|
+
domain(domain) {
|
|
1023
|
+
this.routes.forEach((route) => this.#updateRouteDomain(route, domain));
|
|
1024
|
+
return this;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Prepend name to the routes name.
|
|
1028
|
+
*
|
|
1029
|
+
* ```ts
|
|
1030
|
+
* Route.group(() => {
|
|
1031
|
+
* }).as('version1')
|
|
1032
|
+
* ```
|
|
1033
|
+
* @param name - The name to prepend to all route names in the group
|
|
1034
|
+
* @returns Current RouteGroup instance for method chaining
|
|
1035
|
+
*/
|
|
1036
|
+
as(name) {
|
|
1037
|
+
this.routes.forEach((route) => this.#updateRouteName(route, name));
|
|
1038
|
+
return this;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Prepend an array of middleware to all routes middleware.
|
|
1042
|
+
*
|
|
1043
|
+
* ```ts
|
|
1044
|
+
* Route.group(() => {
|
|
1045
|
+
* }).use(middleware.auth())
|
|
1046
|
+
* ```
|
|
1047
|
+
* @param middleware - Middleware function(s) to apply to all routes in the group
|
|
1048
|
+
* @returns Current RouteGroup instance for method chaining
|
|
1049
|
+
*/
|
|
1050
|
+
use(middleware) {
|
|
1051
|
+
/**
|
|
1052
|
+
* Register middleware with children. We share the group middleware
|
|
1053
|
+
* array by reference, therefore have to register it only for the
|
|
1054
|
+
* first time.
|
|
1055
|
+
*/
|
|
1056
|
+
if (!this.#middleware.length) this.routes.forEach((route) => this.#shareMiddlewareStackWithRoutes(route));
|
|
1057
|
+
if (Array.isArray(middleware)) for (let one of middleware) this.#middleware.push(one);
|
|
1058
|
+
else this.#middleware.push(middleware);
|
|
1059
|
+
return this;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Alias for {@link RouteGroup.use}
|
|
1063
|
+
* @param middleware - Middleware function(s) to apply to all routes in the group
|
|
1064
|
+
* @returns Current RouteGroup instance for method chaining
|
|
1065
|
+
*/
|
|
1066
|
+
middleware(middleware) {
|
|
1067
|
+
return this.use(middleware);
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region src/utils.ts
|
|
1072
|
+
const proxyCache = new Cache({ max: 200 });
|
|
1073
|
+
/**
|
|
1074
|
+
* Makes input string consistent by having only the starting slash.
|
|
1075
|
+
*
|
|
1076
|
+
* Removes trailing slashes and ensures the path starts with a forward slash,
|
|
1077
|
+
* except for the root path '/' which remains unchanged.
|
|
1078
|
+
*
|
|
1079
|
+
* @param input - The input path string to normalize
|
|
1080
|
+
*
|
|
1081
|
+
* @example
|
|
1082
|
+
* ```ts
|
|
1083
|
+
* dropSlash('/users/') // '/users'
|
|
1084
|
+
* dropSlash('users') // '/users'
|
|
1085
|
+
* dropSlash('/') // '/'
|
|
1086
|
+
* ```
|
|
1087
|
+
*/
|
|
1088
|
+
function dropSlash(input) {
|
|
1089
|
+
if (input === "/") return "/";
|
|
1090
|
+
return `/${input.replace(/^\//, "").replace(/\/$/, "")}`;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Returns a flat list of routes from route groups, resources, and brisk routes.
|
|
1094
|
+
*
|
|
1095
|
+
* This function recursively processes route collections, extracting individual routes
|
|
1096
|
+
* from groups and resources while filtering out any deleted routes.
|
|
1097
|
+
*
|
|
1098
|
+
* @param routes - Array containing route groups, individual routes, resources, and brisk routes
|
|
1099
|
+
*
|
|
1100
|
+
* @example
|
|
1101
|
+
* ```ts
|
|
1102
|
+
* const flatRoutes = toRoutesJSON([
|
|
1103
|
+
* routeGroup,
|
|
1104
|
+
* singleRoute,
|
|
1105
|
+
* resourceRoutes
|
|
1106
|
+
* ])
|
|
1107
|
+
* ```
|
|
1108
|
+
*/
|
|
1109
|
+
function toRoutesJSON(routes) {
|
|
1110
|
+
return routes.reduce((list, route) => {
|
|
1111
|
+
if (route instanceof RouteGroup) {
|
|
1112
|
+
list = list.concat(toRoutesJSON(route.routes));
|
|
1113
|
+
return list;
|
|
1114
|
+
}
|
|
1115
|
+
if (route instanceof RouteResource) {
|
|
1116
|
+
list = list.concat(toRoutesJSON(route.routes));
|
|
1117
|
+
return list;
|
|
1118
|
+
}
|
|
1119
|
+
if (route instanceof BriskRoute) {
|
|
1120
|
+
if (route.route && !route.route.isDeleted()) list.push(route.route.toJSON());
|
|
1121
|
+
return list;
|
|
1122
|
+
}
|
|
1123
|
+
if (!route.isDeleted()) list.push(route.toJSON());
|
|
1124
|
+
return list;
|
|
1125
|
+
}, []);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Helper to determine if a remote address should be trusted.
|
|
1129
|
+
*
|
|
1130
|
+
* Uses caching to avoid repeated expensive proxy function calls for the same
|
|
1131
|
+
* remote address. The cache improves performance when the same addresses are
|
|
1132
|
+
* checked multiple times.
|
|
1133
|
+
*
|
|
1134
|
+
* @param remoteAddress - The remote IP address to check
|
|
1135
|
+
* @param proxyFn - Function that determines if an address should be trusted
|
|
1136
|
+
*
|
|
1137
|
+
* @example
|
|
1138
|
+
* ```ts
|
|
1139
|
+
* const isTrusted = trustProxy('192.168.1.1', proxyAddr.compile('loopback'))
|
|
1140
|
+
* ```
|
|
1141
|
+
*/
|
|
1142
|
+
function trustProxy(remoteAddress, proxyFn) {
|
|
1143
|
+
if (proxyCache.has(remoteAddress)) return proxyCache.get(remoteAddress);
|
|
1144
|
+
const result = proxyFn(remoteAddress, 0);
|
|
1145
|
+
proxyCache.set(remoteAddress, result);
|
|
1146
|
+
return result;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Parses a range expression (e.g., '200..299') into an object with numeric keys.
|
|
1150
|
+
*
|
|
1151
|
+
* Supports both single values and ranges. For ranges, all numbers between
|
|
1152
|
+
* the start and end (inclusive) are mapped to the provided value.
|
|
1153
|
+
*
|
|
1154
|
+
* @param range - Range expression as a string (e.g., '200', '200..299')
|
|
1155
|
+
* @param value - Value to assign to each number in the range
|
|
1156
|
+
*
|
|
1157
|
+
* @example
|
|
1158
|
+
* ```ts
|
|
1159
|
+
* parseRange('200', 'success') // { 200: 'success' }
|
|
1160
|
+
* parseRange('200..202', 'success') // { 200: 'success', 201: 'success', 202: 'success' }
|
|
1161
|
+
* ```
|
|
1162
|
+
*/
|
|
1163
|
+
function parseRange(range, value) {
|
|
1164
|
+
const parts = range.split("..");
|
|
1165
|
+
const min = Number(parts[0]);
|
|
1166
|
+
const max = Number(parts[1]);
|
|
1167
|
+
/**
|
|
1168
|
+
* The ending status code does not exists
|
|
1169
|
+
*/
|
|
1170
|
+
if (parts.length === 1 && !Number.isNaN(min)) return { [min]: value };
|
|
1171
|
+
/**
|
|
1172
|
+
* The starting status code is not a number
|
|
1173
|
+
*/
|
|
1174
|
+
if (Number.isNaN(min) || Number.isNaN(max)) return {};
|
|
1175
|
+
/**
|
|
1176
|
+
* Min and max are same
|
|
1177
|
+
*/
|
|
1178
|
+
if (min === max) return { [min]: value };
|
|
1179
|
+
/**
|
|
1180
|
+
* Max cannot be smaller than min
|
|
1181
|
+
*/
|
|
1182
|
+
if (max < min) throw new InvalidArgumentsException(`Invalid range "${range}"`);
|
|
1183
|
+
/**
|
|
1184
|
+
* Loop over the range and create a collection
|
|
1185
|
+
* of status codes
|
|
1186
|
+
*/
|
|
1187
|
+
return [...Array(max - min + 1).keys()].reduce((result, step) => {
|
|
1188
|
+
result[min + step] = value;
|
|
1189
|
+
return result;
|
|
1190
|
+
}, {});
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Decodes specific percent-encoded characters in URI components.
|
|
1194
|
+
*
|
|
1195
|
+
* This function handles decoding of specific character codes that are commonly
|
|
1196
|
+
* percent-encoded in URIs, such as %, #, $, &, +, etc.
|
|
1197
|
+
*
|
|
1198
|
+
* @param highCharCode - The high character code of the hex pair
|
|
1199
|
+
* @param lowCharCode - The low character code of the hex pair
|
|
1200
|
+
*/
|
|
1201
|
+
function decodeComponentChar(highCharCode, lowCharCode) {
|
|
1202
|
+
if (highCharCode === 50) {
|
|
1203
|
+
if (lowCharCode === 53) return "%";
|
|
1204
|
+
if (lowCharCode === 51) return "#";
|
|
1205
|
+
if (lowCharCode === 52) return "$";
|
|
1206
|
+
if (lowCharCode === 54) return "&";
|
|
1207
|
+
if (lowCharCode === 66) return "+";
|
|
1208
|
+
if (lowCharCode === 98) return "+";
|
|
1209
|
+
if (lowCharCode === 67) return ",";
|
|
1210
|
+
if (lowCharCode === 99) return ",";
|
|
1211
|
+
if (lowCharCode === 70) return "/";
|
|
1212
|
+
if (lowCharCode === 102) return "/";
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
if (highCharCode === 51) {
|
|
1216
|
+
if (lowCharCode === 65) return ":";
|
|
1217
|
+
if (lowCharCode === 97) return ":";
|
|
1218
|
+
if (lowCharCode === 66) return ";";
|
|
1219
|
+
if (lowCharCode === 98) return ";";
|
|
1220
|
+
if (lowCharCode === 68) return "=";
|
|
1221
|
+
if (lowCharCode === 100) return "=";
|
|
1222
|
+
if (lowCharCode === 70) return "?";
|
|
1223
|
+
if (lowCharCode === 102) return "?";
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
if (highCharCode === 52 && lowCharCode === 48) return "@";
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Safely decodes a URI path while handling special characters and query strings.
|
|
1231
|
+
*
|
|
1232
|
+
* This function carefully parses and decodes URI components, handling edge cases
|
|
1233
|
+
* like double-encoded characters and non-standard query string delimiters.
|
|
1234
|
+
* It separates the pathname from query parameters and determines whether
|
|
1235
|
+
* route parameters should be decoded.
|
|
1236
|
+
*
|
|
1237
|
+
* @param path - The URI path to decode
|
|
1238
|
+
* @param useSemicolonDelimiter - Whether to treat semicolons as query string delimiters
|
|
1239
|
+
*
|
|
1240
|
+
* @example
|
|
1241
|
+
* ```ts
|
|
1242
|
+
* const result = safeDecodeURI('/users/123?name=john', false)
|
|
1243
|
+
* // Returns: { pathname: '/users/123', query: 'name=john', shouldDecodeParam: false }
|
|
1244
|
+
* ```
|
|
1245
|
+
*/
|
|
1246
|
+
function safeDecodeURI(path, useSemicolonDelimiter) {
|
|
1247
|
+
let shouldDecode = false;
|
|
1248
|
+
let shouldDecodeParam = false;
|
|
1249
|
+
let querystring = "";
|
|
1250
|
+
for (let i = 1; i < path.length; i++) {
|
|
1251
|
+
const charCode = path.charCodeAt(i);
|
|
1252
|
+
if (charCode === 37) {
|
|
1253
|
+
const highCharCode = path.charCodeAt(i + 1);
|
|
1254
|
+
const lowCharCode = path.charCodeAt(i + 2);
|
|
1255
|
+
if (decodeComponentChar(highCharCode, lowCharCode) === null) shouldDecode = true;
|
|
1256
|
+
else {
|
|
1257
|
+
shouldDecodeParam = true;
|
|
1258
|
+
if (highCharCode === 50 && lowCharCode === 53) {
|
|
1259
|
+
shouldDecode = true;
|
|
1260
|
+
path = path.slice(0, i + 1) + "25" + path.slice(i + 1);
|
|
1261
|
+
i += 2;
|
|
1262
|
+
}
|
|
1263
|
+
i += 2;
|
|
1264
|
+
}
|
|
1265
|
+
} else if (charCode === 63 || charCode === 35 || charCode === 59 && useSemicolonDelimiter) {
|
|
1266
|
+
querystring = path.slice(i + 1);
|
|
1267
|
+
path = path.slice(0, i);
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
pathname: shouldDecode ? decodeURI(path) : path,
|
|
1273
|
+
query: querystring,
|
|
1274
|
+
shouldDecodeParam
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
//#endregion
|
|
1278
|
+
//#region src/helpers.ts
|
|
1279
|
+
/**
|
|
1280
|
+
* Parse a route pattern into an array of tokens. These tokes can be used
|
|
1281
|
+
* to match routes, or print them with semantic information.
|
|
1282
|
+
*
|
|
1283
|
+
* Token types
|
|
1284
|
+
*
|
|
1285
|
+
* - 0: (static) segment
|
|
1286
|
+
* - 1: (parameter) segment
|
|
1287
|
+
* - 2: (wildcard) segment
|
|
1288
|
+
* - 3: (optional parameter) segment
|
|
1289
|
+
*
|
|
1290
|
+
* Value (val) refers to the segment value
|
|
1291
|
+
*
|
|
1292
|
+
* end refers to be the suffix or the segment (if any)
|
|
1293
|
+
*
|
|
1294
|
+
* @param pattern - The route pattern to parse
|
|
1295
|
+
* @param matchers - Optional route matchers
|
|
1296
|
+
* @returns {MatchItRouteToken[]} Array of parsed route tokens
|
|
1297
|
+
*/
|
|
1298
|
+
function parseRoute(pattern, matchers) {
|
|
1299
|
+
return matchit.parse(pattern, matchers);
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Makes signed URL for a given route pattern using its parsed tokens. The
|
|
1303
|
+
* tokens could be generated using the "parseRoute" method.
|
|
1304
|
+
*
|
|
1305
|
+
* @param identifier - Route identifier
|
|
1306
|
+
* @param tokens - Array of parsed route tokens
|
|
1307
|
+
* @param searchParamsStringifier - Function to stringify query parameters
|
|
1308
|
+
* @param encryption - Encryption instance for signing
|
|
1309
|
+
* @param params - Route parameters as array or object
|
|
1310
|
+
* @param options - Signed URL options
|
|
1311
|
+
* @returns {string} The generated signed URL
|
|
1312
|
+
*/
|
|
1313
|
+
function createSignedURL(identifier, tokens, searchParamsStringifier, encryption, params, options) {
|
|
1314
|
+
const signature = encryption.getMessageVerifier().sign(createURL(identifier, tokens, searchParamsStringifier, params, {
|
|
1315
|
+
...options,
|
|
1316
|
+
prefixUrl: void 0
|
|
1317
|
+
}), options?.expiresIn, options?.purpose);
|
|
1318
|
+
return createURL(identifier, tokens, searchParamsStringifier, params, {
|
|
1319
|
+
...options,
|
|
1320
|
+
qs: {
|
|
1321
|
+
...options?.qs,
|
|
1322
|
+
signature
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Match a given URI with an array of patterns and extract the params
|
|
1328
|
+
* from the URL. Null value is returned in case of no match
|
|
1329
|
+
*
|
|
1330
|
+
* @param url - The URL to match
|
|
1331
|
+
* @param patterns - Array of route patterns to match against
|
|
1332
|
+
* @returns {null | Record<string, string>} Extracted parameters or null if no match
|
|
1333
|
+
*/
|
|
1334
|
+
function matchRoute(url, patterns) {
|
|
1335
|
+
const tokensBucket = patterns.map((pattern) => parseRoute(pattern));
|
|
1336
|
+
const match = matchit.match(url, tokensBucket);
|
|
1337
|
+
if (!match.length) return null;
|
|
1338
|
+
return matchit.exec(url, match);
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Serialize the value of a cookie to a string you can send via
|
|
1342
|
+
* set-cookie response header.
|
|
1343
|
+
*
|
|
1344
|
+
* @param key - Cookie name
|
|
1345
|
+
* @param value - Cookie value
|
|
1346
|
+
* @param options - Cookie options
|
|
1347
|
+
* @returns {string} Serialized cookie string
|
|
1348
|
+
*/
|
|
1349
|
+
function serializeCookie(key, value, options) {
|
|
1350
|
+
let expires;
|
|
1351
|
+
let maxAge;
|
|
1352
|
+
if (options) {
|
|
1353
|
+
expires = typeof options.expires === "function" ? options.expires() : options.expires;
|
|
1354
|
+
maxAge = options.maxAge ? string.seconds.parse(options.maxAge) : void 0;
|
|
1355
|
+
}
|
|
1356
|
+
return serialize(key, value, {
|
|
1357
|
+
...options,
|
|
1358
|
+
maxAge,
|
|
1359
|
+
expires
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Returns the info about a middleware handler. In case of lazy imports, the method
|
|
1364
|
+
* will return the import path
|
|
1365
|
+
*
|
|
1366
|
+
* @param middleware - The middleware function or parsed middleware
|
|
1367
|
+
* @returns {Promise<MiddlewareHandlerInfo>} Promise resolving to middleware handler information
|
|
1368
|
+
*/
|
|
1369
|
+
async function middlewareInfo(middleware) {
|
|
1370
|
+
if (typeof middleware === "function") return {
|
|
1371
|
+
type: "closure",
|
|
1372
|
+
name: middleware.name || "closure"
|
|
1373
|
+
};
|
|
1374
|
+
if ("args" in middleware) return {
|
|
1375
|
+
type: "named",
|
|
1376
|
+
name: middleware.name,
|
|
1377
|
+
args: middleware.args,
|
|
1378
|
+
...await parseBindingReference([middleware.reference])
|
|
1379
|
+
};
|
|
1380
|
+
return {
|
|
1381
|
+
type: "global",
|
|
1382
|
+
name: middleware.name,
|
|
1383
|
+
...await parseBindingReference([middleware.reference])
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Returns the info about a route handler. In case of lazy imports, the method
|
|
1388
|
+
* will return the import path.
|
|
1389
|
+
*
|
|
1390
|
+
* @param route - The route JSON object
|
|
1391
|
+
* @returns {Promise<RouteHandlerInfo>} Promise resolving to route handler information
|
|
1392
|
+
*/
|
|
1393
|
+
async function routeInfo(route) {
|
|
1394
|
+
return "reference" in route.handler ? {
|
|
1395
|
+
type: "controller",
|
|
1396
|
+
...await parseBindingReference(route.handler.reference)
|
|
1397
|
+
} : {
|
|
1398
|
+
type: "closure",
|
|
1399
|
+
name: route.handler.name || "closure",
|
|
1400
|
+
args: "listArgs" in route.handler ? String(route.handler.listArgs) : void 0
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Appends query string parameters to a URI. Existing query parameters
|
|
1405
|
+
* in the URI are merged with the new ones.
|
|
1406
|
+
*
|
|
1407
|
+
* @param uri - The base URI to append query string to
|
|
1408
|
+
* @param queryString - Object containing query parameters to append
|
|
1409
|
+
* @param qsParser - Query string parser instance for stringify/parse operations
|
|
1410
|
+
*
|
|
1411
|
+
* @example
|
|
1412
|
+
* ```ts
|
|
1413
|
+
* const result = appendQueryString('/users', { page: 1, limit: 10 }, qsParser)
|
|
1414
|
+
* // Returns: '/users?page=1&limit=10'
|
|
1415
|
+
*
|
|
1416
|
+
* const result2 = appendQueryString('/users?sort=name', { page: 1 }, qsParser)
|
|
1417
|
+
* // Returns: '/users?sort=name&page=1'
|
|
1418
|
+
* ```
|
|
1419
|
+
*/
|
|
1420
|
+
function appendQueryString(uri, queryString, qsParser) {
|
|
1421
|
+
const { query, pathname } = safeDecodeURI(uri, false);
|
|
1422
|
+
const mergedQueryString = qsParser.stringify(Object.assign(qsParser.parse(query), queryString));
|
|
1423
|
+
return mergedQueryString ? `${pathname}?${mergedQueryString}` : pathname;
|
|
1424
|
+
}
|
|
1425
|
+
//#endregion
|
|
1426
|
+
export { canWriteResponseBody as C, tracing_channels_exports as S, Route as _, middlewareInfo as a, httpRequest as b, routeInfo as c, safeDecodeURI as d, toRoutesJSON as f, BriskRoute as g, RouteResource as h, matchRoute as i, serializeCookie as l, RouteGroup as m, createSignedURL as n, mime as o, trustProxy as p, encodeUrl as r, parseRoute as s, appendQueryString as t, parseRange as u, httpExceptionHandler as v, debug_default as w, httpResponseSerializer as x, httpMiddleware as y };
|