routup 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +10 -8
  2. package/dist/adapters/node/module.d.ts +7 -0
  3. package/dist/{utils → adapters/raw}/header.d.ts +1 -1
  4. package/dist/{layer → adapters/raw}/index.d.ts +1 -1
  5. package/dist/adapters/raw/module.d.ts +4 -0
  6. package/dist/{dispatcher/adapters → adapters}/raw/type.d.ts +1 -1
  7. package/dist/adapters/web/module.d.ts +4 -0
  8. package/dist/constants.d.ts +7 -7
  9. package/dist/dispatcher/event/dispatch.d.ts +4 -0
  10. package/dist/dispatcher/event/error.d.ts +5 -0
  11. package/dist/dispatcher/event/index.d.ts +5 -0
  12. package/dist/dispatcher/event/is.d.ts +3 -0
  13. package/dist/dispatcher/event/module.d.ts +56 -0
  14. package/dist/dispatcher/event/types.d.ts +9 -0
  15. package/dist/dispatcher/index.d.ts +1 -2
  16. package/dist/dispatcher/type.d.ts +2 -30
  17. package/dist/error/create.d.ts +3 -3
  18. package/dist/error/is.d.ts +2 -2
  19. package/dist/error/module.d.ts +1 -1
  20. package/dist/handler/constants.d.ts +1 -0
  21. package/dist/handler/core/define.d.ts +4 -3
  22. package/dist/handler/core/types.d.ts +3 -3
  23. package/dist/handler/error/define.d.ts +4 -3
  24. package/dist/handler/error/types.d.ts +5 -5
  25. package/dist/handler/index.d.ts +1 -0
  26. package/dist/handler/is.d.ts +3 -1
  27. package/dist/handler/module.d.ts +23 -0
  28. package/dist/handler/types-base.d.ts +6 -2
  29. package/dist/handler/types.d.ts +3 -4
  30. package/dist/hook/constants.d.ts +8 -0
  31. package/dist/hook/index.d.ts +3 -0
  32. package/dist/hook/module.d.ts +19 -0
  33. package/dist/hook/types.d.ts +5 -0
  34. package/dist/index.cjs +701 -450
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.mjs +692 -443
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/response/helpers/send-web-blob.d.ts +1 -1
  40. package/dist/response/helpers/send-web-response.d.ts +1 -1
  41. package/dist/router/constants.d.ts +8 -0
  42. package/dist/router/index.d.ts +0 -1
  43. package/dist/router/module.d.ts +56 -24
  44. package/dist/router/types.d.ts +7 -0
  45. package/dist/types.d.ts +1 -0
  46. package/dist/utils/index.d.ts +2 -1
  47. package/dist/utils/method.d.ts +3 -0
  48. package/dist/utils/next.d.ts +2 -0
  49. package/package.json +15 -15
  50. package/dist/dispatcher/adapters/node/module.d.ts +0 -7
  51. package/dist/dispatcher/adapters/raw/module.d.ts +0 -4
  52. package/dist/dispatcher/adapters/web/index.d.ts +0 -2
  53. package/dist/dispatcher/adapters/web/module.d.ts +0 -5
  54. package/dist/dispatcher/utils.d.ts +0 -5
  55. package/dist/layer/constants.d.ts +0 -1
  56. package/dist/layer/module.d.ts +0 -17
  57. package/dist/layer/type.d.ts +0 -8
  58. package/dist/layer/utils.d.ts +0 -2
  59. /package/dist/{dispatcher/adapters → adapters}/index.d.ts +0 -0
  60. /package/dist/{dispatcher/adapters → adapters}/node/index.d.ts +0 -0
  61. /package/dist/{dispatcher/adapters/raw → adapters/web}/index.d.ts +0 -0
  62. /package/dist/{dispatcher/adapters → adapters}/web/type.d.ts +0 -0
package/dist/index.cjs CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var http = require('@ebec/http');
4
3
  var smob = require('smob');
5
4
  var buffer = require('buffer');
6
5
  var uncrypto = require('uncrypto');
@@ -8,17 +7,18 @@ var proxyAddr = require('proxy-addr');
8
7
  var mimeExplorer = require('mime-explorer');
9
8
  var Negotiator = require('negotiator');
10
9
  var readableStream = require('readable-stream');
10
+ var http = require('@ebec/http');
11
11
  var pathToRegexp = require('path-to-regexp');
12
12
 
13
13
  exports.MethodName = void 0;
14
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";
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
22
  })(exports.MethodName || (exports.MethodName = {}));
23
23
  exports.HeaderName = void 0;
24
24
  (function(HeaderName) {
@@ -53,32 +53,6 @@ exports.HeaderName = void 0;
53
53
  HeaderName["X_FORWARDED_PROTO"] = "x-forwarded-proto";
54
54
  })(exports.HeaderName || (exports.HeaderName = {}));
