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