@emeryld/rrroutes-server 2.6.5 → 2.6.7

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 CHANGED
@@ -499,6 +499,9 @@ var defaultSend = (res, data) => {
499
499
  res.json(data);
500
500
  };
501
501
  var REGISTERED_ROUTES_SYMBOL = /* @__PURE__ */ Symbol.for("routesV3.registeredRoutes");
502
+ var REGISTERED_ROUTE_ENTRIES_SYMBOL = /* @__PURE__ */ Symbol.for(
503
+ "routesV3.registeredRouteEntries"
504
+ );
502
505
  function getRegisteredRouteStore(router) {
503
506
  const existing = router[REGISTERED_ROUTES_SYMBOL];
504
507
  if (existing) return existing;
@@ -506,6 +509,101 @@ function getRegisteredRouteStore(router) {
506
509
  router[REGISTERED_ROUTES_SYMBOL] = store;
507
510
  return store;
508
511
  }
512
+ function parseRegisteredRouteKey(key) {
513
+ const firstSpace = key.indexOf(" ");
514
+ if (firstSpace <= 0) return void 0;
515
+ const method = key.slice(0, firstSpace).toUpperCase();
516
+ const path = key.slice(firstSpace + 1);
517
+ if (!path) return void 0;
518
+ return { method, path, key, origin: "unknown (pre-existing router stack)" };
519
+ }
520
+ function getRegistrationOrigin() {
521
+ const stack = new Error().stack;
522
+ if (!stack) return "unknown";
523
+ const lines = stack.split("\n").map((line) => line.trim()).filter(Boolean);
524
+ for (const line of lines) {
525
+ if (/^Error:?$/.test(line)) continue;
526
+ if (line.includes("getRegistrationOrigin") || line.includes("findShadowingPriorRoute") || line.includes("createRRRoute") || line.includes("register (") || line.includes("routesV3.server.ts") || line.includes("<anonymous>") || line.includes("node_modules") || line.includes("internal/")) {
527
+ continue;
528
+ }
529
+ return line.replace(/^at\s+/, "");
530
+ }
531
+ return lines.find((line) => !/^Error:?$/.test(line))?.replace(/^at\s+/, "") ?? "unknown";
532
+ }
533
+ function getRegisteredRouteEntriesStore(router) {
534
+ const existing = router[REGISTERED_ROUTE_ENTRIES_SYMBOL];
535
+ if (existing) return existing;
536
+ const seeded = collectRoutesFromStack(router).map(parseRegisteredRouteKey).filter((entry) => Boolean(entry));
537
+ router[REGISTERED_ROUTE_ENTRIES_SYMBOL] = seeded;
538
+ return seeded;
539
+ }
540
+ function toParsedRouteSegments(path) {
541
+ const segments = path.split("/").filter(Boolean);
542
+ return segments.map((segment) => {
543
+ if (segment.startsWith("*") && segment.length > 1) return { kind: "wildcard" };
544
+ if (segment.startsWith(":") && segment.length > 1) return { kind: "param" };
545
+ return { kind: "static", value: segment };
546
+ });
547
+ }
548
+ function buildProbePathForRoute(path) {
549
+ const parsed = toParsedRouteSegments(path);
550
+ const segments = [];
551
+ for (const segment of parsed) {
552
+ if (segment.kind === "static") {
553
+ segments.push(segment.value);
554
+ continue;
555
+ }
556
+ if (segment.kind === "param") {
557
+ segments.push("__rrroutes_param__");
558
+ continue;
559
+ }
560
+ segments.push("__rrroutes_splat__", "__rrroutes_tail__");
561
+ }
562
+ return `/${segments.join("/")}`.replace(/\/+/g, "/");
563
+ }
564
+ function routePatternMatchesConcretePath(routePattern, concretePath) {
565
+ const patternSegments = toParsedRouteSegments(routePattern);
566
+ const pathSegments = concretePath.split("/").filter(Boolean);
567
+ let i = 0;
568
+ let j = 0;
569
+ while (i < patternSegments.length) {
570
+ const pattern = patternSegments[i];
571
+ if (pattern.kind === "wildcard") {
572
+ return j < pathSegments.length;
573
+ }
574
+ if (j >= pathSegments.length) return false;
575
+ if (pattern.kind === "param") {
576
+ i += 1;
577
+ j += 1;
578
+ continue;
579
+ }
580
+ if (pattern.value !== pathSegments[j]) return false;
581
+ i += 1;
582
+ j += 1;
583
+ }
584
+ return j === pathSegments.length;
585
+ }
586
+ function findShadowingPriorRoute(entries, method, path) {
587
+ const probePath = buildProbePathForRoute(path);
588
+ return entries.find((entry) => {
589
+ if (entry.method !== method) return false;
590
+ if (entry.path === path) return true;
591
+ return routePatternMatchesConcretePath(entry.path, probePath);
592
+ });
593
+ }
594
+ function formatRouteShadowWarning(args) {
595
+ const { method, path, currentOrigin, prior } = args;
596
+ const newRoute = `${method} ${path}`;
597
+ const priorRoute = `${prior.method} ${prior.path}`;
598
+ return [
599
+ "[rrroutes-server][route-shadow-warning]",
600
+ ` New route: ${newRoute}`,
601
+ ` Existing route: ${priorRoute}`,
602
+ ` Precedence: ${priorRoute} takes precedence (registered first)`,
603
+ ` New origin: ${currentOrigin}`,
604
+ ` Existing origin: ${prior.origin}`
605
+ ].join("\n");
606
+ }
509
607
  function collectRoutesFromStack(appOrRouter) {
510
608
  const result = [];
511
609
  const stack = appOrRouter.stack ?? (appOrRouter._router ? appOrRouter._router.stack : void 0) ?? [];
@@ -542,6 +640,7 @@ function createRRRoute(router, config) {
542
640
  const preCtxMws = [...middlewareConfig.preCtx ?? []];
543
641
  const sanitizerMw = middlewareConfig.sanitizer === void 0 ? void 0 : typeof middlewareConfig.sanitizer === "function" ? middlewareConfig.sanitizer : createRequestSanitizationMiddleware(middlewareConfig.sanitizer);
544
642
  const registered = getRegisteredRouteStore(router);
643
+ const registeredEntries = getRegisteredRouteEntriesStore(router);
545
644
  const getMulterOptions = (fields) => {
546
645
  if (!fields || fields.length === 0) return void 0;
547
646
  const resolved = typeof config.multerOptions === "function" ? config.multerOptions(fields) : config.multerOptions;
@@ -584,6 +683,18 @@ function createRRRoute(router, config) {
584
683
  const activeDebugMode = routeDebugEmitter?.mode ?? defaultDebugMode;
585
684
  const emit = (event) => activeEmit(event, debugName);
586
685
  const isVerboseDebug = activeDebugMode === "complete";
686
+ const origin = getRegistrationOrigin();
687
+ const shadowingEntry = findShadowingPriorRoute(registeredEntries, methodUpper, path);
688
+ if (shadowingEntry) {
689
+ console.warn(
690
+ formatRouteShadowWarning({
691
+ method: methodUpper,
692
+ path,
693
+ currentOrigin: origin,
694
+ prior: shadowingEntry
695
+ })
696
+ );
697
+ }
587
698
  emit({ type: "register", method: methodUpper, path });
588
699
  const routeSpecific = (def?.before ?? []).map(
589
700
  (mw) => adaptRouteBeforeMw(mw)
@@ -832,6 +943,7 @@ function createRRRoute(router, config) {
832
943
  };
833
944
  router[method](path, ...before, wrapped);
834
945
  registered.add(key);
946
+ registeredEntries.push({ method: methodUpper, path, key, origin });
835
947
  registeredDefs.set(key, {
836
948
  leaf,
837
949
  def