routup 1.0.3 → 3.0.0-alpha.1

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 (119) hide show
  1. package/README.md +163 -26
  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 +7 -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/{route → dispatcher/adapters/web}/index.d.ts +0 -1
  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 +32 -0
  12. package/dist/dispatcher/utils.d.ts +5 -0
  13. package/dist/error/create.d.ts +11 -0
  14. package/dist/error/index.d.ts +3 -0
  15. package/dist/error/is.d.ts +2 -0
  16. package/dist/error/module.d.ts +3 -0
  17. package/dist/handler/constants.d.ts +4 -0
  18. package/dist/handler/core/define.d.ts +3 -0
  19. package/dist/handler/core/index.d.ts +2 -0
  20. package/dist/handler/core/types.d.ts +10 -0
  21. package/dist/handler/error/define.d.ts +3 -0
  22. package/dist/handler/error/index.d.ts +2 -0
  23. package/dist/handler/error/types.d.ts +11 -0
  24. package/dist/handler/index.d.ts +6 -1
  25. package/dist/handler/is.d.ts +2 -0
  26. package/dist/handler/types-base.d.ts +6 -0
  27. package/dist/handler/types.d.ts +5 -0
  28. package/dist/index.cjs +1202 -629
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.ts +6 -5
  31. package/dist/index.mjs +1172 -597
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/layer/constants.d.ts +1 -0
  34. package/dist/layer/module.d.ts +13 -10
  35. package/dist/layer/type.d.ts +6 -4
  36. package/dist/path/index.d.ts +1 -0
  37. package/dist/path/matcher.d.ts +5 -6
  38. package/dist/path/type.d.ts +1 -0
  39. package/dist/path/utils.d.ts +2 -0
  40. package/dist/plugin/index.d.ts +2 -0
  41. package/dist/plugin/is.d.ts +2 -0
  42. package/dist/plugin/types.d.ts +32 -0
  43. package/dist/{helpers/request → request/helpers}/body.d.ts +1 -1
  44. package/dist/{helpers/request → request/helpers}/cache.d.ts +1 -1
  45. package/dist/{helpers/request → request/helpers}/cookie.d.ts +1 -1
  46. package/dist/{helpers/request → request/helpers}/env.d.ts +1 -1
  47. package/dist/{helpers/request → request/helpers}/header-accept-charset.d.ts +1 -1
  48. package/dist/{helpers/request → request/helpers}/header-accept-language.d.ts +1 -1
  49. package/dist/{helpers/request → request/helpers}/header-accept.d.ts +1 -1
  50. package/dist/{helpers/request → request/helpers}/header-content-type.d.ts +1 -1
  51. package/dist/{helpers/request → request/helpers}/header.d.ts +1 -1
  52. package/dist/{helpers/request → request/helpers}/hostname.d.ts +1 -1
  53. package/dist/{helpers/request → request/helpers}/index.d.ts +1 -0
  54. package/dist/{helpers/request → request/helpers}/ip.d.ts +1 -1
  55. package/dist/{helpers/request → request/helpers}/mount-path.d.ts +1 -1
  56. package/dist/{helpers/request → request/helpers}/negotiator.d.ts +1 -1
  57. package/dist/{helpers/request → request/helpers}/params.d.ts +1 -1
  58. package/dist/{helpers/request → request/helpers}/path.d.ts +1 -1
  59. package/dist/{helpers/request → request/helpers}/protocol.d.ts +1 -1
  60. package/dist/{helpers/request → request/helpers}/query.d.ts +1 -1
  61. package/dist/request/helpers/router.d.ts +3 -0
  62. package/dist/request/index.d.ts +3 -0
  63. package/dist/request/module.d.ts +4 -0
  64. package/dist/request/types.d.ts +13 -0
  65. package/dist/{helpers/response → response/helpers}/cache.d.ts +1 -1
  66. package/dist/response/helpers/gone.d.ts +2 -0
  67. package/dist/{helpers/response → response/helpers}/header-attachment.d.ts +1 -1
  68. package/dist/{helpers/response → response/helpers}/header-content-type.d.ts +1 -1
  69. package/dist/{helpers/response → response/helpers}/header.d.ts +1 -1
  70. package/dist/{helpers/response → response/helpers}/index.d.ts +3 -0
  71. package/dist/{helpers/response → response/helpers}/send-accepted.d.ts +2 -2
  72. package/dist/{helpers/response → response/helpers}/send-created.d.ts +2 -2
  73. package/dist/response/helpers/send-file.d.ts +17 -0
  74. package/dist/{helpers/response → response/helpers}/send-format.d.ts +1 -1
  75. package/dist/response/helpers/send-redirect.d.ts +2 -0
  76. package/dist/response/helpers/send-stream.d.ts +3 -0
  77. package/dist/response/helpers/send-web-blob.d.ts +3 -0
  78. package/dist/response/helpers/send-web-response.d.ts +3 -0
  79. package/dist/response/helpers/send.d.ts +2 -0
  80. package/dist/response/helpers/utils.d.ts +2 -0
  81. package/dist/response/index.d.ts +3 -0
  82. package/dist/response/module.d.ts +3 -0
  83. package/dist/response/types.d.ts +4 -0
  84. package/dist/router/constants.d.ts +1 -0
  85. package/dist/router/index.d.ts +1 -1
  86. package/dist/router/module.d.ts +34 -50
  87. package/dist/router/utils.d.ts +3 -0
  88. package/dist/router-options/index.d.ts +2 -0
  89. package/dist/router-options/module.d.ts +4 -0
  90. package/dist/router-options/transform.d.ts +2 -0
  91. package/dist/router-options/type.d.ts +41 -0
  92. package/dist/types.d.ts +10 -0
  93. package/dist/utils/cookie.d.ts +1 -0
  94. package/dist/utils/etag/module.d.ts +4 -3
  95. package/dist/utils/etag/type.d.ts +1 -1
  96. package/dist/utils/header.d.ts +3 -0
  97. package/dist/utils/index.d.ts +4 -1
  98. package/dist/utils/is-instance.d.ts +1 -1
  99. package/dist/utils/path.d.ts +5 -2
  100. package/dist/utils/stream.d.ts +8 -0
  101. package/dist/utils/web.d.ts +3 -0
  102. package/package.json +21 -19
  103. package/dist/config/module.d.ts +0 -8
  104. package/dist/config/type.d.ts +0 -34
  105. package/dist/handler/utils.d.ts +0 -2
  106. package/dist/helpers/index.d.ts +0 -2
  107. package/dist/helpers/response/send-file.d.ts +0 -9
  108. package/dist/helpers/response/send-redirect.d.ts +0 -2
  109. package/dist/helpers/response/send-stream.d.ts +0 -4
  110. package/dist/helpers/response/send.d.ts +0 -2
  111. package/dist/helpers/response/utils.d.ts +0 -3
  112. package/dist/route/module.d.ts +0 -27
  113. package/dist/route/type.d.ts +0 -6
  114. package/dist/route/utils.d.ts +0 -2
  115. package/dist/router/type.d.ts +0 -24
  116. package/dist/type.d.ts +0 -24
  117. package/dist/utils/request.d.ts +0 -2
  118. /package/dist/{config → dispatcher/adapters/raw}/index.d.ts +0 -0
  119. /package/dist/{helpers/request → request/helpers}/header-accept-encoding.d.ts +0 -0
package/dist/index.mjs CHANGED
@@ -1,17 +1,184 @@
1
- import { Continu } from 'continu';
2
- import process from 'node:process';
3
- import zod from 'zod';
4
- import crypto from 'node:crypto';
5
- import { Stats, stat, createReadStream } from 'node:fs';
1
+ import { HTTPError } from '@ebec/http';
6
2
  import { merge, hasOwnProperty, distinctArray } from 'smob';
3
+ import { Buffer } from 'buffer';
4
+ import { subtle } from 'uncrypto';
7
5
  import { compile, all } from 'proxy-addr';
8
6
  import { getType, get } from 'mime-explorer';
9
- import { GatewayTimeoutErrorOptions, BadRequestError } from '@ebec/http';
10
7
  import Negotiator from 'negotiator';
11
- import { URL } from 'node:url';
12
- import path from 'node:path';
8
+ import { Readable, Writable } from 'readable-stream';
13
9
  import { pathToRegexp } from 'path-to-regexp';
