/** URL state helpers for server-side filtered pages (ยง295). */ export type SP = Record; export function str(sp: SP, key: string, fallback = ''): string { const v = sp[key]; const s = Array.isArray(v) ? v[0] : v; return (s ?? fallback).toString().trim(); } export function int(sp: SP, key: string, fallback: number, min = -Infinity, max = Infinity): number { const n = Number.parseInt(str(sp, key, ''), 10); if (!Number.isFinite(n)) return fallback; return Math.min(max, Math.max(min, n)); } export function oneOf(sp: SP, key: string, allowed: readonly T[], fallback: T): T { const v = str(sp, key, ''); return (allowed as readonly string[]).includes(v) ? (v as T) : fallback; } export function bool(sp: SP, key: string): boolean | null { const v = str(sp, key, ''); if (v === '1' || v === 'true' || v === 'yes') return true; if (v === '0' || v === 'false' || v === 'no') return false; return null; } /** Build a query string from the current params with overrides (drops empty values). */ export function withParams(current: Record, overrides: Record): string { const merged = { ...current, ...overrides }; const qs = new URLSearchParams(); for (const [k, v] of Object.entries(merged)) { if (v == null || v === '' || v === 'all' && k !== 'level') continue; qs.set(k, String(v)); } const s = qs.toString(); return s ? `?${s}` : ''; }