55
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
56
  function isRequestCacheable(req, modifiedTime) {
83
57
  const modifiedSince = req.headers[exports.HeaderName.IF_MODIFIED_SINCE];
84
58
  if (!modifiedSince) {
@@ -209,33 +183,6 @@ function setRequestHeader(req, name, value) {
209
183
  return cookiesStrings;
210
184
  }
211
185
 
212
- function transformHeaderToTuples(key, value) {
213
- const output = [];
214
- if (Array.isArray(value)) {
215
- for(let j = 0; j < value.length; j++){
216
- output.push([
217
- key,
218
- value[j]
219
- ]);
220
- }
221
- } else if (value !== undefined) {
222
- output.push([
223
- key,
224
- String(value)
225
- ]);
226
- }
227
- return output;
228
- }
229
- function transformHeadersToTuples(input) {
230
- const output = [];
231
- const keys = Object.keys(input);
232
- for(let i = 0; i < keys.length; i++){
233
- const key = keys[i].toLowerCase();
234
- output.push(...transformHeaderToTuples(key, input[key]));
235
- }
236
- return output;
237
- }
238
-
239
186
  function isObject(item) {
240
187
  return !!item && typeof item === 'object' && !Array.isArray(item);
241
188
  }
@@ -348,6 +295,15 @@ function getCharsetForMimeType(type) {
348
295
  return undefined;
349
296
  }
350
297
 
298
+ function toMethodName(input, alt) {
299
+ if (input) {
300
+ return input.toUpperCase();
301
+ }
302
+ return alt;
303
+ }
304
+
305
+ const nextPlaceholder = (_err)=>{};
306
+
351
307
  /**
352
308
  * Based on https://github.com/unjs/pathe v1.1.1 (055f50a6f1131f4e5c56cf259dd8816168fba329)
353
309
  */ function normalizeWindowsPath(input = '') {
@@ -751,6 +707,35 @@ function createRequest(context) {
751
707
  return readable;
752
708
  }
753
709
 
710
+ class RoutupError extends http.HTTPError {
711
+ }
712
+
713
+ function isError(input) {
714
+ return input instanceof RoutupError;
715
+ }
716
+
717
+ /**
718
+ * Create an internal error object by
719
+ * - an existing error (accessible via cause property)
720
+ * - options
721
+ * - message
722
+ *
723
+ * @param input
724
+ */ function createError(input) {
725
+ if (isError(input)) {
726
+ return input;
727
+ }
728
+ if (typeof input === 'string') {
729
+ return new RoutupError(input);
730
+ }
731
+ if (!isObject(input)) {
732
+ return new RoutupError();
733
+ }
734
+ return new RoutupError({
735
+ cause: input
736
+ }, input);
737
+ }
738
+
754
739
  function setResponseCacheHeaders(res, options) {
755
740
  options = options || {};
756
741
  const cacheControls = [
@@ -859,6 +844,78 @@ function setResponseHeaderContentType(res, input, ifNotExists) {
859
844
  }
860
845
  }
861
846
 
847
+ async function sendStream(res, stream, next) {
848
+ if (isWebStream(stream)) {
849
+ return stream.pipeTo(new WritableStream({
850
+ write (chunk) {
851
+ res.write(chunk);
852
+ }
853
+ })).then(()=>{
854
+ if (next) {
855
+ return next();
856
+ }
857
+ res.end();
858
+ return Promise.resolve();
859
+ }).catch((err)=>{
860
+ if (next) {
861
+ return next(err);
862
+ }
863
+ return Promise.reject(err);
864
+ });
865
+ }
866
+ return new Promise((resolve, reject)=>{
867
+ stream.on('open', ()=>{
868
+ stream.pipe(res);
869
+ });
870
+ /* istanbul ignore next */ stream.on('error', (err)=>{
871
+ if (next) {
872
+ Promise.resolve().then(()=>next(err)).then(()=>resolve()).catch((e)=>reject(e));
873
+ return;
874
+ }
875
+ res.end();
876
+ reject(err);
877
+ });
878
+ stream.on('close', ()=>{
879
+ if (next) {
880
+ Promise.resolve().then(()=>next()).then(()=>resolve()).catch((e)=>reject(e));
881
+ return;
882
+ }
883
+ res.end();
884
+ resolve();
885
+ });
886
+ });
887
+ }
888
+
889
+ async function sendWebBlob(res, blob) {
890
+ setResponseHeaderContentType(res, blob.type);
891
+ await sendStream(res, blob.stream());
892
+ }
893
+
894
+ async function sendWebResponse(res, webResponse) {
895
+ if (webResponse.redirected) {
896
+ res.setHeader(exports.HeaderName.LOCATION, webResponse.url);
897
+ }
898
+ if (webResponse.status) {
899
+ res.statusCode = webResponse.status;
900
+ }
901
+ if (webResponse.statusText) {
902
+ res.statusMessage = webResponse.statusText;
903
+ }
904
+ webResponse.headers.forEach((value, key)=>{
905
+ if (key === exports.HeaderName.SET_COOKIE) {
906
+ res.appendHeader(key, splitCookiesString(value));
907
+ } else {
908
+ res.setHeader(key, value);
909
+ }
910
+ });
911
+ if (webResponse.body) {
912
+ await sendStream(res, webResponse.body);
913
+ return Promise.resolve();
914
+ }
915
+ res.end();
916
+ return Promise.resolve();
917
+ }
918
+
862
919
  async function send(res, chunk) {
863
920
  switch(typeof chunk){
864
921
  case 'string':
@@ -870,11 +927,28 @@ async function send(res, chunk) {
870
927
  case 'number':
871
928
  case 'object':
872
929
  {
873
- if (buffer.Buffer.isBuffer(chunk)) {
874
- setResponseHeaderContentType(res, 'bin', true);
875
- } else if (chunk !== null) {
876
- chunk = JSON.stringify(chunk);
877
- setResponseHeaderContentType(res, 'application/json', true);
930
+ if (chunk !== null) {
931
+ if (chunk instanceof Error) {
932
+ throw chunk;
933
+ }
934
+ if (isStream(chunk)) {
935
+ await sendStream(res, chunk);
936
+ return;
937
+ }
938
+ if (isWebBlob(chunk)) {
939
+ await sendWebBlob(res, chunk);
940
+ return;
941
+ }
942
+ if (isWebResponse(chunk)) {
943
+ await sendWebResponse(res, chunk);
944
+ return;
945
+ }
946
+ if (buffer.Buffer.isBuffer(chunk)) {
947
+ setResponseHeaderContentType(res, 'bin', true);
948
+ } else {
949
+ chunk = JSON.stringify(chunk);
950
+ setResponseHeaderContentType(res, 'application/json', true);
951
+ }
878
952
  }
879
953
  break;
880
954
  }
@@ -906,7 +980,7 @@ async function send(res, chunk) {
906
980
  const etagFn = findRouterOption('etag', useRequestRouterPath(res.req));
907
981
  const chunkHash = await etagFn(chunk, encoding, len);
908
982
  if (isResponseGone(res)) {
909
- return Promise.resolve();
983
+ return;
910
984
  }
911
985
  if (typeof chunkHash === 'string') {
912
986
  res.setHeader(exports.HeaderName.ETag, chunkHash);
@@ -927,23 +1001,22 @@ async function send(res, chunk) {
927
1001
  res.removeHeader(exports.HeaderName.TRANSFER_ENCODING);
928
1002
  }
929
1003
  if (isResponseGone(res)) {
930
- return Promise.resolve();
1004
+ return;
931
1005
  }
932
- if (res.req.method === 'HEAD') {
1006
+ if (res.req.method === 'HEAD' || res.req.method === 'head') {
933
1007
  // skip body for HEAD
934
1008
  res.end();
935
- return Promise.resolve();
1009
+ return;
936
1010
  }
937
1011
  if (typeof chunk === 'undefined' || chunk === null) {
938
1012
  res.end();
939
- return Promise.resolve();
1013
+ return;
940
1014
  }
941
1015
  if (typeof encoding !== 'undefined') {
942
1016
  res.end(chunk, encoding);
943
- return Promise.resolve();
1017
+ return;
944
1018
  }
945
1019
  res.end(chunk);
946
- return Promise.resolve();
947
1020
  }
948
1021
 
949
1022
  function sendAccepted(res, chunk) {
@@ -958,48 +1031,6 @@ function sendCreated(res, chunk) {
958
1031
  return send(res, chunk);
959
1032
  }
960
1033
 
961
- async function sendStream(res, stream, next) {
962
- if (isWebStream(stream)) {
963
- return stream.pipeTo(new WritableStream({
964
- write (chunk) {
965
- res.write(chunk);
966
- }
967
- })).then(()=>{
968
- if (next) {
969
- return next();
970
- }
971
- res.end();
972
- return Promise.resolve();
973
- }).catch((err)=>{
974
- if (next) {
975
- return next(err);
976
- }
977
- return Promise.reject(err);
978
- });
979
- }
980
- return new Promise((resolve, reject)=>{
981
- stream.on('open', ()=>{
982
- stream.pipe(res);
983
- });
984
- /* istanbul ignore next */ stream.on('error', (err)=>{
985
- if (next) {
986
- Promise.resolve().then(()=>next(err)).then(()=>resolve()).catch((e)=>reject(e));
987
- return;
988
- }
989
- res.end();
990
- reject(err);
991
- });
992
- stream.on('close', ()=>{
993
- if (next) {
994
- Promise.resolve().then(()=>next()).then(()=>resolve()).catch((e)=>reject(e));
995
- return;
996
- }
997
- res.end();
998
- resolve();
999
- });
1000
- });
1001
- }
1002
-
1003
1034
  async function sendFile(res, options, next) {
1004
1035
  let stats;
1005
1036
  try {
@@ -1089,35 +1120,6 @@ function sendRedirect(res, location, statusCode = 302) {
1089
1120
  return send(res, html);
1090
1121
  }
1091
1122
 
1092
- function sendWebResponse(res, webResponse) {
1093
- if (webResponse.redirected) {
1094
- res.setHeader(exports.HeaderName.LOCATION, webResponse.url);
1095
- }
1096
- if (webResponse.status) {
1097
- res.statusCode = webResponse.status;
1098
- }
1099
- if (webResponse.statusText) {
1100
- res.statusMessage = webResponse.statusText;
1101
- }
1102
- webResponse.headers.forEach((value, key)=>{
1103
- if (key === exports.HeaderName.SET_COOKIE) {
1104
- res.appendHeader(key, splitCookiesString(value));
1105
- } else {
1106
- res.setHeader(key, value);
1107
- }
1108
- });
1109
- if (webResponse.body) {
1110
- return sendStream(res, webResponse.body);
1111
- }
1112
- res.end();
1113
- return Promise.resolve();
1114
- }
1115
-
1116
- function sendWebBlob(res, blob) {
1117
- setResponseHeaderContentType(res, blob.type);
1118
- return sendStream(res, blob.stream());
1119
- }
1120
-
1121
1123
  function createResponse(request) {
1122
1124
  let output;
1123
1125
  let encoding;
@@ -1263,84 +1265,112 @@ function createResponse(request) {
1263
1265
  return writable;
1264
1266
  }
1265
1267
 
1266
- function buildDispatcherMeta(input) {
1267
- return {
1268
- mountPath: input.mountPath || '/',
1269
- params: input.params || {},
1270
- path: input.path || '/',
1271
- routerPath: []
1272
- };
1268
+ function dispatch(event, target) {
1269
+ setRequestParams(event.request, event.params);
1270
+ setRequestMountPath(event.request, event.mountPath);
1271
+ setRequestRouterPath(event.request, event.routerPath);
1272
+ return new Promise((resolve, reject)=>{
1273
+ let handled = false;
1274
+ const unsubscribe = ()=>{
1275
+ event.response.off('close', done);
1276
+ event.response.off('error', done);
1277
+ };
1278
+ const shutdown = (dispatched, err)=>{
1279
+ if (handled) {
1280
+ return;
1281
+ }
1282
+ handled = true;
1283
+ unsubscribe();
1284
+ if (err) {
1285
+ reject(createError(err));
1286
+ } else {
1287
+ resolve(dispatched);
1288
+ }
1289
+ };
1290
+ const done = (err)=>shutdown(true, err);
1291
+ const next = (err)=>shutdown(false, err);
1292
+ event.response.once('close', done);
1293
+ event.response.once('error', done);
1294
+ const handle = async (data)=>{
1295
+ if (typeof data === 'undefined' || handled) {
1296
+ return false;
1297
+ }
1298
+ handled = true;
1299
+ unsubscribe();
1300
+ if (!event.dispatched) {
1301
+ await send(event.response, data);
1302
+ }
1303
+ return true;
1304
+ };
1305
+ try {
1306
+ const output = target(next);
1307
+ if (isPromise(output)) {
1308
+ output.then((r)=>handle(r)).then((resolved)=>{
1309
+ if (resolved) {
1310
+ resolve(true);
1311
+ }
1312
+ }).catch((e)=>reject(createError(e)));
1313
+ return;
1314
+ }
1315
+ Promise.resolve().then(()=>handle(output)).then((resolved)=>{
1316
+ if (resolved) {
1317
+ resolve(true);
1318
+ }
1319
+ }).catch((e)=>reject(createError(e)));
1320
+ } catch (error) {
1321
+ next(error);
1322
+ }
1323
+ });
1273
1324
  }
1274
- function cloneDispatcherMeta(input) {
1275
- return {
1276
- path: input.path,
1277
- mountPath: input.mountPath,
1278
- error: input.error,
1279
- routerPath: [
1280
- ...input.routerPath
1281
- ],
1282
- params: cloneDispatcherMetaParams(input.params)
1283
- };
1325
+
1326
+ function isDispatcherErrorEvent(event) {
1327
+ return typeof event.error !== 'undefined';
1284
1328
  }
1285
- function cloneDispatcherMetaParams(input) {
1286
- if (typeof input === 'undefined') {
1287
- return {};
1329
+
1330
+ class DispatchEvent {
1331
+ get dispatched() {
1332
+ return this._dispatched || this.response.writableEnded || this.response.headersSent;
1288
1333
  }
1289
- const keys = Object.keys(input);
1290
- if (keys.length === 0) {
1291
- return {};
1334
+ set dispatched(value) {
1335
+ this._dispatched = value;
1292
1336
  }
1293
- const output = {};
1294
- for(let i = 0; i < keys.length; i++){
1295
- output[keys[i]] = input[keys[i]];
1337
+ constructor(context){
1338
+ this.request = context.request;
1339
+ this.response = context.response;
1340
+ this.method = context.method || exports.MethodName.GET;
1341
+ this.methodsAllowed = [];
1342
+ this.mountPath = '/';
1343
+ this.params = {};
1344
+ this.path = context.path || '/';
1345
+ this.routerPath = [];
1346
+ this.next = nextPlaceholder;
1296
1347
  }
1297
- return output;
1298
1348
  }
1299
- function mergeDispatcherMetaParams(t1, t2) {
1300
- if (!t1 && !t2) {
1301
- return {};
1302
- }
1303
- if (!t1 || !t2) {
1304
- return t1 || t2;
1305
- }
1306
- const keys = Object.keys(t2);
1307
- if (keys.length === 0) {
1308
- return t1;
1309
- }
1310
- for(let i = 0; i < keys.length; i++){
1311
- t1[keys[i]] = t2[keys[i]];
1312
- }
1313
- return t1;
1349
+
1350
+ class DispatchErrorEvent extends DispatchEvent {
1314
1351
  }
1315
1352
 
1316
- async function dispatchNodeRequest(router, req, res) {
1317
- try {
1318
- const dispatched = await router.dispatch({
1319
- req,
1320
- res
1321
- }, buildDispatcherMeta({
1322
- path: useRequestPath(req)
1323
- }));
1324
- if (dispatched) {
1325
- return;
1326
- }
1327
- if (!isResponseGone(res)) {
1328
- res.statusCode = 404;
1329
- res.end();
1330
- }
1331
- } catch (e) {
1332
- if (!isResponseGone(res)) {
1333
- if (isError(e)) {
1334
- res.statusCode = e.statusCode;
1335
- if (e.statusMessage) {
1336
- res.statusMessage = e.statusMessage;
1337
- }
1338
- } else {
1339
- res.statusCode = 500;
1340
- }
1341
- res.end();
1353
+ async function dispatchNodeRequest(router, request, response) {
1354
+ const event = new DispatchEvent({
1355
+ request,
1356
+ response,
1357
+ path: useRequestPath(request),
1358
+ method: toMethodName(request.method, exports.MethodName.GET)
1359
+ });
1360
+ await router.dispatch(event);
1361
+ if (event.dispatched) {
1362
+ return;
1363
+ }
1364
+ if (event.error) {
1365
+ event.response.statusCode = event.error.statusCode;
1366
+ if (event.error.statusMessage) {
1367
+ event.response.statusMessage = event.error.statusMessage;
1342
1368
  }
1369
+ event.response.end();
1370
+ return;
1343
1371
  }
1372
+ event.response.statusCode = 404;
1373
+ event.response.end();
1344
1374
  }
1345
1375
  function createNodeDispatcher(router) {
1346
1376
  return (req, res)=>{
@@ -1349,10 +1379,38 @@ function createNodeDispatcher(router) {
1349
1379
  };
1350
1380
  }
1351
1381
 
1352
- async function dispatchRawRequest(router, request, options = {}) {
1382
+ function transformHeaderToTuples(key, value) {
1383
+ const output = [];
1384
+ if (Array.isArray(value)) {
1385
+ for(let j = 0; j < value.length; j++){
1386
+ output.push([
1387
+ key,
1388
+ value[j]
1389
+ ]);
1390
+ }
1391
+ } else if (value !== undefined) {
1392
+ output.push([
1393
+ key,
1394
+ String(value)
1395
+ ]);
1396
+ }
1397
+ return output;
1398
+ }
1399
+ function transformHeadersToTuples(input) {
1400
+ const output = [];
1401
+ const keys = Object.keys(input);
1402
+ for(let i = 0; i < keys.length; i++){
1403
+ const key = keys[i].toLowerCase();
1404
+ output.push(...transformHeaderToTuples(key, input[key]));
1405
+ }
1406
+ return output;
1407
+ }
1408
+
1409
+ async function dispatchRawRequest(router, request) {
1410
+ const method = toMethodName(request.method, exports.MethodName.GET);
1353
1411
  const req = createRequest({
1354
1412
  url: request.path,
1355
- method: request.method,
1413
+ method,
1356
1414
  body: request.body,
1357
1415
  headers: request.headers
1358
1416
  });
@@ -1377,52 +1435,45 @@ async function dispatchRawRequest(router, request, options = {}) {
1377
1435
  headers: getHeaders(),
1378
1436
  body: res.body
1379
1437
  });
1380
- try {
1381
- const dispatched = await router.dispatch({
1382
- req,
1383
- res
1384
- }, buildDispatcherMeta({
1385
- path: useRequestPath(req)
1386
- }));
1387
- if (dispatched) {
1388
- return createRawResponse();
1389
- }
1390
- return createRawResponse({
1391
- status: 404
1392
- });
1393
- } catch (e) {
1394
- if (options.throwOnError) {
1395
- throw e;
1396
- }
1397
- if (isError(e)) {
1398
- return createRawResponse({
1399
- status: e.statusCode,
1400
- statusMessage: e.statusMessage
1401
- });
1402
- }
1438
+ const event = new DispatchEvent({
1439
+ request: req,
1440
+ response: res,
1441
+ path: request.path,
1442
+ method
1443
+ });
1444
+ await router.dispatch(event);
1445
+ if (event.dispatched) {
1446
+ return createRawResponse();
1447
+ }
1448
+ if (event.error) {
1403
1449
  return createRawResponse({
1404
- status: 500
1450
+ status: event.error.statusCode,
1451
+ statusMessage: event.error.statusMessage
1405
1452
  });
1406
1453
  }
1454
+ return createRawResponse({
1455
+ status: 404
1456
+ });
1407
1457
  }
1408
1458
  function createRawDispatcher(router) {
1409
1459
  return async (request)=>dispatchRawRequest(router, request);
1410
1460
  }
1411
1461
 
1412
- async function dispatchWebRequest(router, request, options = {}) {
1462
+ async function dispatchWebRequest(router, request) {
1413
1463
  const url = new URL(request.url);
1414
1464
  const headers = {};
1415
1465
  request.headers.forEach((value, key)=>{
1416
1466
  headers[key] = value;
1417
1467
  });
1468
+ const method = toMethodName(request.method, exports.MethodName.GET);
1418
1469
  const res = await dispatchRawRequest(router, {
1419
- method: request.method,
1470
+ method,
1420
1471
  path: url.pathname + url.search,
1421
1472
  headers,
1422
1473
  body: request.body
1423
- }, options);
1474
+ });
1424
1475
  let body;
1425
- if (request.method === exports.MethodName.HEAD || res.status === 304 || res.status === 101 || res.status === 204 || res.status === 205) {
1476
+ if (method === exports.MethodName.HEAD || res.status === 304 || res.status === 101 || res.status === 204 || res.status === 205) {
1426
1477
  body = null;
1427
1478
  } else {
1428
1479
  body = res.body;
@@ -1442,35 +1493,98 @@ exports.HandlerType = void 0;
1442
1493
  HandlerType["CORE"] = "core";
1443
1494
  HandlerType["ERROR"] = "error";
1444
1495
  })(exports.HandlerType || (exports.HandlerType = {}));
1445
-
1446
- function coreHandler(input) {
1447
- if (typeof input === 'function') {
1448
- return {
1449
- type: exports.HandlerType.CORE,
1450
- fn: input
1496
+ const HandlerSymbol = Symbol.for('Handler');
1497
+
1498
+ var HookName;
1499
+ (function(HookName) {
1500
+ HookName["ERROR"] = "error";
1501
+ HookName["DISPATCH_START"] = "dispatchStart";
1502
+ HookName["DISPATCH_END"] = "dispatchEnd";
1503
+ HookName["CHILD_MATCH"] = "childMatch";
1504
+ HookName["CHILD_DISPATCH_BEFORE"] = "childDispatchBefore";
1505
+ HookName["CHILD_DISPATCH_AFTER"] = "childDispatchAfter";
1506
+ })(HookName || (HookName = {}));
1507
+
1508
+ class HookManager {
1509
+ // --------------------------------------------------
1510
+ addListener(name, fn) {
1511
+ this.items[name] = this.items[name] || [];
1512
+ this.items[name].push(fn);
1513
+ return ()=>{
1514
+ this.removeListener(name, fn);
1451
1515
  };
1452
1516
  }
1453
- return {
1454
- type: exports.HandlerType.CORE,
1455
- ...input
1456
- };
1457
- }
1458
-
1459
- function errorHandler(input) {
1460
- if (typeof input === 'function') {
1461
- return {
1462
- type: exports.HandlerType.ERROR,
1463
- fn: input
1464
- };
1517
+ removeListener(name, fn) {
1518
+ if (!this.items[name]) {
1519
+ return;
1520
+ }
1521
+ if (typeof fn === 'undefined') {
1522
+ delete this.items[name];
1523
+ return;
1524
+ }
1525
+ if (typeof fn === 'function') {
1526
+ const index = this.items[name].indexOf(fn);
1527
+ if (index !== -1) {
1528
+ this.items[name].splice(index, 1);
1529
+ }
1530
+ }
1531
+ if (this.items[name].length === 0) {
1532
+ delete this.items[name];
1533
+ }
1534
+ }
1535
+ // --------------------------------------------------
1536
+ /**
1537
+ * @throws RoutupError
1538
+ *
1539
+ * @param name
1540
+ * @param event
1541
+ */ async trigger(name, event) {
1542
+ if (!this.items[name] || this.items[name].length === 0) {
1543
+ return;
1544
+ }
1545
+ try {
1546
+ for(let i = 0; i < this.items[name].length; i++){
1547
+ const hook = this.items[name][i];
1548
+ event.dispatched = await dispatch(event, (next)=>Promise.resolve().then(()=>{
1549
+ event.next = next;
1550
+ return this.triggerListener(name, event, hook);
1551
+ }).catch((err)=>next(err)));
1552
+ event.next = nextPlaceholder;
1553
+ if (event.dispatched) {
1554
+ if (event.error) {
1555
+ event.error = undefined;
1556
+ }
1557
+ return;
1558
+ }
1559
+ }
1560
+ } catch (e) {
1561
+ event.error = e;
1562
+ if (!this.isErrorListenerHook(name)) {
1563
+ await this.trigger(HookName.ERROR, event);
1564
+ if (event.dispatched) {
1565
+ if (event.error) {
1566
+ event.error = undefined;
1567
+ }
1568
+ }
1569
+ }
1570
+ }
1571
+ }
1572
+ triggerListener(name, event, listener) {
1573
+ if (this.isErrorListenerHook(name)) {
1574
+ if (isDispatcherErrorEvent(event)) {
1575
+ return listener(event);
1576
+ }
1577
+ return undefined;
1578
+ }
1579
+ return listener(event);
1580
+ }
1581
+ isErrorListenerHook(input) {
1582
+ return input === HookName.ERROR;
1583
+ }
1584
+ // --------------------------------------------------
1585
+ constructor(){
1586
+ this.items = {};
1465
1587
  }
1466
- return {
1467
- type: exports.HandlerType.ERROR,
1468
- ...input
1469
- };
1470
- }
1471
-
1472
- function isHandler(input) {
1473
- return isObject(input) && typeof input.fn === 'function' && typeof input.type === 'string';
1474
1588
  }
1475
1589
 
1476
1590
  function decodeParam(val) {
@@ -1528,93 +1642,59 @@ function isPath(input) {
1528
1642
  return typeof input === 'string' || input instanceof RegExp;
1529
1643
  }
1530
1644
 
1531
- const LayerSymbol = Symbol.for('Layer');
1532
-
1533
- class Layer {
1645
+ class Handler {
1534
1646
  // --------------------------------------------------
1535
1647
  get type() {
1536
- return this.handler.type;
1648
+ return this.config.type;
1537
1649
  }
1538
1650
  get path() {
1539
- return this.handler.path;
1651
+ return this.config.path;
1540
1652
  }
1541
1653
  get method() {
1542
- return this.handler.method ? this.handler.method.toLowerCase() : undefined;
1654
+ if (this._method || !this.config.method) {
1655
+ return this._method;
1656
+ }
1657
+ this._method = toMethodName(this.config.method);
1658
+ return this._method;
1543
1659
  }
1544
1660
  // --------------------------------------------------
1545
- dispatch(event, meta) {
1661
+ async dispatch(event) {
1546
1662
  if (this.pathMatcher) {
1547
- const pathMatch = this.pathMatcher.exec(meta.path);
1663
+ const pathMatch = this.pathMatcher.exec(event.path);
1548
1664
  if (pathMatch) {
1549
- meta.params = mergeDispatcherMetaParams(meta.params, pathMatch.params);
1665
+ event.params = {
1666
+ ...event.params,
1667
+ ...pathMatch.params
1668
+ };
1550
1669
  }
1551
1670
  }
1552
- setRequestParams(event.req, meta.params);
1553
- setRequestMountPath(event.req, meta.mountPath);
1554
- setRequestRouterPath(event.req, meta.routerPath);
1555
- return new Promise((resolve, reject)=>{
1556
- let handled = false;
1557
- const unsubscribe = ()=>{
1558
- event.res.off('close', onFinished);
1559
- event.res.off('error', onFinished);
1560
- };
1561
- const shutdown = (dispatched, err)=>{
1562
- if (handled) {
1563
- return;
1564
- }
1565
- handled = true;
1566
- unsubscribe();
1567
- if (err) {
1568
- reject(createError(err));
1569
- } else {
1570
- resolve(dispatched);
1571
- }
1572
- };
1573
- const onFinished = (err)=>shutdown(true, err);
1574
- const onNext = (err)=>shutdown(false, err);
1575
- event.res.once('close', onFinished);
1576
- event.res.once('error', onFinished);
1577
- const handle = (data)=>{
1578
- if (typeof data === 'undefined' || handled) {
1579
- return Promise.resolve();
1580
- }
1581
- handled = true;
1582
- unsubscribe();
1583
- return this.sendOutput(event.res, data).then(()=>resolve(true)).catch((e)=>reject(createError(e)));
1584
- };
1585
- try {
1586
- let output;
1587
- if (this.handler.type === exports.HandlerType.ERROR) {
1588
- if (meta.error) {
1589
- output = this.handler.fn(meta.error, event.req, event.res, onNext);
1671
+ await this.hookManager.trigger(HookName.CHILD_DISPATCH_BEFORE, event);
1672
+ if (event.dispatched) {
1673
+ return Promise.resolve();
1674
+ }
1675
+ try {
1676
+ event.dispatched = await dispatch(event, (done)=>{
1677
+ if (this.config.type === exports.HandlerType.ERROR) {
1678
+ if (event.error) {
1679
+ return this.config.fn(event.error, event.request, event.response, done);
1590
1680
  }
1591
1681
  } else {
1592
- output = this.handler.fn(event.req, event.res, onNext);
1682
+ return this.config.fn(event.request, event.response, done);
1593
1683
  }
1594
- if (isPromise(output)) {
1595
- output.then((r)=>handle(r)).catch((e)=>reject(createError(e)));
1596
- return;
1684
+ return undefined;
1685
+ });
1686
+ } catch (e) {
1687
+ if (isError(e)) {
1688
+ event.error = e;
1689
+ await this.hookManager.trigger(HookName.ERROR, event);
1690
+ if (event.dispatched) {
1691
+ event.error = undefined;
1692
+ } else {
1693
+ throw e;
1597
1694
  }
1598
- Promise.resolve().then(()=>handle(output)).catch((e)=>reject(createError(e)));
1599
- } catch (error) {
1600
- onNext(error);
1601
1695
  }
1602
- });
1603
- }
1604
- sendOutput(res, input) {
1605
- if (input instanceof Error) {
1606
- return Promise.reject(createError(input));
1607
- }
1608
- if (isStream(input)) {
1609
- return sendStream(res, input);
1610
- }
1611
- if (isWebBlob(input)) {
1612
- return sendWebBlob(res, input);
1613
- }
1614
- if (isWebResponse(input)) {
1615
- return sendWebResponse(res, input);
1616
1696
  }
1617
- return send(res, input);
1697
+ return this.hookManager.trigger(HookName.CHILD_DISPATCH_AFTER, event);
1618
1698
  }
1619
1699
  // --------------------------------------------------
1620
1700
  matchPath(path) {
@@ -1623,30 +1703,81 @@ class Layer {
1623
1703
  }
1624
1704
  return this.pathMatcher.test(path);
1625
1705
  }
1706
+ setPath(path) {
1707
+ if (typeof path === 'string') {
1708
+ path = withLeadingSlash(path);
1709
+ }
1710
+ this.config.path = path;
1711
+ if (typeof path === 'undefined') {
1712
+ this.pathMatcher = undefined;
1713
+ return;
1714
+ }
1715
+ this.pathMatcher = new PathMatcher(path, {
1716
+ end: !!this.config.method
1717
+ });
1718
+ }
1719
+ // --------------------------------------------------
1626
1720
  matchMethod(method) {
1627
- if (!this.method) {
1628
- return true;
1721
+ return !this.method || method === this.method || method === exports.MethodName.HEAD && this.method === exports.MethodName.GET;
1722
+ }
1723
+ setMethod(input) {
1724
+ const method = toMethodName(input);
1725
+ this.config.method = method;
1726
+ this._method = method;
1727
+ }
1728
+ // --------------------------------------------------
1729
+ mountHooks() {
1730
+ if (this.config.onBefore) {
1731
+ this.hookManager.addListener(HookName.CHILD_DISPATCH_BEFORE, this.config.onBefore);
1629
1732
  }
1630
- const name = method.toLowerCase();
1631
- if (name === this.method) {
1632
- return true;
1733
+ if (this.config.onAfter) {
1734
+ this.hookManager.addListener(HookName.CHILD_DISPATCH_AFTER, this.config.onAfter);
1735
+ }
1736
+ if (this.config.onError) {
1737
+ this.hookManager.addListener(HookName.ERROR, this.config.onError);
1633
1738
  }
1634
- return name === exports.MethodName.HEAD && this.method === exports.MethodName.GET;
1635
1739
  }
1636
1740
  // --------------------------------------------------
1637
1741
  constructor(handler){
1638
- this['@instanceof'] = LayerSymbol;
1639
- this.handler = handler;
1640
- if (handler.path) {
1641
- this.pathMatcher = new PathMatcher(handler.path, {
1642
- end: !!handler.method
1643
- });
1644
- }
1742
+ this['@instanceof'] = HandlerSymbol;
1743
+ this.config = handler;
1744
+ this.hookManager = new HookManager();
1745
+ this.mountHooks();
1746
+ this.setPath(handler.path);
1747
+ }
1748
+ }
1749
+
1750
+ function coreHandler(input) {
1751
+ if (typeof input === 'function') {
1752
+ return new Handler({
1753
+ type: exports.HandlerType.CORE,
1754
+ fn: input
1755
+ });
1756
+ }
1757
+ return new Handler({
1758
+ type: exports.HandlerType.CORE,
1759
+ ...input
1760
+ });
1761
+ }
1762
+
1763
+ function errorHandler(input) {
1764
+ if (typeof input === 'function') {
1765
+ return new Handler({
1766
+ type: exports.HandlerType.ERROR,
1767
+ fn: input
1768
+ });
1645
1769
  }
1770
+ return new Handler({
1771
+ type: exports.HandlerType.ERROR,
1772
+ ...input
1773
+ });
1646
1774
  }
1647
1775
 
1648
- function isLayerInstance(input) {
1649
- return isInstance(input, LayerSymbol);
1776
+ function isHandlerConfig(input) {
1777
+ return isObject(input) && typeof input.fn === 'function' && typeof input.type === 'string';
1778
+ }
1779
+ function isHandler(input) {
1780
+ return isInstance(input, HandlerSymbol);
1650
1781
  }
1651
1782
 
1652
1783
  function isPlugin(input) {
@@ -1670,6 +1801,15 @@ function transformRouterOptions(input) {
1670
1801
  }
1671
1802
 
1672
1803
  const RouterSymbol = Symbol.for('Router');
1804
+ var RouterPipelineStep;
1805
+ (function(RouterPipelineStep) {
1806
+ RouterPipelineStep[RouterPipelineStep["START"] = 0] = "START";
1807
+ RouterPipelineStep[RouterPipelineStep["LOOKUP"] = 1] = "LOOKUP";
1808
+ RouterPipelineStep[RouterPipelineStep["CHILD_BEFORE"] = 2] = "CHILD_BEFORE";
1809
+ RouterPipelineStep[RouterPipelineStep["CHILD_DISPATCH"] = 3] = "CHILD_DISPATCH";
1810
+ RouterPipelineStep[RouterPipelineStep["CHILD_AFTER"] = 4] = "CHILD_AFTER";
1811
+ RouterPipelineStep[RouterPipelineStep["FINISH"] = 5] = "FINISH";
1812
+ })(RouterPipelineStep || (RouterPipelineStep = {}));
1673
1813
 
1674
1814
  let nextId = 0;
1675
1815
  function generateRouterID() {
@@ -1681,102 +1821,187 @@ function isRouterInstance(input) {
1681
1821
 
1682
1822
  class Router {
1683
1823
  // --------------------------------------------------
1824
+ matchPath(path) {
1825
+ if (this.pathMatcher) {
1826
+ return this.pathMatcher.test(path);
1827
+ }
1828
+ return true;
1829
+ }
1684
1830
  setPath(value) {
1685
- if (value === '/' || !isPath(value)) {
1831
+ if (value === '/' || typeof value === 'undefined') {
1832
+ this.pathMatcher = undefined;
1686
1833
  return;
1687
1834
  }
1688
- let path;
1689
1835
  if (typeof value === 'string') {
1690
- path = withLeadingSlash(withoutTrailingSlash(`${value}`));
1836
+ this.pathMatcher = new PathMatcher(withLeadingSlash(withoutTrailingSlash(`${value}`)), {
1837
+ end: false
1838
+ });
1691
1839
  } else {
1692
- path = value;
1840
+ this.pathMatcher = new PathMatcher(value, {
1841
+ end: false
1842
+ });
1693
1843
  }
1694
- this.pathMatcher = new PathMatcher(path, {
1695
- end: false
1696
- });
1697
1844
  }
1698
1845
  // --------------------------------------------------
1699
- matchPath(path) {
1700
- if (this.pathMatcher) {
1701
- return this.pathMatcher.test(path);
1846
+ async executePipelineStep(context) {
1847
+ switch(context.step){
1848
+ case RouterPipelineStep.START:
1849
+ {
1850
+ return this.executePipelineStepStart(context);
1851
+ }
1852
+ case RouterPipelineStep.LOOKUP:
1853
+ {
1854
+ return this.executePipelineStepLookup(context);
1855
+ }
1856
+ case RouterPipelineStep.CHILD_BEFORE:
1857
+ {
1858
+ return this.executePipelineStepChildBefore(context);
1859
+ }
1860
+ case RouterPipelineStep.CHILD_DISPATCH:
1861
+ {
1862
+ return this.executePipelineStepChildDispatch(context);
1863
+ }
1864
+ case RouterPipelineStep.CHILD_AFTER:
1865
+ {
1866
+ return this.executePipelineStepChildAfter(context);
1867
+ }
1868
+ case RouterPipelineStep.FINISH:
1869
+ default:
1870
+ {
1871
+ return this.executePipelineStepFinish(context);
1872
+ }
1702
1873
  }
1703
- return true;
1704
1874
  }
1705
- // --------------------------------------------------
1706
- async dispatch(event, meta) {
1707
- const allowedMethods = [];
1708
- if (this.pathMatcher) {
1709
- const output = this.pathMatcher.exec(meta.path);
1710
- if (typeof output !== 'undefined') {
1711
- meta.mountPath = cleanDoubleSlashes(`${meta.mountPath}/${output.path}`);
1712
- if (meta.path === output.path) {
1713
- meta.path = '/';
1714
- } else {
1715
- meta.path = withLeadingSlash(meta.path.substring(output.path.length));
1716
- }
1717
- meta.params = {
1718
- ...meta.params,
1719
- ...output.params
1720
- };
1875
+ async executePipelineStepStart(context) {
1876
+ return this.hookManager.trigger(HookName.DISPATCH_START, context.event).then(()=>{
1877
+ if (context.event.dispatched) {
1878
+ context.step = RouterPipelineStep.FINISH;
1879
+ } else {
1880
+ context.step++;
1721
1881
  }
1722
- }
1723
- meta.routerPath.push(this.id);
1724
- let err;
1725
- let item;
1726
- let itemMeta;
1727
- let match = false;
1728
- for(let i = 0; i < this.stack.length; i++){
1729
- item = this.stack[i];
1730
- if (isLayerInstance(item)) {
1731
- if (item.type !== exports.HandlerType.ERROR && err) {
1732
- continue;
1882
+ return this.executePipelineStep(context);
1883
+ });
1884
+ }
1885
+ async executePipelineStepLookup(context) {
1886
+ if (context.event.dispatched || context.stackIndex >= this.stack.length) {
1887
+ context.step = RouterPipelineStep.FINISH;
1888
+ return this.executePipelineStep(context);
1889
+ }
1890
+ let match;
1891
+ const item = this.stack[context.stackIndex];
1892
+ if (isHandler(item)) {
1893
+ if (context.event.error && item.type === exports.HandlerType.CORE || !context.event.error && item.type === exports.HandlerType.ERROR) {
1894
+ context.stackIndex++;
1895
+ return this.executePipelineStepLookup(context);
1896
+ }
1897
+ match = item.matchPath(context.event.path);
1898
+ if (match) {
1899
+ if (item.method) {
1900
+ context.event.methodsAllowed.push(item.method);
1733
1901
  }
1734
- match = item.matchPath(meta.path);
1735
- if (match && event.req.method) {
1736
- if (!item.matchMethod(event.req.method)) {
1737
- match = false;
1738
- }
1739
- if (item.method) {
1740
- allowedMethods.push(item.method);
1902
+ if (item.matchMethod(context.event.method)) {
1903
+ await this.hookManager.trigger(HookName.CHILD_MATCH, context.event);
1904
+ if (context.event.dispatched) {
1905
+ context.step = RouterPipelineStep.FINISH;
1906
+ } else {
1907
+ context.step++;
1741
1908
  }
1909
+ return this.executePipelineStep(context);
1742
1910
  }
1743
- } else if (isRouterInstance(item)) {
1744
- match = item.matchPath(meta.path);
1745
1911
  }
1746
- if (!match) {
1747
- continue;
1912
+ context.stackIndex++;
1913
+ return this.executePipelineStepLookup(context);
1914
+ }
1915
+ match = item.matchPath(context.event.path);
1916
+ if (match) {
1917
+ await this.hookManager.trigger(HookName.CHILD_MATCH, context.event);
1918
+ if (context.event.dispatched) {
1919
+ context.step = RouterPipelineStep.FINISH;
1920
+ } else {
1921
+ context.step++;
1748
1922
  }
1749
- itemMeta = cloneDispatcherMeta(meta);
1750
- if (err) {
1751
- itemMeta.error = err;
1923
+ return this.executePipelineStep(context);
1924
+ }
1925
+ context.stackIndex++;
1926
+ return this.executePipelineStepLookup(context);
1927
+ }
1928
+ async executePipelineStepChildBefore(context) {
1929
+ return this.hookManager.trigger(HookName.CHILD_DISPATCH_BEFORE, context.event).then(()=>{
1930
+ if (context.event.dispatched) {
1931
+ context.step = RouterPipelineStep.FINISH;
1932
+ } else {
1933
+ context.step++;
1752
1934
  }
1753
- try {
1754
- const dispatched = await item.dispatch(event, itemMeta);
1755
- if (dispatched) {
1756
- return true;
1757
- }
1758
- } catch (e) {
1759
- if (isError(e)) {
1760
- err = e;
1761
- }
1935
+ return this.executePipelineStep(context);
1936
+ });
1937
+ }
1938
+ async executePipelineStepChildAfter(context) {
1939
+ return this.hookManager.trigger(HookName.CHILD_DISPATCH_AFTER, context.event).then(()=>{
1940
+ if (context.event.dispatched) {
1941
+ context.step = RouterPipelineStep.FINISH;
1942
+ } else {
1943
+ context.step = RouterPipelineStep.LOOKUP;
1762
1944
  }
1945
+ return this.executePipelineStep(context);
1946
+ });
1947
+ }
1948
+ async executePipelineStepChildDispatch(context) {
1949
+ if (context.event.dispatched || typeof this.stack[context.stackIndex] === 'undefined') {
1950
+ context.step = RouterPipelineStep.FINISH;
1951
+ return this.executePipelineStep(context);
1952
+ }
1953
+ try {
1954
+ await this.stack[context.stackIndex].dispatch(context.event);
1955
+ } catch (e) {
1956
+ context.event.error = e;
1957
+ await this.hookManager.trigger(HookName.ERROR, context.event);
1763
1958
  }
1764
- if (err) {
1765
- throw err;
1959
+ context.stackIndex++;
1960
+ context.step++;
1961
+ return this.executePipelineStep(context);
1962
+ }
1963
+ async executePipelineStepFinish(context) {
1964
+ if (context.event.error || context.event.dispatched) {
1965
+ return this.hookManager.trigger(HookName.DISPATCH_END, context.event);
1766
1966
  }
1767
- if (event.req.method && event.req.method.toLowerCase() === exports.MethodName.OPTIONS) {
1768
- if (allowedMethods.indexOf(exports.MethodName.GET) !== -1) {
1769
- allowedMethods.push(exports.MethodName.HEAD);
1967
+ if (!context.event.dispatched && context.event.routerPath.length === 1 && context.event.method && context.event.method === exports.MethodName.OPTIONS) {
1968
+ if (context.event.methodsAllowed.indexOf(exports.MethodName.GET) !== -1) {
1969
+ context.event.methodsAllowed.push(exports.MethodName.HEAD);
1770
1970
  }
1771
- smob.distinctArray(allowedMethods);
1772
- const options = allowedMethods.map((key)=>key.toUpperCase()).join(',');
1773
- if (!isResponseGone(event.res)) {
1774
- event.res.setHeader(exports.HeaderName.ALLOW, options);
1775
- await send(event.res, options);
1971
+ smob.distinctArray(context.event.methodsAllowed);
1972
+ const options = context.event.methodsAllowed.map((key)=>key.toUpperCase()).join(',');
1973
+ context.event.response.setHeader(exports.HeaderName.ALLOW, options);
1974
+ await send(context.event.response, options);
1975
+ context.event.dispatched = true;
1976
+ }
1977
+ return this.hookManager.trigger(HookName.DISPATCH_END, context.event);
1978
+ }
1979
+ // --------------------------------------------------
1980
+ async dispatch(event) {
1981
+ if (this.pathMatcher) {
1982
+ const output = this.pathMatcher.exec(event.path);
1983
+ if (typeof output !== 'undefined') {
1984
+ event.mountPath = cleanDoubleSlashes(`${event.mountPath}/${output.path}`);
1985
+ if (event.path === output.path) {
1986
+ event.path = '/';
1987
+ } else {
1988
+ event.path = withLeadingSlash(event.path.substring(output.path.length));
1989
+ }
1990
+ event.params = {
1991
+ ...event.params,
1992
+ ...output.params
1993
+ };
1776
1994
  }
1777
- return true;
1778
1995
  }
1779
- return false;
1996
+ const context = {
1997
+ step: RouterPipelineStep.START,
1998
+ event,
1999
+ stackIndex: 0
2000
+ };
2001
+ event.routerPath.push(this.id);
2002
+ return this.executePipelineStepStart(context).then(()=>{
2003
+ context.event.routerPath.pop();
2004
+ });
1780
2005
  }
1781
2006
  delete(...input) {
1782
2007
  this.useForMethod(exports.MethodName.DELETE, ...input);
@@ -1808,33 +2033,40 @@ class Router {
1808
2033
  }
1809
2034
  // --------------------------------------------------
1810
2035
  useForMethod(method, ...input) {
1811
- const base = {
1812
- method
1813
- };
2036
+ let path;
1814
2037
  for(let i = 0; i < input.length; i++){
1815
2038
  const element = input[i];
1816
2039
  if (isPath(element)) {
1817
- base.path = element;
2040
+ path = element;
1818
2041
  continue;
1819
2042
  }
1820
- this.use({
1821
- ...base,
1822
- ...element
1823
- });
2043
+ if (isHandlerConfig(element)) {
2044
+ if (path) {
2045
+ element.path = path;
2046
+ }
2047
+ element.method = method;
2048
+ this.use(element);
2049
+ continue;
2050
+ }
2051
+ if (isHandler(element)) {
2052
+ if (path) {
2053
+ element.setPath(path);
2054
+ }
2055
+ element.setMethod(method);
2056
+ this.use(element);
2057
+ }
1824
2058
  }
1825
2059
  }
1826
2060
  use(...input) {
1827
- const modifyPath = (input)=>{
1828
- if (typeof input === 'string') {
1829
- return withLeadingSlash(input);
1830
- }
1831
- return input;
1832
- };
1833
2061
  let path;
1834
2062
  for(let i = 0; i < input.length; i++){
1835
2063
  const item = input[i];
1836
2064
  if (isPath(item)) {
1837
- path = modifyPath(item);
2065
+ if (typeof item === 'string') {
2066
+ path = withLeadingSlash(item);
2067
+ } else {
2068
+ path = item;
2069
+ }
1838
2070
  continue;
1839
2071
  }
1840
2072
  if (isRouterInstance(item)) {
@@ -1844,9 +2076,14 @@ class Router {
1844
2076
  this.stack.push(item);
1845
2077
  continue;
1846
2078
  }
2079
+ if (isHandlerConfig(item)) {
2080
+ item.path = path || item.path;
2081
+ this.stack.push(new Handler(item));
2082
+ continue;
2083
+ }
1847
2084
  if (isHandler(item)) {
1848
- item.path = path || modifyPath(item.path);
1849
- this.stack.push(new Layer(item));
2085
+ item.setPath(path || item.path);
2086
+ this.stack.push(item);
1850
2087
  continue;
1851
2088
  }
1852
2089
  if (isPlugin(item)) {
@@ -1875,6 +2112,17 @@ class Router {
1875
2112
  }
1876
2113
  return this;
1877
2114
  }
2115
+ on(name, fn) {
2116
+ return this.hookManager.addListener(name, fn);
2117
+ }
2118
+ off(name, fn) {
2119
+ if (typeof fn === 'undefined') {
2120
+ this.hookManager.removeListener(name);
2121
+ return this;
2122
+ }
2123
+ this.hookManager.removeListener(name, fn);
2124
+ return this;
2125
+ }
1878
2126
  // --------------------------------------------------
1879
2127
  constructor(options = {}){
1880
2128
  this['@instanceof'] = RouterSymbol;
@@ -1885,20 +2133,21 @@ class Router {
1885
2133
  */ this.stack = [];
1886
2134
  this.id = generateRouterID();
1887
2135
  this.name = options.name;
2136
+ this.hookManager = new HookManager();
1888
2137
  this.setPath(options.path);
1889
2138
  setRouterOptions(this.id, transformRouterOptions(options));
1890
2139
  }
1891
2140
  }
1892
2141
 
1893
- exports.ErrorProxy = ErrorProxy;
1894
- exports.Layer = Layer;
2142
+ exports.DispatchErrorEvent = DispatchErrorEvent;
2143
+ exports.DispatchEvent = DispatchEvent;
2144
+ exports.Handler = Handler;
2145
+ exports.HandlerSymbol = HandlerSymbol;
1895
2146
  exports.PathMatcher = PathMatcher;
1896
2147
  exports.Router = Router;
2148
+ exports.RoutupError = RoutupError;
1897
2149
  exports.appendResponseHeader = appendResponseHeader;
1898
2150
  exports.appendResponseHeaderDirective = appendResponseHeaderDirective;
1899
- exports.buildDispatcherMeta = buildDispatcherMeta;
1900
- exports.cloneDispatcherMeta = cloneDispatcherMeta;
1901
- exports.cloneDispatcherMetaParams = cloneDispatcherMetaParams;
1902
2151
  exports.coreHandler = coreHandler;
1903
2152
  exports.createError = createError;
1904
2153
  exports.createNodeDispatcher = createNodeDispatcher;
@@ -1906,6 +2155,7 @@ exports.createRawDispatcher = createRawDispatcher;
1906
2155
  exports.createRequest = createRequest;
1907
2156
  exports.createResponse = createResponse;
1908
2157
  exports.createWebDispatcher = createWebDispatcher;
2158
+ exports.dispatch = dispatch;
1909
2159
  exports.dispatchNodeRequest = dispatchNodeRequest;
1910
2160
  exports.dispatchRawRequest = dispatchRawRequest;
1911
2161
  exports.dispatchWebRequest = dispatchWebRequest;
@@ -1922,16 +2172,15 @@ exports.getRequestHeader = getRequestHeader;
1922
2172
  exports.getRequestHostName = getRequestHostName;
1923
2173
  exports.getRequestIP = getRequestIP;
1924
2174
  exports.getRequestProtocol = getRequestProtocol;
2175
+ exports.isDispatcherErrorEvent = isDispatcherErrorEvent;
1925
2176
  exports.isError = isError;
1926
2177
  exports.isHandler = isHandler;
1927
- exports.isLayerInstance = isLayerInstance;
2178
+ exports.isHandlerConfig = isHandlerConfig;
1928
2179
  exports.isPath = isPath;
1929
2180
  exports.isPlugin = isPlugin;
1930
2181
  exports.isRequestCacheable = isRequestCacheable;
1931
2182
  exports.isResponseGone = isResponseGone;
1932
- exports.isRouterInstance = isRouterInstance;
1933
2183
  exports.matchRequestContentType = matchRequestContentType;
1934
- exports.mergeDispatcherMetaParams = mergeDispatcherMetaParams;
1935
2184
  exports.send = send;
1936
2185
  exports.sendAccepted = sendAccepted;
1937
2186
  exports.sendCreated = sendCreated;
@@ -1951,6 +2200,8 @@ exports.setResponseCacheHeaders = setResponseCacheHeaders;
1951
2200
  exports.setResponseContentTypeByFileName = setResponseContentTypeByFileName;
1952
2201
  exports.setResponseHeaderAttachment = setResponseHeaderAttachment;
1953
2202
  exports.setResponseHeaderContentType = setResponseHeaderContentType;
2203
+ exports.transformHeaderToTuples = transformHeaderToTuples;
2204
+ exports.transformHeadersToTuples = transformHeadersToTuples;
1954
2205
  exports.unsetRequestEnv = unsetRequestEnv;
1955
2206
  exports.useRequestEnv = useRequestEnv;
1956
2207
  exports.useRequestMountPath = useRequestMountPath;