import { RoutePattern } from './route-pattern.ts' import { parsePattern } from './route-pattern/parse.ts' import { Trie } from './match/trie.ts' import type { Match } from './match/types.ts' import * as Specificity from './specificity.ts' export type MatcherOptions = { /** * When `true`, pathname matching is case-insensitive for all patterns. Hostname is always * case-insensitive; search remains case-sensitive. Defaults to `false`. */ ignoreCase?: boolean } export type Matcher = { match(url: string | URL): Match | null } /** * Create a matcher for a single route pattern. * * @param pattern The route pattern to match against * @param options Options for matching URLs * @returns A matcher for the given pattern */ export function createMatcher( pattern: source | RoutePattern, options?: MatcherOptions, ): Matcher { pattern = typeof pattern === 'string' ? RoutePattern.parse(pattern) : pattern let matcher = createMultiMatcher(options) matcher.add(pattern, undefined) return { match(url: string | URL): Match | null { return matcher.match(url) as Match | null }, } } export type MultiMatcher = { readonly ignoreCase: boolean add(pattern: string | RoutePattern, data: data): void /** Most specific match for `url`, or `null` when nothing matches. */ match(url: string | URL): Match | null /** Every match for `url`, sorted from most to least specific. */ matchAll(url: string | URL): Array> } /** * Create a matcher for multiple route patterns. * * @param options Options for matching URLs * @returns A matcher that can register multiple patterns with associated data */ export function createMultiMatcher(options?: MatcherOptions): MultiMatcher { return new TrieMatcher(options) } class TrieMatcher implements MultiMatcher { readonly ignoreCase: boolean #trie: Trie constructor(options?: MatcherOptions) { this.ignoreCase = options?.ignoreCase ?? false this.#trie = new Trie({ ignoreCase: this.ignoreCase }) } add(pattern: string | RoutePattern, data: data): void { pattern = typeof pattern === 'string' ? parsePattern(pattern) : pattern this.#trie.insert(pattern, data) } match(url: string | URL): Match | null { let parsedUrl = typeof url === 'string' ? new URL(url) : url let best: Match | null = null for (let match of this.#trie.search(parsedUrl)) { if (best === null || Specificity.greaterThan(match, best)) { best = match } } return best } matchAll(url: string | URL): Array> { let parsedUrl = typeof url === 'string' ? new URL(url) : url let matches: Array> = [] for (let match of this.#trie.search(parsedUrl)) { matches.push(match) } return matches.sort(Specificity.descending) } }