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