import { arrayEquals } from '#lib/kit-temp' import { type Path } from '@wollybeard/kit' // // // // // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Path // // export type Path = PathSegment[] export type PathRoot = [] export type PathTop = [PathSegment] export type PathSub = [PathSegment, PathSegment, ...PathSegment[]] export type PathSegment = string export const sep = `/` export const pathToExpression = (path: Path) => { return sep + path.join(sep) } // // // // // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Route (Generic) // // export interface Route { logical: RouteLogical file: RouteFile id: string // Absolute file path for unique identification parentId: string | null // Parent directory path, null for root-level files } export interface RouteLogical { path: Path order?: number } export interface RouteFile { path: { relative: Path.Parsed absolute: Path.Parsed } } // // // // // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Top Level Route // // export interface TopLevelRoute extends Route { logical: TopLevelRouteLogical } export interface TopLevelRouteLogical { path: PathTop } /** * Route is top level meaning exists directly under the root. * * It excludes the root level route. */ export const routeIsTopLevel = (route: Route): route is TopLevelRoute => { return route.logical.path.length === 1 } // // ━━ Sub Level // // // // // // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Sub Level Route // // export interface RouteSubLevel extends Route { logical: RoutePathSubLevel } export interface RoutePathSubLevel { path: PathSub } /** * Route is not top or root level */ export const routeIsSubLevel = (route: Route): route is RouteSubLevel => { return route.logical.path.length > 1 } // // ━━ Root Level // // // // // // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Root Level Route // // /** * Route is the singular root route. * This is the case of index under root. */ export const routeIsRootLevel = (route: Route): route is TopLevelRoute => { // No need to check for name "index" // Segments is uniquely empty for /index return route.logical.path.length === 0 } // // // // ━━━━━━━━━━━━━━ • Route Functions // // export const routeIsFromIndexFile = (route: Route): boolean => { return route.file.path.relative.name === conventions.index.name } export const routeIsSubOf = (route: Route, potentialAncestorPath: PathSegment[]): boolean => { if (route.logical.path.length <= potentialAncestorPath.length) { return false } return arrayEquals( route.logical.path.slice(0, potentialAncestorPath.length), potentialAncestorPath, ) } /** * You are responsible for ensuring given ancestor path is really an ancestor of given route's path. */ export const makeRelativeUnsafe = (route: Route, assumedAncestorPath: PathSegment[]): Route => { // We assume that we're working with paths where index is elided per our FileRouter system. const newPath = route.logical.path.slice(assumedAncestorPath.length) return { ...route, logical: { ...route.logical, path: newPath, }, } } export const routeToPathExpression = (route: Route) => { return pathToExpression(route.logical.path) } const conventions = { index: { name: `index`, }, }