14
- import { createServer } from 'node:http';
10
+
11
+ var MethodName;
12
+ (function(MethodName) {
13
+ MethodName["GET"] = "get";
14
+ MethodName["POST"] = "post";
15
+ MethodName["PUT"] = "put";
16
+ MethodName["PATCH"] = "patch";
17
+ MethodName["DELETE"] = "delete";
18
+ MethodName["OPTIONS"] = "options";
19
+ MethodName["HEAD"] = "head";
20
+ })(MethodName || (MethodName = {}));
21
+ var HeaderName;
22
+ (function(HeaderName) {
23
+ HeaderName["ACCEPT"] = "accept";
24
+ HeaderName["ACCEPT_CHARSET"] = "accept-charset";
25
+ HeaderName["ACCEPT_ENCODING"] = "accept-encoding";
26
+ HeaderName["ACCEPT_LANGUAGE"] = "accept-language";
27
+ HeaderName["ACCEPT_RANGES"] = "accept-ranges";
28
+ HeaderName["ALLOW"] = "allow";
29
+ HeaderName["CACHE_CONTROL"] = "cache-control";
30
+ HeaderName["CONTENT_DISPOSITION"] = "content-disposition";
31
+ HeaderName["CONTENT_ENCODING"] = "content-encoding";
32
+ HeaderName["CONTENT_LENGTH"] = "content-length";
33
+ HeaderName["CONTENT_RANGE"] = "content-range";
34
+ HeaderName["CONTENT_TYPE"] = "content-type";
35
+ HeaderName["COOKIE"] = "cookie";
36
+ HeaderName["ETag"] = "etag";
37
+ HeaderName["HOST"] = "host";
38
+ HeaderName["IF_MODIFIED_SINCE"] = "if-modified-since";
39
+ HeaderName["IF_NONE_MATCH"] = "if-none-match";
40
+ HeaderName["LAST_MODIFIED"] = "last-modified";
41
+ HeaderName["LOCATION"] = "location";
42
+ HeaderName["RANGE"] = "range";
43
+ HeaderName["RATE_LIMIT_LIMIT"] = "ratelimit-limit";
44
+ HeaderName["RATE_LIMIT_REMAINING"] = "ratelimit-remaining";
45
+ HeaderName["RATE_LIMIT_RESET"] = "ratelimit-reset";
46
+ HeaderName["RETRY_AFTER"] = "retry-after";
47
+ HeaderName["SET_COOKIE"] = "set-cookie";
48
+ HeaderName["TRANSFER_ENCODING"] = "transfer-encoding";
49
+ HeaderName["X_FORWARDED_HOST"] = "x-forwarded-host";
50
+ HeaderName["X_FORWARDED_FOR"] = "x-forwarded-for";
51
+ HeaderName["X_FORWARDED_PROTO"] = "x-forwarded-proto";
52
+ })(HeaderName || (HeaderName = {}));
53
+
54
+ class ErrorProxy extends HTTPError {
55
+ }
56
+
57
+ function isError(input) {
58
+ return input instanceof ErrorProxy;
59
+ }
60
+
61
+ /**
62
+ * Create an error proxy by
63
+ * - an existing error (accessible via cause property)
64
+ * - options
65
+ * - message
66
+ *
67
+ * @param input
68
+ */ function createError(input) {
69
+ if (isError(input)) {
70
+ return input;
71
+ }
72
+ if (typeof input === 'string') {
73
+ return new ErrorProxy(input);
74
+ }
75
+ return new ErrorProxy({
76
+ cause: input
77
+ }, input);
78
+ }
79
+
80
+ /*
81
+ Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
82
+ that are within a single set-cookie field-value, such as in the Expires portion.
83
+
84
+ This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
85
+ Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
86
+ React Native's fetch does this for *every* header, including set-cookie.
87
+
88
+ Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
89
+ Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
90
+ */ function splitCookiesString(input) {
91
+ if (Array.isArray(input)) {
92
+ return input.flatMap((el)=>splitCookiesString(el));
93
+ }
94
+ if (typeof input !== 'string') {
95
+ return [];
96
+ }
97
+ const cookiesStrings = [];
98
+ let pos = 0;
99
+ let start;
100
+ let ch;
101
+ let lastComma;
102
+ let nextStart;
103
+ let cookiesSeparatorFound;
104
+ const skipWhitespace = ()=>{
105
+ while(pos < input.length && /\s/.test(input.charAt(pos))){
106
+ pos += 1;
107
+ }
108
+ return pos < input.length;
109
+ };
110
+ const notSpecialChar = ()=>{
111
+ ch = input.charAt(pos);
112
+ return ch !== '=' && ch !== ';' && ch !== ',';
113
+ };
114
+ while(pos < input.length){
115
+ start = pos;
116
+ cookiesSeparatorFound = false;
117
+ while(skipWhitespace()){
118
+ ch = input.charAt(pos);
119
+ if (ch === ',') {
120
+ // ',' is a cookie separator if we have later first '=', not ';' or ','
121
+ lastComma = pos;
122
+ pos += 1;
123
+ skipWhitespace();
124
+ nextStart = pos;
125
+ while(pos < input.length && notSpecialChar()){
126
+ pos += 1;
127
+ }
128
+ // currently special character
129
+ if (pos < input.length && input.charAt(pos) === '=') {
130
+ // we found cookies separator
131
+ cookiesSeparatorFound = true;
132
+ // pos is inside the next cookie, so back up and return it.
133
+ pos = nextStart;
134
+ cookiesStrings.push(input.substring(start, lastComma));
135
+ start = pos;
136
+ } else {
137
+ // in param ',' or param separator ';',
138
+ // we continue from that comma
139
+ pos = lastComma + 1;
140
+ }
141
+ } else {
142
+ pos += 1;
143
+ }
144
+ }
145
+ if (!cookiesSeparatorFound || pos >= input.length) {
146
+ cookiesStrings.push(input.substring(start, input.length));
147
+ }
148
+ }
149
+ return cookiesStrings;
150
+ }
151
+
152
+ function transformHeaderToTuples(key, value) {
153
+ const output = [];
154
+ if (Array.isArray(value)) {
155
+ for(let j = 0; j < value.length; j++){
156
+ output.push([
157
+ key,
158
+ value[j]
159
+ ]);
160
+ }
161
+ } else if (value !== undefined) {
162
+ output.push([
163
+ key,
164
+ String(value)
165
+ ]);
166
+ }
167
+ return output;
168
+ }
169
+ function transformHeadersToTuples(input) {
170
+ const output = [];
171
+ const keys = Object.keys(input);
172
+ for(let i = 0; i < keys.length; i++){
173
+ const key = keys[i].toLowerCase();
174
+ output.push(...transformHeaderToTuples(key, input[key]));
175
+ }
176
+ return output;
177
+ }
178
+
179
+ function isObject(item) {
180
+ return !!item && typeof item === 'object' && !Array.isArray(item);
181
+ }
15
182
 
16
183
  /**
17
184
  * Determine if object is a Stats object.
@@ -20,15 +187,17 @@ import { createServer } from 'node:http';
20
187
  * @return {boolean}
21
188
  * @api private
22
189
  */ function isStatsObject(obj) {
23
- /* istanbul ignore next */ if (typeof Stats === 'function' && obj instanceof Stats) {
24
- return true;
25
- }
26
190
  // quack quack
27
- return !!obj && typeof obj === 'object' && 'ctime' in obj && Object.prototype.toString.call(obj.ctime) === '[object Date]' && 'mtime' in obj && Object.prototype.toString.call(obj.mtime) === '[object Date]' && 'ino' in obj && typeof obj.ino === 'number' && 'size' in obj && typeof obj.size === 'number';
191
+ 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';
192
+ }
193
+ async function sha1(str) {
194
+ const enc = new TextEncoder();
195
+ const hash = await subtle.digest('SHA-1', enc.encode(str));
196
+ return btoa(String.fromCharCode(...new Uint8Array(hash)));
28
197
  }
