routup 1.0.2 → 2.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 (100) hide show
  1. package/README.md +92 -8
  2. package/dist/dispatcher/adapters/index.d.ts +3 -0
  3. package/dist/dispatcher/adapters/node/index.d.ts +1 -0
  4. package/dist/dispatcher/adapters/node/module.d.ts +6 -0
  5. package/dist/dispatcher/adapters/raw/module.d.ts +4 -0
  6. package/dist/dispatcher/adapters/raw/type.d.ts +18 -0
  7. package/dist/dispatcher/adapters/web/index.d.ts +2 -0
  8. package/dist/dispatcher/adapters/web/module.d.ts +5 -0
  9. package/dist/dispatcher/adapters/web/type.d.ts +3 -0
  10. package/dist/dispatcher/index.d.ts +3 -0
  11. package/dist/dispatcher/type.d.ts +30 -0
  12. package/dist/dispatcher/utils.d.ts +4 -0
  13. package/dist/error.d.ts +1 -0
  14. package/dist/index.cjs +1075 -538
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.ts +4 -5
  17. package/dist/index.mjs +1054 -509
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/layer/module.d.ts +5 -4
  20. package/dist/layer/type.d.ts +1 -2
  21. package/dist/layer/utils.d.ts +1 -1
  22. package/dist/path/index.d.ts +1 -0
  23. package/dist/path/matcher.d.ts +1 -2
  24. package/dist/path/type.d.ts +1 -0
  25. package/dist/path/utils.d.ts +2 -0
  26. package/dist/{helpers/request → request/helpers}/body.d.ts +1 -1
  27. package/dist/{helpers/request → request/helpers}/cache.d.ts +1 -1
  28. package/dist/{helpers/request → request/helpers}/cookie.d.ts +1 -1
  29. package/dist/{helpers/request → request/helpers}/env.d.ts +1 -1
  30. package/dist/{helpers/request → request/helpers}/header-accept-charset.d.ts +1 -1
  31. package/dist/{helpers/request → request/helpers}/header-accept-language.d.ts +1 -1
  32. package/dist/{helpers/request → request/helpers}/header-accept.d.ts +1 -1
  33. package/dist/{helpers/request → request/helpers}/header-content-type.d.ts +1 -1
  34. package/dist/{helpers/request → request/helpers}/header.d.ts +1 -1
  35. package/dist/{helpers/request → request/helpers}/hostname.d.ts +1 -1
  36. package/dist/{helpers/request → request/helpers}/index.d.ts +1 -0
  37. package/dist/{helpers/request → request/helpers}/ip.d.ts +1 -1
  38. package/dist/{helpers/request → request/helpers}/mount-path.d.ts +1 -1
  39. package/dist/{helpers/request → request/helpers}/negotiator.d.ts +1 -1
  40. package/dist/{helpers/request → request/helpers}/params.d.ts +1 -1
  41. package/dist/{helpers/request → request/helpers}/path.d.ts +1 -1
  42. package/dist/{helpers/request → request/helpers}/protocol.d.ts +1 -1
  43. package/dist/{helpers/request → request/helpers}/query.d.ts +1 -1
  44. package/dist/request/helpers/router.d.ts +3 -0
  45. package/dist/request/index.d.ts +3 -0
  46. package/dist/request/module.d.ts +4 -0
  47. package/dist/request/types.d.ts +9 -0
  48. package/dist/{helpers/response → response/helpers}/cache.d.ts +1 -1
  49. package/dist/response/helpers/gone.d.ts +2 -0
  50. package/dist/{helpers/response → response/helpers}/header-attachment.d.ts +1 -1
  51. package/dist/{helpers/response → response/helpers}/header-content-type.d.ts +1 -1
  52. package/dist/{helpers/response → response/helpers}/header.d.ts +1 -1
  53. package/dist/{helpers/response → response/helpers}/index.d.ts +3 -0
  54. package/dist/{helpers/response → response/helpers}/send-accepted.d.ts +2 -2
  55. package/dist/{helpers/response → response/helpers}/send-created.d.ts +2 -2
  56. package/dist/response/helpers/send-file.d.ts +17 -0
  57. package/dist/{helpers/response → response/helpers}/send-format.d.ts +1 -1
  58. package/dist/response/helpers/send-redirect.d.ts +2 -0
  59. package/dist/response/helpers/send-stream.d.ts +2 -0
  60. package/dist/response/helpers/send-web-blob.d.ts +2 -0
  61. package/dist/response/helpers/send-web-response.d.ts +2 -0
  62. package/dist/response/helpers/send.d.ts +2 -0
  63. package/dist/response/helpers/utils.d.ts +2 -0
  64. package/dist/response/index.d.ts +2 -0
  65. package/dist/response/module.d.ts +2 -0
  66. package/dist/route/module.d.ts +6 -5
  67. package/dist/route/type.d.ts +1 -1
  68. package/dist/route/utils.d.ts +1 -1
  69. package/dist/router/index.d.ts +0 -1
  70. package/dist/router/module.d.ts +13 -32
  71. package/dist/router/utils.d.ts +1 -0
  72. package/dist/router-options/index.d.ts +2 -0
  73. package/dist/router-options/module.d.ts +4 -0
  74. package/dist/router-options/transform.d.ts +2 -0
  75. package/dist/router-options/type.d.ts +50 -0
  76. package/dist/types.d.ts +19 -0
  77. package/dist/utils/cookie.d.ts +1 -0
  78. package/dist/utils/etag/module.d.ts +4 -3
  79. package/dist/utils/etag/type.d.ts +1 -1
  80. package/dist/utils/header.d.ts +3 -0
  81. package/dist/utils/index.d.ts +4 -1
  82. package/dist/utils/path.d.ts +5 -2
  83. package/dist/utils/stream.d.ts +8 -0
  84. package/dist/utils/web.d.ts +3 -0
  85. package/package.json +17 -16
  86. package/dist/config/module.d.ts +0 -8
  87. package/dist/config/type.d.ts +0 -34
  88. package/dist/handler/index.d.ts +0 -1
  89. package/dist/handler/utils.d.ts +0 -2
  90. package/dist/helpers/index.d.ts +0 -2
  91. package/dist/helpers/response/send-file.d.ts +0 -9
  92. package/dist/helpers/response/send-redirect.d.ts +0 -2
  93. package/dist/helpers/response/send-stream.d.ts +0 -4
  94. package/dist/helpers/response/send.d.ts +0 -2
  95. package/dist/helpers/response/utils.d.ts +0 -3
  96. package/dist/router/type.d.ts +0 -24
  97. package/dist/type.d.ts +0 -24
  98. package/dist/utils/request.d.ts +0 -2
  99. /package/dist/{config → dispatcher/adapters/raw}/index.d.ts +0 -0
  100. /package/dist/{helpers/request → request/helpers}/header-accept-encoding.d.ts +0 -0
package/dist/index.mjs CHANGED
@@ -1,17 +1,231 @@
1
- import { Continu } from 'continu';
2
- import process from 'node:process';
3
- import zod from 'zod';
4
- import crypto from 'node:crypto';
5
- import { Stats, stat, createReadStream } from 'node:fs';
1
+ import { Buffer } from 'buffer';
2
+ import { subtle } from 'uncrypto';
6
3
  import { merge, hasOwnProperty, distinctArray } from 'smob';
7
- import proxyAddr from 'proxy-addr';
4
+ import { compile, all } from 'proxy-addr';
8
5
  import { getType, get } from 'mime-explorer';
9
- import { GatewayTimeoutErrorOptions, BadRequestError } from '@ebec/http';
10
6
  import Negotiator from 'negotiator';
11
- import { URL } from 'node:url';
12
- import path from 'node:path';
7
+ import { Writable, Readable } from 'readable-stream';
13
8
  import { pathToRegexp } from 'path-to-regexp';
