@pylonsync/sdk 0.3.246 → 0.3.247

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +27 -6
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.246",
6
+ "version": "0.3.247",
7
7
  "type": "module",
8
8
  "main": "src/index.ts",
9
9
  "types": "src/index.ts",
package/src/index.ts CHANGED
@@ -781,14 +781,27 @@ export async function discoverAppRoutes(opts?: {
781
781
  }
782
782
  walk(appDir, [], []);
783
783
 
784
+ // Segment kinds, least-to-most greedy:
785
+ // static "blog" rank 0 (most specific)
786
+ // dynamic param "[slug]" rank 1 → :slug
787
+ // catch-all "[...slug]" rank 2 → *slug (matches ≥1 segment)
788
+ // optional catch-all"[[...slug]]" rank 3 → *?slug (matches ≥0 segments)
789
+ // A catch-all is only valid as the LAST segment of a route (it consumes
790
+ // the rest of the path); Next.js enforces the same.
791
+ const isOptionalCatchAll = (s: string): boolean =>
792
+ s.startsWith("[[...") && s.endsWith("]]");
793
+ const isCatchAll = (s: string): boolean =>
794
+ s.startsWith("[...") && s.endsWith("]") && !isOptionalCatchAll(s);
784
795
  const isParam = (s: string): boolean =>
785
- s.startsWith("[") && s.endsWith("]");
796
+ s.startsWith("[") && s.endsWith("]") && !isCatchAll(s) && !isOptionalCatchAll(s);
797
+ const segRank = (s: string): number =>
798
+ isOptionalCatchAll(s) ? 3 : isCatchAll(s) ? 2 : isParam(s) ? 1 : 0;
786
799
  pages.sort((a, b) => {
787
800
  const minLen = Math.min(a.segments.length, b.segments.length);
788
801
  for (let i = 0; i < minLen; i++) {
789
- const ap = isParam(a.segments[i]);
790
- const bp = isParam(b.segments[i]);
791
- if (ap !== bp) return ap ? 1 : -1;
802
+ const ar = segRank(a.segments[i]);
803
+ const br = segRank(b.segments[i]);
804
+ if (ar !== br) return ar - br; // more specific (lower rank) first
792
805
  if (a.segments[i] !== b.segments[i]) {
793
806
  return a.segments[i] < b.segments[i] ? -1 : 1;
794
807
  }
@@ -796,9 +809,17 @@ export async function discoverAppRoutes(opts?: {
796
809
  return a.segments.length - b.segments.length;
797
810
  });
798
811
 
812
+ // Convert a discovered file segment to its route-pattern token. The Rust
813
+ // matcher (frontend.rs `match_ssr_route`) reads these markers back:
814
+ // :name dynamic param *name catch-all *?name optional catch-all
815
+ const segmentToToken = (s: string): string => {
816
+ if (isOptionalCatchAll(s)) return `*?${s.slice(5, -2)}`; // [[...x]] → *?x
817
+ if (isCatchAll(s)) return `*${s.slice(4, -1)}`; // [...x] → *x
818
+ if (isParam(s)) return `:${s.slice(1, -1)}`; // [x] → :x
819
+ return s;
820
+ };
799
821
  const segmentsToPath = (segments: string[]): string =>
800
- "/" +
801
- segments.map((s) => (isParam(s) ? `:${s.slice(1, -1)}` : s)).join("/");
822
+ "/" + segments.map(segmentToToken).join("/");
802
823
 
803
824
  const pageRoutes: RouteDefinition[] = pages.map((p) => ({
804
825
  path: segmentsToPath(p.segments),