29
198
  /**
30
199
  * Generate an ETag.
31
- */ function generateETag(input) {
200
+ */ async function generateETag(input) {
32
201
  if (isStatsObject(input)) {
33
202
  const mtime = input.mtime.getTime().toString(16);
34
203
  const size = input.size.toString(16);
@@ -40,30 +209,26 @@ import { createServer } from 'node:http';
40
209
  }
41
210
  const entity = Buffer.isBuffer(input) ? input.toString('utf-8') : input;
42
211
  // compute hash of entity
43
- const hash = crypto.createHash('sha1').update(entity, 'utf8').digest('base64').substring(0, 27);
44
- return `"${entity.length.toString(16)}-${hash}"`;
212
+ const hash = await sha1(entity);
213
+ return `"${entity.length.toString(16)}-${hash.substring(0, 27)}"`;
45
214
  }
46
215
  /**
47
216
  * Create a simple ETag.
48
- */ function createEtag(input, options) {
217
+ */ async function createEtag(input, options) {
49
218
  options = options || {};
50
219
  const weak = typeof options.weak === 'boolean' ? options.weak : isStatsObject(input);
51
220
  // generate entity tag
52
- const tag = generateETag(input);
221
+ const tag = await generateETag(input);
53
222
  return weak ? `W/${tag}` : tag;
54
223
  }
55
224
 
56
- function isObject(item) {
57
- return !!item && typeof item === 'object' && !Array.isArray(item);
58
- }
59
-
60
225
  function buildEtagFn(input) {
61
226
  if (typeof input === 'function') {
62
227
  return input;
63
228
  }
64
229
  input = input ?? true;
65
230
  if (input === false) {
66
- return ()=>undefined;
231
+ return ()=>Promise.resolve(undefined);
67
232
  }
68
233
  let options = {
69
234
  weak: true
@@ -71,7 +236,7 @@ function buildEtagFn(input) {
71
236
  if (isObject(input)) {
72
237
  options = merge(input, options);
73
238
  }
74
- return (body, encoding, size)=>{
239
+ return async (body, encoding, size)=>{
75
240
  const buff = Buffer.isBuffer(body) ? body : Buffer.from(body, encoding);
76
241
  if (typeof options.threshold !== 'undefined') {
77
242
  size = size ?? Buffer.byteLength(buff);
@@ -99,8 +264,11 @@ function buildTrustProxyFn(input) {
99
264
  return compile(input || []);
100
265
  }
101
266
 
102
- function isInstance(input, name) {
103
- return typeof input === 'object' && input !== null && input['@instanceof'] === Symbol.for(name);
267
+ function isInstance(input, sym) {
268
+ if (!isObject(input)) {
269
+ return false;
270
+ }
271
+ return input['@instanceof'] === sym;
104
272
  }
105
273
 
106
274
  function getMimeType(type) {
@@ -120,8 +288,25 @@ function getCharsetForMimeType(type) {
120
288
  return undefined;
121
289
  }
122
290
 
123
- function isPath(input) {
124
- return typeof input === 'string' || input instanceof RegExp;
291
+ /**
292
+ * Based on https://github.com/unjs/pathe v1.1.1 (055f50a6f1131f4e5c56cf259dd8816168fba329)
293
+ */ function normalizeWindowsPath(input = '') {
294
+ if (!input || !input.includes('\\')) {
295
+ return input;
296
+ }
297
+ return input.replace(/\\/g, '/');
298
+ }
299
+ const EXTNAME_RE = /.(\.[^./]+)$/;
300
+ function extname(input) {
301
+ const match = EXTNAME_RE.exec(normalizeWindowsPath(input));
302
+ return match && match[1] || '';
303
+ }
304
+ function basename(input, extension) {
305
+ const lastSegment = normalizeWindowsPath(input).split('/').pop();
306
+ if (!lastSegment) {
307
+ return input;
308
+ }
309
+ return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
125
310
  }
126
311
 
127
312
  function isPromise(p) {
@@ -130,24 +315,14 @@ function isPromise(p) {
130
315
  typeof p.then === 'function');
131
316
  }
132
317
 
133
- /* istanbul ignore next */ function createRequestTimeout(res, timeout, done) {
134
- const instance = setTimeout(()=>{
135
- res.statusCode = GatewayTimeoutErrorOptions.statusCode;
136
- res.statusMessage = GatewayTimeoutErrorOptions.message;
137
- res.end();
138
- }, timeout);
139
- res.once('close', ()=>{
140
- clearTimeout(instance);
141
- /* istanbul ignore next */ if (typeof done === 'function') {
142
- done();
143
- }
144
- });
145
- /* istanbul ignore next */ res.once('error', (e)=>{
146
- clearTimeout(instance);
147
- if (typeof done === 'function') {
148
- done(e);
149
- }
150
- });
318
+ function isNodeStream(input) {
319
+ return isObject(input) && typeof input.pipe === 'function' && typeof input.read === 'function';
320
+ }
321
+ function isWebStream(input) {
322
+ return isObject(input) && typeof input.pipeTo === 'function';
323
+ }
324
+ function isStream(data) {
325
+ return isNodeStream(data) || isWebStream(data);
151
326
  }
152
327
 
153
328
  const TRAILING_SLASH_RE = /\/$|\/\?/;
@@ -167,115 +342,26 @@ function withoutTrailingSlash(input = '', queryParams = false) {
167
342
  const [s0, ...s] = input.split('?');
168
343
  return (s0.slice(0, -1) || '/') + (s.length ? `?${s.join('?')}` : '');
169
344
  }
170
- function withTrailingSlash(input = '', queryParams = false) {
171
- if (!queryParams) {
172
- return input.endsWith('/') ? input : `${input}/`;
173
- }
174
- if (hasTrailingSlash(input, true)) {
175
- return input || '/';
176
- }
177
- const [s0, ...s] = input.split('?');
178
- return `${s0}/${s.length ? `?${s.join('?')}` : ''}`;
179
- }
180
345
  function hasLeadingSlash(input = '') {
181
346
  return input.startsWith('/');
182
347
  }
183
- function withoutLeadingSlash(input = '') {
184
- return (hasLeadingSlash(input) ? input.substr(1) : input) || '/';
185
- }
186
348
  function withLeadingSlash(input = '') {
187
349
  return hasLeadingSlash(input) ? input : `/${input}`;
188
350
  }
189
351
  function cleanDoubleSlashes(input = '') {
190
- return input.split('://').map((str)=>str.replace(/\/{2,}/g, '/')).join('://');
191
- }
192
-
193
- let instance;
194
- function buildConfig() {
195
- return new Continu({
196
- defaults: {
197
- env: process.env.NODE_ENV || 'development',
198
- trustProxy: ()=>false,
199
- subdomainOffset: 2,
200
- etag: buildEtagFn(),
201
- proxyIpMax: 0
202
- },
203
- transformers: {
204
- etag: (value)=>buildEtagFn(value),
205
- trustProxy: (value)=>buildTrustProxyFn(value)
206
- },
207
- validators: {
208
- env: (value)=>zod.string().safeParse(value),
209
- trustProxy: (value)=>zod.any().safeParse(value),
210
- subdomainOffset: (value)=>zod.number().nonnegative().safeParse(value),
211
- etag: (value)=>zod.any().safeParse(value),
212
- proxyIpMax: (value)=>zod.number().nonnegative().safeParse(value)
213
- }
214
- });
215
- }
216
- function useConfig() {
217
- if (typeof instance !== 'undefined') {
218
- return instance;
352
+ if (input.indexOf('://') !== -1) {
353
+ return input.split('://').map((str)=>cleanDoubleSlashes(str)).join('://');
219
354
  }
220
- instance = buildConfig();
221
- return instance;
355
+ return input.replace(/\/+/g, '/');
222
356
  }
223
- function setConfig(config) {
224
- instance = config;
225
- }
226
- function setConfigOption(key, value) {
227
- const config = useConfig();
228
- config.setRaw(key, value);
229
- return config.get();
357
+
358
+ function isWebBlob(input) {
359
+ return typeof Blob !== 'undefined' && input instanceof Blob;
230
360
  }
231
- function getConfigOption(key) {
232
- const config = useConfig();
233
- return config.get(key);
361
+ function isWebResponse(input) {
362
+ return typeof Response !== 'undefined' && input instanceof Response;
234
363
  }
235
364
 
236
- var MethodName;
237
- (function(MethodName) {
238
- MethodName["GET"] = 'get';
239
- MethodName["POST"] = 'post';
240
- MethodName["PUT"] = 'put';
241
- MethodName["PATCH"] = 'patch';
242
- MethodName["DELETE"] = 'delete';
243
- MethodName["OPTIONS"] = 'options';
244
- MethodName["HEAD"] = 'head';
245
- })(MethodName || (MethodName = {}));
246
- var HeaderName;
247
- (function(HeaderName) {
248
- HeaderName["ACCEPT"] = 'accept';
249
- HeaderName["ACCEPT_CHARSET"] = 'accept-charset';
250
- HeaderName["ACCEPT_ENCODING"] = 'accept-encoding';
251
- HeaderName["ACCEPT_LANGUAGE"] = 'accept-language';
252
- HeaderName["ACCEPT_RANGES"] = 'accept-ranges';
253
- HeaderName["ALLOW"] = 'allow';
254
- HeaderName["CACHE_CONTROL"] = 'cache-control';
255
- HeaderName["CONTENT_DISPOSITION"] = 'content-disposition';
256
- HeaderName["CONTENT_ENCODING"] = 'content-encoding';
257
- HeaderName["CONTENT_LENGTH"] = 'content-length';
258
- HeaderName["CONTENT_RANGE"] = 'content-range';
259
- HeaderName["CONTENT_TYPE"] = 'content-type';
260
- HeaderName["COOKIE"] = 'cookie';
261
- HeaderName["ETag"] = 'etag';
262
- HeaderName["HOST"] = 'host';
263
- HeaderName["IF_MODIFIED_SINCE"] = 'if-modified-since';
264
- HeaderName["IF_NONE_MATCH"] = 'if-none-match';
265
- HeaderName["LAST_MODIFIED"] = 'last-modified';
266
- HeaderName["LOCATION"] = 'location';
267
- HeaderName["RANGE"] = 'range';
268
- HeaderName["RATE_LIMIT_LIMIT"] = 'ratelimit-limit';
269
- HeaderName["RATE_LIMIT_REMAINING"] = 'ratelimit-remaining';
270
- HeaderName["RATE_LIMIT_RESET"] = 'ratelimit-reset';
271
- HeaderName["RETRY_AFTER"] = 'retry-after';
272
- HeaderName["SET_COOKIE"] = 'set-cookie';
273
- HeaderName["TRANSFER_ENCODING"] = 'transfer-encoding';
274
- HeaderName["X_FORWARDED_HOST"] = 'x-forwarded-host';
275
- HeaderName["X_FORWARDED_FOR"] = 'x-forwarded-for';
276
- HeaderName["X_FORWARDED_PROTO"] = 'x-forwarded-proto';
277
- })(HeaderName || (HeaderName = {}));
278
-
279
365
  const BodySymbol = Symbol.for('ReqBody');
280
366
  function useRequestBody(req, key) {
281
367
  let body;
@@ -537,14 +623,48 @@ function matchRequestContentType(req, contentType) {
537
623
  return header.split('; ').shift() === getMimeType(contentType);
538
624
  }
539
625
 
626
+ const defaults = {
627
+ trustProxy: ()=>false,
628
+ subdomainOffset: 2,
629
+ etag: buildEtagFn(),
630
+ proxyIpMax: 0
631
+ };
632
+ const instances = {};
633
+ function setRouterOptions(id, input) {
634
+ instances[id] = input;
635
+ }
636
+ function findRouterOption(key, path) {
637
+ if (!path || path.length === 0) {
638
+ return defaults[key];
639
+ }
640
+ if (path.length > 0) {
641
+ for(let i = path.length; i >= 0; i--){
642
+ if (hasOwnProperty(instances, path[i]) && typeof instances[path[i]][key] !== 'undefined') {
643
+ return instances[path[i]][key];
644
+ }
645
+ }
646
+ }
647
+ return defaults[key];
648
+ }
649
+
650
+ const routerSymbol = Symbol.for('ReqRouterID');
651
+ function setRequestRouterPath(req, path) {
652
+ req[routerSymbol] = path;
653
+ }
654
+ function useRequestRouterPath(req) {
655
+ if (routerSymbol in req) {
656
+ return req[routerSymbol];
657
+ }
658
+ return undefined;
659
+ }
660
+
540
661
  function getRequestHostName(req, options) {
541
662
  options = options || {};
542
663
  let trustProxy;
543
664
  if (typeof options.trustProxy !== 'undefined') {
544
665
  trustProxy = buildTrustProxyFn(options.trustProxy);
545
666
  } else {
546
- const config = useConfig();
547
- trustProxy = config.get('trustProxy');
667
+ trustProxy = findRouterOption('trustProxy', useRequestRouterPath(req));
548
668
  }
549
669
  let hostname = req.headers[HeaderName.X_FORWARDED_HOST];
550
670
  if (!hostname || !req.socket.remoteAddress || !trustProxy(req.socket.remoteAddress, 0)) {
@@ -570,8 +690,7 @@ function getRequestIP(req, options) {
570
690
  if (typeof options.trustProxy !== 'undefined') {
571
691
  trustProxy = buildTrustProxyFn(options.trustProxy);
572
692
  } else {
573
- const config = useConfig();
574
- trustProxy = config.get('trustProxy');
693
+ trustProxy = findRouterOption('trustProxy', useRequestRouterPath(req));
575
694
  }
576
695
  const addrs = all(req, trustProxy);
577
696
  return addrs[addrs.length - 1];
@@ -632,8 +751,7 @@ function getRequestProtocol(req, options) {
632
751
  if (typeof options.trustProxy !== 'undefined') {
633
752
  trustProxy = buildTrustProxyFn(options.trustProxy);
634
753
  } else {
635
- const config = useConfig();
636
- trustProxy = config.get('trustProxy');
754
+ trustProxy = findRouterOption('trustProxy', useRequestRouterPath(req));
637
755
  }
638
756
  let protocol = options.default;
639
757
  /* istanbul ignore next */ if (hasOwnProperty(req.socket, 'encrypted') && !!req.socket.encrypted) {
@@ -701,6 +819,78 @@ function extendRequestQuery(req, key, value) {
701
819
  setRequestQuery(req, key, value);
702
820
  }
703
821
 
822
+ function createRequest(context) {
823
+ let readable;
824
+ if (context.body) {
825
+ if (isWebStream(context.body)) {
826
+ readable = Readable.fromWeb(context.body);
827
+ } else {
828
+ readable = Readable.from(context.body);
829
+ }
830
+ } else {
831
+ readable = new Readable();
832
+ }
833
+ const headers = context.headers || {};
834
+ const rawHeaders = [];
835
+ let keys = Object.keys(headers);
836
+ for(let i = 0; i < keys.length; i++){
837
+ const header = headers[keys[i]];
838
+ if (Array.isArray(header)) {
839
+ for(let j = 0; j < header.length; j++){
840
+ rawHeaders.push(keys[i], header[j]);
841
+ }
842
+ } else if (typeof header === 'string') {
843
+ rawHeaders.push(keys[i], header);
844
+ }
845
+ }
846
+ const headersDistinct = {};
847
+ keys = Object.keys(headers);
848
+ for(let i = 0; i < keys.length; i++){
849
+ const header = headers[keys[i]];
850
+ if (Array.isArray(header)) {
851
+ headersDistinct[keys[i]] = header;
852
+ }
853
+ if (typeof header === 'string') {
854
+ headersDistinct[keys[i]] = [
855
+ header
856
+ ];
857
+ }
858
+ }
859
+ Object.defineProperty(readable, 'connection', {
860
+ get () {
861
+ return {
862
+ remoteAddress: '127.0.0.1'
863
+ };
864
+ }
865
+ });
866
+ Object.defineProperty(readable, 'socket', {
867
+ get () {
868
+ return {
869
+ remoteAddress: '127.0.0.1'
870
+ };
871
+ }
872
+ });
873
+ Object.assign(readable, {
874
+ aborted: false,
875
+ complete: true,
876
+ headers,
877
+ headersDistinct,
878
+ httpVersion: '1.1',
879
+ httpVersionMajor: 1,
880
+ httpVersionMinor: 1,
881
+ method: context.method || 'GET',
882
+ rawHeaders,
883
+ rawTrailers: [],
884
+ trailers: {},
885
+ trailersDistinct: {},
886
+ url: context.url || '/',
887
+ setTimeout (_msecs, _callback) {
888
+ return this;
889
+ }
890
+ });
891
+ return readable;
892
+ }
893
+
704
894
  function setResponseCacheHeaders(res, options) {
705
895
  options = options || {};
706
896
  const cacheControls = [
@@ -716,6 +906,17 @@ function setResponseCacheHeaders(res, options) {
716
906
  res.setHeader('cache-control', cacheControls.join(', '));
717
907
  }
718
908
 
909
+ const GoneSymbol = Symbol.for('ResGone');
910
+ function isResponseGone(res) {
911
+ if (res.headersSent || res.writableEnded) {
912
+ return true;
913
+ }
914
+ if (GoneSymbol in res) {
915
+ return res[GoneSymbol];
916
+ }
917
+ return false;
918
+ }
919
+
719
920
  function appendResponseHeader(res, name, value) {
720
921
  let header = res.getHeader(name);
721
922
  if (!header) {
@@ -765,7 +966,7 @@ function appendResponseHeaderDirective(res, name, value) {
765
966
  }
766
967
 
767
968
  function setResponseContentTypeByFileName(res, fileName) {
768
- const ext = path.extname(fileName);
969
+ const ext = extname(fileName);
769
970
  if (ext) {
770
971
  let type = getMimeType(ext.substring(1));
771
972
  if (type) {
@@ -777,23 +978,6 @@ function setResponseContentTypeByFileName(res, fileName) {
777
978
  }
778
979
  }
779
980
  }
780
- /* istanbul ignore next */ function onResponseFinished(res, cb) {
781
- let called;
782
- const callCallback = (err)=>{
783
- if (called) return;
784
- called = true;
785
- cb(err);
786
- };
787
- res.on('finish', ()=>{
788
- callCallback();
789
- });
790
- res.on('close', ()=>{
791
- callCallback();
792
- });
793
- res.on('error', (err)=>{
794
- callCallback(err);
795
- });
796
- }
797
981
 
798
982
  function setResponseHeaderAttachment(res, filename) {
799
983
  if (typeof filename === 'string') {
@@ -815,7 +999,7 @@ function setResponseHeaderContentType(res, input, ifNotExists) {
815
999
  }
816
1000
  }
817
1001
 
818
- function send(res, chunk) {
1002
+ async function send(res, chunk) {
819
1003
  switch(typeof chunk){
820
1004
  case 'string':
821
1005
  {
@@ -826,11 +1010,9 @@ function send(res, chunk) {
826
1010
  case 'number':
827
1011
  case 'object':
828
1012
  {
829
- if (chunk === null) {
830
- chunk = '';
831
- } else if (Buffer.isBuffer(chunk)) {
1013
+ if (Buffer.isBuffer(chunk)) {
832
1014
  setResponseHeaderContentType(res, 'bin', true);
833
- } else {
1015
+ } else if (chunk !== null) {
834
1016
  chunk = JSON.stringify(chunk);
835
1017
  setResponseHeaderContentType(res, 'application/json', true);
836
1018
  }
@@ -845,7 +1027,7 @@ function send(res, chunk) {
845
1027
  }
846
1028
  // populate Content-Length
847
1029
  let len;
848
- if (chunk !== undefined) {
1030
+ if (chunk !== undefined && chunk !== null) {
849
1031
  if (Buffer.isBuffer(chunk)) {
850
1032
  // get length of Buffer
851
1033
  len = chunk.length;
@@ -860,10 +1042,12 @@ function send(res, chunk) {
860
1042
  }
861
1043
  res.setHeader(HeaderName.CONTENT_LENGTH, `${len}`);
862
1044
  }
863
- const config = useConfig();
864
- const etagFn = config.get('etag');
865
1045
  if (typeof len !== 'undefined') {
866
- const chunkHash = etagFn(chunk, encoding, len);
1046
+ const etagFn = findRouterOption('etag', useRequestRouterPath(res.req));
1047
+ const chunkHash = await etagFn(chunk, encoding, len);
1048
+ if (isResponseGone(res)) {
1049
+ return Promise.resolve();
1050
+ }
867
1051
  if (typeof chunkHash === 'string') {
868
1052
  res.setHeader(HeaderName.ETag, chunkHash);
869
1053
  if (res.req.headers[HeaderName.IF_NONE_MATCH] === chunkHash) {
@@ -876,24 +1060,30 @@ function send(res, chunk) {
876
1060
  res.removeHeader(HeaderName.CONTENT_TYPE);
877
1061
  res.removeHeader(HeaderName.CONTENT_LENGTH);
878
1062
  res.removeHeader(HeaderName.TRANSFER_ENCODING);
879
- chunk = '';
880
1063
  }
881
1064
  // alter headers for 205
882
1065
  if (res.statusCode === 205) {
883
1066
  res.setHeader(HeaderName.CONTENT_LENGTH, 0);
884
1067
  res.removeHeader(HeaderName.TRANSFER_ENCODING);
885
- chunk = '';
1068
+ }
1069
+ if (isResponseGone(res)) {
1070
+ return Promise.resolve();
886
1071
  }
887
1072
  if (res.req.method === 'HEAD') {
888
1073
  // skip body for HEAD
889
1074
  res.end();
890
- return;
1075
+ return Promise.resolve();
1076
+ }
1077
+ if (typeof chunk === 'undefined' || chunk === null) {
1078
+ res.end();
1079
+ return Promise.resolve();
891
1080
  }
892
1081
  if (typeof encoding !== 'undefined') {
893
1082
  res.end(chunk, encoding);
894
- return;
1083
+ return Promise.resolve();
895
1084
  }
896
1085
  res.end(chunk);
1086
+ return Promise.resolve();
897
1087
  }
898
1088
 
899
1089
  function sendAccepted(res, chunk) {
@@ -908,87 +1098,116 @@ function sendCreated(res, chunk) {
908
1098
  return send(res, chunk);
909
1099
  }
910
1100
 
911
- function sendStream(res, stream, fn) {
912
- stream.on('open', ()=>{
913
- stream.pipe(res);
914
- });
915
- /* istanbul ignore next */ stream.on('error', (err)=>{
916
- if (typeof fn === 'function') {
917
- fn(err);
918
- } else {
919
- res.statusCode = 400;
1101
+ async function sendStream(res, stream, next) {
1102
+ if (isWebStream(stream)) {
1103
+ return stream.pipeTo(new WritableStream({
1104
+ write (chunk) {
1105
+ res.write(chunk);
1106
+ }
1107
+ })).then(()=>{
1108
+ if (next) {
1109
+ return next();
1110
+ }
920
1111
  res.end();
921
- }
922
- });
923
- stream.on('close', ()=>{
924
- if (typeof fn === 'function') {
925
- fn();
926
- } else {
1112
+ return Promise.resolve();
1113
+ }).catch((err)=>{
1114
+ if (next) {
1115
+ return next(err);
1116
+ }
1117
+ return Promise.reject(err);
1118
+ });
1119
+ }
1120
+ return new Promise((resolve, reject)=>{
1121
+ stream.on('open', ()=>{
1122
+ stream.pipe(res);
1123
+ });
1124
+ /* istanbul ignore next */ stream.on('error', (err)=>{
1125
+ if (next) {
1126
+ Promise.resolve().then(()=>next(err)).then(()=>resolve()).catch((e)=>reject(e));
1127
+ return;
1128
+ }
927
1129
  res.end();
928
- }
1130
+ reject(err);
1131
+ });
1132
+ stream.on('close', ()=>{
1133
+ if (next) {
1134
+ Promise.resolve().then(()=>next()).then(()=>resolve()).catch((e)=>reject(e));
1135
+ return;
1136
+ }
1137
+ res.end();
1138
+ resolve();
1139
+ });
929
1140
  });
930
1141
  }
931
1142
 
932
- function resolveStats(options, cb) {
933
- if (options.stats) {
934
- cb(null, options.stats);
935
- return;
936
- }
937
- stat(options.filePath, (err, stats)=>cb(err, stats));
938
- }
939
- function sendFile(res, filePath, fn) {
940
- let options;
941
- if (typeof filePath === 'string') {
942
- options = {
943
- filePath
944
- };
945
- } else {
946
- options = filePath;
947
- }
948
- const fileName = path.basename(options.filePath);
949
- if (options.attachment) {
950
- const dispositionHeader = res.getHeader(HeaderName.CONTENT_DISPOSITION);
951
- if (!dispositionHeader) {
952
- setResponseHeaderAttachment(res, fileName);
1143
+ async function sendFile(res, options, next) {
1144
+ let stats;
1145
+ try {
1146
+ stats = await options.stats();
1147
+ } catch (e) {
1148
+ if (next) {
1149
+ return next(e);
953
1150
  }
954
- } else {
955
- setResponseContentTypeByFileName(res, fileName);
956
- }
957
- resolveStats(options, (err, stats)=>{
958
- /* istanbul ignore next */ if (err) {
959
- if (typeof fn === 'function') {
960
- fn(err);
961
- } else {
962
- res.statusCode = 404;
963
- res.end();
1151
+ if (isResponseGone(res)) {
1152
+ return Promise.resolve();
1153
+ }
1154
+ return Promise.reject(e);
1155
+ }
1156
+ const name = options.name || stats.name;
1157
+ if (name) {
1158
+ const fileName = basename(name);
1159
+ if (options.attachment) {
1160
+ const dispositionHeader = res.getHeader(HeaderName.CONTENT_DISPOSITION);
1161
+ if (!dispositionHeader) {
1162
+ setResponseHeaderAttachment(res, fileName);
964
1163
  }
965
- return;
1164
+ } else {
1165
+ setResponseContentTypeByFileName(res, fileName);
966
1166
  }
967
- const streamOptions = {};
1167
+ }
1168
+ const contentOptions = {};
1169
+ if (stats.size) {
968
1170
  const rangeHeader = res.req.headers[HeaderName.RANGE];
969
1171
  if (rangeHeader) {
970
1172
  const [x, y] = rangeHeader.replace('bytes=', '').split('-');
971
- streamOptions.end = Math.min(parseInt(y, 10) || stats.size - 1, stats.size - 1);
972
- streamOptions.start = parseInt(x, 10) || 0;
973
- if (streamOptions.end >= stats.size) {
974
- streamOptions.end = stats.size - 1;
1173
+ contentOptions.end = Math.min(parseInt(y, 10) || stats.size - 1, stats.size - 1);
1174
+ contentOptions.start = parseInt(x, 10) || 0;
1175
+ if (contentOptions.end >= stats.size) {
1176
+ contentOptions.end = stats.size - 1;
975
1177
  }
976
- if (streamOptions.start >= stats.size) {
1178
+ if (contentOptions.start >= stats.size) {
977
1179
  res.setHeader(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
978
1180
  res.statusCode = 416;
979
1181
  res.end();
980
- return;
1182
+ return Promise.resolve();
981
1183
  }
982
- res.setHeader(HeaderName.CONTENT_RANGE, `bytes ${streamOptions.start}-${streamOptions.end}/${stats.size}`);
983
- res.setHeader(HeaderName.CONTENT_LENGTH, streamOptions.end - streamOptions.start + 1);
1184
+ res.setHeader(HeaderName.CONTENT_RANGE, `bytes ${contentOptions.start}-${contentOptions.end}/${stats.size}`);
1185
+ res.setHeader(HeaderName.CONTENT_LENGTH, contentOptions.end - contentOptions.start + 1);
984
1186
  } else {
985
1187
  res.setHeader(HeaderName.CONTENT_LENGTH, stats.size);
986
1188
  }
987
1189
  res.setHeader(HeaderName.ACCEPT_RANGES, 'bytes');
988
- res.setHeader(HeaderName.LAST_MODIFIED, stats.mtime.toUTCString());
989
- res.setHeader(HeaderName.ETag, `W/"${stats.size}-${stats.mtime.getTime()}"`);
990
- sendStream(res, createReadStream(options.filePath, streamOptions), fn);
991
- });
1190
+ if (stats.mtime) {
1191
+ const mtime = new Date(stats.mtime);
1192
+ res.setHeader(HeaderName.LAST_MODIFIED, mtime.toUTCString());
1193
+ res.setHeader(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
1194
+ }
1195
+ }
1196
+ try {
1197
+ const content = await options.content(contentOptions);
1198
+ if (isStream(content)) {
1199
+ return await sendStream(res, content, next);
1200
+ }
1201
+ return await send(res, content);
1202
+ } catch (e) {
1203
+ if (next) {
1204
+ return next(e);
1205
+ }
1206
+ if (isResponseGone(res)) {
1207
+ return Promise.resolve();
1208
+ }
1209
+ return Promise.reject(e);
1210
+ }
992
1211
  }
993
1212
 
994
1213
  function sendFormat(res, input) {
@@ -1010,19 +1229,388 @@ function sendRedirect(res, location, statusCode = 302) {
1010
1229
  return send(res, html);
1011
1230
  }
1012
1231
 
1013
- function processHandlerExecutionOutput(res, next, output) {
1014
- if (isPromise(output)) {
1015
- output.then((r)=>{
1016
- if (typeof r !== 'undefined') {
1017
- send(res, r);
1232
+ function sendWebResponse(res, webResponse) {
1233
+ if (webResponse.redirected) {
1234
+ res.setHeader(HeaderName.LOCATION, webResponse.url);
1235
+ }
1236
+ if (webResponse.status) {
1237
+ res.statusCode = webResponse.status;
1238
+ }
1239
+ if (webResponse.statusText) {
1240
+ res.statusMessage = webResponse.statusText;
1241
+ }
1242
+ webResponse.headers.forEach((value, key)=>{
1243
+ if (key === HeaderName.SET_COOKIE) {
1244
+ res.appendHeader(key, splitCookiesString(value));
1245
+ } else {
1246
+ res.setHeader(key, value);
1247
+ }
1248
+ });
1249
+ if (webResponse.body) {
1250
+ return sendStream(res, webResponse.body);
1251
+ }
1252
+ res.end();
1253
+ return Promise.resolve();
1254
+ }
1255
+
1256
+ function sendWebBlob(res, blob) {
1257
+ setResponseHeaderContentType(res, blob.type);
1258
+ return sendStream(res, blob.stream());
1259
+ }
1260
+
1261
+ function createResponse(request) {
1262
+ let output;
1263
+ let encoding;
1264
+ const write = (chunk, chunkEncoding, callback)=>{
1265
+ if (typeof chunk !== 'undefined') {
1266
+ const chunkEncoded = typeof chunk === 'string' ? Buffer.from(chunk, chunkEncoding || encoding || 'utf8') : chunk;
1267
+ if (typeof output !== 'undefined') {
1268
+ output = Buffer.concat([
1269
+ output,
1270
+ chunkEncoded
1271
+ ]);
1272
+ } else {
1273
+ output = chunkEncoded;
1018
1274
  }
1019
- return r;
1020
- }).catch(next);
1021
- return;
1275
+ }
1276
+ encoding = chunkEncoding;
1277
+ if (callback) {
1278
+ callback();
1279
+ }
1280
+ };
1281
+ const writable = new Writable({
1282
+ decodeStrings: false,
1283
+ write (chunk, arg2, arg3) {
1284
+ const chunkEncoding = typeof arg2 === 'string' ? encoding : 'utf-8';
1285
+ let cb;
1286
+ if (typeof arg2 === 'function') {
1287
+ cb = arg2;
1288
+ } else if (typeof arg3 === 'function') {
1289
+ cb = arg3;
1290
+ }
1291
+ write(chunk, chunkEncoding, cb);
1292
+ return true;
1293
+ }
1294
+ });
1295
+ Object.defineProperty(writable, 'body', {
1296
+ get () {
1297
+ if (output) {
1298
+ const arrayBuffer = new ArrayBuffer(output.length);
1299
+ const view = new Uint8Array(arrayBuffer);
1300
+ for(let i = 0; i < output.length; ++i){
1301
+ view[i] = output[i];
1302
+ }
1303
+ return arrayBuffer;
1304
+ }
1305
+ return new ArrayBuffer(0);
1306
+ }
1307
+ });
1308
+ const headers = {};
1309
+ Object.assign(writable, {
1310
+ req: request,
1311
+ chunkedEncoding: false,
1312
+ connection: null,
1313
+ headersSent: false,
1314
+ sendDate: false,
1315
+ shouldKeepAlive: false,
1316
+ socket: null,
1317
+ statusCode: 200,
1318
+ statusMessage: '',
1319
+ strictContentLength: false,
1320
+ useChunkedEncodingByDefault: false,
1321
+ finished: false,
1322
+ addTrailers (_headers) {},
1323
+ appendHeader (name, value) {
1324
+ if (name === HeaderName.SET_COOKIE) {
1325
+ value = splitCookiesString(value);
1326
+ }
1327
+ name = name.toLowerCase();
1328
+ const current = headers[name];
1329
+ const all = [
1330
+ ...Array.isArray(current) ? current : [
1331
+ current
1332
+ ],
1333
+ ...Array.isArray(value) ? value : [
1334
+ value
1335
+ ]
1336
+ ].filter(Boolean);
1337
+ headers[name] = all.length > 1 ? all : all[0];
1338
+ return this;
1339
+ },
1340
+ assignSocket (_socket) {},
1341
+ detachSocket (_socket) {},
1342
+ flushHeaders () {},
1343
+ getHeader (name) {
1344
+ return headers[name.toLowerCase()];
1345
+ },
1346
+ getHeaderNames () {
1347
+ return Object.keys(headers);
1348
+ },
1349
+ getHeaders () {
1350
+ return headers;
1351
+ },
1352
+ hasHeader (name) {
1353
+ return hasOwnProperty(headers, name.toLowerCase());
1354
+ },
1355
+ removeHeader (name) {
1356
+ delete headers[name.toLowerCase()];
1357
+ },
1358
+ setHeader (name, value) {
1359
+ if (name === HeaderName.SET_COOKIE && typeof value !== 'number') {
1360
+ value = splitCookiesString(value);
1361
+ }
1362
+ headers[name.toLowerCase()] = value;
1363
+ return this;
1364
+ },
1365
+ setTimeout (_msecs, _callback) {
1366
+ return this;
1367
+ },
1368
+ writeContinue (_callback) {},
1369
+ writeEarlyHints (_hints, callback) {
1370
+ if (typeof callback !== 'undefined') {
1371
+ callback();
1372
+ }
1373
+ },
1374
+ writeProcessing () {},
1375
+ writeHead (statusCode, arg1, arg2) {
1376
+ this.statusCode = statusCode;
1377
+ if (typeof arg1 === 'string') {
1378
+ this.statusMessage = arg1;
1379
+ arg1 = undefined;
1380
+ }
1381
+ const headers = arg2 || arg1;
1382
+ if (headers) {
1383
+ if (Array.isArray(headers)) {
1384
+ for(let i = 0; i < headers.length; i++){
1385
+ const keys = Object.keys(headers[i]);
1386
+ for(let j = 0; j < keys.length; j++){
1387
+ this.setHeader(keys[i], headers[i][keys[j]]);
1388
+ }
1389
+ }
1390
+ } else {
1391
+ const keys = Object.keys(headers);
1392
+ for(let i = 0; i < keys.length; i++){
1393
+ this.setHeader(keys[i], headers[keys[i]]);
1394
+ }
1395
+ }
1396
+ }
1397
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1398
+ // @ts-ignore
1399
+ this.headersSent = true;
1400
+ return this;
1401
+ }
1402
+ });
1403
+ return writable;
1404
+ }
1405
+
1406
+ function buildDispatcherMeta(input) {
1407
+ return {
1408
+ mountPath: input.mountPath || '/',
1409
+ params: input.params || {},
1410
+ path: input.path || '/',
1411
+ routerPath: []
1412
+ };
1413
+ }
1414
+ function cloneDispatcherMeta(input) {
1415
+ return {
1416
+ path: input.path,
1417
+ mountPath: input.mountPath,
1418
+ error: input.error,
1419
+ routerPath: [
1420
+ ...input.routerPath
1421
+ ],
1422
+ params: cloneDispatcherMetaParams(input.params)
1423
+ };
1424
+ }
1425
+ function cloneDispatcherMetaParams(input) {
1426
+ if (typeof input === 'undefined') {
1427
+ return {};
1428
+ }
1429
+ const keys = Object.keys(input);
1430
+ if (keys.length === 0) {
1431
+ return {};
1432
+ }
1433
+ const output = {};
1434
+ for(let i = 0; i < keys.length; i++){
1435
+ output[keys[i]] = input[keys[i]];
1436
+ }
1437
+ return output;
1438
+ }
1439
+ function mergeDispatcherMetaParams(t1, t2) {
1440
+ if (!t1 && !t2) {
1441
+ return {};
1442
+ }
1443
+ if (!t1 || !t2) {
1444
+ return t1 || t2;
1445
+ }
1446
+ const keys = Object.keys(t2);
1447
+ if (keys.length === 0) {
1448
+ return t1;
1022
1449
  }
1023
- if (typeof output !== 'undefined') {
1024
- send(res, output);
1450
+ for(let i = 0; i < keys.length; i++){
1451
+ t1[keys[i]] = t2[keys[i]];
1025
1452
  }
1453
+ return t1;
1454
+ }
1455
+
1456
+ async function dispatchNodeRequest(router, req, res) {
1457
+ try {
1458
+ const dispatched = await router.dispatch({
1459
+ req,
1460
+ res
1461
+ }, buildDispatcherMeta({
1462
+ path: useRequestPath(req)
1463
+ }));
1464
+ if (dispatched) {
1465
+ return;
1466
+ }
1467
+ if (!isResponseGone(res)) {
1468
+ res.statusCode = 404;
1469
+ res.end();
1470
+ }
1471
+ } catch (e) {
1472
+ if (!isResponseGone(res)) {
1473
+ if (isError(e)) {
1474
+ res.statusCode = e.statusCode;
1475
+ if (e.statusMessage) {
1476
+ res.statusMessage = e.statusMessage;
1477
+ }
1478
+ } else {
1479
+ res.statusCode = 500;
1480
+ }
1481
+ res.end();
1482
+ }
1483
+ }
1484
+ }
1485
+ function createNodeDispatcher(router) {
1486
+ return (req, res)=>{
1487
+ // eslint-disable-next-line no-void
1488
+ void dispatchNodeRequest(router, req, res);
1489
+ };
1490
+ }
1491
+
1492
+ async function dispatchRawRequest(router, request, options = {}) {
1493
+ const req = createRequest({
1494
+ url: request.path,
1495
+ method: request.method,
1496
+ body: request.body,
1497
+ headers: request.headers
1498
+ });
1499
+ const res = createResponse(req);
1500
+ const getHeaders = ()=>{
1501
+ const output = {};
1502
+ const headers = res.getHeaders();
1503
+ const keys = Object.keys(headers);
1504
+ for(let i = 0; i < keys.length; i++){
1505
+ const header = headers[keys[i]];
1506
+ if (typeof header === 'number') {
1507
+ output[keys[i]] = `${header}`;
1508
+ } else if (header) {
1509
+ output[keys[i]] = header;
1510
+ }
1511
+ }
1512
+ return output;
1513
+ };
1514
+ const createRawResponse = (input = {})=>({
1515
+ status: input.status || res.statusCode,
1516
+ statusMessage: input.statusMessage || res.statusMessage,
1517
+ headers: getHeaders(),
1518
+ body: res.body
1519
+ });
1520
+ try {
1521
+ const dispatched = await router.dispatch({
1522
+ req,
1523
+ res
1524
+ }, buildDispatcherMeta({
1525
+ path: useRequestPath(req)
1526
+ }));
1527
+ if (dispatched) {
1528
+ return createRawResponse();
1529
+ }
1530
+ return createRawResponse({
1531
+ status: 404
1532
+ });
1533
+ } catch (e) {
1534
+ if (options.throwOnError) {
1535
+ throw e;
1536
+ }
1537
+ if (isError(e)) {
1538
+ return createRawResponse({
1539
+ status: e.statusCode,
1540
+ statusMessage: e.statusMessage
1541
+ });
1542
+ }
1543
+ return createRawResponse({
1544
+ status: 500
1545
+ });
1546
+ }
1547
+ }
1548
+ function createRawDispatcher(router) {
1549
+ return async (request)=>dispatchRawRequest(router, request);
1550
+ }
1551
+
1552
+ async function dispatchWebRequest(router, request, options = {}) {
1553
+ const url = new URL(request.url);
1554
+ const headers = {};
1555
+ request.headers.forEach((value, key)=>{
1556
+ headers[key] = value;
1557
+ });
1558
+ const res = await dispatchRawRequest(router, {
1559
+ method: request.method,
1560
+ path: url.pathname + url.search,
1561
+ headers,
1562
+ body: request.body
1563
+ }, options);
1564
+ let body;
1565
+ if (request.method === MethodName.HEAD || res.status === 304 || res.status === 101 || res.status === 204 || res.status === 205) {
1566
+ body = null;
1567
+ } else {
1568
+ body = res.body;
1569
+ }
1570
+ return new Response(body, {
1571
+ headers: transformHeadersToTuples(res.headers),
1572
+ status: res.status,
1573
+ statusText: res.statusMessage
1574
+ });
1575
+ }
1576
+ function createWebDispatcher(router) {
1577
+ return async (request)=>dispatchWebRequest(router, request);
1578
+ }
1579
+
1580
+ var HandlerType;
1581
+ (function(HandlerType) {
1582
+ HandlerType["CORE"] = "core";
1583
+ HandlerType["ERROR"] = "error";
1584
+ })(HandlerType || (HandlerType = {}));
1585
+
1586
+ function coreHandler(input) {
1587
+ if (typeof input === 'function') {
1588
+ return {
1589
+ type: HandlerType.CORE,
1590
+ fn: input
1591
+ };
1592
+ }
1593
+ return {
1594
+ type: HandlerType.CORE,
1595
+ ...input
1596
+ };
1597
+ }
1598
+
1599
+ function errorHandler(input) {
1600
+ if (typeof input === 'function') {
1601
+ return {
1602
+ type: HandlerType.ERROR,
1603
+ fn: input
1604
+ };
1605
+ }
1606
+ return {
1607
+ type: HandlerType.ERROR,
1608
+ ...input
1609
+ };
1610
+ }
1611
+
1612
+ function isHandler(input) {
1613
+ return isObject(input) && typeof input.fn === 'function' && typeof input.type === 'string';
1026
1614
  }
1027
1615
 
1028
1616
  function decodeParam(val) {
@@ -1033,268 +1621,219 @@ function decodeParam(val) {
1033
1621
  }
1034
1622
  class PathMatcher {
1035
1623
  test(path) {
1036
- const fastSlash = this.path === '/' && this.regexpOptions.end === false;
1037
- if (fastSlash) {
1038
- return true;
1039
- }
1040
1624
  return this.regexp.test(path);
1041
1625
  }
1042
1626
  exec(path) {
1043
- let match = null;
1044
- const fastSlash = this.path === '/' && this.regexpOptions.end === false;
1045
- if (fastSlash) {
1627
+ if (this.path === '/' && this.regexpOptions.end === false) {
1046
1628
  return {
1047
1629
  path: '/',
1048
1630
  params: {}
1049
1631
  };
1050
1632
  }
1051
- match = this.regexp.exec(path);
1052
- if (!match) {
1053
- return undefined;
1054
- }
1055
- if (this.path instanceof RegExp) {
1633
+ if (this.path === '*') {
1056
1634
  return {
1057
1635
  path,
1058
1636
  params: {
1059
- 0: decodeParam(match[0])
1637
+ 0: decodeParam(path)
1060
1638
  }
1061
1639
  };
1062
1640
  }
1063
- const output = {};
1641
+ const match = this.regexp.exec(path);
1642
+ if (!match) {
1643
+ return undefined;
1644
+ }
1645
+ const params = {};
1064
1646
  for(let i = 1; i < match.length; i++){
1065
1647
  const key = this.regexpKeys[i - 1];
1066
1648
  const prop = key.name;
1067
1649
  const val = decodeParam(match[i]);
1068
1650
  if (typeof val !== 'undefined') {
1069
- output[prop] = val;
1651
+ params[prop] = val;
1070
1652
  }
1071
1653
  }
1072
1654
  return {
1073
1655
  path: match[0],
1074
- params: output
1656
+ params
1075
1657
  };
1076
1658
  }
1077
1659
  constructor(path, options){
1078
1660
  this.regexpKeys = [];
1079
1661
  this.path = path;
1080
1662
  this.regexpOptions = options || {};
1081
- if (path instanceof RegExp) {
1082
- this.regexp = path;
1083
- } else {
1084
- this.regexp = pathToRegexp(path, this.regexpKeys, options);
1085
- }
1663
+ this.regexp = pathToRegexp(path, this.regexpKeys, options);
1086
1664
  }
1087
1665
  }
1088
1666
 
1667
+ function isPath(input) {
1668
+ return typeof input === 'string' || input instanceof RegExp;
1669
+ }
1670
+
1671
+ const LayerSymbol = Symbol.for('Layer');
1672
+
1089
1673
  class Layer {
1090
1674
  // --------------------------------------------------
1091
- isError() {
1092
- return this.fn.length === 4;
1093
- }
1094
- dispatch(req, res, meta, next, err) {
1095
- setRequestParams(req, meta.params || {});
1096
- setRequestMountPath(req, meta.mountPath || '/');
1097
- if (typeof err !== 'undefined') {
1098
- if (this.fn.length === 4) {
1099
- try {
1100
- this.fn(err, req, res, next);
1101
- } catch (e) {
1102
- /* istanbul ignore next */ /* istanbul ignore next */ if (e instanceof Error) {
1103
- next(e);
1104
- } else {
1105
- next(new BadRequestError({
1106
- message: 'The request could not be processed by the error handler.'
1107
- }));
1675
+ get type() {
1676
+ return this.handler.type;
1677
+ }
1678
+ get path() {
1679
+ return this.handler.path;
1680
+ }
1681
+ get method() {
1682
+ return this.handler.method ? this.handler.method.toLowerCase() : undefined;
1683
+ }
1684
+ // --------------------------------------------------
1685
+ dispatch(event, meta) {
1686
+ if (this.pathMatcher) {
1687
+ const pathMatch = this.pathMatcher.exec(meta.path);
1688
+ if (pathMatch) {
1689
+ meta.params = mergeDispatcherMetaParams(meta.params, pathMatch.params);
1690
+ }
1691
+ }
1692
+ setRequestParams(event.req, meta.params);
1693
+ setRequestMountPath(event.req, meta.mountPath);
1694
+ setRequestRouterPath(event.req, meta.routerPath);
1695
+ return new Promise((resolve, reject)=>{
1696
+ let handled = false;
1697
+ const unsubscribe = ()=>{
1698
+ event.res.off('close', onFinished);
1699
+ event.res.off('error', onFinished);
1700
+ };
1701
+ const shutdown = (dispatched, err)=>{
1702
+ if (handled) {
1703
+ return;
1704
+ }
1705
+ handled = true;
1706
+ unsubscribe();
1707
+ if (err) {
1708
+ reject(createError(err));
1709
+ } else {
1710
+ resolve(dispatched);
1711
+ }
1712
+ };
1713
+ const onFinished = (err)=>shutdown(true, err);
1714
+ const onNext = (err)=>shutdown(false, err);
1715
+ event.res.once('close', onFinished);
1716
+ event.res.once('error', onFinished);
1717
+ const handle = (data)=>{
1718
+ if (typeof data === 'undefined' || handled) {
1719
+ return Promise.resolve();
1720
+ }
1721
+ handled = true;
1722
+ unsubscribe();
1723
+ return this.sendOutput(event.res, data).then(()=>resolve(true)).catch((e)=>reject(createError(e)));
1724
+ };
1725
+ try {
1726
+ let output;
1727
+ if (this.handler.type === HandlerType.ERROR) {
1728
+ if (meta.error) {
1729
+ output = this.handler.fn(meta.error, event.req, event.res, onNext);
1108
1730
  }
1731
+ } else {
1732
+ output = this.handler.fn(event.req, event.res, onNext);
1109
1733
  }
1110
- return;
1734
+ if (isPromise(output)) {
1735
+ output.then((r)=>handle(r)).catch((e)=>reject(createError(e)));
1736
+ return;
1737
+ }
1738
+ Promise.resolve().then(()=>handle(output)).catch((e)=>reject(createError(e)));
1739
+ } catch (error) {
1740
+ onNext(error);
1111
1741
  }
1112
- /* istanbul ignore next */ next(err);
1113
- /* istanbul ignore next */ return;
1742
+ });
1743
+ }
1744
+ sendOutput(res, input) {
1745
+ if (input instanceof Error) {
1746
+ return Promise.reject(createError(input));
1114
1747
  }
1115
- /* istanbul ignore next */ if (this.fn.length > 3) {
1116
- next();
1117
- return;
1748
+ if (isStream(input)) {
1749
+ return sendStream(res, input);
1118
1750
  }
1119
- try {
1120
- const output = this.fn(req, res, next);
1121
- processHandlerExecutionOutput(res, next, output);
1122
- } catch (e) {
1123
- /* istanbul ignore next */ if (e instanceof Error) {
1124
- next(e);
1125
- } else {
1126
- next(new BadRequestError({
1127
- message: 'The request could not be processed by the handler.'
1128
- }));
1129
- }
1751
+ if (isWebBlob(input)) {
1752
+ return sendWebBlob(res, input);
1753
+ }
1754
+ if (isWebResponse(input)) {
1755
+ return sendWebResponse(res, input);
1130
1756
  }
1757
+ return send(res, input);
1131
1758
  }
1132
1759
  // --------------------------------------------------
1133
1760
  matchPath(path) {
1761
+ if (!this.pathMatcher) {
1762
+ return true;
1763
+ }
1134
1764
  return this.pathMatcher.test(path);
1135
1765
  }
1136
- exec(path) {
1137
- return this.pathMatcher.exec(path);
1766
+ matchMethod(method) {
1767
+ if (!this.method) {
1768
+ return true;
1769
+ }
1770
+ const name = method.toLowerCase();
1771
+ if (name === this.method) {
1772
+ return true;
1773
+ }
1774
+ return name === MethodName.HEAD && this.method === MethodName.GET;
1138
1775
  }
1139
1776
  // --------------------------------------------------
1140
- constructor(options, fn){
1141
- this['@instanceof'] = Symbol.for('Layer');
1142
- this.pathMatcher = new PathMatcher(options.path, options.pathMatcher);
1143
- this.fn = fn;
1777
+ constructor(handler){
1778
+ this['@instanceof'] = LayerSymbol;
1779
+ this.handler = handler;
1780
+ if (handler.path) {
1781
+ this.pathMatcher = new PathMatcher(handler.path, {
1782
+ end: !!handler.method
1783
+ });
1784
+ }
1144
1785
  }
1145
1786
  }
1146
1787
 
1147
1788
  function isLayerInstance(input) {
1148
- return isInstance(input, 'Layer');
1789
+ return isInstance(input, LayerSymbol);
1149
1790
  }
1150
1791
 
1151
- class Route {
1152
- // --------------------------------------------------
1153
- matchPath(path) {
1154
- return this.pathMatcher.test(path);
1155
- }
1156
- matchMethod(method) {
1157
- let name = method.toLowerCase();
1158
- if (name === MethodName.HEAD && !hasOwnProperty(this.layers, name)) {
1159
- name = MethodName.GET;
1160
- }
1161
- return Object.prototype.hasOwnProperty.call(this.layers, name);
1162
- }
1163
- // --------------------------------------------------
1164
- getMethods() {
1165
- const keys = Object.keys(this.layers);
1166
- if (hasOwnProperty(this.layers, MethodName.GET) && !hasOwnProperty(this.layers, MethodName.HEAD)) {
1167
- keys.push(MethodName.HEAD);
1168
- }
1169
- return keys;
1170
- }
1171
- // --------------------------------------------------
1172
- dispatch(req, res, meta, done) {
1173
- /* istanbul ignore next */ if (!req.method) {
1174
- done();
1175
- return;
1176
- }
1177
- let name = req.method.toLowerCase();
1178
- if (name === MethodName.HEAD && !hasOwnProperty(this.layers, name)) {
1179
- name = MethodName.GET;
1180
- }
1181
- const layers = this.layers[name];
1182
- /* istanbul ignore next */ if (typeof layers === 'undefined' || layers.length === 0 || typeof meta.path === 'undefined') {
1183
- done();
1184
- return;
1185
- }
1186
- const layerMeta = {
1187
- ...meta
1188
- };
1189
- const output = this.pathMatcher.exec(meta.path);
1190
- if (output) {
1191
- layerMeta.params = merge({}, meta.params || {}, output.params);
1192
- }
1193
- let index = -1;
1194
- const next = (err)=>{
1195
- index++;
1196
- if (index >= layers.length) {
1197
- setImmediate(done, err);
1198
- return;
1199
- }
1200
- const layer = layers[index];
1201
- if (err && !layer.isError()) {
1202
- next(err);
1203
- return;
1204
- }
1205
- layer.dispatch(req, res, {
1206
- ...layerMeta
1207
- }, next);
1208
- };
1209
- next();
1210
- }
1211
- // --------------------------------------------------
1212
- register(method, ...handlers) {
1213
- this.layers[method] = [];
1214
- for(let i = 0; i < handlers.length; i++){
1215
- const layer = new Layer({
1216
- path: this.path,
1217
- pathMatcher: this.pathMatcherOptions
1218
- }, handlers[i]);
1219
- this.layers[method].push(layer);
1220
- }
1221
- }
1222
- get(...handlers) {
1223
- return this.register(MethodName.GET, ...handlers);
1224
- }
1225
- post(...handlers) {
1226
- return this.register(MethodName.POST, ...handlers);
1227
- }
1228
- put(...handlers) {
1229
- return this.register(MethodName.PUT, ...handlers);
1230
- }
1231
- patch(...handlers) {
1232
- return this.register(MethodName.PATCH, ...handlers);
1233
- }
1234
- delete(...handlers) {
1235
- return this.register(MethodName.DELETE, ...handlers);
1236
- }
1237
- head(...handlers) {
1238
- return this.register(MethodName.HEAD, ...handlers);
1792
+ function isPluginInstallContext(input) {
1793
+ if (!isObject(input) || !isObject(input.options)) {
1794
+ return false;
1239
1795
  }
1240
- options(...handlers) {
1241
- return this.register(MethodName.OPTIONS, ...handlers);
1796
+ if (typeof input.name !== 'undefined' && typeof input.name !== 'string') {
1797
+ return false;
1242
1798
  }
1243
- // --------------------------------------------------
1244
- isStrictPath() {
1245
- return typeof this.path !== 'string' || this.path !== '/' && this.path.length !== 0;
1799
+ return typeof input.path === 'undefined' || isPath(input.path);
1800
+ }
1801
+
1802
+ function transformRouterOptions(input) {
1803
+ if (typeof input.etag !== 'undefined') {
1804
+ input.etag = buildEtagFn(input.etag);
1246
1805
  }
1247
- // --------------------------------------------------
1248
- constructor(options){
1249
- this['@instanceof'] = Symbol.for('Route');
1250
- this.layers = {};
1251
- this.path = options.path;
1252
- this.pathMatcherOptions = {
1253
- end: true,
1254
- strict: this.isStrictPath(),
1255
- ...options.pathMatcher
1256
- };
1257
- this.pathMatcher = new PathMatcher(this.path, this.pathMatcherOptions);
1806
+ if (typeof input.trustProxy !== 'undefined') {
1807
+ input.trustProxy = buildTrustProxyFn(input.trustProxy);
1258
1808
  }
1809
+ return input;
1259
1810
  }
1260
1811
 
1261
- function isRouteInstance(input) {
1262
- return isInstance(input, 'Route');
1263
- }
1812
+ const RouterSymbol = Symbol.for('Router');
1264
1813
 
1814
+ let nextId = 0;
1815
+ function generateRouterID() {
1816
+ return ++nextId;
1817
+ }
1265
1818
  function isRouterInstance(input) {
1266
- return isInstance(input, 'Router');
1819
+ return isInstance(input, RouterSymbol);
1267
1820
  }
1821
+
1268
1822
  class Router {
1269
1823
  // --------------------------------------------------
1270
- setPathMatcherOptions(input) {
1271
- this.pathMatcherOptions = input;
1272
- if (this.pathMatcher) {
1273
- this.pathMatcher.regexpOptions = this.pathMatcherOptions;
1274
- }
1275
- }
1276
1824
  setPath(value) {
1277
1825
  if (value === '/' || !isPath(value)) {
1278
- this.path = '/';
1279
1826
  return;
1280
1827
  }
1828
+ let path;
1281
1829
  if (typeof value === 'string') {
1282
- this.path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1830
+ path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1283
1831
  } else {
1284
- this.path = value;
1832
+ path = value;
1285
1833
  }
1286
- this.pathMatcher = new PathMatcher(this.path, this.pathMatcherOptions);
1287
- }
1288
- // --------------------------------------------------
1289
- createListener() {
1290
- this.isRoot = true;
1291
- return (req, res)=>{
1292
- this.dispatch(req, res);
1293
- };
1294
- }
1295
- /* istanbul ignore next */ listen(port) {
1296
- const server = createServer(this.createListener());
1297
- return server.listen(port);
1834
+ this.pathMatcher = new PathMatcher(path, {
1835
+ end: false
1836
+ });
1298
1837
  }
1299
1838
  // --------------------------------------------------
1300
1839
  matchPath(path) {
@@ -1304,219 +1843,255 @@ class Router {
1304
1843
  return true;
1305
1844
  }
1306
1845
  // --------------------------------------------------
1307
- dispatch(req, res, meta, done) {
1308
- let index = -1;
1309
- meta = meta || {};
1310
- let allowedMethods = [];
1311
- if (this.isRoot && typeof this.timeout === 'number') {
1312
- createRequestTimeout(res, this.timeout, done);
1313
- }
1314
- const fn = (err)=>{
1315
- /* istanbul ignore if */ if (!this.isRoot) {
1316
- if (typeof done !== 'undefined') {
1317
- setImmediate(()=>done(err));
1318
- }
1319
- return;
1320
- }
1321
- if (typeof err !== 'undefined') {
1322
- res.statusCode = 400;
1323
- res.end();
1324
- return;
1325
- }
1326
- if (req.method && req.method.toLowerCase() === MethodName.OPTIONS) {
1327
- const options = allowedMethods.map((key)=>key.toUpperCase()).join(',');
1328
- res.setHeader(HeaderName.ALLOW, options);
1329
- send(res, options);
1330
- return;
1331
- }
1332
- res.statusCode = 404;
1333
- res.end();
1334
- };
1335
- let path = meta.path || useRequestPath(req);
1846
+ async dispatch(event, meta) {
1847
+ const allowedMethods = [];
1336
1848
  if (this.pathMatcher) {
1337
- const output = this.pathMatcher.exec(path);
1849
+ const output = this.pathMatcher.exec(meta.path);
1338
1850
  if (typeof output !== 'undefined') {
1339
- meta.mountPath = cleanDoubleSlashes(`${meta.mountPath || ''}/${output.path}`);
1340
- if (path === output.path) {
1341
- path = '/';
1851
+ meta.mountPath = cleanDoubleSlashes(`${meta.mountPath}/${output.path}`);
1852
+ if (meta.path === output.path) {
1853
+ meta.path = '/';
1342
1854
  } else {
1343
- path = withLeadingSlash(path.substring(output.path.length));
1855
+ meta.path = withLeadingSlash(meta.path.substring(output.path.length));
1344
1856
  }
1345
- meta.params = merge(meta.params || {}, output.params);
1857
+ meta.params = {
1858
+ ...meta.params,
1859
+ ...output.params
1860
+ };
1346
1861
  }
1347
1862
  }
1348
- meta.path = path;
1349
- if (!meta.mountPath) {
1350
- meta.mountPath = '/';
1351
- }
1352
- const next = (err)=>{
1353
- if (index >= this.stack.length) {
1354
- setImmediate(fn, err);
1355
- return;
1356
- }
1357
- let layer;
1358
- let match = false;
1359
- while(!match && index < this.stack.length){
1360
- index++;
1361
- layer = this.stack[index];
1362
- if (isLayerInstance(layer)) {
1363
- if (!layer.isError() && err) {
1364
- continue;
1365
- }
1366
- match = layer.matchPath(path);
1863
+ meta.routerPath.push(this.id);
1864
+ let err;
1865
+ let item;
1866
+ let itemMeta;
1867
+ let match = false;
1868
+ for(let i = 0; i < this.stack.length; i++){
1869
+ item = this.stack[i];
1870
+ if (isLayerInstance(item)) {
1871
+ if (item.type !== HandlerType.ERROR && err) {
1872
+ continue;
1367
1873
  }
1368
- if (isRouterInstance(layer)) {
1369
- match = layer.matchPath(path);
1370
- }
1371
- if (isRouteInstance(layer)) {
1372
- match = layer.matchPath(path);
1373
- if (req.method && !layer.matchMethod(req.method)) {
1874
+ match = item.matchPath(meta.path);
1875
+ if (match && event.req.method) {
1876
+ if (!item.matchMethod(event.req.method)) {
1374
1877
  match = false;
1375
- if (req.method.toLowerCase() === MethodName.OPTIONS) {
1376
- allowedMethods = distinctArray(merge(allowedMethods, layer.getMethods()));
1377
- }
1878
+ }
1879
+ if (item.method) {
1880
+ allowedMethods.push(item.method);
1378
1881
  }
1379
1882
  }
1883
+ } else if (isRouterInstance(item)) {
1884
+ match = item.matchPath(meta.path);
1380
1885
  }
1381
- if (!match || !layer) {
1382
- setImmediate(fn, err);
1383
- return;
1384
- }
1385
- const layerMeta = {
1386
- ...meta
1387
- };
1388
- if (isLayerInstance(layer)) {
1389
- const output = layer.exec(path);
1390
- if (output) {
1391
- layerMeta.params = merge(output.params, layerMeta.params || {});
1392
- layerMeta.mountPath = cleanDoubleSlashes(`${layerMeta.mountPath || ''}/${output.path}`);
1393
- }
1886
+ if (!match) {
1887
+ continue;
1394
1888
  }
1889
+ itemMeta = cloneDispatcherMeta(meta);
1395
1890
  if (err) {
1396
- if (isLayerInstance(layer) && layer.isError()) {
1397
- layer.dispatch(req, res, layerMeta, next, err);
1398
- return;
1399
- }
1400
- /* istanbul ignore next */ setImmediate(next, err);
1401
- return;
1891
+ itemMeta.error = err;
1402
1892
  }
1403
- layer.dispatch(req, res, layerMeta, next);
1404
- };
1405
- next();
1406
- }
1407
- /* istanbul ignore next */ dispatchAsync(req, res) {
1408
- return new Promise((resolve, reject)=>{
1409
- this.dispatch(req, res, {}, (err)=>{
1410
- if (err) {
1411
- reject(err);
1412
- return;
1893
+ try {
1894
+ const dispatched = await item.dispatch(event, itemMeta);
1895
+ if (dispatched) {
1896
+ return true;
1413
1897
  }
1414
- resolve();
1415
- });
1416
- });
1417
- }
1418
- // --------------------------------------------------
1419
- route(path) {
1420
- if (typeof path === 'string' && path.length > 0) {
1421
- path = withLeadingSlash(path);
1898
+ } catch (e) {
1899
+ if (isError(e)) {
1900
+ err = e;
1901
+ }
1902
+ }
1422
1903
  }
1423
- const index = this.stack.findIndex((item)=>isRouteInstance(item) && item.path === path);
1424
- if (index !== -1) {
1425
- return this.stack[index];
1904
+ if (err) {
1905
+ throw err;
1426
1906
  }
1427
- const route = new Route({
1428
- path,
1429
- pathMatcher: {
1430
- sensitive: this.pathMatcherOptions.sensitive
1907
+ if (event.req.method && event.req.method.toLowerCase() === MethodName.OPTIONS) {
1908
+ if (allowedMethods.indexOf(MethodName.GET) !== -1) {
1909
+ allowedMethods.push(MethodName.HEAD);
1431
1910
  }
1432
- });
1433
- this.stack.push(route);
1434
- return route;
1911
+ distinctArray(allowedMethods);
1912
+ const options = allowedMethods.map((key)=>key.toUpperCase()).join(',');
1913
+ if (!isResponseGone(event.res)) {
1914
+ event.res.setHeader(HeaderName.ALLOW, options);
1915
+ await send(event.res, options);
1916
+ }
1917
+ return true;
1918
+ }
1919
+ return false;
1435
1920
  }
1436
- delete(path, ...handlers) {
1437
- const route = this.route(path);
1438
- route.delete(...handlers);
1921
+ delete(path, handler) {
1922
+ if (isPath(path)) {
1923
+ this.use({
1924
+ ...handler,
1925
+ method: MethodName.DELETE,
1926
+ path
1927
+ });
1928
+ return this;
1929
+ }
1930
+ this.use({
1931
+ ...path,
1932
+ method: MethodName.DELETE
1933
+ });
1439
1934
  return this;
1440
1935
  }
1441
- get(path, ...handlers) {
1442
- const route = this.route(path);
1443
- route.get(...handlers);
1936
+ get(path, handler) {
1937
+ if (isPath(path)) {
1938
+ this.use({
1939
+ ...handler,
1940
+ method: MethodName.GET,
1941
+ path
1942
+ });
1943
+ return this;
1944
+ }
1945
+ this.use({
1946
+ ...path,
1947
+ method: MethodName.GET
1948
+ });
1444
1949
  return this;
1445
1950
  }
1446
- post(path, ...handlers) {
1447
- const route = this.route(path);
1448
- route.post(...handlers);
1951
+ post(path, handler) {
1952
+ if (isPath(path)) {
1953
+ this.use({
1954
+ ...handler,
1955
+ method: MethodName.POST,
1956
+ path
1957
+ });
1958
+ return this;
1959
+ }
1960
+ this.use({
1961
+ ...path,
1962
+ method: MethodName.POST
1963
+ });
1449
1964
  return this;
1450
1965
  }
1451
- put(path, ...handlers) {
1452
- const route = this.route(path);
1453
- route.put(...handlers);
1966
+ put(path, handler) {
1967
+ if (isPath(path)) {
1968
+ this.use({
1969
+ ...handler,
1970
+ method: MethodName.PUT,
1971
+ path
1972
+ });
1973
+ return this;
1974
+ }
1975
+ this.use({
1976
+ ...path,
1977
+ method: MethodName.PUT
1978
+ });
1454
1979
  return this;
1455
1980
  }
1456
- patch(path, ...handlers) {
1457
- const route = this.route(path);
1458
- route.patch(...handlers);
1981
+ patch(path, handler) {
1982
+ if (isPath(path)) {
1983
+ this.use({
1984
+ ...handler,
1985
+ method: MethodName.PATCH,
1986
+ path
1987
+ });
1988
+ return this;
1989
+ }
1990
+ this.use({
1991
+ ...path,
1992
+ method: MethodName.PATCH
1993
+ });
1459
1994
  return this;
1460
1995
  }
1461
- head(path, ...handlers) {
1462
- const route = this.route(path);
1463
- route.head(...handlers);
1996
+ head(path, handler) {
1997
+ if (isPath(path)) {
1998
+ this.use({
1999
+ ...handler,
2000
+ method: MethodName.HEAD,
2001
+ path
2002
+ });
2003
+ return this;
2004
+ }
2005
+ this.use({
2006
+ ...path,
2007
+ method: MethodName.HEAD
2008
+ });
1464
2009
  return this;
1465
2010
  }
1466
- options(path, ...handlers) {
1467
- const route = this.route(path);
1468
- route.options(...handlers);
2011
+ options(path, handler) {
2012
+ if (isPath(path)) {
2013
+ this.use({
2014
+ ...handler,
2015
+ method: MethodName.OPTIONS,
2016
+ path
2017
+ });
2018
+ return this;
2019
+ }
2020
+ this.use({
2021
+ ...path,
2022
+ method: MethodName.OPTIONS
2023
+ });
1469
2024
  return this;
1470
2025
  }
1471
2026
  use(...input) {
1472
2027
  /* istanbul ignore next */ if (input.length === 0) {
1473
2028
  return this;
1474
2029
  }
2030
+ const modifyPath = (input)=>{
2031
+ if (typeof input === 'string') {
2032
+ return withLeadingSlash(input);
2033
+ }
2034
+ return input;
2035
+ };
1475
2036
  let path;
1476
- if (isPath(input[0])) {
1477
- path = input.shift();
1478
- }
1479
2037
  for(let i = 0; i < input.length; i++){
1480
2038
  const item = input[i];
2039
+ if (isPath(item)) {
2040
+ path = modifyPath(item);
2041
+ continue;
2042
+ }
1481
2043
  if (isRouterInstance(item)) {
1482
2044
  if (path) {
1483
2045
  item.setPath(path);
1484
2046
  }
1485
- item.setPathMatcherOptions(this.pathMatcherOptions);
1486
2047
  this.stack.push(item);
1487
2048
  continue;
1488
2049
  }
1489
- if (typeof item === 'function') {
1490
- this.stack.push(new Layer({
1491
- path: path || '/',
1492
- pathMatcher: {
1493
- strict: false,
1494
- end: false,
1495
- sensitive: this.pathMatcherOptions.sensitive
1496
- }
1497
- }, item));
2050
+ if (isHandler(item)) {
2051
+ item.path = path || modifyPath(item.path);
2052
+ this.stack.push(new Layer(item));
1498
2053
  }
1499
2054
  }
1500
2055
  return this;
1501
2056
  }
1502
2057
  // --------------------------------------------------
1503
- constructor(ctx){
1504
- this['@instanceof'] = Symbol.for('Router');
2058
+ install(plugin, context) {
2059
+ if (isPluginInstallContext(context)) {
2060
+ const name = context.name || plugin.name;
2061
+ if (context.path) {
2062
+ const router = new Router({
2063
+ name
2064
+ });
2065
+ plugin.install(router, context.options);
2066
+ this.use(context.path, router);
2067
+ return this;
2068
+ }
2069
+ plugin.install(this, context.options);
2070
+ return this;
2071
+ }
2072
+ plugin.install(this, context);
2073
+ return this;
2074
+ }
2075
+ uninstall(name) {
2076
+ const index = this.stack.findIndex((el)=>isRouterInstance(el) && el.name === name);
2077
+ if (index !== -1) {
2078
+ this.stack.splice(index, 1);
2079
+ }
2080
+ }
2081
+ // --------------------------------------------------
2082
+ constructor(options = {}){
2083
+ this['@instanceof'] = RouterSymbol;
1505
2084
  /**
1506
2085
  * Array of mounted layers, routes & routers.
1507
2086
  *
1508
2087
  * @protected
1509
2088
  */ this.stack = [];
1510
- ctx = ctx || {};
1511
- this.pathMatcherOptions = {
1512
- end: false,
1513
- sensitive: true,
1514
- ...ctx.pathMatcher || {}
1515
- };
1516
- this.timeout = ctx.timeout;
1517
- this.setPath(ctx.path || '/');
2089
+ this.id = generateRouterID();
2090
+ this.name = options.name;
2091
+ this.setPath(options.path);
2092
+ setRouterOptions(this.id, transformRouterOptions(options));
1518
2093
  }
1519
2094
  }
1520
2095
 
1521
- export { HeaderName, Layer, MethodName, PathMatcher, Route, Router, appendResponseHeader, appendResponseHeaderDirective, buildConfig, buildEtagFn, buildTrustProxyFn, cleanDoubleSlashes, createEtag, createRequestTimeout, extendRequestBody, extendRequestCookies, extendRequestQuery, generateETag, getCharsetForMimeType, getConfigOption, getMimeType, getRequestAcceptableCharset, getRequestAcceptableCharsets, getRequestAcceptableContentType, getRequestAcceptableContentTypes, getRequestAcceptableEncoding, getRequestAcceptableEncodings, getRequestAcceptableLanguage, getRequestAcceptableLanguages, getRequestHeader, getRequestHostName, getRequestIP, getRequestProtocol, hasLeadingSlash, hasRequestBody, hasRequestCookies, hasRequestQuery, hasTrailingSlash, isInstance, isLayerInstance, isObject, isPath, isPromise, isRequestCacheable, isRouteInstance, isRouterInstance, matchRequestContentType, onResponseFinished, processHandlerExecutionOutput, send, sendAccepted, sendCreated, sendFile, sendFormat, sendRedirect, sendStream, setConfig, setConfigOption, setRequestBody, setRequestCookies, setRequestEnv, setRequestHeader, setRequestMountPath, setRequestParam, setRequestParams, setRequestQuery, setResponseCacheHeaders, setResponseContentTypeByFileName, setResponseHeaderAttachment, setResponseHeaderContentType, unsetRequestEnv, useConfig, useRequestBody, useRequestCookie, useRequestCookies, useRequestEnv, useRequestMountPath, useRequestNegotiator, useRequestParam, useRequestParams, useRequestPath, useRequestQuery, withLeadingSlash, withTrailingSlash, withoutLeadingSlash, withoutTrailingSlash };
2096
+ export { ErrorProxy, HandlerType, HeaderName, Layer, MethodName, PathMatcher, Router, appendResponseHeader, appendResponseHeaderDirective, buildDispatcherMeta, cloneDispatcherMeta, cloneDispatcherMetaParams, coreHandler, createError, createNodeDispatcher, createRawDispatcher, createRequest, createResponse, createWebDispatcher, dispatchNodeRequest, dispatchRawRequest, dispatchWebRequest, errorHandler, extendRequestBody, extendRequestCookies, extendRequestQuery, getRequestAcceptableCharset, getRequestAcceptableCharsets, getRequestAcceptableContentType, getRequestAcceptableContentTypes, getRequestAcceptableEncoding, getRequestAcceptableEncodings, getRequestAcceptableLanguage, getRequestAcceptableLanguages, getRequestHeader, getRequestHostName, getRequestIP, getRequestProtocol, hasRequestBody, hasRequestCookies, hasRequestQuery, isError, isHandler, isLayerInstance, isPath, isPluginInstallContext, isRequestCacheable, isResponseGone, isRouterInstance, matchRequestContentType, mergeDispatcherMetaParams, send, sendAccepted, sendCreated, sendFile, sendFormat, sendRedirect, sendStream, sendWebBlob, sendWebResponse, setRequestBody, setRequestCookies, setRequestEnv, setRequestHeader, setRequestMountPath, setRequestParam, setRequestParams, setRequestQuery, setRequestRouterPath, setResponseCacheHeaders, setResponseContentTypeByFileName, setResponseHeaderAttachment, setResponseHeaderContentType, unsetRequestEnv, useRequestBody, useRequestCookie, useRequestCookies, useRequestEnv, useRequestMountPath, useRequestNegotiator, useRequestParam, useRequestParams, useRequestPath, useRequestQuery, useRequestRouterPath };
1522
2097
  //# sourceMappingURL=index.mjs.map