blaizejs 0.2.2 → 0.2.3
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 +680 -264
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +685 -264
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* blaizejs v0.2.
|
|
2
|
+
* blaizejs v0.2.3
|
|
3
3
|
* A blazing-fast, TypeScript-first Node.js framework with HTTP/2 support, file-based routing, powerful middleware system, and end-to-end type safety for building modern APIs.
|
|
4
4
|
*
|
|
5
5
|
* Copyright (c) 2025 BlaizeJS Contributors
|
|
6
6
|
* @license MIT
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
|
+
}) : x)(function(x) {
|
|
12
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
|
+
});
|
|
9
15
|
|
|
10
16
|
// src/middleware/execute.ts
|
|
11
17
|
function execute(middleware, ctx, next) {
|
|
@@ -107,6 +113,9 @@ function create2(name, version, setup, defaultOptions = {}) {
|
|
|
107
113
|
};
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
// src/router/create.ts
|
|
117
|
+
import { fileURLToPath } from "node:url";
|
|
118
|
+
|
|
110
119
|
// src/config.ts
|
|
111
120
|
var config = {};
|
|
112
121
|
function setRuntimeConfig(newConfig) {
|
|
@@ -178,73 +187,75 @@ function getCallerFilePath() {
|
|
|
178
187
|
if (!fileName) {
|
|
179
188
|
throw new Error("Unable to determine caller file name");
|
|
180
189
|
}
|
|
190
|
+
if (fileName.startsWith("file://")) {
|
|
191
|
+
return fileURLToPath(fileName);
|
|
192
|
+
}
|
|
181
193
|
return fileName;
|
|
182
194
|
} finally {
|
|
183
195
|
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
184
196
|
}
|
|
185
197
|
}
|
|
186
198
|
function getRoutePath() {
|
|
187
|
-
console.log("getRoutePath called");
|
|
188
199
|
const callerPath = getCallerFilePath();
|
|
189
200
|
const routesDir = getRoutesDir();
|
|
190
201
|
const parsedRoute = parseRoutePath(callerPath, routesDir);
|
|
191
|
-
console.log(
|
|
202
|
+
console.log(`\u{1F50E} Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
|
|
192
203
|
return parsedRoute.routePath;
|
|
193
204
|
}
|
|
194
205
|
var createGetRoute = (config2) => {
|
|
195
206
|
validateMethodConfig("GET", config2);
|
|
196
|
-
const
|
|
207
|
+
const path6 = getRoutePath();
|
|
197
208
|
return {
|
|
198
209
|
GET: config2,
|
|
199
|
-
path:
|
|
210
|
+
path: path6
|
|
200
211
|
};
|
|
201
212
|
};
|
|
202
213
|
var createPostRoute = (config2) => {
|
|
203
214
|
validateMethodConfig("POST", config2);
|
|
204
|
-
const
|
|
215
|
+
const path6 = getRoutePath();
|
|
205
216
|
return {
|
|
206
217
|
POST: config2,
|
|
207
|
-
path:
|
|
218
|
+
path: path6
|
|
208
219
|
};
|
|
209
220
|
};
|
|
210
221
|
var createPutRoute = (config2) => {
|
|
211
222
|
validateMethodConfig("PUT", config2);
|
|
212
|
-
const
|
|
223
|
+
const path6 = getRoutePath();
|
|
213
224
|
return {
|
|
214
225
|
PUT: config2,
|
|
215
|
-
path:
|
|
226
|
+
path: path6
|
|
216
227
|
};
|
|
217
228
|
};
|
|
218
229
|
var createDeleteRoute = (config2) => {
|
|
219
230
|
validateMethodConfig("DELETE", config2);
|
|
220
|
-
const
|
|
231
|
+
const path6 = getRoutePath();
|
|
221
232
|
return {
|
|
222
233
|
DELETE: config2,
|
|
223
|
-
path:
|
|
234
|
+
path: path6
|
|
224
235
|
};
|
|
225
236
|
};
|
|
226
237
|
var createPatchRoute = (config2) => {
|
|
227
238
|
validateMethodConfig("PATCH", config2);
|
|
228
|
-
const
|
|
239
|
+
const path6 = getRoutePath();
|
|
229
240
|
return {
|
|
230
241
|
PATCH: config2,
|
|
231
|
-
path:
|
|
242
|
+
path: path6
|
|
232
243
|
};
|
|
233
244
|
};
|
|
234
245
|
var createHeadRoute = (config2) => {
|
|
235
246
|
validateMethodConfig("HEAD", config2);
|
|
236
|
-
const
|
|
247
|
+
const path6 = getRoutePath();
|
|
237
248
|
return {
|
|
238
249
|
HEAD: config2,
|
|
239
|
-
path:
|
|
250
|
+
path: path6
|
|
240
251
|
};
|
|
241
252
|
};
|
|
242
253
|
var createOptionsRoute = (config2) => {
|
|
243
254
|
validateMethodConfig("OPTIONS", config2);
|
|
244
|
-
const
|
|
255
|
+
const path6 = getRoutePath();
|
|
245
256
|
return {
|
|
246
257
|
OPTIONS: config2,
|
|
247
|
-
path:
|
|
258
|
+
path: path6
|
|
248
259
|
};
|
|
249
260
|
};
|
|
250
261
|
function validateMethodConfig(method, config2) {
|
|
@@ -389,7 +400,7 @@ function parseRequestUrl(req) {
|
|
|
389
400
|
const fullUrl = `${protocol}://${host}${originalUrl.startsWith("/") ? "" : "/"}${originalUrl}`;
|
|
390
401
|
try {
|
|
391
402
|
const url = new URL(fullUrl);
|
|
392
|
-
const
|
|
403
|
+
const path6 = url.pathname;
|
|
393
404
|
const query = {};
|
|
394
405
|
url.searchParams.forEach((value, key) => {
|
|
395
406
|
if (query[key] !== void 0) {
|
|
@@ -402,7 +413,7 @@ function parseRequestUrl(req) {
|
|
|
402
413
|
query[key] = value;
|
|
403
414
|
}
|
|
404
415
|
});
|
|
405
|
-
return { path:
|
|
416
|
+
return { path: path6, url, query };
|
|
406
417
|
} catch (error) {
|
|
407
418
|
console.warn(`Invalid URL: ${fullUrl}`, error);
|
|
408
419
|
throw new ParseUrlError(`Invalid URL: ${fullUrl}`);
|
|
@@ -424,7 +435,7 @@ function getProtocol(req) {
|
|
|
424
435
|
return encrypted ? "https" : "http";
|
|
425
436
|
}
|
|
426
437
|
async function createContext(req, res, options = {}) {
|
|
427
|
-
const { path:
|
|
438
|
+
const { path: path6, url, query } = parseRequestUrl(req);
|
|
428
439
|
const method = req.method || "GET";
|
|
429
440
|
const isHttp2 = isHttp2Request(req);
|
|
430
441
|
const protocol = getProtocol(req);
|
|
@@ -433,7 +444,7 @@ async function createContext(req, res, options = {}) {
|
|
|
433
444
|
const responseState = { sent: false };
|
|
434
445
|
const ctx = {
|
|
435
446
|
request: createRequestObject(req, {
|
|
436
|
-
path:
|
|
447
|
+
path: path6,
|
|
437
448
|
url,
|
|
438
449
|
query,
|
|
439
450
|
params,
|
|
@@ -705,13 +716,13 @@ function setBodyError(ctx, type, message, error) {
|
|
|
705
716
|
ctx.state._bodyError = { type, message, error };
|
|
706
717
|
}
|
|
707
718
|
async function readRequestBody(req) {
|
|
708
|
-
return new Promise((
|
|
719
|
+
return new Promise((resolve3, reject) => {
|
|
709
720
|
const chunks = [];
|
|
710
721
|
req.on("data", (chunk) => {
|
|
711
722
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
712
723
|
});
|
|
713
724
|
req.on("end", () => {
|
|
714
|
-
|
|
725
|
+
resolve3(Buffer.concat(chunks).toString("utf8"));
|
|
715
726
|
});
|
|
716
727
|
req.on("error", (err) => {
|
|
717
728
|
reject(err);
|
|
@@ -809,7 +820,7 @@ function createServerInstance(isHttp2, certOptions) {
|
|
|
809
820
|
return http2.createSecureServer(http2ServerOptions);
|
|
810
821
|
}
|
|
811
822
|
function listenOnPort(server, port, host, isHttp2) {
|
|
812
|
-
return new Promise((
|
|
823
|
+
return new Promise((resolve3, reject) => {
|
|
813
824
|
server.listen(port, host, () => {
|
|
814
825
|
const protocol = isHttp2 ? "https" : "http";
|
|
815
826
|
const url = `${protocol}://${host}:${port}`;
|
|
@@ -826,7 +837,7 @@ function listenOnPort(server, port, host, isHttp2) {
|
|
|
826
837
|
|
|
827
838
|
\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}\u{1F525}
|
|
828
839
|
`);
|
|
829
|
-
|
|
840
|
+
resolve3();
|
|
830
841
|
});
|
|
831
842
|
server.on("error", (err) => {
|
|
832
843
|
console.error("Server error:", err);
|
|
@@ -870,55 +881,128 @@ async function startServer(serverInstance, serverOptions) {
|
|
|
870
881
|
}
|
|
871
882
|
|
|
872
883
|
// src/server/stop.ts
|
|
884
|
+
var isShuttingDown = false;
|
|
873
885
|
async function stopServer(serverInstance, options = {}) {
|
|
874
886
|
const server = serverInstance.server;
|
|
875
887
|
const events = serverInstance.events;
|
|
888
|
+
if (isShuttingDown) {
|
|
889
|
+
console.log("\u26A0\uFE0F Shutdown already in progress, ignoring duplicate shutdown request");
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
876
892
|
if (!server) {
|
|
877
893
|
return;
|
|
878
894
|
}
|
|
879
|
-
|
|
895
|
+
isShuttingDown = true;
|
|
896
|
+
const timeout = options.timeout || 5e3;
|
|
880
897
|
try {
|
|
881
898
|
if (options.onStopping) {
|
|
882
899
|
await options.onStopping();
|
|
883
900
|
}
|
|
884
901
|
events.emit("stopping");
|
|
885
|
-
|
|
902
|
+
if (serverInstance.router && typeof serverInstance.router.close === "function") {
|
|
903
|
+
console.log("\u{1F50C} Closing router watchers...");
|
|
904
|
+
try {
|
|
905
|
+
await Promise.race([
|
|
906
|
+
serverInstance.router.close(),
|
|
907
|
+
new Promise(
|
|
908
|
+
(_, reject) => setTimeout(() => reject(new Error("Router close timeout")), 2e3)
|
|
909
|
+
)
|
|
910
|
+
]);
|
|
911
|
+
console.log("\u2705 Router watchers closed");
|
|
912
|
+
} catch (error) {
|
|
913
|
+
console.error("\u274C Error closing router watchers:", error);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
await Promise.race([
|
|
918
|
+
serverInstance.pluginManager.onServerStop(serverInstance, server),
|
|
919
|
+
new Promise(
|
|
920
|
+
(_, reject) => setTimeout(() => reject(new Error("Plugin stop timeout")), 2e3)
|
|
921
|
+
)
|
|
922
|
+
]);
|
|
923
|
+
} catch (error) {
|
|
924
|
+
console.error("\u274C Plugin stop timeout:", error);
|
|
925
|
+
}
|
|
926
|
+
const closePromise = new Promise((resolve3, reject) => {
|
|
927
|
+
server.close((err) => {
|
|
928
|
+
if (err) return reject(err);
|
|
929
|
+
resolve3();
|
|
930
|
+
});
|
|
931
|
+
});
|
|
886
932
|
const timeoutPromise = new Promise((_, reject) => {
|
|
887
933
|
setTimeout(() => {
|
|
888
|
-
reject(new Error("Server shutdown
|
|
934
|
+
reject(new Error("Server shutdown timeout"));
|
|
889
935
|
}, timeout);
|
|
890
936
|
});
|
|
891
|
-
const closePromise = new Promise((resolve2, reject) => {
|
|
892
|
-
server.close((err) => {
|
|
893
|
-
if (err) {
|
|
894
|
-
return reject(err);
|
|
895
|
-
}
|
|
896
|
-
resolve2();
|
|
897
|
-
});
|
|
898
|
-
});
|
|
899
937
|
await Promise.race([closePromise, timeoutPromise]);
|
|
900
|
-
|
|
938
|
+
try {
|
|
939
|
+
await Promise.race([
|
|
940
|
+
serverInstance.pluginManager.terminatePlugins(serverInstance),
|
|
941
|
+
new Promise(
|
|
942
|
+
(_, reject) => setTimeout(() => reject(new Error("Plugin terminate timeout")), 1e3)
|
|
943
|
+
)
|
|
944
|
+
]);
|
|
945
|
+
} catch (error) {
|
|
946
|
+
console.error("\u274C Plugin terminate timeout:", error);
|
|
947
|
+
}
|
|
901
948
|
if (options.onStopped) {
|
|
902
949
|
await options.onStopped();
|
|
903
950
|
}
|
|
904
951
|
events.emit("stopped");
|
|
905
952
|
serverInstance.server = null;
|
|
953
|
+
console.log("\u2705 Graceful shutdown completed");
|
|
954
|
+
isShuttingDown = false;
|
|
906
955
|
} catch (error) {
|
|
956
|
+
isShuttingDown = false;
|
|
957
|
+
console.error("\u26A0\uFE0F Shutdown error (forcing exit):", error);
|
|
958
|
+
if (server && typeof server.close === "function") {
|
|
959
|
+
server.close();
|
|
960
|
+
}
|
|
961
|
+
if (process.env.NODE_ENV === "development") {
|
|
962
|
+
console.log("\u{1F504} Forcing exit for development restart...");
|
|
963
|
+
process.exit(0);
|
|
964
|
+
}
|
|
907
965
|
events.emit("error", error);
|
|
908
966
|
throw error;
|
|
909
967
|
}
|
|
910
968
|
}
|
|
911
969
|
function registerSignalHandlers(stopFn) {
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
970
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
971
|
+
if (isDevelopment) {
|
|
972
|
+
const sigintHandler = () => {
|
|
973
|
+
console.log("\u{1F4E4} SIGINT received, forcing exit for development restart...");
|
|
974
|
+
process.exit(0);
|
|
975
|
+
};
|
|
976
|
+
const sigtermHandler = () => {
|
|
977
|
+
console.log("\u{1F4E4} SIGTERM received, forcing exit for development restart...");
|
|
978
|
+
process.exit(0);
|
|
979
|
+
};
|
|
980
|
+
process.on("SIGINT", sigintHandler);
|
|
981
|
+
process.on("SIGTERM", sigtermHandler);
|
|
982
|
+
return {
|
|
983
|
+
unregister: () => {
|
|
984
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
985
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
} else {
|
|
989
|
+
const sigintHandler = () => {
|
|
990
|
+
console.log("\u{1F4E4} SIGINT received, starting graceful shutdown...");
|
|
991
|
+
stopFn().catch(console.error);
|
|
992
|
+
};
|
|
993
|
+
const sigtermHandler = () => {
|
|
994
|
+
console.log("\u{1F4E4} SIGTERM received, starting graceful shutdown...");
|
|
995
|
+
stopFn().catch(console.error);
|
|
996
|
+
};
|
|
997
|
+
process.on("SIGINT", sigintHandler);
|
|
998
|
+
process.on("SIGTERM", sigtermHandler);
|
|
999
|
+
return {
|
|
1000
|
+
unregister: () => {
|
|
1001
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
1002
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
922
1006
|
}
|
|
923
1007
|
|
|
924
1008
|
// src/server/validation.ts
|
|
@@ -1124,59 +1208,33 @@ function validatePlugin(plugin, options = {}) {
|
|
|
1124
1208
|
}
|
|
1125
1209
|
}
|
|
1126
1210
|
|
|
1127
|
-
// src/router/discovery/
|
|
1211
|
+
// src/router/discovery/cache.ts
|
|
1212
|
+
import * as crypto from "node:crypto";
|
|
1128
1213
|
import * as fs3 from "node:fs/promises";
|
|
1214
|
+
import { createRequire } from "node:module";
|
|
1129
1215
|
import * as path3 from "node:path";
|
|
1130
|
-
async function findRouteFiles(routesDir, options = {}) {
|
|
1131
|
-
const absoluteDir = path3.isAbsolute(routesDir) ? routesDir : path3.resolve(process.cwd(), routesDir);
|
|
1132
|
-
console.log("Creating router with routes directory:", absoluteDir);
|
|
1133
|
-
try {
|
|
1134
|
-
const stats = await fs3.stat(absoluteDir);
|
|
1135
|
-
if (!stats.isDirectory()) {
|
|
1136
|
-
throw new Error(`Route directory is not a directory: ${absoluteDir}`);
|
|
1137
|
-
}
|
|
1138
|
-
} catch (error) {
|
|
1139
|
-
if (error.code === "ENOENT") {
|
|
1140
|
-
throw new Error(`Route directory not found: ${absoluteDir}`);
|
|
1141
|
-
}
|
|
1142
|
-
throw error;
|
|
1143
|
-
}
|
|
1144
|
-
const routeFiles = [];
|
|
1145
|
-
const ignore = options.ignore || ["node_modules", ".git"];
|
|
1146
|
-
async function scanDirectory(dir) {
|
|
1147
|
-
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
1148
|
-
for (const entry of entries) {
|
|
1149
|
-
const fullPath = path3.join(dir, entry.name);
|
|
1150
|
-
if (entry.isDirectory() && ignore.includes(entry.name)) {
|
|
1151
|
-
continue;
|
|
1152
|
-
}
|
|
1153
|
-
if (entry.isDirectory()) {
|
|
1154
|
-
await scanDirectory(fullPath);
|
|
1155
|
-
} else if (isRouteFile(entry.name)) {
|
|
1156
|
-
routeFiles.push(fullPath);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
await scanDirectory(absoluteDir);
|
|
1161
|
-
return routeFiles;
|
|
1162
|
-
}
|
|
1163
|
-
function isRouteFile(filename) {
|
|
1164
|
-
return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
|
|
1165
|
-
}
|
|
1166
1216
|
|
|
1167
1217
|
// src/router/discovery/loader.ts
|
|
1168
1218
|
async function dynamicImport(filePath) {
|
|
1169
|
-
|
|
1219
|
+
const cacheBuster = `?t=${Date.now()}`;
|
|
1220
|
+
const importPath = filePath + cacheBuster;
|
|
1221
|
+
try {
|
|
1222
|
+
const module = await import(importPath);
|
|
1223
|
+
console.log(`\u2705 Successfully imported module`);
|
|
1224
|
+
return module;
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1227
|
+
console.log(`\u26A0\uFE0F Error importing with cache buster, trying original path:`, errorMessage);
|
|
1228
|
+
return import(filePath);
|
|
1229
|
+
}
|
|
1170
1230
|
}
|
|
1171
1231
|
async function loadRouteModule(filePath, basePath) {
|
|
1172
1232
|
try {
|
|
1173
1233
|
const parsedRoute = parseRoutePath(filePath, basePath);
|
|
1174
|
-
console.log("parsedRoute:", parsedRoute);
|
|
1175
1234
|
const module = await dynamicImport(filePath);
|
|
1176
|
-
console.log("Module exports:", Object.keys(module));
|
|
1235
|
+
console.log("\u{1F4E6} Module exports:", Object.keys(module));
|
|
1177
1236
|
const routes = [];
|
|
1178
1237
|
if (module.default && typeof module.default === "object") {
|
|
1179
|
-
console.log("Found default export:", module.default);
|
|
1180
1238
|
const route = {
|
|
1181
1239
|
...module.default,
|
|
1182
1240
|
path: parsedRoute.routePath
|
|
@@ -1189,7 +1247,6 @@ async function loadRouteModule(filePath, basePath) {
|
|
|
1189
1247
|
}
|
|
1190
1248
|
const potentialRoute = exportValue;
|
|
1191
1249
|
if (isValidRoute(potentialRoute)) {
|
|
1192
|
-
console.log(`Found named route export: ${exportName}`, potentialRoute);
|
|
1193
1250
|
const route = {
|
|
1194
1251
|
...potentialRoute,
|
|
1195
1252
|
// Use the route's own path if it has one, otherwise derive from file
|
|
@@ -1202,7 +1259,7 @@ async function loadRouteModule(filePath, basePath) {
|
|
|
1202
1259
|
console.warn(`Route file ${filePath} does not export any valid route definitions`);
|
|
1203
1260
|
return [];
|
|
1204
1261
|
}
|
|
1205
|
-
console.log(
|
|
1262
|
+
console.log(`\u2705 Successfully Loaded ${routes.length} route(s)`);
|
|
1206
1263
|
return routes;
|
|
1207
1264
|
} catch (error) {
|
|
1208
1265
|
console.error(`Failed to load route module ${filePath}:`, error);
|
|
@@ -1220,25 +1277,222 @@ function isValidRoute(obj) {
|
|
|
1220
1277
|
return hasHttpMethod;
|
|
1221
1278
|
}
|
|
1222
1279
|
|
|
1223
|
-
// src/router/discovery/
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1280
|
+
// src/router/discovery/cache.ts
|
|
1281
|
+
var fileRouteCache = /* @__PURE__ */ new Map();
|
|
1282
|
+
async function processChangedFile(filePath, routesDir, updateCache = true) {
|
|
1283
|
+
const stat3 = await fs3.stat(filePath);
|
|
1284
|
+
const lastModified = stat3.mtime.getTime();
|
|
1285
|
+
const cachedEntry = fileRouteCache.get(filePath);
|
|
1286
|
+
if (updateCache && cachedEntry && cachedEntry.timestamp === lastModified) {
|
|
1287
|
+
return cachedEntry.routes;
|
|
1288
|
+
}
|
|
1289
|
+
invalidateModuleCache(filePath);
|
|
1290
|
+
const routes = await loadRouteModule(filePath, routesDir);
|
|
1291
|
+
if (updateCache) {
|
|
1292
|
+
const hash = hashRoutes(routes);
|
|
1293
|
+
fileRouteCache.set(filePath, {
|
|
1294
|
+
routes,
|
|
1295
|
+
timestamp: lastModified,
|
|
1296
|
+
hash
|
|
1297
|
+
});
|
|
1234
1298
|
}
|
|
1235
1299
|
return routes;
|
|
1236
1300
|
}
|
|
1301
|
+
function hasRouteContentChanged(filePath, newRoutes) {
|
|
1302
|
+
const cachedEntry = fileRouteCache.get(filePath);
|
|
1303
|
+
if (!cachedEntry) {
|
|
1304
|
+
return true;
|
|
1305
|
+
}
|
|
1306
|
+
const newHash = hashRoutes(newRoutes);
|
|
1307
|
+
return cachedEntry.hash !== newHash;
|
|
1308
|
+
}
|
|
1309
|
+
function clearFileCache(filePath) {
|
|
1310
|
+
if (filePath) {
|
|
1311
|
+
fileRouteCache.delete(filePath);
|
|
1312
|
+
} else {
|
|
1313
|
+
fileRouteCache.clear();
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function hashRoutes(routes) {
|
|
1317
|
+
const routeData = routes.map((route) => ({
|
|
1318
|
+
path: route.path,
|
|
1319
|
+
methods: Object.keys(route).filter((key) => key !== "path").sort().map((method) => {
|
|
1320
|
+
const methodDef = route[method];
|
|
1321
|
+
const handlerString = methodDef?.handler ? methodDef.handler.toString() : null;
|
|
1322
|
+
return {
|
|
1323
|
+
method,
|
|
1324
|
+
// Include handler function string for change detection
|
|
1325
|
+
handler: handlerString,
|
|
1326
|
+
// Include middleware if present
|
|
1327
|
+
middleware: methodDef?.middleware ? methodDef.middleware.length : 0,
|
|
1328
|
+
// Include schema structure (but not full serialization which can be unstable)
|
|
1329
|
+
hasSchema: !!methodDef?.schema,
|
|
1330
|
+
schemaKeys: methodDef?.schema ? Object.keys(methodDef.schema).sort() : []
|
|
1331
|
+
};
|
|
1332
|
+
})
|
|
1333
|
+
}));
|
|
1334
|
+
const dataString = JSON.stringify(routeData);
|
|
1335
|
+
const hash = crypto.createHash("md5").update(dataString).digest("hex");
|
|
1336
|
+
return hash;
|
|
1337
|
+
}
|
|
1338
|
+
function invalidateModuleCache(filePath) {
|
|
1339
|
+
try {
|
|
1340
|
+
const absolutePath = path3.resolve(filePath);
|
|
1341
|
+
if (typeof __require !== "undefined") {
|
|
1342
|
+
delete __require.cache[absolutePath];
|
|
1343
|
+
try {
|
|
1344
|
+
const resolvedPath = __require.resolve(absolutePath);
|
|
1345
|
+
delete __require.cache[resolvedPath];
|
|
1346
|
+
} catch (resolveError) {
|
|
1347
|
+
const errorMessage = resolveError instanceof Error ? resolveError.message : String(resolveError);
|
|
1348
|
+
console.log(`\u26A0\uFE0F Could not resolve path: ${errorMessage}`);
|
|
1349
|
+
}
|
|
1350
|
+
} else {
|
|
1351
|
+
try {
|
|
1352
|
+
const require2 = createRequire(import.meta.url);
|
|
1353
|
+
delete require2.cache[absolutePath];
|
|
1354
|
+
try {
|
|
1355
|
+
const resolvedPath = require2.resolve(absolutePath);
|
|
1356
|
+
delete require2.cache[resolvedPath];
|
|
1357
|
+
} catch {
|
|
1358
|
+
console.log(`\u26A0\uFE0F Could not resolve ESM path`);
|
|
1359
|
+
}
|
|
1360
|
+
} catch {
|
|
1361
|
+
console.log(`\u26A0\uFE0F createRequire not available in pure ESM`);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
console.log(`\u26A0\uFE0F Error during module cache invalidation for ${filePath}:`, error);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1237
1368
|
|
|
1238
|
-
// src/router/discovery/
|
|
1369
|
+
// src/router/discovery/parallel.ts
|
|
1370
|
+
import * as os from "node:os";
|
|
1371
|
+
|
|
1372
|
+
// src/router/discovery/finder.ts
|
|
1373
|
+
import * as fs4 from "node:fs/promises";
|
|
1239
1374
|
import * as path4 from "node:path";
|
|
1375
|
+
async function findRouteFiles(routesDir, options = {}) {
|
|
1376
|
+
const absoluteDir = path4.isAbsolute(routesDir) ? routesDir : path4.resolve(process.cwd(), routesDir);
|
|
1377
|
+
console.log("Creating router with routes directory:", absoluteDir);
|
|
1378
|
+
try {
|
|
1379
|
+
const stats = await fs4.stat(absoluteDir);
|
|
1380
|
+
if (!stats.isDirectory()) {
|
|
1381
|
+
throw new Error(`Route directory is not a directory: ${absoluteDir}`);
|
|
1382
|
+
}
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
if (error.code === "ENOENT") {
|
|
1385
|
+
throw new Error(`Route directory not found: ${absoluteDir}`);
|
|
1386
|
+
}
|
|
1387
|
+
throw error;
|
|
1388
|
+
}
|
|
1389
|
+
const routeFiles = [];
|
|
1390
|
+
const ignore = options.ignore || ["node_modules", ".git"];
|
|
1391
|
+
async function scanDirectory(dir) {
|
|
1392
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
1393
|
+
for (const entry of entries) {
|
|
1394
|
+
const fullPath = path4.join(dir, entry.name);
|
|
1395
|
+
if (entry.isDirectory() && ignore.includes(entry.name)) {
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
if (entry.isDirectory()) {
|
|
1399
|
+
await scanDirectory(fullPath);
|
|
1400
|
+
} else if (isRouteFile(entry.name)) {
|
|
1401
|
+
routeFiles.push(fullPath);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
await scanDirectory(absoluteDir);
|
|
1406
|
+
return routeFiles;
|
|
1407
|
+
}
|
|
1408
|
+
function isRouteFile(filename) {
|
|
1409
|
+
return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/router/discovery/parallel.ts
|
|
1413
|
+
async function processFilesInParallel(filePaths, processor, concurrency = Math.max(1, Math.floor(os.cpus().length / 2))) {
|
|
1414
|
+
const chunks = chunkArray(filePaths, concurrency);
|
|
1415
|
+
const results = [];
|
|
1416
|
+
for (const chunk of chunks) {
|
|
1417
|
+
const chunkResults = await Promise.allSettled(chunk.map((filePath) => processor(filePath)));
|
|
1418
|
+
const successfulResults = chunkResults.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
1419
|
+
results.push(...successfulResults);
|
|
1420
|
+
}
|
|
1421
|
+
return results;
|
|
1422
|
+
}
|
|
1423
|
+
async function loadInitialRoutesParallel(routesDir) {
|
|
1424
|
+
const files = await findRouteFiles(routesDir);
|
|
1425
|
+
const routeArrays = await processFilesInParallel(
|
|
1426
|
+
files,
|
|
1427
|
+
(filePath) => processChangedFile(filePath, routesDir)
|
|
1428
|
+
);
|
|
1429
|
+
return routeArrays.flat();
|
|
1430
|
+
}
|
|
1431
|
+
function chunkArray(array, chunkSize) {
|
|
1432
|
+
const chunks = [];
|
|
1433
|
+
for (let i = 0; i < array.length; i += chunkSize) {
|
|
1434
|
+
chunks.push(array.slice(i, i + chunkSize));
|
|
1435
|
+
}
|
|
1436
|
+
return chunks;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// src/router/discovery/profiler.ts
|
|
1440
|
+
var profilerState = {
|
|
1441
|
+
fileChanges: 0,
|
|
1442
|
+
totalReloadTime: 0,
|
|
1443
|
+
averageReloadTime: 0,
|
|
1444
|
+
slowReloads: []
|
|
1445
|
+
};
|
|
1446
|
+
function trackReloadPerformance(filePath, startTime) {
|
|
1447
|
+
const duration = Date.now() - startTime;
|
|
1448
|
+
profilerState.fileChanges++;
|
|
1449
|
+
profilerState.totalReloadTime += duration;
|
|
1450
|
+
profilerState.averageReloadTime = profilerState.totalReloadTime / profilerState.fileChanges;
|
|
1451
|
+
if (duration > 100) {
|
|
1452
|
+
profilerState.slowReloads.push({ file: filePath, time: duration });
|
|
1453
|
+
if (profilerState.slowReloads.length > 10) {
|
|
1454
|
+
profilerState.slowReloads.shift();
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (process.env.NODE_ENV === "development") {
|
|
1458
|
+
const emoji = duration < 50 ? "\u26A1" : duration < 100 ? "\u{1F504}" : "\u{1F40C}";
|
|
1459
|
+
console.log(`${emoji} Route reload: ${filePath} (${duration}ms)`);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
function withPerformanceTracking(fn, filePath) {
|
|
1463
|
+
console.log(`Tracking performance for: ${filePath}`);
|
|
1464
|
+
return async (...args) => {
|
|
1465
|
+
const startTime = Date.now();
|
|
1466
|
+
try {
|
|
1467
|
+
const result = await fn(...args);
|
|
1468
|
+
trackReloadPerformance(filePath, startTime);
|
|
1469
|
+
return result;
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
trackReloadPerformance(filePath, startTime);
|
|
1472
|
+
throw error;
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// src/router/discovery/watchers.ts
|
|
1478
|
+
import * as path5 from "node:path";
|
|
1240
1479
|
import { watch } from "chokidar";
|
|
1241
1480
|
function watchRoutes(routesDir, options = {}) {
|
|
1481
|
+
const debounceMs = options.debounceMs || 16;
|
|
1482
|
+
const debouncedCallbacks = /* @__PURE__ */ new Map();
|
|
1483
|
+
function createDebouncedCallback(fn, filePath) {
|
|
1484
|
+
return (...args) => {
|
|
1485
|
+
const existingTimeout = debouncedCallbacks.get(filePath);
|
|
1486
|
+
if (existingTimeout) {
|
|
1487
|
+
clearTimeout(existingTimeout);
|
|
1488
|
+
}
|
|
1489
|
+
const timeoutId = setTimeout(() => {
|
|
1490
|
+
fn(...args);
|
|
1491
|
+
debouncedCallbacks.delete(filePath);
|
|
1492
|
+
}, debounceMs);
|
|
1493
|
+
debouncedCallbacks.set(filePath, timeoutId);
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1242
1496
|
const routesByPath = /* @__PURE__ */ new Map();
|
|
1243
1497
|
async function loadInitialRoutes() {
|
|
1244
1498
|
try {
|
|
@@ -1254,28 +1508,34 @@ function watchRoutes(routesDir, options = {}) {
|
|
|
1254
1508
|
}
|
|
1255
1509
|
async function loadAndNotify(filePath) {
|
|
1256
1510
|
try {
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1511
|
+
const existingRoutes = routesByPath.get(filePath);
|
|
1512
|
+
const newRoutes = await processChangedFile(filePath, routesDir, false);
|
|
1513
|
+
if (!newRoutes || newRoutes.length === 0) {
|
|
1259
1514
|
return;
|
|
1260
1515
|
}
|
|
1261
|
-
|
|
1516
|
+
if (existingRoutes && !hasRouteContentChanged(filePath, newRoutes)) {
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
await processChangedFile(filePath, routesDir, true);
|
|
1520
|
+
const normalizedPath = path5.normalize(filePath);
|
|
1262
1521
|
if (existingRoutes) {
|
|
1263
|
-
routesByPath.set(filePath,
|
|
1522
|
+
routesByPath.set(filePath, newRoutes);
|
|
1264
1523
|
if (options.onRouteChanged) {
|
|
1265
|
-
options.onRouteChanged(
|
|
1524
|
+
options.onRouteChanged(normalizedPath, newRoutes);
|
|
1266
1525
|
}
|
|
1267
1526
|
} else {
|
|
1268
|
-
routesByPath.set(filePath,
|
|
1527
|
+
routesByPath.set(filePath, newRoutes);
|
|
1269
1528
|
if (options.onRouteAdded) {
|
|
1270
|
-
options.onRouteAdded(
|
|
1529
|
+
options.onRouteAdded(normalizedPath, newRoutes);
|
|
1271
1530
|
}
|
|
1272
1531
|
}
|
|
1273
1532
|
} catch (error) {
|
|
1533
|
+
console.log(`\u26A0\uFE0F Error processing file ${filePath}:`, error);
|
|
1274
1534
|
handleError(error);
|
|
1275
1535
|
}
|
|
1276
1536
|
}
|
|
1277
1537
|
function handleRemoved(filePath) {
|
|
1278
|
-
const normalizedPath =
|
|
1538
|
+
const normalizedPath = path5.normalize(filePath);
|
|
1279
1539
|
const routes = routesByPath.get(normalizedPath);
|
|
1280
1540
|
if (routes && routes.length > 0 && options.onRouteRemoved) {
|
|
1281
1541
|
options.onRouteRemoved(normalizedPath, routes);
|
|
@@ -1286,33 +1546,53 @@ function watchRoutes(routesDir, options = {}) {
|
|
|
1286
1546
|
if (options.onError && error instanceof Error) {
|
|
1287
1547
|
options.onError(error);
|
|
1288
1548
|
} else {
|
|
1289
|
-
console.error("Route watcher error:", error);
|
|
1549
|
+
console.error("\u26A0\uFE0F Route watcher error:", error);
|
|
1290
1550
|
}
|
|
1291
1551
|
}
|
|
1292
1552
|
const watcher = watch(routesDir, {
|
|
1553
|
+
// Much faster response times
|
|
1554
|
+
awaitWriteFinish: {
|
|
1555
|
+
stabilityThreshold: 50,
|
|
1556
|
+
// Reduced from 300ms
|
|
1557
|
+
pollInterval: 10
|
|
1558
|
+
// Reduced from 100ms
|
|
1559
|
+
},
|
|
1560
|
+
// Performance optimizations
|
|
1561
|
+
usePolling: false,
|
|
1562
|
+
atomic: true,
|
|
1563
|
+
followSymlinks: false,
|
|
1564
|
+
depth: 10,
|
|
1565
|
+
// More aggressive ignoring
|
|
1293
1566
|
ignored: [
|
|
1294
1567
|
/(^|[/\\])\../,
|
|
1295
|
-
// Ignore dot files
|
|
1296
1568
|
/node_modules/,
|
|
1569
|
+
/\.git/,
|
|
1570
|
+
/\.DS_Store/,
|
|
1571
|
+
/Thumbs\.db/,
|
|
1572
|
+
/\.(test|spec)\.(ts|js)$/,
|
|
1573
|
+
/\.d\.ts$/,
|
|
1574
|
+
/\.map$/,
|
|
1575
|
+
/~$/,
|
|
1297
1576
|
...options.ignore || []
|
|
1298
|
-
]
|
|
1299
|
-
persistent: true,
|
|
1300
|
-
ignoreInitial: false,
|
|
1301
|
-
awaitWriteFinish: {
|
|
1302
|
-
stabilityThreshold: 300,
|
|
1303
|
-
pollInterval: 100
|
|
1304
|
-
}
|
|
1577
|
+
]
|
|
1305
1578
|
});
|
|
1306
|
-
watcher.on("add",
|
|
1579
|
+
watcher.on("add", (filePath) => {
|
|
1580
|
+
const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
|
|
1581
|
+
debouncedLoad(filePath);
|
|
1582
|
+
}).on("change", (filePath) => {
|
|
1583
|
+
const debouncedLoad = createDebouncedCallback(loadAndNotify, filePath);
|
|
1584
|
+
debouncedLoad(filePath);
|
|
1585
|
+
}).on("unlink", (filePath) => {
|
|
1586
|
+
const debouncedRemove = createDebouncedCallback(handleRemoved, filePath);
|
|
1587
|
+
debouncedRemove(filePath);
|
|
1588
|
+
}).on("error", handleError);
|
|
1307
1589
|
loadInitialRoutes().catch(handleError);
|
|
1308
1590
|
return {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
* Get all currently loaded routes (flattened)
|
|
1315
|
-
*/
|
|
1591
|
+
close: () => {
|
|
1592
|
+
debouncedCallbacks.forEach((timeout) => clearTimeout(timeout));
|
|
1593
|
+
debouncedCallbacks.clear();
|
|
1594
|
+
return watcher.close();
|
|
1595
|
+
},
|
|
1316
1596
|
getRoutes: () => {
|
|
1317
1597
|
const allRoutes = [];
|
|
1318
1598
|
for (const routes of routesByPath.values()) {
|
|
@@ -1320,9 +1600,6 @@ function watchRoutes(routesDir, options = {}) {
|
|
|
1320
1600
|
}
|
|
1321
1601
|
return allRoutes;
|
|
1322
1602
|
},
|
|
1323
|
-
/**
|
|
1324
|
-
* Get routes organized by file path
|
|
1325
|
-
*/
|
|
1326
1603
|
getRoutesByFile: () => new Map(routesByPath)
|
|
1327
1604
|
};
|
|
1328
1605
|
}
|
|
@@ -1533,8 +1810,8 @@ async function executeHandler(ctx, routeOptions, params) {
|
|
|
1533
1810
|
}
|
|
1534
1811
|
|
|
1535
1812
|
// src/router/matching/params.ts
|
|
1536
|
-
function extractParams(
|
|
1537
|
-
const match = pattern.exec(
|
|
1813
|
+
function extractParams(path6, pattern, paramNames) {
|
|
1814
|
+
const match = pattern.exec(path6);
|
|
1538
1815
|
if (!match) {
|
|
1539
1816
|
return {};
|
|
1540
1817
|
}
|
|
@@ -1544,15 +1821,15 @@ function extractParams(path5, pattern, paramNames) {
|
|
|
1544
1821
|
}
|
|
1545
1822
|
return params;
|
|
1546
1823
|
}
|
|
1547
|
-
function compilePathPattern(
|
|
1824
|
+
function compilePathPattern(path6) {
|
|
1548
1825
|
const paramNames = [];
|
|
1549
|
-
if (
|
|
1826
|
+
if (path6 === "/") {
|
|
1550
1827
|
return {
|
|
1551
1828
|
pattern: /^\/$/,
|
|
1552
1829
|
paramNames: []
|
|
1553
1830
|
};
|
|
1554
1831
|
}
|
|
1555
|
-
let patternString =
|
|
1832
|
+
let patternString = path6.replace(/([.+*?^$(){}|\\])/g, "\\$1");
|
|
1556
1833
|
patternString = patternString.replace(/\/:([^/]+)/g, (_, paramName) => {
|
|
1557
1834
|
paramNames.push(paramName);
|
|
1558
1835
|
return "/([^/]+)";
|
|
@@ -1575,10 +1852,10 @@ function createMatcher() {
|
|
|
1575
1852
|
/**
|
|
1576
1853
|
* Add a route to the matcher
|
|
1577
1854
|
*/
|
|
1578
|
-
add(
|
|
1579
|
-
const { pattern, paramNames } = compilePathPattern(
|
|
1855
|
+
add(path6, method, routeOptions) {
|
|
1856
|
+
const { pattern, paramNames } = compilePathPattern(path6);
|
|
1580
1857
|
const newRoute = {
|
|
1581
|
-
path:
|
|
1858
|
+
path: path6,
|
|
1582
1859
|
method,
|
|
1583
1860
|
pattern,
|
|
1584
1861
|
paramNames,
|
|
@@ -1591,17 +1868,33 @@ function createMatcher() {
|
|
|
1591
1868
|
routes.splice(insertIndex, 0, newRoute);
|
|
1592
1869
|
}
|
|
1593
1870
|
},
|
|
1871
|
+
/**
|
|
1872
|
+
* Remove a route from the matcher by path
|
|
1873
|
+
*/
|
|
1874
|
+
remove(path6) {
|
|
1875
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
1876
|
+
if (routes[i].path === path6) {
|
|
1877
|
+
routes.splice(i, 1);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
},
|
|
1881
|
+
/**
|
|
1882
|
+
* Clear all routes from the matcher
|
|
1883
|
+
*/
|
|
1884
|
+
clear() {
|
|
1885
|
+
routes.length = 0;
|
|
1886
|
+
},
|
|
1594
1887
|
/**
|
|
1595
1888
|
* Match a URL path to a route
|
|
1596
1889
|
*/
|
|
1597
|
-
match(
|
|
1598
|
-
const pathname =
|
|
1890
|
+
match(path6, method) {
|
|
1891
|
+
const pathname = path6.split("?")[0];
|
|
1599
1892
|
if (!pathname) return null;
|
|
1600
1893
|
for (const route of routes) {
|
|
1601
1894
|
if (route.method !== method) continue;
|
|
1602
1895
|
const match = route.pattern.exec(pathname);
|
|
1603
1896
|
if (match) {
|
|
1604
|
-
const params = extractParams(
|
|
1897
|
+
const params = extractParams(path6, route.pattern, route.paramNames);
|
|
1605
1898
|
return {
|
|
1606
1899
|
route: route.routeOptions,
|
|
1607
1900
|
params
|
|
@@ -1609,14 +1902,14 @@ function createMatcher() {
|
|
|
1609
1902
|
}
|
|
1610
1903
|
}
|
|
1611
1904
|
const matchingPath = routes.find(
|
|
1612
|
-
(route) => route.method !== method && route.pattern.test(
|
|
1905
|
+
(route) => route.method !== method && route.pattern.test(path6)
|
|
1613
1906
|
);
|
|
1614
1907
|
if (matchingPath) {
|
|
1615
1908
|
return {
|
|
1616
1909
|
route: null,
|
|
1617
1910
|
params: {},
|
|
1618
1911
|
methodNotAllowed: true,
|
|
1619
|
-
allowedMethods: routes.filter((route) => route.pattern.test(
|
|
1912
|
+
allowedMethods: routes.filter((route) => route.pattern.test(path6)).map((route) => route.method)
|
|
1620
1913
|
};
|
|
1621
1914
|
}
|
|
1622
1915
|
return null;
|
|
@@ -1633,16 +1926,93 @@ function createMatcher() {
|
|
|
1633
1926
|
/**
|
|
1634
1927
|
* Find routes matching a specific path
|
|
1635
1928
|
*/
|
|
1636
|
-
findRoutes(
|
|
1637
|
-
return routes.filter((route) => route.pattern.test(
|
|
1929
|
+
findRoutes(path6) {
|
|
1930
|
+
return routes.filter((route) => route.pattern.test(path6)).map((route) => ({
|
|
1638
1931
|
path: route.path,
|
|
1639
1932
|
method: route.method,
|
|
1640
|
-
params: extractParams(
|
|
1933
|
+
params: extractParams(path6, route.pattern, route.paramNames)
|
|
1641
1934
|
}));
|
|
1642
1935
|
}
|
|
1643
1936
|
};
|
|
1644
1937
|
}
|
|
1645
1938
|
|
|
1939
|
+
// src/router/registry/fast-registry.ts
|
|
1940
|
+
function createRouteRegistry() {
|
|
1941
|
+
return {
|
|
1942
|
+
routesByPath: /* @__PURE__ */ new Map(),
|
|
1943
|
+
routesByFile: /* @__PURE__ */ new Map(),
|
|
1944
|
+
pathToFile: /* @__PURE__ */ new Map()
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
function updateRoutesFromFile(registry, filePath, newRoutes) {
|
|
1948
|
+
console.log(`Updating routes from file: ${filePath}`);
|
|
1949
|
+
const oldPaths = registry.routesByFile.get(filePath) || /* @__PURE__ */ new Set();
|
|
1950
|
+
const newPaths = new Set(newRoutes.map((r) => r.path));
|
|
1951
|
+
const added = newRoutes.filter((r) => !oldPaths.has(r.path));
|
|
1952
|
+
const removed = Array.from(oldPaths).filter((p) => !newPaths.has(p));
|
|
1953
|
+
const potentiallyChanged = newRoutes.filter((r) => oldPaths.has(r.path));
|
|
1954
|
+
const changed = potentiallyChanged.filter((route) => {
|
|
1955
|
+
const existingRoute = registry.routesByPath.get(route.path);
|
|
1956
|
+
return !existingRoute || !routesEqual(existingRoute, route);
|
|
1957
|
+
});
|
|
1958
|
+
applyRouteUpdates(registry, filePath, { added, removed, changed });
|
|
1959
|
+
return { added, removed, changed };
|
|
1960
|
+
}
|
|
1961
|
+
function getAllRoutesFromRegistry(registry) {
|
|
1962
|
+
return Array.from(registry.routesByPath.values());
|
|
1963
|
+
}
|
|
1964
|
+
function applyRouteUpdates(registry, filePath, updates) {
|
|
1965
|
+
const { added, removed, changed } = updates;
|
|
1966
|
+
removed.forEach((path6) => {
|
|
1967
|
+
registry.routesByPath.delete(path6);
|
|
1968
|
+
registry.pathToFile.delete(path6);
|
|
1969
|
+
});
|
|
1970
|
+
[...added, ...changed].forEach((route) => {
|
|
1971
|
+
registry.routesByPath.set(route.path, route);
|
|
1972
|
+
registry.pathToFile.set(route.path, filePath);
|
|
1973
|
+
});
|
|
1974
|
+
const allPathsForFile = /* @__PURE__ */ new Set([
|
|
1975
|
+
...added.map((r) => r.path),
|
|
1976
|
+
...changed.map((r) => r.path),
|
|
1977
|
+
...Array.from(registry.routesByFile.get(filePath) || []).filter((p) => !removed.includes(p))
|
|
1978
|
+
]);
|
|
1979
|
+
if (allPathsForFile.size > 0) {
|
|
1980
|
+
registry.routesByFile.set(filePath, allPathsForFile);
|
|
1981
|
+
} else {
|
|
1982
|
+
registry.routesByFile.delete(filePath);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
function routesEqual(route1, route2) {
|
|
1986
|
+
if (route1.path !== route2.path) return false;
|
|
1987
|
+
const methods1 = Object.keys(route1).filter((k) => k !== "path").sort();
|
|
1988
|
+
const methods2 = Object.keys(route2).filter((k) => k !== "path").sort();
|
|
1989
|
+
if (methods1.length !== methods2.length) return false;
|
|
1990
|
+
return methods1.every((method) => {
|
|
1991
|
+
const handler1 = route1[method];
|
|
1992
|
+
const handler2 = route2[method];
|
|
1993
|
+
return typeof handler1 === typeof handler2;
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// src/router/utils/matching-helpers.ts
|
|
1998
|
+
function addRouteToMatcher(route, matcher) {
|
|
1999
|
+
Object.entries(route).forEach(([method, methodOptions]) => {
|
|
2000
|
+
if (method === "path" || !methodOptions) return;
|
|
2001
|
+
matcher.add(route.path, method, methodOptions);
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
function removeRouteFromMatcher(path6, matcher) {
|
|
2005
|
+
if ("remove" in matcher && typeof matcher.remove === "function") {
|
|
2006
|
+
matcher.remove(path6);
|
|
2007
|
+
} else {
|
|
2008
|
+
console.warn("Matcher does not support selective removal, consider adding remove() method");
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
function updateRouteInMatcher(route, matcher) {
|
|
2012
|
+
removeRouteFromMatcher(route.path, matcher);
|
|
2013
|
+
addRouteToMatcher(route, matcher);
|
|
2014
|
+
}
|
|
2015
|
+
|
|
1646
2016
|
// src/router/router.ts
|
|
1647
2017
|
var DEFAULT_ROUTER_OPTIONS = {
|
|
1648
2018
|
routesDir: "./routes",
|
|
@@ -1657,46 +2027,55 @@ function createRouter(options) {
|
|
|
1657
2027
|
if (options.basePath && !options.basePath.startsWith("/")) {
|
|
1658
2028
|
console.warn("Base path does nothing");
|
|
1659
2029
|
}
|
|
1660
|
-
const
|
|
2030
|
+
const registry = createRouteRegistry();
|
|
1661
2031
|
const matcher = createMatcher();
|
|
1662
2032
|
let initialized = false;
|
|
1663
2033
|
let initializationPromise = null;
|
|
1664
2034
|
let _watchers = null;
|
|
1665
|
-
const routeSources = /* @__PURE__ */ new Map();
|
|
1666
2035
|
const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
|
|
1667
|
-
function
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2036
|
+
function applyMatcherChanges(changes) {
|
|
2037
|
+
console.log("\n\u{1F527} APPLYING MATCHER CHANGES:");
|
|
2038
|
+
console.log(` Adding ${changes.added.length} routes`);
|
|
2039
|
+
console.log(` Removing ${changes.removed.length} routes`);
|
|
2040
|
+
console.log(` Updating ${changes.changed.length} routes`);
|
|
2041
|
+
changes.removed.forEach((routePath) => {
|
|
2042
|
+
console.log(` \u2796 Removing: ${routePath}`);
|
|
2043
|
+
removeRouteFromMatcher(routePath, matcher);
|
|
2044
|
+
});
|
|
2045
|
+
changes.added.forEach((route) => {
|
|
2046
|
+
const methods = Object.keys(route).filter((key) => key !== "path");
|
|
2047
|
+
console.log(` \u2795 Adding: ${route.path} [${methods.join(", ")}]`);
|
|
2048
|
+
addRouteToMatcher(route, matcher);
|
|
2049
|
+
});
|
|
2050
|
+
changes.changed.forEach((route) => {
|
|
2051
|
+
const methods = Object.keys(route).filter((key) => key !== "path");
|
|
2052
|
+
console.log(` \u{1F504} Updating: ${route.path} [${methods.join(", ")}]`);
|
|
2053
|
+
updateRouteInMatcher(route, matcher);
|
|
2054
|
+
});
|
|
2055
|
+
console.log("\u2705 Matcher changes applied\n");
|
|
2056
|
+
}
|
|
2057
|
+
function addRoutesWithSource(routes, source) {
|
|
2058
|
+
try {
|
|
2059
|
+
const changes = updateRoutesFromFile(registry, source, routes);
|
|
2060
|
+
applyMatcherChanges(changes);
|
|
2061
|
+
return changes;
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
console.error(`\u26A0\uFE0F Route conflicts from ${source}:`, error);
|
|
2064
|
+
throw error;
|
|
1679
2065
|
}
|
|
1680
|
-
routeSources.set(route.path, [...existingSources, source]);
|
|
1681
|
-
addRouteInternal(route);
|
|
1682
2066
|
}
|
|
1683
2067
|
async function loadRoutesFromDirectory(directory, source, prefix) {
|
|
1684
2068
|
try {
|
|
1685
|
-
const discoveredRoutes = await
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
...route,
|
|
1691
|
-
path: `${prefix}${route.path}`
|
|
1692
|
-
} : route;
|
|
1693
|
-
addRouteWithSource(finalRoute, source);
|
|
1694
|
-
}
|
|
2069
|
+
const discoveredRoutes = await loadInitialRoutesParallel(directory);
|
|
2070
|
+
const finalRoutes = discoveredRoutes.map(
|
|
2071
|
+
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
2072
|
+
);
|
|
2073
|
+
const changes = addRoutesWithSource(finalRoutes, source);
|
|
1695
2074
|
console.log(
|
|
1696
|
-
`Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""}`
|
|
2075
|
+
`Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""} (${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed)`
|
|
1697
2076
|
);
|
|
1698
2077
|
} catch (error) {
|
|
1699
|
-
console.error(
|
|
2078
|
+
console.error(`\u26A0\uFE0F Failed to load routes from ${source}:`, error);
|
|
1700
2079
|
throw error;
|
|
1701
2080
|
}
|
|
1702
2081
|
}
|
|
@@ -1706,105 +2085,126 @@ function createRouter(options) {
|
|
|
1706
2085
|
}
|
|
1707
2086
|
initializationPromise = (async () => {
|
|
1708
2087
|
try {
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2088
|
+
await Promise.all(
|
|
2089
|
+
Array.from(routeDirectories).map(
|
|
2090
|
+
(directory) => loadRoutesFromDirectory(directory, directory)
|
|
2091
|
+
)
|
|
2092
|
+
);
|
|
1712
2093
|
if (routerOptions.watchMode) {
|
|
1713
|
-
|
|
2094
|
+
setupOptimizedWatching();
|
|
1714
2095
|
}
|
|
1715
2096
|
initialized = true;
|
|
1716
2097
|
} catch (error) {
|
|
1717
|
-
console.error("Failed to initialize router:", error);
|
|
2098
|
+
console.error("\u26A0\uFE0F Failed to initialize router:", error);
|
|
1718
2099
|
throw error;
|
|
1719
2100
|
}
|
|
1720
2101
|
})();
|
|
1721
2102
|
return initializationPromise;
|
|
1722
2103
|
}
|
|
1723
|
-
function
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
}
|
|
1757
|
-
|
|
2104
|
+
function setupOptimizedWatching() {
|
|
2105
|
+
if (!_watchers) {
|
|
2106
|
+
_watchers = /* @__PURE__ */ new Map();
|
|
2107
|
+
}
|
|
2108
|
+
for (const directory of routeDirectories) {
|
|
2109
|
+
if (!_watchers.has(directory)) {
|
|
2110
|
+
const watcher = watchRoutes(directory, {
|
|
2111
|
+
debounceMs: 16,
|
|
2112
|
+
// ~60fps debouncing
|
|
2113
|
+
ignore: ["node_modules", ".git"],
|
|
2114
|
+
onRouteAdded: (filepath, addedRoutes) => {
|
|
2115
|
+
try {
|
|
2116
|
+
const changes = updateRoutesFromFile(registry, filepath, addedRoutes);
|
|
2117
|
+
applyMatcherChanges(changes);
|
|
2118
|
+
} catch (error) {
|
|
2119
|
+
console.error(`Error adding routes from ${directory}:`, error);
|
|
2120
|
+
}
|
|
2121
|
+
},
|
|
2122
|
+
onRouteChanged: withPerformanceTracking(
|
|
2123
|
+
async (filepath, changedRoutes) => {
|
|
2124
|
+
try {
|
|
2125
|
+
console.log(`Processing changes for ${filepath}`);
|
|
2126
|
+
const changes = updateRoutesFromFile(registry, filepath, changedRoutes);
|
|
2127
|
+
console.log(
|
|
2128
|
+
`Changes detected: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
|
|
2129
|
+
);
|
|
2130
|
+
applyMatcherChanges(changes);
|
|
2131
|
+
console.log(
|
|
2132
|
+
`Route changes applied: ${changes.added.length} added, ${changes.changed.length} changed, ${changes.removed.length} removed`
|
|
2133
|
+
);
|
|
2134
|
+
} catch (error) {
|
|
2135
|
+
console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
|
|
2136
|
+
}
|
|
2137
|
+
},
|
|
2138
|
+
directory
|
|
2139
|
+
),
|
|
2140
|
+
onRouteRemoved: (filePath, removedRoutes) => {
|
|
2141
|
+
console.log(`File removed: ${filePath} with ${removedRoutes.length} routes`);
|
|
2142
|
+
try {
|
|
2143
|
+
removedRoutes.forEach((route) => {
|
|
2144
|
+
removeRouteFromMatcher(route.path, matcher);
|
|
2145
|
+
});
|
|
2146
|
+
clearFileCache(filePath);
|
|
2147
|
+
} catch (error) {
|
|
2148
|
+
console.error(`\u26A0\uFE0F Error removing routes from ${filePath}:`, error);
|
|
1758
2149
|
}
|
|
2150
|
+
},
|
|
2151
|
+
onError: (error) => {
|
|
2152
|
+
console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
|
|
1759
2153
|
}
|
|
1760
|
-
const finalRoute = prefix ? { ...route, path: finalPath } : route;
|
|
1761
|
-
addRouteWithSource(finalRoute, source);
|
|
1762
2154
|
});
|
|
2155
|
+
_watchers.set(directory, watcher);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
function setupWatcherForNewDirectory(directory, prefix) {
|
|
2160
|
+
if (!_watchers) {
|
|
2161
|
+
_watchers = /* @__PURE__ */ new Map();
|
|
2162
|
+
}
|
|
2163
|
+
const watcher = watchRoutes(directory, {
|
|
2164
|
+
debounceMs: 16,
|
|
2165
|
+
ignore: ["node_modules", ".git"],
|
|
2166
|
+
onRouteAdded: (filePath, addedRoutes) => {
|
|
2167
|
+
try {
|
|
2168
|
+
const finalRoutes = addedRoutes.map(
|
|
2169
|
+
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
2170
|
+
);
|
|
2171
|
+
const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
|
|
2172
|
+
applyMatcherChanges(changes);
|
|
2173
|
+
} catch (error) {
|
|
2174
|
+
console.error(`\u26A0\uFE0F Error adding routes from ${directory}:`, error);
|
|
2175
|
+
}
|
|
1763
2176
|
},
|
|
2177
|
+
onRouteChanged: withPerformanceTracking(async (filePath, changedRoutes) => {
|
|
2178
|
+
try {
|
|
2179
|
+
const finalRoutes = changedRoutes.map(
|
|
2180
|
+
(route) => prefix ? { ...route, path: `${prefix}${route.path}` } : route
|
|
2181
|
+
);
|
|
2182
|
+
const changes = updateRoutesFromFile(registry, filePath, finalRoutes);
|
|
2183
|
+
applyMatcherChanges(changes);
|
|
2184
|
+
} catch (error) {
|
|
2185
|
+
console.error(`\u26A0\uFE0F Error updating routes from ${directory}:`, error);
|
|
2186
|
+
}
|
|
2187
|
+
}, directory),
|
|
1764
2188
|
onRouteRemoved: (filePath, removedRoutes) => {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
}
|
|
1775
|
-
const sources = routeSources.get(finalPath) || [];
|
|
1776
|
-
const filteredSources = sources.filter((s) => s !== source);
|
|
1777
|
-
if (filteredSources.length > 0) {
|
|
1778
|
-
routeSources.set(finalPath, filteredSources);
|
|
1779
|
-
} else {
|
|
1780
|
-
routeSources.delete(finalPath);
|
|
1781
|
-
}
|
|
1782
|
-
});
|
|
2189
|
+
try {
|
|
2190
|
+
removedRoutes.forEach((route) => {
|
|
2191
|
+
const finalPath = prefix ? `${prefix}${route.path}` : route.path;
|
|
2192
|
+
removeRouteFromMatcher(finalPath, matcher);
|
|
2193
|
+
});
|
|
2194
|
+
clearFileCache(filePath);
|
|
2195
|
+
} catch (error) {
|
|
2196
|
+
console.error(`Error removing routes from ${filePath}:`, error);
|
|
2197
|
+
}
|
|
1783
2198
|
},
|
|
1784
2199
|
onError: (error) => {
|
|
1785
|
-
console.error(
|
|
2200
|
+
console.error(`\u26A0\uFE0F Route watcher error for ${directory}:`, error);
|
|
1786
2201
|
}
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
1789
|
-
function setupWatcherForDirectory(directory, source, prefix) {
|
|
1790
|
-
const callbacks = createWatcherCallbacks(directory, source, prefix);
|
|
1791
|
-
const watcher = watchRoutes(directory, {
|
|
1792
|
-
ignore: ["node_modules", ".git"],
|
|
1793
|
-
...callbacks
|
|
1794
2202
|
});
|
|
1795
|
-
if (!_watchers) {
|
|
1796
|
-
_watchers = /* @__PURE__ */ new Map();
|
|
1797
|
-
}
|
|
1798
2203
|
_watchers.set(directory, watcher);
|
|
1799
2204
|
return watcher;
|
|
1800
2205
|
}
|
|
1801
|
-
function setupWatcherForAllDirectories() {
|
|
1802
|
-
for (const directory of routeDirectories) {
|
|
1803
|
-
setupWatcherForDirectory(directory, directory);
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
2206
|
initialize().catch((error) => {
|
|
1807
|
-
console.error("Failed to initialize router on creation:", error);
|
|
2207
|
+
console.error("\u26A0\uFE0F Failed to initialize router on creation:", error);
|
|
1808
2208
|
});
|
|
1809
2209
|
return {
|
|
1810
2210
|
/**
|
|
@@ -1812,17 +2212,23 @@ function createRouter(options) {
|
|
|
1812
2212
|
*/
|
|
1813
2213
|
async handleRequest(ctx) {
|
|
1814
2214
|
if (!initialized) {
|
|
2215
|
+
console.log("\u{1F504} Router not initialized, initializing...");
|
|
1815
2216
|
await initialize();
|
|
1816
2217
|
}
|
|
1817
|
-
const { method, path:
|
|
1818
|
-
|
|
2218
|
+
const { method, path: path6 } = ctx.request;
|
|
2219
|
+
console.log(`
|
|
2220
|
+
\u{1F4E5} Handling request: ${method} ${path6}`);
|
|
2221
|
+
const match = matcher.match(path6, method);
|
|
1819
2222
|
if (!match) {
|
|
2223
|
+
console.log(`\u274C No match found for: ${method} ${path6}`);
|
|
1820
2224
|
ctx.response.status(404).json({ error: "Not Found" });
|
|
1821
2225
|
return;
|
|
1822
2226
|
}
|
|
2227
|
+
console.log(`\u2705 Route matched: ${method} ${path6}`);
|
|
2228
|
+
console.log(` Params: ${JSON.stringify(match.params)}`);
|
|
1823
2229
|
if (match.methodNotAllowed) {
|
|
1824
2230
|
ctx.response.status(405).json({
|
|
1825
|
-
error: "Method Not Allowed",
|
|
2231
|
+
error: "\u274C Method Not Allowed",
|
|
1826
2232
|
allowed: match.allowedMethods
|
|
1827
2233
|
});
|
|
1828
2234
|
if (match.allowedMethods && match.allowedMethods.length > 0) {
|
|
@@ -1841,19 +2247,28 @@ function createRouter(options) {
|
|
|
1841
2247
|
}
|
|
1842
2248
|
},
|
|
1843
2249
|
/**
|
|
1844
|
-
* Get all registered routes
|
|
2250
|
+
* Get all registered routes (using optimized registry)
|
|
1845
2251
|
*/
|
|
1846
2252
|
getRoutes() {
|
|
1847
|
-
return
|
|
2253
|
+
return getAllRoutesFromRegistry(registry);
|
|
1848
2254
|
},
|
|
1849
2255
|
/**
|
|
1850
2256
|
* Add a route programmatically
|
|
1851
2257
|
*/
|
|
1852
2258
|
addRoute(route) {
|
|
1853
|
-
|
|
2259
|
+
const changes = updateRoutesFromFile(registry, "programmatic", [route]);
|
|
2260
|
+
applyMatcherChanges(changes);
|
|
1854
2261
|
},
|
|
1855
2262
|
/**
|
|
1856
|
-
* Add
|
|
2263
|
+
* Add multiple routes programmatically with batch processing
|
|
2264
|
+
*/
|
|
2265
|
+
addRoutes(routes) {
|
|
2266
|
+
const changes = updateRoutesFromFile(registry, "programmatic", routes);
|
|
2267
|
+
applyMatcherChanges(changes);
|
|
2268
|
+
return changes;
|
|
2269
|
+
},
|
|
2270
|
+
/**
|
|
2271
|
+
* Add a route directory (for plugins) with optimized loading
|
|
1857
2272
|
*/
|
|
1858
2273
|
async addRouteDirectory(directory, options2 = {}) {
|
|
1859
2274
|
if (routeDirectories.has(directory)) {
|
|
@@ -1864,21 +2279,27 @@ function createRouter(options) {
|
|
|
1864
2279
|
if (initialized) {
|
|
1865
2280
|
await loadRoutesFromDirectory(directory, directory, options2.prefix);
|
|
1866
2281
|
if (routerOptions.watchMode) {
|
|
1867
|
-
|
|
2282
|
+
setupWatcherForNewDirectory(directory, options2.prefix);
|
|
1868
2283
|
}
|
|
1869
2284
|
}
|
|
1870
2285
|
},
|
|
1871
2286
|
/**
|
|
1872
|
-
* Get route conflicts
|
|
2287
|
+
* Get route conflicts (using registry)
|
|
1873
2288
|
*/
|
|
1874
2289
|
getRouteConflicts() {
|
|
1875
2290
|
const conflicts = [];
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
2291
|
+
return conflicts;
|
|
2292
|
+
},
|
|
2293
|
+
/**
|
|
2294
|
+
* Close watchers and cleanup (useful for testing)
|
|
2295
|
+
*/
|
|
2296
|
+
async close() {
|
|
2297
|
+
if (_watchers) {
|
|
2298
|
+
for (const watcher of _watchers.values()) {
|
|
2299
|
+
await watcher.close();
|
|
1879
2300
|
}
|
|
2301
|
+
_watchers.clear();
|
|
1880
2302
|
}
|
|
1881
|
-
return conflicts;
|
|
1882
2303
|
}
|
|
1883
2304
|
};
|
|
1884
2305
|
}
|