@adonisjs/http-server 8.0.0-next.9 → 8.0.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 (49) hide show
  1. package/build/define_config-D-kQXU0e.js +2438 -0
  2. package/build/factories/http_context.d.ts +10 -4
  3. package/build/factories/main.d.ts +2 -2
  4. package/build/factories/main.js +170 -345
  5. package/build/factories/request.d.ts +4 -4
  6. package/build/factories/response.d.ts +4 -4
  7. package/build/factories/router.d.ts +1 -1
  8. package/build/factories/server_factory.d.ts +1 -1
  9. package/build/factories/url_builder_factory.d.ts +3 -3
  10. package/build/helpers-C_2HouOe.js +52 -0
  11. package/build/index.d.ts +2 -2
  12. package/build/index.js +155 -373
  13. package/build/src/client/helpers.d.ts +37 -0
  14. package/build/src/client/types.d.ts +194 -0
  15. package/build/src/client/url_builder.d.ts +15 -0
  16. package/build/src/client/url_builder.js +115 -0
  17. package/build/src/cookies/client.d.ts +28 -5
  18. package/build/src/cookies/drivers/encrypted.d.ts +1 -1
  19. package/build/src/cookies/drivers/signed.d.ts +1 -1
  20. package/build/src/cookies/parser.d.ts +1 -1
  21. package/build/src/cookies/serializer.d.ts +2 -2
  22. package/build/src/debug.d.ts +14 -1
  23. package/build/src/define_config.d.ts +19 -1
  24. package/build/src/define_middleware.d.ts +19 -3
  25. package/build/src/errors.d.ts +60 -5
  26. package/build/src/exception_handler.d.ts +28 -8
  27. package/build/src/helpers.d.ts +5 -17
  28. package/build/src/helpers.js +76 -24
  29. package/build/src/http_context/main.d.ts +67 -17
  30. package/build/src/qs.d.ts +17 -3
  31. package/build/src/redirect.d.ts +22 -3
  32. package/build/src/request.d.ts +12 -5
  33. package/build/src/response.d.ts +5 -5
  34. package/build/src/response_status.d.ts +14 -0
  35. package/build/src/router/main.d.ts +6 -2
  36. package/build/src/router/route.d.ts +130 -32
  37. package/build/src/router/signed_url_builder.d.ts +1 -1
  38. package/build/src/server/main.d.ts +6 -6
  39. package/build/src/types/main.js +1 -0
  40. package/build/src/types/response.d.ts +6 -1
  41. package/build/src/types/route.d.ts +3 -27
  42. package/build/src/types/url_builder.d.ts +2 -140
  43. package/build/src/utils.d.ts +71 -6
  44. package/build/types-AUwURgIL.js +1 -0
  45. package/build/utils-BjSHKI3s.js +618 -0
  46. package/package.json +55 -46
  47. package/build/chunk-CVZAIRWJ.js +0 -1222
  48. package/build/chunk-JD6QW4NQ.js +0 -4428
  49. package/build/src/router/url_builder.d.ts +0 -9
