@e22m4u/js-trie-router 0.5.7 → 0.5.9
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.
- package/dist/cjs/index.cjs +53 -46
- package/package.json +3 -3
- package/src/branch/router-branch.js +2 -2
- package/src/parsers/body-parser.js +13 -5
- package/src/parsers/cookies-parser.js +2 -2
- package/src/parsers/query-parser.js +2 -2
- package/src/parsers/request-parser.js +1 -1
- package/src/route/route.js +2 -6
- package/src/route-registry.js +5 -13
- package/src/senders/data-sender.js +10 -10
- package/src/senders/error-sender.js +2 -2
- package/src/trie-router.js +8 -8
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +1 -0
- package/src/utils/to-pascal-case.d.ts +6 -0
- package/src/utils/to-pascal-case.js +26 -0
- package/src/utils/to-pascal-case.spec.js +15 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -70,6 +70,7 @@ __export(index_exports, {
|
|
|
70
70
|
parseCookieString: () => parseCookieString,
|
|
71
71
|
parseJsonBody: () => parseJsonBody,
|
|
72
72
|
toCamelCase: () => toCamelCase,
|
|
73
|
+
toPascalCase: () => toPascalCase,
|
|
73
74
|
validateRouteDefinition: () => validateRouteDefinition
|
|
74
75
|
});
|
|
75
76
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -200,6 +201,15 @@ function toCamelCase(input) {
|
|
|
200
201
|
}
|
|
201
202
|
__name(toCamelCase, "toCamelCase");
|
|
202
203
|
|
|
204
|
+
// src/utils/to-pascal-case.js
|
|
205
|
+
function toPascalCase(input) {
|
|
206
|
+
if (!input) {
|
|
207
|
+
return "";
|
|
208
|
+
}
|
|
209
|
+
return input.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([0-9])([a-zA-Z])/g, "$1 $2").replace(/[-_]+|[^\p{L}\p{N}]/gu, " ").toLowerCase().replace(new RegExp("(?:^|\\s)(\\p{L})", "gu"), (_, letter) => letter.toUpperCase()).replace(/\s+/g, "");
|
|
210
|
+
}
|
|
211
|
+
__name(toPascalCase, "toPascalCase");
|
|
212
|
+
|
|
203
213
|
// src/utils/normalize-path.js
|
|
204
214
|
function normalizePath(value, noStartingSlash = false) {
|
|
205
215
|
if (typeof value !== "string") {
|
|
@@ -1157,7 +1167,7 @@ var _Route = class _Route extends import_js_debug.Debuggable {
|
|
|
1157
1167
|
this._hookRegistry.addHook(RouterHookType.POST_HANDLER, hook);
|
|
1158
1168
|
});
|
|
1159
1169
|
}
|
|
1160
|
-
this.ctorDebug("
|
|
1170
|
+
this.ctorDebug("Created a route %s %v.", this.method, this.path);
|
|
1161
1171
|
}
|
|
1162
1172
|
/**
|
|
1163
1173
|
* Handle request.
|
|
@@ -1168,11 +1178,7 @@ var _Route = class _Route extends import_js_debug.Debuggable {
|
|
|
1168
1178
|
handle(context) {
|
|
1169
1179
|
const debug = this.getDebuggerFor(this.handle);
|
|
1170
1180
|
const requestPath = getRequestPathname(context.request);
|
|
1171
|
-
debug(
|
|
1172
|
-
"Invoking the Route handler for the request %s %v.",
|
|
1173
|
-
this.method,
|
|
1174
|
-
requestPath
|
|
1175
|
-
);
|
|
1181
|
+
debug("Invoking a route handler %s %v.", this.method, requestPath);
|
|
1176
1182
|
return this.handler(context);
|
|
1177
1183
|
}
|
|
1178
1184
|
};
|
|
@@ -1304,9 +1310,14 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1304
1310
|
*/
|
|
1305
1311
|
parse(request) {
|
|
1306
1312
|
const debug = this.getDebuggerFor(this.parse);
|
|
1313
|
+
debug(
|
|
1314
|
+
"Parsing a request body %s %v.",
|
|
1315
|
+
request.method.toUpperCase(),
|
|
1316
|
+
getRequestPathname(request)
|
|
1317
|
+
);
|
|
1307
1318
|
if (!METHODS_WITH_BODY.includes(request.method.toUpperCase())) {
|
|
1308
1319
|
debug(
|
|
1309
|
-
"
|
|
1320
|
+
"Skipping body parsing for the %s method.",
|
|
1310
1321
|
request.method.toUpperCase()
|
|
1311
1322
|
);
|
|
1312
1323
|
return;
|
|
@@ -1316,9 +1327,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1316
1327
|
"$1"
|
|
1317
1328
|
);
|
|
1318
1329
|
if (!contentType) {
|
|
1319
|
-
debug(
|
|
1320
|
-
"Body parsing was skipped because the request had no content type."
|
|
1321
|
-
);
|
|
1330
|
+
debug("Skipping body parsing because no content type is provided.");
|
|
1322
1331
|
return;
|
|
1323
1332
|
}
|
|
1324
1333
|
const { mediaType } = parseContentType(contentType);
|
|
@@ -1331,7 +1340,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1331
1340
|
const parser = this._parsers[mediaType];
|
|
1332
1341
|
if (!parser) {
|
|
1333
1342
|
if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
|
|
1334
|
-
debug("
|
|
1343
|
+
debug("Skipping body parsing for the media type %v.", mediaType);
|
|
1335
1344
|
return;
|
|
1336
1345
|
}
|
|
1337
1346
|
throw createError(
|
|
@@ -1341,10 +1350,14 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1341
1350
|
);
|
|
1342
1351
|
}
|
|
1343
1352
|
const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
|
|
1353
|
+
debug("Fetching a request body.");
|
|
1354
|
+
debug("Body limit is %v bytes.", bodyBytesLimit);
|
|
1344
1355
|
return fetchRequestBody(request, bodyBytesLimit).then((rawBody) => {
|
|
1345
1356
|
if (rawBody != null) {
|
|
1357
|
+
debug("Read %v bytes.", Buffer.byteLength(rawBody, "utf8"));
|
|
1346
1358
|
return parser(rawBody);
|
|
1347
1359
|
}
|
|
1360
|
+
debug("Request body has no content.");
|
|
1348
1361
|
return rawBody;
|
|
1349
1362
|
});
|
|
1350
1363
|
}
|
|
@@ -1379,11 +1392,11 @@ var _QueryParser = class _QueryParser extends DebuggableService {
|
|
|
1379
1392
|
const queryKeys = Object.keys(query);
|
|
1380
1393
|
if (queryKeys.length) {
|
|
1381
1394
|
queryKeys.forEach((key) => {
|
|
1382
|
-
debug("
|
|
1395
|
+
debug("Found a query parameter %v with a value %v.", key, query[key]);
|
|
1383
1396
|
});
|
|
1384
1397
|
} else {
|
|
1385
1398
|
debug(
|
|
1386
|
-
"
|
|
1399
|
+
"Request %s %v had no query parameters.",
|
|
1387
1400
|
request.method,
|
|
1388
1401
|
getRequestPathname(request)
|
|
1389
1402
|
);
|
|
@@ -1409,11 +1422,11 @@ var _CookiesParser = class _CookiesParser extends DebuggableService {
|
|
|
1409
1422
|
const cookiesKeys = Object.keys(cookies);
|
|
1410
1423
|
if (cookiesKeys.length) {
|
|
1411
1424
|
cookiesKeys.forEach((key) => {
|
|
1412
|
-
debug("
|
|
1425
|
+
debug("Found a cookie %v with a value %v.", key, cookies[key]);
|
|
1413
1426
|
});
|
|
1414
1427
|
} else {
|
|
1415
1428
|
debug(
|
|
1416
|
-
"
|
|
1429
|
+
"Request %s %v had no cookies.",
|
|
1417
1430
|
request.method,
|
|
1418
1431
|
getRequestPathname(request)
|
|
1419
1432
|
);
|
|
@@ -1499,11 +1512,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
|
|
|
1499
1512
|
const route = new Route(routeDef);
|
|
1500
1513
|
const triePath = `${route.method}/${route.path}`;
|
|
1501
1514
|
this._trie.add(triePath, route);
|
|
1502
|
-
debug(
|
|
1503
|
-
"The route %s %v was registered.",
|
|
1504
|
-
route.method.toUpperCase(),
|
|
1505
|
-
route.path
|
|
1506
|
-
);
|
|
1515
|
+
debug("Registered a route %s %v.", route.method.toUpperCase(), route.path);
|
|
1507
1516
|
return route;
|
|
1508
1517
|
}
|
|
1509
1518
|
/**
|
|
@@ -1516,7 +1525,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
|
|
|
1516
1525
|
const debug = this.getDebuggerFor(this.matchRouteByRequest);
|
|
1517
1526
|
const requestPath = getRequestPathname(request);
|
|
1518
1527
|
debug(
|
|
1519
|
-
"Matching routes
|
|
1528
|
+
"Matching routes for the request %s %v.",
|
|
1520
1529
|
request.method.toUpperCase(),
|
|
1521
1530
|
requestPath
|
|
1522
1531
|
);
|
|
@@ -1525,16 +1534,12 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
|
|
|
1525
1534
|
const resolved = this._trie.match(triePath);
|
|
1526
1535
|
if (resolved) {
|
|
1527
1536
|
const route = resolved.value;
|
|
1528
|
-
debug(
|
|
1529
|
-
"The route %s %v was matched.",
|
|
1530
|
-
route.method.toUpperCase(),
|
|
1531
|
-
route.path
|
|
1532
|
-
);
|
|
1537
|
+
debug("Matched route is %s %v.", route.method.toUpperCase(), route.path);
|
|
1533
1538
|
const paramNames = Object.keys(resolved.params);
|
|
1534
1539
|
if (paramNames.length) {
|
|
1535
1540
|
paramNames.forEach((name) => {
|
|
1536
1541
|
debug(
|
|
1537
|
-
"
|
|
1542
|
+
"Found a path parameter %v with a value %v.",
|
|
1538
1543
|
name,
|
|
1539
1544
|
resolved.params[name]
|
|
1540
1545
|
);
|
|
@@ -1545,7 +1550,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
|
|
|
1545
1550
|
return { route, params: resolved.params };
|
|
1546
1551
|
}
|
|
1547
1552
|
debug(
|
|
1548
|
-
"No
|
|
1553
|
+
"No route found for the request %s %v.",
|
|
1549
1554
|
request.method.toUpperCase(),
|
|
1550
1555
|
requestPath
|
|
1551
1556
|
);
|
|
@@ -1752,21 +1757,19 @@ var _DataSender = class _DataSender extends DebuggableService {
|
|
|
1752
1757
|
send(response, data) {
|
|
1753
1758
|
const debug = this.getDebuggerFor(this.send);
|
|
1754
1759
|
if (data === response || response.headersSent) {
|
|
1755
|
-
debug(
|
|
1756
|
-
"Response sending was skipped because its headers where sent already."
|
|
1757
|
-
);
|
|
1760
|
+
debug("Skipping response because headers have already been sent.");
|
|
1758
1761
|
return;
|
|
1759
1762
|
}
|
|
1760
1763
|
if (data == null) {
|
|
1761
1764
|
response.statusCode = 204;
|
|
1762
1765
|
response.end();
|
|
1763
|
-
debug("
|
|
1766
|
+
debug("Empty response has been sent.");
|
|
1764
1767
|
return;
|
|
1765
1768
|
}
|
|
1766
1769
|
if (isReadableStream(data)) {
|
|
1767
1770
|
response.setHeader("Content-Type", "application/octet-stream");
|
|
1768
1771
|
data.pipe(response);
|
|
1769
|
-
debug("
|
|
1772
|
+
debug("Sending response with a Stream.");
|
|
1770
1773
|
return;
|
|
1771
1774
|
}
|
|
1772
1775
|
let debugMsg;
|
|
@@ -1776,16 +1779,19 @@ var _DataSender = class _DataSender extends DebuggableService {
|
|
|
1776
1779
|
case "number":
|
|
1777
1780
|
if (Buffer.isBuffer(data)) {
|
|
1778
1781
|
response.setHeader("content-type", "application/octet-stream");
|
|
1779
|
-
debugMsg = "
|
|
1782
|
+
debugMsg = "Buffer has been sent as binary data.";
|
|
1780
1783
|
} else {
|
|
1781
1784
|
response.setHeader("content-type", "application/json");
|
|
1782
|
-
debugMsg = (0, import_js_format18.format)(
|
|
1785
|
+
debugMsg = (0, import_js_format18.format)(
|
|
1786
|
+
"%v has been sent as JSON.",
|
|
1787
|
+
toPascalCase(typeof data)
|
|
1788
|
+
);
|
|
1783
1789
|
data = JSON.stringify(data);
|
|
1784
1790
|
}
|
|
1785
1791
|
break;
|
|
1786
1792
|
default:
|
|
1787
1793
|
response.setHeader("content-type", "text/plain");
|
|
1788
|
-
debugMsg = "
|
|
1794
|
+
debugMsg = "Response data has been sent as plain text.";
|
|
1789
1795
|
data = String(data);
|
|
1790
1796
|
break;
|
|
1791
1797
|
}
|
|
@@ -1853,7 +1859,7 @@ var _ErrorSender = class _ErrorSender extends DebuggableService {
|
|
|
1853
1859
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
1854
1860
|
response.end(JSON.stringify(body, null, 2), "utf-8");
|
|
1855
1861
|
debug(
|
|
1856
|
-
"
|
|
1862
|
+
"%s error has been sent for the request %s %v.",
|
|
1857
1863
|
statusCode,
|
|
1858
1864
|
request.method,
|
|
1859
1865
|
getRequestPathname(request)
|
|
@@ -1872,7 +1878,7 @@ var _ErrorSender = class _ErrorSender extends DebuggableService {
|
|
|
1872
1878
|
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
1873
1879
|
response.end("404 Not Found", "utf-8");
|
|
1874
1880
|
debug(
|
|
1875
|
-
"
|
|
1881
|
+
"404 error has been sent for the request %s %v.",
|
|
1876
1882
|
request.method,
|
|
1877
1883
|
getRequestPathname(request)
|
|
1878
1884
|
);
|
|
@@ -2070,8 +2076,8 @@ var _RouterBranch = class _RouterBranch extends DebuggableService {
|
|
|
2070
2076
|
validateRouterBranchDefinition(branchDef);
|
|
2071
2077
|
this._definition = cloneDeep(branchDef);
|
|
2072
2078
|
}
|
|
2073
|
-
this.ctorDebug("
|
|
2074
|
-
this.ctorDebug("Branch path
|
|
2079
|
+
this.ctorDebug("Created a branch %v.", normalizePath(branchDef.path, true));
|
|
2080
|
+
this.ctorDebug("Branch path is %v.", this._definition.path);
|
|
2075
2081
|
}
|
|
2076
2082
|
/**
|
|
2077
2083
|
* Define route.
|
|
@@ -2188,14 +2194,14 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
|
|
|
2188
2194
|
async _handleRequest(request, response) {
|
|
2189
2195
|
const debug = this.getDebuggerFor(this._handleRequest);
|
|
2190
2196
|
const requestPath = getRequestPathname(request);
|
|
2191
|
-
debug(
|
|
2192
|
-
"Preparing to handle an incoming request %s %v.",
|
|
2193
|
-
request.method,
|
|
2194
|
-
requestPath
|
|
2195
|
-
);
|
|
2197
|
+
debug("Handling an incoming request %s %v.", request.method, requestPath);
|
|
2196
2198
|
const resolved = this.getService(RouteRegistry).matchRouteByRequest(request);
|
|
2197
2199
|
if (!resolved) {
|
|
2198
|
-
debug(
|
|
2200
|
+
debug(
|
|
2201
|
+
"No route found for the request %s %v.",
|
|
2202
|
+
request.method,
|
|
2203
|
+
requestPath
|
|
2204
|
+
);
|
|
2199
2205
|
this.getService(ErrorSender).send404(request, response);
|
|
2200
2206
|
} else {
|
|
2201
2207
|
const { route, params } = resolved;
|
|
@@ -2348,5 +2354,6 @@ var TrieRouter = _TrieRouter;
|
|
|
2348
2354
|
parseCookieString,
|
|
2349
2355
|
parseJsonBody,
|
|
2350
2356
|
toCamelCase,
|
|
2357
|
+
toPascalCase,
|
|
2351
2358
|
validateRouteDefinition
|
|
2352
2359
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e22m4u/js-trie-router",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
|
|
5
5
|
"author": "Mikhail Evstropov <e22m4u@yandex.ru>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"statuses": "~2.0.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@commitlint/cli": "~20.
|
|
51
|
-
"@commitlint/config-conventional": "~20.
|
|
50
|
+
"@commitlint/cli": "~20.3.0",
|
|
51
|
+
"@commitlint/config-conventional": "~20.3.0",
|
|
52
52
|
"@eslint/js": "~9.39.2",
|
|
53
53
|
"@types/chai": "~5.2.3",
|
|
54
54
|
"@types/chai-as-promised": "~8.0.2",
|
|
@@ -120,8 +120,8 @@ export class RouterBranch extends DebuggableService {
|
|
|
120
120
|
validateRouterBranchDefinition(branchDef);
|
|
121
121
|
this._definition = cloneDeep(branchDef);
|
|
122
122
|
}
|
|
123
|
-
this.ctorDebug('
|
|
124
|
-
this.ctorDebug('Branch path
|
|
123
|
+
this.ctorDebug('Created a branch %v.', normalizePath(branchDef.path, true));
|
|
124
|
+
this.ctorDebug('Branch path is %v.', this._definition.path);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
createError,
|
|
8
8
|
parseContentType,
|
|
9
9
|
fetchRequestBody,
|
|
10
|
+
getRequestPathname,
|
|
10
11
|
} from '../utils/index.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -113,9 +114,14 @@ export class BodyParser extends DebuggableService {
|
|
|
113
114
|
*/
|
|
114
115
|
parse(request) {
|
|
115
116
|
const debug = this.getDebuggerFor(this.parse);
|
|
117
|
+
debug(
|
|
118
|
+
'Parsing a request body %s %v.',
|
|
119
|
+
request.method.toUpperCase(),
|
|
120
|
+
getRequestPathname(request),
|
|
121
|
+
);
|
|
116
122
|
if (!METHODS_WITH_BODY.includes(request.method.toUpperCase())) {
|
|
117
123
|
debug(
|
|
118
|
-
'
|
|
124
|
+
'Skipping body parsing for the %s method.',
|
|
119
125
|
request.method.toUpperCase(),
|
|
120
126
|
);
|
|
121
127
|
return;
|
|
@@ -125,9 +131,7 @@ export class BodyParser extends DebuggableService {
|
|
|
125
131
|
'$1',
|
|
126
132
|
);
|
|
127
133
|
if (!contentType) {
|
|
128
|
-
debug(
|
|
129
|
-
'Body parsing was skipped because the request had no content type.',
|
|
130
|
-
);
|
|
134
|
+
debug('Skipping body parsing because no content type is provided.');
|
|
131
135
|
return;
|
|
132
136
|
}
|
|
133
137
|
const {mediaType} = parseContentType(contentType);
|
|
@@ -140,7 +144,7 @@ export class BodyParser extends DebuggableService {
|
|
|
140
144
|
const parser = this._parsers[mediaType];
|
|
141
145
|
if (!parser) {
|
|
142
146
|
if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
|
|
143
|
-
debug('
|
|
147
|
+
debug('Skipping body parsing for the media type %v.', mediaType);
|
|
144
148
|
return;
|
|
145
149
|
}
|
|
146
150
|
throw createError(
|
|
@@ -150,10 +154,14 @@ export class BodyParser extends DebuggableService {
|
|
|
150
154
|
);
|
|
151
155
|
}
|
|
152
156
|
const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
|
|
157
|
+
debug('Fetching a request body.');
|
|
158
|
+
debug('Body limit is %v bytes.', bodyBytesLimit);
|
|
153
159
|
return fetchRequestBody(request, bodyBytesLimit).then(rawBody => {
|
|
154
160
|
if (rawBody != null) {
|
|
161
|
+
debug('Read %v bytes.', Buffer.byteLength(rawBody, 'utf8'));
|
|
155
162
|
return parser(rawBody);
|
|
156
163
|
}
|
|
164
|
+
debug('Request body has no content.');
|
|
157
165
|
return rawBody;
|
|
158
166
|
});
|
|
159
167
|
}
|
|
@@ -18,11 +18,11 @@ export class CookiesParser extends DebuggableService {
|
|
|
18
18
|
const cookiesKeys = Object.keys(cookies);
|
|
19
19
|
if (cookiesKeys.length) {
|
|
20
20
|
cookiesKeys.forEach(key => {
|
|
21
|
-
debug('
|
|
21
|
+
debug('Found a cookie %v with a value %v.', key, cookies[key]);
|
|
22
22
|
});
|
|
23
23
|
} else {
|
|
24
24
|
debug(
|
|
25
|
-
'
|
|
25
|
+
'Request %s %v had no cookies.',
|
|
26
26
|
request.method,
|
|
27
27
|
getRequestPathname(request),
|
|
28
28
|
);
|
|
@@ -19,11 +19,11 @@ export class QueryParser extends DebuggableService {
|
|
|
19
19
|
const queryKeys = Object.keys(query);
|
|
20
20
|
if (queryKeys.length) {
|
|
21
21
|
queryKeys.forEach(key => {
|
|
22
|
-
debug('
|
|
22
|
+
debug('Found a query parameter %v with a value %v.', key, query[key]);
|
|
23
23
|
});
|
|
24
24
|
} else {
|
|
25
25
|
debug(
|
|
26
|
-
'
|
|
26
|
+
'Request %s %v had no query parameters.',
|
|
27
27
|
request.method,
|
|
28
28
|
getRequestPathname(request),
|
|
29
29
|
);
|
|
@@ -55,7 +55,7 @@ export class RequestParser extends DebuggableService {
|
|
|
55
55
|
} else {
|
|
56
56
|
data.body = parsedBody;
|
|
57
57
|
}
|
|
58
|
-
//
|
|
58
|
+
// чтобы предотвратить модификацию
|
|
59
59
|
// заголовков, возвращаем их копию
|
|
60
60
|
data.headers = Object.assign({}, request.headers);
|
|
61
61
|
// если имеются асинхронные операции, то результат
|
package/src/route/route.js
CHANGED
|
@@ -147,7 +147,7 @@ export class Route extends Debuggable {
|
|
|
147
147
|
this._hookRegistry.addHook(RouterHookType.POST_HANDLER, hook);
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
|
-
this.ctorDebug('
|
|
150
|
+
this.ctorDebug('Created a route %s %v.', this.method, this.path);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/**
|
|
@@ -159,11 +159,7 @@ export class Route extends Debuggable {
|
|
|
159
159
|
handle(context) {
|
|
160
160
|
const debug = this.getDebuggerFor(this.handle);
|
|
161
161
|
const requestPath = getRequestPathname(context.request);
|
|
162
|
-
debug(
|
|
163
|
-
'Invoking the Route handler for the request %s %v.',
|
|
164
|
-
this.method,
|
|
165
|
-
requestPath,
|
|
166
|
-
);
|
|
162
|
+
debug('Invoking a route handler %s %v.', this.method, requestPath);
|
|
167
163
|
return this.handler(context);
|
|
168
164
|
}
|
|
169
165
|
}
|
package/src/route-registry.js
CHANGED
|
@@ -43,11 +43,7 @@ export class RouteRegistry extends DebuggableService {
|
|
|
43
43
|
const route = new Route(routeDef);
|
|
44
44
|
const triePath = `${route.method}/${route.path}`;
|
|
45
45
|
this._trie.add(triePath, route);
|
|
46
|
-
debug(
|
|
47
|
-
'The route %s %v was registered.',
|
|
48
|
-
route.method.toUpperCase(),
|
|
49
|
-
route.path,
|
|
50
|
-
);
|
|
46
|
+
debug('Registered a route %s %v.', route.method.toUpperCase(), route.path);
|
|
51
47
|
return route;
|
|
52
48
|
}
|
|
53
49
|
|
|
@@ -61,7 +57,7 @@ export class RouteRegistry extends DebuggableService {
|
|
|
61
57
|
const debug = this.getDebuggerFor(this.matchRouteByRequest);
|
|
62
58
|
const requestPath = getRequestPathname(request);
|
|
63
59
|
debug(
|
|
64
|
-
'Matching routes
|
|
60
|
+
'Matching routes for the request %s %v.',
|
|
65
61
|
request.method.toUpperCase(),
|
|
66
62
|
requestPath,
|
|
67
63
|
);
|
|
@@ -72,16 +68,12 @@ export class RouteRegistry extends DebuggableService {
|
|
|
72
68
|
const resolved = this._trie.match(triePath);
|
|
73
69
|
if (resolved) {
|
|
74
70
|
const route = resolved.value;
|
|
75
|
-
debug(
|
|
76
|
-
'The route %s %v was matched.',
|
|
77
|
-
route.method.toUpperCase(),
|
|
78
|
-
route.path,
|
|
79
|
-
);
|
|
71
|
+
debug('Matched route is %s %v.', route.method.toUpperCase(), route.path);
|
|
80
72
|
const paramNames = Object.keys(resolved.params);
|
|
81
73
|
if (paramNames.length) {
|
|
82
74
|
paramNames.forEach(name => {
|
|
83
75
|
debug(
|
|
84
|
-
'
|
|
76
|
+
'Found a path parameter %v with a value %v.',
|
|
85
77
|
name,
|
|
86
78
|
resolved.params[name],
|
|
87
79
|
);
|
|
@@ -92,7 +84,7 @@ export class RouteRegistry extends DebuggableService {
|
|
|
92
84
|
return {route, params: resolved.params};
|
|
93
85
|
}
|
|
94
86
|
debug(
|
|
95
|
-
'No
|
|
87
|
+
'No route found for the request %s %v.',
|
|
96
88
|
request.method.toUpperCase(),
|
|
97
89
|
requestPath,
|
|
98
90
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {format} from '@e22m4u/js-format';
|
|
2
|
-
import {isReadableStream} from '../utils/index.js';
|
|
3
2
|
import {DebuggableService} from '../debuggable-service.js';
|
|
3
|
+
import {isReadableStream, toPascalCase} from '../utils/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Data sender.
|
|
@@ -20,10 +20,7 @@ export class DataSender extends DebuggableService {
|
|
|
20
20
|
// заголовки, то считаем, что контроллер
|
|
21
21
|
// уже отправил ответ самостоятельно
|
|
22
22
|
if (data === response || response.headersSent) {
|
|
23
|
-
debug(
|
|
24
|
-
'Response sending was skipped because ' +
|
|
25
|
-
'its headers where sent already.',
|
|
26
|
-
);
|
|
23
|
+
debug('Skipping response because headers have already been sent.');
|
|
27
24
|
return;
|
|
28
25
|
}
|
|
29
26
|
// если ответ контроллера пуст, то отправляем
|
|
@@ -31,7 +28,7 @@ export class DataSender extends DebuggableService {
|
|
|
31
28
|
if (data == null) {
|
|
32
29
|
response.statusCode = 204;
|
|
33
30
|
response.end();
|
|
34
|
-
debug('
|
|
31
|
+
debug('Empty response has been sent.');
|
|
35
32
|
return;
|
|
36
33
|
}
|
|
37
34
|
// если ответ контроллера является стримом,
|
|
@@ -39,7 +36,7 @@ export class DataSender extends DebuggableService {
|
|
|
39
36
|
if (isReadableStream(data)) {
|
|
40
37
|
response.setHeader('Content-Type', 'application/octet-stream');
|
|
41
38
|
data.pipe(response);
|
|
42
|
-
debug('
|
|
39
|
+
debug('Sending response with a Stream.');
|
|
43
40
|
return;
|
|
44
41
|
}
|
|
45
42
|
// подготовка данных перед отправкой, и установка
|
|
@@ -53,16 +50,19 @@ export class DataSender extends DebuggableService {
|
|
|
53
50
|
// тип Buffer отправляется
|
|
54
51
|
// как бинарные данные
|
|
55
52
|
response.setHeader('content-type', 'application/octet-stream');
|
|
56
|
-
debugMsg = '
|
|
53
|
+
debugMsg = 'Buffer has been sent as binary data.';
|
|
57
54
|
} else {
|
|
58
55
|
response.setHeader('content-type', 'application/json');
|
|
59
|
-
debugMsg = format(
|
|
56
|
+
debugMsg = format(
|
|
57
|
+
'%v has been sent as JSON.',
|
|
58
|
+
toPascalCase(typeof data),
|
|
59
|
+
);
|
|
60
60
|
data = JSON.stringify(data);
|
|
61
61
|
}
|
|
62
62
|
break;
|
|
63
63
|
default:
|
|
64
64
|
response.setHeader('content-type', 'text/plain');
|
|
65
|
-
debugMsg = '
|
|
65
|
+
debugMsg = 'Response data has been sent as plain text.';
|
|
66
66
|
data = String(data);
|
|
67
67
|
break;
|
|
68
68
|
}
|
|
@@ -66,7 +66,7 @@ export class ErrorSender extends DebuggableService {
|
|
|
66
66
|
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
67
67
|
response.end(JSON.stringify(body, null, 2), 'utf-8');
|
|
68
68
|
debug(
|
|
69
|
-
'
|
|
69
|
+
'%s error has been sent for the request %s %v.',
|
|
70
70
|
statusCode,
|
|
71
71
|
request.method,
|
|
72
72
|
getRequestPathname(request),
|
|
@@ -86,7 +86,7 @@ export class ErrorSender extends DebuggableService {
|
|
|
86
86
|
response.setHeader('content-type', 'text/plain; charset=utf-8');
|
|
87
87
|
response.end('404 Not Found', 'utf-8');
|
|
88
88
|
debug(
|
|
89
|
-
'
|
|
89
|
+
'404 error has been sent for the request %s %v.',
|
|
90
90
|
request.method,
|
|
91
91
|
getRequestPathname(request),
|
|
92
92
|
);
|
package/src/trie-router.js
CHANGED
|
@@ -99,20 +99,20 @@ export class TrieRouter extends DebuggableService {
|
|
|
99
99
|
async _handleRequest(request, response) {
|
|
100
100
|
const debug = this.getDebuggerFor(this._handleRequest);
|
|
101
101
|
const requestPath = getRequestPathname(request);
|
|
102
|
-
debug(
|
|
103
|
-
'Preparing to handle an incoming request %s %v.',
|
|
104
|
-
request.method,
|
|
105
|
-
requestPath,
|
|
106
|
-
);
|
|
102
|
+
debug('Handling an incoming request %s %v.', request.method, requestPath);
|
|
107
103
|
const resolved =
|
|
108
104
|
this.getService(RouteRegistry).matchRouteByRequest(request);
|
|
109
105
|
if (!resolved) {
|
|
110
|
-
debug(
|
|
106
|
+
debug(
|
|
107
|
+
'No route found for the request %s %v.',
|
|
108
|
+
request.method,
|
|
109
|
+
requestPath,
|
|
110
|
+
);
|
|
111
111
|
this.getService(ErrorSender).send404(request, response);
|
|
112
112
|
} else {
|
|
113
113
|
const {route, params} = resolved;
|
|
114
114
|
// создание дочернего сервис-контейнера для передачи
|
|
115
|
-
// в контекст запроса,
|
|
115
|
+
// в контекст запроса, чтобы родительский контекст
|
|
116
116
|
// нельзя было модифицировать
|
|
117
117
|
const container = new ServiceContainer(this.container);
|
|
118
118
|
const context = new RequestContext(container, request, response, route);
|
|
@@ -137,7 +137,7 @@ export class TrieRouter extends DebuggableService {
|
|
|
137
137
|
// записывается в контекст передаваемый обработчику
|
|
138
138
|
const reqDataOrPromise = this.getService(RequestParser).parse(request);
|
|
139
139
|
// результат разбора может являться асинхронным, и вместо
|
|
140
|
-
// того,
|
|
140
|
+
// того, чтобы разрывать поток выполнения, стоит проверить,
|
|
141
141
|
// действительно ли необходимо использование оператора "await"
|
|
142
142
|
if (isPromise(reqDataOrPromise)) {
|
|
143
143
|
const reqData = await reqDataOrPromise;
|
package/src/utils/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './merge-deep.js';
|
|
|
3
3
|
export * from './is-promise.js';
|
|
4
4
|
export * from './create-error.js';
|
|
5
5
|
export * from './to-camel-case.js';
|
|
6
|
+
export * from './to-pascal-case.js';
|
|
6
7
|
export * from './normalize-path.js';
|
|
7
8
|
export * from './is-response-sent.js';
|
|
8
9
|
export * from './create-route-mock.js';
|
package/src/utils/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from './merge-deep.js';
|
|
|
3
3
|
export * from './is-promise.js';
|
|
4
4
|
export * from './create-error.js';
|
|
5
5
|
export * from './to-camel-case.js';
|
|
6
|
+
export * from './to-pascal-case.js';
|
|
6
7
|
export * from './normalize-path.js';
|
|
7
8
|
export * from './is-response-sent.js';
|
|
8
9
|
export * from './create-route-mock.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* To pascal case.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} input
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function toPascalCase(input) {
|
|
8
|
+
if (!input) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
return (
|
|
12
|
+
input
|
|
13
|
+
// splits camelCase words into separate words
|
|
14
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
15
|
+
// splits numbers and words
|
|
16
|
+
.replace(/([0-9])([a-zA-Z])/g, '$1 $2')
|
|
17
|
+
// replaces dashes, underscores, and special characters with spaces
|
|
18
|
+
.replace(/[-_]+|[^\p{L}\p{N}]/gu, ' ')
|
|
19
|
+
// converts the entire string to lowercase
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
// capitalizes the first letter of each word
|
|
22
|
+
.replace(/(?:^|\s)(\p{L})/gu, (_, letter) => letter.toUpperCase())
|
|
23
|
+
// removes all spaces
|
|
24
|
+
.replace(/\s+/g, '')
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {expect} from 'chai';
|
|
2
|
+
import {toPascalCase} from './to-pascal-case.js';
|
|
3
|
+
|
|
4
|
+
describe('toPascalCase', function () {
|
|
5
|
+
it('returns a PascalCase string', function () {
|
|
6
|
+
expect(toPascalCase('hello world')).to.be.eq('HelloWorld');
|
|
7
|
+
expect(toPascalCase('snake_case')).to.be.eq('SnakeCase');
|
|
8
|
+
expect(toPascalCase('kebab-case')).to.be.eq('KebabCase');
|
|
9
|
+
expect(toPascalCase('alreadyCamel')).to.be.eq('AlreadyCamel');
|
|
10
|
+
expect(toPascalCase('AlreadyPascal')).to.be.eq('AlreadyPascal');
|
|
11
|
+
expect(toPascalCase(' single word ')).to.be.eq('SingleWord');
|
|
12
|
+
expect(toPascalCase('')).to.be.eq('');
|
|
13
|
+
expect(toPascalCase('1number')).to.be.eq('1Number');
|
|
14
|
+
});
|
|
15
|
+
});
|