14
- import { createServer } from 'node:http';
9
+
10
+ var MethodName;
11
+ (function(MethodName) {
12
+ MethodName["GET"] = 'get';
13
+ MethodName["POST"] = 'post';
14
+ MethodName["PUT"] = 'put';
15
+ MethodName["PATCH"] = 'patch';
16
+ MethodName["DELETE"] = 'delete';
17
+ MethodName["OPTIONS"] = 'options';
18
+ MethodName["HEAD"] = 'head';
19
+ })(MethodName || (MethodName = {}));
20
+ var HeaderName;
21
+ (function(HeaderName) {
22
+ HeaderName["ACCEPT"] = 'accept';
23
+ HeaderName["ACCEPT_CHARSET"] = 'accept-charset';
24
+ HeaderName["ACCEPT_ENCODING"] = 'accept-encoding';
25
+ HeaderName["ACCEPT_LANGUAGE"] = 'accept-language';
26
+ HeaderName["ACCEPT_RANGES"] = 'accept-ranges';
27
+ HeaderName["ALLOW"] = 'allow';
28
+ HeaderName["CACHE_CONTROL"] = 'cache-control';
29
+ HeaderName["CONTENT_DISPOSITION"] = 'content-disposition';
30
+ HeaderName["CONTENT_ENCODING"] = 'content-encoding';
31
+ HeaderName["CONTENT_LENGTH"] = 'content-length';
32
+ HeaderName["CONTENT_RANGE"] = 'content-range';
33
+ HeaderName["CONTENT_TYPE"] = 'content-type';
34
+ HeaderName["COOKIE"] = 'cookie';
35
+ HeaderName["ETag"] = 'etag';
36
+ HeaderName["HOST"] = 'host';
37
+ HeaderName["IF_MODIFIED_SINCE"] = 'if-modified-since';
38
+ HeaderName["IF_NONE_MATCH"] = 'if-none-match';
39
+ HeaderName["LAST_MODIFIED"] = 'last-modified';
40
+ HeaderName["LOCATION"] = 'location';
41
+ HeaderName["RANGE"] = 'range';
42
+ HeaderName["RATE_LIMIT_LIMIT"] = 'ratelimit-limit';
43
+ HeaderName["RATE_LIMIT_REMAINING"] = 'ratelimit-remaining';
44
+ HeaderName["RATE_LIMIT_RESET"] = 'ratelimit-reset';
45
+ HeaderName["RETRY_AFTER"] = 'retry-after';
46
+ HeaderName["SET_COOKIE"] = 'set-cookie';
47
+ HeaderName["TRANSFER_ENCODING"] = 'transfer-encoding';
48
+ HeaderName["X_FORWARDED_HOST"] = 'x-forwarded-host';
49
+ HeaderName["X_FORWARDED_FOR"] = 'x-forwarded-for';
50
+ HeaderName["X_FORWARDED_PROTO"] = 'x-forwarded-proto';
51
+ })(HeaderName || (HeaderName = {}));
52
+
53
+ function setResponseCacheHeaders(res, options) {
54
+ options = options || {};
55
+ const cacheControls = [
56
+ 'public'
57
+ ].concat(options.cacheControls || []);
58
+ if (options.maxAge !== undefined) {
59
+ cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
60
+ }
61
+ if (options.modifiedTime) {
62
+ const modifiedTime = typeof options.modifiedTime === 'string' ? new Date(options.modifiedTime) : options.modifiedTime;
63
+ res.setHeader('last-modified', modifiedTime.toUTCString());
64
+ }
65
+ res.setHeader('cache-control', cacheControls.join(', '));
66
+ }
67
+
68
+ const GoneSymbol = Symbol.for('ResGone');
69
+ function isResponseGone(res) {
70
+ if (res.headersSent || res.writableEnded) {
71
+ return true;
72
+ }
73
+ if (GoneSymbol in res) {
74
+ return res[GoneSymbol];
75
+ }
76
+ return false;
77
+ }
78
+
79
+ function appendResponseHeader(res, name, value) {
80
+ let header = res.getHeader(name);
81
+ if (!header) {
82
+ res.setHeader(name, value);
83
+ return;
84
+ }
85
+ if (!Array.isArray(header)) {
86
+ header = [
87
+ header.toString()
88
+ ];
89
+ }
90
+ res.setHeader(name, [
91
+ ...header,
92
+ value
93
+ ]);
94
+ }
95
+ function appendResponseHeaderDirective(res, name, value) {
96
+ let header = res.getHeader(name);
97
+ if (!header) {
98
+ if (Array.isArray(value)) {
99
+ res.setHeader(name, value.join('; '));
100
+ return;
101
+ }
102
+ res.setHeader(name, value);
103
+ return;
104
+ }
105
+ if (!Array.isArray(header)) {
106
+ if (typeof header === 'string') {
107
+ // split header by directive(s)
108
+ header = header.split('; ');
109
+ }
110
+ if (typeof header === 'number') {
111
+ header = [
112
+ header.toString()
113
+ ];
114
+ }
115
+ }
116
+ if (Array.isArray(value)) {
117
+ header.push(...value);
118
+ } else {
119
+ header.push(`${value}`);
120
+ }
121
+ header = [
122
+ ...new Set(header)
123
+ ];
124
+ res.setHeader(name, header.join('; '));
125
+ }
126
+
127
+ /*
128
+ Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
129
+ that are within a single set-cookie field-value, such as in the Expires portion.
130
+
131
+ This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
132
+ Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
133
+ React Native's fetch does this for *every* header, including set-cookie.
134
+
135
+ Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
136
+ Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
137
+ */ function splitCookiesString(input) {
138
+ if (Array.isArray(input)) {
139
+ return input.flatMap((el)=>splitCookiesString(el));
140
+ }
141
+ if (typeof input !== 'string') {
142
+ return [];
143
+ }
144
+ const cookiesStrings = [];
145
+ let pos = 0;
146
+ let start;
147
+ let ch;
148
+ let lastComma;
149
+ let nextStart;
150
+ let cookiesSeparatorFound;
151
+ const skipWhitespace = ()=>{
152
+ while(pos < input.length && /\s/.test(input.charAt(pos))){
153
+ pos += 1;
154
+ }
155
+ return pos < input.length;
156
+ };
157
+ const notSpecialChar = ()=>{
158
+ ch = input.charAt(pos);
159
+ return ch !== '=' && ch !== ';' && ch !== ',';
160
+ };
161
+ while(pos < input.length){
162
+ start = pos;
163
+ cookiesSeparatorFound = false;
164
+ while(skipWhitespace()){
165
+ ch = input.charAt(pos);
166
+ if (ch === ',') {
167
+ // ',' is a cookie separator if we have later first '=', not ';' or ','
168
+ lastComma = pos;
169
+ pos += 1;
170
+ skipWhitespace();
171
+ nextStart = pos;
172
+ while(pos < input.length && notSpecialChar()){
173
+ pos += 1;
174
+ }
175
+ // currently special character
176
+ if (pos < input.length && input.charAt(pos) === '=') {
177
+ // we found cookies separator
178
+ cookiesSeparatorFound = true;
179
+ // pos is inside the next cookie, so back up and return it.
180
+ pos = nextStart;
181
+ cookiesStrings.push(input.substring(start, lastComma));
182
+ start = pos;
183
+ } else {
184
+ // in param ',' or param separator ';',
185
+ // we continue from that comma
186
+ pos = lastComma + 1;
187
+ }
188
+ } else {
189
+ pos += 1;
190
+ }
191
+ }
192
+ if (!cookiesSeparatorFound || pos >= input.length) {
193
+ cookiesStrings.push(input.substring(start, input.length));
194
+ }
195
+ }
196
+ return cookiesStrings;
197
+ }
198
+
199
+ function transformHeaderToTuples(key, value) {
200
+ const output = [];
201
+ if (Array.isArray(value)) {
202
+ for(let j = 0; j < value.length; j++){
203
+ output.push([
204
+ key,
205
+ value[j]
206
+ ]);
207
+ }
208
+ } else if (value !== undefined) {
209
+ output.push([
210
+ key,
211
+ String(value)
212
+ ]);
213
+ }
214
+ return output;
215
+ }
216
+ function transformHeadersToTuples(input) {
217
+ const output = [];
218
+ const keys = Object.keys(input);
219
+ for(let i = 0; i < keys.length; i++){
220
+ const key = keys[i].toLowerCase();
221
+ output.push(...transformHeaderToTuples(key, input[key]));
222
+ }
223
+ return output;
224
+ }
225
+
226
+ function isObject(item) {
227
+ return !!item && typeof item === 'object' && !Array.isArray(item);
228
+ }
15
229
 
16
230
  /**
17
231
  * Determine if object is a Stats object.
@@ -20,15 +234,17 @@ import { createServer } from 'node:http';
20
234
  * @return {boolean}
21
235
  * @api private
22
236
  */ function isStatsObject(obj) {
23
- /* istanbul ignore next */ if (typeof Stats === 'function' && obj instanceof Stats) {
24
- return true;
25
- }
26
237
  // quack quack
27
- return !!obj && typeof obj === 'object' && 'ctime' in obj && Object.prototype.toString.call(obj.ctime) === '[object Date]' && 'mtime' in obj && Object.prototype.toString.call(obj.mtime) === '[object Date]' && 'ino' in obj && typeof obj.ino === 'number' && 'size' in obj && typeof obj.size === 'number';
238
+ return isObject(obj) && 'ctime' in obj && Object.prototype.toString.call(obj.ctime) === '[object Date]' && 'mtime' in obj && Object.prototype.toString.call(obj.mtime) === '[object Date]' && 'ino' in obj && typeof obj.ino === 'number' && 'size' in obj && typeof obj.size === 'number';
239
+ }
240
+ async function sha1(str) {
241
+ const enc = new TextEncoder();
242
+ const hash = await subtle.digest('SHA-1', enc.encode(str));
243
+ return btoa(String.fromCharCode(...new Uint8Array(hash)));
28
244
  }
