@jay-framework/stack-route-scanner 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -13,14 +13,50 @@ type JayRoute = {
13
13
  rawRoute: string;
14
14
  jayHtmlPath: string;
15
15
  compPath: string;
16
+ /**
17
+ * For static override routes, inferred params from sibling dynamic routes.
18
+ * e.g., /products/ceramic-flower-vase has inferredParams: { slug: 'ceramic-flower-vase' }
19
+ * when /products/[slug] exists as a sibling.
20
+ */
21
+ inferredParams?: Record<string, string>;
16
22
  };
17
23
  type JayRoutes = JayRoute[];
18
24
  interface ScanFilesOptions {
19
25
  jayHtmlFilename: string;
20
26
  compFilename: string;
21
27
  }
28
+ /**
29
+ * Sort routes by priority so that more specific routes match first.
30
+ * Static routes come before dynamic routes at the same position.
31
+ */
32
+ declare function sortRoutesByPriority(routes: JayRoutes): JayRoutes;
33
+ /**
34
+ * Result of param inference for logging/debugging.
35
+ */
36
+ interface ParamInferenceResult {
37
+ staticRoute: string;
38
+ dynamicRoute: string;
39
+ inferredParams: Record<string, string>;
40
+ }
41
+ /**
42
+ * Infer params for static routes based on sibling dynamic routes.
43
+ *
44
+ * For each fully-static route, find a sibling dynamic route and map
45
+ * the static segment values to the param names from the dynamic route.
46
+ *
47
+ * Example:
48
+ * /products/ceramic-flower-vase (static)
49
+ * /products/[slug] (dynamic sibling)
50
+ * → inferredParams: { slug: 'ceramic-flower-vase' }
51
+ *
52
+ * Returns the routes with inferred params added, plus an inference log.
53
+ */
54
+ declare function inferParamsForStaticRoutes(routes: JayRoutes): {
55
+ routes: JayRoutes;
56
+ inferenceLog: ParamInferenceResult[];
57
+ };
22
58
  declare function scanRoutes(baseDir: string, options: ScanFilesOptions): Promise<JayRoutes>;
23
59
 
24
60
  declare function routeToExpressRoute(route: JayRoute): string;
25
61
 
26
- export { type JayRoute, type JayRouteParam, JayRouteParamType, type JayRouteSegment, type JayRoutes, type ScanFilesOptions, routeToExpressRoute, scanRoutes };
62
+ export { type JayRoute, type JayRouteParam, JayRouteParamType, type JayRouteSegment, type JayRoutes, type ParamInferenceResult, type ScanFilesOptions, inferParamsForStaticRoutes, routeToExpressRoute, scanRoutes, sortRoutesByPriority };
package/dist/index.js CHANGED
@@ -41,9 +41,110 @@ async function scanDirectory(BASE_DIR, directory, options) {
41
41
  }
42
42
  return routes;
43
43
  }
