const REDIS_GLOB_SPECIAL_CHARS = /([*?\[\]\\])/g; const ASCII_LETTER = /^[A-Za-z]$/; export type RedisSearchMode = 'fuzzy' | 'exact'; const escapeRedisGlobLiteral = (value: string): string => { return value.replace(REDIS_GLOB_SPECIAL_CHARS, '\\$1'); }; const toCaseInsensitiveRedisGlobLiteral = (value: string): string => { return Array.from(value).map((char) => { if (!ASCII_LETTER.test(char)) { return escapeRedisGlobLiteral(char); } const lower = char.toLowerCase(); const upper = char.toUpperCase(); return `[${lower}${upper}]`; }).join(''); }; export const normalizeRedisSearchInput = ( rawValue: string, mode: RedisSearchMode = 'fuzzy', ): { keyword: string; pattern: string } => { const keyword = String(rawValue || '').trim(); if (!keyword) { return { keyword: '', pattern: '*' }; } if (mode === 'exact') { return { keyword, pattern: escapeRedisGlobLiteral(keyword), }; } return { keyword, pattern: `*${toCaseInsensitiveRedisGlobLiteral(keyword)}*`, }; }; export const normalizeRedisSearchDraftChange = (rawValue: string, mode: RedisSearchMode = 'fuzzy'): { keyword: string; pattern: string; shouldSearchImmediately: boolean; } => { const normalized = normalizeRedisSearchInput(rawValue, mode); return { ...normalized, shouldSearchImmediately: normalized.keyword === '', }; };