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.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,8 +770,7 @@ 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
775
  const addrs = proxyAddr.all(req, trustProxy);
579
776
  return addrs[addrs.length - 1];
@@ -623,7 +820,7 @@ function useRequestPath(req) {
623
820
  if (typeof req.url === 'undefined') {
624
821
  return '/';
625
822
  }
626
- const parsed = new node_url.URL(req.url, 'http://localhost/');
823
+ const parsed = new URL(req.url, 'http://localhost/');
627
824
  req[PathSymbol] = parsed.pathname;
628
825
  return req[PathSymbol];
629
826
  }
@@ -634,8 +831,7 @@ function getRequestProtocol(req, options) {
634
831
  if (typeof options.trustProxy !== 'undefined') {
635
832
  trustProxy = buildTrustProxyFn(options.trustProxy);
636
833
  } else {
637
- const config = useConfig();
638
- trustProxy = config.get('trustProxy');
834
+ trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
639
835
  }
640
836
  let protocol = options.default;
641
837
  /* istanbul ignore next */ if (smob.hasOwnProperty(req.socket, 'encrypted') && !!req.socket.encrypted) {
@@ -703,121 +899,7 @@ function extendRequestQuery(req, key, value) {
703
899
  setRequestQuery(req, key, value);
704
900
  }
705
901
 
706
- function setResponseCacheHeaders(res, options) {
707
- options = options || {};
708
- const cacheControls = [
709
- 'public'
710
- ].concat(options.cacheControls || []);
711
- if (options.maxAge !== undefined) {
712
- cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
713
- }
714
- if (options.modifiedTime) {
715
- const modifiedTime = typeof options.modifiedTime === 'string' ? new Date(options.modifiedTime) : options.modifiedTime;
716
- res.setHeader('last-modified', modifiedTime.toUTCString());
717
- }
718
- res.setHeader('cache-control', cacheControls.join(', '));
719
- }
720
-
721
- function appendResponseHeader(res, name, value) {
722
- let header = res.getHeader(name);
723
- if (!header) {
724
- res.setHeader(name, value);
725
- return;
726
- }
727
- if (!Array.isArray(header)) {
728
- header = [
729
- header.toString()
730
- ];
731
- }
732
- res.setHeader(name, [
733
- ...header,
734
- value
735
- ]);
736
- }
737
- function appendResponseHeaderDirective(res, name, value) {
738
- let header = res.getHeader(name);
739
- if (!header) {
740
- if (Array.isArray(value)) {
741
- res.setHeader(name, value.join('; '));
742
- return;
743
- }
744
- res.setHeader(name, value);
745
- return;
746
- }
747
- if (!Array.isArray(header)) {
748
- if (typeof header === 'string') {
749
- // split header by directive(s)
750
- header = header.split('; ');
751
- }
752
- if (typeof header === 'number') {
753
- header = [
754
- header.toString()
755
- ];
756
- }
757
- }
758
- if (Array.isArray(value)) {
759
- header.push(...value);
760
- } else {
761
- header.push(`${value}`);
762
- }
763
- header = [
764
- ...new Set(header)
765
- ];
766
- res.setHeader(name, header.join('; '));
767
- }
768
-
769
- function setResponseContentTypeByFileName(res, fileName) {
770
- const ext = path.extname(fileName);
771
- if (ext) {
772
- let type = getMimeType(ext.substring(1));
773
- if (type) {
774
- const charset = getCharsetForMimeType(type);
775
- if (charset) {
776
- type += `; charset=${charset}`;
777
- }
778
- res.setHeader(exports.HeaderName.CONTENT_TYPE, type);
779
- }
780
- }
781
- }
782
- /* istanbul ignore next */ function onResponseFinished(res, cb) {
783
- let called;
784
- const callCallback = (err)=>{
785
- if (called) return;
786
- called = true;
787
- cb(err);
788
- };
789
- res.on('finish', ()=>{
790
- callCallback();
791
- });
792
- res.on('close', ()=>{
793
- callCallback();
794
- });
795
- res.on('error', (err)=>{
796
- callCallback(err);
797
- });
798
- }
799
-
800
- function setResponseHeaderAttachment(res, filename) {
801
- if (typeof filename === 'string') {
802
- setResponseContentTypeByFileName(res, filename);
803
- }
804
- res.setHeader(exports.HeaderName.CONTENT_DISPOSITION, `attachment${filename ? `; filename="${filename}"` : ''}`);
805
- }
806
-
807
- function setResponseHeaderContentType(res, input, ifNotExists) {
808
- if (ifNotExists) {
809
- const header = res.getHeader(exports.HeaderName.CONTENT_TYPE);
810
- if (header) {
811
- return;
812
- }
813
- }
814
- const contentType = getMimeType(input);
815
- if (contentType) {
816
- res.setHeader(exports.HeaderName.CONTENT_TYPE, contentType);
817
- }
818
- }
819
-
820
- function send(res, chunk) {
902
+ async function send(res, chunk) {
821
903
  switch(typeof chunk){
822
904
  case 'string':
823
905
  {
@@ -830,7 +912,7 @@ function send(res, chunk) {
830
912
  {
831
913
  if (chunk === null) {
832
914
  chunk = '';
833
- } else if (Buffer.isBuffer(chunk)) {
915
+ } else if (buffer.Buffer.isBuffer(chunk)) {
834
916
  setResponseHeaderContentType(res, 'bin', true);
835
917
  } else {
836
918
  chunk = JSON.stringify(chunk);
@@ -848,24 +930,26 @@ function send(res, chunk) {
848
930
  // populate Content-Length
849
931
  let len;
850
932
  if (chunk !== undefined) {
851
- if (Buffer.isBuffer(chunk)) {
933
+ if (buffer.Buffer.isBuffer(chunk)) {
852
934
  // get length of Buffer
853
935
  len = chunk.length;
854
936
  } else if (chunk.length < 1000) {
855
937
  // just calculate length when no ETag + small chunk
856
- len = Buffer.byteLength(chunk, encoding);
938
+ len = buffer.Buffer.byteLength(chunk, encoding);
857
939
  } else {
858
940
  // convert chunk to Buffer and calculate
859
- chunk = Buffer.from(chunk, encoding);
941
+ chunk = buffer.Buffer.from(chunk, encoding);
860
942
  encoding = undefined;
861
943
  len = chunk.length;
862
944
  }
863
945
  res.setHeader(exports.HeaderName.CONTENT_LENGTH, `${len}`);
864
946
  }
865
- const config = useConfig();
866
- const etagFn = config.get('etag');
867
947
  if (typeof len !== 'undefined') {
868
- 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
+ }
869
953
  if (typeof chunkHash === 'string') {
870
954
  res.setHeader(exports.HeaderName.ETag, chunkHash);
871
955
  if (res.req.headers[exports.HeaderName.IF_NONE_MATCH] === chunkHash) {
@@ -886,16 +970,20 @@ function send(res, chunk) {
886
970
  res.removeHeader(exports.HeaderName.TRANSFER_ENCODING);
887
971
  chunk = '';
888
972
  }
973
+ if (isResponseGone(res)) {
974
+ return Promise.resolve();
975
+ }
889
976
  if (res.req.method === 'HEAD') {
890
977
  // skip body for HEAD
891
978
  res.end();
892
- return;
979
+ return Promise.resolve();
893
980
  }
894
981
  if (typeof encoding !== 'undefined') {
895
982
  res.end(chunk, encoding);
896
- return;
983
+ return Promise.resolve();
897
984
  }
898
985
  res.end(chunk);
986
+ return Promise.resolve();
899
987
  }
900
988
 
901
989
  function sendAccepted(res, chunk) {
@@ -910,87 +998,116 @@ function sendCreated(res, chunk) {
910
998
  return send(res, chunk);
911
999
  }
912
1000
 
913
- function sendStream(res, stream, fn) {
914
- stream.on('open', ()=>{
915
- stream.pipe(res);
916
- });
917
- /* istanbul ignore next */ stream.on('error', (err)=>{
918
- if (typeof fn === 'function') {
919
- fn(err);
920
- } else {
921
- 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
+ }
922
1011
  res.end();
923
- }
924
- });
925
- stream.on('close', ()=>{
926
- if (typeof fn === 'function') {
927
- fn();
928
- } 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
+ }
929
1029
  res.end();
930
- }
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
+ });
931
1040
  });
932
1041
  }
933
1042
 
934
- function resolveStats(options, cb) {
935
- if (options.stats) {
936
- cb(null, options.stats);
937
- return;
938
- }
939
- node_fs.stat(options.filePath, (err, stats)=>cb(err, stats));
940
- }
941
- function sendFile(res, filePath, fn) {
942
- let options;
943
- if (typeof filePath === 'string') {
944
- options = {
945
- filePath
946
- };
947
- } else {
948
- options = filePath;
949
- }
950
- const fileName = path.basename(options.filePath);
951
- if (options.attachment) {
952
- const dispositionHeader = res.getHeader(exports.HeaderName.CONTENT_DISPOSITION);
953
- if (!dispositionHeader) {
954
- 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);
955
1050
  }
956
- } else {
957
- setResponseContentTypeByFileName(res, fileName);
958
- }
959
- resolveStats(options, (err, stats)=>{
960
- /* istanbul ignore next */ if (err) {
961
- if (typeof fn === 'function') {
962
- fn(err);
963
- } else {
964
- res.statusCode = 404;
965
- 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);
966
1063
  }
967
- return;
1064
+ } else {
1065
+ setResponseContentTypeByFileName(res, fileName);
968
1066
  }
969
- const streamOptions = {};
1067
+ }
1068
+ const contentOptions = {};
1069
+ if (stats.size) {
970
1070
  const rangeHeader = res.req.headers[exports.HeaderName.RANGE];
971
1071
  if (rangeHeader) {
972
1072
  const [x, y] = rangeHeader.replace('bytes=', '').split('-');
973
- streamOptions.end = Math.min(parseInt(y, 10) || stats.size - 1, stats.size - 1);
974
- streamOptions.start = parseInt(x, 10) || 0;
975
- if (streamOptions.end >= stats.size) {
976
- 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;
977
1077
  }
978
- if (streamOptions.start >= stats.size) {
1078
+ if (contentOptions.start >= stats.size) {
979
1079
  res.setHeader(exports.HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
980
1080
  res.statusCode = 416;
981
1081
  res.end();
982
- return;
1082
+ return Promise.resolve();
983
1083
  }
984
- res.setHeader(exports.HeaderName.CONTENT_RANGE, `bytes ${streamOptions.start}-${streamOptions.end}/${stats.size}`);
985
- 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);
986
1086
  } else {
987
1087
  res.setHeader(exports.HeaderName.CONTENT_LENGTH, stats.size);
988
1088
  }
989
1089
  res.setHeader(exports.HeaderName.ACCEPT_RANGES, 'bytes');
990
- res.setHeader(exports.HeaderName.LAST_MODIFIED, stats.mtime.toUTCString());
991
- res.setHeader(exports.HeaderName.ETag, `W/"${stats.size}-${stats.mtime.getTime()}"`);
992
- sendStream(res, node_fs.createReadStream(options.filePath, streamOptions), fn);
993
- });
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
+ }
994
1111
  }
995
1112
 
996
1113
  function sendFormat(res, input) {
@@ -1012,19 +1129,406 @@ function sendRedirect(res, location, statusCode = 302) {
1012
1129
  return send(res, html);
1013
1130
  }
1014
1131
 
1015
- function processHandlerExecutionOutput(res, next, output) {
1016
- if (isPromise(output)) {
1017
- output.then((r)=>{
1018
- if (typeof r !== 'undefined') {
1019
- 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;
1020
1174
  }
1021
- return r;
1022
- }).catch(next);
1023
- 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
+ };
1024
1450
  }
1025
- if (typeof output !== 'undefined') {
1026
- 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;
1027
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;
1028
1532
  }
1029
1533
 
1030
1534
  function decodeParam(val) {
@@ -1088,48 +1592,98 @@ class PathMatcher {
1088
1592
  }
1089
1593
  }
1090
1594
 
1595
+ function isPath(input) {
1596
+ return typeof input === 'string' || input instanceof RegExp;
1597
+ }
1598
+
1091
1599
  class Layer {
1092
1600
  // --------------------------------------------------
1093
1601
  isError() {
1094
1602
  return this.fn.length === 4;
1095
1603
  }
1096
- dispatch(req, res, meta, next, err) {
1097
- setRequestParams(req, meta.params || {});
1098
- setRequestMountPath(req, meta.mountPath || '/');
1099
- if (typeof err !== 'undefined') {
1100
- if (this.fn.length === 4) {
1101
- try {
1102
- this.fn(err, req, res, next);
1103
- } catch (e) {
1104
- /* istanbul ignore next */ /* istanbul ignore next */ if (e instanceof Error) {
1105
- next(e);
1106
- } else {
1107
- next(new http.BadRequestError({
1108
- message: 'The request could not be processed by the error handler.'
1109
- }));
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();
1110
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;
1111
1666
  }
1112
- return;
1667
+ Promise.resolve().then(()=>handle(output)).catch((e)=>reject(createError(e)));
1668
+ } catch (error) {
1669
+ onNext(error);
1113
1670
  }
1114
- /* istanbul ignore next */ next(err);
1115
- /* istanbul ignore next */ return;
1671
+ });
1672
+ }
1673
+ sendOutput(res, input) {
1674
+ if (input instanceof Error) {
1675
+ return Promise.reject(input);
1116
1676
  }
1117
- /* istanbul ignore next */ if (this.fn.length > 3) {
1118
- next();
1119
- return;
1677
+ if (isStream(input)) {
1678
+ return sendStream(res, input);
1120
1679
  }
1121
- try {
1122
- const output = this.fn(req, res, next);
1123
- processHandlerExecutionOutput(res, next, output);
1124
- } catch (e) {
1125
- /* istanbul ignore next */ if (e instanceof Error) {
1126
- next(e);
1127
- } else {
1128
- next(new http.BadRequestError({
1129
- message: 'The request could not be processed by the handler.'
1130
- }));
1131
- }
1680
+ if (isWebBlob(input)) {
1681
+ return sendWebBlob(res, input);
1682
+ }
1683
+ if (isWebResponse(input)) {
1684
+ return sendWebResponse(res, input);
1132
1685
  }
1686
+ return send(res, input);
1133
1687
  }
1134
1688
  // --------------------------------------------------
1135
1689
  matchPath(path) {
@@ -1147,6 +1701,9 @@ class Layer {
1147
1701
  }
1148
1702
 
1149
1703
  function isLayerInstance(input) {
1704
+ if (input instanceof Layer) {
1705
+ return true;
1706
+ }
1150
1707
  return isInstance(input, 'Layer');
1151
1708
  }
1152
1709
 
@@ -1171,44 +1728,46 @@ class Route {
1171
1728
  return keys;
1172
1729
  }
1173
1730
  // --------------------------------------------------
1174
- dispatch(req, res, meta, done) {
1175
- /* istanbul ignore next */ if (!req.method) {
1176
- done();
1177
- return;
1731
+ async dispatch(event, meta) {
1732
+ /* istanbul ignore next */ if (!event.req.method) {
1733
+ return false;
1178
1734
  }
1179
- let name = req.method.toLowerCase();
1735
+ let name = event.req.method.toLowerCase();
1180
1736
  if (name === exports.MethodName.HEAD && !smob.hasOwnProperty(this.layers, name)) {
1181
1737
  name = exports.MethodName.GET;
1182
1738
  }
1183
1739
  const layers = this.layers[name];
1184
1740
  /* istanbul ignore next */ if (typeof layers === 'undefined' || layers.length === 0 || typeof meta.path === 'undefined') {
1185
- done();
1186
- return;
1741
+ return false;
1187
1742
  }
1188
- const layerMeta = {
1189
- ...meta
1190
- };
1743
+ const layerMeta = cloneDispatcherMeta(meta);
1191
1744
  const output = this.pathMatcher.exec(meta.path);
1192
1745
  if (output) {
1193
- layerMeta.params = smob.merge({}, meta.params || {}, output.params);
1746
+ layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1194
1747
  }
1195
- let index = -1;
1196
- const next = (err)=>{
1197
- index++;
1198
- if (index >= layers.length) {
1199
- setImmediate(done, err);
1200
- return;
1201
- }
1202
- const layer = layers[index];
1748
+ let err;
1749
+ for(let i = 0; i < layers.length; i++){
1750
+ const layer = layers[i];
1203
1751
  if (err && !layer.isError()) {
1204
- next(err);
1205
- return;
1752
+ continue;
1206
1753
  }
1207
- layer.dispatch(req, res, {
1208
- ...layerMeta
1209
- }, next);
1210
- };
1211
- 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;
1212
1771
  }
1213
1772
  // --------------------------------------------------
1214
1773
  register(method, ...handlers) {
@@ -1261,42 +1820,50 @@ class Route {
1261
1820
  }
1262
1821
 
1263
1822
  function isRouteInstance(input) {
1823
+ if (input instanceof Route) {
1824
+ return true;
1825
+ }
1264
1826
  return isInstance(input, 'Route');
1265
1827
  }
1266
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
+
1267
1844
  function isRouterInstance(input) {
1845
+ if (input instanceof Router) {
1846
+ return true;
1847
+ }
1268
1848
  return isInstance(input, 'Router');
1269
1849
  }
1270
1850
  class Router {
1271
1851
  // --------------------------------------------------
1272
- setPathMatcherOptions(input) {
1273
- this.pathMatcherOptions = input;
1274
- if (this.pathMatcher) {
1275
- this.pathMatcher.regexpOptions = this.pathMatcherOptions;
1276
- }
1277
- }
1278
1852
  setPath(value) {
1279
1853
  if (value === '/' || !isPath(value)) {
1280
- this.path = '/';
1281
1854
  return;
1282
1855
  }
1856
+ let path;
1283
1857
  if (typeof value === 'string') {
1284
- this.path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1858
+ path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1285
1859
  } else {
1286
- this.path = value;
1860
+ path = value;
1287
1861
  }
1288
- this.pathMatcher = new PathMatcher(this.path, this.pathMatcherOptions);
1289
- }
1290
- // --------------------------------------------------
1291
- createListener() {
1292
- this.isRoot = true;
1293
- return (req, res)=>{
1294
- this.dispatch(req, res);
1295
- };
1296
- }
1297
- /* istanbul ignore next */ listen(port) {
1298
- const server = node_http.createServer(this.createListener());
1299
- return server.listen(port);
1862
+ this.pathMatcher = new PathMatcher(path, {
1863
+ end: false,
1864
+ sensitive: false,
1865
+ ...this.pathMatcherOptions ? this.pathMatcherOptions : {}
1866
+ });
1300
1867
  }
1301
1868
  // --------------------------------------------------
1302
1869
  matchPath(path) {
@@ -1306,35 +1873,9 @@ class Router {
1306
1873
  return true;
1307
1874
  }
1308
1875
  // --------------------------------------------------
1309
- dispatch(req, res, meta, done) {
1310
- let index = -1;
1311
- meta = meta || {};
1312
- let allowedMethods = [];
1313
- if (this.isRoot && typeof this.timeout === 'number') {
1314
- createRequestTimeout(res, this.timeout, done);
1315
- }
1316
- const fn = (err)=>{
1317
- /* istanbul ignore if */ if (!this.isRoot) {
1318
- if (typeof done !== 'undefined') {
1319
- setImmediate(()=>done(err));
1320
- }
1321
- return;
1322
- }
1323
- if (typeof err !== 'undefined') {
1324
- res.statusCode = 400;
1325
- res.end();
1326
- return;
1327
- }
1328
- if (req.method && req.method.toLowerCase() === exports.MethodName.OPTIONS) {
1329
- const options = allowedMethods.map((key)=>key.toUpperCase()).join(',');
1330
- res.setHeader(exports.HeaderName.ALLOW, options);
1331
- send(res, options);
1332
- return;
1333
- }
1334
- res.statusCode = 404;
1335
- res.end();
1336
- };
1337
- let path = meta.path || useRequestPath(req);
1876
+ async dispatch(event, meta = {}) {
1877
+ const allowedMethods = [];
1878
+ let path = meta.path || useRequestPath(event.req);
1338
1879
  if (this.pathMatcher) {
1339
1880
  const output = this.pathMatcher.exec(path);
1340
1881
  if (typeof output !== 'undefined') {
@@ -1348,74 +1889,78 @@ class Router {
1348
1889
  }
1349
1890
  }
1350
1891
  meta.path = path;
1892
+ if (meta.routerIds) {
1893
+ meta.routerIds.push(this.id);
1894
+ } else {
1895
+ meta.routerIds = [
1896
+ this.id
1897
+ ];
1898
+ }
1351
1899
  if (!meta.mountPath) {
1352
1900
  meta.mountPath = '/';
1353
1901
  }
1354
- const next = (err)=>{
1355
- if (index >= this.stack.length) {
1356
- setImmediate(fn, err);
1357
- return;
1358
- }
1359
- let layer;
1360
- let match = false;
1361
- while(!match && index < this.stack.length){
1362
- index++;
1363
- layer = this.stack[index];
1364
- if (isLayerInstance(layer)) {
1365
- if (!layer.isError() && err) {
1366
- continue;
1367
- }
1368
- 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;
1369
1910
  }
1370
- if (isRouterInstance(layer)) {
1371
- match = layer.matchPath(path);
1372
- }
1373
- if (isRouteInstance(layer)) {
1374
- match = layer.matchPath(path);
1375
- if (req.method && !layer.matchMethod(req.method)) {
1376
- match = false;
1377
- if (req.method.toLowerCase() === exports.MethodName.OPTIONS) {
1378
- allowedMethods = smob.distinctArray(smob.merge(allowedMethods, layer.getMethods()));
1379
- }
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());
1380
1922
  }
1381
1923
  }
1382
1924
  }
1383
- if (!match || !layer) {
1384
- setImmediate(fn, err);
1385
- return;
1925
+ if (!match) {
1926
+ continue;
1386
1927
  }
1387
- const layerMeta = {
1388
- ...meta
1389
- };
1928
+ const layerMeta = cloneDispatcherMeta(meta);
1390
1929
  if (isLayerInstance(layer)) {
1391
1930
  const output = layer.exec(path);
1392
1931
  if (output) {
1393
- layerMeta.params = smob.merge(output.params, layerMeta.params || {});
1932
+ layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1394
1933
  layerMeta.mountPath = cleanDoubleSlashes(`${layerMeta.mountPath || ''}/${output.path}`);
1395
1934
  }
1396
- }
1397
- if (err) {
1398
- if (isLayerInstance(layer) && layer.isError()) {
1399
- layer.dispatch(req, res, layerMeta, next, err);
1400
- return;
1935
+ if (err) {
1936
+ layerMeta.error = err;
1401
1937
  }
1402
- /* istanbul ignore next */ setImmediate(next, err);
1403
- return;
1938
+ } else if (err) {
1939
+ continue;
1404
1940
  }
1405
- layer.dispatch(req, res, layerMeta, next);
1406
- };
1407
- next();
1408
- }
1409
- /* istanbul ignore next */ dispatchAsync(req, res) {
1410
- return new Promise((resolve, reject)=>{
1411
- this.dispatch(req, res, {}, (err)=>{
1412
- if (err) {
1413
- reject(err);
1414
- return;
1941
+ try {
1942
+ const dispatched = await layer.dispatch(event, layerMeta);
1943
+ if (dispatched) {
1944
+ return true;
1415
1945
  }
1416
- resolve();
1417
- });
1418
- });
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;
1419
1964
  }
1420
1965
  // --------------------------------------------------
1421
1966
  route(path) {
@@ -1429,7 +1974,9 @@ class Router {
1429
1974
  const route = new Route({
1430
1975
  path,
1431
1976
  pathMatcher: {
1432
- sensitive: this.pathMatcherOptions.sensitive
1977
+ ...this.pathMatcherOptions ? {
1978
+ sensitive: this.pathMatcherOptions.sensitive
1979
+ } : {}
1433
1980
  }
1434
1981
  });
1435
1982
  this.stack.push(route);
@@ -1484,7 +2031,6 @@ class Router {
1484
2031
  if (path) {
1485
2032
  item.setPath(path);
1486
2033
  }
1487
- item.setPathMatcherOptions(this.pathMatcherOptions);
1488
2034
  this.stack.push(item);
1489
2035
  continue;
1490
2036
  }
@@ -1494,7 +2040,9 @@ class Router {
1494
2040
  pathMatcher: {
1495
2041
  strict: false,
1496
2042
  end: false,
1497
- sensitive: this.pathMatcherOptions.sensitive
2043
+ ...this.pathMatcherOptions ? {
2044
+ sensitive: this.pathMatcherOptions.sensitive
2045
+ } : {}
1498
2046
  }
1499
2047
  }, item));
1500
2048
  }
@@ -1502,21 +2050,17 @@ class Router {
1502
2050
  return this;
1503
2051
  }
1504
2052
  // --------------------------------------------------
1505
- constructor(ctx){
2053
+ constructor(options = {}){
1506
2054
  this['@instanceof'] = Symbol.for('Router');
1507
2055
  /**
1508
2056
  * Array of mounted layers, routes & routers.
1509
2057
  *
1510
2058
  * @protected
1511
2059
  */ this.stack = [];
1512
- ctx = ctx || {};
1513
- this.pathMatcherOptions = {
1514
- end: false,
1515
- sensitive: true,
1516
- ...ctx.pathMatcher || {}
1517
- };
1518
- this.timeout = ctx.timeout;
1519
- this.setPath(ctx.path || '/');
2060
+ this.id = generateRouterID();
2061
+ this.pathMatcherOptions = options.pathMatcher;
2062
+ this.setPath(options.path);
2063
+ setRouterOptions(this.id, transformRouterOptions(options));
1520
2064
  }
1521
2065
  }
1522
2066
 
@@ -1526,19 +2070,19 @@ exports.Route = Route;
1526
2070
  exports.Router = Router;
1527
2071
  exports.appendResponseHeader = appendResponseHeader;
1528
2072
  exports.appendResponseHeaderDirective = appendResponseHeaderDirective;
1529
- exports.buildConfig = buildConfig;
1530
- exports.buildEtagFn = buildEtagFn;
1531
- exports.buildTrustProxyFn = buildTrustProxyFn;
1532
- exports.cleanDoubleSlashes = cleanDoubleSlashes;
1533
- exports.createEtag = createEtag;
1534
- 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;
1535
2083
  exports.extendRequestBody = extendRequestBody;
1536
2084
  exports.extendRequestCookies = extendRequestCookies;
1537
2085
  exports.extendRequestQuery = extendRequestQuery;
1538
- exports.generateETag = generateETag;
1539
- exports.getCharsetForMimeType = getCharsetForMimeType;
1540
- exports.getConfigOption = getConfigOption;
1541
- exports.getMimeType = getMimeType;
1542
2086
  exports.getRequestAcceptableCharset = getRequestAcceptableCharset;
1543
2087
  exports.getRequestAcceptableCharsets = getRequestAcceptableCharsets;
1544
2088
  exports.getRequestAcceptableContentType = getRequestAcceptableContentType;
@@ -1551,22 +2095,17 @@ exports.getRequestHeader = getRequestHeader;
1551
2095
  exports.getRequestHostName = getRequestHostName;
1552
2096
  exports.getRequestIP = getRequestIP;
1553
2097
  exports.getRequestProtocol = getRequestProtocol;
1554
- exports.hasLeadingSlash = hasLeadingSlash;
1555
2098
  exports.hasRequestBody = hasRequestBody;
1556
2099
  exports.hasRequestCookies = hasRequestCookies;
1557
2100
  exports.hasRequestQuery = hasRequestQuery;
1558
- exports.hasTrailingSlash = hasTrailingSlash;
1559
- exports.isInstance = isInstance;
1560
2101
  exports.isLayerInstance = isLayerInstance;
1561
- exports.isObject = isObject;
1562
2102
  exports.isPath = isPath;
1563
- exports.isPromise = isPromise;
1564
2103
  exports.isRequestCacheable = isRequestCacheable;
2104
+ exports.isResponseGone = isResponseGone;
1565
2105
  exports.isRouteInstance = isRouteInstance;
1566
2106
  exports.isRouterInstance = isRouterInstance;
1567
2107
  exports.matchRequestContentType = matchRequestContentType;
1568
- exports.onResponseFinished = onResponseFinished;
1569
- exports.processHandlerExecutionOutput = processHandlerExecutionOutput;
2108
+ exports.mergeDispatcherMetaParams = mergeDispatcherMetaParams;
1570
2109
  exports.send = send;
1571
2110
  exports.sendAccepted = sendAccepted;
1572
2111
  exports.sendCreated = sendCreated;
@@ -1574,8 +2113,8 @@ exports.sendFile = sendFile;
1574
2113
  exports.sendFormat = sendFormat;
1575
2114
  exports.sendRedirect = sendRedirect;
1576
2115
  exports.sendStream = sendStream;
1577
- exports.setConfig = setConfig;
1578
- exports.setConfigOption = setConfigOption;
2116
+ exports.sendWebBlob = sendWebBlob;
2117
+ exports.sendWebResponse = sendWebResponse;
1579
2118
  exports.setRequestBody = setRequestBody;
1580
2119
  exports.setRequestCookies = setRequestCookies;
1581
2120
  exports.setRequestEnv = setRequestEnv;
@@ -1584,12 +2123,12 @@ exports.setRequestMountPath = setRequestMountPath;
1584
2123
  exports.setRequestParam = setRequestParam;
1585
2124
  exports.setRequestParams = setRequestParams;
1586
2125
  exports.setRequestQuery = setRequestQuery;
2126
+ exports.setRequestRouterIds = setRequestRouterIds;
1587
2127
  exports.setResponseCacheHeaders = setResponseCacheHeaders;
1588
2128
  exports.setResponseContentTypeByFileName = setResponseContentTypeByFileName;
1589
2129
  exports.setResponseHeaderAttachment = setResponseHeaderAttachment;
1590
2130
  exports.setResponseHeaderContentType = setResponseHeaderContentType;
1591
2131
  exports.unsetRequestEnv = unsetRequestEnv;
1592
- exports.useConfig = useConfig;
1593
2132
  exports.useRequestBody = useRequestBody;
1594
2133
  exports.useRequestCookie = useRequestCookie;
1595
2134
  exports.useRequestCookies = useRequestCookies;
@@ -1600,8 +2139,5 @@ exports.useRequestParam = useRequestParam;
1600
2139
  exports.useRequestParams = useRequestParams;
1601
2140
  exports.useRequestPath = useRequestPath;
1602
2141
  exports.useRequestQuery = useRequestQuery;
1603
- exports.withLeadingSlash = withLeadingSlash;
1604
- exports.withTrailingSlash = withTrailingSlash;
1605
- exports.withoutLeadingSlash = withoutLeadingSlash;
1606
- exports.withoutTrailingSlash = withoutTrailingSlash;
2142
+ exports.useRequestRouterIds = useRequestRouterIds;
1607
2143
  //# sourceMappingURL=index.cjs.map