routup 1.0.3 → 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 +1073 -537
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.ts +4 -5
  17. package/dist/index.mjs +1050 -506
  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 +13 -12
  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
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);
@@ -100,7 +312,7 @@ function buildTrustProxyFn(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,8 +768,7 @@ 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
773
  const addrs = all(req, trustProxy);
577
774
  return addrs[addrs.length - 1];
@@ -632,8 +829,7 @@ function getRequestProtocol(req, options) {
632
829
  if (typeof options.trustProxy !== 'undefined') {
633
830
  trustProxy = buildTrustProxyFn(options.trustProxy);
634
831
  } else {
635
- const config = useConfig();
636
- trustProxy = config.get('trustProxy');
832
+ trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
637
833
  }
638
834
  let protocol = options.default;
639
835
  /* istanbul ignore next */ if (hasOwnProperty(req.socket, 'encrypted') && !!req.socket.encrypted) {
@@ -701,121 +897,7 @@ function extendRequestQuery(req, key, value) {
701
897
  setRequestQuery(req, key, value);
702
898
  }
703
899
 
704
- function setResponseCacheHeaders(res, options) {
705
- options = options || {};
706
- const cacheControls = [
707
- 'public'
708
- ].concat(options.cacheControls || []);
709
- if (options.maxAge !== undefined) {
710
- cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
711
- }
712
- if (options.modifiedTime) {
713
- const modifiedTime = typeof options.modifiedTime === 'string' ? new Date(options.modifiedTime) : options.modifiedTime;
714
- res.setHeader('last-modified', modifiedTime.toUTCString());
715
- }
716
- res.setHeader('cache-control', cacheControls.join(', '));
717
- }
718
-
719
- function appendResponseHeader(res, name, value) {
720
- let header = res.getHeader(name);
721
- if (!header) {
722
- res.setHeader(name, value);
723
- return;
724
- }
725
- if (!Array.isArray(header)) {
726
- header = [
727
- header.toString()
728
- ];
729
- }
730
- res.setHeader(name, [
731
- ...header,
732
- value
733
- ]);
734
- }
735
- function appendResponseHeaderDirective(res, name, value) {
736
- let header = res.getHeader(name);
737
- if (!header) {
738
- if (Array.isArray(value)) {
739
- res.setHeader(name, value.join('; '));
740
- return;
741
- }
742
- res.setHeader(name, value);
743
- return;
744
- }
745
- if (!Array.isArray(header)) {
746
- if (typeof header === 'string') {
747
- // split header by directive(s)
748
- header = header.split('; ');
749
- }
750
- if (typeof header === 'number') {
751
- header = [
752
- header.toString()
753
- ];
754
- }
755
- }
756
- if (Array.isArray(value)) {
757
- header.push(...value);
758
- } else {
759
- header.push(`${value}`);
760
- }
761
- header = [
762
- ...new Set(header)
763
- ];
764
- res.setHeader(name, header.join('; '));
765
- }
766
-
767
- function setResponseContentTypeByFileName(res, fileName) {
768
- const ext = path.extname(fileName);
769
- if (ext) {
770
- let type = getMimeType(ext.substring(1));
771
- if (type) {
772
- const charset = getCharsetForMimeType(type);
773
- if (charset) {
774
- type += `; charset=${charset}`;
775
- }
776
- res.setHeader(HeaderName.CONTENT_TYPE, type);
777
- }
778
- }
779
- }
780
- /* istanbul ignore next */ function onResponseFinished(res, cb) {
781
- let called;
782
- const callCallback = (err)=>{
783
- if (called) return;
784
- called = true;
785
- cb(err);
786
- };
787
- res.on('finish', ()=>{
788
- callCallback();
789
- });
790
- res.on('close', ()=>{
791
- callCallback();
792
- });
793
- res.on('error', (err)=>{
794
- callCallback(err);
795
- });
796
- }
797
-
798
- function setResponseHeaderAttachment(res, filename) {
799
- if (typeof filename === 'string') {
800
- setResponseContentTypeByFileName(res, filename);
801
- }
802
- res.setHeader(HeaderName.CONTENT_DISPOSITION, `attachment${filename ? `; filename="${filename}"` : ''}`);
803
- }
804
-
805
- function setResponseHeaderContentType(res, input, ifNotExists) {
806
- if (ifNotExists) {
807
- const header = res.getHeader(HeaderName.CONTENT_TYPE);
808
- if (header) {
809
- return;
810
- }
811
- }
812
- const contentType = getMimeType(input);
813
- if (contentType) {
814
- res.setHeader(HeaderName.CONTENT_TYPE, contentType);
815
- }
816
- }
817
-
818
- function send(res, chunk) {
900
+ async function send(res, chunk) {
819
901
  switch(typeof chunk){
820
902
  case 'string':
821
903
  {
@@ -860,10 +942,12 @@ function send(res, chunk) {
860
942
  }
861
943
  res.setHeader(HeaderName.CONTENT_LENGTH, `${len}`);
862
944
  }
863
- const config = useConfig();
864
- const etagFn = config.get('etag');
865
945
  if (typeof len !== 'undefined') {
866
- 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
+ }
867
951
  if (typeof chunkHash === 'string') {
868
952
  res.setHeader(HeaderName.ETag, chunkHash);
869
953
  if (res.req.headers[HeaderName.IF_NONE_MATCH] === chunkHash) {
@@ -884,16 +968,20 @@ function send(res, chunk) {
884
968
  res.removeHeader(HeaderName.TRANSFER_ENCODING);
885
969
  chunk = '';
886
970
  }
971
+ if (isResponseGone(res)) {
972
+ return Promise.resolve();
973
+ }
887
974
  if (res.req.method === 'HEAD') {
888
975
  // skip body for HEAD
889
976
  res.end();
890
- return;
977
+ return Promise.resolve();
891
978
  }
892
979
  if (typeof encoding !== 'undefined') {
893
980
  res.end(chunk, encoding);
894
- return;
981
+ return Promise.resolve();
895
982
  }
896
983
  res.end(chunk);
984
+ return Promise.resolve();
897
985
  }
898
986
 
899
987
  function sendAccepted(res, chunk) {
@@ -908,87 +996,116 @@ function sendCreated(res, chunk) {
908
996
  return send(res, chunk);
909
997
  }
910
998
 
911
- function sendStream(res, stream, fn) {
912
- stream.on('open', ()=>{
913
- stream.pipe(res);
914
- });
915
- /* istanbul ignore next */ stream.on('error', (err)=>{
916
- if (typeof fn === 'function') {
917
- fn(err);
918
- } else {
919
- 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
+ }
920
1009
  res.end();
921
- }
922
- });
923
- stream.on('close', ()=>{
924
- if (typeof fn === 'function') {
925
- fn();
926
- } 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
+ }
927
1027
  res.end();
928
- }
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
+ });
929
1038
  });
930
1039
  }
931
1040
 
932
- function resolveStats(options, cb) {
933
- if (options.stats) {
934
- cb(null, options.stats);
935
- return;
936
- }
937
- stat(options.filePath, (err, stats)=>cb(err, stats));
938
- }
939
- function sendFile(res, filePath, fn) {
940
- let options;
941
- if (typeof filePath === 'string') {
942
- options = {
943
- filePath
944
- };
945
- } else {
946
- options = filePath;
947
- }
948
- const fileName = path.basename(options.filePath);
949
- if (options.attachment) {
950
- const dispositionHeader = res.getHeader(HeaderName.CONTENT_DISPOSITION);
951
- if (!dispositionHeader) {
952
- 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);
953
1048
  }
954
- } else {
955
- setResponseContentTypeByFileName(res, fileName);
956
- }
957
- resolveStats(options, (err, stats)=>{
958
- /* istanbul ignore next */ if (err) {
959
- if (typeof fn === 'function') {
960
- fn(err);
961
- } else {
962
- res.statusCode = 404;
963
- 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);
964
1061
  }
965
- return;
1062
+ } else {
1063
+ setResponseContentTypeByFileName(res, fileName);
966
1064
  }
967
- const streamOptions = {};
1065
+ }
1066
+ const contentOptions = {};
1067
+ if (stats.size) {
968
1068
  const rangeHeader = res.req.headers[HeaderName.RANGE];
969
1069
  if (rangeHeader) {
970
1070
  const [x, y] = rangeHeader.replace('bytes=', '').split('-');
971
- streamOptions.end = Math.min(parseInt(y, 10) || stats.size - 1, stats.size - 1);
972
- streamOptions.start = parseInt(x, 10) || 0;
973
- if (streamOptions.end >= stats.size) {
974
- 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;
975
1075
  }
976
- if (streamOptions.start >= stats.size) {
1076
+ if (contentOptions.start >= stats.size) {
977
1077
  res.setHeader(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
978
1078
  res.statusCode = 416;
979
1079
  res.end();
980
- return;
1080
+ return Promise.resolve();
981
1081
  }
982
- res.setHeader(HeaderName.CONTENT_RANGE, `bytes ${streamOptions.start}-${streamOptions.end}/${stats.size}`);
983
- 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);
984
1084
  } else {
985
1085
  res.setHeader(HeaderName.CONTENT_LENGTH, stats.size);
986
1086
  }
987
1087
  res.setHeader(HeaderName.ACCEPT_RANGES, 'bytes');
988
- res.setHeader(HeaderName.LAST_MODIFIED, stats.mtime.toUTCString());
989
- res.setHeader(HeaderName.ETag, `W/"${stats.size}-${stats.mtime.getTime()}"`);
990
- sendStream(res, createReadStream(options.filePath, streamOptions), fn);
991
- });
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
+ }
992
1109
  }
993
1110
 
994
1111
  function sendFormat(res, input) {
@@ -1010,19 +1127,406 @@ function sendRedirect(res, location, statusCode = 302) {
1010
1127
  return send(res, html);
1011
1128
  }
1012
1129
 
1013
- function processHandlerExecutionOutput(res, next, output) {
1014
- if (isPromise(output)) {
1015
- output.then((r)=>{
1016
- if (typeof r !== 'undefined') {
1017
- 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;
1018
1172
  }
1019
- return r;
1020
- }).catch(next);
1021
- 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
+ };
1022
1448
  }
1023
- if (typeof output !== 'undefined') {
1024
- 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;
1025
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;
1026
1530
  }
1027
1531
 
1028
1532
  function decodeParam(val) {
@@ -1086,48 +1590,98 @@ class PathMatcher {
1086
1590
  }
1087
1591
  }
1088
1592
 
1593
+ function isPath(input) {
1594
+ return typeof input === 'string' || input instanceof RegExp;
1595
+ }
1596
+
1089
1597
  class Layer {
1090
1598
  // --------------------------------------------------
1091
1599
  isError() {
1092
1600
  return this.fn.length === 4;
1093
1601
  }
1094
- dispatch(req, res, meta, next, err) {
1095
- setRequestParams(req, meta.params || {});
1096
- setRequestMountPath(req, meta.mountPath || '/');
1097
- if (typeof err !== 'undefined') {
1098
- if (this.fn.length === 4) {
1099
- try {
1100
- this.fn(err, req, res, next);
1101
- } catch (e) {
1102
- /* istanbul ignore next */ /* istanbul ignore next */ if (e instanceof Error) {
1103
- next(e);
1104
- } else {
1105
- next(new BadRequestError({
1106
- message: 'The request could not be processed by the error handler.'
1107
- }));
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();
1108
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;
1109
1664
  }
1110
- return;
1665
+ Promise.resolve().then(()=>handle(output)).catch((e)=>reject(createError(e)));
1666
+ } catch (error) {
1667
+ onNext(error);
1111
1668
  }
1112
- /* istanbul ignore next */ next(err);
1113
- /* istanbul ignore next */ return;
1669
+ });
1670
+ }
1671
+ sendOutput(res, input) {
1672
+ if (input instanceof Error) {
1673
+ return Promise.reject(input);
1114
1674
  }
1115
- /* istanbul ignore next */ if (this.fn.length > 3) {
1116
- next();
1117
- return;
1675
+ if (isStream(input)) {
1676
+ return sendStream(res, input);
1118
1677
  }
1119
- try {
1120
- const output = this.fn(req, res, next);
1121
- processHandlerExecutionOutput(res, next, output);
1122
- } catch (e) {
1123
- /* istanbul ignore next */ if (e instanceof Error) {
1124
- next(e);
1125
- } else {
1126
- next(new BadRequestError({
1127
- message: 'The request could not be processed by the handler.'
1128
- }));
1129
- }
1678
+ if (isWebBlob(input)) {
1679
+ return sendWebBlob(res, input);
1680
+ }
1681
+ if (isWebResponse(input)) {
1682
+ return sendWebResponse(res, input);
1130
1683
  }
1684
+ return send(res, input);
1131
1685
  }
1132
1686
  // --------------------------------------------------
1133
1687
  matchPath(path) {
@@ -1145,6 +1699,9 @@ class Layer {
1145
1699
  }
1146
1700
 
1147
1701
  function isLayerInstance(input) {
1702
+ if (input instanceof Layer) {
1703
+ return true;
1704
+ }
1148
1705
  return isInstance(input, 'Layer');
1149
1706
  }
1150
1707
 
@@ -1169,44 +1726,46 @@ class Route {
1169
1726
  return keys;
1170
1727
  }
1171
1728
  // --------------------------------------------------
1172
- dispatch(req, res, meta, done) {
1173
- /* istanbul ignore next */ if (!req.method) {
1174
- done();
1175
- return;
1729
+ async dispatch(event, meta) {
1730
+ /* istanbul ignore next */ if (!event.req.method) {
1731
+ return false;
1176
1732
  }
1177
- let name = req.method.toLowerCase();
1733
+ let name = event.req.method.toLowerCase();
1178
1734
  if (name === MethodName.HEAD && !hasOwnProperty(this.layers, name)) {
1179
1735
  name = MethodName.GET;
1180
1736
  }
1181
1737
  const layers = this.layers[name];
1182
1738
  /* istanbul ignore next */ if (typeof layers === 'undefined' || layers.length === 0 || typeof meta.path === 'undefined') {
1183
- done();
1184
- return;
1739
+ return false;
1185
1740
  }
1186
- const layerMeta = {
1187
- ...meta
1188
- };
1741
+ const layerMeta = cloneDispatcherMeta(meta);
1189
1742
  const output = this.pathMatcher.exec(meta.path);
1190
1743
  if (output) {
1191
- layerMeta.params = merge({}, meta.params || {}, output.params);
1744
+ layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1192
1745
  }
1193
- let index = -1;
1194
- const next = (err)=>{
1195
- index++;
1196
- if (index >= layers.length) {
1197
- setImmediate(done, err);
1198
- return;
1199
- }
1200
- const layer = layers[index];
1746
+ let err;
1747
+ for(let i = 0; i < layers.length; i++){
1748
+ const layer = layers[i];
1201
1749
  if (err && !layer.isError()) {
1202
- next(err);
1203
- return;
1750
+ continue;
1204
1751
  }
1205
- layer.dispatch(req, res, {
1206
- ...layerMeta
1207
- }, next);
1208
- };
1209
- 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;
1210
1769
  }
1211
1770
  // --------------------------------------------------
1212
1771
  register(method, ...handlers) {
@@ -1259,42 +1818,50 @@ class Route {
1259
1818
  }
1260
1819
 
1261
1820
  function isRouteInstance(input) {
1821
+ if (input instanceof Route) {
1822
+ return true;
1823
+ }
1262
1824
  return isInstance(input, 'Route');
1263
1825
  }
1264
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
+
1265
1842
  function isRouterInstance(input) {
1843
+ if (input instanceof Router) {
1844
+ return true;
1845
+ }
1266
1846
  return isInstance(input, 'Router');
1267
1847
  }
1268
1848
  class Router {
1269
1849
  // --------------------------------------------------
1270
- setPathMatcherOptions(input) {
1271
- this.pathMatcherOptions = input;
1272
- if (this.pathMatcher) {
1273
- this.pathMatcher.regexpOptions = this.pathMatcherOptions;
1274
- }
1275
- }
1276
1850
  setPath(value) {
1277
1851
  if (value === '/' || !isPath(value)) {
1278
- this.path = '/';
1279
1852
  return;
1280
1853
  }
1854
+ let path;
1281
1855
  if (typeof value === 'string') {
1282
- this.path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1856
+ path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1283
1857
  } else {
1284
- this.path = value;
1858
+ path = value;
1285
1859
  }
1286
- this.pathMatcher = new PathMatcher(this.path, this.pathMatcherOptions);
1287
- }
1288
- // --------------------------------------------------
1289
- createListener() {
1290
- this.isRoot = true;
1291
- return (req, res)=>{
1292
- this.dispatch(req, res);
1293
- };
1294
- }
1295
- /* istanbul ignore next */ listen(port) {
1296
- const server = createServer(this.createListener());
1297
- return server.listen(port);
1860
+ this.pathMatcher = new PathMatcher(path, {
1861
+ end: false,
1862
+ sensitive: false,
1863
+ ...this.pathMatcherOptions ? this.pathMatcherOptions : {}
1864
+ });
1298
1865
  }
1299
1866
  // --------------------------------------------------
1300
1867
  matchPath(path) {
@@ -1304,35 +1871,9 @@ class Router {
1304
1871
  return true;
1305
1872
  }
1306
1873
  // --------------------------------------------------
1307
- dispatch(req, res, meta, done) {
1308
- let index = -1;
1309
- meta = meta || {};
1310
- let allowedMethods = [];
1311
- if (this.isRoot && typeof this.timeout === 'number') {
1312
- createRequestTimeout(res, this.timeout, done);
1313
- }
1314
- const fn = (err)=>{
1315
- /* istanbul ignore if */ if (!this.isRoot) {
1316
- if (typeof done !== 'undefined') {
1317
- setImmediate(()=>done(err));
1318
- }
1319
- return;
1320
- }
1321
- if (typeof err !== 'undefined') {
1322
- res.statusCode = 400;
1323
- res.end();
1324
- return;
1325
- }
1326
- if (req.method && req.method.toLowerCase() === MethodName.OPTIONS) {
1327
- const options = allowedMethods.map((key)=>key.toUpperCase()).join(',');
1328
- res.setHeader(HeaderName.ALLOW, options);
1329
- send(res, options);
1330
- return;
1331
- }
1332
- res.statusCode = 404;
1333
- res.end();
1334
- };
1335
- let path = meta.path || useRequestPath(req);
1874
+ async dispatch(event, meta = {}) {
1875
+ const allowedMethods = [];
1876
+ let path = meta.path || useRequestPath(event.req);
1336
1877
  if (this.pathMatcher) {
1337
1878
  const output = this.pathMatcher.exec(path);
1338
1879
  if (typeof output !== 'undefined') {
@@ -1346,74 +1887,78 @@ class Router {
1346
1887
  }
1347
1888
  }
1348
1889
  meta.path = path;
1890
+ if (meta.routerIds) {
1891
+ meta.routerIds.push(this.id);
1892
+ } else {
1893
+ meta.routerIds = [
1894
+ this.id
1895
+ ];
1896
+ }
1349
1897
  if (!meta.mountPath) {
1350
1898
  meta.mountPath = '/';
1351
1899
  }
1352
- const next = (err)=>{
1353
- if (index >= this.stack.length) {
1354
- setImmediate(fn, err);
1355
- return;
1356
- }
1357
- let layer;
1358
- let match = false;
1359
- while(!match && index < this.stack.length){
1360
- index++;
1361
- layer = this.stack[index];
1362
- if (isLayerInstance(layer)) {
1363
- if (!layer.isError() && err) {
1364
- continue;
1365
- }
1366
- 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;
1367
1908
  }
1368
- if (isRouterInstance(layer)) {
1369
- match = layer.matchPath(path);
1370
- }
1371
- if (isRouteInstance(layer)) {
1372
- match = layer.matchPath(path);
1373
- if (req.method && !layer.matchMethod(req.method)) {
1374
- match = false;
1375
- if (req.method.toLowerCase() === MethodName.OPTIONS) {
1376
- allowedMethods = distinctArray(merge(allowedMethods, layer.getMethods()));
1377
- }
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());
1378
1920
  }
1379
1921
  }
1380
1922
  }
1381
- if (!match || !layer) {
1382
- setImmediate(fn, err);
1383
- return;
1923
+ if (!match) {
1924
+ continue;
1384
1925
  }
1385
- const layerMeta = {
1386
- ...meta
1387
- };
1926
+ const layerMeta = cloneDispatcherMeta(meta);
1388
1927
  if (isLayerInstance(layer)) {
1389
1928
  const output = layer.exec(path);
1390
1929
  if (output) {
1391
- layerMeta.params = merge(output.params, layerMeta.params || {});
1930
+ layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1392
1931
  layerMeta.mountPath = cleanDoubleSlashes(`${layerMeta.mountPath || ''}/${output.path}`);
1393
1932
  }
1394
- }
1395
- if (err) {
1396
- if (isLayerInstance(layer) && layer.isError()) {
1397
- layer.dispatch(req, res, layerMeta, next, err);
1398
- return;
1933
+ if (err) {
1934
+ layerMeta.error = err;
1399
1935
  }
1400
- /* istanbul ignore next */ setImmediate(next, err);
1401
- return;
1936
+ } else if (err) {
1937
+ continue;
1402
1938
  }
1403
- layer.dispatch(req, res, layerMeta, next);
1404
- };
1405
- next();
1406
- }
1407
- /* istanbul ignore next */ dispatchAsync(req, res) {
1408
- return new Promise((resolve, reject)=>{
1409
- this.dispatch(req, res, {}, (err)=>{
1410
- if (err) {
1411
- reject(err);
1412
- return;
1939
+ try {
1940
+ const dispatched = await layer.dispatch(event, layerMeta);
1941
+ if (dispatched) {
1942
+ return true;
1413
1943
  }
1414
- resolve();
1415
- });
1416
- });
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;
1417
1962
  }
1418
1963
  // --------------------------------------------------
1419
1964
  route(path) {
@@ -1427,7 +1972,9 @@ class Router {
1427
1972
  const route = new Route({
1428
1973
  path,
1429
1974
  pathMatcher: {
1430
- sensitive: this.pathMatcherOptions.sensitive
1975
+ ...this.pathMatcherOptions ? {
1976
+ sensitive: this.pathMatcherOptions.sensitive
1977
+ } : {}
1431
1978
  }
1432
1979
  });
1433
1980
  this.stack.push(route);
@@ -1482,7 +2029,6 @@ class Router {
1482
2029
  if (path) {
1483
2030
  item.setPath(path);
1484
2031
  }
1485
- item.setPathMatcherOptions(this.pathMatcherOptions);
1486
2032
  this.stack.push(item);
1487
2033
  continue;
1488
2034
  }
@@ -1492,7 +2038,9 @@ class Router {
1492
2038
  pathMatcher: {
1493
2039
  strict: false,
1494
2040
  end: false,
1495
- sensitive: this.pathMatcherOptions.sensitive
2041
+ ...this.pathMatcherOptions ? {
2042
+ sensitive: this.pathMatcherOptions.sensitive
2043
+ } : {}
1496
2044
  }
1497
2045
  }, item));
1498
2046
  }
@@ -1500,23 +2048,19 @@ class Router {
1500
2048
  return this;
1501
2049
  }
1502
2050
  // --------------------------------------------------
1503
- constructor(ctx){
2051
+ constructor(options = {}){
1504
2052
  this['@instanceof'] = Symbol.for('Router');
1505
2053
  /**
1506
2054
  * Array of mounted layers, routes & routers.
1507
2055
  *
1508
2056
  * @protected
1509
2057
  */ this.stack = [];
1510
- ctx = ctx || {};
1511
- this.pathMatcherOptions = {
1512
- end: false,
1513
- sensitive: true,
1514
- ...ctx.pathMatcher || {}
1515
- };
1516
- this.timeout = ctx.timeout;
1517
- this.setPath(ctx.path || '/');
2058
+ this.id = generateRouterID();
2059
+ this.pathMatcherOptions = options.pathMatcher;
2060
+ this.setPath(options.path);
2061
+ setRouterOptions(this.id, transformRouterOptions(options));
1518
2062
  }
1519
2063
  }
1520
2064
 
1521
- 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 };
1522
2066
  //# sourceMappingURL=index.mjs.map