// TIJDELIJKE debug-endpoint voor M8-acceptance. // Geen auth, geen filtering — alle pg_notify-events op `scrum4me_changes` // stromen rauw door naar de browser. Bedoeld om te isoleren of de // SSE + LISTEN-pipe op Vercel werkt, los van iron-session, productfilter // of solo-store. // // VERWIJDEREN VOOR M8 OUT-OF-DRAFT. import { NextRequest } from 'next/server' import { Client } from 'pg' export const runtime = 'nodejs' export const dynamic = 'force-dynamic' export const maxDuration = 300 const CHANNEL = 'scrum4me_changes' export async function GET(request: NextRequest) { const directUrl = process.env.DIRECT_URL ?? process.env.DATABASE_URL if (!directUrl) { return Response.json({ error: 'DIRECT_URL/DATABASE_URL niet gezet' }, { status: 500 }) } const isPooled = directUrl.includes('pooler.') const hostHint = directUrl.match(/@([^/]+)/)?.[1] ?? 'unknown-host' console.log(`[debug-realtime] connecting (${isPooled ? 'POOLED' : 'direct'}) host=${hostHint}`) const encoder = new TextEncoder() const pgClient = new Client({ connectionString: directUrl }) let closed = false let heartbeatTimer: ReturnType | null = null const stream = new ReadableStream({ async start(controller) { const enqueue = (chunk: string) => { if (closed) return try { controller.enqueue(encoder.encode(chunk)) } catch { // already closed } } const cleanup = async (reason: string) => { if (closed) return closed = true if (heartbeatTimer) clearInterval(heartbeatTimer) try { await pgClient.end() } catch { // ignore } try { controller.close() } catch { // already closed } console.log(`[debug-realtime] closed: ${reason}`) } try { await pgClient.connect() await pgClient.query(`LISTEN ${CHANNEL}`) console.log('[debug-realtime] LISTEN ready') } catch (err) { console.error('[debug-realtime] pg connect/listen failed:', err) enqueue(`event: error\ndata: ${JSON.stringify({ message: String(err) })}\n\n`) await cleanup('pg connect failed') return } pgClient.on('notification', (msg) => { console.log(`[debug-realtime] RAW notification length=${msg.payload?.length ?? 0}`) if (!msg.payload) return enqueue(`data: ${msg.payload}\n\n`) }) pgClient.on('error', async (err) => { console.error('[debug-realtime] pg client error:', err) await cleanup('pg error') }) pgClient.on('end', () => { console.log('[debug-realtime] pg client end') }) enqueue( `event: ready\ndata: ${JSON.stringify({ host: hostHint, pooled: isPooled, channel: CHANNEL, time: new Date().toISOString(), })}\n\n`, ) heartbeatTimer = setInterval(() => { enqueue(`: heartbeat ${new Date().toISOString()}\n\n`) }, 25_000) request.signal.addEventListener('abort', () => { cleanup('client aborted') }) }, }) return new Response(stream, { headers: { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache, no-transform', Connection: 'keep-alive', 'X-Accel-Buffering': 'no', }, }) }