From b243e06175221973ffc1a92cc3a05e6a214ffab4 Mon Sep 17 00:00:00 2001 From: mariosemes Date: Thu, 26 Mar 2026 22:22:45 +0100 Subject: [PATCH] Fix SSE streaming by hijacking Fastify response Fastify was buffering the SSE response, causing store progress to appear stuck. Using reply.hijack() hands off the raw response so events flush immediately to the client. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/server/routes/search.ts | 45 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/server/routes/search.ts b/src/server/routes/search.ts index ff954d6..aa75d66 100644 --- a/src/server/routes/search.ts +++ b/src/server/routes/search.ts @@ -66,24 +66,41 @@ export const searchRoutes: FastifyPluginAsync = async (app) => { ? stores.split(',').map(Number).filter((n) => !isNaN(n)) : undefined; - reply.raw.writeHead(200, { + // Hijack the response from Fastify so we can stream SSE directly + reply.hijack(); + + const res = reply.raw; + + res.writeHead(200, { 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', + 'Cache-Control': 'no-cache, no-transform', 'Connection': 'keep-alive', + 'X-Accel-Buffering': 'no', }); - await searchStreaming( - { - query: q, - storeIds, - categoryId: category ? Number(category) : undefined, - groupId: group ? Number(group) : undefined, - }, - (event) => { - reply.raw.write(`data: ${JSON.stringify(event)}\n\n`); - }, - ); + // Send initial comment to establish connection + res.write(':ok\n\n'); - reply.raw.end(); + const sendEvent = (event: any) => { + if (!res.closed) { + res.write(`data: ${JSON.stringify(event)}\n\n`); + } + }; + + try { + await searchStreaming( + { + query: q, + storeIds, + categoryId: category ? Number(category) : undefined, + groupId: group ? Number(group) : undefined, + }, + sendEvent, + ); + } catch (err) { + sendEvent({ type: 'done', meta: { query: q, duration: 0, storeCount: 0, totalResults: 0, errors: [{ storeId: 0, storeName: 'System', error: String(err) }] } }); + } + + res.end(); }); };