44
+ function getSegmentPriority(segment) {
45
+ if (typeof segment === "string")
46
+ return 0;
47
+ switch (segment.type) {
48
+ case 0:
49
+ return 1;
50
+ case 2:
51
+ return 2;
52
+ case 1:
53
+ return 3;
54
+ }
55
+ }
56
+ function compareRoutes(a, b) {
57
+ const maxLen = Math.max(a.segments.length, b.segments.length);
58
+ for (let i = 0; i < maxLen; i++) {
59
+ const segA = a.segments[i];
60
+ const segB = b.segments[i];
61
+ if (segA === void 0)
62
+ return 1;
63
+ if (segB === void 0)
64
+ return -1;
65
+ const priorityA = getSegmentPriority(segA);
66
+ const priorityB = getSegmentPriority(segB);
67
+ if (priorityA !== priorityB)
68
+ return priorityA - priorityB;
69
+ if (typeof segA === "string" && typeof segB === "string") {
70
+ const cmp = segA.localeCompare(segB);
71
+ if (cmp !== 0)
72
+ return cmp;
73
+ }
74
+ }
75
+ return 0;
76
+ }
77
+ function sortRoutesByPriority(routes) {
78
+ return [...routes].sort(compareRoutes);
79
+ }
80
+ function hasDynamicSegments(route) {
81
+ return route.segments.some((seg) => typeof seg !== "string");
82
+ }
83
+ function isFullyStaticRoute(route) {
84
+ return route.segments.every((seg) => typeof seg === "string");
85
+ }
86
+ function dynamicRouteCouldMatch(staticRoute, dynamicRoute) {
87
+ if (staticRoute.segments.length !== dynamicRoute.segments.length)
88
+ return false;
89
+ if (staticRoute.segments.length === 0)
90
+ return false;
91
+ for (let i = 0; i < staticRoute.segments.length; i++) {
92
+ const staticSeg = staticRoute.segments[i];
93
+ const dynSeg = dynamicRoute.segments[i];
94
+ if (typeof staticSeg !== "string")
95
+ return false;
96
+ if (typeof dynSeg === "string") {
97
+ if (staticSeg !== dynSeg)
98
+ return false;
99
+ }
100
+ }
101
+ return true;
102
+ }
103
+ function inferParamsForStaticRoutes(routes) {
104
+ const inferenceLog = [];
105
+ const dynamicRoutes = routes.filter(hasDynamicSegments);
106
+ const enrichedRoutes = routes.map((route) => {
107
+ if (!isFullyStaticRoute(route))
108
+ return route;
109
+ if (route.segments.length === 0)
110
+ return route;
111
+ const dynamicSibling = dynamicRoutes.find((dyn) => dynamicRouteCouldMatch(route, dyn));
112
+ if (!dynamicSibling)
113
+ return route;
114
+ const inferredParams = {};
115
+ for (let i = 0; i < route.segments.length; i++) {
116
+ const staticSeg = route.segments[i];
117
+ const dynSeg = dynamicSibling.segments[i];
118
+ if (typeof staticSeg === "string" && typeof dynSeg !== "string") {
119
+ inferredParams[dynSeg.name] = staticSeg;
120
+ }
121
+ }
122
+ if (Object.keys(inferredParams).length === 0)
123
+ return route;
124
+ inferenceLog.push({
125
+ staticRoute: route.rawRoute,
126
+ dynamicRoute: dynamicSibling.rawRoute,
127
+ inferredParams
128
+ });
129
+ return { ...route, inferredParams };
130
+ });
131
+ return { routes: enrichedRoutes, inferenceLog };
132
+ }
44
133
  async function scanRoutes(baseDir, options) {
45
134
  const BASE_DIR = path.resolve(baseDir);
46
- return await scanDirectory(BASE_DIR, BASE_DIR, options);
135
+ const routes = await scanDirectory(BASE_DIR, BASE_DIR, options);
136
+ const sortedRoutes = sortRoutesByPriority(routes);
137
+ const { routes: enrichedRoutes, inferenceLog } = inferParamsForStaticRoutes(sortedRoutes);
138
+ if (inferenceLog.length > 0) {
139
+ console.log("[route-scanner] Inferred params for static override routes:");
140
+ for (const entry of inferenceLog) {
141
+ console.log(
142
+ ` ${entry.staticRoute} → params from ${entry.dynamicRoute}:`,
143
+ entry.inferredParams
144
+ );
145
+ }
146
+ }
147
+ return enrichedRoutes;
47
148
  }
48
149
  function routeToExpressRoute(route) {
49
150
  return "/" + route.segments.map((segment) => {
@@ -60,5 +161,7 @@ function routeToExpressRoute(route) {
60
161
  }).join("/");
61
162
  }
62
163
  exports.JayRouteParamType = JayRouteParamType;
164
+ exports.inferParamsForStaticRoutes = inferParamsForStaticRoutes;
63
165
  exports.routeToExpressRoute = routeToExpressRoute;
64
166
  exports.scanRoutes = scanRoutes;
167
+ exports.sortRoutesByPriority = sortRoutesByPriority;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/stack-route-scanner",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.mts",
@@ -20,7 +20,7 @@
20
20
  "test:watch": "vitest"
21
21
  },
22
22
  "devDependencies": {
23
- "@jay-framework/dev-environment": "^0.10.0",
23
+ "@jay-framework/dev-environment": "^0.11.0",
24
24
  "@types/node": "^20.11.5",
25
25
  "nodemon": "^3.0.3",
26
26
  "replace-in-file": "^7.1.0",