fix: cache query status

This commit is contained in:
2026-09-21 10:11:50 +07:00
parent b66587fd54
commit ace43b39a9
3 changed files with 32 additions and 4 deletions
+2 -1
View File
@@ -12,7 +12,8 @@ export function useStatusQuery() {
queryKey: statusKeys.all,
queryFn: ({ signal }) => getStatus(signal),
refetchInterval: 60_000,
refetchIntervalInBackground: false,
refetchIntervalInBackground: true,
refetchOnWindowFocus: true,
});
}
+18 -3
View File
@@ -9,6 +9,7 @@ import { resolveFavicon } from './monitors';
const DAY_MS = 24 * 60 * 60 * 1000;
const FAVICON_CACHE_SECONDS = 86_400;
const STATUS_CACHE_TIME_HEADER = 'X-Upwatch-Status-Cache-Time';
type ServiceStatus = 'up' | 'degraded' | 'down' | 'unknown' | 'maintenance';
type OverallStatus = 'operational' | 'degraded' | 'down';
type DailyAggregate = { monitorId: number; day: Date; totalChecks: number; upChecks: number };
@@ -134,20 +135,34 @@ function statusCacheKey(context: Context<{ Bindings: Env }>): Request {
* STATUS_CACHE_SECONDS env var to 0 to disable.
*/
async function cachedStatusResponse(context: Context<{ Bindings: Env }>): Promise<Response | undefined> {
if (resolveStatusCacheSeconds(context.env) <= 0) return undefined;
const maxAgeSeconds = resolveStatusCacheSeconds(context.env);
if (maxAgeSeconds <= 0) return undefined;
const cache = edgeCache();
if (!cache) return undefined;
try {
return await cache.match(statusCacheKey(context));
const cached = await cache.match(statusCacheKey(context));
if (!cached) return undefined;
if (!isStatusCacheFresh(cached, maxAgeSeconds)) return undefined;
return cached;
} catch {
return undefined;
}
}
export function isStatusCacheFresh(response: Response, maxAgeSeconds: number, now = Date.now()): boolean {
const cachedAt = Number(response.headers.get(STATUS_CACHE_TIME_HEADER));
return Number.isFinite(cachedAt) && now - cachedAt < maxAgeSeconds * 1_000;
}
function jsonWithEdgeCache(context: Context<{ Bindings: Env }>, body: unknown): Response {
const seconds = resolveStatusCacheSeconds(context.env);
if (seconds <= 0) return Response.json(body);
const response = Response.json(body, { headers: { 'Cache-Control': `public, max-age=${seconds}` } });
const response = Response.json(body, {
headers: {
'Cache-Control': `public, max-age=${seconds}`,
[STATUS_CACHE_TIME_HEADER]: String(Date.now()),
},
});
const cache = edgeCache();
if (cache) {
try {
+12
View File
@@ -2,6 +2,7 @@ import { applyD1Migrations, type D1Migration } from 'cloudflare:test';
import { env, exports as worker } from 'cloudflare:workers';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { DEGRADED_MESSAGE } from '../src/worker/ai/fallback-message';
import { isStatusCacheFresh } from '../src/worker/routes/status';
const DAY_MS = 24 * 60 * 60 * 1000;
@@ -143,6 +144,17 @@ describe('public status API', () => {
}
});
it('only serves status cache entries within the configured TTL', () => {
const now = 1_000_000;
const fresh = new Response(null, { headers: { 'X-Upwatch-Status-Cache-Time': String(now - 59_999) } });
const expired = new Response(null, { headers: { 'X-Upwatch-Status-Cache-Time': String(now - 60_000) } });
const legacy = new Response();
expect(isStatusCacheFresh(fresh, 60, now)).toBe(true);
expect(isStatusCacheFresh(expired, 60, now)).toBe(false);
expect(isStatusCacheFresh(legacy, 60, now)).toBe(false);
});
it('keeps a service operational while failures are unconfirmed', async () => {
await insertMonitor({
name: 'Flaky API',