/** * Joins multiple path segments into a single path string. * Handles null, undefined, and URL objects gracefully. * * @public * @param paths - An array of path segments to join. * @returns A single joined path string. */ export const joinUrlPaths = ( ...paths: Array ): string => { let nextPath = null; const safeJoinSegments = ( segment1: string | null | undefined, segment2: string | null | undefined, ): string => { if (!segment1) { if (!segment2) { return ""; } return segment2; } if (!segment2) { return segment1; } if (segment1.endsWith("/") && segment2.startsWith("/")) { return segment1 + segment2.slice(1); } if (segment1.endsWith("/") || segment2.startsWith("/")) { return segment1 + segment2; } return `${segment1}/${segment2}`; }; const validPaths = paths.filter( (path) => path !== null && path !== undefined, ); for (const path of validPaths) { nextPath = safeJoinSegments( nextPath, path instanceof URL ? path.pathname : path, ); } return nextPath ?? ""; }; /** * Returns a normalized path by ensuring it starts with a single leading slash * and does not end with a trailing slash (unless it's the root path). */ export function normalizePath(pathname: string): string { if (!pathname.startsWith("/")) { pathname = `/${pathname}`; } if (pathname !== "/" && pathname.endsWith("/")) { pathname = pathname.slice(0, -1); } return pathname; } /** * The pathname, search, and hash values of a URL. */ interface Path { /** * A URL pathname, beginning with a /. */ pathname: string; /** * A URL search string, beginning with a ?. */ search: string; /** * A URL fragment identifier, beginning with a #. */ hash: string; } /** * Parses a string URL path into its separate pathname, search, and hash components. */ export function parsePath(path: string): Partial { let parsedPath: Partial = {}; if (path) { let hashIndex = path.indexOf("#"); if (hashIndex >= 0) { parsedPath.hash = path.substring(hashIndex); path = path.substring(0, hashIndex); } let searchIndex = path.indexOf("?"); if (searchIndex >= 0) { parsedPath.search = path.substring(searchIndex); path = path.substring(0, searchIndex); } if (path) { parsedPath.pathname = path; } } return parsedPath; } /** * Creates a string URL path from the given pathname, search, and hash components. */ export function createPath({ pathname = "/", search = "", hash = "", }: Partial) { if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : `?${search}`; if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : `#${hash}`; return pathname; }