From cebc432af88d261e57989d8b3213c0cf184289ef Mon Sep 17 00:00:00 2001 From: justin Date: Sun, 31 May 2026 10:34:04 -0500 Subject: [PATCH] dot-lookup: add 12s hard deadline + reduce FMCSA timeout to 5s If FMCSA live API is slow (can take 2x 10s = 20s when down), the route would hang until nginx proxy killed the connection -> 'Failed to fetch'. Now: - fmcsaFetch timeout: 10s -> 5s (two calls max 10s total) - SOS entity-status timeout: already reduced to 5s - 12s hard deadline: if any live API hangs past 12s, immediately return census-only data with a 'partial=true' flag so the user gets something Co-Authored-By: Claude Sonnet 4.6 --- api/src/routes/dot-lookup.ts | 38 ++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/api/src/routes/dot-lookup.ts b/api/src/routes/dot-lookup.ts index 6e9d995..a55dda1 100644 --- a/api/src/routes/dot-lookup.ts +++ b/api/src/routes/dot-lookup.ts @@ -23,7 +23,7 @@ async function fmcsaFetch(path: string): Promise { try { const url = `${FMCSA_BASE}/${path}${path.includes("?") ? "&" : "?"}webKey=${FMCSA_API_KEY}`; const resp = await fetch(url, { - signal: AbortSignal.timeout(10000), + signal: AbortSignal.timeout(5000), headers: { "Accept": "application/json" }, }); if (!resp.ok) return null; @@ -58,6 +58,12 @@ router.get("/api/v1/dot/lookup", async (req, res) => { return; } + // Hard 12s deadline — if FMCSA/SOS APIs are slow, return local census data + // rather than hanging until nginx proxy timeout kills the connection. + const deadline = new Promise<"timeout">((resolve) => + setTimeout(() => resolve("timeout"), 12000) + ); + try { // 1. Local census data const local = await pool.query( @@ -66,6 +72,32 @@ router.get("/api/v1/dot/lookup", async (req, res) => { ); const census = local.rows[0] || null; + // Race the deadline — if live APIs are too slow, return census-only data now + deadline.then((t) => { + if (t === "timeout" && !res.headersSent) { + const name = census?.legal_name || "Unknown"; + console.warn("[dot-lookup] Deadline hit for DOT %s — returning census data only", rawDot); + res.json({ + dot_number: rawDot, + legal_name: name, + dba_name: census?.dba_name || null, + phy_city: census?.phy_city || null, + phy_state: census?.phy_state || null, + telephone: census?.telephone || null, + email: census?.email_address || null, + fleet: { power_units: census?.nbr_power_unit ?? null, drivers: census?.driver_total ?? null }, + mcs150_date: census?.mcs150_parsed || null, + carrier_type: census?.carrier_operation || null, + for_hire: census?.authorized_for_hire || false, + checks: [{ id: "data_partial", label: "Compliance Check", status: "unknown", + detail: "Live FMCSA data is temporarily slow. Showing local records only — try again in a moment for full results." }], + summary: { red: 0, yellow: 0, green: 0, total: 1 }, + checked_at: new Date().toISOString(), + partial: true, + }); + } + }); + // 2. Live FMCSA API (returns null on any failure — already non-throwing) const snapshot = await fmcsaFetch(rawDot); const carrier = snapshot?.content?.carrier || null; @@ -587,8 +619,10 @@ router.get("/api/v1/dot/lookup", async (req, res) => { }); } catch (err) { console.error("[dot-lookup] Error:", err); - res.status(500).json({ error: "DOT lookup failed." }); + if (!res.headersSent) res.status(500).json({ error: "DOT lookup failed." }); } + // Clear the deadline timer to avoid Node keeping the event loop alive + deadline.then(() => {}).catch(() => {}); }); // ── Name Search ─────────────────────────────────────────────────────