()
| 152 | // ── Snapshot & notification ────────────────────────────────────────────── |
| 153 | |
| 154 | function rebuildSnapshot(): boolean { |
| 155 | const out: QuerySnapshot = { |
| 156 | __openui_loading: [], |
| 157 | __openui_refetching: [], |
| 158 | __openui_errors: [], |
| 159 | }; |
| 160 | |
| 161 | for (const [sid, q] of queries) { |
| 162 | const entry = cache.get(q.cacheKey); |
| 163 | if (entry && entry.data !== undefined) { |
| 164 | // Use settled data (even while refetching — shows last good value) |
| 165 | out[sid] = entry.data; |
| 166 | } else if (q.prevCacheKey) { |
| 167 | const prev = cache.get(q.prevCacheKey); |
| 168 | if (prev && prev.data !== undefined) { |
| 169 | out[sid] = prev.data; |
| 170 | } else { |
| 171 | out[sid] = q.defaults; |
| 172 | } |
| 173 | } else { |
| 174 | out[sid] = q.defaults; |
| 175 | } |
| 176 | if (q.loading) { |
| 177 | out.__openui_loading.push(sid); |
| 178 | if (q.everFetched) out.__openui_refetching.push(sid); |
| 179 | } |
| 180 | if (q.error) out.__openui_errors.push(q.error); |
| 181 | } |
| 182 | |
| 183 | for (const [sid, m] of mutations) { |
| 184 | out[sid] = m.result; |
| 185 | if (m.error) out.__openui_errors.push(m.error); |
| 186 | } |
| 187 | |
| 188 | try { |
| 189 | const outJson = JSON.stringify(out); |
| 190 | if (outJson === snapshotJson) return false; |
| 191 | snapshot = out; |
| 192 | snapshotJson = outJson; |
| 193 | } catch { |
| 194 | snapshot = out; |
| 195 | snapshotJson = ""; |
| 196 | } |
| 197 | return true; |
| 198 | } |
| 199 | |
| 200 | function notify() { |
| 201 | for (const listener of [...listeners]) { |
no test coverage detected