import { createMemo, getOwner, runWithOwner } from "solid-js"; const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i; const trimPathRegex = /^\/+|(\/)\/+$/g; export const mockBase = "http://sr"; export function normalizePath(path, omitSlash = false) { const s = path.replace(trimPathRegex, "$1"); return s ? (omitSlash || /^[?#]/.test(s) ? s : "/" + s) : ""; } export function resolvePath(base, path, from) { if (hasSchemeRegex.test(path)) { return undefined; } const basePath = normalizePath(base); const fromPath = from && normalizePath(from); let result = ""; if (!fromPath || path.startsWith("/")) { result = basePath; } else if (fromPath.toLowerCase().indexOf(basePath.toLowerCase()) !== 0) { result = basePath + fromPath; } else { result = fromPath; } return (result || "/") + normalizePath(path, !result); } export function invariant(value, message) { if (value == null) { throw new Error(message); } return value; } export function joinPaths(from, to) { return normalizePath(from).replace(/\/*(\*.*)?$/g, "") + normalizePath(to); } export function extractSearchParams(url) { const params = {}; url.searchParams.forEach((value, key) => { if (key in params) { if (Array.isArray(params[key])) params[key].push(value); else params[key] = [params[key], value]; } else params[key] = value; }); return params; } export function createMatcher(path, partial, matchFilters) { const [pattern, splat] = path.split("/*", 2); const segments = pattern.split("/").filter(Boolean); const len = segments.length; return (location) => { const locSegments = location.split("/").filter(Boolean); const lenDiff = locSegments.length - len; if (lenDiff < 0 || (lenDiff > 0 && splat === undefined && !partial)) { return null; } const match = { path: len ? "" : "/", params: {} }; const matchFilter = (s) => matchFilters === undefined ? undefined : matchFilters[s]; for (let i = 0; i < len; i++) { const segment = segments[i]; const dynamic = segment[0] === ":"; const locSegment = dynamic ? locSegments[i] : locSegments[i].toLowerCase(); const key = dynamic ? segment.slice(1) : segment.toLowerCase(); if (dynamic && matchSegment(locSegment, matchFilter(key))) { match.params[key] = locSegment; } else if (dynamic || !matchSegment(locSegment, key)) { return null; } match.path += `/${locSegment}`; } if (splat) { const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : ""; if (matchSegment(remainder, matchFilter(splat))) { match.params[splat] = remainder; } else { return null; } } return match; }; } function matchSegment(input, filter) { const isEqual = (s) => s === input; if (filter === undefined) { return true; } else if (typeof filter === "string") { return isEqual(filter); } else if (typeof filter === "function") { return filter(input); } else if (Array.isArray(filter)) { return filter.some(isEqual); } else if (filter instanceof RegExp) { return filter.test(input); } return false; } export function scoreRoute(route) { const [pattern, splat] = route.pattern.split("/*", 2); const segments = pattern.split("/").filter(Boolean); return segments.reduce((score, segment) => score + (segment.startsWith(":") ? 2 : 3), segments.length - (splat === undefined ? 0 : 1)); } export function createMemoObject(fn) { const map = new Map(); const owner = getOwner(); return new Proxy({}, { get(_, property) { if (!map.has(property)) { runWithOwner(owner, () => map.set(property, createMemo(() => fn()[property]))); } return map.get(property)(); }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true }; }, ownKeys() { return Reflect.ownKeys(fn()); }, has(_, property) { return property in fn(); } }); } export function mergeSearchString(search, params) { const merged = new URLSearchParams(search); Object.entries(params).forEach(([key, value]) => { if (value == null || value === "" || (value instanceof Array && !value.length)) { merged.delete(key); } else { if (value instanceof Array) { // Delete all instances of the key before appending merged.delete(key); value.forEach(v => { merged.append(key, String(v)); }); } else { merged.set(key, String(value)); } } }); const s = merged.toString(); return s ? `?${s}` : ""; } export function expandOptionals(pattern) { let match = /(\/?\:[^\/]+)\?/.exec(pattern); if (!match) return [pattern]; let prefix = pattern.slice(0, match.index); let suffix = pattern.slice(match.index + match[0].length); const prefixes = [prefix, (prefix += match[1])]; // This section handles adjacent optional params. We don't actually want all permuations since // that will lead to equivalent routes which have the same number of params. For example // `/:a?/:b?/:c`? only has the unique expansion: `/`, `/:a`, `/:a/:b`, `/:a/:b/:c` and we can // discard `/:b`, `/:c`, `/:b/:c` by building them up in order and not recursing. This also helps // ensure predictability where earlier params have precidence. while ((match = /^(\/\:[^\/]+)\?/.exec(suffix))) { prefixes.push((prefix += match[1])); suffix = suffix.slice(match[0].length); } return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []); } export function setFunctionName(obj, value) { Object.defineProperty(obj, "name", { value, writable: false, configurable: false }); return obj; }