routup 2.0.0 → 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 (83) hide show
  1. package/README.md +88 -35
  2. package/dist/dispatcher/adapters/node/module.d.ts +2 -1
  3. package/dist/dispatcher/type.d.ts +8 -6
  4. package/dist/dispatcher/utils.d.ts +2 -1
  5. package/dist/error/create.d.ts +11 -0
  6. package/dist/error/index.d.ts +3 -0
  7. package/dist/error/is.d.ts +2 -0
  8. package/dist/error/module.d.ts +3 -0
  9. package/dist/handler/constants.d.ts +4 -0
  10. package/dist/handler/core/define.d.ts +3 -0
  11. package/dist/handler/core/index.d.ts +2 -0
  12. package/dist/handler/core/types.d.ts +10 -0
  13. package/dist/handler/error/define.d.ts +3 -0
  14. package/dist/handler/error/index.d.ts +2 -0
  15. package/dist/handler/error/types.d.ts +11 -0
  16. package/dist/handler/index.d.ts +6 -0
  17. package/dist/handler/is.d.ts +2 -0
  18. package/dist/handler/types-base.d.ts +6 -0
  19. package/dist/handler/types.d.ts +5 -0
  20. package/dist/index.cjs +732 -695
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.ts +3 -1
  23. package/dist/index.mjs +724 -693
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/layer/constants.d.ts +1 -0
  26. package/dist/layer/module.d.ts +9 -7
  27. package/dist/layer/type.d.ts +6 -3
  28. package/dist/layer/utils.d.ts +1 -1
  29. package/dist/path/matcher.d.ts +4 -4
  30. package/dist/plugin/index.d.ts +2 -0
  31. package/dist/plugin/is.d.ts +2 -0
  32. package/dist/plugin/types.d.ts +32 -0
  33. package/dist/request/helpers/body.d.ts +1 -1
  34. package/dist/request/helpers/cache.d.ts +1 -1
  35. package/dist/request/helpers/cookie.d.ts +1 -1
  36. package/dist/request/helpers/env.d.ts +1 -1
  37. package/dist/request/helpers/header-accept-charset.d.ts +1 -1
  38. package/dist/request/helpers/header-accept-language.d.ts +1 -1
  39. package/dist/request/helpers/header-accept.d.ts +1 -1
  40. package/dist/request/helpers/header-content-type.d.ts +1 -1
  41. package/dist/request/helpers/header.d.ts +1 -1
  42. package/dist/request/helpers/hostname.d.ts +1 -1
  43. package/dist/request/helpers/ip.d.ts +1 -1
  44. package/dist/request/helpers/mount-path.d.ts +1 -1
  45. package/dist/request/helpers/negotiator.d.ts +1 -1
  46. package/dist/request/helpers/params.d.ts +1 -1
  47. package/dist/request/helpers/path.d.ts +1 -1
  48. package/dist/request/helpers/protocol.d.ts +1 -1
  49. package/dist/request/helpers/query.d.ts +1 -1
  50. package/dist/request/helpers/router.d.ts +3 -3
  51. package/dist/request/types.d.ts +4 -0
  52. package/dist/response/helpers/cache.d.ts +1 -1
  53. package/dist/response/helpers/gone.d.ts +1 -1
  54. package/dist/response/helpers/header-attachment.d.ts +1 -1
  55. package/dist/response/helpers/header-content-type.d.ts +1 -1
  56. package/dist/response/helpers/header.d.ts +1 -1
  57. package/dist/response/helpers/send-accepted.d.ts +1 -1
  58. package/dist/response/helpers/send-created.d.ts +1 -1
  59. package/dist/response/helpers/send-file.d.ts +1 -1
  60. package/dist/response/helpers/send-format.d.ts +1 -1
  61. package/dist/response/helpers/send-redirect.d.ts +1 -1
  62. package/dist/response/helpers/send-stream.d.ts +2 -1
  63. package/dist/response/helpers/send-web-blob.d.ts +2 -1
  64. package/dist/response/helpers/send-web-response.d.ts +2 -1
  65. package/dist/response/helpers/send.d.ts +1 -1
  66. package/dist/response/helpers/utils.d.ts +1 -1
  67. package/dist/response/index.d.ts +1 -0
  68. package/dist/response/module.d.ts +2 -1
  69. package/dist/response/types.d.ts +4 -0
  70. package/dist/router/constants.d.ts +1 -0
  71. package/dist/router/index.d.ts +1 -0
  72. package/dist/router/module.d.ts +26 -23
  73. package/dist/router/utils.d.ts +2 -0
  74. package/dist/router-options/module.d.ts +1 -1
  75. package/dist/router-options/type.d.ts +3 -12
  76. package/dist/types.d.ts +0 -9
  77. package/dist/utils/is-instance.d.ts +1 -1
  78. package/package.json +15 -14
  79. package/dist/error.d.ts +0 -1
  80. package/dist/route/index.d.ts +0 -3
  81. package/dist/route/module.d.ts +0 -28
  82. package/dist/route/type.d.ts +0 -6
  83. package/dist/route/utils.d.ts +0 -2
package/dist/index.mjs CHANGED
@@ -1,127 +1,80 @@
1
+ import { HTTPError } from '@ebec/http';
2
+ import { merge, hasOwnProperty, distinctArray } from 'smob';
1
3
  import { Buffer } from 'buffer';
2
4
  import { subtle } from 'uncrypto';
3
- import { merge, hasOwnProperty, distinctArray } from 'smob';
4
5
  import { compile, all } from 'proxy-addr';
5
6
  import { getType, get } from 'mime-explorer';
6
7
  import Negotiator from 'negotiator';
7
- import { Writable, Readable } from 'readable-stream';
8
+ import { Readable, Writable } from 'readable-stream';
8
9
  import { pathToRegexp } from 'path-to-regexp';
9
10
 
10
11
  var MethodName;
11
12
  (function(MethodName) {
12
- MethodName["GET"] = 'get';
13
- MethodName["POST"] = 'post';
14
- MethodName["PUT"] = 'put';
15
- MethodName["PATCH"] = 'patch';
16
- MethodName["DELETE"] = 'delete';
17
- MethodName["OPTIONS"] = 'options';
18
- MethodName["HEAD"] = 'head';
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";
19
20
  })(MethodName || (MethodName = {}));
20
21
  var HeaderName;
21
22
  (function(HeaderName) {
22
- HeaderName["ACCEPT"] = 'accept';
23
- HeaderName["ACCEPT_CHARSET"] = 'accept-charset';
24
- HeaderName["ACCEPT_ENCODING"] = 'accept-encoding';
25
- HeaderName["ACCEPT_LANGUAGE"] = 'accept-language';
26
- HeaderName["ACCEPT_RANGES"] = 'accept-ranges';
27
- HeaderName["ALLOW"] = 'allow';
28
- HeaderName["CACHE_CONTROL"] = 'cache-control';
29
- HeaderName["CONTENT_DISPOSITION"] = 'content-disposition';
30
- HeaderName["CONTENT_ENCODING"] = 'content-encoding';
31
- HeaderName["CONTENT_LENGTH"] = 'content-length';
32
- HeaderName["CONTENT_RANGE"] = 'content-range';
33
- HeaderName["CONTENT_TYPE"] = 'content-type';
34
- HeaderName["COOKIE"] = 'cookie';
35
- HeaderName["ETag"] = 'etag';
36
- HeaderName["HOST"] = 'host';
37
- HeaderName["IF_MODIFIED_SINCE"] = 'if-modified-since';
38
- HeaderName["IF_NONE_MATCH"] = 'if-none-match';
39
- HeaderName["LAST_MODIFIED"] = 'last-modified';
40
- HeaderName["LOCATION"] = 'location';
41
- HeaderName["RANGE"] = 'range';
42
- HeaderName["RATE_LIMIT_LIMIT"] = 'ratelimit-limit';
43
- HeaderName["RATE_LIMIT_REMAINING"] = 'ratelimit-remaining';
44
- HeaderName["RATE_LIMIT_RESET"] = 'ratelimit-reset';
45
- HeaderName["RETRY_AFTER"] = 'retry-after';
46
- HeaderName["SET_COOKIE"] = 'set-cookie';
47
- HeaderName["TRANSFER_ENCODING"] = 'transfer-encoding';
48
- HeaderName["X_FORWARDED_HOST"] = 'x-forwarded-host';
49
- HeaderName["X_FORWARDED_FOR"] = 'x-forwarded-for';
50
- HeaderName["X_FORWARDED_PROTO"] = 'x-forwarded-proto';
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";
51
52
  })(HeaderName || (HeaderName = {}));
