|
| 1 | +/** |
| 2 | + * Shared redirect hot path: inlined top bangs + zero-copy overlays when possible. |
| 3 | + */ |
| 4 | +import { inflateBangs } from "./bang-compact"; |
| 5 | +import { HOT_BANGS } from "./bangs-hot.generated"; |
| 6 | +import { searxSearchTemplate } from "./searx"; |
| 7 | +import { |
| 8 | + buildBangMap, |
| 9 | + ensureEssentialBangs, |
| 10 | + extractBangTrigger, |
| 11 | + longestTriggerPrefix, |
| 12 | + type Bang, |
| 13 | +} from "../src/redirect"; |
| 14 | + |
| 15 | +/** Built once at module load — never mutate. */ |
| 16 | +export const INLINE_HOT_MAP: ReadonlyMap<string, Bang> = ensureEssentialBangs( |
| 17 | + buildBangMap(inflateBangs([...HOT_BANGS])), |
| 18 | +); |
| 19 | + |
| 20 | +export type HotOverlayPrefs = { |
| 21 | + customBangs?: readonly Bang[]; |
| 22 | + customSearxUrl?: string; |
| 23 | +}; |
| 24 | + |
| 25 | +/** |
| 26 | + * Apply custom bangs / Searx host on top of a base map. |
| 27 | + * Returns the same Map instance when overlays are empty (zero clone). |
| 28 | + */ |
| 29 | +export function withPrefsOverlays( |
| 30 | + base: ReadonlyMap<string, Bang>, |
| 31 | + prefs: HotOverlayPrefs, |
| 32 | +): Map<string, Bang> { |
| 33 | + const customs = prefs.customBangs; |
| 34 | + const host = prefs.customSearxUrl?.trim() ?? ""; |
| 35 | + const hasCustoms = Boolean(customs && customs.length > 0); |
| 36 | + |
| 37 | + let needsSearx = false; |
| 38 | + if (host) { |
| 39 | + const template = searxSearchTemplate(host); |
| 40 | + const cur = base.get("searxng") ?? base.get("searx"); |
| 41 | + needsSearx = !cur || cur.u !== template; |
| 42 | + } |
| 43 | + |
| 44 | + if (!hasCustoms && !needsSearx) { |
| 45 | + return base instanceof Map ? base : new Map(base); |
| 46 | + } |
| 47 | + |
| 48 | + const map = ensureEssentialBangs(new Map(base)); |
| 49 | + if (customs) { |
| 50 | + for (const bang of customs) map.set(bang.t, bang); |
| 51 | + } |
| 52 | + if (needsSearx && host) { |
| 53 | + const u = searxSearchTemplate(host); |
| 54 | + for (const t of ["searx", "searxng"] as const) { |
| 55 | + map.set(t, { t, d: host, u, s: "SearxNG" }); |
| 56 | + } |
| 57 | + } |
| 58 | + return map; |
| 59 | +} |
| 60 | + |
| 61 | +/** |
| 62 | + * True when `query` can be resolved correctly from `map` alone |
| 63 | + * (no need to await the full catalog for a rare bang). |
| 64 | + */ |
| 65 | +export function canResolveWithMap( |
| 66 | + query: string, |
| 67 | + map: ReadonlyMap<string, Bang>, |
| 68 | + bangPrefix = "!", |
| 69 | +): boolean { |
| 70 | + const hint = extractBangTrigger(query, bangPrefix); |
| 71 | + if (!hint) return true; |
| 72 | + if (map.has(hint)) return true; |
| 73 | + if (longestTriggerPrefix(hint, map as Map<string, Bang>)) return true; |
| 74 | + return false; |
| 75 | +} |
0 commit comments