fix(cctv): serve the camera catalog while it refreshes

An expired catalog made every /api/cctv request (camera list and frames)
wait for the full refresh, which lasts as long as the slowest of the live
packs: Tarktee's DATEX feeds took 2 to 16.6 s across ten requests. With
Tarktee's timeout raised to 30 s, that wait could reach 30 s at each
15-minute refresh. The cached list is now returned while a single-flight
refresh replaces it in the background; only a first load, with nothing
to serve, waits. A background refresh that throws is logged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
quanticsoul4772andClaude Opus 5.5 committed 2026-10-18 22:08:24 -07:00
1 parent 6b3f7a3ec1
commit f26586bb3e
2 files changed
+95 -5

No files matched your search

+20 -5
View File
@@ -204,7 +204,8 @@ export function createCctvCatalog({ sourceRoot = process.cwd() } = {}) {
*
* Merges every source pack (live open-data packs, local file, env
* variable), deduplicates by ID, shares the catalog cap fairly across
* packs, and caches for CCTV_SOURCE_CACHE_MS.
* packs, and caches for CCTV_SOURCE_CACHE_MS. Past that, the cached list is
* returned while a background refresh replaces it; only a first load waits.
*
* @returns {Promise<Array<object>>} Deduplicated, capped source list.
*/
@@ -219,10 +220,24 @@ export function createCctvCatalog({ sourceRoot = process.cwd() } = {}) {
// Single-flight: a burst of requests arriving past the TTL shares ONE refresh
// instead of each launching the full multi-provider refetch. The `.finally`
// clears the ref so the next post-TTL cycle starts fresh.
if (_cctvSourceInflight) return _cctvSourceInflight;
_cctvSourceInflight = refreshCctvSources().finally(() => {
_cctvSourceInflight = null;
});
if (!_cctvSourceInflight) {
_cctvSourceInflight = refreshCctvSources().finally(() => {
_cctvSourceInflight = null;
});
}
// With a catalog in hand, answer with it while the refresh runs: the
// refresh lasts as long as the slowest pack (Tarktee's DATEX feeds take up
// to its 30 s timeout), and every /api/cctv request would otherwise wait
// for it. Only the first load, with nothing to serve yet, waits.
if (_cctvSourceCache.length) {
_cctvSourceInflight.catch((error) => {
console.warn(
'[CCTV] background catalog refresh failed:',
error?.message || error,
);
});
return _cctvSourceCache;
}
return _cctvSourceInflight;
}