52
53
 
53
- function setResponseCacheHeaders(res, options) {
54
- options = options || {};
55
- const cacheControls = [
56
- 'public'
57
- ].concat(options.cacheControls || []);
58
- if (options.maxAge !== undefined) {
59
- cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
60
- }
61
- if (options.modifiedTime) {
62
- const modifiedTime = typeof options.modifiedTime === 'string' ? new Date(options.modifiedTime) : options.modifiedTime;
63
- res.setHeader('last-modified', modifiedTime.toUTCString());
64
- }
65
- res.setHeader('cache-control', cacheControls.join(', '));
54
+ class ErrorProxy extends HTTPError {
66
55
  }
67
56
 
68
- const GoneSymbol = Symbol.for('ResGone');
69
- function isResponseGone(res) {
70
- if (res.headersSent || res.writableEnded) {
71
- return true;
72
- }
73
- if (GoneSymbol in res) {
74
- return res[GoneSymbol];
75
- }
76
- return false;
57
+ function isError(input) {
58
+ return input instanceof ErrorProxy;
77
59
  }
78
60
 
79
- function appendResponseHeader(res, name, value) {
80
- let header = res.getHeader(name);
81
- if (!header) {
82
- res.setHeader(name, value);
83
- return;
84
- }
85
- if (!Array.isArray(header)) {
86
- header = [
87
- header.toString()
88
- ];
89
- }
90
- res.setHeader(name, [
91
- ...header,
92
- value
93
- ]);
94
- }
95
- function appendResponseHeaderDirective(res, name, value) {
96
- let header = res.getHeader(name);
97
- if (!header) {
98
- if (Array.isArray(value)) {
99
- res.setHeader(name, value.join('; '));
100
- return;
101
- }
102
- res.setHeader(name, value);
103
- return;
104
- }
105
- if (!Array.isArray(header)) {
106
- if (typeof header === 'string') {
107
- // split header by directive(s)
108
- header = header.split('; ');
109
- }
110
- if (typeof header === 'number') {
111
- header = [
112
- header.toString()
113
- ];
114
- }
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;
115
71
  }
116
- if (Array.isArray(value)) {
117
- header.push(...value);
118
- } else {
119
- header.push(`${value}`);
72
+ if (typeof input === 'string') {
73
+ return new ErrorProxy(input);
120
74
  }
121
- header = [
122
- ...new Set(header)
123
- ];
124
- res.setHeader(name, header.join('; '));
75
+ return new ErrorProxy({
76
+ cause: input
77
+ }, input);
125
78
  }
126
79
 
127
80
  /*
@@ -311,8 +264,11 @@ function buildTrustProxyFn(input) {
311
264
  return compile(input || []);
312
265
  }
313
266
 
314
- function isInstance(input, name) {
315
- return isObject(input) && input['@instanceof'] === Symbol.for(name);
267
+ function isInstance(input, sym) {
268
+ if (!isObject(input)) {
269
+ return false;
270
+ }
271
+ return input['@instanceof'] === sym;
316
272
  }
317
273
 
318
274
  function getMimeType(type) {
@@ -393,7 +349,10 @@ function withLeadingSlash(input = '') {
393
349
  return hasLeadingSlash(input) ? input : `/${input}`;
394
350
  }
395
351
  function cleanDoubleSlashes(input = '') {
396
- return input.split('://').map((str)=>str.replace(/\/{2,}/g, '/')).join('://');
352
+ if (input.indexOf('://') !== -1) {
353
+ return input.split('://').map((str)=>cleanDoubleSlashes(str)).join('://');
354
+ }
355
+ return input.replace(/\/+/g, '/');
397
356
  }
398
357
 
399
358
  function isWebBlob(input) {
@@ -403,67 +362,6 @@ function isWebResponse(input) {
403
362
  return typeof Response !== 'undefined' && input instanceof Response;
404
363
  }
405
364
 
406
- function setResponseContentTypeByFileName(res, fileName) {
407
- const ext = extname(fileName);
408
- if (ext) {
409
- let type = getMimeType(ext.substring(1));
410
- if (type) {
411
- const charset = getCharsetForMimeType(type);
412
- if (charset) {
413
- type += `; charset=${charset}`;
414
- }
415
- res.setHeader(HeaderName.CONTENT_TYPE, type);
416
- }
417
- }
418
- }
419
-
420
- function setResponseHeaderAttachment(res, filename) {
421
- if (typeof filename === 'string') {
422
- setResponseContentTypeByFileName(res, filename);
423
- }
424
- res.setHeader(HeaderName.CONTENT_DISPOSITION, `attachment${filename ? `; filename="${filename}"` : ''}`);
425
- }
426
-
427
- function setResponseHeaderContentType(res, input, ifNotExists) {
428
- if (ifNotExists) {
429
- const header = res.getHeader(HeaderName.CONTENT_TYPE);
430
- if (header) {
431
- return;
432
- }
433
- }
434
- const contentType = getMimeType(input);
435
- if (contentType) {
436
- res.setHeader(HeaderName.CONTENT_TYPE, contentType);
437
- }
438
- }
439
-
440
- const defaults = {
441
- trustProxy: ()=>false,
442
- subdomainOffset: 2,
443
- etag: buildEtagFn(),
444
- proxyIpMax: 0
445
- };
446
- const instances = {};
447
- function setRouterOptions(id, input) {
448
- instances[id] = input;
449
- }
450
- function findRouterOption(key, id) {
451
- if (!id) {
452
- return defaults[key];
453
- }
454
- const ids = Array.isArray(id) ? id : [
455
- id
456
- ];
457
- if (ids.length > 0) {
458
- for(let i = ids.length; i >= 0; i--){
459
- if (hasOwnProperty(instances, ids[i]) && typeof instances[ids[i]][key] !== 'undefined') {
460
- return instances[ids[i]][key];
461
- }
462
- }
463
- }
464
- return defaults[key];
465
- }
466
-
467
365
  const BodySymbol = Symbol.for('ReqBody');
468
366
  function useRequestBody(req, key) {
469
367
  let body;
@@ -725,11 +623,35 @@ function matchRequestContentType(req, contentType) {
725
623
  return header.split('; ').shift() === getMimeType(contentType);
726
624
  }
727
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
+
728
650
  const routerSymbol = Symbol.for('ReqRouterID');
729
- function setRequestRouterIds(req, ids) {
730
- req[routerSymbol] = ids;
651
+ function setRequestRouterPath(req, path) {
652
+ req[routerSymbol] = path;
731
653
  }
732
- function useRequestRouterIds(req) {
654
+ function useRequestRouterPath(req) {
733
655
  if (routerSymbol in req) {
734
656
  return req[routerSymbol];
735
657
  }
@@ -742,7 +664,7 @@ function getRequestHostName(req, options) {
742
664
  if (typeof options.trustProxy !== 'undefined') {
743
665
  trustProxy = buildTrustProxyFn(options.trustProxy);
744
666
  } else {
745
- trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
667
+ trustProxy = findRouterOption('trustProxy', useRequestRouterPath(req));
746
668
  }
747
669
  let hostname = req.headers[HeaderName.X_FORWARDED_HOST];
748
670
  if (!hostname || !req.socket.remoteAddress || !trustProxy(req.socket.remoteAddress, 0)) {
@@ -768,7 +690,7 @@ function getRequestIP(req, options) {
768
690
  if (typeof options.trustProxy !== 'undefined') {
769
691
  trustProxy = buildTrustProxyFn(options.trustProxy);
770
692
  } else {
771
- trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
693
+ trustProxy = findRouterOption('trustProxy', useRequestRouterPath(req));
772
694
  }
773
695
  const addrs = all(req, trustProxy);
774
696
  return addrs[addrs.length - 1];
@@ -829,7 +751,7 @@ function getRequestProtocol(req, options) {
829
751
  if (typeof options.trustProxy !== 'undefined') {
830
752
  trustProxy = buildTrustProxyFn(options.trustProxy);
831
753
  } else {
832
- trustProxy = findRouterOption('trustProxy', useRequestRouterIds(req));
754
+ trustProxy = findRouterOption('trustProxy', useRequestRouterPath(req));
833
755
  }
834
756
  let protocol = options.default;
835
757
  /* istanbul ignore next */ if (hasOwnProperty(req.socket, 'encrypted') && !!req.socket.encrypted) {
@@ -897,100 +819,280 @@ function extendRequestQuery(req, key, value) {
897
819
  setRequestQuery(req, key, value);
898
820
  }
899
821
 
900
- async function send(res, chunk) {
901
- switch(typeof chunk){
902
- case 'string':
903
- {
904
- setResponseHeaderContentType(res, 'html', true);
905
- break;
906
- }
907
- case 'boolean':
908
- case 'number':
909
- case 'object':
910
- {
911
- if (chunk === null) {
912
- chunk = '';
913
- } else if (Buffer.isBuffer(chunk)) {
914
- setResponseHeaderContentType(res, 'bin', true);
915
- } else {
916
- chunk = JSON.stringify(chunk);
917
- setResponseHeaderContentType(res, 'application/json', true);
918
- }
919
- break;
920
- }
921
- }
922
- let encoding;
923
- if (typeof chunk === 'string') {
924
- res.setHeader(HeaderName.CONTENT_ENCODING, 'utf-8');
925
- appendResponseHeaderDirective(res, HeaderName.CONTENT_TYPE, 'charset=utf-8');
926
- encoding = 'utf-8';
927
- }
928
- // populate Content-Length
929
- let len;
930
- if (chunk !== undefined) {
931
- if (Buffer.isBuffer(chunk)) {
932
- // get length of Buffer
933
- len = chunk.length;
934
- } else if (chunk.length < 1000) {
935
- // just calculate length when no ETag + small chunk
936
- len = Buffer.byteLength(chunk, encoding);
822
+ function createRequest(context) {
823
+ let readable;
824
+ if (context.body) {
825
+ if (isWebStream(context.body)) {
826
+ readable = Readable.fromWeb(context.body);
937
827
  } else {
938
- // convert chunk to Buffer and calculate
939
- chunk = Buffer.from(chunk, encoding);
940
- encoding = undefined;
941
- len = chunk.length;
828
+ readable = Readable.from(context.body);
942
829
  }
943
- res.setHeader(HeaderName.CONTENT_LENGTH, `${len}`);
830
+ } else {
831
+ readable = new Readable();
944
832
  }
945
- if (typeof len !== 'undefined') {
946
- const etagFn = findRouterOption('etag', useRequestRouterIds(res.req));
947
- const chunkHash = await etagFn(chunk, encoding, len);
948
- if (isResponseGone(res)) {
949
- return Promise.resolve();
950
- }
951
- if (typeof chunkHash === 'string') {
952
- res.setHeader(HeaderName.ETag, chunkHash);
953
- if (res.req.headers[HeaderName.IF_NONE_MATCH] === chunkHash) {
954
- res.statusCode = 304;
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]);
955
841
  }
842
+ } else if (typeof header === 'string') {
843
+ rawHeaders.push(keys[i], header);
956
844
  }
957
845
  }
958
- // strip irrelevant headers
959
- if (res.statusCode === 204 || res.statusCode === 304) {
960
- res.removeHeader(HeaderName.CONTENT_TYPE);
961
- res.removeHeader(HeaderName.CONTENT_LENGTH);
962
- res.removeHeader(HeaderName.TRANSFER_ENCODING);
963
- chunk = '';
964
- }
965
- // alter headers for 205
966
- if (res.statusCode === 205) {
967
- res.setHeader(HeaderName.CONTENT_LENGTH, 0);
968
- res.removeHeader(HeaderName.TRANSFER_ENCODING);
969
- chunk = '';
970
- }
971
- if (isResponseGone(res)) {
972
- return Promise.resolve();
973
- }
974
- if (res.req.method === 'HEAD') {
975
- // skip body for HEAD
976
- res.end();
977
- return Promise.resolve();
978
- }
979
- if (typeof encoding !== 'undefined') {
980
- res.end(chunk, encoding);
981
- return Promise.resolve();
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
+ }
982
858
  }
983
- res.end(chunk);
984
- return Promise.resolve();
985
- }
986
-
987
- function sendAccepted(res, chunk) {
988
- res.statusCode = 202;
989
- res.statusMessage = 'Accepted';
990
- return send(res, chunk);
991
- }
992
-
993
- function sendCreated(res, chunk) {
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
+
894
+ function setResponseCacheHeaders(res, options) {
895
+ options = options || {};
896
+ const cacheControls = [
897
+ 'public'
898
+ ].concat(options.cacheControls || []);
899
+ if (options.maxAge !== undefined) {
900
+ cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
901
+ }
902
+ if (options.modifiedTime) {
903
+ const modifiedTime = typeof options.modifiedTime === 'string' ? new Date(options.modifiedTime) : options.modifiedTime;
904
+ res.setHeader('last-modified', modifiedTime.toUTCString());
905
+ }
906
+ res.setHeader('cache-control', cacheControls.join(', '));
907
+ }
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
+
920
+ function appendResponseHeader(res, name, value) {
921
+ let header = res.getHeader(name);
922
+ if (!header) {
923
+ res.setHeader(name, value);
924
+ return;
925
+ }
926
+ if (!Array.isArray(header)) {
927
+ header = [
928
+ header.toString()
929
+ ];
930
+ }
931
+ res.setHeader(name, [
932
+ ...header,
933
+ value
934
+ ]);
935
+ }
936
+ function appendResponseHeaderDirective(res, name, value) {
937
+ let header = res.getHeader(name);
938
+ if (!header) {
939
+ if (Array.isArray(value)) {
940
+ res.setHeader(name, value.join('; '));
941
+ return;
942
+ }
943
+ res.setHeader(name, value);
944
+ return;
945
+ }
946
+ if (!Array.isArray(header)) {
947
+ if (typeof header === 'string') {
948
+ // split header by directive(s)
949
+ header = header.split('; ');
950
+ }
951
+ if (typeof header === 'number') {
952
+ header = [
953
+ header.toString()
954
+ ];
955
+ }
956
+ }
957
+ if (Array.isArray(value)) {
958
+ header.push(...value);
959
+ } else {
960
+ header.push(`${value}`);
961
+ }
962
+ header = [
963
+ ...new Set(header)
964
+ ];
965
+ res.setHeader(name, header.join('; '));
966
+ }
967
+
968
+ function setResponseContentTypeByFileName(res, fileName) {
969
+ const ext = extname(fileName);
970
+ if (ext) {
971
+ let type = getMimeType(ext.substring(1));
972
+ if (type) {
973
+ const charset = getCharsetForMimeType(type);
974
+ if (charset) {
975
+ type += `; charset=${charset}`;
976
+ }
977
+ res.setHeader(HeaderName.CONTENT_TYPE, type);
978
+ }
979
+ }
980
+ }
981
+
982
+ function setResponseHeaderAttachment(res, filename) {
983
+ if (typeof filename === 'string') {
984
+ setResponseContentTypeByFileName(res, filename);
985
+ }
986
+ res.setHeader(HeaderName.CONTENT_DISPOSITION, `attachment${filename ? `; filename="${filename}"` : ''}`);
987
+ }
988
+
989
+ function setResponseHeaderContentType(res, input, ifNotExists) {
990
+ if (ifNotExists) {
991
+ const header = res.getHeader(HeaderName.CONTENT_TYPE);
992
+ if (header) {
993
+ return;
994
+ }
995
+ }
996
+ const contentType = getMimeType(input);
997
+ if (contentType) {
998
+ res.setHeader(HeaderName.CONTENT_TYPE, contentType);
999
+ }
1000
+ }
1001
+
1002
+ async function send(res, chunk) {
1003
+ switch(typeof chunk){
1004
+ case 'string':
1005
+ {
1006
+ setResponseHeaderContentType(res, 'html', true);
1007
+ break;
1008
+ }
1009
+ case 'boolean':
1010
+ case 'number':
1011
+ case 'object':
1012
+ {
1013
+ if (Buffer.isBuffer(chunk)) {
1014
+ setResponseHeaderContentType(res, 'bin', true);
1015
+ } else if (chunk !== null) {
1016
+ chunk = JSON.stringify(chunk);
1017
+ setResponseHeaderContentType(res, 'application/json', true);
1018
+ }
1019
+ break;
1020
+ }
1021
+ }
1022
+ let encoding;
1023
+ if (typeof chunk === 'string') {
1024
+ res.setHeader(HeaderName.CONTENT_ENCODING, 'utf-8');
1025
+ appendResponseHeaderDirective(res, HeaderName.CONTENT_TYPE, 'charset=utf-8');
1026
+ encoding = 'utf-8';
1027
+ }
1028
+ // populate Content-Length
1029
+ let len;
1030
+ if (chunk !== undefined && chunk !== null) {
1031
+ if (Buffer.isBuffer(chunk)) {
1032
+ // get length of Buffer
1033
+ len = chunk.length;
1034
+ } else if (chunk.length < 1000) {
1035
+ // just calculate length when no ETag + small chunk
1036
+ len = Buffer.byteLength(chunk, encoding);
1037
+ } else {
1038
+ // convert chunk to Buffer and calculate
1039
+ chunk = Buffer.from(chunk, encoding);
1040
+ encoding = undefined;
1041
+ len = chunk.length;
1042
+ }
1043
+ res.setHeader(HeaderName.CONTENT_LENGTH, `${len}`);
1044
+ }
1045
+ if (typeof len !== 'undefined') {
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
+ }
1051
+ if (typeof chunkHash === 'string') {
1052
+ res.setHeader(HeaderName.ETag, chunkHash);
1053
+ if (res.req.headers[HeaderName.IF_NONE_MATCH] === chunkHash) {
1054
+ res.statusCode = 304;
1055
+ }
1056
+ }
1057
+ }
1058
+ // strip irrelevant headers
1059
+ if (res.statusCode === 204 || res.statusCode === 304) {
1060
+ res.removeHeader(HeaderName.CONTENT_TYPE);
1061
+ res.removeHeader(HeaderName.CONTENT_LENGTH);
1062
+ res.removeHeader(HeaderName.TRANSFER_ENCODING);
1063
+ }
1064
+ // alter headers for 205
1065
+ if (res.statusCode === 205) {
1066
+ res.setHeader(HeaderName.CONTENT_LENGTH, 0);
1067
+ res.removeHeader(HeaderName.TRANSFER_ENCODING);
1068
+ }
1069
+ if (isResponseGone(res)) {
1070
+ return Promise.resolve();
1071
+ }
1072
+ if (res.req.method === 'HEAD') {
1073
+ // skip body for HEAD
1074
+ res.end();
1075
+ return Promise.resolve();
1076
+ }
1077
+ if (typeof chunk === 'undefined' || chunk === null) {
1078
+ res.end();
1079
+ return Promise.resolve();
1080
+ }
1081
+ if (typeof encoding !== 'undefined') {
1082
+ res.end(chunk, encoding);
1083
+ return Promise.resolve();
1084
+ }
1085
+ res.end(chunk);
1086
+ return Promise.resolve();
1087
+ }
1088
+
1089
+ function sendAccepted(res, chunk) {
1090
+ res.statusCode = 202;
1091
+ res.statusMessage = 'Accepted';
1092
+ return send(res, chunk);
1093
+ }
1094
+
1095
+ function sendCreated(res, chunk) {
994
1096
  res.statusCode = 201;
995
1097
  res.statusMessage = 'Created';
996
1098
  return send(res, chunk);
@@ -1301,99 +1403,90 @@ function createResponse(request) {
1301
1403
  return writable;
1302
1404
  }
1303
1405
 
1304
- async function dispatchNodeRequest(router, req, res) {
1305
- try {
1306
- const dispatched = await router.dispatch({
1307
- req,
1308
- res
1309
- });
1310
- if (dispatched) {
1311
- return;
1312
- }
1313
- if (!isResponseGone(res)) {
1314
- res.statusCode = 404;
1315
- res.end();
1316
- }
1317
- } catch (e) {
1318
- if (!isResponseGone(res)) {
1319
- res.statusCode = 500;
1320
- res.end();
1321
- }
1322
- }
1323
- }
1324
- function createNodeDispatcher(router) {
1325
- return (req, res)=>{
1326
- // eslint-disable-next-line no-void
1327
- void dispatchNodeRequest(router, req, res);
1406
+ function buildDispatcherMeta(input) {
1407
+ return {
1408
+ mountPath: input.mountPath || '/',
1409
+ params: input.params || {},
1410
+ path: input.path || '/',
1411
+ routerPath: []
1328
1412
  };
1329
1413
  }
1330
-
1331
- function createRequest(context) {
1332
- let readable;
1333
- if (context.body) {
1334
- if (isWebStream(context.body)) {
1335
- readable = Readable.fromWeb(context.body);
1336
- } else {
1337
- readable = Readable.from(context.body);
1338
- }
1339
- } else {
1340
- readable = new Readable();
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 {};
1341
1428
  }
1342
- const headers = context.headers || {};
1343
- const rawHeaders = [];
1344
- let keys = Object.keys(headers);
1429
+ const keys = Object.keys(input);
1430
+ if (keys.length === 0) {
1431
+ return {};
1432
+ }
1433
+ const output = {};
1345
1434
  for(let i = 0; i < keys.length; i++){
1346
- const header = headers[keys[i]];
1347
- if (Array.isArray(header)) {
1348
- for(let j = 0; j < header.length; j++){
1349
- rawHeaders.push(keys[i], header[j]);
1350
- }
1351
- } else if (typeof header === 'string') {
1352
- rawHeaders.push(keys[i], header);
1353
- }
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;
1354
1449
  }
1355
- const headersDistinct = {};
1356
- keys = Object.keys(headers);
1357
1450
  for(let i = 0; i < keys.length; i++){
1358
- const header = headers[keys[i]];
1359
- if (Array.isArray(header)) {
1360
- headersDistinct[keys[i]] = header;
1361
- }
1362
- if (typeof header === 'string') {
1363
- headersDistinct[keys[i]] = [
1364
- header
1365
- ];
1366
- }
1451
+ t1[keys[i]] = t2[keys[i]];
1367
1452
  }
1368
- Object.defineProperty(readable, 'connection', {
1369
- get () {
1370
- return {};
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;
1371
1466
  }
1372
- });
1373
- Object.defineProperty(readable, 'socket', {
1374
- get () {
1375
- return {};
1467
+ if (!isResponseGone(res)) {
1468
+ res.statusCode = 404;
1469
+ res.end();
1376
1470
  }
1377
- });
1378
- Object.assign(readable, {
1379
- aborted: false,
1380
- complete: true,
1381
- headers,
1382
- headersDistinct,
1383
- httpVersion: '1.1',
1384
- httpVersionMajor: 1,
1385
- httpVersionMinor: 1,
1386
- method: context.method || 'GET',
1387
- rawHeaders,
1388
- rawTrailers: [],
1389
- trailers: {},
1390
- trailersDistinct: {},
1391
- url: context.url || '/',
1392
- setTimeout (_msecs, _callback) {
1393
- return this;
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();
1394
1482
  }
1395
- });
1396
- return readable;
1483
+ }
1484
+ }
1485
+ function createNodeDispatcher(router) {
1486
+ return (req, res)=>{
1487
+ // eslint-disable-next-line no-void
1488
+ void dispatchNodeRequest(router, req, res);
1489
+ };
1397
1490
  }
1398
1491
 
1399
1492
  async function dispatchRawRequest(router, request, options = {}) {
@@ -1418,33 +1511,38 @@ async function dispatchRawRequest(router, request, options = {}) {
1418
1511
  }
1419
1512
  return output;
1420
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
+ });
1421
1520
  try {
1422
1521
  const dispatched = await router.dispatch({
1423
1522
  req,
1424
1523
  res
1425
- });
1524
+ }, buildDispatcherMeta({
1525
+ path: useRequestPath(req)
1526
+ }));
1426
1527
  if (dispatched) {
1427
- return {
1428
- status: res.statusCode,
1429
- statusMessage: res.statusMessage,
1430
- headers: getHeaders(),
1431
- body: res.body
1432
- };
1528
+ return createRawResponse();
1433
1529
  }
1434
- return {
1435
- status: 404,
1436
- headers: getHeaders(),
1437
- body: res.body
1438
- };
1530
+ return createRawResponse({
1531
+ status: 404
1532
+ });
1439
1533
  } catch (e) {
1440
1534
  if (options.throwOnError) {
1441
1535
  throw e;
1442
1536
  }
1443
- return {
1444
- status: 500,
1445
- headers: getHeaders(),
1446
- body: res.body
1447
- };
1537
+ if (isError(e)) {
1538
+ return createRawResponse({
1539
+ status: e.statusCode,
1540
+ statusMessage: e.statusMessage
1541
+ });
1542
+ }
1543
+ return createRawResponse({
1544
+ status: 500
1545
+ });
1448
1546
  }
1449
1547
  }
1450
1548
  function createRawDispatcher(router) {
@@ -1479,54 +1577,40 @@ function createWebDispatcher(router) {
1479
1577
  return async (request)=>dispatchWebRequest(router, request);
1480
1578
  }
1481
1579
 
1482
- function cloneDispatcherMeta(input) {
1483
- if (!input) {
1484
- return {};
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
+ };
1485
1592
  }
1486
1593
  return {
1487
- path: input.path,
1488
- mountPath: input.mountPath,
1489
- error: input.error,
1490
- routerIds: [
1491
- ...input.routerIds || []
1492
- ],
1493
- params: cloneDispatcherMetaParams(input.params)
1594
+ type: HandlerType.CORE,
1595
+ ...input
1494
1596
  };
1495
1597
  }
1496
- function cloneDispatcherMetaParams(input) {
1497
- if (typeof input === 'undefined') {
1498
- return {};
1499
- }
1500
- const keys = Object.keys(input);
1501
- const output = {};
1502
- for(let i = 0; i < keys.length; i++){
1503
- output[keys[i]] = input[keys[i]];
1504
- }
1505
- return output;
1506
- }
1507
- function mergeDispatcherMetaParams(t1, t2) {
1508
- if (!t1 && !t2) {
1509
- return {};
1510
- }
1511
- if (!t1 || !t2) {
1512
- return t1 || t2;
1513
- }
1514
- const keys = Object.keys(t2);
1515
- for(let i = 0; i < keys.length; i++){
1516
- t1[keys[i]] = t2[keys[i]];
1598
+
1599
+ function errorHandler(input) {
1600
+ if (typeof input === 'function') {
1601
+ return {
1602
+ type: HandlerType.ERROR,
1603
+ fn: input
1604
+ };
1517
1605
  }
1518
- return t1;
1606
+ return {
1607
+ type: HandlerType.ERROR,
1608
+ ...input
1609
+ };
1519
1610
  }
1520
1611
 
1521
- function createError(input) {
1522
- if (input instanceof Error) {
1523
- return input;
1524
- }
1525
- const error = new Error();
1526
- if (typeof input.message === 'string') {
1527
- error.message = input.message;
1528
- }
1529
- return error;
1612
+ function isHandler(input) {
1613
+ return isObject(input) && typeof input.fn === 'function' && typeof input.type === 'string';
1530
1614
  }
1531
1615
 
1532
1616
  function decodeParam(val) {
@@ -1537,56 +1621,46 @@ function decodeParam(val) {
1537
1621
  }
1538
1622
  class PathMatcher {
1539
1623
  test(path) {
1540
- const fastSlash = this.path === '/' && this.regexpOptions.end === false;
1541
- if (fastSlash) {
1542
- return true;
1543
- }
1544
1624
  return this.regexp.test(path);
1545
1625
  }
1546
1626
  exec(path) {
1547
- let match = null;
1548
- const fastSlash = this.path === '/' && this.regexpOptions.end === false;
1549
- if (fastSlash) {
1627
+ if (this.path === '/' && this.regexpOptions.end === false) {
1550
1628
  return {
1551
1629
  path: '/',
1552
1630
  params: {}
1553
1631
  };
1554
1632
  }
1555
- match = this.regexp.exec(path);
1556
- if (!match) {
1557
- return undefined;
1558
- }
1559
- if (this.path instanceof RegExp) {
1633
+ if (this.path === '*') {
1560
1634
  return {
1561
1635
  path,
1562
1636
  params: {
1563
- 0: decodeParam(match[0])
1637
+ 0: decodeParam(path)
1564
1638
  }
1565
1639
  };
1566
1640
  }
1567
- const output = {};
1641
+ const match = this.regexp.exec(path);
1642
+ if (!match) {
1643
+ return undefined;
1644
+ }
1645
+ const params = {};
1568
1646
  for(let i = 1; i < match.length; i++){
1569
1647
  const key = this.regexpKeys[i - 1];
1570
1648
  const prop = key.name;
1571
1649
  const val = decodeParam(match[i]);
1572
1650
  if (typeof val !== 'undefined') {
1573
- output[prop] = val;
1651
+ params[prop] = val;
1574
1652
  }
1575
1653
  }
1576
1654
  return {
1577
1655
  path: match[0],
1578
- params: output
1656
+ params
1579
1657
  };
1580
1658
  }
1581
1659
  constructor(path, options){
1582
1660
  this.regexpKeys = [];
1583
1661
  this.path = path;
1584
1662
  this.regexpOptions = options || {};
1585
- if (path instanceof RegExp) {
1586
- this.regexp = path;
1587
- } else {
1588
- this.regexp = pathToRegexp(path, this.regexpKeys, options);
1589
- }
1663
+ this.regexp = pathToRegexp(path, this.regexpKeys, options);
1590
1664
  }
1591
1665
  }
1592
1666
 
@@ -1594,27 +1668,33 @@ function isPath(input) {
1594
1668
  return typeof input === 'string' || input instanceof RegExp;
1595
1669
  }
1596
1670
 
1671
+ const LayerSymbol = Symbol.for('Layer');
1672
+
1597
1673
  class Layer {
1598
1674
  // --------------------------------------------------
1599
- isError() {
1600
- return this.fn.length === 4;
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;
1601
1683
  }
1602
1684
  // --------------------------------------------------
1603
1685
  dispatch(event, meta) {
1604
- setRequestParams(event.req, meta.params || {});
1605
- setRequestMountPath(event.req, meta.mountPath || '/');
1606
- setRequestRouterIds(event.req, meta.routerIds || []);
1607
- if (this.fn.length !== 4 && meta.error || this.fn.length === 4 && !meta.error) {
1608
- return Promise.reject(meta.error);
1686
+ if (this.pathMatcher) {
1687
+ const pathMatch = this.pathMatcher.exec(meta.path);
1688
+ if (pathMatch) {
1689
+ meta.params = mergeDispatcherMetaParams(meta.params, pathMatch.params);
1690
+ }
1609
1691
  }
1610
- const timeout = findRouterOption('timeout', meta.routerIds);
1692
+ setRequestParams(event.req, meta.params);
1693
+ setRequestMountPath(event.req, meta.mountPath);
1694
+ setRequestRouterPath(event.req, meta.routerPath);
1611
1695
  return new Promise((resolve, reject)=>{
1612
- let timeoutInstance;
1613
1696
  let handled = false;
1614
1697
  const unsubscribe = ()=>{
1615
- if (timeoutInstance) {
1616
- clearTimeout(timeoutInstance);
1617
- }
1618
1698
  event.res.off('close', onFinished);
1619
1699
  event.res.off('error', onFinished);
1620
1700
  };
@@ -1634,30 +1714,23 @@ class Layer {
1634
1714
  const onNext = (err)=>shutdown(false, err);
1635
1715
  event.res.once('close', onFinished);
1636
1716
  event.res.once('error', onFinished);
1637
- if (timeout) {
1638
- timeoutInstance = setTimeout(()=>{
1639
- handled = true;
1640
- unsubscribe();
1641
- event.res.statusCode = 504;
1642
- event.res.statusMessage = 'Gateway Timeout';
1643
- event.res.end();
1644
- }, timeout);
1645
- }
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
+ };
1646
1725
  try {
1647
1726
  let output;
1648
- if (meta.error) {
1649
- output = this.fn(meta.error, event.req, event.res, onNext);
1727
+ if (this.handler.type === HandlerType.ERROR) {
1728
+ if (meta.error) {
1729
+ output = this.handler.fn(meta.error, event.req, event.res, onNext);
1730
+ }
1650
1731
  } else {
1651
- output = this.fn(event.req, event.res, onNext);
1732
+ output = this.handler.fn(event.req, event.res, onNext);
1652
1733
  }
1653
- const handle = (data)=>{
1654
- if (typeof data === 'undefined' || handled) {
1655
- return Promise.resolve();
1656
- }
1657
- handled = true;
1658
- unsubscribe();
1659
- return this.sendOutput(event.res, data).then(()=>resolve(true)).catch((e)=>reject(createError(e)));
1660
- };
1661
1734
  if (isPromise(output)) {
1662
1735
  output.then((r)=>handle(r)).catch((e)=>reject(createError(e)));
1663
1736
  return;
@@ -1670,7 +1743,7 @@ class Layer {
1670
1743
  }
1671
1744
  sendOutput(res, input) {
1672
1745
  if (input instanceof Error) {
1673
- return Promise.reject(input);
1746
+ return Promise.reject(createError(input));
1674
1747
  }
1675
1748
  if (isStream(input)) {
1676
1749
  return sendStream(res, input);
@@ -1685,143 +1758,45 @@ class Layer {
1685
1758
  }
1686
1759
  // --------------------------------------------------
1687
1760
  matchPath(path) {
1761
+ if (!this.pathMatcher) {
1762
+ return true;
1763
+ }
1688
1764
  return this.pathMatcher.test(path);
1689
1765
  }
1690
- exec(path) {
1691
- 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;
1692
1775
  }
1693
1776
  // --------------------------------------------------
1694
- constructor(options, fn){
1695
- this['@instanceof'] = Symbol.for('Layer');
1696
- this.pathMatcher = new PathMatcher(options.path, options.pathMatcher);
1697
- 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
+ }
1698
1785
  }
1699
1786
  }
1700
1787
 
1701
1788
  function isLayerInstance(input) {
1702
- if (input instanceof Layer) {
1703
- return true;
1704
- }
1705
- return isInstance(input, 'Layer');
1789
+ return isInstance(input, LayerSymbol);
1706
1790
  }
1707
1791
 
1708
- class Route {
1709
- // --------------------------------------------------
1710
- matchPath(path) {
1711
- return this.pathMatcher.test(path);
1712
- }
1713
- matchMethod(method) {
1714
- let name = method.toLowerCase();
1715
- if (name === MethodName.HEAD && !hasOwnProperty(this.layers, name)) {
1716
- name = MethodName.GET;
1717
- }
1718
- return Object.prototype.hasOwnProperty.call(this.layers, name);
1719
- }
1720
- // --------------------------------------------------
1721
- getMethods() {
1722
- const keys = Object.keys(this.layers);
1723
- if (hasOwnProperty(this.layers, MethodName.GET) && !hasOwnProperty(this.layers, MethodName.HEAD)) {
1724
- keys.push(MethodName.HEAD);
1725
- }
1726
- return keys;
1727
- }
1728
- // --------------------------------------------------
1729
- async dispatch(event, meta) {
1730
- /* istanbul ignore next */ if (!event.req.method) {
1731
- return false;
1732
- }
1733
- let name = event.req.method.toLowerCase();
1734
- if (name === MethodName.HEAD && !hasOwnProperty(this.layers, name)) {
1735
- name = MethodName.GET;
1736
- }
1737
- const layers = this.layers[name];
1738
- /* istanbul ignore next */ if (typeof layers === 'undefined' || layers.length === 0 || typeof meta.path === 'undefined') {
1739
- return false;
1740
- }
1741
- const layerMeta = cloneDispatcherMeta(meta);
1742
- const output = this.pathMatcher.exec(meta.path);
1743
- if (output) {
1744
- layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1745
- }
1746
- let err;
1747
- for(let i = 0; i < layers.length; i++){
1748
- const layer = layers[i];
1749
- if (err && !layer.isError()) {
1750
- continue;
1751
- }
1752
- try {
1753
- const dispatched = await layer.dispatch(event, {
1754
- ...layerMeta
1755
- });
1756
- if (dispatched) {
1757
- return true;
1758
- }
1759
- } catch (e) {
1760
- if (e instanceof Error) {
1761
- err = e;
1762
- }
1763
- }
1764
- }
1765
- if (err) {
1766
- throw err;
1767
- }
1792
+ function isPluginInstallContext(input) {
1793
+ if (!isObject(input) || !isObject(input.options)) {
1768
1794
  return false;
1769
1795
  }
1770
- // --------------------------------------------------
1771
- register(method, ...handlers) {
1772
- this.layers[method] = [];
1773
- for(let i = 0; i < handlers.length; i++){
1774
- const layer = new Layer({
1775
- path: this.path,
1776
- pathMatcher: this.pathMatcherOptions
1777
- }, handlers[i]);
1778
- this.layers[method].push(layer);
1779
- }
1780
- }
1781
- get(...handlers) {
1782
- return this.register(MethodName.GET, ...handlers);
1783
- }
1784
- post(...handlers) {
1785
- return this.register(MethodName.POST, ...handlers);
1786
- }
1787
- put(...handlers) {
1788
- return this.register(MethodName.PUT, ...handlers);
1789
- }
1790
- patch(...handlers) {
1791
- return this.register(MethodName.PATCH, ...handlers);
1792
- }
1793
- delete(...handlers) {
1794
- return this.register(MethodName.DELETE, ...handlers);
1795
- }
1796
- head(...handlers) {
1797
- return this.register(MethodName.HEAD, ...handlers);
1798
- }
1799
- options(...handlers) {
1800
- return this.register(MethodName.OPTIONS, ...handlers);
1801
- }
1802
- // --------------------------------------------------
1803
- isStrictPath() {
1804
- return typeof this.path !== 'string' || this.path !== '/' && this.path.length !== 0;
1805
- }
1806
- // --------------------------------------------------
1807
- constructor(options){
1808
- this['@instanceof'] = Symbol.for('Route');
1809
- this.layers = {};
1810
- this.path = options.path;
1811
- this.pathMatcherOptions = {
1812
- end: true,
1813
- strict: this.isStrictPath(),
1814
- ...options.pathMatcher
1815
- };
1816
- this.pathMatcher = new PathMatcher(this.path, this.pathMatcherOptions);
1817
- }
1818
- }
1819
-
1820
- function isRouteInstance(input) {
1821
- if (input instanceof Route) {
1822
- return true;
1796
+ if (typeof input.name !== 'undefined' && typeof input.name !== 'string') {
1797
+ return false;
1823
1798
  }
1824
- return isInstance(input, 'Route');
1799
+ return typeof input.path === 'undefined' || isPath(input.path);
1825
1800
  }
1826
1801
 
1827
1802
  function transformRouterOptions(input) {
@@ -1834,17 +1809,16 @@ function transformRouterOptions(input) {
1834
1809
  return input;
1835
1810
  }
1836
1811
 
1812
+ const RouterSymbol = Symbol.for('Router');
1813
+
1837
1814
  let nextId = 0;
1838
1815
  function generateRouterID() {
1839
1816
  return ++nextId;
1840
1817
  }
1841
-
1842
1818
  function isRouterInstance(input) {
1843
- if (input instanceof Router) {
1844
- return true;
1845
- }
1846
- return isInstance(input, 'Router');
1819
+ return isInstance(input, RouterSymbol);
1847
1820
  }
1821
+
1848
1822
  class Router {
1849
1823
  // --------------------------------------------------
1850
1824
  setPath(value) {
@@ -1858,9 +1832,7 @@ class Router {
1858
1832
  path = value;
1859
1833
  }
1860
1834
  this.pathMatcher = new PathMatcher(path, {
1861
- end: false,
1862
- sensitive: false,
1863
- ...this.pathMatcherOptions ? this.pathMatcherOptions : {}
1835
+ end: false
1864
1836
  });
1865
1837
  }
1866
1838
  // --------------------------------------------------
@@ -1871,78 +1843,60 @@ class Router {
1871
1843
  return true;
1872
1844
  }
1873
1845
  // --------------------------------------------------
1874
- async dispatch(event, meta = {}) {
1846
+ async dispatch(event, meta) {
1875
1847
  const allowedMethods = [];
1876
- let path = meta.path || useRequestPath(event.req);
1877
1848
  if (this.pathMatcher) {
1878
- const output = this.pathMatcher.exec(path);
1849
+ const output = this.pathMatcher.exec(meta.path);
1879
1850
  if (typeof output !== 'undefined') {
1880
- meta.mountPath = cleanDoubleSlashes(`${meta.mountPath || ''}/${output.path}`);
1881
- if (path === output.path) {
1882
- path = '/';
1851
+ meta.mountPath = cleanDoubleSlashes(`${meta.mountPath}/${output.path}`);
1852
+ if (meta.path === output.path) {
1853
+ meta.path = '/';
1883
1854
  } else {
1884
- path = withLeadingSlash(path.substring(output.path.length));
1855
+ meta.path = withLeadingSlash(meta.path.substring(output.path.length));
1885
1856
  }
1886
- meta.params = merge(meta.params || {}, output.params);
1857
+ meta.params = {
1858
+ ...meta.params,
1859
+ ...output.params
1860
+ };
1887
1861
  }
1888
1862
  }
1889
- meta.path = path;
1890
- if (meta.routerIds) {
1891
- meta.routerIds.push(this.id);
1892
- } else {
1893
- meta.routerIds = [
1894
- this.id
1895
- ];
1896
- }
1897
- if (!meta.mountPath) {
1898
- meta.mountPath = '/';
1899
- }
1863
+ meta.routerPath.push(this.id);
1900
1864
  let err;
1901
- let layer;
1865
+ let item;
1866
+ let itemMeta;
1902
1867
  let match = false;
1903
1868
  for(let i = 0; i < this.stack.length; i++){
1904
- layer = this.stack[i];
1905
- if (layer instanceof Layer) {
1906
- if (!layer.isError() && err) {
1869
+ item = this.stack[i];
1870
+ if (isLayerInstance(item)) {
1871
+ if (item.type !== HandlerType.ERROR && err) {
1907
1872
  continue;
1908
1873
  }
1909
- match = layer.matchPath(path);
1910
- }
1911
- if (isRouterInstance(layer)) {
1912
- match = layer.matchPath(path);
1913
- }
1914
- if (isRouteInstance(layer)) {
1915
- match = layer.matchPath(path);
1916
- if (event.req.method && !layer.matchMethod(event.req.method)) {
1917
- match = false;
1918
- if (event.req.method.toLowerCase() === MethodName.OPTIONS) {
1919
- allowedMethods.push(...layer.getMethods());
1874
+ match = item.matchPath(meta.path);
1875
+ if (match && event.req.method) {
1876
+ if (!item.matchMethod(event.req.method)) {
1877
+ match = false;
1878
+ }
1879
+ if (item.method) {
1880
+ allowedMethods.push(item.method);
1920
1881
  }
1921
1882
  }
1883
+ } else if (isRouterInstance(item)) {
1884
+ match = item.matchPath(meta.path);
1922
1885
  }
1923
1886
  if (!match) {
1924
1887
  continue;
1925
1888
  }
1926
- const layerMeta = cloneDispatcherMeta(meta);
1927
- if (isLayerInstance(layer)) {
1928
- const output = layer.exec(path);
1929
- if (output) {
1930
- layerMeta.params = mergeDispatcherMetaParams(layerMeta.params, output.params);
1931
- layerMeta.mountPath = cleanDoubleSlashes(`${layerMeta.mountPath || ''}/${output.path}`);
1932
- }
1933
- if (err) {
1934
- layerMeta.error = err;
1935
- }
1936
- } else if (err) {
1937
- continue;
1889
+ itemMeta = cloneDispatcherMeta(meta);
1890
+ if (err) {
1891
+ itemMeta.error = err;
1938
1892
  }
1939
1893
  try {
1940
- const dispatched = await layer.dispatch(event, layerMeta);
1894
+ const dispatched = await item.dispatch(event, itemMeta);
1941
1895
  if (dispatched) {
1942
1896
  return true;
1943
1897
  }
1944
1898
  } catch (e) {
1945
- if (e instanceof Error) {
1899
+ if (isError(e)) {
1946
1900
  err = e;
1947
1901
  }
1948
1902
  }
@@ -1951,7 +1905,11 @@ class Router {
1951
1905
  throw err;
1952
1906
  }
1953
1907
  if (event.req.method && event.req.method.toLowerCase() === MethodName.OPTIONS) {
1954
- const options = distinctArray(allowedMethods).map((key)=>key.toUpperCase()).join(',');
1908
+ if (allowedMethods.indexOf(MethodName.GET) !== -1) {
1909
+ allowedMethods.push(MethodName.HEAD);
1910
+ }
1911
+ distinctArray(allowedMethods);
1912
+ const options = allowedMethods.map((key)=>key.toUpperCase()).join(',');
1955
1913
  if (!isResponseGone(event.res)) {
1956
1914
  event.res.setHeader(HeaderName.ALLOW, options);
1957
1915
  await send(event.res, options);
@@ -1960,71 +1918,128 @@ class Router {
1960
1918
  }
1961
1919
  return false;
1962
1920
  }
1963
- // --------------------------------------------------
1964
- route(path) {
1965
- if (typeof path === 'string' && path.length > 0) {
1966
- path = withLeadingSlash(path);
1921
+ delete(path, handler) {
1922
+ if (isPath(path)) {
1923
+ this.use({
1924
+ ...handler,
1925
+ method: MethodName.DELETE,
1926
+ path
1927
+ });
1928
+ return this;
1967
1929
  }
1968
- const index = this.stack.findIndex((item)=>isRouteInstance(item) && item.path === path);
1969
- if (index !== -1) {
1970
- return this.stack[index];
1971
- }
1972
- const route = new Route({
1973
- path,
1974
- pathMatcher: {
1975
- ...this.pathMatcherOptions ? {
1976
- sensitive: this.pathMatcherOptions.sensitive
1977
- } : {}
1978
- }
1930
+ this.use({
1931
+ ...path,
1932
+ method: MethodName.DELETE
1979
1933
  });
1980
- this.stack.push(route);
1981
- return route;
1982
- }
1983
- delete(path, ...handlers) {
1984
- const route = this.route(path);
1985
- route.delete(...handlers);
1986
1934
  return this;
1987
1935
  }
1988
- get(path, ...handlers) {
1989
- const route = this.route(path);
1990
- 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
+ });
1991
1949
  return this;
1992
1950
  }
1993
- post(path, ...handlers) {
1994
- const route = this.route(path);
1995
- 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
+ });
1996
1964
  return this;
1997
1965
  }
1998
- put(path, ...handlers) {
1999
- const route = this.route(path);
2000
- 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
+ });
2001
1979
  return this;
2002
1980
  }
2003
- patch(path, ...handlers) {
2004
- const route = this.route(path);
2005
- 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
+ });
2006
1994
  return this;
2007
1995
  }
2008
- head(path, ...handlers) {
2009
- const route = this.route(path);
2010
- 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
+ });
2011
2009
  return this;
2012
2010
  }
2013
- options(path, ...handlers) {
2014
- const route = this.route(path);
2015
- 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
+ });
2016
2024
  return this;
2017
2025
  }
2018
2026
  use(...input) {
2019
2027
  /* istanbul ignore next */ if (input.length === 0) {
2020
2028
  return this;
2021
2029
  }
2030
+ const modifyPath = (input)=>{
2031
+ if (typeof input === 'string') {
2032
+ return withLeadingSlash(input);
2033
+ }
2034
+ return input;
2035
+ };
2022
2036
  let path;
2023
- if (isPath(input[0])) {
2024
- path = input.shift();
2025
- }
2026
2037
  for(let i = 0; i < input.length; i++){
2027
2038
  const item = input[i];
2039
+ if (isPath(item)) {
2040
+ path = modifyPath(item);
2041
+ continue;
2042
+ }
2028
2043
  if (isRouterInstance(item)) {
2029
2044
  if (path) {
2030
2045
  item.setPath(path);
@@ -2032,35 +2047,51 @@ class Router {
2032
2047
  this.stack.push(item);
2033
2048
  continue;
2034
2049
  }
2035
- if (typeof item === 'function') {
2036
- this.stack.push(new Layer({
2037
- path: path || '/',
2038
- pathMatcher: {
2039
- strict: false,
2040
- end: false,
2041
- ...this.pathMatcherOptions ? {
2042
- sensitive: this.pathMatcherOptions.sensitive
2043
- } : {}
2044
- }
2045
- }, item));
2050
+ if (isHandler(item)) {
2051
+ item.path = path || modifyPath(item.path);
2052
+ this.stack.push(new Layer(item));
2046
2053
  }
2047
2054
  }
2048
2055
  return this;
2049
2056
  }
2050
2057
  // --------------------------------------------------
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
+ // --------------------------------------------------
2051
2082
  constructor(options = {}){
2052
- this['@instanceof'] = Symbol.for('Router');
2083
+ this['@instanceof'] = RouterSymbol;
2053
2084
  /**
2054
2085
  * Array of mounted layers, routes & routers.
2055
2086
  *
2056
2087
  * @protected
2057
2088
  */ this.stack = [];
2058
2089
  this.id = generateRouterID();
2059
- this.pathMatcherOptions = options.pathMatcher;
2090
+ this.name = options.name;
2060
2091
  this.setPath(options.path);
2061
2092
  setRouterOptions(this.id, transformRouterOptions(options));
2062
2093
  }
2063
2094
  }
2064
2095
 
2065
- export { HeaderName, Layer, MethodName, PathMatcher, Route, Router, appendResponseHeader, appendResponseHeaderDirective, cloneDispatcherMeta, cloneDispatcherMetaParams, createNodeDispatcher, createRawDispatcher, createRequest, createResponse, createWebDispatcher, dispatchNodeRequest, dispatchRawRequest, dispatchWebRequest, extendRequestBody, extendRequestCookies, extendRequestQuery, getRequestAcceptableCharset, getRequestAcceptableCharsets, getRequestAcceptableContentType, getRequestAcceptableContentTypes, getRequestAcceptableEncoding, getRequestAcceptableEncodings, getRequestAcceptableLanguage, getRequestAcceptableLanguages, getRequestHeader, getRequestHostName, getRequestIP, getRequestProtocol, hasRequestBody, hasRequestCookies, hasRequestQuery, isLayerInstance, isPath, isRequestCacheable, isResponseGone, isRouteInstance, isRouterInstance, matchRequestContentType, mergeDispatcherMetaParams, send, sendAccepted, sendCreated, sendFile, sendFormat, sendRedirect, sendStream, sendWebBlob, sendWebResponse, setRequestBody, setRequestCookies, setRequestEnv, setRequestHeader, setRequestMountPath, setRequestParam, setRequestParams, setRequestQuery, setRequestRouterIds, setResponseCacheHeaders, setResponseContentTypeByFileName, setResponseHeaderAttachment, setResponseHeaderContentType, unsetRequestEnv, useRequestBody, useRequestCookie, useRequestCookies, useRequestEnv, useRequestMountPath, useRequestNegotiator, useRequestParam, useRequestParams, useRequestPath, useRequestQuery, useRequestRouterIds };
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 };
2066
2097
  //# sourceMappingURL=index.mjs.map