@@ -1,4428 +0,0 @@
1
- import {
2
- BriskRoute,
3
- Route,
4
- RouteGroup,
5
- RouteResource,
6
- __export,
7
- createSignedURL,
8
- createURL,
9
- debug_default,
10
- default as default2,
11
- default2 as default3,
12
- httpExceptionHandler,
13
- httpMiddleware,
14
- httpRequest,
15
- httpResponseSerializer,
16
- parseRoute,
17
- safeDecodeURI,
18
- serializeCookie,
19
- toRoutesJSON,
20
- trustProxy
21
- } from "./chunk-CVZAIRWJ.js";
22
-
23
- // src/qs.ts
24
- import { parse, stringify } from "qs";
25
- var Qs = class {
26
- /**
27
- * Configuration object containing parse and stringify options for query strings
28
- */
29
- #config;
30
- /**
31
- * Creates a new query string parser instance with the provided configuration
32
- * @param config - Configuration object with parse and stringify options
33
- */
34
- constructor(config) {
35
- this.#config = config;
36
- }
37
- /**
38
- * Parses a query string into a JavaScript object using the configured options
39
- * @param value - Query string to parse (e.g., "foo=bar&baz=qux")
40
- * @returns Parsed object representation of the query string
41
- */
42
- parse = (value) => {
43
- return parse(value, this.#config.parse);
44
- };
45
- /**
46
- * Converts a JavaScript object into a query string using the configured options
47
- * @param value - Object to convert to query string
48
- * @returns Stringified query string representation of the object
49
- */
50
- stringify = (value) => {
51
- return stringify(value, this.#config.stringify);
52
- };
53
- };
54
-
55
- // src/errors.ts
56
- var errors_exports = {};
57
- __export(errors_exports, {
58
- E_CANNOT_LOOKUP_ROUTE: () => E_CANNOT_LOOKUP_ROUTE,
59
- E_HTTP_EXCEPTION: () => E_HTTP_EXCEPTION,
60
- E_HTTP_REQUEST_ABORTED: () => E_HTTP_REQUEST_ABORTED,
61
- E_ROUTE_NOT_FOUND: () => E_ROUTE_NOT_FOUND
62
- });
63
- import { createError, Exception } from "@poppinss/utils/exception";
64
- var E_ROUTE_NOT_FOUND = createError(
65
- "Cannot %s:%s",
66
- "E_ROUTE_NOT_FOUND",
67
- 404
68
- );
69
- var E_CANNOT_LOOKUP_ROUTE = createError(
70
- 'Cannot lookup route "%s"',
71
- "E_CANNOT_LOOKUP_ROUTE",
72
- 500
73
- );
74
- var E_HTTP_EXCEPTION = class HttpException extends Exception {
75
- body;
76
- static code = "E_HTTP_EXCEPTION";
77
- /**
78
- * This method returns an instance of the exception class
79
- */
80
- static invoke(body, status, code = "E_HTTP_EXCEPTION") {
81
- if (body === null || body === void 0) {
82
- const error2 = new this("HTTP Exception", { status, code });
83
- error2.body = "Internal server error";
84
- return error2;
85
- }
86
- if (typeof body === "object") {
87
- const error2 = new this(body.message || "HTTP Exception", { status, code });
88
- error2.body = body;
89
- return error2;
90
- }
91
- const error = new this(body, { status, code });
92
- error.body = body;
93
- return error;
94
- }
95
- };
96
- var E_HTTP_REQUEST_ABORTED = class AbortException extends E_HTTP_EXCEPTION {
97
- handle(error, ctx) {
98
- ctx.response.status(error.status).send(error.body);
99
- }
100
- };
101
-
102
- // src/cookies/drivers/plain.ts
103
- import base64 from "@poppinss/utils/base64";
104
- import { MessageBuilder } from "@poppinss/utils";
105
- function pack(value) {
106
- if (value === void 0 || value === null) {
107
- return null;
108
- }
109
- return base64.urlEncode(new MessageBuilder().build(value));
110
- }
111
- function canUnpack(encodedValue) {
112
- return typeof encodedValue === "string";
113
- }
114
- function unpack(encodedValue) {
115
- return new MessageBuilder().verify(base64.urlDecode(encodedValue, "utf-8", false));
116
- }
117
-
118
- // src/cookies/drivers/signed.ts
119
- function pack2(key, value, encryption) {
120
- if (value === void 0 || value === null) {
121
- return null;
122
- }
123
- return `s:${encryption.verifier.sign(value, void 0, key)}`;
124
- }
125
- function canUnpack2(signedValue) {
126
- return typeof signedValue === "string" && signedValue.substring(0, 2) === "s:";
127
- }
128
- function unpack2(key, signedValue, encryption) {
129
- const value = signedValue.slice(2);
130
- if (!value) {
131
- return null;
132
- }
133
- return encryption.verifier.unsign(value, key);
134
- }
135
-
136
- // src/cookies/drivers/encrypted.ts
137
- function pack3(key, value, encryption) {
138
- if (value === void 0 || value === null) {
139
- return null;
140
- }
141
- return `e:${encryption.encrypt(value, void 0, key)}`;
142
- }
143
- function canUnpack3(encryptedValue) {
144
- return typeof encryptedValue === "string" && encryptedValue.substring(0, 2) === "e:";
145
- }
146
- function unpack3(key, encryptedValue, encryption) {
147
- const value = encryptedValue.slice(2);
148
- if (!value) {
149
- return null;
150
- }
151
- return encryption.decrypt(value, key);
152
- }
153
-
154
- // src/cookies/client.ts
155
- var CookieClient = class {
156
- /**
157
- * Private encryption instance used for signing and encrypting cookies
158
- */
159
- #encryption;
160
- /**
161
- * Create a new instance of CookieClient
162
- *
163
- * @param encryption - The encryption instance for cookie operations
164
- */
165
- constructor(encryption) {
166
- this.#encryption = encryption;
167
- }
168
- /**
169
- * Encrypt a key value pair to be sent in the cookie header
170
- *
171
- * @param key - The cookie key
172
- * @param value - The value to encrypt
173
- * @returns The encrypted cookie string or null if encryption fails
174
- */
175
- encrypt(key, value) {
176
- return pack3(key, value, this.#encryption);
177
- }
178
- /**
179
- * Sign a key value pair to be sent in the cookie header
180
- *
181
- * @param key - The cookie key
182
- * @param value - The value to sign
183
- * @returns The signed cookie string or null if signing fails
184
- */
185
- sign(key, value) {
186
- return pack2(key, value, this.#encryption);
187
- }
188
- /**
189
- * Encode a key value pair to be sent in the cookie header
190
- *
191
- * @param _ - Unused key parameter
192
- * @param value - The value to encode
193
- * @param stringify - Whether to stringify the value before encoding
194
- * @returns The encoded cookie string or null if encoding fails
195
- */
196
- encode(_, value, stringify2 = true) {
197
- return stringify2 ? pack(value) : value;
198
- }
199
- /**
200
- * Unsign a signed cookie value
201
- *
202
- * @param key - The cookie key
203
- * @param value - The signed cookie value to unsign
204
- * @returns The original value if valid signature, null otherwise
205
- */
206
- unsign(key, value) {
207
- return canUnpack2(value) ? unpack2(key, value, this.#encryption) : null;
208
- }
209
- /**
210
- * Decrypt an encrypted cookie value
211
- *
212
- * @param key - The cookie key
213
- * @param value - The encrypted cookie value to decrypt
214
- * @returns The decrypted value or null if decryption fails
215
- */
216
- decrypt(key, value) {
217
- return canUnpack3(value) ? unpack3(key, value, this.#encryption) : null;
218
- }
219
- /**
220
- * Decode an encoded cookie value
221
- *
222
- * @param _ - Unused key parameter
223
- * @param value - The encoded cookie value to decode
224
- * @param stringified - Whether the value was stringified during encoding
225
- * @returns The decoded value or null if decoding fails
226
- */
227
- decode(_, value, stringified = true) {
228
- if (!stringified) {
229
- return value;
230
- }
231
- return canUnpack(value) ? unpack(value) : null;
232
- }
233
- /**
234
- * Parse response cookie
235
- *
236
- * @param key - The cookie key
237
- * @param value - The cookie value to parse
238
- * @returns The parsed value or undefined if parsing fails
239
- */
240
- parse(key, value) {
241
- if (canUnpack2(value)) {
242
- return unpack2(key, value, this.#encryption);
243
- }
244
- if (canUnpack3(value)) {
245
- return unpack3(key, value, this.#encryption);
246
- }
247
- if (canUnpack(value)) {
248
- return unpack(value);
249
- }
250
- }
251
- };
252
-
253
- // src/cookies/parser.ts
254
- import cookie from "cookie";
255
- var CookieParser = class {
256
- /**
257
- * Cookie client instance for handling cookie operations
258
- */
259
- #client;
260
- /**
261
- * A copy of cached cookies, they are cached during a request after
262
- * initial decoding, unsigning or decrypting.
263
- */
264
- #cachedCookies = {
265
- signedCookies: {},
266
- plainCookies: {},
267
- encryptedCookies: {}
268
- };
269
- /**
270
- * An object of key-value pair collected by parsing
271
- * the request cookie header.
272
- */
273
- #cookies;
274
- /**
275
- * Create a new instance of CookieParser
276
- *
277
- * @param cookieHeader - The raw cookie header string from the request
278
- * @param encryption - The encryption instance for cookie operations
279
- */
280
- constructor(cookieHeader, encryption) {
281
- this.#client = new CookieClient(encryption);
282
- this.#cookies = this.#parse(cookieHeader);
283
- }
284
- /**
285
- * Parses the request `cookie` header
286
- *
287
- * @param cookieHeader - The cookie header string to parse
288
- * @returns Parsed cookies as key-value pairs
289
- */
290
- #parse(cookieHeader) {
291
- if (!cookieHeader) {
292
- return {};
293
- }
294
- return cookie.parse(cookieHeader);
295
- }
296
- /**
297
- * Attempts to decode a cookie by the name. When calling this method,
298
- * you are assuming that the cookie was just stringified in the first
299
- * place and not signed or encrypted.
300
- *
301
- * @param key - The cookie key to decode
302
- * @param stringified - Whether the cookie value was stringified
303
- * @returns The decoded cookie value or null if decoding fails
304
- */
305
- decode(key, stringified = true) {
306
- const value = this.#cookies[key];
307
- if (value === null || value === void 0) {
308
- return null;
309
- }
310
- const cache = this.#cachedCookies.plainCookies;
311
- if (cache[key] !== void 0) {
312
- return cache[key];
313
- }
314
- const parsed = this.#client.decode(key, value, stringified);
315
- if (parsed !== null) {
316
- cache[key] = parsed;
317
- }
318
- return parsed;
319
- }
320
- /**
321
- * Attempts to unsign a cookie by the name. When calling this method,
322
- * you are assuming that the cookie was signed in the first place.
323
- *
324
- * @param key - The cookie key to unsign
325
- * @returns The original cookie value or null if unsigning fails
326
- */
327
- unsign(key) {
328
- const value = this.#cookies[key];
329
- if (value === null || value === void 0) {
330
- return null;
331
- }
332
- const cache = this.#cachedCookies.signedCookies;
333
- if (cache[key] !== void 0) {
334
- return cache[key];
335
- }
336
- const parsed = this.#client.unsign(key, value);
337
- if (parsed !== null) {
338
- cache[key] = parsed;
339
- }
340
- return parsed;
341
- }
342
- /**
343
- * Attempts to decrypt a cookie by the name. When calling this method,
344
- * you are assuming that the cookie was encrypted in the first place.
345
- *
346
- * @param key - The cookie key to decrypt
347
- * @returns The decrypted cookie value or null if decryption fails
348
- */
349
- decrypt(key) {
350
- const value = this.#cookies[key];
351
- if (value === null || value === void 0) {
352
- return null;
353
- }
354
- const cache = this.#cachedCookies.encryptedCookies;
355
- if (cache[key] !== void 0) {
356
- return cache[key];
357
- }
358
- const parsed = this.#client.decrypt(key, value);
359
- if (parsed !== null) {
360
- cache[key] = parsed;
361
- }
362
- return parsed;
363
- }
364
- /**
365
- * Returns an object of cookies key-value pair. Do note, the
366
- * cookies are not decoded, unsigned or decrypted inside this
367
- * list.
368
- *
369
- * @returns Raw cookies as key-value pairs
370
- */
371
- list() {
372
- return this.#cookies;
373
- }
374
- };
375
-
376
- // src/request.ts
377
- import fresh from "fresh";
378
- import typeIs from "type-is";
379
- import accepts from "accepts";
380
- import { isIP } from "net";
381
- import is from "@sindresorhus/is";
382
- import proxyaddr from "proxy-addr";
383
- import { safeEqual } from "@poppinss/utils";
384
- import Macroable from "@poppinss/macroable";
385
- import lodash from "@poppinss/utils/lodash";
386
- var Request = class extends Macroable {
387
- /**
388
- * Creates a new Request instance wrapping the native Node.js HTTP request
389
- * @param request - Native Node.js incoming message instance
390
- * @param response - Native Node.js server response instance
391
- * @param encryption - Encryption module for cookie and URL signing
392
- * @param config - Request configuration options
393
- * @param qsParser - Query string parser instance
394
- */
395
- constructor(request, response, encryption, config, qsParser) {
396
- super();
397
- this.request = request;
398
- this.response = response;
399
- this.#qsParser = qsParser;
400
- this.#config = config;
401
- this.#encryption = encryption;
402
- this.parsedUrl = safeDecodeURI(request.url, false);
403
- this.#parseQueryString();
404
- }
405
- /**
406
- * Query string parser
407
- */
408
- #qsParser;
409
- /**
410
- * Encryption module to verify signed URLs and unsign/decrypt
411
- * cookies
412
- */
413
- #encryption;
414
- /**
415
- * Request config
416
- */
417
- #config;
418
- /**
419
- * Request body set using `setBody` method
420
- */
421
- #requestBody = {};
422
- /**
423
- * A merged copy of `request body` and `querystring`
424
- */
425
- #requestData = {};
426
- /**
427
- * Original merged copy of `request body` and `querystring`.
428
- * Further mutation to this object are not allowed
429
- */
430
- #originalRequestData = {};
431
- /**
432
- * Parsed query string
433
- */
434
- #requestQs = {};
435
- /**
436
- * Raw request body as text
437
- */
438
- #rawRequestBody;
439
- /**
440
- * Cached copy of `accepts` fn to do content
441
- * negotiation.
442
- */
443
- #lazyAccepts;
444
- /**
445
- * Copy of lazily parsed signed and plain cookies.
446
- */
447
- #cookieParser;
448
- /**
449
- * Parsed URL with query string stored as a string and decode flag
450
- */
451
- parsedUrl;
452
- /**
453
- * HTTP context reference - creates a circular reference when set by the context
454
- */
455
- ctx;
456
- /**
457
- * Parses the query string from the parsed URL and updates internal state
458
- */
459
- #parseQueryString() {
460
- if (this.parsedUrl.query) {
461
- this.updateQs(this.#qsParser.parse(this.parsedUrl.query));
462
- this.#originalRequestData = { ...this.#requestData };
463
- }
464
- }
465
- /**
466
- * Initiates the cookie parser lazily when first needed
467
- */
468
- #initiateCookieParser() {
469
- if (!this.#cookieParser) {
470
- this.#cookieParser = new CookieParser(this.header("cookie"), this.#encryption);
471
- }
472
- }
473
- /**
474
- * Lazily initiates the `accepts` module to make sure to parse
475
- * the request headers only when one of the content-negotiation
476
- * methods are used.
477
- */
478
- #initiateAccepts() {
479
- this.#lazyAccepts = this.#lazyAccepts || accepts(this.request);
480
- }
481
- /**
482
- * Returns the request id from the `x-request-id` header. The
483
- * header is untouched, if it already exists.
484
- * @returns The request ID or undefined if not found/generated
485
- */
486
- id() {
487
- let requestId = this.header("x-request-id");
488
- if (!requestId && this.#config.generateRequestId) {
489
- requestId = this.#config.createRequestId();
490
- this.request.headers["x-request-id"] = requestId;
491
- }
492
- return requestId;
493
- }
494
- /**
495
- * Set initial request body. A copy of the input will be maintained as the original
496
- * request body. Since the request body and query string is subject to mutations, we
497
- * keep one original reference to flash old data (whenever required).
498
- *
499
- * This method is supposed to be invoked by the body parser and must be called only
500
- * once. For further mutations make use of `updateBody` method.
501
- * @param body - Parsed request body data
502
- * @returns {void}
503
- */
504
- setInitialBody(body) {
505
- if (this.#originalRequestData && Object.isFrozen(this.#originalRequestData)) {
506
- throw new Error('Cannot re-set initial body. Use "request.updateBody" instead');
507
- }
508
- this.updateBody(body);
509
- this.#originalRequestData = Object.freeze(lodash.cloneDeep(this.#requestData));
510
- }
511
- /**
512
- * Update the request body with new data object. The `all` property
513
- * will be re-computed by merging the query string and request
514
- * body.
515
- * @param body - New request body data to set
516
- * @returns {void}
517
- */
518
- updateBody(body) {
519
- this.#requestBody = body;
520
- this.#requestData = { ...this.#requestBody, ...this.#requestQs };
521
- }
522
- /**
523
- * Update the request raw body. Bodyparser sets this when unable to parse
524
- * the request body or when request is multipart/form-data.
525
- * @param rawBody - Raw request body as string
526
- * @returns {void}
527
- */
528
- updateRawBody(rawBody) {
529
- this.#rawRequestBody = rawBody;
530
- }
531
- /**
532
- * Update the query string with the new data object. The `all` property
533
- * will be re-computed by merging the query and the request body.
534
- * @param data - New query string data to set
535
- * @returns {void}
536
- */
537
- updateQs(data) {
538
- this.#requestQs = data;
539
- this.#requestData = { ...this.#requestBody, ...this.#requestQs };
540
- }
541
- /**
542
- * Returns route params
543
- * @returns {Record<string, any>} Object containing route parameters
544
- */
545
- params() {
546
- return this.ctx?.params || {};
547
- }
548
- /**
549
- * Returns the query string object by reference
550
- * @returns {Record<string, any>} Object containing parsed query string parameters
551
- */
552
- qs() {
553
- return this.#requestQs;
554
- }
555
- /**
556
- * Returns reference to the request body
557
- * @returns {Record<string, any>} Object containing parsed request body
558
- */
559
- body() {
560
- return this.#requestBody;
561
- }
562
- /**
563
- * Returns reference to the merged copy of request body
564
- * and query string
565
- * @returns {Record<string, any>} Object containing merged request body and query parameters
566
- */
567
- all() {
568
- return this.#requestData;
569
- }
570
- /**
571
- * Returns reference to the merged copy of original request
572
- * query string and body
573
- * @returns {Record<string, any>} Object containing original merged request data
574
- */
575
- original() {
576
- return this.#originalRequestData;
577
- }
578
- /**
579
- * Returns the request raw body (if exists), or returns `null`.
580
- *
581
- * Ideally you must be dealing with the parsed body accessed using [[input]], [[all]] or
582
- * [[post]] methods. The `raw` body is always a string.
583
- * @returns {string | null} Raw request body as string or null if not set
584
- */
585
- raw() {
586
- return this.#rawRequestBody || null;
587
- }
588
- /**
589
- * Returns value for a given key from the request body or query string.
590
- * The `defaultValue` is used when original value is `undefined`.
591
- *
592
- * @example
593
- * ```js
594
- * request.input('username')
595
- *
596
- * // with default value
597
- * request.input('username', 'virk')
598
- * ```
599
- * @param key - Key to lookup in request data
600
- * @param defaultValue - Default value when key is not found
601
- * @returns Value from request data or default value
602
- */
603
- input(key, defaultValue) {
604
- return lodash.get(this.#requestData, key, defaultValue);
605
- }
606
- /**
607
- * Returns value for a given key from route params
608
- *
609
- * @example
610
- * ```js
611
- * request.param('id')
612
- *
613
- * // with default value
614
- * request.param('id', 1)
615
- * ```
616
- * @param key - Parameter key to lookup
617
- * @param defaultValue - Default value when parameter is not found
618
- * @returns Value from route parameters or default value
619
- */
620
- param(key, defaultValue) {
621
- return lodash.get(this.params(), key, defaultValue);
622
- }
623
- /**
624
- * Get everything from the request body except the given keys.
625
- *
626
- * @example
627
- * ```js
628
- * request.except(['_csrf'])
629
- * ```
630
- * @param keys - Array of keys to exclude from the result
631
- * @returns {Record<string, any>} Object with all request data except specified keys
632
- */
633
- except(keys) {
634
- return lodash.omit(this.#requestData, keys);
635
- }
636
- /**
637
- * Get value for specified keys.
638
- *
639
- * @example
640
- * ```js
641
- * request.only(['username', 'age'])
642
- * ```
643
- * @param keys - Array of keys to include in the result
644
- * @returns {{ [K in T]: any }} Object with only the specified keys from request data
645
- */
646
- only(keys) {
647
- return lodash.pick(this.#requestData, keys);
648
- }
649
- /**
650
- * Returns the HTTP request method. This is the original
651
- * request method. For spoofed request method, make
652
- * use of [[method]].
653
- *
654
- * @example
655
- * ```js
656
- * request.intended()
657
- * ```
658
- * @returns {string} Original HTTP method from the request
659
- */
660
- intended() {
661
- return this.request.method;
662
- }
663
- /**
664
- * Returns the request HTTP method by taking method spoofing into account.
665
- *
666
- * Method spoofing works when all of the following are true.
667
- *
668
- * 1. `app.http.allowMethodSpoofing` config value is true.
669
- * 2. request query string has `_method`.
670
- * 3. The [[intended]] request method is `POST`.
671
- *
672
- * @example
673
- * ```js
674
- * request.method()
675
- * ```
676
- * @returns {string} HTTP method (potentially spoofed)
677
- */
678
- method() {
679
- if (this.#config.allowMethodSpoofing && this.intended() === "POST") {
680
- return this.input("_method", this.intended()).toUpperCase();
681
- }
682
- return this.intended();
683
- }
684
- /**
685
- * Returns a copy of headers as an object
686
- * @returns {IncomingHttpHeaders} Object containing all HTTP headers
687
- */
688
- headers() {
689
- return this.request.headers;
690
- }
691
- /**
692
- * Returns value for a given header key. The default value is
693
- * used when original value is `undefined`.
694
- * @param key - Header name to lookup
695
- * @param defaultValue - Default value when header is not found
696
- * @returns {string | undefined} Header value or default value if not found
697
- */
698
- header(key, defaultValue) {
699
- key = key.toLowerCase();
700
- const headers = this.headers();
701
- switch (key) {
702
- case "referer":
703
- case "referrer":
704
- return headers.referrer || headers.referer || defaultValue;
705
- default:
706
- return headers[key] || defaultValue;
707
- }
708
- }
709
- /**
710
- * Returns the ip address of the user. This method is optimize to fetch
711
- * ip address even when running your AdonisJs app behind a proxy.
712
- *
713
- * You can also define your own custom function to compute the ip address by
714
- * defining `app.http.getIp` as a function inside the config file.
715
- *
716
- * ```js
717
- * {
718
- * http: {
719
- * getIp (request) {
720
- * // I am using nginx as a proxy server and want to trust 'x-real-ip'
721
- * return request.header('x-real-ip')
722
- * }
723
- * }
724
- * }
725
- * ```
726
- *
727
- * You can control the behavior of trusting the proxy values by defining it
728
- * inside the `config/app.js` file.
729
- *
730
- * ```js
731
- * {
732
- * http: {
733
- * trustProxy: '127.0.0.1'
734
- * }
735
- * }
736
- * ```
737
- *
738
- * The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
739
- * @returns {string} Client IP address
740
- */
741
- ip() {
742
- const ipFn = this.#config.getIp;
743
- if (typeof ipFn === "function") {
744
- return ipFn(this);
745
- }
746
- return proxyaddr(this.request, this.#config.trustProxy);
747
- }
748
- /**
749
- * Returns an array of ip addresses from most to least trusted one.
750
- * This method is optimize to fetch ip address even when running
751
- * your AdonisJs app behind a proxy.
752
- *
753
- * You can control the behavior of trusting the proxy values by defining it
754
- * inside the `config/app.js` file.
755
- *
756
- * ```js
757
- * {
758
- * http: {
759
- * trustProxy: '127.0.0.1'
760
- * }
761
- * }
762
- * ```
763
- *
764
- * The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
765
- * @returns {string[]} Array of IP addresses from most to least trusted
766
- */
767
- ips() {
768
- return proxyaddr.all(this.request, this.#config.trustProxy);
769
- }
770
- /**
771
- * Returns the request protocol by checking for the URL protocol or
772
- * `X-Forwarded-Proto` header.
773
- *
774
- * If the `trust` is evaluated to `false`, then URL protocol is returned,
775
- * otherwise `X-Forwarded-Proto` header is used (if exists).
776
- *
777
- * You can control the behavior of trusting the proxy values by defining it
778
- * inside the `config/app.js` file.
779
- *
780
- * ```js
781
- * {
782
- * http: {
783
- * trustProxy: '127.0.0.1'
784
- * }
785
- * }
786
- * ```
787
- *
788
- * The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
789
- * @returns {string} Request protocol ('http' or 'https')
790
- */
791
- protocol() {
792
- if ("encrypted" in this.request.socket) {
793
- return "https";
794
- }
795
- if (trustProxy(this.request.socket.remoteAddress, this.#config.trustProxy)) {
796
- const forwardedProtocol = this.header("X-Forwarded-Proto");
797
- return forwardedProtocol ? forwardedProtocol.split(/\s*,\s*/)[0] : "http";
798
- }
799
- return "http";
800
- }
801
- /**
802
- * Returns a boolean telling if request is served over `https`
803
- * or not. Check [[protocol]] method to know how protocol is
804
- * fetched.
805
- * @returns {boolean} True if request is served over HTTPS
806
- */
807
- secure() {
808
- return this.protocol() === "https";
809
- }
810
- /**
811
- * Returns the request host. If proxy headers are trusted, then
812
- * `X-Forwarded-Host` is given priority over the `Host` header.
813
- *
814
- * You can control the behavior of trusting the proxy values by defining it
815
- * inside the `config/app.js` file.
816
- *
817
- * ```js
818
- * {
819
- * http: {
820
- * trustProxy: '127.0.0.1'
821
- * }
822
- * }
823
- * ```
824
- *
825
- * The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
826
- * @returns {string | null} Request host or null if not found
827
- */
828
- host() {
829
- let host = this.header("host");
830
- if (trustProxy(this.request.socket.remoteAddress, this.#config.trustProxy)) {
831
- host = this.header("X-Forwarded-Host") || host;
832
- }
833
- if (!host) {
834
- return null;
835
- }
836
- return host;
837
- }
838
- /**
839
- * Returns the request hostname. If proxy headers are trusted, then
840
- * `X-Forwarded-Host` is given priority over the `Host` header.
841
- *
842
- * You can control the behavior of trusting the proxy values by defining it
843
- * inside the `config/app.js` file.
844
- *
845
- * ```js
846
- * {
847
- * http: {
848
- * trustProxy: '127.0.0.1'
849
- * }
850
- * }
851
- * ```
852
- *
853
- * The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
854
- * @returns {string | null} Request hostname (without port) or null if not found
855
- */
856
- hostname() {
857
- const host = this.host();
858
- if (!host) {
859
- return null;
860
- }
861
- const offset = host[0] === "[" ? host.indexOf("]") + 1 : 0;
862
- const index = host.indexOf(":", offset);
863
- return index !== -1 ? host.substring(0, index) : host;
864
- }
865
- /**
866
- * Returns an array of subdomains for the given host. An empty array is
867
- * returned if [[hostname]] is `null` or is an IP address.
868
- *
869
- * Also `www` is not considered as a subdomain
870
- * @returns {string[]} Array of subdomains (excluding www)
871
- */
872
- subdomains() {
873
- const hostname = this.hostname();
874
- if (!hostname || isIP(hostname)) {
875
- return [];
876
- }
877
- const offset = this.#config.subdomainOffset;
878
- const subdomains = hostname.split(".").reverse().slice(offset);
879
- if (subdomains[subdomains.length - 1] === "www") {
880
- subdomains.splice(subdomains.length - 1, 1);
881
- }
882
- return subdomains;
883
- }
884
- /**
885
- * Returns a boolean telling, if request `X-Requested-With === 'xmlhttprequest'`
886
- * or not.
887
- * @returns {boolean} True if request is an AJAX request
888
- */
889
- ajax() {
890
- const xRequestedWith = this.header("X-Requested-With", "");
891
- return xRequestedWith.toLowerCase() === "xmlhttprequest";
892
- }
893
- /**
894
- * Returns a boolean telling, if request has `X-Pjax` header
895
- * set or not
896
- * @returns {boolean} True if request is a PJAX request
897
- */
898
- pjax() {
899
- return !!this.header("X-Pjax");
900
- }
901
- /**
902
- * Returns the request relative URL.
903
- *
904
- * @example
905
- * ```js
906
- * request.url()
907
- *
908
- * // include query string
909
- * request.url(true)
910
- * ```
911
- * @param includeQueryString - Whether to include query string in the URL
912
- * @returns {string} Request pathname, optionally with query string
913
- */
914
- url(includeQueryString) {
915
- const pathname = this.parsedUrl.pathname;
916
- return includeQueryString && this.parsedUrl.query ? `${pathname}?${this.parsedUrl.query}` : pathname;
917
- }
918
- /**
919
- * Returns the complete HTTP url by combining
920
- * [[protocol]]://[[hostname]]/[[url]]
921
- *
922
- * @example
923
- * ```js
924
- * request.completeUrl()
925
- *
926
- * // include query string
927
- * request.completeUrl(true)
928
- * ```
929
- * @param includeQueryString - Whether to include query string in the URL
930
- * @returns {string} Complete URL including protocol and host
931
- */
932
- completeUrl(includeQueryString) {
933
- const protocol = this.protocol();
934
- const hostname = this.host();
935
- return `${protocol}://${hostname}${this.url(includeQueryString)}`;
936
- }
937
- /**
938
- * Find if the current HTTP request is for the given route or the routes
939
- * @param routeIdentifier - Route name, pattern, or handler reference to match
940
- * @returns {boolean} True if the request matches any of the given route identifiers
941
- */
942
- matchesRoute(routeIdentifier) {
943
- if (!this.ctx || !this.ctx.route) {
944
- return false;
945
- }
946
- const route = this.ctx.route;
947
- return !!(Array.isArray(routeIdentifier) ? routeIdentifier : [routeIdentifier]).find(
948
- (identifier) => {
949
- if (route.pattern === identifier || route.name === identifier) {
950
- return true;
951
- }
952
- if (typeof route.handler === "function") {
953
- return false;
954
- }
955
- return route.handler.reference === identifier;
956
- }
957
- );
958
- }
959
- /**
960
- * Returns the best matching content type of the request by
961
- * matching against the given types.
962
- *
963
- * The content type is picked from the `content-type` header and request
964
- * must have body.
965
- *
966
- * The method response highly depends upon the types array values. Described below:
967
- *
968
- * | Type(s) | Return value |
969
- * |----------|---------------|
970
- * | ['json'] | json |
971
- * | ['application/*'] | application/json |
972
- * | ['vnd+json'] | application/json |
973
- *
974
- * @example
975
- * ```js
976
- * const bodyType = request.is(['json', 'xml'])
977
- *
978
- * if (bodyType === 'json') {
979
- * // process JSON
980
- * }
981
- *
982
- * if (bodyType === 'xml') {
983
- * // process XML
984
- * }
985
- * ```
986
- * @param types - Array of content types to match against
987
- * @returns {string | null} Best matching content type or null if no match
988
- */
989
- is(types) {
990
- return typeIs(this.request, types) || null;
991
- }
992
- /**
993
- * Returns the best type using `Accept` header and
994
- * by matching it against the given types.
995
- *
996
- * If nothing is matched, then `null` will be returned
997
- *
998
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
999
- * docs too.
1000
- *
1001
- * @example
1002
- * ```js
1003
- * switch (request.accepts(['json', 'html'])) {
1004
- * case 'json':
1005
- * return response.json(user)
1006
- * case 'html':
1007
- * return view.render('user', { user })
1008
- * default:
1009
- * // decide yourself
1010
- * }
1011
- * ```
1012
- * @param types - Array of types to match against Accept header
1013
- * @returns {T | null} Best matching accept type or null if no match
1014
- */
1015
- accepts(types) {
1016
- this.#initiateAccepts();
1017
- return this.#lazyAccepts.type(types) || null;
1018
- }
1019
- /**
1020
- * Return the types that the request accepts, in the order of the
1021
- * client's preference (most preferred first).
1022
- *
1023
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
1024
- * docs too.
1025
- * @returns {string[]} Array of accepted types in preference order
1026
- */
1027
- types() {
1028
- this.#initiateAccepts();
1029
- return this.#lazyAccepts.types();
1030
- }
1031
- /**
1032
- * Returns the best language using `Accept-language` header
1033
- * and by matching it against the given languages.
1034
- *
1035
- * If nothing is matched, then `null` will be returned
1036
- *
1037
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
1038
- * docs too.
1039
- *
1040
- * @example
1041
- * ```js
1042
- * switch (request.language(['fr', 'de'])) {
1043
- * case 'fr':
1044
- * return view.render('about', { lang: 'fr' })
1045
- * case 'de':
1046
- * return view.render('about', { lang: 'de' })
1047
- * default:
1048
- * return view.render('about', { lang: 'en' })
1049
- * }
1050
- * ```
1051
- * @param languages - Array of languages to match against Accept-Language header
1052
- * @returns {T | null} Best matching language or null if no match
1053
- */
1054
- language(languages) {
1055
- this.#initiateAccepts();
1056
- return this.#lazyAccepts.language(languages) || null;
1057
- }
1058
- /**
1059
- * Return the languages that the request accepts, in the order of the
1060
- * client's preference (most preferred first).
1061
- *
1062
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
1063
- * docs too.
1064
- * @returns {string[]} Array of accepted languages in preference order
1065
- */
1066
- languages() {
1067
- this.#initiateAccepts();
1068
- return this.#lazyAccepts.languages();
1069
- }
1070
- /**
1071
- * Returns the best charset using `Accept-charset` header
1072
- * and by matching it against the given charsets.
1073
- *
1074
- * If nothing is matched, then `null` will be returned
1075
- *
1076
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
1077
- * docs too.
1078
- *
1079
- * @example
1080
- * ```js
1081
- * switch (request.charset(['utf-8', 'ISO-8859-1'])) {
1082
- * case 'utf-8':
1083
- * // make utf-8 friendly response
1084
- * case 'ISO-8859-1':
1085
- * // make ISO-8859-1 friendly response
1086
- * }
1087
- * ```
1088
- * @param charsets - Array of charsets to match against Accept-Charset header
1089
- * @returns {T | null} Best matching charset or null if no match
1090
- */
1091
- charset(charsets) {
1092
- this.#initiateAccepts();
1093
- return this.#lazyAccepts.charset(charsets) || null;
1094
- }
1095
- /**
1096
- * Return the charsets that the request accepts, in the order of the
1097
- * client's preference (most preferred first).
1098
- *
1099
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
1100
- * docs too.
1101
- * @returns {string[]} Array of accepted charsets in preference order
1102
- */
1103
- charsets() {
1104
- this.#initiateAccepts();
1105
- return this.#lazyAccepts.charsets();
1106
- }
1107
- /**
1108
- * Returns the best encoding using `Accept-encoding` header
1109
- * and by matching it against the given encodings.
1110
- *
1111
- * If nothing is matched, then `null` will be returned
1112
- *
1113
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
1114
- * docs too.
1115
- * @param encodings - Array of encodings to match against Accept-Encoding header
1116
- * @returns {T | null} Best matching encoding or null if no match
1117
- */
1118
- encoding(encodings) {
1119
- this.#initiateAccepts();
1120
- return this.#lazyAccepts.encoding(encodings) || null;
1121
- }
1122
- /**
1123
- * Return the encodings that the request accepts, in the order of the
1124
- * client's preference (most preferred first).
1125
- *
1126
- * Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
1127
- * docs too.
1128
- * @returns {string[]} Array of accepted encodings in preference order
1129
- */
1130
- encodings() {
1131
- this.#initiateAccepts();
1132
- return this.#lazyAccepts.encodings();
1133
- }
1134
- /**
1135
- * Returns a boolean telling if request has body
1136
- * @returns {boolean} True if request contains a body
1137
- */
1138
- hasBody() {
1139
- return typeIs.hasBody(this.request);
1140
- }
1141
- /**
1142
- * Returns a boolean telling if the new response etag evaluates same
1143
- * as the request header `if-none-match`. In case of `true`, the
1144
- * server must return `304` response, telling the browser to
1145
- * use the client cache.
1146
- *
1147
- * You won't have to deal with this method directly, since AdonisJs will
1148
- * handle this for you when `http.etag = true` inside `config/app.js` file.
1149
- *
1150
- * However, this is how you can use it manually.
1151
- *
1152
- * ```js
1153
- * const responseBody = view.render('some-view')
1154
- *
1155
- * // sets the HTTP etag header for response
1156
- * response.setEtag(responseBody)
1157
- *
1158
- * if (request.fresh()) {
1159
- * response.sendStatus(304)
1160
- * } else {
1161
- * response.send(responseBody)
1162
- * }
1163
- * ```
1164
- * @returns {boolean} True if client cache is fresh (should return 304)
1165
- */
1166
- fresh() {
1167
- if (["GET", "HEAD"].indexOf(this.intended()) === -1) {
1168
- return false;
1169
- }
1170
- const status = this.response.statusCode;
1171
- if (status >= 200 && status < 300 || status === 304) {
1172
- return fresh(this.headers(), this.response.getHeaders());
1173
- }
1174
- return false;
1175
- }
1176
- /**
1177
- * Opposite of [[fresh]]
1178
- * @returns {boolean} True if client cache is stale (should send new response)
1179
- */
1180
- stale() {
1181
- return !this.fresh();
1182
- }
1183
- /**
1184
- * Returns all parsed and signed cookies. Signed cookies ensures
1185
- * that their value isn't tampered.
1186
- * @returns {{ [key: string]: any }} Object containing all parsed cookies
1187
- */
1188
- cookiesList() {
1189
- this.#initiateCookieParser();
1190
- return this.#cookieParser.list();
1191
- }
1192
- /**
1193
- * Returns value for a given key from signed cookies. Optional
1194
- * defaultValue is returned when actual value is undefined.
1195
- * @param key - Cookie name to lookup
1196
- * @param defaultValue - Default value when cookie is not found
1197
- * @returns Cookie value or default value if not found
1198
- */
1199
- cookie(key, defaultValue) {
1200
- this.#initiateCookieParser();
1201
- return this.#cookieParser.unsign(key) || defaultValue;
1202
- }
1203
- /**
1204
- * Returns value for a given key from encrypted cookies. Optional
1205
- * defaultValue is returned when actual value is undefined.
1206
- * @param key - Cookie name to lookup
1207
- * @param defaultValue - Default value when cookie is not found
1208
- * @returns Decrypted cookie value or default value if not found
1209
- */
1210
- encryptedCookie(key, defaultValue) {
1211
- this.#initiateCookieParser();
1212
- return this.#cookieParser.decrypt(key) || defaultValue;
1213
- }
1214
- plainCookie(key, defaultValueOrOptions, encoded) {
1215
- this.#initiateCookieParser();
1216
- if (is.object(defaultValueOrOptions)) {
1217
- return this.#cookieParser.decode(key, defaultValueOrOptions?.encoded) || defaultValueOrOptions.defaultValue;
1218
- }
1219
- return this.#cookieParser.decode(key, encoded) || defaultValueOrOptions;
1220
- }
1221
- /**
1222
- * Returns a boolean telling if a signed url has a valid signature
1223
- * or not.
1224
- * @param purpose - Optional purpose for signature verification
1225
- * @returns {boolean} True if the signed URL has a valid signature
1226
- */
1227
- hasValidSignature(purpose) {
1228
- const { signature, ...rest } = this.qs();
1229
- if (!signature) {
1230
- return false;
1231
- }
1232
- const signedUrl = this.#encryption.verifier.unsign(signature, purpose);
1233
- if (!signedUrl) {
1234
- return false;
1235
- }
1236
- const queryString = this.#qsParser.stringify(rest);
1237
- return queryString ? safeEqual(signedUrl, `${this.url()}?${queryString}`) : safeEqual(signedUrl, this.url());
1238
- }
1239
- /**
1240
- * Serializes request to JSON format
1241
- * @returns Object representation of the request
1242
- */
1243
- serialize() {
1244
- return {
1245
- id: this.id(),
1246
- url: this.url(),
1247
- query: this.parsedUrl.query,
1248
- body: this.all(),
1249
- params: this.params(),
1250
- headers: this.headers(),
1251
- method: this.method(),
1252
- protocol: this.protocol(),
1253
- cookies: this.cookiesList(),
1254
- hostname: this.hostname(),
1255
- ip: this.ip(),
1256
- subdomains: this.ctx?.subdomains || {}
1257
- };
1258
- }
1259
- /**
1260
- * toJSON copy of the request
1261
- * @returns JSON representation of the request
1262
- */
1263
- toJSON() {
1264
- return this.serialize();
1265
- }
1266
- };
1267
-
1268
- // src/redirect.ts
1269
- var Redirect = class {
1270
- /**
1271
- * Flag indicating whether to forward the existing query string from the current request
1272
- */
1273
- #forwardQueryString = false;
1274
- /**
1275
- * HTTP status code to use for the redirect response (defaults to 302)
1276
- */
1277
- #statusCode = 302;
1278
- /**
1279
- * Custom query string parameters to include in the redirect URL
1280
- */
1281
- #queryString = {};
1282
- /**
1283
- * Reference to the Node.js incoming HTTP request
1284
- */
1285
- #request;
1286
- /**
1287
- * Reference to the AdonisJS response instance
1288
- */
1289
- #response;
1290
- /**
1291
- * Reference to the AdonisJS router instance for URL building
1292
- */
1293
- #router;
1294
- /**
1295
- * Query string parser instance
1296
- */
1297
- #qs;
1298
- /**
1299
- * Creates a new Redirect instance for handling HTTP redirects
1300
- * @param request - Node.js incoming HTTP request
1301
- * @param response - AdonisJS response instance
1302
- * @param router - AdonisJS router instance
1303
- * @param qs - Query string parser instance
1304
- */
1305
- constructor(request, response, router, qs) {
1306
- this.#request = request;
1307
- this.#response = response;
1308
- this.#router = router;
1309
- this.#qs = qs;
1310
- }
1311
- /**
1312
- * Sends the redirect response by setting required headers and status code
1313
- * @param url - Target URL for redirection
1314
- * @param query - Query string parameters to append
1315
- */
1316
- #sendResponse(url, query) {
1317
- const stringified = this.#qs.stringify(query);
1318
- url = stringified ? `${url}?${stringified}` : url;
1319
- debug_default('redirecting to url "%s"', url);
1320
- this.#response.location(default2(url));
1321
- this.#response.safeStatus(this.#statusCode);
1322
- this.#response.type("text/plain; charset=utf-8");
1323
- this.#response.send(`Redirecting to ${url}`);
1324
- }
1325
- /**
1326
- * Extracts and returns the referrer URL from request headers
1327
- * @returns {string} The referrer URL or '/' if not found
1328
- */
1329
- #getReferrerUrl() {
1330
- let url = this.#request.headers["referer"] || this.#request.headers["referrer"] || "/";
1331
- return Array.isArray(url) ? url[0] : url;
1332
- }
1333
- /**
1334
- * Sets a custom HTTP status code for the redirect response
1335
- * @param statusCode - HTTP status code to use (e.g., 301, 302, 307)
1336
- * @returns {this} The Redirect instance for method chaining
1337
- */
1338
- status(statusCode) {
1339
- this.#statusCode = statusCode;
1340
- return this;
1341
- }
1342
- /**
1343
- * Clears any query string values previously added using the withQs method
1344
- * @returns {this} The Redirect instance for method chaining
1345
- */
1346
- clearQs() {
1347
- this.#forwardQueryString = false;
1348
- this.#queryString = {};
1349
- return this;
1350
- }
1351
- withQs(name, value) {
1352
- if (typeof name === "undefined") {
1353
- this.#forwardQueryString = true;
1354
- return this;
1355
- }
1356
- if (typeof name === "string") {
1357
- this.#queryString[name] = value;
1358
- return this;
1359
- }
1360
- Object.assign(this.#queryString, name);
1361
- return this;
1362
- }
1363
- /**
1364
- * Redirects to the previous path using the Referer header
1365
- * Falls back to '/' if no referrer is found
1366
- */
1367
- back() {
1368
- let query = {};
1369
- const referrerUrl = this.#getReferrerUrl();
1370
- const url = safeDecodeURI(referrerUrl, false);
1371
- debug_default('referrer url "%s"', referrerUrl);
1372
- debug_default('referrer base url "%s"', url.pathname);
1373
- if (this.#forwardQueryString) {
1374
- query = this.#qs.parse(url.query || "");
1375
- }
1376
- Object.assign(query, this.#queryString);
1377
- this.#sendResponse(url.pathname || "", query);
1378
- }
1379
- /**
1380
- * Redirects to a route using its identifier (name, pattern, or handler reference)
1381
- * @param args - Route identifier, parameters, and options for URL building
1382
- */
1383
- toRoute(...args) {
1384
- const [identifier, params, options] = args;
1385
- if (options && options.qs) {
1386
- this.withQs(options.qs);
1387
- options.qs = void 0;
1388
- }
1389
- const url = this.#router.urlBuilder.urlFor(identifier, params, options);
1390
- return this.toPath(url);
1391
- }
1392
- /**
1393
- * Redirects to a specific URL path
1394
- * @param url - Target URL path for redirection
1395
- */
1396
- toPath(url) {
1397
- let query = {};
1398
- if (this.#forwardQueryString) {
1399
- query = this.#qs.parse(safeDecodeURI(this.#request.url, false).query || "");
1400
- }
1401
- Object.assign(query, this.#queryString);
1402
- this.#sendResponse(url, query);
1403
- }
1404
- };
1405
-
1406
- // src/response_status.ts
1407
- var ResponseStatus = {
1408
- Continue: 100,
1409
- SwitchingProtocols: 101,
1410
- Processing: 102,
1411
- EarlyHints: 103,
1412
- Ok: 200,
1413
- Created: 201,
1414
- Accepted: 202,
1415
- NonAuthoritativeInformation: 203,
1416
- NoContent: 204,
1417
- ResetContent: 205,
1418
- PartialContent: 206,
1419
- MultiStatus: 207,
1420
- AlreadyReported: 208,
1421
- IMUsed: 226,
1422
- MultipleChoices: 300,
1423
- MovedPermanently: 301,
1424
- Found: 302,
1425
- SeeOther: 303,
1426
- NotModified: 304,
1427
- UseProxy: 305,
1428
- TemporaryRedirect: 307,
1429
- PermanentRedirect: 308,
1430
- BadRequest: 400,
1431
- Unauthorized: 401,
1432
- PaymentRequired: 402,
1433
- Forbidden: 403,
1434
- NotFound: 404,
1435
- MethodNotAllowed: 405,
1436
- NotAcceptable: 406,
1437
- ProxyAuthenticationRequired: 407,
1438
- RequestTimeout: 408,
1439
- Conflict: 409,
1440
- Gone: 410,
1441
- LengthRequired: 411,
1442
- PreconditionFailed: 412,
1443
- PayloadTooLarge: 413,
1444
- URITooLong: 414,
1445
- UnsupportedMediaType: 415,
1446
- RangeNotSatisfiable: 416,
1447
- ExpectationFailed: 417,
1448
- ImATeapot: 418,
1449
- MisdirectedRequest: 421,
1450
- UnprocessableEntity: 422,
1451
- Locked: 423,
1452
- FailedDependency: 424,
1453
- TooEarly: 425,
1454
- UpgradeRequired: 426,
1455
- PreconditionRequired: 428,
1456
- TooManyRequests: 429,
1457
- RequestHeaderFieldsTooLarge: 431,
1458
- UnavailableForLegalReasons: 451,
1459
- InternalServerError: 500,
1460
- NotImplemented: 501,
1461
- BadGateway: 502,
1462
- ServiceUnavailable: 503,
1463
- GatewayTimeout: 504,
1464
- HTTPVersionNotSupported: 505,
1465
- VariantAlsoNegotiates: 506,
1466
- InsufficientStorage: 507,
1467
- LoopDetected: 508,
1468
- NotExtended: 510,
1469
- NetworkAuthenticationRequired: 511
1470
- };
1471
-
1472
- // src/cookies/serializer.ts
1473
- var CookieSerializer = class {
1474
- /**
1475
- * Cookie client instance for handling cookie operations
1476
- */
1477
- #client;
1478
- /**
1479
- * Create a new instance of CookieSerializer
1480
- *
1481
- * @param encryption - The encryption instance for cookie operations
1482
- */
1483
- constructor(encryption) {
1484
- this.#client = new CookieClient(encryption);
1485
- }
1486
- /**
1487
- * Encodes value as a plain cookie. By default, the plain value will be converted
1488
- * to a string using "JSON.stringify" method and then encoded as a base64 string.
1489
- *
1490
- * You can disable cookie stringifaction by setting `options.stringify = false`.
1491
- *
1492
- * ```ts
1493
- * serializer.encode('name', 'virk')
1494
- * serializer.encode('name', 'virk', { stringify: false })
1495
- * ```
1496
- *
1497
- * @param key - The cookie key
1498
- * @param value - The value to encode
1499
- * @param options - Cookie encoding options
1500
- * @returns The serialized cookie string or null if encoding fails
1501
- */
1502
- encode(key, value, options) {
1503
- const stringify2 = options?.stringify ?? options?.encode;
1504
- const packedValue = this.#client.encode(key, value, stringify2);
1505
- if (packedValue === null || packedValue === void 0) {
1506
- return null;
1507
- }
1508
- return serializeCookie(key, packedValue, options);
1509
- }
1510
- /**
1511
- * Sign a key-value pair to a signed cookie. The signed value has a
1512
- * verification hash attached to it to detect data tampering.
1513
- *
1514
- * @param key - The cookie key
1515
- * @param value - The value to sign
1516
- * @param options - Cookie options
1517
- * @returns The serialized signed cookie string or null if signing fails
1518
- */
1519
- sign(key, value, options) {
1520
- const packedValue = this.#client.sign(key, value);
1521
- if (packedValue === null) {
1522
- return null;
1523
- }
1524
- return serializeCookie(key, packedValue, options);
1525
- }
1526
- /**
1527
- * Encrypts a key-value pair to an encrypted cookie.
1528
- *
1529
- * @param key - The cookie key
1530
- * @param value - The value to encrypt
1531
- * @param options - Cookie options
1532
- * @returns The serialized encrypted cookie string or null if encryption fails
1533
- */
1534
- encrypt(key, value, options) {
1535
- const packedValue = this.#client.encrypt(key, value);
1536
- if (packedValue === null) {
1537
- return null;
1538
- }
1539
- return serializeCookie(key, packedValue, options);
1540
- }
1541
- };
1542
-
1543
- // src/response.ts
1544
- import etag from "etag";
1545
- import vary from "vary";
1546
- import fresh2 from "fresh";
1547
- import destroy from "destroy";
1548
- import { extname } from "path";
1549
- import { Buffer } from "buffer";
1550
- import onFinished from "on-finished";
1551
- import { stat } from "fs/promises";
1552
- import Macroable2 from "@poppinss/macroable";
1553
- import { createReadStream } from "fs";
1554
- import contentDisposition from "content-disposition";
1555
- import { safeStringify } from "@poppinss/utils/json";
1556
- import { RuntimeException } from "@poppinss/utils/exception";
1557
- var CACHEABLE_HTTP_METHODS = ["GET", "HEAD"];
1558
- var Response = class extends Macroable2 {
1559
- /**
1560
- * Creates a new Response instance
1561
- *
1562
- * @param request - Node.js IncomingMessage instance
1563
- * @param response - Node.js ServerResponse instance
1564
- * @param encryption - Encryption service for cookie handling
1565
- * @param config - Response configuration settings
1566
- * @param router - Router instance for URL generation
1567
- * @param qs - Query string parser
1568
- */
1569
- constructor(request, response, encryption, config, router, qs) {
1570
- super();
1571
- this.request = request;
1572
- this.response = response;
1573
- this.#qs = qs;
1574
- this.#config = config;
1575
- this.#router = router;
1576
- this.#cookieSerializer = new CookieSerializer(encryption);
1577
- }
1578
- /**
1579
- * Query string parser instance used for URL manipulation
1580
- */
1581
- #qs;
1582
- /**
1583
- * Collection of outgoing HTTP headers to be sent with the response
1584
- */
1585
- #headers = {};
1586
- /**
1587
- * Flag indicating whether an explicit status code has been set
1588
- */
1589
- #hasExplicitStatus = false;
1590
- /**
1591
- * Cookie serializer instance for handling cookie encryption, signing, and encoding
1592
- */
1593
- #cookieSerializer;
1594
- /**
1595
- * Router instance used for generating redirect URLs from route definitions
1596
- */
1597
- #router;
1598
- /**
1599
- * Configuration object containing response-related settings
1600
- */
1601
- #config;
1602
- /**
1603
- * Indicates whether the response has any content (body, stream, or file) ready to be sent
1604
- */
1605
- get hasLazyBody() {
1606
- return !!(this.lazyBody.content || this.lazyBody.fileToStream || this.lazyBody.stream);
1607
- }
1608
- /**
1609
- * Indicates whether the response has non-stream content set
1610
- */
1611
- get hasContent() {
1612
- return !!this.lazyBody.content;
1613
- }
1614
- /**
1615
- * Indicates whether the response body is set as a readable stream
1616
- */
1617
- get hasStream() {
1618
- return !!this.lazyBody.stream;
1619
- }
1620
- /**
1621
- * Indicates whether the response is configured to stream a file
1622
- */
1623
- get hasFileToStream() {
1624
- return !!this.lazyBody.fileToStream;
1625
- }
1626
- /**
1627
- * The response content data
1628
- */
1629
- get content() {
1630
- return this.lazyBody.content;
1631
- }
1632
- /**
1633
- * The readable stream instance configured for the response
1634
- */
1635
- get outgoingStream() {
1636
- return this.lazyBody.stream?.[0];
1637
- }
1638
- /**
1639
- * Configuration for file streaming including path and etag generation flag
1640
- */
1641
- get fileToStream() {
1642
- return this.lazyBody.fileToStream ? {
1643
- path: this.lazyBody.fileToStream[0],
1644
- generateEtag: this.lazyBody.fileToStream[1]
1645
- } : void 0;
1646
- }
1647
- /**
1648
- * Lazy body container that holds response content until ready to send.
1649
- * Contains different types of response data: content, stream, or fileToStream.
1650
- */
1651
- lazyBody = {};
1652
- /**
1653
- * HTTP context reference (creates circular dependency with HttpContext)
1654
- */
1655
- ctx;
1656
- /**
1657
- * Indicates whether the response has been completely sent
1658
- */
1659
- get finished() {
1660
- return this.response.writableFinished;
1661
- }
1662
- /**
1663
- * Indicates whether response headers have been sent to the client
1664
- */
1665
- get headersSent() {
1666
- return this.response.headersSent;
1667
- }
1668
- /**
1669
- * Indicates whether the response is still pending (headers and body can still be modified)
1670
- */
1671
- get isPending() {
1672
- return !this.headersSent && !this.finished;
1673
- }
1674
- /**
1675
- * Normalizes header value to a string or an array of strings
1676
- *
1677
- * @param value - The header value to normalize
1678
- * @returns Normalized header value
1679
- */
1680
- #castHeaderValue(value) {
1681
- return Array.isArray(value) ? value.map(String) : String(value);
1682
- }
1683
- /**
1684
- * Ends the response by flushing headers and writing body
1685
- *
1686
- * @param body - Optional response body
1687
- * @param statusCode - Optional status code
1688
- */
1689
- #endResponse(body, statusCode) {
1690
- this.writeHead(statusCode);
1691
- const res = this.response;
1692
- res.end(body, null, null);
1693
- }
1694
- /**
1695
- * Determines the data type of the content for serialization
1696
- *
1697
- * Supported types:
1698
- * - Dates
1699
- * - Arrays
1700
- * - Booleans
1701
- * - Objects
1702
- * - Strings
1703
- * - Buffer
1704
- *
1705
- * @param content - The content to analyze
1706
- * @returns The determined data type as string
1707
- */
1708
- #getDataType(content) {
1709
- const dataType = typeof content;
1710
- if (dataType === "number" || dataType === "boolean" || dataType === "string" || dataType === "bigint") {
1711
- return dataType;
1712
- }
1713
- if (dataType === "object") {
1714
- if (content instanceof Uint8Array) {
1715
- return "buffer";
1716
- }
1717
- if (content instanceof RegExp) {
1718
- return "regexp";
1719
- }
1720
- if (content instanceof Date) {
1721
- return "date";
1722
- }
1723
- return "object";
1724
- }
1725
- throw new RuntimeException(`Cannot serialize "${dataType}" to HTTP response`);
1726
- }
1727
- /**
1728
- * Writes the response body with appropriate headers and content type detection
1729
- *
1730
- * Automatically sets:
1731
- * - Content-Type based on content analysis
1732
- * - Content-Length header
1733
- * - ETag header (if enabled)
1734
- * - Status code 204 for empty bodies
1735
- *
1736
- * @param content - The response content
1737
- * @param generateEtag - Whether to generate ETag header
1738
- * @param jsonpCallbackName - Optional JSONP callback name
1739
- */
1740
- writeBody(content, generateEtag, jsonpCallbackName) {
1741
- const hasEmptyBody = content === null || content === void 0 || content === "";
1742
- if (hasEmptyBody) {
1743
- this.safeStatus(204);
1744
- }
1745
- const statusCode = this.response.statusCode;
1746
- if (statusCode && (statusCode < ResponseStatus.Ok || statusCode === ResponseStatus.NoContent || statusCode === ResponseStatus.NotModified)) {
1747
- this.removeHeader("Content-Type");
1748
- this.removeHeader("Content-Length");
1749
- this.removeHeader("Transfer-Encoding");
1750
- this.#endResponse();
1751
- return;
1752
- }
1753
- if (hasEmptyBody) {
1754
- this.removeHeader("Content-Length");
1755
- this.#endResponse();
1756
- return;
1757
- }
1758
- const dataType = this.#getDataType(content);
1759
- let contentType;
1760
- switch (dataType) {
1761
- case "string":
1762
- contentType = content.trimStart().startsWith("<") ? "text/html; charset=utf-8" : "text/plain; charset=utf-8";
1763
- break;
1764
- case "number":
1765
- case "boolean":
1766
- case "bigint":
1767
- case "regexp":
1768
- content = String(content);
1769
- contentType = "text/plain; charset=utf-8";
1770
- break;
1771
- case "date":
1772
- content = content.toISOString();
1773
- contentType = "text/plain; charset=utf-8";
1774
- break;
1775
- case "buffer":
1776
- contentType = "application/octet-stream; charset=utf-8";
1777
- break;
1778
- case "object":
1779
- content = safeStringify(content);
1780
- contentType = "application/json; charset=utf-8";
1781
- break;
1782
- }
1783
- if (jsonpCallbackName) {
1784
- content = content.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1785
- content = `/**/ typeof ${jsonpCallbackName} === 'function' && ${jsonpCallbackName}(${content});`;
1786
- }
1787
- if (generateEtag) {
1788
- this.setEtag(content);
1789
- }
1790
- if (generateEtag && this.fresh()) {
1791
- this.removeHeader("Content-Type");
1792
- this.removeHeader("Content-Length");
1793
- this.removeHeader("Transfer-Encoding");
1794
- this.#endResponse(null, ResponseStatus.NotModified);
1795
- return;
1796
- }
1797
- this.setRequestId();
1798
- this.header("Content-Length", Buffer.byteLength(content));
1799
- if (jsonpCallbackName) {
1800
- this.header("X-Content-Type-Options", "nosniff");
1801
- this.safeHeader("Content-Type", "text/javascript; charset=utf-8");
1802
- } else {
1803
- this.safeHeader("Content-type", contentType);
1804
- }
1805
- this.#endResponse(content);
1806
- }
1807
- /**
1808
- * Streams the response body and handles error cleanup
1809
- *
1810
- * Manages stream lifecycle including:
1811
- * - Error handling with custom callbacks
1812
- * - Proper stream cleanup to prevent memory leaks
1813
- * - Response finalization
1814
- *
1815
- * @param body - The readable stream to pipe
1816
- * @param errorCallback - Optional custom error handler
1817
- * @returns Promise that resolves when streaming is complete
1818
- */
1819
- streamBody(body, errorCallback) {
1820
- return new Promise((resolve) => {
1821
- let finished = false;
1822
- body.on("error", (error) => {
1823
- if (finished) {
1824
- return;
1825
- }
1826
- finished = true;
1827
- destroy(body);
1828
- this.type("text");
1829
- if (!this.headersSent) {
1830
- if (typeof errorCallback === "function") {
1831
- this.#endResponse(...errorCallback(error));
1832
- } else {
1833
- this.#endResponse(
1834
- error.code === "ENOENT" ? "File not found" : "Cannot process file",
1835
- error.code === "ENOENT" ? ResponseStatus.NotFound : ResponseStatus.InternalServerError
1836
- );
1837
- }
1838
- } else {
1839
- this.response.destroy();
1840
- }
1841
- resolve();
1842
- });
1843
- body.on("end", () => {
1844
- if (!this.headersSent) {
1845
- this.#endResponse();
1846
- }
1847
- resolve();
1848
- });
1849
- onFinished(this.response, () => {
1850
- finished = true;
1851
- destroy(body);
1852
- });
1853
- this.relayHeaders();
1854
- body.pipe(this.response);
1855
- });
1856
- }
1857
- /**
1858
- * Streams a file for download with proper headers and caching support
1859
- *
1860
- * Sets appropriate headers:
1861
- * - Last-Modified based on file stats
1862
- * - Content-Type based on file extension
1863
- * - Content-Length from file size
1864
- * - ETag (if enabled)
1865
- *
1866
- * Handles HEAD requests and cache validation (304 responses).
1867
- *
1868
- * @param filePath - Path to the file to stream
1869
- * @param generateEtag - Whether to generate ETag header
1870
- * @param errorCallback - Optional custom error handler
1871
- */
1872
- async streamFileForDownload(filePath, generateEtag, errorCallback) {
1873
- try {
1874
- const stats = await stat(filePath);
1875
- if (!stats || !stats.isFile()) {
1876
- throw new TypeError("response.download only accepts path to a file");
1877
- }
1878
- this.header("Last-Modified", stats.mtime.toUTCString());
1879
- this.type(extname(filePath));
1880
- if (generateEtag) {
1881
- this.setEtag(stats, true);
1882
- }
1883
- if (this.request.method === "HEAD") {
1884
- this.#endResponse(
1885
- null,
1886
- generateEtag && this.fresh() ? ResponseStatus.NotModified : ResponseStatus.Ok
1887
- );
1888
- return;
1889
- }
1890
- if (generateEtag && this.fresh()) {
1891
- this.#endResponse(null, ResponseStatus.NotModified);
1892
- return;
1893
- }
1894
- this.header("Content-length", stats.size);
1895
- return this.streamBody(createReadStream(filePath), errorCallback);
1896
- } catch (error) {
1897
- this.type("text");
1898
- this.removeHeader("Etag");
1899
- if (typeof errorCallback === "function") {
1900
- this.#endResponse(...errorCallback(error));
1901
- } else {
1902
- this.#endResponse(
1903
- error.code === "ENOENT" ? "File not found" : "Cannot process file",
1904
- error.code === "ENOENT" ? ResponseStatus.NotFound : ResponseStatus.InternalServerError
1905
- );
1906
- }
1907
- }
1908
- }
1909
- /**
1910
- * Registers a callback to be called when the response is finished
1911
- *
1912
- * The callback is executed when the response has been completely sent.
1913
- * Uses the "on-finished" package internally.
1914
- *
1915
- * @param callback - Function to call when response is finished
1916
- */
1917
- onFinish(callback) {
1918
- onFinished(this.response, callback);
1919
- }
1920
- /**
1921
- * Transfers all buffered headers to the underlying Node.js response object
1922
- */
1923
- relayHeaders() {
1924
- if (!this.headersSent) {
1925
- for (let key in this.#headers) {
1926
- const value = this.#headers[key];
1927
- if (value) {
1928
- this.response.setHeader(key, value);
1929
- }
1930
- }
1931
- }
1932
- }
1933
- /**
1934
- * Writes the response status code and headers
1935
- *
1936
- * @param statusCode - Optional status code to set
1937
- * @returns The Response instance for chaining
1938
- */
1939
- writeHead(statusCode) {
1940
- this.response.writeHead(statusCode || this.response.statusCode, this.#headers);
1941
- return this;
1942
- }
1943
- /**
1944
- * Gets the value of a response header
1945
- *
1946
- * @param key - Header name
1947
- * @returns The header value
1948
- */
1949
- getHeader(key) {
1950
- const value = this.#headers[key.toLowerCase()];
1951
- return value === void 0 ? this.response.getHeader(key) : value;
1952
- }
1953
- /**
1954
- * Gets all response headers as an object
1955
- *
1956
- * @returns Object containing all headers
1957
- */
1958
- getHeaders() {
1959
- return {
1960
- ...this.response.getHeaders(),
1961
- ...this.#headers
1962
- };
1963
- }
1964
- /**
1965
- * Sets a response header (replaces existing value)
1966
- *
1967
- * @param key - Header name
1968
- * @param value - Header value (ignored if null/undefined)
1969
- * @returns The Response instance for chaining
1970
- *
1971
- * @example
1972
- * ```ts
1973
- * response.header('Content-Type', 'application/json')
1974
- * ```
1975
- */
1976
- header(key, value) {
1977
- if (value === null || value === void 0) {
1978
- return this;
1979
- }
1980
- this.#headers[key.toLowerCase()] = this.#castHeaderValue(value);
1981
- return this;
1982
- }
1983
- /**
1984
- * Appends a value to an existing response header
1985
- *
1986
- * @param key - Header name
1987
- * @param value - Header value to append (ignored if null/undefined)
1988
- * @returns The Response instance for chaining
1989
- *
1990
- * @example
1991
- * ```ts
1992
- * response.append('Set-Cookie', 'session=abc123')
1993
- * ```
1994
- */
1995
- append(key, value) {
1996
- if (value === null || value === void 0) {
1997
- return this;
1998
- }
1999
- key = key.toLowerCase();
2000
- let existingHeader = this.getHeader(key);
2001
- let casted = this.#castHeaderValue(value);
2002
- if (!existingHeader) {
2003
- this.#headers[key] = casted;
2004
- return this;
2005
- }
2006
- existingHeader = this.#castHeaderValue(existingHeader);
2007
- casted = Array.isArray(existingHeader) ? existingHeader.concat(casted) : [existingHeader].concat(casted);
2008
- this.#headers[key] = casted;
2009
- return this;
2010
- }
2011
- /**
2012
- * Sets a header only if it doesn't already exist
2013
- *
2014
- * @param key - Header name
2015
- * @param value - Header value
2016
- * @returns The Response instance for chaining
2017
- */
2018
- safeHeader(key, value) {
2019
- if (!this.getHeader(key)) {
2020
- this.header(key, value);
2021
- }
2022
- return this;
2023
- }
2024
- /**
2025
- * Removes a response header
2026
- *
2027
- * @param key - Header name to remove
2028
- * @returns The Response instance for chaining
2029
- */
2030
- removeHeader(key) {
2031
- key = key.toLowerCase();
2032
- this.response.removeHeader(key);
2033
- if (this.#headers[key]) {
2034
- delete this.#headers[key.toLowerCase()];
2035
- }
2036
- return this;
2037
- }
2038
- /**
2039
- * Gets the current response status code
2040
- *
2041
- * @returns The HTTP status code
2042
- */
2043
- getStatus() {
2044
- return this.response.statusCode;
2045
- }
2046
- /**
2047
- * Sets the response status code
2048
- *
2049
- * @param code - HTTP status code
2050
- * @returns The Response instance for chaining
2051
- */
2052
- status(code) {
2053
- this.#hasExplicitStatus = true;
2054
- this.response.statusCode = code;
2055
- return this;
2056
- }
2057
- /**
2058
- * Sets the status code only if not explicitly set already
2059
- *
2060
- * @param code - HTTP status code
2061
- * @returns The Response instance for chaining
2062
- */
2063
- safeStatus(code) {
2064
- if (this.#hasExplicitStatus) {
2065
- return this;
2066
- }
2067
- this.response.statusCode = code;
2068
- return this;
2069
- }
2070
- /**
2071
- * Sets the Content-Type header based on mime type lookup
2072
- *
2073
- * @param type - File extension or mime type
2074
- * @param charset - Optional character encoding
2075
- * @returns The Response instance for chaining
2076
- *
2077
- * @example
2078
- * ```ts
2079
- * response.type('.json') // Content-Type: application/json
2080
- * response.type('html', 'utf-8') // Content-Type: text/html; charset=utf-8
2081
- * ```
2082
- */
2083
- type(type, charset) {
2084
- type = charset ? `${type}; charset=${charset}` : type;
2085
- this.header("Content-Type", default3.contentType(type));
2086
- return this;
2087
- }
2088
- /**
2089
- * Sets the Vary HTTP header for cache control
2090
- *
2091
- * @param field - Header field name(s) to vary on
2092
- * @returns The Response instance for chaining
2093
- */
2094
- vary(field) {
2095
- vary(this.response, field);
2096
- return this;
2097
- }
2098
- /**
2099
- * Sets the ETag header by computing a hash from the response body
2100
- *
2101
- * @param body - The response body to hash
2102
- * @param weak - Whether to generate a weak ETag
2103
- * @returns The Response instance for chaining
2104
- */
2105
- setEtag(body, weak = false) {
2106
- this.header("Etag", etag(body, { weak }));
2107
- return this;
2108
- }
2109
- /**
2110
- * Sets the X-Request-Id header by copying from the incoming request
2111
- *
2112
- * @returns The Response instance for chaining
2113
- */
2114
- setRequestId() {
2115
- const requestId = this.request.headers["x-request-id"];
2116
- if (requestId) {
2117
- this.header("X-Request-Id", requestId);
2118
- }
2119
- return this;
2120
- }
2121
- /**
2122
- * Checks if the response is fresh (client cache is valid)
2123
- *
2124
- * Compares ETags and modified dates between request and response
2125
- * to determine if a 304 Not Modified response should be sent.
2126
- *
2127
- * @returns True if client cache is fresh, false otherwise
2128
- *
2129
- * @example
2130
- * ```ts
2131
- * response.setEtag(content)
2132
- * if (response.fresh()) {
2133
- * response.status(304).send(null)
2134
- * } else {
2135
- * response.send(content)
2136
- * }
2137
- * ```
2138
- */
2139
- fresh() {
2140
- if (this.request.method && !CACHEABLE_HTTP_METHODS.includes(this.request.method)) {
2141
- return false;
2142
- }
2143
- const status = this.response.statusCode;
2144
- if (status >= ResponseStatus.Ok && status < ResponseStatus.MultipleChoices || status === ResponseStatus.NotModified) {
2145
- return fresh2(this.request.headers, this.#headers);
2146
- }
2147
- return false;
2148
- }
2149
- /**
2150
- * Gets the response body content
2151
- *
2152
- * @returns The response body or null if not set or is a stream
2153
- */
2154
- getBody() {
2155
- if (this.lazyBody.content) {
2156
- return this.lazyBody.content[0];
2157
- }
2158
- return null;
2159
- }
2160
- /**
2161
- * Sends the response body with optional ETag generation
2162
- *
2163
- * @param body - The response body
2164
- * @param generateEtag - Whether to generate ETag header (defaults to config)
2165
- */
2166
- send(body, generateEtag = this.#config.etag) {
2167
- this.lazyBody.content = [body, generateEtag];
2168
- }
2169
- /**
2170
- * Sends a JSON response (alias for send)
2171
- *
2172
- * @param body - The response body to serialize as JSON
2173
- * @param generateEtag - Whether to generate ETag header
2174
- */
2175
- json(body, generateEtag = this.#config.etag) {
2176
- return this.send(body, generateEtag);
2177
- }
2178
- /**
2179
- * Sends a JSONP response with callback wrapping
2180
- *
2181
- * Callback name resolution priority:
2182
- * 1. Explicit callbackName parameter
2183
- * 2. Query string parameter
2184
- * 3. Config value
2185
- * 4. Default "callback"
2186
- *
2187
- * @param body - The response body
2188
- * @param callbackName - JSONP callback function name
2189
- * @param generateEtag - Whether to generate ETag header
2190
- */
2191
- jsonp(body, callbackName = this.#config.jsonpCallbackName, generateEtag = this.#config.etag) {
2192
- this.lazyBody.content = [body, generateEtag, callbackName];
2193
- }
2194
- /**
2195
- * Pipes a readable stream to the response with graceful error handling
2196
- *
2197
- * @param body - The readable stream to pipe
2198
- * @param errorCallback - Optional custom error handler
2199
- *
2200
- * @example
2201
- * ```ts
2202
- * // Auto error handling
2203
- * response.stream(fs.createReadStream('file.txt'))
2204
- *
2205
- * // Custom error handling
2206
- * response.stream(stream, (error) => {
2207
- * return error.code === 'ENOENT' ? ['Not found', 404] : ['Error', 500]
2208
- * })
2209
- * ```
2210
- */
2211
- stream(body, errorCallback) {
2212
- if (typeof body.pipe !== "function" || !body.readable || typeof body.read !== "function") {
2213
- throw new TypeError("response.stream accepts a readable stream only");
2214
- }
2215
- this.lazyBody.stream = [body, errorCallback];
2216
- }
2217
- /**
2218
- * Downloads a file by streaming it with appropriate headers
2219
- *
2220
- * Automatically sets:
2221
- * - Content-Type from file extension
2222
- * - Content-Length from file size
2223
- * - Last-Modified from file stats
2224
- * - ETag (if enabled)
2225
- *
2226
- * @param filePath - Path to the file to download
2227
- * @param generateEtag - Whether to generate ETag header
2228
- * @param errorCallback - Optional custom error handler
2229
- *
2230
- * @example
2231
- * ```ts
2232
- * response.download('/path/to/file.pdf')
2233
- * response.download('/images/photo.jpg', true, (err) => ['Custom error', 500])
2234
- * ```
2235
- */
2236
- download(filePath, generateEtag = this.#config.etag, errorCallback) {
2237
- this.lazyBody.fileToStream = [filePath, generateEtag, errorCallback];
2238
- }
2239
- /**
2240
- * Forces file download by setting Content-Disposition header
2241
- *
2242
- * @param filePath - Path to the file to download
2243
- * @param name - Optional filename for download (defaults to original filename)
2244
- * @param disposition - Content-Disposition type (defaults to 'attachment')
2245
- * @param generateEtag - Whether to generate ETag header
2246
- * @param errorCallback - Optional custom error handler
2247
- */
2248
- attachment(filePath, name, disposition, generateEtag, errorCallback) {
2249
- name = name || filePath;
2250
- this.header("Content-Disposition", contentDisposition(name, { type: disposition }));
2251
- return this.download(filePath, generateEtag, errorCallback);
2252
- }
2253
- /**
2254
- * Sets the Location header for redirects
2255
- *
2256
- * @param url - The URL to redirect to
2257
- * @returns The Response instance for chaining
2258
- *
2259
- * @example
2260
- * ```ts
2261
- * response.location('/dashboard')
2262
- * ```
2263
- */
2264
- location(url) {
2265
- this.header("Location", url);
2266
- return this;
2267
- }
2268
- redirect(path, forwardQueryString = false, statusCode = ResponseStatus.Found) {
2269
- const handler = new Redirect(this.request, this, this.#router, this.#qs);
2270
- if (forwardQueryString) {
2271
- handler.withQs();
2272
- }
2273
- if (path === "back") {
2274
- return handler.status(statusCode).back();
2275
- }
2276
- if (path) {
2277
- return handler.status(statusCode).toPath(path);
2278
- }
2279
- return handler;
2280
- }
2281
- /**
2282
- * Aborts the request with a custom response body and status code
2283
- *
2284
- * @param body - Response body for the aborted request
2285
- * @param status - HTTP status code (defaults to 400)
2286
- * @throws Always throws an HTTP exception
2287
- */
2288
- abort(body, status) {
2289
- throw E_HTTP_REQUEST_ABORTED.invoke(body, status || ResponseStatus.BadRequest);
2290
- }
2291
- /**
2292
- * Conditionally aborts the request if the condition is truthy
2293
- *
2294
- * @param condition - Condition to evaluate
2295
- * @param body - Response body for the aborted request
2296
- * @param status - HTTP status code (defaults to 400)
2297
- */
2298
- abortIf(condition, body, status) {
2299
- if (condition) {
2300
- this.abort(body, status);
2301
- }
2302
- }
2303
- /**
2304
- * Conditionally aborts the request if the condition is falsy
2305
- *
2306
- * @param condition - Condition to evaluate
2307
- * @param body - Response body for the aborted request
2308
- * @param status - HTTP status code (defaults to 400)
2309
- */
2310
- abortUnless(condition, body, status) {
2311
- if (!condition) {
2312
- this.abort(body, status);
2313
- }
2314
- }
2315
- /**
2316
- * Sets a signed cookie in the response
2317
- *
2318
- * @param key - Cookie name
2319
- * @param value - Cookie value
2320
- * @param options - Cookie options (overrides config defaults)
2321
- * @returns The Response instance for chaining
2322
- */
2323
- cookie(key, value, options) {
2324
- options = Object.assign({}, this.#config.cookie, options);
2325
- const serialized = this.#cookieSerializer.sign(key, value, options);
2326
- if (!serialized) {
2327
- return this;
2328
- }
2329
- this.append("set-cookie", serialized);
2330
- return this;
2331
- }
2332
- /**
2333
- * Sets an encrypted cookie in the response
2334
- *
2335
- * @param key - Cookie name
2336
- * @param value - Cookie value
2337
- * @param options - Cookie options (overrides config defaults)
2338
- * @returns The Response instance for chaining
2339
- */
2340
- encryptedCookie(key, value, options) {
2341
- options = Object.assign({}, this.#config.cookie, options);
2342
- const serialized = this.#cookieSerializer.encrypt(key, value, options);
2343
- if (!serialized) {
2344
- return this;
2345
- }
2346
- this.append("set-cookie", serialized);
2347
- return this;
2348
- }
2349
- /**
2350
- * Sets a plain (unsigned/unencrypted) cookie in the response
2351
- *
2352
- * @param key - Cookie name
2353
- * @param value - Cookie value
2354
- * @param options - Cookie options including encode flag
2355
- * @returns The Response instance for chaining
2356
- */
2357
- plainCookie(key, value, options) {
2358
- options = Object.assign({}, this.#config.cookie, options);
2359
- const serialized = this.#cookieSerializer.encode(key, value, options);
2360
- if (!serialized) {
2361
- return this;
2362
- }
2363
- this.append("set-cookie", serialized);
2364
- return this;
2365
- }
2366
- /**
2367
- * Clears an existing cookie by setting it to expire
2368
- *
2369
- * @param key - Cookie name to clear
2370
- * @param options - Cookie options (should match original cookie options)
2371
- * @returns The Response instance for chaining
2372
- */
2373
- clearCookie(key, options) {
2374
- options = Object.assign({}, this.#config.cookie, options);
2375
- options.expires = /* @__PURE__ */ new Date(1);
2376
- options.maxAge = -1;
2377
- const serialized = this.#cookieSerializer.encode(key, "", { ...options, encode: false });
2378
- this.append("set-cookie", serialized);
2379
- return this;
2380
- }
2381
- /**
2382
- * Finalizes and sends the response
2383
- *
2384
- * Writes the buffered body (content, stream, or file) to the client.
2385
- * This method is idempotent - calling it multiple times has no effect.
2386
- */
2387
- finish() {
2388
- if (!this.isPending) {
2389
- return;
2390
- }
2391
- if (this.content) {
2392
- httpResponseSerializer.traceSync(this.writeBody, void 0, this, ...this.content);
2393
- return;
2394
- }
2395
- if (this.lazyBody.stream) {
2396
- this.streamBody(...this.lazyBody.stream);
2397
- return;
2398
- }
2399
- if (this.lazyBody.fileToStream) {
2400
- this.streamFileForDownload(...this.lazyBody.fileToStream);
2401
- return;
2402
- }
2403
- this.#endResponse();
2404
- }
2405
- /**
2406
- * Sends a 100 Continue response
2407
- */
2408
- continue() {
2409
- this.status(ResponseStatus.Continue);
2410
- return this.send(null, false);
2411
- }
2412
- /**
2413
- * Sends a 101 Switching Protocols response
2414
- */
2415
- switchingProtocols() {
2416
- this.status(ResponseStatus.SwitchingProtocols);
2417
- return this.send(null, false);
2418
- }
2419
- /**
2420
- * Sends a 200 OK response
2421
- *
2422
- * @param body - Response body
2423
- * @param generateEtag - Whether to generate ETag header
2424
- */
2425
- ok(body, generateEtag) {
2426
- this.status(ResponseStatus.Ok);
2427
- return this.send(body, generateEtag);
2428
- }
2429
- /**
2430
- * Sends a 201 Created response
2431
- *
2432
- * @param body - Response body
2433
- * @param generateEtag - Whether to generate ETag header
2434
- */
2435
- created(body, generateEtag) {
2436
- this.status(ResponseStatus.Created);
2437
- return this.send(body, generateEtag);
2438
- }
2439
- /**
2440
- * Sends a 202 Accepted response
2441
- *
2442
- * @param body - Response body
2443
- * @param generateEtag - Whether to generate ETag header
2444
- */
2445
- accepted(body, generateEtag) {
2446
- this.status(ResponseStatus.Accepted);
2447
- return this.send(body, generateEtag);
2448
- }
2449
- /**
2450
- * Sends a 203 Non-Authoritative Information response
2451
- *
2452
- * @param body - Response body
2453
- * @param generateEtag - Whether to generate ETag header
2454
- */
2455
- nonAuthoritativeInformation(body, generateEtag) {
2456
- this.status(ResponseStatus.NonAuthoritativeInformation);
2457
- return this.send(body, generateEtag);
2458
- }
2459
- /**
2460
- * Sends a 204 No Content response
2461
- */
2462
- noContent() {
2463
- this.status(ResponseStatus.NoContent);
2464
- return this.send(null, false);
2465
- }
2466
- /**
2467
- * Sends a 205 Reset Content response
2468
- */
2469
- resetContent() {
2470
- this.status(ResponseStatus.ResetContent);
2471
- return this.send(null, false);
2472
- }
2473
- /**
2474
- * Sends a 206 Partial Content response
2475
- *
2476
- * @param body - Response body
2477
- * @param generateEtag - Whether to generate ETag header
2478
- */
2479
- partialContent(body, generateEtag) {
2480
- this.status(ResponseStatus.PartialContent);
2481
- return this.send(body, generateEtag);
2482
- }
2483
- /**
2484
- * Sends a 300 Multiple Choices response
2485
- *
2486
- * @param body - Response body
2487
- * @param generateEtag - Whether to generate ETag header
2488
- */
2489
- multipleChoices(body, generateEtag) {
2490
- this.status(ResponseStatus.MultipleChoices);
2491
- return this.send(body, generateEtag);
2492
- }
2493
- /**
2494
- * Sends a 301 Moved Permanently response
2495
- *
2496
- * @param body - Response body
2497
- * @param generateEtag - Whether to generate ETag header
2498
- */
2499
- movedPermanently(body, generateEtag) {
2500
- this.status(ResponseStatus.MovedPermanently);
2501
- return this.send(body, generateEtag);
2502
- }
2503
- /**
2504
- * Sends a 302 Found (Moved Temporarily) response
2505
- *
2506
- * @param body - Response body
2507
- * @param generateEtag - Whether to generate ETag header
2508
- */
2509
- movedTemporarily(body, generateEtag) {
2510
- this.status(ResponseStatus.Found);
2511
- return this.send(body, generateEtag);
2512
- }
2513
- /**
2514
- * Sends a 303 See Other response
2515
- *
2516
- * @param body - Response body
2517
- * @param generateEtag - Whether to generate ETag header
2518
- */
2519
- seeOther(body, generateEtag) {
2520
- this.status(ResponseStatus.SeeOther);
2521
- return this.send(body, generateEtag);
2522
- }
2523
- /**
2524
- * Sends a 304 Not Modified response
2525
- *
2526
- * @param body - Response body
2527
- * @param generateEtag - Whether to generate ETag header
2528
- */
2529
- notModified(body, generateEtag) {
2530
- this.status(ResponseStatus.NotModified);
2531
- return this.send(body, generateEtag);
2532
- }
2533
- /**
2534
- * Sends a 305 Use Proxy response
2535
- *
2536
- * @param body - Response body
2537
- * @param generateEtag - Whether to generate ETag header
2538
- */
2539
- useProxy(body, generateEtag) {
2540
- this.status(ResponseStatus.UseProxy);
2541
- return this.send(body, generateEtag);
2542
- }
2543
- /**
2544
- * Sends a 307 Temporary Redirect response
2545
- *
2546
- * @param body - Response body
2547
- * @param generateEtag - Whether to generate ETag header
2548
- */
2549
- temporaryRedirect(body, generateEtag) {
2550
- this.status(ResponseStatus.TemporaryRedirect);
2551
- return this.send(body, generateEtag);
2552
- }
2553
- /**
2554
- * Sends a 400 Bad Request response
2555
- *
2556
- * @param body - Response body
2557
- * @param generateEtag - Whether to generate ETag header
2558
- */
2559
- badRequest(body, generateEtag) {
2560
- this.status(ResponseStatus.BadRequest);
2561
- return this.send(body, generateEtag);
2562
- }
2563
- /**
2564
- * Sends a 401 Unauthorized response
2565
- *
2566
- * @param body - Response body
2567
- * @param generateEtag - Whether to generate ETag header
2568
- */
2569
- unauthorized(body, generateEtag) {
2570
- this.status(ResponseStatus.Unauthorized);
2571
- return this.send(body, generateEtag);
2572
- }
2573
- /**
2574
- * Sends a 402 Payment Required response
2575
- *
2576
- * @param body - Response body
2577
- * @param generateEtag - Whether to generate ETag header
2578
- */
2579
- paymentRequired(body, generateEtag) {
2580
- this.status(ResponseStatus.PaymentRequired);
2581
- return this.send(body, generateEtag);
2582
- }
2583
- /**
2584
- * Sends a 403 Forbidden response
2585
- *
2586
- * @param body - Response body
2587
- * @param generateEtag - Whether to generate ETag header
2588
- */
2589
- forbidden(body, generateEtag) {
2590
- this.status(ResponseStatus.Forbidden);
2591
- return this.send(body, generateEtag);
2592
- }
2593
- /**
2594
- * Sends a 404 Not Found response
2595
- *
2596
- * @param body - Response body
2597
- * @param generateEtag - Whether to generate ETag header
2598
- */
2599
- notFound(body, generateEtag) {
2600
- this.status(ResponseStatus.NotFound);
2601
- return this.send(body, generateEtag);
2602
- }
2603
- /**
2604
- * Sends a 405 Method Not Allowed response
2605
- *
2606
- * @param body - Response body
2607
- * @param generateEtag - Whether to generate ETag header
2608
- */
2609
- methodNotAllowed(body, generateEtag) {
2610
- this.status(ResponseStatus.MethodNotAllowed);
2611
- return this.send(body, generateEtag);
2612
- }
2613
- /**
2614
- * Sends a 406 Not Acceptable response
2615
- *
2616
- * @param body - Response body
2617
- * @param generateEtag - Whether to generate ETag header
2618
- */
2619
- notAcceptable(body, generateEtag) {
2620
- this.status(ResponseStatus.NotAcceptable);
2621
- return this.send(body, generateEtag);
2622
- }
2623
- /**
2624
- * Sends a 407 Proxy Authentication Required response
2625
- *
2626
- * @param body - Response body
2627
- * @param generateEtag - Whether to generate ETag header
2628
- */
2629
- proxyAuthenticationRequired(body, generateEtag) {
2630
- this.status(ResponseStatus.ProxyAuthenticationRequired);
2631
- return this.send(body, generateEtag);
2632
- }
2633
- /**
2634
- * Sends a 408 Request Timeout response
2635
- *
2636
- * @param body - Response body
2637
- * @param generateEtag - Whether to generate ETag header
2638
- */
2639
- requestTimeout(body, generateEtag) {
2640
- this.status(ResponseStatus.RequestTimeout);
2641
- return this.send(body, generateEtag);
2642
- }
2643
- /**
2644
- * Sends a 409 Conflict response
2645
- *
2646
- * @param body - Response body
2647
- * @param generateEtag - Whether to generate ETag header
2648
- */
2649
- conflict(body, generateEtag) {
2650
- this.status(ResponseStatus.Conflict);
2651
- return this.send(body, generateEtag);
2652
- }
2653
- /**
2654
- * Sends a 410 Gone response
2655
- *
2656
- * @param body - Response body
2657
- * @param generateEtag - Whether to generate ETag header
2658
- */
2659
- gone(body, generateEtag) {
2660
- this.status(ResponseStatus.Gone);
2661
- return this.send(body, generateEtag);
2662
- }
2663
- /**
2664
- * Sends a 411 Length Required response
2665
- *
2666
- * @param body - Response body
2667
- * @param generateEtag - Whether to generate ETag header
2668
- */
2669
- lengthRequired(body, generateEtag) {
2670
- this.status(ResponseStatus.LengthRequired);
2671
- return this.send(body, generateEtag);
2672
- }
2673
- /**
2674
- * Sends a 412 Precondition Failed response
2675
- *
2676
- * @param body - Response body
2677
- * @param generateEtag - Whether to generate ETag header
2678
- */
2679
- preconditionFailed(body, generateEtag) {
2680
- this.status(ResponseStatus.PreconditionFailed);
2681
- return this.send(body, generateEtag);
2682
- }
2683
- /**
2684
- * Sends a 413 Payload Too Large response
2685
- *
2686
- * @param body - Response body
2687
- * @param generateEtag - Whether to generate ETag header
2688
- */
2689
- requestEntityTooLarge(body, generateEtag) {
2690
- this.status(ResponseStatus.PayloadTooLarge);
2691
- return this.send(body, generateEtag);
2692
- }
2693
- /**
2694
- * Sends a 414 URI Too Long response
2695
- *
2696
- * @param body - Response body
2697
- * @param generateEtag - Whether to generate ETag header
2698
- */
2699
- requestUriTooLong(body, generateEtag) {
2700
- this.status(ResponseStatus.URITooLong);
2701
- return this.send(body, generateEtag);
2702
- }
2703
- /**
2704
- * Sends a 415 Unsupported Media Type response
2705
- *
2706
- * @param body - Response body
2707
- * @param generateEtag - Whether to generate ETag header
2708
- */
2709
- unsupportedMediaType(body, generateEtag) {
2710
- this.status(ResponseStatus.UnsupportedMediaType);
2711
- return this.send(body, generateEtag);
2712
- }
2713
- /**
2714
- * Sends a 416 Range Not Satisfiable response
2715
- *
2716
- * @param body - Response body
2717
- * @param generateEtag - Whether to generate ETag header
2718
- */
2719
- requestedRangeNotSatisfiable(body, generateEtag) {
2720
- this.status(ResponseStatus.RangeNotSatisfiable);
2721
- return this.send(body, generateEtag);
2722
- }
2723
- /**
2724
- * Sends a 417 Expectation Failed response
2725
- *
2726
- * @param body - Response body
2727
- * @param generateEtag - Whether to generate ETag header
2728
- */
2729
- expectationFailed(body, generateEtag) {
2730
- this.status(ResponseStatus.ExpectationFailed);
2731
- return this.send(body, generateEtag);
2732
- }
2733
- /**
2734
- * Sends a 422 Unprocessable Entity response
2735
- *
2736
- * @param body - Response body
2737
- * @param generateEtag - Whether to generate ETag header
2738
- */
2739
- unprocessableEntity(body, generateEtag) {
2740
- this.status(ResponseStatus.UnprocessableEntity);
2741
- return this.send(body, generateEtag);
2742
- }
2743
- /**
2744
- * Sends a 429 Too Many Requests response
2745
- *
2746
- * @param body - Response body
2747
- * @param generateEtag - Whether to generate ETag header
2748
- */
2749
- tooManyRequests(body, generateEtag) {
2750
- this.status(ResponseStatus.TooManyRequests);
2751
- return this.send(body, generateEtag);
2752
- }
2753
- /**
2754
- * Sends a 500 Internal Server Error response
2755
- *
2756
- * @param body - Response body
2757
- * @param generateEtag - Whether to generate ETag header
2758
- */
2759
- internalServerError(body, generateEtag) {
2760
- this.status(ResponseStatus.InternalServerError);
2761
- return this.send(body, generateEtag);
2762
- }
2763
- /**
2764
- * Sends a 501 Not Implemented response
2765
- *
2766
- * @param body - Response body
2767
- * @param generateEtag - Whether to generate ETag header
2768
- */
2769
- notImplemented(body, generateEtag) {
2770
- this.status(ResponseStatus.NotImplemented);
2771
- return this.send(body, generateEtag);
2772
- }
2773
- /**
2774
- * Sends a 502 Bad Gateway response
2775
- *
2776
- * @param body - Response body
2777
- * @param generateEtag - Whether to generate ETag header
2778
- */
2779
- badGateway(body, generateEtag) {
2780
- this.status(ResponseStatus.BadGateway);
2781
- return this.send(body, generateEtag);
2782
- }
2783
- /**
2784
- * Sends a 503 Service Unavailable response
2785
- *
2786
- * @param body - Response body
2787
- * @param generateEtag - Whether to generate ETag header
2788
- */
2789
- serviceUnavailable(body, generateEtag) {
2790
- this.status(ResponseStatus.ServiceUnavailable);
2791
- return this.send(body, generateEtag);
2792
- }
2793
- /**
2794
- * Sends a 504 Gateway Timeout response
2795
- *
2796
- * @param body - Response body
2797
- * @param generateEtag - Whether to generate ETag header
2798
- */
2799
- gatewayTimeout(body, generateEtag) {
2800
- this.status(ResponseStatus.GatewayTimeout);
2801
- return this.send(body, generateEtag);
2802
- }
2803
- /**
2804
- * Sends a 505 HTTP Version Not Supported response
2805
- *
2806
- * @param body - Response body
2807
- * @param generateEtag - Whether to generate ETag header
2808
- */
2809
- httpVersionNotSupported(body, generateEtag) {
2810
- this.status(ResponseStatus.HTTPVersionNotSupported);
2811
- return this.send(body, generateEtag);
2812
- }
2813
- };
2814
-
2815
- // src/router/main.ts
2816
- import is2 from "@sindresorhus/is";
2817
- import { moduleImporter as moduleImporter2 } from "@adonisjs/fold";
2818
- import { RuntimeException as RuntimeException3 } from "@poppinss/utils/exception";
2819
-
2820
- // src/router/store.ts
2821
- import matchit from "@poppinss/matchit";
2822
- import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
2823
- var RoutesStore = class {
2824
- /**
2825
- * A flag to know if routes for explicit domains
2826
- * have been registered
2827
- */
2828
- usingDomains = false;
2829
- /**
2830
- * Tree of registered routes and their matchit tokens
2831
- */
2832
- tree = { tokens: [], domains: {} };
2833
- /**
2834
- * Returns the domain node for a given domain.
2835
- */
2836
- #getDomainNode(domain) {
2837
- if (!this.tree.domains[domain]) {
2838
- this.tree.tokens.push(parseRoute(domain));
2839
- this.tree.domains[domain] = {};
2840
- }
2841
- return this.tree.domains[domain];
2842
- }
2843
- /**
2844
- * Returns the method node for a given domain and method.
2845
- */
2846
- #getMethodNode(domain, method) {
2847
- const domainNode = this.#getDomainNode(domain);
2848
- if (!domainNode[method]) {
2849
- domainNode[method] = { tokens: [], routes: {}, routeKeys: {} };
2850
- }
2851
- return domainNode[method];
2852
- }
2853
- /**
2854
- * Collects route params
2855
- */
2856
- #collectRouteParams(route, tokens) {
2857
- const collectedParams = /* @__PURE__ */ new Set();
2858
- for (let token of tokens) {
2859
- if ([1, 3].includes(token.type)) {
2860
- if (collectedParams.has(token.val)) {
2861
- throw new RuntimeException2(`Duplicate param "${token.val}" found in "${route.pattern}"`);
2862
- } else {
2863
- collectedParams.add(token.val);
2864
- }
2865
- }
2866
- }
2867
- const params = [...collectedParams];
2868
- collectedParams.clear();
2869
- return params;
2870
- }
2871
- /**
2872
- * Register route for a given domain and method
2873
- */
2874
- #registerRoute(domain, method, tokens, route) {
2875
- const methodRoutes = this.#getMethodNode(domain, method);
2876
- if (methodRoutes.routes[route.pattern]) {
2877
- throw new RuntimeException2(
2878
- `Duplicate route found. "${method}: ${route.pattern}" route already exists`
2879
- );
2880
- }
2881
- if (debug_default.enabled) {
2882
- debug_default("registering route to the store %O", route);
2883
- debug_default("route middleware %O", route.middleware.all().entries());
2884
- }
2885
- methodRoutes.tokens.push(tokens);
2886
- methodRoutes.routes[route.pattern] = route;
2887
- methodRoutes.routeKeys[route.pattern] = domain !== "root" ? `${domain}-${method}-${route.pattern}` : `${method}-${route.pattern}`;
2888
- }
2889
- /**
2890
- * Add a route to the store
2891
- *
2892
- * ```ts
2893
- * store.add({
2894
- * pattern: 'post/:id',
2895
- * methods: ['GET'],
2896
- * matchers: {},
2897
- * meta: {},
2898
- * handler: function handler () {
2899
- * }
2900
- * })
2901
- * ```
2902
- * @param route - The route to add to the store
2903
- * @returns Current RoutesStore instance for method chaining
2904
- */
2905
- add(route) {
2906
- if (route.domain !== "root") {
2907
- this.usingDomains = true;
2908
- }
2909
- const routeNode = { ...route };
2910
- routeNode.meta.params = this.#collectRouteParams(routeNode, route.tokens);
2911
- route.methods.forEach((method) => {
2912
- this.#registerRoute(route.domain, method, route.tokens, routeNode);
2913
- });
2914
- return this;
2915
- }
2916
- /**
2917
- * Matches the url, method and optionally domain to pull the matching
2918
- * route. `null` is returned when unable to match the URL against
2919
- * registered routes.
2920
- *
2921
- * The domain parameter has to be a registered pattern and not the fully
2922
- * qualified runtime domain. You must call `matchDomain` first to fetch
2923
- * the pattern for qualified domain
2924
- * @param url - The URL to match
2925
- * @param method - HTTP method
2926
- * @param shouldDecodeParam - Whether to decode parameters
2927
- * @param domain - Optional domain tokens and hostname
2928
- * @returns Matched route or null if no match found
2929
- */
2930
- match(url, method, shouldDecodeParam, domain) {
2931
- const domainName = domain?.tokens[0]?.old || "root";
2932
- const matchedDomain = this.tree.domains[domainName];
2933
- if (!matchedDomain) {
2934
- return null;
2935
- }
2936
- const matchedMethod = this.tree.domains[domainName][method];
2937
- if (!matchedMethod) {
2938
- return null;
2939
- }
2940
- const matchedRoute = matchit.match(url, matchedMethod.tokens);
2941
- if (!matchedRoute.length) {
2942
- return null;
2943
- }
2944
- const route = matchedMethod.routes[matchedRoute[0].old];
2945
- return {
2946
- route,
2947
- routeKey: matchedMethod.routeKeys[route.pattern],
2948
- params: matchit.exec(url, matchedRoute, shouldDecodeParam),
2949
- subdomains: domain?.hostname ? matchit.exec(domain.hostname, domain.tokens) : {}
2950
- };
2951
- }
2952
- /**
2953
- * Match hostname against registered domains.
2954
- * @param hostname - The hostname to match
2955
- * @returns Array of matched domain tokens
2956
- */
2957
- matchDomain(hostname) {
2958
- if (!hostname || !this.usingDomains) {
2959
- return [];
2960
- }
2961
- return matchit.match(hostname, this.tree.tokens);
2962
- }
2963
- };
2964
-
2965
- // src/router/url_builder.ts
2966
- function createUrlBuilder(router, searchParamsStringifier) {
2967
- let domainsList;
2968
- function createUrlForRoute(identifier, params, options, method) {
2969
- if (!domainsList) {
2970
- domainsList = Object.keys(router.toJSON()).filter((domain2) => domain2 !== "root");
2971
- }
2972
- const domain = domainsList.find((name) => identifier.startsWith(`${name}@`));
2973
- const routeIdentifier = domain ? identifier.replace(new RegExp(`^${domain}@`), "") : identifier;
2974
- const route = router.findOrFail(routeIdentifier, domain, method, true);
2975
- return createURL(
2976
- route.name ?? route.pattern,
2977
- route.tokens,
2978
- searchParamsStringifier,
2979
- params,
2980
- options
2981
- );
2982
- }
2983
- const urlFor = function route(...[identifier, params, options]) {
2984
- return createUrlForRoute(identifier, params, options);
2985
- };
2986
- urlFor.get = function urlForMethodGet(...[identifier, params, options]) {
2987
- return {
2988
- url: createUrlForRoute(identifier, params, options, "GET"),
2989
- method: "get",
2990
- toString() {
2991
- return this.url;
2992
- }
2993
- };
2994
- };
2995
- urlFor.post = function urlForMethodPost(...[identifier, params, options]) {
2996
- return {
2997
- url: createUrlForRoute(identifier, params, options, "POST"),
2998
- method: "post",
2999
- toString() {
3000
- return this.url;
3001
- }
3002
- };
3003
- };
3004
- urlFor.put = function urlForMethodPut(...[identifier, params, options]) {
3005
- return {
3006
- url: createUrlForRoute(identifier, params, options, "PUT"),
3007
- method: "put",
3008
- toString() {
3009
- return this.url;
3010
- }
3011
- };
3012
- };
3013
- urlFor.patch = function urlForMethodPatch(...[identifier, params, options]) {
3014
- return {
3015
- url: createUrlForRoute(identifier, params, options, "PATCH"),
3016
- method: "patch",
3017
- toString() {
3018
- return this.url;
3019
- }
3020
- };
3021
- };
3022
- urlFor.delete = function urlForMethodDelete(...[identifier, params, options]) {
3023
- return {
3024
- url: createUrlForRoute(identifier, params, options, "DELETE"),
3025
- method: "delete",
3026
- toString() {
3027
- return this.url;
3028
- }
3029
- };
3030
- };
3031
- urlFor.method = function urlForCustomMethod(method, ...[identifier, params, options]) {
3032
- return {
3033
- url: createUrlForRoute(identifier, params, options, method),
3034
- method,
3035
- toString() {
3036
- return this.url;
3037
- }
3038
- };
3039
- };
3040
- return urlFor;
3041
- }
3042
-
3043
- // src/router/legacy/url_builder.ts
3044
- var UrlBuilder = class {
3045
- /**
3046
- * The parameters to apply on the route
3047
- */
3048
- #params = {};
3049
- /**
3050
- * Query string to append to the route
3051
- */
3052
- #qs = {};
3053
- /**
3054
- * Should we perform the route lookup or just build the
3055
- * given pattern as it is.
3056
- */
3057
- #shouldPerformLookup = true;
3058
- /**
3059
- * BaseURL to append to the constructored URL
3060
- */
3061
- #baseUrl;
3062
- /**
3063
- * Route finder for finding route pattern
3064
- */
3065
- #router;
3066
- /**
3067
- * Domain to use for URL generation
3068
- */
3069
- #domain;
3070
- /**
3071
- * Creates a new UrlBuilder instance
3072
- * @param router - The router instance
3073
- * @param domain - Optional domain for URL generation
3074
- */
3075
- constructor(router, domain) {
3076
- this.#router = router;
3077
- this.#domain = domain;
3078
- }
3079
- /**
3080
- * Prefix a custom base URL to the final URI
3081
- * @deprecated Instead use "@adonisjs/core/services/url_builder" instead
3082
- */
3083
- prefixUrl(url) {
3084
- this.#baseUrl = url;
3085
- return this;
3086
- }
3087
- /**
3088
- * Disable route lookup. Calling this method considers
3089
- * the "identifier" as the route pattern
3090
- * @deprecated Instead use "@adonisjs/core/services/url_builder" instead
3091
- */
3092
- disableRouteLookup() {
3093
- this.#shouldPerformLookup = false;
3094
- return this;
3095
- }
3096
- /**
3097
- * Append query string to the final URI
3098
- * @deprecated Instead use "@adonisjs/core/services/url_builder" instead
3099
- */
3100
- qs(queryString) {
3101
- if (!queryString) {
3102
- return this;
3103
- }
3104
- this.#qs = queryString;
3105
- return this;
3106
- }
3107
- /**
3108
- * Specify params to apply to the route pattern
3109
- * @deprecated Instead use "@adonisjs/core/services/url_builder" instead
3110
- */
3111
- params(params) {
3112
- if (!params) {
3113
- return this;
3114
- }
3115
- this.#params = params;
3116
- return this;
3117
- }
3118
- /**
3119
- * Generate URL for the given route identifier. The identifier can be the
3120
- * route name, controller.method name or the route pattern
3121
- * itself.
3122
- *
3123
- * @deprecated Instead use "@adonisjs/core/services/url_builder" instead
3124
- * @param identifier - Route identifier to generate URL for
3125
- * @returns Generated URL string
3126
- */
3127
- make(identifier) {
3128
- return this.#router.makeUrl(identifier, this.#params, {
3129
- prefixUrl: this.#baseUrl,
3130
- disableRouteLookup: !this.#shouldPerformLookup,
3131
- domain: this.#domain,
3132
- qs: this.#qs
3133
- });
3134
- }
3135
- /**
3136
- * Generate a signed URL for the given route identifier. The identifier can be the
3137
- * route name, controller.method name or the route pattern
3138
- * itself.
3139
- *
3140
- * @deprecated Instead use "@adonisjs/core/services/url_builder" instead
3141
- *
3142
- */
3143
- makeSigned(identifier, options) {
3144
- return this.#router.makeSignedUrl(identifier, this.#params, {
3145
- prefixUrl: this.#baseUrl,
3146
- disableRouteLookup: !this.#shouldPerformLookup,
3147
- domain: this.#domain,
3148
- qs: this.#qs,
3149
- ...options
3150
- });
3151
- }
3152
- };
3153
-
3154
- // src/router/matchers.ts
3155
- import Macroable3 from "@poppinss/macroable";
3156
- var RouteMatchers = class extends Macroable3 {
3157
- /**
3158
- * Enforce value to be a number and also casts it to number data
3159
- * type
3160
- * @returns Route matcher configuration for numeric values
3161
- */
3162
- number() {
3163
- return { match: /^[0-9]+$/, cast: (value) => Number(value) };
3164
- }
3165
- /**
3166
- * Enforce value to be formatted as uuid
3167
- * @returns Route matcher configuration for UUID values
3168
- */
3169
- uuid() {
3170
- return {
3171
- match: /^[0-9a-zA-F]{8}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{12}$/,
3172
- cast: (value) => value.toLowerCase()
3173
- };
3174
- }
3175
- /**
3176
- * Enforce value to be formatted as slug
3177
- * @returns Route matcher configuration for slug values
3178
- */
3179
- slug() {
3180
- return { match: /^[^\s-_](?!.*?[-_]{2,})([a-z0-9-\\]{1,})[^\s]*[^-_\s]$/ };
3181
- }
3182
- };
3183
-
3184
- // src/define_middleware.ts
3185
- import { moduleImporter } from "@adonisjs/fold";
3186
- function middlewareReferenceBuilder(name, middleware) {
3187
- const handler = moduleImporter(middleware, "handle").toHandleMethod();
3188
- return function(...args) {
3189
- return {
3190
- ...handler,
3191
- name,
3192
- reference: middleware,
3193
- args: args[0]
3194
- };
3195
- };
3196
- }
3197
- function defineNamedMiddleware(collection) {
3198
- return Object.keys(collection).reduce(
3199
- (result, key) => {
3200
- result[key] = middlewareReferenceBuilder(key, collection[key]);
3201
- return result;
3202
- },
3203
- {}
3204
- );
3205
- }
3206
-
3207
- // src/router/signed_url_builder.ts
3208
- function createSignedUrlBuilder(router, encryption, searchParamsStringifier) {
3209
- let domainsList;
3210
- function createSignedUrlForRoute(identifier, params, options, method) {
3211
- if (!domainsList) {
3212
- domainsList = Object.keys(router.toJSON()).filter((domain2) => domain2 !== "root");
3213
- }
3214
- const domain = domainsList.find((name) => identifier.startsWith(`${name}@`));
3215
- const routeIdentifier = domain ? identifier.replace(new RegExp(`^${domain}@`), "") : identifier;
3216
- const route = router.findOrFail(routeIdentifier, domain, method, true);
3217
- return createSignedURL(
3218
- route.name ?? route.pattern,
3219
- route.tokens,
3220
- searchParamsStringifier,
3221
- encryption,
3222
- params,
3223
- options
3224
- );
3225
- }
3226
- const signedRoute = function route(...[identifier, params, options]) {
3227
- return createSignedUrlForRoute(identifier, params, options);
3228
- };
3229
- signedRoute.get = function routeGet(...[identifier, params, options]) {
3230
- return {
3231
- url: createSignedUrlForRoute(identifier, params, options, "GET"),
3232
- method: "get",
3233
- toString() {
3234
- return this.url;
3235
- }
3236
- };
3237
- };
3238
- signedRoute.post = function routePost(...[identifier, params, options]) {
3239
- return {
3240
- url: createSignedUrlForRoute(identifier, params, options, "POST"),
3241
- method: "post",
3242
- toString() {
3243
- return this.url;
3244
- }
3245
- };
3246
- };
3247
- signedRoute.put = function routePut(...[identifier, params, options]) {
3248
- return {
3249
- url: createSignedUrlForRoute(identifier, params, options, "PUT"),
3250
- method: "put",
3251
- toString() {
3252
- return this.url;
3253
- }
3254
- };
3255
- };
3256
- signedRoute.patch = function routePatch(...[identifier, params, options]) {
3257
- return {
3258
- url: createSignedUrlForRoute(identifier, params, options, "PATCH"),
3259
- method: "patch",
3260
- toString() {
3261
- return this.url;
3262
- }
3263
- };
3264
- };
3265
- signedRoute.delete = function routeDelete(...[identifier, params, options]) {
3266
- return {
3267
- url: createSignedUrlForRoute(identifier, params, options, "DELETE"),
3268
- method: "delete",
3269
- toString() {
3270
- return this.url;
3271
- }
3272
- };
3273
- };
3274
- signedRoute.method = function routeGet(method, ...[identifier, params, options]) {
3275
- return {
3276
- url: createSignedUrlForRoute(identifier, params, options, method),
3277
- method,
3278
- toString() {
3279
- return this.url;
3280
- }
3281
- };
3282
- };
3283
- return signedRoute;
3284
- }
3285
-
3286
- // src/router/main.ts
3287
- var Router = class {
3288
- /**
3289
- * Flag to avoid re-comitting routes to the store
3290
- */
3291
- #commited = false;
3292
- /**
3293
- * Application is needed to resolve string based controller expressions
3294
- */
3295
- #app;
3296
- /**
3297
- * Store with tokenized routes
3298
- */
3299
- #store = new RoutesStore();
3300
- /**
3301
- * Encryption for making signed URLs
3302
- */
3303
- #encryption;
3304
- /**
3305
- * Global matchers to test route params against regular expressions.
3306
- */
3307
- #globalMatchers = {};
3308
- /**
3309
- * Middleware store to be shared with the routes
3310
- */
3311
- #middleware = [];
3312
- /**
3313
- * A boolean to tell the router that a group is in
3314
- * open state right now
3315
- */
3316
- #openedGroups = [];
3317
- /**
3318
- * Collection of routes to be committed with the store, including
3319
- * route resource and route group.
3320
- */
3321
- #routesToBeCommitted = [];
3322
- /**
3323
- * A flag to know if routes for explicit domains have been registered.
3324
- * The boolean is computed after calling the "commit" method.
3325
- */
3326
- usingDomains = false;
3327
- /**
3328
- * Shortcut methods for commonly used route matchers
3329
- */
3330
- matchers = new RouteMatchers();
3331
- /**
3332
- * Check if routes have been committed to the store. Once
3333
- * routes are committed, defining new set of routes will
3334
- * have no impact
3335
- */
3336
- get commited() {
3337
- return this.#commited;
3338
- }
3339
- /**
3340
- * Query string parser for making URLs
3341
- */
3342
- qs;
3343
- /**
3344
- * The URLBuilder offers a type-safe API for creating URL for pre-registered
3345
- * routes or the route patterns.
3346
- *
3347
- * We recommend using the URLBuilder over the "makeUrl" and "makeSignedUrl"
3348
- * methods.
3349
- */
3350
- urlBuilder;
3351
- /**
3352
- * List of route references kept for lookup.
3353
- */
3354
- routes = {};
3355
- /**
3356
- * Creates a new Router instance
3357
- * @param app - The AdonisJS application instance
3358
- * @param encryption - Encryption service for signed URLs
3359
- * @param qsParser - Query string parser for URL generation
3360
- */
3361
- constructor(app, encryption, qsParser) {
3362
- this.#app = app;
3363
- this.#encryption = encryption;
3364
- this.qs = qsParser;
3365
- this.urlBuilder = {
3366
- urlFor: createUrlBuilder(this, this.qs.stringify),
3367
- signedUrlFor: createSignedUrlBuilder(this, this.#encryption, this.qs.stringify)
3368
- };
3369
- }
3370
- /**
3371
- * Register route JSON payload
3372
- */
3373
- register(route) {
3374
- this.routes[route.domain] = this.routes[route.domain] || [];
3375
- this.routes[route.domain].push(route);
3376
- }
3377
- /**
3378
- * Push a give router entity to the list of routes or the
3379
- * recently opened group.
3380
- */
3381
- #pushToRoutes(entity) {
3382
- const openedGroup = this.#openedGroups[this.#openedGroups.length - 1];
3383
- if (openedGroup) {
3384
- openedGroup.routes.push(entity);
3385
- return;
3386
- }
3387
- this.#routesToBeCommitted.push(entity);
3388
- }
3389
- /**
3390
- * Parses the route pattern
3391
- * @param pattern - The route pattern to parse
3392
- * @param matchers - Optional route matchers
3393
- */
3394
- parsePattern(pattern, matchers) {
3395
- return parseRoute(pattern, matchers);
3396
- }
3397
- /**
3398
- * Define an array of middleware to use on all the routes.
3399
- * Calling this method multiple times pushes to the
3400
- * existing list of middleware
3401
- * @param middleware - Array of middleware classes to apply globally
3402
- * @returns Current Router instance for method chaining
3403
- */
3404
- use(middleware) {
3405
- middleware.forEach(
3406
- (one) => this.#middleware.push({
3407
- reference: one,
3408
- ...moduleImporter2(one, "handle").toHandleMethod()
3409
- })
3410
- );
3411
- return this;
3412
- }
3413
- /**
3414
- * Define a collection of named middleware. The defined collection is
3415
- * not registered anywhere, but instead converted in a new collection
3416
- * of functions you can apply on the routes, or router groups.
3417
- * @param collection - Object mapping middleware names to middleware classes
3418
- * @returns Named middleware functions
3419
- */
3420
- named(collection) {
3421
- return defineNamedMiddleware(collection);
3422
- }
3423
- /**
3424
- * Add route for a given pattern and methods
3425
- * @param pattern - The route pattern
3426
- * @param methods - Array of HTTP methods
3427
- * @param handler - Route handler (function, string, or controller tuple)
3428
- * @returns The created route instance
3429
- */
3430
- route(pattern, methods, handler) {
3431
- const route = new Route(this.#app, this.#middleware, {
3432
- pattern,
3433
- methods,
3434
- handler,
3435
- globalMatchers: this.#globalMatchers
3436
- });
3437
- this.#pushToRoutes(route);
3438
- return route;
3439
- }
3440
- /**
3441
- * Define a route that handles all common HTTP methods
3442
- * @param pattern - The route pattern
3443
- * @param handler - Route handler (function, string, or controller tuple)
3444
- * @returns The created route instance
3445
- */
3446
- any(pattern, handler) {
3447
- return this.route(
3448
- pattern,
3449
- ["HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE"],
3450
- handler
3451
- );
3452
- }
3453
- /**
3454
- * Define `GET` route
3455
- * @param pattern - The route pattern
3456
- * @param handler - Route handler (function, string, or controller tuple)
3457
- * @returns The created route instance
3458
- */
3459
- get(pattern, handler) {
3460
- return this.route(pattern, ["GET", "HEAD"], handler);
3461
- }
3462
- /**
3463
- * Define `POST` route
3464
- * @param pattern - The route pattern
3465
- * @param handler - Route handler (function, string, or controller tuple)
3466
- * @returns The created route instance
3467
- */
3468
- post(pattern, handler) {
3469
- return this.route(pattern, ["POST"], handler);
3470
- }
3471
- /**
3472
- * Define `PUT` route
3473
- * @param pattern - The route pattern
3474
- * @param handler - Route handler (function, string, or controller tuple)
3475
- * @returns The created route instance
3476
- */
3477
- put(pattern, handler) {
3478
- return this.route(pattern, ["PUT"], handler);
3479
- }
3480
- /**
3481
- * Define `PATCH` route
3482
- * @param pattern - The route pattern
3483
- * @param handler - Route handler (function, string, or controller tuple)
3484
- * @returns The created route instance
3485
- */
3486
- patch(pattern, handler) {
3487
- return this.route(pattern, ["PATCH"], handler);
3488
- }
3489
- /**
3490
- * Define `DELETE` route
3491
- * @param pattern - The route pattern
3492
- * @param handler - Route handler (function, string, or controller tuple)
3493
- * @returns The created route instance
3494
- */
3495
- delete(pattern, handler) {
3496
- return this.route(pattern, ["DELETE"], handler);
3497
- }
3498
- /**
3499
- * Creates a group of routes. A route group can apply transforms
3500
- * to routes in bulk
3501
- * @param callback - Function that defines routes within the group
3502
- * @returns The created route group instance
3503
- */
3504
- group(callback) {
3505
- const group = new RouteGroup([]);
3506
- this.#pushToRoutes(group);
3507
- this.#openedGroups.push(group);
3508
- callback();
3509
- this.#openedGroups.pop();
3510
- return group;
3511
- }
3512
- /**
3513
- * Registers a route resource with conventional set of routes
3514
- * @param resource - The resource name
3515
- * @param controller - Controller to handle the resource
3516
- * @returns The created route resource instance
3517
- */
3518
- resource(resource, controller) {
3519
- const resourceInstance = new RouteResource(this.#app, this.#middleware, {
3520
- resource,
3521
- controller,
3522
- shallow: false,
3523
- globalMatchers: this.#globalMatchers
3524
- });
3525
- this.#pushToRoutes(resourceInstance);
3526
- return resourceInstance;
3527
- }
3528
- /**
3529
- * Register a route resource with shallow nested routes.
3530
- * @param resource - The resource name
3531
- * @param controller - Controller to handle the resource
3532
- * @returns The created route resource instance
3533
- */
3534
- shallowResource(resource, controller) {
3535
- const resourceInstance = new RouteResource(this.#app, this.#middleware, {
3536
- resource,
3537
- controller,
3538
- shallow: true,
3539
- globalMatchers: this.#globalMatchers
3540
- });
3541
- this.#pushToRoutes(resourceInstance);
3542
- return resourceInstance;
3543
- }
3544
- /**
3545
- * Returns a brisk route instance for a given URL pattern
3546
- * @param pattern - The route pattern
3547
- * @returns The created brisk route instance
3548
- */
3549
- on(pattern) {
3550
- const briskRoute = new BriskRoute(this.#app, this.#middleware, {
3551
- pattern,
3552
- globalMatchers: this.#globalMatchers
3553
- });
3554
- this.#pushToRoutes(briskRoute);
3555
- return briskRoute;
3556
- }
3557
- /**
3558
- * Define matcher for a given param. The global params are applied
3559
- * on all the routes (unless overridden at the route level).
3560
- * @param param - The parameter name to match
3561
- * @param matcher - The matcher pattern (RegExp, string, or RouteMatcher)
3562
- * @returns Current Router instance for method chaining
3563
- */
3564
- where(param, matcher) {
3565
- if (typeof matcher === "string") {
3566
- this.#globalMatchers[param] = { match: new RegExp(matcher) };
3567
- } else if (is2.regExp(matcher)) {
3568
- this.#globalMatchers[param] = { match: matcher };
3569
- } else {
3570
- this.#globalMatchers[param] = matcher;
3571
- }
3572
- return this;
3573
- }
3574
- /**
3575
- * Commit routes to the store. The router is freezed after the
3576
- * commit method is called.
3577
- */
3578
- commit() {
3579
- if (this.#commited) {
3580
- return;
3581
- }
3582
- debug_default("Committing routes to the routes store");
3583
- const routeNamesByDomain = /* @__PURE__ */ new Map();
3584
- toRoutesJSON(this.#routesToBeCommitted).forEach((route) => {
3585
- if (!routeNamesByDomain.has(route.domain)) {
3586
- routeNamesByDomain.set(route.domain, /* @__PURE__ */ new Set());
3587
- }
3588
- const routeNames = routeNamesByDomain.get(route.domain);
3589
- if (route.name && routeNames.has(route.name)) {
3590
- throw new RuntimeException3(
3591
- `A route with name "${route.name}" already exists. It may happen when two routes use the same controller, so make sure to give explicit names to these routes`
3592
- );
3593
- }
3594
- if (route.name) {
3595
- routeNames.add(route.name);
3596
- }
3597
- this.register(route);
3598
- this.#store.add(route);
3599
- });
3600
- routeNamesByDomain.clear();
3601
- this.usingDomains = this.#store.usingDomains;
3602
- this.#routesToBeCommitted = [];
3603
- this.#globalMatchers = {};
3604
- this.#middleware = [];
3605
- this.#openedGroups = [];
3606
- this.#commited = true;
3607
- }
3608
- /**
3609
- * Finds a route by its identifier. The identifier can be the
3610
- * route name, controller.method name or the route pattern
3611
- * itself.
3612
- *
3613
- * When "disableLegacyLookup" is set, the lookup will be performed
3614
- * only using the route name
3615
- * @param routeIdentifier - Route name, pattern, or controller reference
3616
- * @param domain - Optional domain to search within
3617
- * @param method - Optional HTTP method to filter by
3618
- * @param disableLegacyLookup - Whether to disable legacy lookup strategies
3619
- * @returns Found route or null if not found
3620
- */
3621
- find(routeIdentifier, domain, method, disableLegacyLookup) {
3622
- if (!domain) {
3623
- let route = null;
3624
- for (const routeDomain of Object.keys(this.routes)) {
3625
- route = this.find(routeIdentifier, routeDomain, method, disableLegacyLookup);
3626
- if (route) {
3627
- break;
3628
- }
3629
- }
3630
- return route;
3631
- }
3632
- const routes = this.routes[domain];
3633
- if (!routes) {
3634
- return null;
3635
- }
3636
- const lookupByName = true;
3637
- const lookupByPattern = !disableLegacyLookup;
3638
- const lookupByController = !disableLegacyLookup;
3639
- return routes.find((route) => {
3640
- if (method && !route.methods.includes(method)) {
3641
- return false;
3642
- }
3643
- if (route.name === routeIdentifier && lookupByName || route.pattern === routeIdentifier && lookupByPattern) {
3644
- return true;
3645
- }
3646
- if (typeof route.handler === "function" || !lookupByController) {
3647
- return false;
3648
- }
3649
- return route.handler.reference === routeIdentifier;
3650
- }) || null;
3651
- }
3652
- /**
3653
- * Finds a route by its identifier. The identifier can be the
3654
- * route name, controller.method name or the route pattern
3655
- * itself.
3656
- *
3657
- * An error is raised when unable to find the route.
3658
- *
3659
- * When "disableLegacyLookup" is set, the lookup will be performed
3660
- * only using the route name
3661
- * @param routeIdentifier - Route name, pattern, or controller reference
3662
- * @param domain - Optional domain to search within
3663
- * @param method - Optional HTTP method to filter by
3664
- * @param disableLegacyLookup - Whether to disable legacy lookup strategies
3665
- * @returns Found route
3666
- * @throws Error when route is not found
3667
- */
3668
- findOrFail(routeIdentifier, domain, method, disableLegacyLookup) {
3669
- const route = this.find(routeIdentifier, domain, method, disableLegacyLookup);
3670
- if (!route) {
3671
- throw new Error(`Cannot lookup route "${routeIdentifier}"`);
3672
- }
3673
- return route;
3674
- }
3675
- /**
3676
- * Check if a route exists. The identifier can be the
3677
- * route name, controller.method name or the route pattern
3678
- * itself.
3679
- *
3680
- * When "followLookupStrategy" is enabled, the lookup will be performed
3681
- * on the basis of the lookup strategy enabled via the "lookupStrategies"
3682
- * method. The default lookupStrategy is "name" and "pattern".
3683
- * @param routeIdentifier - Route name, pattern, or controller reference
3684
- * @param domain - Optional domain to search within
3685
- * @param method - Optional HTTP method to filter by
3686
- * @param followLookupStrategy - Whether to follow the configured lookup strategy
3687
- * @returns True if route exists, false otherwise
3688
- */
3689
- has(routeIdentifier, domain, method, followLookupStrategy) {
3690
- return !!this.find(routeIdentifier, domain, method, followLookupStrategy);
3691
- }
3692
- /**
3693
- * Returns a list of routes grouped by their domain names
3694
- * @returns Object mapping domain names to route arrays
3695
- */
3696
- toJSON() {
3697
- return this.routes;
3698
- }
3699
- /**
3700
- * Generates types for the URL builder. These types must
3701
- * be written inside a file for the URL builder to
3702
- * pick them up.
3703
- * @param indentation - Indentation level for generated types
3704
- * @returns Generated TypeScript types as string
3705
- */
3706
- generateTypes(indentation = 0) {
3707
- const routesList = {};
3708
- function trackRoute(route, domain) {
3709
- let params = [];
3710
- let paramsTuple = [];
3711
- let hasRequiredParams = false;
3712
- for (let token of route.tokens) {
3713
- if (token.type === 1) {
3714
- hasRequiredParams = true;
3715
- params.push(`'${token.val}': string`);
3716
- paramsTuple.push("string");
3717
- } else if (token.type === 3) {
3718
- params.push(`'${token.val}'?: string`);
3719
- paramsTuple.push("string?");
3720
- } else if (token.type === 2) {
3721
- hasRequiredParams = true;
3722
- params.push(`'*': string[]`);
3723
- paramsTuple.push("...string[]");
3724
- break;
3725
- }
3726
- }
3727
- route.methods.forEach((method) => {
3728
- routesList["ALL"] = routesList["ALL"] ?? {};
3729
- routesList[method] = routesList[method] ?? {};
3730
- const identifiers = [];
3731
- if (route.name) {
3732
- identifiers.push(
3733
- domain && routesList[method][route.name] ? `${domain}@${route.name}` : route.name
3734
- );
3735
- }
3736
- identifiers.forEach((identifier) => {
3737
- routesList["ALL"][identifier] = {
3738
- params,
3739
- paramsTuple,
3740
- hasRequiredParams
3741
- };
3742
- routesList[method][identifier] = {
3743
- params,
3744
- paramsTuple,
3745
- hasRequiredParams
3746
- };
3747
- });
3748
- });
3749
- }
3750
- const domains = Object.keys(this.routes).filter((domain) => domain !== "root");
3751
- this.routes["root"]?.forEach((route) => trackRoute.bind(this)(route));
3752
- domains.forEach(
3753
- (domain) => this.routes[domain].forEach((route) => trackRoute.bind(this)(route, domain))
3754
- );
3755
- return Object.keys(routesList).reduce((result, method) => {
3756
- result.push(`${" ".repeat(indentation)}${method}: {`);
3757
- Object.keys(routesList[method]).forEach((identifier) => {
3758
- const key = `'${identifier}'`;
3759
- const { paramsTuple, hasRequiredParams, params } = routesList[method][identifier];
3760
- const dictName = hasRequiredParams ? "params" : "params?";
3761
- const tupleName = hasRequiredParams ? "paramsTuple" : "paramsTuple?";
3762
- const dictValue = `{${params.join(",")}}`;
3763
- const tupleValue = `[${paramsTuple?.join(",")}]`;
3764
- const value = `{ ${tupleName}: ${tupleValue}; ${dictName}: ${dictValue} }`;
3765
- result.push(`${" ".repeat(indentation + 2)}${key}: ${value}`);
3766
- });
3767
- result.push(`${" ".repeat(indentation)}}`);
3768
- return result;
3769
- }, []).join("\n");
3770
- }
3771
- /**
3772
- * Find route for a given URL, method and optionally domain
3773
- * @param uri - The URI to match
3774
- * @param method - HTTP method
3775
- * @param shouldDecodeParam - Whether to decode parameters
3776
- * @param hostname - Optional hostname for domain matching
3777
- * @returns Matched route or null if no match found
3778
- */
3779
- match(uri, method, shouldDecodeParam, hostname) {
3780
- const matchingDomain = this.#store.matchDomain(hostname);
3781
- return matchingDomain.length ? this.#store.match(uri, method, shouldDecodeParam, {
3782
- tokens: matchingDomain,
3783
- hostname
3784
- }) : this.#store.match(uri, method, shouldDecodeParam);
3785
- }
3786
- /**
3787
- * Create URL builder instance.
3788
- * @deprecated Instead use "@adonisjs/core/services/url_builder" instead
3789
- */
3790
- builder() {
3791
- return new UrlBuilder(this);
3792
- }
3793
- /**
3794
- * Create URL builder instance for a given domain.
3795
- * @deprecated
3796
- *
3797
- * Instead use "@adonisjs/core/services/url_builder"
3798
- */
3799
- builderForDomain(domain) {
3800
- return new UrlBuilder(this, domain);
3801
- }
3802
- /**
3803
- * Make URL to a pre-registered route
3804
- *
3805
- * @deprecated
3806
- * Instead use "@adonisjs/core/services/url_builder"
3807
- */
3808
- makeUrl(routeIdentifier, params, options) {
3809
- const normalizedOptions = Object.assign({}, options);
3810
- if (options?.disableRouteLookup) {
3811
- return createURL(
3812
- routeIdentifier,
3813
- parseRoute(routeIdentifier),
3814
- this.qs.stringify,
3815
- params,
3816
- options
3817
- );
3818
- }
3819
- const route = this.findOrFail(routeIdentifier, normalizedOptions.domain);
3820
- return createURL(route.name ?? route.pattern, route.tokens, this.qs.stringify, params, options);
3821
- }
3822
- /**
3823
- * Makes a signed URL to a pre-registered route.
3824
- *
3825
- * @deprecated
3826
- * Instead use "@adonisjs/core/services/url_builder"
3827
- */
3828
- makeSignedUrl(routeIdentifier, params, options) {
3829
- const normalizedOptions = Object.assign({}, options);
3830
- if (options?.disableRouteLookup) {
3831
- return createSignedURL(
3832
- routeIdentifier,
3833
- parseRoute(routeIdentifier),
3834
- this.qs.stringify,
3835
- this.#encryption,
3836
- params,
3837
- options
3838
- );
3839
- }
3840
- const route = this.findOrFail(routeIdentifier, normalizedOptions.domain);
3841
- return createSignedURL(
3842
- route.name ?? route.pattern,
3843
- route.tokens,
3844
- this.qs.stringify,
3845
- this.#encryption,
3846
- params,
3847
- options
3848
- );
3849
- }
3850
- };
3851
-
3852
- // src/http_context/main.ts
3853
- import { inspect } from "util";
3854
- import Macroable4 from "@poppinss/macroable";
3855
- import { RuntimeException as RuntimeException4 } from "@poppinss/utils/exception";
3856
-
3857
- // src/http_context/local_storage.ts
3858
- import { AsyncLocalStorage } from "async_hooks";
3859
- var asyncLocalStorage = {
3860
- isEnabled: false,
3861
- storage: null,
3862
- create() {
3863
- this.isEnabled = true;
3864
- this.storage = new AsyncLocalStorage();
3865
- return this.storage;
3866
- },
3867
- destroy() {
3868
- this.isEnabled = false;
3869
- this.storage = null;
3870
- }
3871
- };
3872
-
3873
- // src/http_context/main.ts
3874
- var HttpContext = class extends Macroable4 {
3875
- /**
3876
- * Creates a new HttpContext instance
3877
- *
3878
- * @param {Request} request - The HTTP request instance
3879
- * @param {Response} response - The HTTP response instance
3880
- * @param {Logger} logger - The logger instance
3881
- * @param {ContainerResolver<any>} containerResolver - The IoC container resolver
3882
- */
3883
- constructor(request, response, logger, containerResolver) {
3884
- super();
3885
- this.request = request;
3886
- this.response = response;
3887
- this.logger = logger;
3888
- this.containerResolver = containerResolver;
3889
- this.request.ctx = this;
3890
- this.response.ctx = this;
3891
- }
3892
- /**
3893
- * Find if async localstorage is enabled for HTTP requests
3894
- * or not
3895
- */
3896
- static get usingAsyncLocalStorage() {
3897
- return asyncLocalStorage.isEnabled;
3898
- }
3899
- /**
3900
- * Get access to the HTTP context. Available only when
3901
- * "usingAsyncLocalStorage" is true
3902
- */
3903
- static get() {
3904
- if (!this.usingAsyncLocalStorage || !asyncLocalStorage.storage) {
3905
- return null;
3906
- }
3907
- return asyncLocalStorage.storage.getStore() || null;
3908
- }
3909
- /**
3910
- * Get the HttpContext instance or raise an exception if not
3911
- * available
3912
- */
3913
- static getOrFail() {
3914
- if (!this.usingAsyncLocalStorage || !asyncLocalStorage.storage) {
3915
- throw new RuntimeException4(
3916
- 'HTTP context is not available. Enable "useAsyncLocalStorage" inside "config/app.ts" file'
3917
- );
3918
- }
3919
- const store = this.get();
3920
- if (!store) {
3921
- throw new RuntimeException4("Http context is not available outside of an HTTP request");
3922
- }
3923
- return store;
3924
- }
3925
- /**
3926
- * Run a method that doesn't have access to HTTP context from
3927
- * the async local storage.
3928
- */
3929
- static runOutsideContext(callback, ...args) {
3930
- if (!asyncLocalStorage.storage) {
3931
- return callback(...args);
3932
- }
3933
- return asyncLocalStorage.storage.exit(callback, ...args);
3934
- }
3935
- /**
3936
- * Reference to the current route. Not available inside
3937
- * server middleware
3938
- */
3939
- route;
3940
- /**
3941
- * A unique key for the current route
3942
- */
3943
- routeKey;
3944
- /**
3945
- * Route params
3946
- */
3947
- params = {};
3948
- /**
3949
- * Route subdomains
3950
- */
3951
- subdomains = {};
3952
- /**
3953
- * A helper to see top level properties on the context object
3954
- */
3955
- /* c8 ignore next 3 */
3956
- inspect() {
3957
- return inspect(this, false, 1, true);
3958
- }
3959
- };
3960
-
3961
- // src/server/main.ts
3962
- import onFinished2 from "on-finished";
3963
- import Middleware from "@poppinss/middleware";
3964
- import { moduleCaller, moduleImporter as moduleImporter3 } from "@adonisjs/fold";
3965
-
3966
- // src/server/factories/route_finder.ts
3967
- function routeFinder(router, resolver, ctx, errorResponder) {
3968
- return function() {
3969
- const url = ctx.request.url();
3970
- const method = ctx.request.method();
3971
- const hostname = router.usingDomains ? ctx.request.hostname() : void 0;
3972
- const route = router.match(url, method, ctx.request.parsedUrl.shouldDecodeParam, hostname);
3973
- if (route) {
3974
- ctx.params = route.params;
3975
- ctx.subdomains = route.subdomains;
3976
- ctx.route = route.route;
3977
- ctx.routeKey = route.routeKey;
3978
- return route.route.execute(route.route, resolver, ctx, errorResponder);
3979
- }
3980
- return Promise.reject(new E_ROUTE_NOT_FOUND([method, url]));
3981
- };
3982
- }
3983
-
3984
- // src/server/factories/write_response.ts
3985
- function writeResponse(ctx) {
3986
- return function() {
3987
- try {
3988
- ctx.response.finish();
3989
- } catch (error) {
3990
- ctx.logger.fatal({ err: error }, "Response serialization failed");
3991
- ctx.response.internalServerError(error.message);
3992
- ctx.response.finish();
3993
- }
3994
- };
3995
- }
3996
-
3997
- // src/server/factories/middleware_handler.ts
3998
- function middlewareHandler(resolver, ctx) {
3999
- return function(fn, next) {
4000
- debug_default("executing middleware %s", fn.name);
4001
- return httpMiddleware.tracePromise(
4002
- fn.handle,
4003
- httpMiddleware.hasSubscribers ? { middleware: fn } : void 0,
4004
- void 0,
4005
- resolver,
4006
- ctx,
4007
- next
4008
- );
4009
- };
4010
- }
4011
-
4012
- // src/server/main.ts
4013
- var Server = class {
4014
- /**
4015
- * Flag indicating whether the server has been booted and initialized
4016
- */
4017
- #booted = false;
4018
- /**
4019
- * Built-in fallback error handler used when no custom handler is registered
4020
- */
4021
- #defaultErrorHandler = {
4022
- report() {
4023
- },
4024
- handle(error, ctx) {
4025
- ctx.response.status(error.status || 500).send(error.message || "Internal server error");
4026
- }
4027
- };
4028
- /**
4029
- * Logger instance for server-level logging (child loggers are created per request)
4030
- */
4031
- #logger;
4032
- /**
4033
- * Lazy import reference to the custom error handler class
4034
- */
4035
- #errorHandler;
4036
- /**
4037
- * Active error handler instance (either custom or default)
4038
- */
4039
- #resolvedErrorHandler = this.#defaultErrorHandler;
4040
- /**
4041
- * Event emitter for HTTP server lifecycle events
4042
- */
4043
- #emitter;
4044
- /**
4045
- * AdonisJS application instance providing IoC container and configuration
4046
- */
4047
- #app;
4048
- /**
4049
- * Encryption service for secure cookie handling and data encryption
4050
- */
4051
- #encryption;
4052
- /**
4053
- * Server configuration settings including timeouts, middleware options, etc.
4054
- */
4055
- #config;
4056
- /**
4057
- * Query string parser instance for URL parameter processing
4058
- */
4059
- #qsParser;
4060
- /**
4061
- * Compiled middleware stack that executes on every incoming HTTP request
4062
- */
4063
- #serverMiddlewareStack;
4064
- /**
4065
- * Router instance responsible for route registration and matching
4066
- */
4067
- #router;
4068
- /**
4069
- * Reference to the underlying Node.js HTTP or HTTPS server instance
4070
- */
4071
- #nodeHttpServer;
4072
- /**
4073
- * Collection of registered global middleware before compilation
4074
- */
4075
- #middleware = [];
4076
- /**
4077
- * Error responder function that handles exceptions in middleware and routes.
4078
- * Reports errors and delegates handling to the configured error handler.
4079
- */
4080
- #requestErrorResponder = (error, ctx) => {
4081
- this.#resolvedErrorHandler.report(error, ctx);
4082
- return httpExceptionHandler.tracePromise(
4083
- this.#resolvedErrorHandler.handle,
4084
- void 0,
4085
- this.#resolvedErrorHandler,
4086
- error,
4087
- ctx
4088
- );
4089
- };
4090
- /**
4091
- * Indicates whether the server has completed its boot process
4092
- */
4093
- get booted() {
4094
- return this.#booted;
4095
- }
4096
- /**
4097
- * Indicates whether async local storage is enabled for request context
4098
- */
4099
- get usingAsyncLocalStorage() {
4100
- return asyncLocalStorage.isEnabled;
4101
- }
4102
- /**
4103
- * Creates a new Server instance
4104
- *
4105
- * @param app - AdonisJS application instance
4106
- * @param encryption - Encryption service for secure operations
4107
- * @param emitter - Event emitter for server lifecycle events
4108
- * @param logger - Logger instance for server operations
4109
- * @param config - Server configuration settings
4110
- */
4111
- constructor(app, encryption, emitter, logger, config) {
4112
- this.#app = app;
4113
- this.#emitter = emitter;
4114
- this.#config = config;
4115
- this.#logger = logger;
4116
- this.#encryption = encryption;
4117
- this.#qsParser = new Qs(this.#config.qs);
4118
- this.#router = new Router(this.#app, this.#encryption, this.#qsParser);
4119
- this.#createAsyncLocalStore();
4120
- debug_default("server config: %O", this.#config);
4121
- }
4122
- /**
4123
- * Initializes or destroys async local storage based on configuration
4124
- */
4125
- #createAsyncLocalStore() {
4126
- if (this.#config.useAsyncLocalStorage) {
4127
- debug_default("creating ALS store for HTTP context");
4128
- asyncLocalStorage.create();
4129
- } else {
4130
- asyncLocalStorage.destroy();
4131
- }
4132
- }
4133
- /**
4134
- * Compiles registered middleware into a frozen middleware stack for execution
4135
- */
4136
- #createServerMiddlewareStack() {
4137
- this.#serverMiddlewareStack = new Middleware();
4138
- this.#middleware.forEach((middleware) => this.#serverMiddlewareStack.add(middleware));
4139
- this.#serverMiddlewareStack.freeze();
4140
- this.#middleware = [];
4141
- }
4142
- /**
4143
- * Processes an HTTP request through the middleware pipeline and routing
4144
- *
4145
- * @param ctx - HTTP context containing request/response objects
4146
- * @param resolver - Container resolver for dependency injection
4147
- * @returns Promise that resolves when request processing is complete
4148
- */
4149
- #handleRequest(ctx, resolver) {
4150
- return this.#serverMiddlewareStack.runner().errorHandler((error) => this.#requestErrorResponder(error, ctx)).finalHandler(routeFinder(this.#router, resolver, ctx, this.#requestErrorResponder)).run(middlewareHandler(resolver, ctx)).catch((error) => {
4151
- ctx.logger.fatal({ err: error }, "Exception raised by error handler");
4152
- return this.#defaultErrorHandler.handle(error, ctx);
4153
- }).finally(writeResponse(ctx));
4154
- }
4155
- /**
4156
- * Creates a testing middleware pipeline for unit/integration testing
4157
- *
4158
- * @param middleware - Array of middleware classes to include in pipeline
4159
- * @returns TestingMiddlewarePipeline instance for test execution
4160
- */
4161
- pipeline(middleware) {
4162
- const middlewareStack = new Middleware();
4163
- middleware.forEach((one) => {
4164
- middlewareStack.add({
4165
- reference: one,
4166
- ...moduleCaller(one, "handle").toHandleMethod()
4167
- });
4168
- });
4169
- middlewareStack.freeze();
4170
- const stackRunner = middlewareStack.runner();
4171
- return {
4172
- finalHandler(handler) {
4173
- stackRunner.finalHandler(handler);
4174
- return this;
4175
- },
4176
- errorHandler(handler) {
4177
- stackRunner.errorHandler(handler);
4178
- return this;
4179
- },
4180
- run(ctx) {
4181
- return stackRunner.run((handler, next) => {
4182
- return handler.handle(ctx.containerResolver, ctx, next);
4183
- });
4184
- }
4185
- };
4186
- }
4187
- /**
4188
- * Registers global middleware to run on all incoming HTTP requests
4189
- *
4190
- * @param middleware - Array of lazy-imported middleware classes
4191
- * @returns The Server instance for method chaining
4192
- */
4193
- use(middleware) {
4194
- middleware.forEach(
4195
- (one) => this.#middleware.push({
4196
- reference: one,
4197
- ...moduleImporter3(one, "handle").toHandleMethod()
4198
- })
4199
- );
4200
- return this;
4201
- }
4202
- /**
4203
- * Registers a custom error handler for HTTP request processing
4204
- *
4205
- * @param handler - Lazy import of the error handler class
4206
- * @returns The Server instance for method chaining
4207
- */
4208
- errorHandler(handler) {
4209
- this.#errorHandler = handler;
4210
- return this;
4211
- }
4212
- /**
4213
- * Initializes the server by compiling middleware, committing routes, and resolving handlers
4214
- *
4215
- * Performs the following operations:
4216
- * - Compiles the middleware stack
4217
- * - Commits registered routes to the router
4218
- * - Resolves and instantiates the custom error handler
4219
- */
4220
- async boot() {
4221
- if (this.#booted) {
4222
- return;
4223
- }
4224
- debug_default("booting HTTP server");
4225
- this.#createServerMiddlewareStack();
4226
- this.#router.commit();
4227
- if (this.#errorHandler) {
4228
- if (debug_default.enabled) {
4229
- debug_default('using custom error handler "%s"', this.#errorHandler);
4230
- }
4231
- const moduleExports = await this.#errorHandler();
4232
- this.#resolvedErrorHandler = await this.#app.container.make(moduleExports.default);
4233
- }
4234
- this.#booted = true;
4235
- }
4236
- /**
4237
- * Configures the underlying Node.js HTTP/HTTPS server with timeout settings
4238
- *
4239
- * @param server - Node.js HTTP or HTTPS server instance
4240
- */
4241
- setNodeServer(server) {
4242
- server.timeout = this.#config.timeout ?? server.timeout;
4243
- server.keepAliveTimeout = this.#config.keepAliveTimeout ?? server.keepAliveTimeout;
4244
- server.headersTimeout = this.#config.headersTimeout ?? server.headersTimeout;
4245
- server.requestTimeout = this.#config.requestTimeout ?? server.requestTimeout;
4246
- this.#nodeHttpServer = server;
4247
- }
4248
- /**
4249
- * Gets the underlying Node.js HTTP/HTTPS server instance
4250
- *
4251
- * @returns The configured server instance or undefined if not set
4252
- */
4253
- getNodeServer() {
4254
- return this.#nodeHttpServer;
4255
- }
4256
- /**
4257
- * Gets the router instance used for route registration and matching
4258
- *
4259
- * @returns The Router instance
4260
- */
4261
- getRouter() {
4262
- return this.#router;
4263
- }
4264
- /**
4265
- * Creates a Request instance from Node.js request/response objects
4266
- *
4267
- * @param req - Node.js IncomingMessage
4268
- * @param res - Node.js ServerResponse
4269
- * @returns New Request instance
4270
- */
4271
- createRequest(req, res) {
4272
- return new Request(req, res, this.#encryption, this.#config, this.#qsParser);
4273
- }
4274
- /**
4275
- * Creates a Response instance from Node.js request/response objects
4276
- *
4277
- * @param req - Node.js IncomingMessage
4278
- * @param res - Node.js ServerResponse
4279
- * @returns New Response instance
4280
- */
4281
- createResponse(req, res) {
4282
- return new Response(req, res, this.#encryption, this.#config, this.#router, this.#qsParser);
4283
- }
4284
- /**
4285
- * Creates an HttpContext instance with request-specific logger
4286
- *
4287
- * @param request - Request instance
4288
- * @param response - Response instance
4289
- * @param resolver - Container resolver for dependency injection
4290
- * @returns New HttpContext instance
4291
- */
4292
- createHttpContext(request, response, resolver) {
4293
- return new HttpContext(
4294
- request,
4295
- response,
4296
- this.#logger.child({ request_id: request.id() }),
4297
- resolver
4298
- );
4299
- }
4300
- /**
4301
- * Gets the list of registered global middleware
4302
- *
4303
- * @returns Array of parsed global middleware
4304
- */
4305
- getMiddlewareList() {
4306
- return this.#serverMiddlewareStack ? Array.from(this.#serverMiddlewareStack.all()) : [...this.#middleware];
4307
- }
4308
- /**
4309
- * Handles an incoming HTTP request by creating context and processing through pipeline
4310
- *
4311
- * @param req - Node.js IncomingMessage
4312
- * @param res - Node.js ServerResponse
4313
- * @returns Promise that resolves when request processing is complete
4314
- */
4315
- handle(req, res) {
4316
- const hasRequestListener = this.#emitter.hasListeners("http:request_completed");
4317
- const startTime = hasRequestListener ? process.hrtime() : null;
4318
- const resolver = this.#app.container.createResolver();
4319
- const ctx = this.createHttpContext(
4320
- this.createRequest(req, res),
4321
- this.createResponse(req, res),
4322
- resolver
4323
- );
4324
- if (startTime) {
4325
- onFinished2(res, () => {
4326
- this.#emitter.emit("http:request_completed", {
4327
- ctx,
4328
- duration: process.hrtime(startTime)
4329
- });
4330
- });
4331
- }
4332
- if (this.usingAsyncLocalStorage) {
4333
- return asyncLocalStorage.storage.run(
4334
- ctx,
4335
- () => httpRequest.tracePromise(
4336
- this.#handleRequest,
4337
- httpRequest.hasSubscribers ? { ctx } : void 0,
4338
- this,
4339
- ctx,
4340
- resolver
4341
- )
4342
- );
4343
- }
4344
- return httpRequest.tracePromise(
4345
- this.#handleRequest,
4346
- httpRequest.hasSubscribers ? { ctx } : void 0,
4347
- this,
4348
- ctx,
4349
- resolver
4350
- );
4351
- }
4352
- };
4353
-
4354
- // src/define_config.ts
4355
- import proxyAddr from "proxy-addr";
4356
- import string from "@poppinss/utils/string";
4357
- import lodash2 from "@poppinss/utils/lodash";
4358
- function defineConfig(config) {
4359
- const { trustProxy: trustProxy2, ...rest } = config;
4360
- const defaults = {
4361
- allowMethodSpoofing: false,
4362
- trustProxy: proxyAddr.compile("loopback"),
4363
- subdomainOffset: 2,
4364
- generateRequestId: !!config.createRequestId,
4365
- createRequestId() {
4366
- return crypto.randomUUID();
4367
- },
4368
- useAsyncLocalStorage: false,
4369
- etag: false,
4370
- jsonpCallbackName: "callback",
4371
- cookie: {
4372
- maxAge: "2h",
4373
- path: "/",
4374
- httpOnly: true,
4375
- secure: true,
4376
- sameSite: "lax"
4377
- },
4378
- qs: {
4379
- parse: {
4380
- depth: 5,
4381
- parameterLimit: 1e3,
4382
- allowSparse: false,
4383
- arrayLimit: 20,
4384
- comma: true
4385
- },
4386
- stringify: {
4387
- encode: true,
4388
- encodeValuesOnly: false,
4389
- arrayFormat: "indices",
4390
- skipNulls: false
4391
- }
4392
- }
4393
- };
4394
- const normalizedConfig = lodash2.merge({}, defaults, rest);
4395
- if (normalizedConfig.cookie.maxAge) {
4396
- normalizedConfig.cookie.maxAge = string.seconds.parse(normalizedConfig.cookie.maxAge);
4397
- }
4398
- if (typeof trustProxy2 === "boolean") {
4399
- const tpValue = trustProxy2;
4400
- normalizedConfig.trustProxy = (_, __) => tpValue;
4401
- } else if (typeof trustProxy2 === "string") {
4402
- const tpValue = trustProxy2;
4403
- normalizedConfig.trustProxy = proxyAddr.compile(tpValue);
4404
- } else if (trustProxy2) {
4405
- normalizedConfig.trustProxy = trustProxy2;
4406
- }
4407
- return normalizedConfig;
4408
- }
4409
-
4410
- export {
4411
- Qs,
4412
- E_ROUTE_NOT_FOUND,
4413
- E_CANNOT_LOOKUP_ROUTE,
4414
- E_HTTP_EXCEPTION,
4415
- E_HTTP_REQUEST_ABORTED,
4416
- errors_exports,
4417
- CookieClient,
4418
- CookieParser,
4419
- Request,
4420
- Redirect,
4421
- ResponseStatus,
4422
- CookieSerializer,
4423
- Response,
4424
- Router,
4425
- HttpContext,
4426
- Server,
4427
- defineConfig
4428
- };