@@ -0,0 +1,95 @@
|
||||
import { serve } from '@hono/node-server';
|
||||
import { config } from './config.ts';
|
||||
import { closeDb, getDb, pruneProbes } from './db.ts';
|
||||
import { closePool, runDiscovery } from './discovery.ts';
|
||||
import { log } from './log.ts';
|
||||
import { probeAll, probeUrls } from './probe.ts';
|
||||
import { createApp } from './server.ts';
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
let shuttingDown = false;
|
||||
/** Set while a probe or discovery cycle is in flight, so shutdown can wait for it. */
|
||||
let inFlight: Promise<unknown> = Promise.resolve();
|
||||
|
||||
function track<T>(work: Promise<T>): Promise<T> {
|
||||
inFlight = inFlight.then(() => work).catch(() => undefined);
|
||||
return work;
|
||||
}
|
||||
|
||||
async function probeCycle(): Promise<void> {
|
||||
if (shuttingDown) return;
|
||||
try {
|
||||
await track(probeAll());
|
||||
} catch (err) {
|
||||
log.error('probe cycle failed', { reason: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
async function discoveryCycle(backfill: boolean): Promise<void> {
|
||||
if (shuttingDown) return;
|
||||
try {
|
||||
const result = await track(runDiscovery(backfill));
|
||||
// New mints are probed straight away, not on the next cycle, so their page has
|
||||
// real metadata as soon as it is reachable.
|
||||
if (result.newMints.length > 0) await track(probeUrls(result.newMints));
|
||||
} catch (err) {
|
||||
log.error('discovery cycle failed', {
|
||||
reason: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
getDb();
|
||||
|
||||
const server = serve({ fetch: createApp().fetch, port: config.port }, (info) => {
|
||||
log.info('api listening', {
|
||||
port: info.port,
|
||||
db: config.dbPath,
|
||||
relays: config.relays.length,
|
||||
});
|
||||
});
|
||||
|
||||
const probeTimer = setInterval(() => void probeCycle(), config.probeIntervalMin * 60 * 1000);
|
||||
const discoveryTimer = setInterval(
|
||||
() => void discoveryCycle(false),
|
||||
config.discoveryIntervalMin * 60 * 1000,
|
||||
);
|
||||
const pruneTimer = setInterval(() => {
|
||||
const removed = pruneProbes();
|
||||
if (removed > 0) log.info('pruned probes', { rows: removed });
|
||||
}, DAY_MS);
|
||||
|
||||
// Startup: probe what we already know, then a full discovery backfill so a fresh
|
||||
// database converges to complete review counts (BACKEND.md).
|
||||
void probeCycle().then(() => discoveryCycle(true));
|
||||
|
||||
const shutdown = (signal: string) => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
log.info('shutting down', { signal });
|
||||
|
||||
clearInterval(probeTimer);
|
||||
clearInterval(discoveryTimer);
|
||||
clearInterval(pruneTimer);
|
||||
|
||||
void inFlight.finally(() => {
|
||||
closePool();
|
||||
server.close(() => {
|
||||
closeDb();
|
||||
process.exit(0);
|
||||
});
|
||||
// Do not hang forever on a relay socket that refuses to close.
|
||||
setTimeout(() => {
|
||||
closeDb();
|
||||
process.exit(0);
|
||||
}, 8000).unref();
|
||||
});
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user