(cacheKey: string, statementId: string)
| 206 | // ── Fetch execution ────────────────────────────────────────────────────── |
| 207 | |
| 208 | async function executeFetch(cacheKey: string, statementId: string) { |
| 209 | if (!toolProvider) return; |
| 210 | |
| 211 | const q = queries.get(statementId); |
| 212 | if (!q) return; |
| 213 | |
| 214 | // Capture the cache key at fetch start — if the query's key changes |
| 215 | // while we're in-flight (deps changed), this fetch is stale. |
| 216 | const fetchKey = cacheKey; |
| 217 | const toolName = q.toolName; |
| 218 | const args = q.args; |
| 219 | |
| 220 | // Mark in-flight — use undefined as "no data yet" sentinel (distinct from null) |
| 221 | let entry = cache.get(fetchKey); |
| 222 | if (!entry) { |
| 223 | entry = { data: undefined, inFlight: true }; |
| 224 | cache.set(fetchKey, entry); |
| 225 | } else { |
| 226 | entry.inFlight = true; |
| 227 | } |
| 228 | q.loading = true; |
| 229 | rebuildSnapshot(); |
| 230 | notify(); |
| 231 | |
| 232 | try { |
| 233 | const data = await toolProvider.callTool(toolName, (args as Record<string, unknown>) ?? {}); |
| 234 | if (disposed) return; |
| 235 | // Query removed or moved to a different cache key while in-flight — discard |
| 236 | const current = queries.get(statementId); |
| 237 | if (!current || current.cacheKey !== fetchKey) { |
| 238 | entry.inFlight = false; |
| 239 | return; |
| 240 | } |
| 241 | |
| 242 | entry.data = data ?? null; |
| 243 | current.everFetched = true; |
| 244 | current.error = undefined; |
| 245 | |
| 246 | // Clean up previous cache entry — clear prevCacheKey FIRST so |
| 247 | // cleanupCacheEntry doesn't see this query as a live reference. |
| 248 | if (current.prevCacheKey && current.prevCacheKey !== fetchKey) { |
| 249 | const prevKey = current.prevCacheKey; |
| 250 | current.prevCacheKey = undefined; |
| 251 | cleanupCacheEntry(prevKey); |
| 252 | } |
| 253 | } catch (err) { |
| 254 | // Only update error state if this fetch is still current |
| 255 | const current = queries.get(statementId); |
| 256 | if (current && current.cacheKey === fetchKey) { |
| 257 | if (err instanceof ToolNotFoundError) { |
| 258 | current.error = { |
| 259 | source: "query", |
| 260 | code: "tool-not-found", |
| 261 | message: `Query tool "${toolName}" not found`, |
| 262 | statementId, |
| 263 | component: "Query", |
| 264 | toolName, |
| 265 | hint: err.availableTools.length |
no test coverage detected