@coherent.js/api 1.0.0-beta.3 → 1.0.0-beta.6
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/index.cjs +180 -52
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +180 -52
- package/dist/index.js.map +3 -3
- package/package.json +26 -2
- package/types/index.d.ts +188 -38
- package/dist/api/errors.d.ts +0 -92
- package/dist/api/errors.d.ts.map +0 -1
- package/dist/api/errors.js +0 -161
- package/dist/api/errors.js.map +0 -1
- package/dist/api/index.d.ts +0 -61
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/index.js +0 -41
- package/dist/api/index.js.map +0 -1
- package/dist/api/middleware.d.ts +0 -57
- package/dist/api/middleware.d.ts.map +0 -1
- package/dist/api/middleware.js +0 -244
- package/dist/api/middleware.js.map +0 -1
- package/dist/api/openapi.d.ts +0 -54
- package/dist/api/openapi.d.ts.map +0 -1
- package/dist/api/openapi.js +0 -144
- package/dist/api/openapi.js.map +0 -1
- package/dist/api/router.d.ts +0 -30
- package/dist/api/router.d.ts.map +0 -1
- package/dist/api/router.js +0 -1476
- package/dist/api/router.js.map +0 -1
- package/dist/api/security.d.ts +0 -64
- package/dist/api/security.d.ts.map +0 -1
- package/dist/api/security.js +0 -239
- package/dist/api/security.js.map +0 -1
- package/dist/api/serialization.d.ts +0 -86
- package/dist/api/serialization.d.ts.map +0 -1
- package/dist/api/serialization.js +0 -151
- package/dist/api/serialization.js.map +0 -1
- package/dist/api/validation.d.ts +0 -34
- package/dist/api/validation.d.ts.map +0 -1
- package/dist/api/validation.js +0 -172
- package/dist/api/validation.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
|
|
4
4
|
// src/errors.js
|
|
5
|
+
import { env } from "node:process";
|
|
5
6
|
var ApiError = class _ApiError extends Error {
|
|
6
7
|
/**
|
|
7
8
|
* Create an API _error
|
|
@@ -108,7 +109,7 @@ function createErrorHandler() {
|
|
|
108
109
|
if (_error.details) {
|
|
109
110
|
response.details = _error.details;
|
|
110
111
|
}
|
|
111
|
-
if (
|
|
112
|
+
if (env.NODE_ENV === "development") {
|
|
112
113
|
response.stack = _error.stack;
|
|
113
114
|
}
|
|
114
115
|
res.status(response.statusCode).json(response);
|
|
@@ -140,8 +141,10 @@ function validateAgainstSchema(schema, data) {
|
|
|
140
141
|
for (const [field, fieldSchema] of Object.entries(schema.properties)) {
|
|
141
142
|
if (field in data) {
|
|
142
143
|
const fieldValue = data[field];
|
|
143
|
-
const
|
|
144
|
-
|
|
144
|
+
const fieldResult = validateField(fieldSchema, fieldValue, field);
|
|
145
|
+
if (!fieldResult.valid) {
|
|
146
|
+
errors.push(...fieldResult.errors);
|
|
147
|
+
}
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
150
|
}
|
|
@@ -153,6 +156,15 @@ function validateAgainstSchema(schema, data) {
|
|
|
153
156
|
}
|
|
154
157
|
function validateField(schema, value, fieldName) {
|
|
155
158
|
const errors = [];
|
|
159
|
+
if (value === null || value === void 0) {
|
|
160
|
+
if (schema.type && schema.type !== "null") {
|
|
161
|
+
errors.push({
|
|
162
|
+
field: fieldName,
|
|
163
|
+
message: `Expected ${schema.type}, got ${value === null ? "null" : "undefined"}`
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return { valid: errors.length === 0, errors };
|
|
167
|
+
}
|
|
156
168
|
if (schema.type) {
|
|
157
169
|
if (schema.type === "string" && typeof value !== "string") {
|
|
158
170
|
errors.push({
|
|
@@ -176,7 +188,7 @@ function validateField(schema, value, fieldName) {
|
|
|
176
188
|
});
|
|
177
189
|
}
|
|
178
190
|
}
|
|
179
|
-
if (schema.type === "string") {
|
|
191
|
+
if (schema.type === "string" && typeof value === "string") {
|
|
180
192
|
if (schema.minLength && value.length < schema.minLength) {
|
|
181
193
|
errors.push({
|
|
182
194
|
field: fieldName,
|
|
@@ -196,7 +208,7 @@ function validateField(schema, value, fieldName) {
|
|
|
196
208
|
});
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
|
-
if (schema.type === "number") {
|
|
211
|
+
if (schema.type === "number" && typeof value === "number") {
|
|
200
212
|
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
201
213
|
errors.push({
|
|
202
214
|
field: fieldName,
|
|
@@ -210,7 +222,7 @@ function validateField(schema, value, fieldName) {
|
|
|
210
222
|
});
|
|
211
223
|
}
|
|
212
224
|
}
|
|
213
|
-
return errors;
|
|
225
|
+
return { valid: errors.length === 0, errors };
|
|
214
226
|
}
|
|
215
227
|
function withValidation(schema) {
|
|
216
228
|
return (req, res, next) => {
|
|
@@ -440,6 +452,9 @@ function registerRoute(method, config, router, path) {
|
|
|
440
452
|
}
|
|
441
453
|
}, { name });
|
|
442
454
|
}
|
|
455
|
+
function isStaticRoute(pattern) {
|
|
456
|
+
return !pattern.includes(":") && !pattern.includes("*") && !pattern.includes("(") && !pattern.includes("?");
|
|
457
|
+
}
|
|
443
458
|
var SimpleRouter = class {
|
|
444
459
|
constructor(options = {}) {
|
|
445
460
|
this.routes = [];
|
|
@@ -451,6 +466,7 @@ var SimpleRouter = class {
|
|
|
451
466
|
this.enableCompilation = options.enableCompilation !== false;
|
|
452
467
|
this.compiledRoutes = /* @__PURE__ */ new Map();
|
|
453
468
|
this.routeCompilationCache = /* @__PURE__ */ new Map();
|
|
469
|
+
this.maxCompilationCacheSize = options.maxCompilationCacheSize || 1e3;
|
|
454
470
|
this.enableVersioning = options.enableVersioning || false;
|
|
455
471
|
this.defaultVersion = options.defaultVersion || "v1";
|
|
456
472
|
this.versionHeader = options.versionHeader || "api-version";
|
|
@@ -479,10 +495,15 @@ var SimpleRouter = class {
|
|
|
479
495
|
// Track WebSocket messages
|
|
480
496
|
};
|
|
481
497
|
}
|
|
498
|
+
this.enableSecurityHeaders = options.enableSecurityHeaders !== false;
|
|
499
|
+
this.enableCORS = options.enableCORS !== false;
|
|
500
|
+
this.enableSmartRouting = options.enableSmartRouting !== false;
|
|
501
|
+
this.staticRoutes = /* @__PURE__ */ new Map();
|
|
502
|
+
this.enableRouteMetrics = options.enableRouteMetrics || false;
|
|
482
503
|
}
|
|
483
504
|
/**
|
|
484
505
|
* Add an HTTP route to the router
|
|
485
|
-
*
|
|
506
|
+
*
|
|
486
507
|
* @param {string} method - HTTP method (GET, POST, PUT, DELETE, PATCH)
|
|
487
508
|
* @param {string} path - Route path pattern (supports :param and wildcards)
|
|
488
509
|
* @param {Function} handler - Route handler function
|
|
@@ -490,7 +511,7 @@ var SimpleRouter = class {
|
|
|
490
511
|
* @param {Array} [options.middleware] - Route-specific middleware
|
|
491
512
|
* @param {string} [options.name] - Named route for URL generation
|
|
492
513
|
* @param {string} [options.version] - API version for this route
|
|
493
|
-
*
|
|
514
|
+
*
|
|
494
515
|
* @example
|
|
495
516
|
* router.addRoute('GET', '/users/:id', (req, res) => {
|
|
496
517
|
* return { user: { id: req.params.id } };
|
|
@@ -513,6 +534,11 @@ var SimpleRouter = class {
|
|
|
513
534
|
if (this.enableCompilation) {
|
|
514
535
|
route.compiled = this.compileRoute(fullPath);
|
|
515
536
|
}
|
|
537
|
+
if (this.enableSmartRouting && isStaticRoute(fullPath)) {
|
|
538
|
+
const staticKey = `${route.method}:${fullPath}`;
|
|
539
|
+
this.staticRoutes.set(staticKey, route);
|
|
540
|
+
route.isStatic = true;
|
|
541
|
+
}
|
|
516
542
|
this.routes.push(route);
|
|
517
543
|
if (this.enableVersioning) {
|
|
518
544
|
if (!this.versionedRoutes.has(route.version)) {
|
|
@@ -826,7 +852,7 @@ var SimpleRouter = class {
|
|
|
826
852
|
}
|
|
827
853
|
});
|
|
828
854
|
socket.on("_error", (err) => {
|
|
829
|
-
console.
|
|
855
|
+
console.error("WebSocket socket _error (connection likely closed):", err.code);
|
|
830
856
|
});
|
|
831
857
|
return ws;
|
|
832
858
|
}
|
|
@@ -885,7 +911,7 @@ var SimpleRouter = class {
|
|
|
885
911
|
try {
|
|
886
912
|
ws.send(message);
|
|
887
913
|
} catch {
|
|
888
|
-
console.
|
|
914
|
+
console.error("Failed to send message to connection:", id);
|
|
889
915
|
}
|
|
890
916
|
}
|
|
891
917
|
}
|
|
@@ -923,16 +949,16 @@ var SimpleRouter = class {
|
|
|
923
949
|
}
|
|
924
950
|
/**
|
|
925
951
|
* Generate URL for named route with parameter substitution
|
|
926
|
-
*
|
|
952
|
+
*
|
|
927
953
|
* @param {string} name - Route name (set during route registration)
|
|
928
954
|
* @param {Object} [params={}] - Parameters to substitute in the URL pattern
|
|
929
955
|
* @returns {string} Generated URL with parameters substituted
|
|
930
956
|
* @throws {Error} If named route is not found
|
|
931
|
-
*
|
|
957
|
+
*
|
|
932
958
|
* @example
|
|
933
959
|
* // Route registered as: router.addRoute('GET', '/users/:id', handler, { name: 'getUser' })
|
|
934
960
|
* const url = router.url('getUser', { id: 123 }); // '/users/123'
|
|
935
|
-
*
|
|
961
|
+
*
|
|
936
962
|
* // With constrained parameters
|
|
937
963
|
* const url = router.url('getUserPosts', { userId: 123, postId: 456 }); // '/users/123/posts/456'
|
|
938
964
|
*/
|
|
@@ -950,11 +976,11 @@ var SimpleRouter = class {
|
|
|
950
976
|
}
|
|
951
977
|
/**
|
|
952
978
|
* Add routes from configuration object
|
|
953
|
-
*
|
|
979
|
+
*
|
|
954
980
|
* @param {Object} routeConfig - Route configuration object with nested structure
|
|
955
981
|
* @description Processes nested route objects and registers HTTP and WebSocket routes.
|
|
956
982
|
* Supports declarative route definition with automatic method detection.
|
|
957
|
-
*
|
|
983
|
+
*
|
|
958
984
|
* @example
|
|
959
985
|
* router.addRoutes({
|
|
960
986
|
* 'api': {
|
|
@@ -970,17 +996,17 @@ var SimpleRouter = class {
|
|
|
970
996
|
}
|
|
971
997
|
/**
|
|
972
998
|
* Add global middleware to the router
|
|
973
|
-
*
|
|
999
|
+
*
|
|
974
1000
|
* @param {Function|Object} middleware - Middleware function or conditional middleware object
|
|
975
1001
|
* @description Adds middleware that runs before all route handlers. Supports both
|
|
976
1002
|
* simple functions and conditional middleware objects.
|
|
977
|
-
*
|
|
1003
|
+
*
|
|
978
1004
|
* @example
|
|
979
1005
|
* // Simple middleware
|
|
980
1006
|
* router.use((req, res) => {
|
|
981
1007
|
* console.log(`${req.method} ${req.url}`);
|
|
982
1008
|
* });
|
|
983
|
-
*
|
|
1009
|
+
*
|
|
984
1010
|
* // Conditional middleware
|
|
985
1011
|
* router.use({
|
|
986
1012
|
* condition: (req) => req.url.startsWith('/api'),
|
|
@@ -1110,7 +1136,10 @@ var SimpleRouter = class {
|
|
|
1110
1136
|
compileRoute(pattern) {
|
|
1111
1137
|
if (this.routeCompilationCache.has(pattern)) {
|
|
1112
1138
|
if (this.enableMetrics) this.metrics.compilationHits++;
|
|
1113
|
-
|
|
1139
|
+
const compiled2 = this.routeCompilationCache.get(pattern);
|
|
1140
|
+
this.routeCompilationCache.delete(pattern);
|
|
1141
|
+
this.routeCompilationCache.set(pattern, compiled2);
|
|
1142
|
+
return compiled2;
|
|
1114
1143
|
}
|
|
1115
1144
|
const paramNames = [];
|
|
1116
1145
|
let regexPattern = pattern;
|
|
@@ -1121,25 +1150,38 @@ var SimpleRouter = class {
|
|
|
1121
1150
|
regexPattern = regexPattern.replace(/\/\*/g, "/([^/]+)");
|
|
1122
1151
|
paramNames.push("splat");
|
|
1123
1152
|
}
|
|
1124
|
-
regexPattern = regexPattern.replace(/:([^(/]+)(\([^)]+\))?(\?)?/g, (match, paramName, constraint, optional) => {
|
|
1153
|
+
regexPattern = regexPattern.replace(/:([^(/]+)(\([^)]+\))?(\?)?/g, (match, paramName, constraint, optional, offset, fullString) => {
|
|
1125
1154
|
paramNames.push(paramName);
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1155
|
+
const hasPrecedingSlash = offset > 0 && fullString[offset - 1] === "/";
|
|
1156
|
+
if (hasPrecedingSlash) {
|
|
1157
|
+
if (constraint) {
|
|
1158
|
+
const constraintPattern = constraint.slice(1, -1);
|
|
1159
|
+
return optional ? `(?:/(?:${constraintPattern}))?` : `(${constraintPattern})`;
|
|
1160
|
+
} else {
|
|
1161
|
+
return optional ? `(?:([^/]+))?` : `([^/]+)`;
|
|
1162
|
+
}
|
|
1129
1163
|
} else {
|
|
1130
|
-
|
|
1164
|
+
if (constraint) {
|
|
1165
|
+
const constraintPattern = constraint.slice(1, -1);
|
|
1166
|
+
return optional ? `(?:/(?:${constraintPattern}))?` : `/${constraintPattern}`;
|
|
1167
|
+
} else {
|
|
1168
|
+
return optional ? `(?:/([^/]+))?` : `/([^/]+)`;
|
|
1169
|
+
}
|
|
1131
1170
|
}
|
|
1132
1171
|
});
|
|
1133
|
-
regexPattern = regexPattern.replace(/[.+?^${}
|
|
1172
|
+
regexPattern = regexPattern.replace(/([.+?^${}|\\[\]()]])/g, "\\$1");
|
|
1173
|
+
regexPattern = regexPattern.replace(/\\\[/g, "[").replace(/\\\]/g, "]").replace(/\\\^/g, "^");
|
|
1134
1174
|
regexPattern = `^${regexPattern}$`;
|
|
1135
1175
|
const compiled = {
|
|
1136
1176
|
regex: new RegExp(regexPattern),
|
|
1137
1177
|
paramNames,
|
|
1138
1178
|
pattern
|
|
1139
1179
|
};
|
|
1140
|
-
if (this.routeCompilationCache.size
|
|
1141
|
-
this.routeCompilationCache.
|
|
1180
|
+
if (this.routeCompilationCache.size >= this.maxCompilationCacheSize) {
|
|
1181
|
+
const firstKey = this.routeCompilationCache.keys().next().value;
|
|
1182
|
+
this.routeCompilationCache.delete(firstKey);
|
|
1142
1183
|
}
|
|
1184
|
+
this.routeCompilationCache.set(pattern, compiled);
|
|
1143
1185
|
return compiled;
|
|
1144
1186
|
}
|
|
1145
1187
|
/**
|
|
@@ -1315,7 +1357,15 @@ var SimpleRouter = class {
|
|
|
1315
1357
|
this.metrics.requests++;
|
|
1316
1358
|
}
|
|
1317
1359
|
const { corsOrigin, rateLimit = { windowMs: 6e4, maxRequests: 100 } } = options;
|
|
1318
|
-
|
|
1360
|
+
if (this.enableSecurityHeaders) {
|
|
1361
|
+
addSecurityHeaders(res, corsOrigin);
|
|
1362
|
+
} else if (this.enableCORS) {
|
|
1363
|
+
const origin = corsOrigin || "http://localhost:3000";
|
|
1364
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1365
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
|
1366
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
1367
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1368
|
+
}
|
|
1319
1369
|
if (req.method === "OPTIONS") {
|
|
1320
1370
|
res.writeHead(204);
|
|
1321
1371
|
res.end();
|
|
@@ -1329,7 +1379,9 @@ var SimpleRouter = class {
|
|
|
1329
1379
|
}
|
|
1330
1380
|
const parsedUrl = parseUrl(req.url, true);
|
|
1331
1381
|
const pathname = parsedUrl.pathname;
|
|
1332
|
-
req.query
|
|
1382
|
+
if (!req.query) {
|
|
1383
|
+
req.query = parsedUrl.query || {};
|
|
1384
|
+
}
|
|
1333
1385
|
try {
|
|
1334
1386
|
req.body = await parseBody(req, options.maxBodySize);
|
|
1335
1387
|
} catch (_error) {
|
|
@@ -1349,24 +1401,48 @@ var SimpleRouter = class {
|
|
|
1349
1401
|
if (this.enableMetrics && requestVersion) {
|
|
1350
1402
|
this.metrics.versionRequests.set(requestVersion, (this.metrics.versionRequests.get(requestVersion) || 0) + 1);
|
|
1351
1403
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1404
|
+
matchedRoute = null;
|
|
1405
|
+
if (this.enableSmartRouting) {
|
|
1406
|
+
const staticKey = `${req.method}:${pathname}`;
|
|
1407
|
+
const staticRoute = this.staticRoutes.get(staticKey);
|
|
1408
|
+
if (staticRoute) {
|
|
1409
|
+
if (!this.enableVersioning || staticRoute.version === requestVersion) {
|
|
1410
|
+
matchedRoute = { route: staticRoute, params: {} };
|
|
1411
|
+
if (this.enableRouteMetrics && this.enableMetrics) {
|
|
1412
|
+
if (!this.metrics.staticRouteMatches) {
|
|
1413
|
+
this.metrics.staticRouteMatches = 0;
|
|
1414
|
+
}
|
|
1415
|
+
this.metrics.staticRouteMatches++;
|
|
1416
|
+
}
|
|
1363
1417
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
if (!matchedRoute) {
|
|
1421
|
+
const routesToSearch = this.enableVersioning && this.versionedRoutes.has(requestVersion) ? this.versionedRoutes.get(requestVersion) : this.routes;
|
|
1422
|
+
for (const route of routesToSearch) {
|
|
1423
|
+
if (route.method === req.method) {
|
|
1424
|
+
if (this.enableVersioning && route.version !== requestVersion) {
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
let params = null;
|
|
1428
|
+
if (this.enableCompilation && route.compiled) {
|
|
1429
|
+
params = this.matchCompiledRoute(route.compiled, pathname);
|
|
1430
|
+
} else {
|
|
1431
|
+
params = extractParams(route.path, pathname);
|
|
1432
|
+
}
|
|
1433
|
+
if (params !== null) {
|
|
1434
|
+
matchedRoute = { route, params };
|
|
1435
|
+
if (this.enableRouteMetrics && this.enableMetrics) {
|
|
1436
|
+
if (!this.metrics.dynamicRouteMatches) {
|
|
1437
|
+
this.metrics.dynamicRouteMatches = 0;
|
|
1438
|
+
}
|
|
1439
|
+
this.metrics.dynamicRouteMatches++;
|
|
1440
|
+
}
|
|
1441
|
+
if (this.routeCache.size < this.maxCacheSize) {
|
|
1442
|
+
this.routeCache.set(cacheKey, matchedRoute);
|
|
1443
|
+
}
|
|
1444
|
+
break;
|
|
1368
1445
|
}
|
|
1369
|
-
break;
|
|
1370
1446
|
}
|
|
1371
1447
|
}
|
|
1372
1448
|
}
|
|
@@ -1377,7 +1453,6 @@ var SimpleRouter = class {
|
|
|
1377
1453
|
const routeKey = `${req.method}:${matchedRoute.route.path}`;
|
|
1378
1454
|
this.metrics.routeMatches.set(routeKey, (this.metrics.routeMatches.get(routeKey) || 0) + 1);
|
|
1379
1455
|
}
|
|
1380
|
-
console.log(`${(/* @__PURE__ */ new Date()).toISOString()} ${req.method} ${pathname}`);
|
|
1381
1456
|
try {
|
|
1382
1457
|
if (matchedRoute.route.middleware && matchedRoute.route.middleware.length > 0) {
|
|
1383
1458
|
for (const middleware of matchedRoute.route.middleware) {
|
|
@@ -1387,16 +1462,26 @@ var SimpleRouter = class {
|
|
|
1387
1462
|
}
|
|
1388
1463
|
const { route } = matchedRoute;
|
|
1389
1464
|
const result = await route.handler(req, res);
|
|
1390
|
-
if (result &&
|
|
1391
|
-
|
|
1392
|
-
|
|
1465
|
+
if (result && !res.headersSent) {
|
|
1466
|
+
if (typeof result === "object") {
|
|
1467
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1468
|
+
res.end(JSON.stringify(result));
|
|
1469
|
+
} else if (typeof result === "string") {
|
|
1470
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1471
|
+
res.end(result);
|
|
1472
|
+
}
|
|
1393
1473
|
}
|
|
1394
1474
|
if (this.enableMetrics) {
|
|
1395
1475
|
const responseTime = Date.now() - startTime;
|
|
1396
|
-
this.
|
|
1397
|
-
|
|
1398
|
-
this.metrics.responseTime = this.metrics.responseTime.slice(-1e3);
|
|
1476
|
+
if (!this._metricsLock) {
|
|
1477
|
+
this._metricsLock = Promise.resolve();
|
|
1399
1478
|
}
|
|
1479
|
+
this._metricsLock = this._metricsLock.then(() => {
|
|
1480
|
+
this.metrics.responseTime.push(responseTime);
|
|
1481
|
+
if (this.metrics.responseTime.length > 1e3) {
|
|
1482
|
+
this.metrics.responseTime = this.metrics.responseTime.slice(-1e3);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1400
1485
|
}
|
|
1401
1486
|
return;
|
|
1402
1487
|
} catch (_error) {
|
|
@@ -1440,6 +1525,49 @@ var SimpleRouter = class {
|
|
|
1440
1525
|
head(path, handler, options = {}) {
|
|
1441
1526
|
return this.addRoute("HEAD", path, handler, options);
|
|
1442
1527
|
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Convert to Express Router middleware
|
|
1530
|
+
*
|
|
1531
|
+
* @param {Object} express - Express module (required)
|
|
1532
|
+
* @returns {Function} Express-compatible router middleware
|
|
1533
|
+
*
|
|
1534
|
+
* @example
|
|
1535
|
+
* import express from 'express';
|
|
1536
|
+
* const router = createRouter();
|
|
1537
|
+
* router.get('/users', handler);
|
|
1538
|
+
* app.use('/api', router.toExpressRouter(express));
|
|
1539
|
+
*/
|
|
1540
|
+
toExpressRouter(express) {
|
|
1541
|
+
if (!express || typeof express.Router !== "function") {
|
|
1542
|
+
throw new Error("Express is required for toExpressRouter(). Pass the express module as argument: router.toExpressRouter(express)");
|
|
1543
|
+
}
|
|
1544
|
+
const expressRouter = express.Router();
|
|
1545
|
+
for (const route of this.routes) {
|
|
1546
|
+
const method = route.method.toLowerCase();
|
|
1547
|
+
const path = route.path;
|
|
1548
|
+
const handler = route.handler;
|
|
1549
|
+
const middleware = route.middleware || [];
|
|
1550
|
+
const expressHandler = async (req, res, next) => {
|
|
1551
|
+
try {
|
|
1552
|
+
for (const mw of middleware) {
|
|
1553
|
+
await new Promise((resolve, reject) => {
|
|
1554
|
+
mw(req, res, (err) => err ? reject(err) : resolve());
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
const result = await handler(req, res);
|
|
1558
|
+
if (result !== void 0 && !res.headersSent) {
|
|
1559
|
+
res.json(result);
|
|
1560
|
+
}
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
next(error);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
if (typeof expressRouter[method] === "function") {
|
|
1566
|
+
expressRouter[method](path, expressHandler);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
return expressRouter;
|
|
1570
|
+
}
|
|
1443
1571
|
};
|
|
1444
1572
|
function createRouter(routeConfig, options = {}) {
|
|
1445
1573
|
const router = new SimpleRouter(options);
|