29
245
  /**
30
246
  * Generate an ETag.
31
- */ function generateETag(input) {
247
+ */ async function generateETag(input) {
32
248
  if (isStatsObject(input)) {
33
249
  const mtime = input.mtime.getTime().toString(16);
34
250
  const size = input.size.toString(16);
@@ -40,30 +256,26 @@ import { createServer } from 'node:http';
40
256
  }
41
257
  const entity = Buffer.isBuffer(input) ? input.toString('utf-8') : input;
42
258
  // compute hash of entity
43
- const hash = crypto.createHash('sha1').update(entity, 'utf8').digest('base64').substring(0, 27);
44
- return `"${entity.length.toString(16)}-${hash}"`;
259
+ const hash = await sha1(entity);
260
+ return `"${entity.length.toString(16)}-${hash.substring(0, 27)}"`;
45
261
  }
46
262
  /**
47
263
  * Create a simple ETag.
48
- */ function createEtag(input, options) {
264
+ */ async function createEtag(input, options) {
49
265
  options = options || {};
50
266
  const weak = typeof options.weak === 'boolean' ? options.weak : isStatsObject(input);
51
267
  // generate entity tag
52
- const tag = generateETag(input);
268
+ const tag = await generateETag(input);
53
269
  return weak ? `W/${tag}` : tag;
54
270
  }
55
271
 
56
- function isObject(item) {
57
- return !!item && typeof item === 'object' && !Array.isArray(item);
58
- }
59
-
60
272
  function buildEtagFn(input) {
61
273
  if (typeof input === 'function') {
62
274
  return input;
63
275
  }
64
276
  input = input ?? true;
65
277
  if (input === false) {
66
- return ()=>undefined;
278
+ return ()=>Promise.resolve(undefined);
67
279
  }
68
280
  let options = {
69
281
  weak: true
@@ -71,7 +283,7 @@ function buildEtagFn(input) {
71
283
  if (isObject(input)) {
72
284
  options = merge(input, options);
73
285
  }
74
- return (body, encoding, size)=>{
286
+ return async (body, encoding, size)=>{
75
287
  const buff = Buffer.isBuffer(body) ? body : Buffer.from(body, encoding);
76
288
  if (typeof options.threshold !== 'undefined') {
77
289
  size = size ?? Buffer.byteLength(buff);
@@ -96,11 +308,11 @@ function buildTrustProxyFn(input) {
96
308
  if (typeof input === 'string') {
97
309
  input = input.split(',').map((value)=>value.trim());
98
310
  }
99
- return proxyAddr.compile(input || []);
311
+ return compile(input || []);
100
312
  }
101
313
 
102
314
  function isInstance(input, name) {
103
- return typeof input === 'object' && input !== null && input['@instanceof'] === Symbol.for(name);
315
+ return isObject(input) && input['@instanceof'] === Symbol.for(name);
104
316
  }
105
317
 
106
318
  function getMimeType(type) {
@@ -120,8 +332,25 @@ function getCharsetForMimeType(type) {
120
332
  return undefined;
121
333
  }
122
334
 
123
- function isPath(input) {
124
- return typeof input === 'string' || input instanceof RegExp;
335
+ /**
336
+ * Based on https://github.com/unjs/pathe v1.1.1 (055f50a6f1131f4e5c56cf259dd8816168fba329)
337
+ */ function normalizeWindowsPath(input = '') {
338
+ if (!input || !input.includes('\\')) {
339
+ return input;
340
+ }
341
+ return input.replace(/\\/g, '/');
342
+ }
343
+ const EXTNAME_RE = /.(\.[^./]+)$/;
344
+ function extname(input) {
345
+ const match = EXTNAME_RE.exec(normalizeWindowsPath(input));
346
+ return match && match[1] || '';
347
+ }
348
+ function basename(input, extension) {
349
+ const lastSegment = normalizeWindowsPath(input).split('/').pop();
350
+ if (!lastSegment) {
351
+ return input;
352
+ }
353
+ return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
125
354
  }
126
355
 
127
356
  function isPromise(p) {
@@ -130,24 +359,14 @@ function isPromise(p) {
130
359
  typeof p.then === 'function');
131
360
  }
132
361
 
133
- /* istanbul ignore next */ function createRequestTimeout(res, timeout, done) {
134
- const instance = setTimeout(()=>{
135
- res.statusCode = GatewayTimeoutErrorOptions.statusCode;
136
- res.statusMessage = GatewayTimeoutErrorOptions.message;
137
- res.end();
138
- }, timeout);
139
- res.once('close', ()=>{
140
- clearTimeout(instance);
141
- /* istanbul ignore next */ if (typeof done === 'function') {
142
- done();
143
- }
144
- });
145
- /* istanbul ignore next */ res.once('error', (e)=>{
146
- clearTimeout(instance);
147
- if (typeof done === 'function') {
148
- done(e);
149
- }
150
- });
362
+ function isNodeStream(input) {
363
+ return isObject(input) && typeof input.pipe === 'function' && typeof input.read === 'function';
364
+ }
365
+ function isWebStream(input) {
366
+ return isObject(input) && typeof input.pipeTo === 'function';
367
+ }
368
+ function isStream(data) {
369
+ return isNodeStream(data) || isWebStream(data);
151
370
  }
152
371
 
153
372
  const TRAILING_SLASH_RE = /\/$|\/\?/;
@@ -167,22 +386,9 @@ function withoutTrailingSlash(input = '', queryParams = false) {
167
386
  const [s0, ...s] = input.split('?');
168
387
  return (s0.slice(0, -1) || '/') + (s.length ? `?${s.join('?')}` : '');
169
388
  }
170
- function withTrailingSlash(input = '', queryParams = false) {
171
- if (!queryParams) {
172
- return input.endsWith('/') ? input : `${input}/`;
173
- }
174
- if (hasTrailingSlash(input, true)) {
175
- return input || '/';
176
- }
177
- const [s0, ...s] = input.split('?');
178
- return `${s0}/${s.length ? `?${s.join('?')}` : ''}`;
179
- }
180
389
  function hasLeadingSlash(input = '') {
181
390
  return input.startsWith('/');
182
391
  }
183
- function withoutLeadingSlash(input = '') {
184
- return (hasLeadingSlash(input) ? input.substr(1) : input) || '/';
185
- }
186
392
  function withLeadingSlash(input = '') {
187
393
  return hasLeadingSlash(input) ? input : `/${input}`;
188
394
  }
@@ -190,91 +396,73 @@ function cleanDoubleSlashes(input = '') {
190
396
  return input.split('://').map((str)=>str.replace(/\/{2,}/g, '/')).join('://');
191
397
  }
192
398
 
193
- let instance;
194
- function buildConfig() {
195
- return new Continu({
196
- defaults: {
197
- env: process.env.NODE_ENV || 'development',
198
- trustProxy: ()=>false,
199
- subdomainOffset: 2,
200
- etag: buildEtagFn(),
201
- proxyIpMax: 0
202
- },
203
- transformers: {
204
- etag: (value)=>buildEtagFn(value),
205
- trustProxy: (value)=>buildTrustProxyFn(value)
206
- },
207
- validators: {
208
- env: (value)=>zod.string().safeParse(value),
209
- trustProxy: (value)=>zod.any().safeParse(value),
210
- subdomainOffset: (value)=>zod.number().nonnegative().safeParse(value),
211
- etag: (value)=>zod.any().safeParse(value),
212
- proxyIpMax: (value)=>zod.number().nonnegative().safeParse(value)
213
- }
214
- });
399
+ function isWebBlob(input) {
400
+ return typeof Blob !== 'undefined' && input instanceof Blob;
215
401
  }
216
- function useConfig() {
217
- if (typeof instance !== 'undefined') {
218
- return instance;
219
- }
220
- instance = buildConfig();
221
- return instance;
402
+ function isWebResponse(input) {
403
+ return typeof Response !== 'undefined' && input instanceof Response;
222
404
  }
223
- function setConfig(config) {
224
- instance = config;
405
+
406
+ function setResponseContentTypeByFileName(res, fileName) {
407
+ const ext = extname(fileName);
408
+ if (ext) {
409
+ let type = getMimeType(ext.substring(1));
410
+ if (type) {
411
+ const charset = getCharsetForMimeType(type);
412
+ if (charset) {
413
+ type += `; charset=${charset}`;
414
+ }
415
+ res.setHeader(HeaderName.CONTENT_TYPE, type);
416
+ }
417
+ }
225
418
  }
226
- function setConfigOption(key, value) {
227
- const config = useConfig();
228
- config.setRaw(key, value);
229
- return config.get();
419
+
420
+ function setResponseHeaderAttachment(res, filename) {
421
+ if (typeof filename === 'string') {
422
+ setResponseContentTypeByFileName(res, filename);
423
+ }
424
+ res.setHeader(HeaderName.CONTENT_DISPOSITION, `attachment${filename ? `; filename="${filename}"` : ''}`);
230
425
  }
231
- function getConfigOption(key) {
232
- const config = useConfig();
233
- return config.get(key);
426
+
427
+ function setResponseHeaderContentType(res, input, ifNotExists) {
428
+ if (ifNotExists) {
429
+ const header = res.getHeader(HeaderName.CONTENT_TYPE);
430
+ if (header) {
431
+ return;
432
+ }
433
+ }
434
+ const contentType = getMimeType(input);
435
+ if (contentType) {
436
+ res.setHeader(HeaderName.CONTENT_TYPE, contentType);
437
+ }
234
438
  }
235
439
 
236
- var MethodName;
237
- (function(MethodName) {
238
- MethodName["GET"] = 'get';
239
- MethodName["POST"] = 'post';
240
- MethodName["PUT"] = 'put';
241
- MethodName["PATCH"] = 'patch';
242
- MethodName["DELETE"] = 'delete';
243
- MethodName["OPTIONS"] = 'options';
244
- MethodName["HEAD"] = 'head';
245
- })(MethodName || (MethodName = {}));
246
- var HeaderName;
247
- (function(HeaderName) {
248
- HeaderName["ACCEPT"] = 'accept';
249
- HeaderName["ACCEPT_CHARSET"] = 'accept-charset';
250
- HeaderName["ACCEPT_ENCODING"] = 'accept-encoding';
251
- HeaderName["ACCEPT_LANGUAGE"] = 'accept-language';
252
- HeaderName["ACCEPT_RANGES"] = 'accept-ranges';
253
- HeaderName["ALLOW"] = 'allow';
254
- HeaderName["CACHE_CONTROL"] = 'cache-control';
255
- HeaderName["CONTENT_DISPOSITION"] = 'content-disposition';
256
- HeaderName["CONTENT_ENCODING"] = 'content-encoding';
257
- HeaderName["CONTENT_LENGTH"] = 'content-length';
258
- HeaderName["CONTENT_RANGE"] = 'content-range';
259
- HeaderName["CONTENT_TYPE"] = 'content-type';
260
- HeaderName["COOKIE"] = 'cookie';
261
- HeaderName["ETag"] = 'etag';
262
- HeaderName["HOST"] = 'host';
263
- HeaderName["IF_MODIFIED_SINCE"] = 'if-modified-since';
264
- HeaderName["IF_NONE_MATCH"] = 'if-none-match';
265
- HeaderName["LAST_MODIFIED"] = 'last-modified';
266
- HeaderName["LOCATION"] = 'location';
267
- HeaderName["RANGE"] = 'range';
268
- HeaderName["RATE_LIMIT_LIMIT"] = 'ratelimit-limit';
269
- HeaderName["RATE_LIMIT_REMAINING"] = 'ratelimit-remaining';
270
- HeaderName["RATE_LIMIT_RESET"] = 'ratelimit-reset';
271
- HeaderName["RETRY_AFTER"] = 'retry-after';
272
- HeaderName["SET_COOKIE"] = 'set-cookie';
273
- HeaderName["TRANSFER_ENCODING"] = 'transfer-encoding';
274
- HeaderName["X_FORWARDED_HOST"] = 'x-forwarded-host';
275
- HeaderName["X_FORWARDED_FOR"] = 'x-forwarded-for';
276
- HeaderName["X_FORWARDED_PROTO"] = 'x-forwarded-proto';
277
- })(HeaderName || (HeaderName = {}));
440
+ const defaults = {
441
+ trustProxy: ()=>false,
442
+ subdomainOffset: 2,
443
+ etag: buildEtagFn(),
444
+ proxyIpMax: 0
445
+ };
446
+ const instances = {};
447
+ function setRouterOptions(id, input) {
448
+ instances[id] = input;
449
+ }
450
+ function findRouterOption(key, id) {
451
+ if (!id) {
452
+ return defaults[key];
453
+ }
454
+ const ids = Array.isArray(id) ? id : [
455
+ id
456
+ ];
457
+ if (ids.length > 0) {
458
+ for(let i = ids.length; i >= 0; i--){
459
+ if (hasOwnProperty(instances, ids[i]) && typeof instances[ids[i]][key] !== 'undefined') {
460
+ return instances[ids[i]][key];
461
+ }
462
+ }
463
+ }
464
+ return defaults[key];
465
+ }
278
466
 
279
467
  const BodySymbol = Symbol.for('ReqBody');
280
468
  function useRequestBody(req, key) {
@@ -537,14 +725,24 @@ function matchRequestContentType(req, contentType) {
537
725
  return header.split('; ').shift() === getMimeType(contentType);
538
726
  }
539
727
 
728
+ const routerSymbol = Symbol.for('ReqRouterID');
729
+ function setRequestRouterIds(req, ids) {
730
+ req[routerSymbol] = ids;
731
+ }
732
+ function useRequestRouterIds(req) {
733
+ if (routerSymbol in req) {
734
+ return req[routerSymbol];
735
+ }
736
+ return undefined;
737
+ }
738
+
540
739
  function getRequestHostName(req, options) {
541
740
  options = options || {};
542
741
  let trustProxy;
543
742
  if (typeof options.trustProxy !== 'undefined') {
544
743
  trustProxy = buildTrustProxyFn(options.trustProxy);
545
744
  } else {
546
- const config = useConfig();
547
- trustProxy = config.get('trustProxy');
745
+ trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
548
746
  }
549
747
  let hostname = req.headers[HeaderName.X_FORWARDED_HOST];
550
748
  if (!hostname || !req.socket.remoteAddress || !trustProxy(req.socket.remoteAddress, 0)) {
@@ -570,10 +768,10 @@ function getRequestIP(req, options) {
570
768
  if (typeof options.trustProxy !== 'undefined') {
571
769
  trustProxy = buildTrustProxyFn(options.trustProxy);
572
770
  } else {
573
- const config = useConfig();
574
- trustProxy = config.get('trustProxy');
771
+ trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
575
772
  }
576
- return proxyAddr(req, trustProxy);
773
+ const addrs = all(req, trustProxy);
774
+ return addrs[addrs.length - 1];
577
775
  }
578
776
 
579
777
  const ReqMountPathSymbol = Symbol.for('ReqMountPath');
@@ -631,8 +829,7 @@ function getRequestProtocol(req, options) {
631
829
  if (typeof options.trustProxy !== 'undefined') {
632
830
  trustProxy = buildTrustProxyFn(options.trustProxy);
633
831
  } else {
634
- const config = useConfig();
635
- trustProxy = config.get('trustProxy');
832
+ trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
636
833
  }
637
834
  let protocol = options.default;
638
835
  /* istanbul ignore next */ if (hasOwnProperty(req.socket, 'encrypted') && !!req.socket.encrypted) {
@@ -700,121 +897,7 @@ function extendRequestQuery(req, key, value) {
700
897
  setRequestQuery(req, key, value);
701
898
  }
702
899
 
703
- function setResponseCacheHeaders(res, options) {
704
- options = options || {};
705
- const cacheControls = [
706
- 'public'
707
- ].concat(options.cacheControls || []);
708
- if (options.maxAge !== undefined) {
709
- cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
710
- }
711
- if (options.modifiedTime) {
712
- const modifiedTime = typeof options.modifiedTime === 'string' ? new Date(options.modifiedTime) : options.modifiedTime;
713
- res.setHeader('last-modified', modifiedTime.toUTCString());
714
- }
715
- res.setHeader('cache-control', cacheControls.join(', '));
716
- }
717
-
718
- function appendResponseHeader(res, name, value) {
719
- let header = res.getHeader(name);
720
- if (!header) {
721
- res.setHeader(name, value);
722
- return;
723
- }
724
- if (!Array.isArray(header)) {
725
- header = [
726
- header.toString()
727
- ];
728
- }
729
- res.setHeader(name, [
730
- ...header,
731
- value
732
- ]);
733
- }
734
- function appendResponseHeaderDirective(res, name, value) {
735
- let header = res.getHeader(name);
736
- if (!header) {
737
- if (Array.isArray(value)) {
738
- res.setHeader(name, value.join('; '));
739
- return;
740
- }
741
- res.setHeader(name, value);
742
- return;
743
- }
744
- if (!Array.isArray(header)) {
745
- if (typeof header === 'string') {
746
- // split header by directive(s)
747
- header = header.split('; ');
748
- }
749
- if (typeof header === 'number') {
750
- header = [
751
- header.toString()
752
- ];
753
- }
754
- }
755
- if (Array.isArray(value)) {
756
- header.push(...value);
757
- } else {
758
- header.push(`${value}`);
759
- }
760
- header = [
761
- ...new Set(header)
762
- ];
763
- res.setHeader(name, header.join('; '));
764
- }
765
-
766
- function setResponseContentTypeByFileName(res, fileName) {
767
- const ext = path.extname(fileName);
768
- if (ext) {
769
- let type = getMimeType(ext.substring(1));
770
- if (type) {
771
- const charset = getCharsetForMimeType(type);
772
- if (charset) {
773
- type += `; charset=${charset}`;
774
- }
775
- res.setHeader(HeaderName.CONTENT_TYPE, type);
776
- }
777
- }
778
- }
779
- /* istanbul ignore next */ function onResponseFinished(res, cb) {
780
- let called;
781
- const callCallback = (err)=>{
782
- if (called) return;
783
- called = true;
784
- cb(err);
785
- };
786
- res.on('finish', ()=>{
787
- callCallback();
788
- });
789
- res.on('close', ()=>{
790
- callCallback();
791
- });
792
- res.on('error', (err)=>{
793
- callCallback(err);
794
- });
795
- }
796
-
797
- function setResponseHeaderAttachment(res, filename) {
798
- if (typeof filename === 'string') {
799
- setResponseContentTypeByFileName(res, filename);
800
- }
801
- res.setHeader(HeaderName.CONTENT_DISPOSITION, `attachment${filename ? `; filename="${filename}"` : ''}`);
802
- }
803
-
804
- function setResponseHeaderContentType(res, input, ifNotExists) {
805
- if (ifNotExists) {
806
- const header = res.getHeader(HeaderName.CONTENT_TYPE);
807
- if (header) {
808
- return;
809
- }
810
- }
811
- const contentType = getMimeType(input);
812
- if (contentType) {
813
- res.setHeader(HeaderName.CONTENT_TYPE, contentType);
814
- }
815
- }
816
-
817
- function send(res, chunk) {
900
+ async function send(res, chunk) {
818
901
  switch(typeof chunk){
819
902
  case 'string':
820
903
  {
@@ -859,10 +942,12 @@ function send(res, chunk) {
859
942
  }
860
943
  res.setHeader(HeaderName.CONTENT_LENGTH, `${len}`);
861
944
  }
862
- const config = useConfig();
863
- const etagFn = config.get('etag');
864
945
  if (typeof len !== 'undefined') {
865
- const chunkHash = etagFn(chunk, encoding, len);
946
+ const etagFn = findRouterOption('etag', useRequestRouterIds(res.req));
947
+ const chunkHash = await etagFn(chunk, encoding, len);
948
+ if (isResponseGone(res)) {
949
+ return Promise.resolve();
950
+ }
866
951
  if (typeof chunkHash === 'string') {
867
952
  res.setHeader(HeaderName.ETag, chunkHash);
868
953
  if (res.req.headers[HeaderName.IF_NONE_MATCH] === chunkHash) {
@@ -883,16 +968,20 @@ function send(res, chunk) {
883
968
  res.removeHeader(HeaderName.TRANSFER_ENCODING);
884
969
  chunk = '';
885
970
  }
971
+ if (isResponseGone(res)) {
972
+ return Promise.resolve();
973
+ }
886
974
  if (res.req.method === 'HEAD') {
887
975
  // skip body for HEAD
888
976
  res.end();
889
- return;
977
+ return Promise.resolve();
890
978
  }
891
979
  if (typeof encoding !== 'undefined') {
892
980
  res.end(chunk, encoding);
893
- return;
981
+ return Promise.resolve();
894
982
  }
895
983
  res.end(chunk);
984
+ return Promise.resolve();
896
985
  }
897
986
 
898
987
  function sendAccepted(res, chunk) {
@@ -907,87 +996,116 @@ function sendCreated(res, chunk) {
907
996
  return send(res, chunk);
908
997
  }
909
998
 
910
- function sendStream(res, stream, fn) {
911
- stream.on('open', ()=>{
912
- stream.pipe(res);
913
- });
914
- /* istanbul ignore next */ stream.on('error', (err)=>{
915
- if (typeof fn === 'function') {
916
- fn(err);
917
- } else {
918
- res.statusCode = 400;
999
+ async function sendStream(res, stream, next) {
1000
+ if (isWebStream(stream)) {
1001
+ return stream.pipeTo(new WritableStream({
1002
+ write (chunk) {
1003
+ res.write(chunk);
1004
+ }
1005
+ })).then(()=>{
1006
+ if (next) {
1007
+ return next();
1008
+ }
919
1009
  res.end();
920
- }
921
- });
922
- stream.on('close', ()=>{
923
- if (typeof fn === 'function') {
924
- fn();
925
- } else {
1010
+ return Promise.resolve();
1011
+ }).catch((err)=>{
1012
+ if (next) {
1013
+ return next(err);
1014
+ }
1015
+ return Promise.reject(err);
1016
+ });
1017
+ }
1018
+ return new Promise((resolve, reject)=>{
1019
+ stream.on('open', ()=>{
1020
+ stream.pipe(res);
1021
+ });
1022
+ /* istanbul ignore next */ stream.on('error', (err)=>{
1023
+ if (next) {
1024
+ Promise.resolve().then(()=>next(err)).then(()=>resolve()).catch((e)=>reject(e));
1025
+ return;
1026
+ }
926
1027
  res.end();
927
- }
1028
+ reject(err);
1029
+ });
1030
+ stream.on('close', ()=>{
1031
+ if (next) {
1032
+ Promise.resolve().then(()=>next()).then(()=>resolve()).catch((e)=>reject(e));
1033
+ return;
1034
+ }
1035
+ res.end();
1036
+ resolve();
1037
+ });
928
1038
  });
929
1039
  }
930
1040
 
931
- function resolveStats(options, cb) {
932
- if (options.stats) {
933
- cb(null, options.stats);
934
- return;
935
- }
936
- stat(options.filePath, (err, stats)=>cb(err, stats));
937
- }
938
- function sendFile(res, filePath, fn) {
939
- let options;
940
- if (typeof filePath === 'string') {
941
- options = {
942
- filePath
943
- };
944
- } else {
945
- options = filePath;
946
- }
947
- const fileName = path.basename(options.filePath);
948
- if (options.attachment) {
949
- const dispositionHeader = res.getHeader(HeaderName.CONTENT_DISPOSITION);
950
- if (!dispositionHeader) {
951
- setResponseHeaderAttachment(res, fileName);
1041
+ async function sendFile(res, options, next) {
1042
+ let stats;
1043
+ try {
1044
+ stats = await options.stats();
1045
+ } catch (e) {
1046
+ if (next) {
1047
+ return next(e);
952
1048
  }
953
- } else {
954
- setResponseContentTypeByFileName(res, fileName);
955
- }
956
- resolveStats(options, (err, stats)=>{
957
- /* istanbul ignore next */ if (err) {
958
- if (typeof fn === 'function') {
959
- fn(err);
960
- } else {
961
- res.statusCode = 404;
962
- res.end();
1049
+ if (isResponseGone(res)) {
1050
+ return Promise.resolve();
1051
+ }
1052
+ return Promise.reject(e);
1053
+ }
1054
+ const name = options.name || stats.name;
1055
+ if (name) {
1056
+ const fileName = basename(name);
1057
+ if (options.attachment) {
1058
+ const dispositionHeader = res.getHeader(HeaderName.CONTENT_DISPOSITION);
1059
+ if (!dispositionHeader) {
1060
+ setResponseHeaderAttachment(res, fileName);
963
1061
  }
964
- return;
1062
+ } else {
1063
+ setResponseContentTypeByFileName(res, fileName);
965
1064
  }
966
- const streamOptions = {};
1065
+ }
1066
+ const contentOptions = {};
1067
+ if (stats.size) {
967
1068
  const rangeHeader = res.req.headers[HeaderName.RANGE];
968
1069
  if (rangeHeader) {
969
1070
  const [x, y] = rangeHeader.replace('bytes=', '').split('-');
970
- streamOptions.end = Math.min(parseInt(y, 10) || stats.size - 1, stats.size - 1);
971
- streamOptions.start = parseInt(x, 10) || 0;
972
- if (streamOptions.end >= stats.size) {
973
- streamOptions.end = stats.size - 1;
1071
+ contentOptions.end = Math.min(parseInt(y, 10) || stats.size - 1, stats.size - 1);
1072
+ contentOptions.start = parseInt(x, 10) || 0;
1073
+ if (contentOptions.end >= stats.size) {
1074
+ contentOptions.end = stats.size - 1;
974
1075
  }
975
- if (streamOptions.start >= stats.size) {
1076
+ if (contentOptions.start >= stats.size) {
976
1077
  res.setHeader(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
977
1078
  res.statusCode = 416;
978
1079
  res.end();
979
- return;
1080
+ return Promise.resolve();
980
1081
  }
981
- res.setHeader(HeaderName.CONTENT_RANGE, `bytes ${streamOptions.start}-${streamOptions.end}/${stats.size}`);
982
- res.setHeader(HeaderName.CONTENT_LENGTH, streamOptions.end - streamOptions.start + 1);
1082
+ res.setHeader(HeaderName.CONTENT_RANGE, `bytes ${contentOptions.start}-${contentOptions.end}/${stats.size}`);
1083
+ res.setHeader(HeaderName.CONTENT_LENGTH, contentOptions.end - contentOptions.start + 1);
983
1084
  } else {
984
1085
  res.setHeader(HeaderName.CONTENT_LENGTH, stats.size);
985
1086
  }
986
1087
  res.setHeader(HeaderName.ACCEPT_RANGES, 'bytes');
987
- res.setHeader(HeaderName.LAST_MODIFIED, stats.mtime.toUTCString());
988
- res.setHeader(HeaderName.ETag, `W/"${stats.size}-${stats.mtime.getTime()}"`);
989
- sendStream(res, createReadStream(options.filePath, streamOptions), fn);
990
- });
1088
+ if (stats.mtime) {
1089
+ const mtime = new Date(stats.mtime);
1090
+ res.setHeader(HeaderName.LAST_MODIFIED, mtime.toUTCString());
1091
+ res.setHeader(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
1092
+ }
1093
+ }
1094
+ try {
1095
+ const content = await options.content(contentOptions);
1096
+ if (isStream(content)) {
1097
+ return await sendStream(res, content, next);
1098
+ }
1099
+ return await send(res, content);
1100
+ } catch (e) {
1101
+ if (next) {
1102
+ return next(e);
1103
+ }
1104
+ if (isResponseGone(res)) {
1105
+ return Promise.resolve();
1106
+ }
1107
+ return Promise.reject(e);
1108
+ }
991
1109
  }
992
1110
 
993
1111
  function sendFormat(res, input) {
@@ -1009,19 +1127,406 @@ function sendRedirect(res, location, statusCode = 302) {
1009
1127
  return send(res, html);
1010
1128
  }
1011
1129
 
1012
- function processHandlerExecutionOutput(res, next, output) {
1013
- if (isPromise(output)) {
1014
- output.then((r)=>{
1015
- if (typeof r !== 'undefined') {
1016
- send(res, r);
1130
+ function sendWebResponse(res, webResponse) {
1131
+ if (webResponse.redirected) {
1132
+ res.setHeader(HeaderName.LOCATION, webResponse.url);
1133
+ }
1134
+ if (webResponse.status) {
1135
+ res.statusCode = webResponse.status;
1136
+ }
1137
+ if (webResponse.statusText) {
1138
+ res.statusMessage = webResponse.statusText;
1139
+ }
1140
+ webResponse.headers.forEach((value, key)=>{
1141
+ if (key === HeaderName.SET_COOKIE) {
1142
+ res.appendHeader(key, splitCookiesString(value));
1143
+ } else {
1144
+ res.setHeader(key, value);
1145
+ }
1146
+ });
1147
+ if (webResponse.body) {
1148
+ return sendStream(res, webResponse.body);
1149
+ }
1150
+ res.end();
1151
+ return Promise.resolve();
1152
+ }
1153
+
1154
+ function sendWebBlob(res, blob) {
1155
+ setResponseHeaderContentType(res, blob.type);
1156
+ return sendStream(res, blob.stream());
1157
+ }
1158
+
1159
+ function createResponse(request) {
1160
+ let output;
1161
+ let encoding;
1162
+ const write = (chunk, chunkEncoding, callback)=>{
1163
+ if (typeof chunk !== 'undefined') {
1164
+ const chunkEncoded = typeof chunk === 'string' ? Buffer.from(chunk, chunkEncoding || encoding || 'utf8') : chunk;
1165
+ if (typeof output !== 'undefined') {
1166
+ output = Buffer.concat([
1167
+ output,
1168
+ chunkEncoded
1169
+ ]);
1170
+ } else {
1171
+ output = chunkEncoded;
1017
1172
  }
1018
- return r;
1019
- }).catch(next);
1020
- return;
1173
+ }
1174
+ encoding = chunkEncoding;
1175
+ if (callback) {
1176
+ callback();
1177
+ }
1178
+ };
1179
+ const writable = new Writable({
1180
+ decodeStrings: false,
1181
+ write (chunk, arg2, arg3) {
1182
+ const chunkEncoding = typeof arg2 === 'string' ? encoding : 'utf-8';
1183
+ let cb;
1184
+ if (typeof arg2 === 'function') {
1185
+ cb = arg2;
1186
+ } else if (typeof arg3 === 'function') {
1187
+ cb = arg3;
1188
+ }
1189
+ write(chunk, chunkEncoding, cb);
1190
+ return true;
1191
+ }
1192
+ });
1193
+ Object.defineProperty(writable, 'body', {
1194
+ get () {
1195
+ if (output) {
1196
+ const arrayBuffer = new ArrayBuffer(output.length);
1197
+ const view = new Uint8Array(arrayBuffer);
1198
+ for(let i = 0; i < output.length; ++i){
1199
+ view[i] = output[i];
1200
+ }
1201
+ return arrayBuffer;
1202
+ }
1203
+ return new ArrayBuffer(0);
1204
+ }
1205
+ });
1206
+ const headers = {};
1207
+ Object.assign(writable, {
1208
+ req: request,
1209
+ chunkedEncoding: false,
1210
+ connection: null,
1211
+ headersSent: false,
1212
+ sendDate: false,
1213
+ shouldKeepAlive: false,
1214
+ socket: null,
1215
+ statusCode: 200,
1216
+ statusMessage: '',
1217
+ strictContentLength: false,
1218
+ useChunkedEncodingByDefault: false,
1219
+ finished: false,
1220
+ addTrailers (_headers) {},
1221
+ appendHeader (name, value) {
1222
+ if (name === HeaderName.SET_COOKIE) {
1223
+ value = splitCookiesString(value);
1224
+ }
1225
+ name = name.toLowerCase();
1226
+ const current = headers[name];
1227
+ const all = [
1228
+ ...Array.isArray(current) ? current : [
1229
+ current
1230
+ ],
1231
+ ...Array.isArray(value) ? value : [
1232
+ value
1233
+ ]
1234
+ ].filter(Boolean);
1235
+ headers[name] = all.length > 1 ? all : all[0];
1236
+ return this;
1237
+ },
1238
+ assignSocket (_socket) {},
1239
+ detachSocket (_socket) {},
1240
+ flushHeaders () {},
1241
+ getHeader (name) {
1242
+ return headers[name.toLowerCase()];
1243
+ },
1244
+ getHeaderNames () {
1245
+ return Object.keys(headers);
1246
+ },
1247
+ getHeaders () {
1248
+ return headers;
1249
+ },
1250
+ hasHeader (name) {
1251
+ return hasOwnProperty(headers, name.toLowerCase());
1252
+ },
1253
+ removeHeader (name) {
1254
+ delete headers[name.toLowerCase()];
1255
+ },
1256
+ setHeader (name, value) {
1257
+ if (name === HeaderName.SET_COOKIE && typeof value !== 'number') {
1258
+ value = splitCookiesString(value);
1259
+ }
1260
+ headers[name.toLowerCase()] = value;
1261
+ return this;
1262
+ },
1263
+ setTimeout (_msecs, _callback) {
1264
+ return this;
1265
+ },
1266
+ writeContinue (_callback) {},
1267
+ writeEarlyHints (_hints, callback) {
1268
+ if (typeof callback !== 'undefined') {
1269
+ callback();
1270
+ }
1271
+ },
1272
+ writeProcessing () {},
1273
+ writeHead (statusCode, arg1, arg2) {
1274
+ this.statusCode = statusCode;
1275
+ if (typeof arg1 === 'string') {
1276
+ this.statusMessage = arg1;
1277
+ arg1 = undefined;
1278
+ }
1279
+ const headers = arg2 || arg1;
1280
+ if (headers) {
1281
+ if (Array.isArray(headers)) {
1282
+ for(let i = 0; i < headers.length; i++){
1283
+ const keys = Object.keys(headers[i]);
1284
+ for(let j = 0; j < keys.length; j++){
1285
+ this.setHeader(keys[i], headers[i][keys[j]]);
1286
+ }
1287
+ }
1288
+ } else {
1289
+ const keys = Object.keys(headers);
1290
+ for(let i = 0; i < keys.length; i++){
1291
+ this.setHeader(keys[i], headers[keys[i]]);
1292
+ }
1293
+ }
1294
+ }
1295
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1296
+ // @ts-ignore
1297
+ this.headersSent = true;
1298
+ return this;
1299
+ }
1300
+ });
1301
+ return writable;
1302
+ }
1303
+
1304
+ async function dispatchNodeRequest(router, req, res) {
1305
+ try {
1306
+ const dispatched = await router.dispatch({
1307
+ req,
1308
+ res
1309
+ });
1310
+ if (dispatched) {
1311
+ return;
1312
+ }
1313
+ if (!isResponseGone(res)) {
1314
+ res.statusCode = 404;
1315
+ res.end();
1316
+ }
1317
+ } catch (e) {
1318
+ if (!isResponseGone(res)) {
1319
+ res.statusCode = 500;
1320
+ res.end();
1321
+ }
1322
+ }
1323
+ }
1324
+ function createNodeDispatcher(router) {
1325
+ return (req, res)=>{
1326
+ // eslint-disable-next-line no-void
1327
+ void dispatchNodeRequest(router, req, res);
1328
+ };
1329
+ }
1330
+
1331
+ function createRequest(context) {
1332
+ let readable;
1333
+ if (context.body) {
1334
+ if (isWebStream(context.body)) {
1335
+ readable = Readable.fromWeb(context.body);
1336
+ } else {
1337
+ readable = Readable.from(context.body);
1338
+ }
1339
+ } else {
1340
+ readable = new Readable();
1341
+ }
1342
+ const headers = context.headers || {};
1343
+ const rawHeaders = [];
1344
+ let keys = Object.keys(headers);
1345
+ for(let i = 0; i < keys.length; i++){
1346
+ const header = headers[keys[i]];
1347
+ if (Array.isArray(header)) {
1348
+ for(let j = 0; j < header.length; j++){
1349
+ rawHeaders.push(keys[i], header[j]);
1350
+ }
1351
+ } else if (typeof header === 'string') {
1352
+ rawHeaders.push(keys[i], header);
1353
+ }
1354
+ }
1355
+ const headersDistinct = {};
1356
+ keys = Object.keys(headers);
1357
+ for(let i = 0; i < keys.length; i++){
1358
+ const header = headers[keys[i]];
1359
+ if (Array.isArray(header)) {
1360
+ headersDistinct[keys[i]] = header;
1361
+ }
1362
+ if (typeof header === 'string') {
1363
+ headersDistinct[keys[i]] = [
1364
+ header
1365
+ ];
1366
+ }
1367
+ }
1368
+ Object.defineProperty(readable, 'connection', {
1369
+ get () {
1370
+ return {};
1371
+ }
1372
+ });
1373
+ Object.defineProperty(readable, 'socket', {
1374
+ get () {
1375
+ return {};
1376
+ }
1377
+ });
1378
+ Object.assign(readable, {
1379
+ aborted: false,
1380
+ complete: true,
1381
+ headers,
1382
+ headersDistinct,
1383
+ httpVersion: '1.1',
1384
+ httpVersionMajor: 1,
1385
+ httpVersionMinor: 1,
1386
+ method: context.method || 'GET',
1387
+ rawHeaders,
1388
+ rawTrailers: [],
1389
+ trailers: {},
1390
+ trailersDistinct: {},
1391
+ url: context.url || '/',
1392
+ setTimeout (_msecs, _callback) {
1393
+ return this;
1394
+ }
1395
+ });
1396
+ return readable;
1397
+ }
1398
+
1399
+ async function dispatchRawRequest(router, request, options = {}) {
1400
+ const req = createRequest({
1401
+ url: request.path,
1402
+ method: request.method,
1403
+ body: request.body,
1404
+ headers: request.headers
1405
+ });
1406
+ const res = createResponse(req);
1407
+ const getHeaders = ()=>{
1408
+ const output = {};
1409
+ const headers = res.getHeaders();
1410
+ const keys = Object.keys(headers);
1411
+ for(let i = 0; i < keys.length; i++){
1412
+ const header = headers[keys[i]];
1413
+ if (typeof header === 'number') {
1414
+ output[keys[i]] = `${header}`;
1415
+ } else if (header) {
1416
+ output[keys[i]] = header;
1417
+ }
1418
+ }
1419
+ return output;
1420
+ };
1421
+ try {
1422
+ const dispatched = await router.dispatch({
1423
+ req,
1424
+ res
1425
+ });
1426
+ if (dispatched) {
1427
+ return {
1428
+ status: res.statusCode,
1429
+ statusMessage: res.statusMessage,
1430
+ headers: getHeaders(),
1431
+ body: res.body
1432
+ };
1433
+ }
1434
+ return {
1435
+ status: 404,
1436
+ headers: getHeaders(),
1437
+ body: res.body
1438
+ };
1439
+ } catch (e) {
1440
+ if (options.throwOnError) {
1441
+ throw e;
1442
+ }
1443
+ return {
1444
+ status: 500,
1445
+ headers: getHeaders(),
1446
+ body: res.body
1447
+ };
1021
1448
  }
1022
- if (typeof output !== 'undefined') {
1023
- send(res, output);
1449
+ }
1450
+ function createRawDispatcher(router) {
1451
+ return async (request)=>dispatchRawRequest(router, request);
1452
+ }
1453
+
1454
+ async function dispatchWebRequest(router, request, options = {}) {
1455
+ const url = new URL(request.url);
1456
+ const headers = {};
1457
+ request.headers.forEach((value, key)=>{
1458
+ headers[key] = value;
1459
+ });
1460
+ const res = await dispatchRawRequest(router, {
1461
+ method: request.method,
1462
+ path: url.pathname + url.search,
1463
+ headers,
1464
+ body: request.body
1465
+ }, options);
1466
+ let body;
1467
+ if (request.method === MethodName.HEAD || res.status === 304 || res.status === 101 || res.status === 204 || res.status === 205) {
1468
+ body = null;
1469
+ } else {
1470
+ body = res.body;
1024
1471
  }
1472
+ return new Response(body, {
1473
+ headers: transformHeadersToTuples(res.headers),
1474
+ status: res.status,
1475
+ statusText: res.statusMessage
1476
+ });
1477
+ }
1478
+ function createWebDispatcher(router) {
1479
+ return async (request)=>dispatchWebRequest(router, request);
1480
+ }
1481
+
1482
+ function cloneDispatcherMeta(input) {
1483
+ if (!input) {
1484
+ return {};
1485
+ }
1486
+ return {
1487
+ path: input.path,
1488
+ mountPath: input.mountPath,
1489
+ error: input.error,
1490
+ routerIds: [
1491
+ ...input.routerIds || []
1492
+ ],
1493
+ params: cloneDispatcherMetaParams(input.params)
1494
+ };
1495
+ }
1496
+ function cloneDispatcherMetaParams(input) {
1497
+ if (typeof input === 'undefined') {
1498
+ return {};
1499
+ }
1500
+ const keys = Object.keys(input);
1501
+ const output = {};
1502
+ for(let i = 0; i < keys.length; i++){
1503
+ output[keys[i]] = input[keys[i]];
1504
+ }
1505
+ return output;
1506
+ }
1507
+ function mergeDispatcherMetaParams(t1, t2) {
1508
+ if (!t1 && !t2) {
1509
+ return {};
1510
+ }
1511
+ if (!t1 || !t2) {
1512
+ return t1 || t2;
1513
+ }
1514
+ const keys = Object.keys(t2);
1515
+ for(let i = 0; i < keys.length; i++){
1516
+ t1[keys[i]] = t2[keys[i]];
1517
+ }
1518
+ return t1;
1519
+ }
1520
+
1521
+ function createError(input) {
1522
+ if (input instanceof Error) {
1523
+ return input;
1524
+ }
1525
+ const error = new Error();
1526
+ if (typeof input.message === 'string') {
1527
+ error.message = input.message;
1528
+ }
1529
+ return error;
1025
1530
  }
1026
1531
 
1027
1532
  function decodeParam(val) {
@@ -1085,48 +1590,98 @@ class PathMatcher {
1085
1590
  }
1086
1591
  }
1087
1592
 
1593
+ function isPath(input) {
1594
+ return typeof input === 'string' || input instanceof RegExp;
1595
+ }
1596
+
1088
1597
  class Layer {
1089
1598
  // --------------------------------------------------
1090
1599
  isError() {
1091
1600
  return this.fn.length === 4;
1092
1601
  }
1093
- dispatch(req, res, meta, next, err) {
1094
- setRequestParams(req, meta.params || {});
1095
- setRequestMountPath(req, meta.mountPath || '/');
1096
- if (typeof err !== 'undefined') {
1097
- if (this.fn.length === 4) {
1098
- try {
1099
- this.fn(err, req, res, next);
1100
- } catch (e) {
1101
- /* istanbul ignore next */ /* istanbul ignore next */ if (e instanceof Error) {
1102
- next(e);
1103
- } else {
1104
- next(new BadRequestError({
1105
- message: 'The request could not be processed by the error handler.'
1106
- }));
1602
+ // --------------------------------------------------
1603
+ dispatch(event, meta) {
1604
+ setRequestParams(event.req, meta.params || {});
1605
+ setRequestMountPath(event.req, meta.mountPath || '/');
1606
+ setRequestRouterIds(event.req, meta.routerIds || []);
1607
+ if (this.fn.length !== 4 && meta.error || this.fn.length === 4 && !meta.error) {
1608
+ return Promise.reject(meta.error);
1609
+ }
1610
+ const timeout = findRouterOption('timeout', meta.routerIds);
1611
+ return new Promise((resolve, reject)=>{
1612
+ let timeoutInstance;
1613
+ let handled = false;
1614
+ const unsubscribe = ()=>{
1615
+ if (timeoutInstance) {
1616
+ clearTimeout(timeoutInstance);
1617
+ }
1618
+ event.res.off('close', onFinished);
1619
+ event.res.off('error', onFinished);
1620
+ };
1621
+ const shutdown = (dispatched, err)=>{
1622
+ if (handled) {
1623
+ return;
1624
+ }
1625
+ handled = true;
1626
+ unsubscribe();
1627
+ if (err) {
1628
+ reject(createError(err));
1629
+ } else {
1630
+ resolve(dispatched);
1631
+ }
1632
+ };
1633
+ const onFinished = (err)=>shutdown(true, err);
1634
+ const onNext = (err)=>shutdown(false, err);
1635
+ event.res.once('close', onFinished);
1636
+ event.res.once('error', onFinished);
1637
+ if (timeout) {
1638
+ timeoutInstance = setTimeout(()=>{
1639
+ handled = true;
1640
+ unsubscribe();
1641
+ event.res.statusCode = 504;
1642
+ event.res.statusMessage = 'Gateway Timeout';
1643
+ event.res.end();
1644
+ }, timeout);
1645
+ }
1646
+ try {
1647
+ let output;
1648
+ if (meta.error) {
1649
+ output = this.fn(meta.error, event.req, event.res, onNext);
1650
+ } else {
1651
+ output = this.fn(event.req, event.res, onNext);
1652
+ }
1653
+ const handle = (data)=>{
1654
+ if (typeof data === 'undefined' || handled) {
1655
+ return Promise.resolve();
1107
1656
  }
1657
+ handled = true;
1658
+ unsubscribe();
1659
+ return this.sendOutput(event.res, data).then(()=>resolve(true)).catch((e)=>reject(createError(e)));
1660
+ };
1661
+ if (isPromise(output)) {
1662
+ output.then((r)=>handle(r)).catch((e)=>reject(createError(e)));
1663
+ return;
1108
1664
  }
1109
- return;
1665
+ Promise.resolve().then(()=>handle(output)).catch((e)=>reject(createError(e)));
1666
+ } catch (error) {
1667
+ onNext(error);
1110
1668
  }
1111
- /* istanbul ignore next */ next(err);
1112
- /* istanbul ignore next */ return;
1669
+ });
1670
+ }
1671
+ sendOutput(res, input) {
1672
+ if (input instanceof Error) {
1673
+ return Promise.reject(input);
1113
1674
  }
1114
- /* istanbul ignore next */ if (this.fn.length > 3) {
1115
- next();
1116
- return;
1675
+ if (isStream(input)) {
1676
+ return sendStream(res, input);
1117
1677
  }
1118
- try {
1119
- const output = this.fn(req, res, next);
1120
- processHandlerExecutionOutput(res, next, output);
1121
- } catch (e) {
1122
- /* istanbul ignore next */ if (e instanceof Error) {
1123
- next(e);
1124
- } else {
1125
- next(new BadRequestError({
1126
- message: 'The request could not be processed by the handler.'
1127
- }));
1128
- }
1678
+ if (isWebBlob(input)) {
1679
+ return sendWebBlob(res, input);
1680
+ }
1681
+ if (isWebResponse(input)) {
1682
+ return sendWebResponse(res, input);
1129
1683
  }
1684
+ return send(res, input);
1130
1685
  }
1131
1686
  // --------------------------------------------------
1132
1687
  matchPath(path) {
@@ -1144,6 +1699,9 @@ class Layer {
1144
1699
  }
1145
1700
 
1146
1701
  function isLayerInstance(input) {
1702
+ if (input instanceof Layer) {
1703
+ return true;
1704
+ }
1147
1705
  return isInstance(input, 'Layer');
1148
1706
  }
1149
1707
 
@@ -1168,44 +1726,46 @@ class Route {
1168
1726
  return keys;
1169
1727
  }
1170
1728
  // --------------------------------------------------
1171
- dispatch(req, res, meta, done) {
1172
- /* istanbul ignore next */ if (!req.method) {
1173
- done();
1174
- return;
1729
+ async dispatch(event, meta) {
1730
+ /* istanbul ignore next */ if (!event.req.method) {
1731
+ return false;
1175
1732
  }
1176
- let name = req.method.toLowerCase();
1733
+ let name = event.req.method.toLowerCase();
1177
1734
  if (name === MethodName.HEAD && !hasOwnProperty(this.layers, name)) {
1178
1735
  name = MethodName.GET;
1179
1736
  }
1180
1737
  const layers = this.layers[name];
1181
1738
  /* istanbul ignore next */ if (typeof layers === 'undefined' || layers.length === 0 || typeof meta.path === 'undefined') {
1182
- done();
1183
- return;
1739
+ return false;
1184
1740
  }
1185
- const layerMeta = {
1186
- ...meta
1187
- };
1741
+ const layerMeta = cloneDispatcherMeta(meta);
1188
1742
  const output = this.pathMatcher.exec(meta.path);
1189
1743
  if (output) {
1190
- layerMeta.params = merge({}, meta.params || {}, output.params);
1744
+ layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1191
1745
  }
1192
- let index = -1;
1193
- const next = (err)=>{
1194
- index++;
1195
- if (index >= layers.length) {
1196
- setImmediate(done, err);
1197
- return;
1198
- }
1199
- const layer = layers[index];
1746
+ let err;
1747
+ for(let i = 0; i < layers.length; i++){
1748
+ const layer = layers[i];
1200
1749
  if (err && !layer.isError()) {
1201
- next(err);
1202
- return;
1750
+ continue;
1203
1751
  }
1204
- layer.dispatch(req, res, {
1205
- ...layerMeta
1206
- }, next);
1207
- };
1208
- next();
1752
+ try {
1753
+ const dispatched = await layer.dispatch(event, {
1754
+ ...layerMeta
1755
+ });
1756
+ if (dispatched) {
1757
+ return true;
1758
+ }
1759
+ } catch (e) {
1760
+ if (e instanceof Error) {
1761
+ err = e;
1762
+ }
1763
+ }
1764
+ }
1765
+ if (err) {
1766
+ throw err;
1767
+ }
1768
+ return false;
1209
1769
  }
1210
1770
  // --------------------------------------------------
1211
1771
  register(method, ...handlers) {
@@ -1258,42 +1818,50 @@ class Route {
1258
1818
  }
1259
1819
 
1260
1820
  function isRouteInstance(input) {
1821
+ if (input instanceof Route) {
1822
+ return true;
1823
+ }
1261
1824
  return isInstance(input, 'Route');
1262
1825
  }
1263
1826
 
1827
+ function transformRouterOptions(input) {
1828
+ if (typeof input.etag !== 'undefined') {
1829
+ input.etag = buildEtagFn(input.etag);
1830
+ }
1831
+ if (typeof input.trustProxy !== 'undefined') {
1832
+ input.trustProxy = buildTrustProxyFn(input.trustProxy);
1833
+ }
1834
+ return input;
1835
+ }
1836
+
1837
+ let nextId = 0;
1838
+ function generateRouterID() {
1839
+ return ++nextId;
1840
+ }
1841
+
1264
1842
  function isRouterInstance(input) {
1843
+ if (input instanceof Router) {
1844
+ return true;
1845
+ }
1265
1846
  return isInstance(input, 'Router');
1266
1847
  }
1267
1848
  class Router {
1268
1849
  // --------------------------------------------------
1269
- setPathMatcherOptions(input) {
1270
- this.pathMatcherOptions = input;
1271
- if (this.pathMatcher) {
1272
- this.pathMatcher.regexpOptions = this.pathMatcherOptions;
1273
- }
1274
- }
1275
1850
  setPath(value) {
1276
1851
  if (value === '/' || !isPath(value)) {
1277
- this.path = '/';
1278
1852
  return;
1279
1853
  }
1854
+ let path;
1280
1855
  if (typeof value === 'string') {
1281
- this.path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1856
+ path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1282
1857
  } else {
1283
- this.path = value;
1858
+ path = value;
1284
1859
  }
1285
- this.pathMatcher = new PathMatcher(this.path, this.pathMatcherOptions);
1286
- }
1287
- // --------------------------------------------------
1288
- createListener() {
1289
- this.isRoot = true;
1290
- return (req, res)=>{
1291
- this.dispatch(req, res);
1292
- };
1293
- }
1294
- /* istanbul ignore next */ listen(port) {
1295
- const server = createServer(this.createListener());
1296
- return server.listen(port);
1860
+ this.pathMatcher = new PathMatcher(path, {
1861
+ end: false,
1862
+ sensitive: false,
1863
+ ...this.pathMatcherOptions ? this.pathMatcherOptions : {}
1864
+ });
1297
1865
  }
1298
1866
  // --------------------------------------------------
1299
1867
  matchPath(path) {
@@ -1303,35 +1871,9 @@ class Router {
1303
1871
  return true;
1304
1872
  }
1305
1873
  // --------------------------------------------------
1306
- dispatch(req, res, meta, done) {
1307
- let index = -1;
1308
- meta = meta || {};
1309
- let allowedMethods = [];
1310
- if (this.isRoot && typeof this.timeout === 'number') {
1311
- createRequestTimeout(res, this.timeout, done);
1312
- }
1313
- const fn = (err)=>{
1314
- /* istanbul ignore if */ if (!this.isRoot) {
1315
- if (typeof done !== 'undefined') {
1316
- setImmediate(()=>done(err));
1317
- }
1318
- return;
1319
- }
1320
- if (typeof err !== 'undefined') {
1321
- res.statusCode = 400;
1322
- res.end();
1323
- return;
1324
- }
1325
- if (req.method && req.method.toLowerCase() === MethodName.OPTIONS) {
1326
- const options = allowedMethods.map((key)=>key.toUpperCase()).join(',');
1327
- res.setHeader(HeaderName.ALLOW, options);
1328
- send(res, options);
1329
- return;
1330
- }
1331
- res.statusCode = 404;
1332
- res.end();
1333
- };
1334
- let path = meta.path || useRequestPath(req);
1874
+ async dispatch(event, meta = {}) {
1875
+ const allowedMethods = [];
1876
+ let path = meta.path || useRequestPath(event.req);
1335
1877
  if (this.pathMatcher) {
1336
1878
  const output = this.pathMatcher.exec(path);
1337
1879
  if (typeof output !== 'undefined') {
@@ -1345,74 +1887,78 @@ class Router {
1345
1887
  }
1346
1888
  }
1347
1889
  meta.path = path;
1890
+ if (meta.routerIds) {
1891
+ meta.routerIds.push(this.id);
1892
+ } else {
1893
+ meta.routerIds = [
1894
+ this.id
1895
+ ];
1896
+ }
1348
1897
  if (!meta.mountPath) {
1349
1898
  meta.mountPath = '/';
1350
1899
  }
1351
- const next = (err)=>{
1352
- if (index >= this.stack.length) {
1353
- setImmediate(fn, err);
1354
- return;
1355
- }
1356
- let layer;
1357
- let match = false;
1358
- while(!match && index < this.stack.length){
1359
- index++;
1360
- layer = this.stack[index];
1361
- if (isLayerInstance(layer)) {
1362
- if (!layer.isError() && err) {
1363
- continue;
1364
- }
1365
- match = layer.matchPath(path);
1900
+ let err;
1901
+ let layer;
1902
+ let match = false;
1903
+ for(let i = 0; i < this.stack.length; i++){
1904
+ layer = this.stack[i];
1905
+ if (layer instanceof Layer) {
1906
+ if (!layer.isError() && err) {
1907
+ continue;
1366
1908
  }
1367
- if (isRouterInstance(layer)) {
1368
- match = layer.matchPath(path);
1369
- }
1370
- if (isRouteInstance(layer)) {
1371
- match = layer.matchPath(path);
1372
- if (req.method && !layer.matchMethod(req.method)) {
1373
- match = false;
1374
- if (req.method.toLowerCase() === MethodName.OPTIONS) {
1375
- allowedMethods = distinctArray(merge(allowedMethods, layer.getMethods()));
1376
- }
1909
+ match = layer.matchPath(path);
1910
+ }
1911
+ if (isRouterInstance(layer)) {
1912
+ match = layer.matchPath(path);
1913
+ }
1914
+ if (isRouteInstance(layer)) {
1915
+ match = layer.matchPath(path);
1916
+ if (event.req.method && !layer.matchMethod(event.req.method)) {
1917
+ match = false;
1918
+ if (event.req.method.toLowerCase() === MethodName.OPTIONS) {
1919
+ allowedMethods.push(...layer.getMethods());
1377
1920
  }
1378
1921
  }
1379
1922
  }
1380
- if (!match || !layer) {
1381
- setImmediate(fn, err);
1382
- return;
1923
+ if (!match) {
1924
+ continue;
1383
1925
  }
1384
- const layerMeta = {
1385
- ...meta
1386
- };
1926
+ const layerMeta = cloneDispatcherMeta(meta);
1387
1927
  if (isLayerInstance(layer)) {
1388
1928
  const output = layer.exec(path);
1389
1929
  if (output) {
1390
- layerMeta.params = merge(output.params, layerMeta.params || {});
1930
+ layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1391
1931
  layerMeta.mountPath = cleanDoubleSlashes(`${layerMeta.mountPath || ''}/${output.path}`);
1392
1932
  }
1393
- }
1394
- if (err) {
1395
- if (isLayerInstance(layer) && layer.isError()) {
1396
- layer.dispatch(req, res, layerMeta, next, err);
1397
- return;
1933
+ if (err) {
1934
+ layerMeta.error = err;
1398
1935
  }
1399
- /* istanbul ignore next */ setImmediate(next, err);
1400
- return;
1936
+ } else if (err) {
1937
+ continue;
1401
1938
  }
1402
- layer.dispatch(req, res, layerMeta, next);
1403
- };
1404
- next();
1405
- }
1406
- /* istanbul ignore next */ dispatchAsync(req, res) {
1407
- return new Promise((resolve, reject)=>{
1408
- this.dispatch(req, res, {}, (err)=>{
1409
- if (err) {
1410
- reject(err);
1411
- return;
1939
+ try {
1940
+ const dispatched = await layer.dispatch(event, layerMeta);
1941
+ if (dispatched) {
1942
+ return true;
1412
1943
  }
1413
- resolve();
1414
- });
1415
- });
1944
+ } catch (e) {
1945
+ if (e instanceof Error) {
1946
+ err = e;
1947
+ }
1948
+ }
1949
+ }
1950
+ if (err) {
1951
+ throw err;
1952
+ }
1953
+ if (event.req.method && event.req.method.toLowerCase() === MethodName.OPTIONS) {
1954
+ const options = distinctArray(allowedMethods).map((key)=>key.toUpperCase()).join(',');
1955
+ if (!isResponseGone(event.res)) {
1956
+ event.res.setHeader(HeaderName.ALLOW, options);
1957
+ await send(event.res, options);
1958
+ }
1959
+ return true;
1960
+ }
1961
+ return false;
1416
1962
  }
1417
1963
  // --------------------------------------------------
1418
1964
  route(path) {
@@ -1426,7 +1972,9 @@ class Router {
1426
1972
  const route = new Route({
1427
1973
  path,
1428
1974
  pathMatcher: {
1429
- sensitive: this.pathMatcherOptions.sensitive
1975
+ ...this.pathMatcherOptions ? {
1976
+ sensitive: this.pathMatcherOptions.sensitive
1977
+ } : {}
1430
1978
  }
1431
1979
  });
1432
1980
  this.stack.push(route);
@@ -1481,7 +2029,6 @@ class Router {
1481
2029
  if (path) {
1482
2030
  item.setPath(path);
1483
2031
  }
1484
- item.setPathMatcherOptions(this.pathMatcherOptions);
1485
2032
  this.stack.push(item);
1486
2033
  continue;
1487
2034
  }
@@ -1491,7 +2038,9 @@ class Router {
1491
2038
  pathMatcher: {
1492
2039
  strict: false,
1493
2040
  end: false,
1494
- sensitive: this.pathMatcherOptions.sensitive
2041
+ ...this.pathMatcherOptions ? {
2042
+ sensitive: this.pathMatcherOptions.sensitive
2043
+ } : {}
1495
2044
  }
1496
2045
  }, item));
1497
2046
  }
@@ -1499,23 +2048,19 @@ class Router {
1499
2048
  return this;
1500
2049
  }
1501
2050
  // --------------------------------------------------
1502
- constructor(ctx){
2051
+ constructor(options = {}){
1503
2052
  this['@instanceof'] = Symbol.for('Router');
1504
2053
  /**
1505
2054
  * Array of mounted layers, routes & routers.
1506
2055
  *
1507
2056
  * @protected
1508
2057
  */ this.stack = [];
1509
- ctx = ctx || {};
1510
- this.pathMatcherOptions = {
1511
- end: false,
1512
- sensitive: true,
1513
- ...ctx.pathMatcher || {}
1514
- };
1515
- this.timeout = ctx.timeout;
1516
- this.setPath(ctx.path || '/');
2058
+ this.id = generateRouterID();
2059
+ this.pathMatcherOptions = options.pathMatcher;
2060
+ this.setPath(options.path);
2061
+ setRouterOptions(this.id, transformRouterOptions(options));
1517
2062
  }
1518
2063
  }
1519
2064
 
1520
- export { HeaderName, Layer, MethodName, PathMatcher, Route, Router, appendResponseHeader, appendResponseHeaderDirective, buildConfig, buildEtagFn, buildTrustProxyFn, cleanDoubleSlashes, createEtag, createRequestTimeout, extendRequestBody, extendRequestCookies, extendRequestQuery, generateETag, getCharsetForMimeType, getConfigOption, getMimeType, getRequestAcceptableCharset, getRequestAcceptableCharsets, getRequestAcceptableContentType, getRequestAcceptableContentTypes, getRequestAcceptableEncoding, getRequestAcceptableEncodings, getRequestAcceptableLanguage, getRequestAcceptableLanguages, getRequestHeader, getRequestHostName, getRequestIP, getRequestProtocol, hasLeadingSlash, hasRequestBody, hasRequestCookies, hasRequestQuery, hasTrailingSlash, isInstance, isLayerInstance, isObject, isPath, isPromise, isRequestCacheable, isRouteInstance, isRouterInstance, matchRequestContentType, onResponseFinished, processHandlerExecutionOutput, send, sendAccepted, sendCreated, sendFile, sendFormat, sendRedirect, sendStream, setConfig, setConfigOption, setRequestBody, setRequestCookies, setRequestEnv, setRequestHeader, setRequestMountPath, setRequestParam, setRequestParams, setRequestQuery, setResponseCacheHeaders, setResponseContentTypeByFileName, setResponseHeaderAttachment, setResponseHeaderContentType, unsetRequestEnv, useConfig, useRequestBody, useRequestCookie, useRequestCookies, useRequestEnv, useRequestMountPath, useRequestNegotiator, useRequestParam, useRequestParams, useRequestPath, useRequestQuery, withLeadingSlash, withTrailingSlash, withoutLeadingSlash, withoutTrailingSlash };
2065
+ export { HeaderName, Layer, MethodName, PathMatcher, Route, Router, appendResponseHeader, appendResponseHeaderDirective, cloneDispatcherMeta, cloneDispatcherMetaParams, createNodeDispatcher, createRawDispatcher, createRequest, createResponse, createWebDispatcher, dispatchNodeRequest, dispatchRawRequest, dispatchWebRequest, extendRequestBody, extendRequestCookies, extendRequestQuery, getRequestAcceptableCharset, getRequestAcceptableCharsets, getRequestAcceptableContentType, getRequestAcceptableContentTypes, getRequestAcceptableEncoding, getRequestAcceptableEncodings, getRequestAcceptableLanguage, getRequestAcceptableLanguages, getRequestHeader, getRequestHostName, getRequestIP, getRequestProtocol, hasRequestBody, hasRequestCookies, hasRequestQuery, isLayerInstance, isPath, isRequestCacheable, isResponseGone, isRouteInstance, isRouterInstance, matchRequestContentType, mergeDispatcherMetaParams, send, sendAccepted, sendCreated, sendFile, sendFormat, sendRedirect, sendStream, sendWebBlob, sendWebResponse, setRequestBody, setRequestCookies, setRequestEnv, setRequestHeader, setRequestMountPath, setRequestParam, setRequestParams, setRequestQuery, setRequestRouterIds, setResponseCacheHeaders, setResponseContentTypeByFileName, setResponseHeaderAttachment, setResponseHeaderContentType, unsetRequestEnv, useRequestBody, useRequestCookie, useRequestCookies, useRequestEnv, useRequestMountPath, useRequestNegotiator, useRequestParam, useRequestParams, useRequestPath, useRequestQuery, useRequestRouterIds };
1521
2066
  //# sourceMappingURL=index.mjs.map