Files
worldmonitor/scripts/seed-service-statuses.mjs
Elie Habib f209c11713 fix(seeds): rethrow non-fetch failures, separate publish errors (#1606)
* fix(seeds): rethrow non-fetch failures in runSeed()

Split runSeed() into two phases so only upstream fetch errors get
the graceful TTL-extension path. Redis publish, seed-meta, and
verification failures now rethrow (exit 1) so monitoring catches them.

* fix(seeds): separate fetch from publish errors in standalone scripts

Split seed-airport-delays, seed-military-flights, and
seed-service-statuses into two phases matching runSeed() pattern:
- Phase 1: upstream fetch errors are graceful (extend TTL, exit 0)
- Phase 2: Redis publish/verify errors propagate (exit 1)

* fix(seeds): make Redis SET throw on failure so publish errors propagate

Local redisSet() returned false instead of throwing, silently masking
Redis write failures. writeExtraKey() also warned instead of throwing.
Both now throw on non-OK responses, ensuring Phase 2 catch fires.

* fix(seed): treat empty Redis key after successful RPC as publish failure

When cachedFetchJson() silently swallows a Redis write failure, the
warm-ping script now throws instead of warning, reaching the outer
catch handler (exit 1) so monitoring detects the issue.
2026-03-15 01:30:54 +04:00

72 lines
2.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Warm-pings the Vercel RPC endpoint to populate the Redis cache.
* The RPC handler (list-service-statuses.ts) does the actual fetching
* and caching via cachedFetchJson. This script just triggers it.
*
* Standalone fallback — primary seeder is the AIS relay loop.
*/
import { loadEnvFile, CHROME_UA, getRedisCredentials, logSeedResult, extendExistingTtl } from './_seed-utils.mjs';
loadEnvFile(import.meta.url);
const RPC_URL = 'https://worldmonitor.app/api/infrastructure/v1/list-service-statuses';
const CANONICAL_KEY = 'infra:service-statuses:v1';
async function warmPing() {
const startMs = Date.now();
console.log('=== infra:service-statuses Warm Ping ===');
console.log(` Key: ${CANONICAL_KEY}`);
console.log(` Target: ${RPC_URL}`);
let data;
try {
const resp = await fetch(RPC_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': CHROME_UA,
Origin: 'https://worldmonitor.app',
},
body: '{}',
signal: AbortSignal.timeout(60_000),
});
if (!resp.ok) throw new Error(`RPC failed: HTTP ${resp.status}`);
data = await resp.json();
} catch (err) {
console.error(` FETCH FAILED: ${err.message || err}`);
await extendExistingTtl([CANONICAL_KEY], 7200);
console.log(`\n=== Failed gracefully (${Math.round(Date.now() - startMs)}ms) ===`);
process.exit(0);
}
const count = data?.statuses?.length || 0;
console.log(` Statuses: ${count}`);
const { url, token } = getRedisCredentials();
const verifyResp = await fetch(`${url}/get/${encodeURIComponent(CANONICAL_KEY)}`, {
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(5_000),
});
const verifyData = await verifyResp.json();
if (verifyData.result) {
console.log(' Verified: data present in Redis');
} else {
throw new Error('Verification failed: Redis key empty after successful RPC');
}
const durationMs = Date.now() - startMs;
logSeedResult('infra', count, durationMs, { mode: 'warm-ping' });
console.log(`\n=== Done (${Math.round(durationMs)}ms) ===`);
}
warmPing().then(() => {
process.exit(0);
}).catch((err) => {
console.error(`ERROR: ${err.message || err}`);
process.exit(1);
});