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