| 148 | } |
| 149 | |
| 150 | function createCache<T>(ttlMs: number) { |
| 151 | let data: T | null = null; |
| 152 | let cachedAt = 0; |
| 153 | let inflight: Promise<T> | null = null; |
| 154 | |
| 155 | return { |
| 156 | async get(fetcher: () => Promise<T>): Promise<T> { |
| 157 | if (data !== null && Date.now() - cachedAt < ttlMs) return data; |
| 158 | if (inflight) return inflight; |
| 159 | inflight = fetcher().then(result => { |
| 160 | data = result; |
| 161 | cachedAt = Date.now(); |
| 162 | return result; |
| 163 | }).finally(() => { inflight = null; }); |
| 164 | return inflight; |
| 165 | }, |
| 166 | invalidate() { data = null; cachedAt = 0; }, |
| 167 | }; |
| 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Normalize a person name for matching: lowercase, remove middle initials, |