From d9861f8bbf5df55baaabed06e89649dbe0f7ab4d Mon Sep 17 00:00:00 2001 From: b1rdmania <102524336+b1rdmania@users.noreply.github.com> Date: Fri, 26 Dec 2025 15:06:25 +0000 Subject: [PATCH] Fail loudly when Vercel KV is missing. Prevents shortlinks from silently breaking due to ephemeral in-memory fallback in production. --- server/src/server.ts | 45 ++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index e7255b1..3cee3eb 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -33,6 +33,8 @@ type SharePayload = }; const localShareStore = new Map(); +const isVercel = Boolean(process.env.VERCEL); +const hasKvEnv = Boolean(process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN); function base62(bytes: Uint8Array): string { const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; @@ -50,22 +52,39 @@ async function shareSet(code: string, payload: SharePayload): Promise { const key = `share:${code}`; // 30 days TTL const exSec = 60 * 60 * 24 * 30; - try { - await kv.set(key, payload, { ex: exSec }); - } catch { - // Local dev / KV not configured: best-effort in-memory store. - localShareStore.set(code, payload); + if (isVercel && !hasKvEnv) { + throw new Error('Vercel KV is not configured for this project.'); } + + try { + if (hasKvEnv) { + await kv.set(key, payload, { ex: exSec }); + return; + } + } catch { + // fall through to local store (dev only) + } + + // Local dev: best-effort in-memory store. + localShareStore.set(code, payload); } async function shareGet(code: string): Promise { const key = `share:${code}`; - try { - const val = await kv.get(key); - return val ?? null; - } catch { - return localShareStore.get(code) ?? null; + if (isVercel && !hasKvEnv) { + throw new Error('Vercel KV is not configured for this project.'); } + + try { + if (hasKvEnv) { + const val = await kv.get(key); + return val ?? null; + } + } catch { + // fall through to local store (dev only) + } + + return localShareStore.get(code) ?? null; } // Search for MIDI files @@ -118,7 +137,8 @@ app.post('/api/share', async (req, res) => { }); } catch (error) { console.error('Share create error:', error); - res.status(500).json({ error: 'Share create failed' }); + const msg = error instanceof Error ? error.message : 'Share create failed'; + res.status(msg.includes('KV') ? 503 : 500).json({ error: msg }); } }); @@ -148,7 +168,8 @@ app.get('/s/:code', async (req, res) => { res.redirect(302, dest); } catch (error) { console.error('Share resolve error:', error); - res.status(500).send('Resolve failed'); + const msg = error instanceof Error ? error.message : 'Resolve failed'; + res.status(msg.includes('KV') ? 503 : 500).send(msg); } });