Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
1 line
No EOL
12 KiB
Text
1 line
No EOL
12 KiB
Text
{"version":3,"sources":["../../../../../src/shared/lib/router/utils/sortable-routes.ts"],"sourcesContent":["/**\n * A route that can be sorted by specificity.\n */\nexport type SortableRoute = {\n /**\n * The source page of the route. This represents the original page that's on\n * disk. For example, the `app/[lang]/[...rest]/page.tsx` would have a source\n * page of '/[lang]/[...rest]'.\n */\n readonly sourcePage: string\n\n /**\n * The page of the route. This represents the final rendered route. For\n * example, the `app/[lang]/[...rest]/page.tsx` that was rendered with a lang\n * value of `en` would have a page of '/en/[...rest]'.\n */\n readonly page: string\n}\n\n/**\n * Determines the specificity of a route segment for sorting purposes.\n *\n * In Next.js routing, more specific routes should match before less specific ones.\n * This function returns a numeric value where lower numbers indicate higher specificity.\n *\n * Specificity order (most to least specific):\n * 1. Static segments (e.g., \"about\", \"api\") - return 0\n * 2. Dynamic segments (e.g., \"[id]\", \"[slug]\") - return 1\n * 3. Catch-all segments (e.g., \"[...slug]\") - return 2\n * 4. Optional catch-all segments (e.g., \"[[...slug]]\") - return 3\n *\n * @param segment - A single path segment (e.g., \"api\", \"[id]\", \"[...slug]\")\n * @returns A numeric specificity value (0-3, where 0 is most specific)\n */\nexport function getSegmentSpecificity(segment: string): number {\n // Static segments are most specific - they match exactly one path\n if (!segment.includes('[')) {\n return 0\n }\n\n // Optional catch-all [[...param]] is least specific - matches zero or more segments\n if (segment.startsWith('[[...') && segment.endsWith(']]')) {\n return 3\n }\n\n // Catch-all [...param] is less specific - matches one or more segments\n if (segment.startsWith('[...') && segment.endsWith(']')) {\n return 2\n }\n\n // Regular dynamic [param] is more specific than catch-all - matches exactly one segment\n if (segment.startsWith('[') && segment.endsWith(']')) {\n return 1\n }\n\n // Default to static (fallback case)\n return 0\n}\n\n/**\n * Compares two route paths using a depth-first traversal approach.\n *\n * This function implements a deterministic comparison that sorts routes by specificity:\n * 1. More specific routes come first (fewer dynamic segments)\n * 2. Shorter routes are more specific than longer ones\n * 3. Routes with same specificity are sorted lexicographically\n *\n * The comparison is done segment by segment, left to right, similar to how\n * you would traverse a route tree in depth-first order.\n *\n * @param pathA - First route path to compare (e.g., \"/api/users/[id]\")\n * @param pathB - Second route path to compare (e.g., \"/api/[...slug]\")\n * @returns Negative if pathA is more specific, positive if pathB is more specific, 0 if equal\n */\nexport function compareRouteSegments(pathA: string, pathB: string): number {\n // Split paths into segments, removing empty strings from leading/trailing slashes\n const segmentsA = pathA.split('/').filter(Boolean)\n const segmentsB = pathB.split('/').filter(Boolean)\n\n // Compare segment by segment up to the length of the longer path\n const maxLength = Math.max(segmentsA.length, segmentsB.length)\n\n for (let i = 0; i < maxLength; i++) {\n const segA = segmentsA[i] || ''\n const segB = segmentsB[i] || ''\n\n // Handle length differences: shorter routes are MORE specific\n // Example: \"/api\" is more specific than \"/api/users\"\n if (!segA && segB) return -1 // pathA is shorter, so more specific\n if (segA && !segB) return 1 // pathB is shorter, so more specific\n if (!segA && !segB) return 0 // Both paths ended, they're equal\n\n // Compare segment specificity using our specificity scoring\n const specificityA = getSegmentSpecificity(segA)\n const specificityB = getSegmentSpecificity(segB)\n\n // Lower specificity number = more specific route\n // Example: \"api\" (0) vs \"[slug]\" (1) - \"api\" wins\n if (specificityA !== specificityB) {\n return specificityA - specificityB\n }\n\n // If segments have same specificity, compare lexicographically for determinism\n // Example: \"[id]\" vs \"[slug]\" - \"[id]\" comes first alphabetically\n if (segA !== segB) {\n return segA.localeCompare(segB)\n }\n\n // Segments are identical, continue to next segment\n }\n\n // All segments compared equally\n return 0\n}\n\n/**\n * Compares two complete routes for sorting purposes.\n *\n * Routes are compared with a two-tier priority system:\n * 1. Primary: Compare by source path specificity\n * 2. Secondary: If sources are equal, compare by page path specificity\n *\n * This ensures that routes are primarily organized by their source patterns,\n * with page-specific variations grouped together.\n *\n * @param a - First route to compare\n * @param b - Second route to compare\n * @returns Negative if route a should come first, positive if route b should come first, 0 if equal\n */\nfunction compareSortableRoutes(a: SortableRoute, b: SortableRoute): number {\n // First compare by source specificity - this is the primary sorting criterion\n // Source represents the original route pattern and takes precedence\n const sourceResult = compareRouteSegments(a.sourcePage, b.sourcePage)\n if (sourceResult !== 0) return sourceResult\n\n // If sources are identical, compare by page specificity as a tiebreaker\n // Page represents the final rendered route and provides secondary ordering\n return compareRouteSegments(a.page, b.page)\n}\n\n/**\n * Sorts an array of routes by specificity using a deterministic depth-first traversal approach.\n *\n * This function implements Next.js route matching priority where more specific routes\n * should be matched before less specific ones. The sorting is deterministic and stable,\n * meaning identical inputs will always produce identical outputs.\n *\n * Sorting criteria (in order of priority):\n * 1. Source path specificity (primary)\n * 2. Page path specificity (secondary)\n * 3. Lexicographic ordering (tertiary, for determinism)\n *\n * Examples of specificity order:\n * - \"/api/users\" (static) comes before \"/api/[slug]\" (dynamic)\n * - \"/api/[id]\" (dynamic) comes before \"/api/[...slug]\" (catch-all)\n * - \"/api/[...slug]\" (catch-all) comes before \"/api/[[...slug]]\" (optional catch-all)\n *\n * @param routes - Array of routes to sort\n * @returns New sorted array (does not mutate input)\n */\nexport function sortSortableRoutes(\n routes: readonly SortableRoute[]\n): readonly SortableRoute[] {\n // Because sort is always in-place, we need to create a shallow copy to avoid\n // mutating the input array.\n return [...routes].sort(compareSortableRoutes)\n}\n\n/**\n * Sorts an array of pages by specificity using a deterministic depth-first\n * traversal approach.\n *\n * @param pages - Array of pages to sort\n * @returns New sorted array (does not mutate input)\n */\nexport function sortPages(pages: readonly string[]): readonly string[] {\n // Because sort is always in-place, we need to create a shallow copy to avoid\n // mutating the input array.\n return [...pages].sort(compareRouteSegments)\n}\n\n/**\n * Sorts an array of objects by sourcePage and page using a deterministic\n * depth-first traversal approach.\n *\n * @param objects - Array of objects to sort\n * @param getter - Function to get the sourcePage and page from an object\n * @returns New sorted array (does not mutate input)\n */\nexport function sortSortableRouteObjects<T>(\n objects: readonly T[],\n getter: (object: T) => SortableRoute\n): readonly T[] {\n // Create a SortableRoute for each object.\n const routes: Array<SortableRoute & { object: T }> = []\n for (const object of objects) {\n const route = getter(object)\n routes.push({ ...route, object })\n }\n\n // In-place sort the SortableRoutes.\n routes.sort(compareSortableRoutes)\n\n // Map the sorted SortableRoutes back to the original objects.\n return routes.map(({ object }) => object)\n}\n\n/**\n * Sorts an array of objects by page using a deterministic depth-first traversal\n * approach.\n *\n * @param objects - Array of objects to sort\n * @param getter - Function to get the page from an object\n * @returns New sorted array (does not mutate input)\n */\nexport function sortPageObjects<T>(\n objects: readonly T[],\n getter: (object: T) => string\n): readonly T[] {\n const indexes: Record<string, number[]> = {}\n const pages: Set<string> = new Set()\n for (let i = 0; i < objects.length; i++) {\n const object = objects[i]\n const page = getter(object)\n indexes[page]?.push(i) || (indexes[page] = [i])\n pages.add(page)\n }\n\n // Sort the unique pages.\n const sortedPages = Array.from(pages).sort(compareRouteSegments)\n\n // Map the sorted pages back to the original objects.\n return sortedPages.reduce<T[]>((sortedObjects, page) => {\n // Add all objects for this page to the sorted array.\n for (const i of indexes[page]) {\n sortedObjects.push(objects[i])\n }\n\n // Return the sorted array.\n return sortedObjects\n }, [])\n}\n"],"names":["compareRouteSegments","getSegmentSpecificity","sortPageObjects","sortPages","sortSortableRouteObjects","sortSortableRoutes","segment","includes","startsWith","endsWith","pathA","pathB","segmentsA","split","filter","Boolean","segmentsB","maxLength","Math","max","length","i","segA","segB","specificityA","specificityB","localeCompare","compareSortableRoutes","a","b","sourceResult","sourcePage","page","routes","sort","pages","objects","getter","object","route","push","map","indexes","Set","add","sortedPages","Array","from","reduce","sortedObjects"],"mappings":"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","ignoreList":[0]} |