import crypto from 'node:crypto'; function safeJsonParse(value, fallback = null) { try { return JSON.parse(value); } catch { return fallback; } } export function createSessionStreamStore({ pool }) { if (!pool) { throw new Error('createSessionStreamStore requires pool'); } async function appendEvent({ userId, sessionId, payload, id = crypto.randomUUID(), upstreamEventId = null, } = {}) { if (!userId || !sessionId || !payload) { throw new Error('appendEvent requires userId, sessionId, and payload'); } const createdAt = Date.now(); const eventType = String(payload?.type ?? 'unknown').slice(0, 64); await pool.query( `INSERT INTO h5_session_stream_events (id, user_id, agent_session_id, event_type, payload_json, upstream_event_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ id, userId, sessionId, eventType, JSON.stringify(payload), upstreamEventId || null, createdAt, ], ); return { id, createdAt, eventType, payload, }; } async function listEventsForUser(userId, sessionId, { afterEventId = null, limit = 500 } = {}) { if (!userId || !sessionId) return null; let afterCreatedAt = null; let cursorMiss = false; if (afterEventId) { const [cursorRows] = await pool.query( `SELECT created_at FROM h5_session_stream_events WHERE id = ? AND agent_session_id = ? AND user_id = ? LIMIT 1`, [afterEventId, sessionId, userId], ); if (!cursorRows[0]) { cursorMiss = true; } else { afterCreatedAt = Number(cursorRows[0].created_at); } } const [rows] = await pool.query( afterCreatedAt == null ? `SELECT id, event_type, payload_json, upstream_event_id, created_at FROM h5_session_stream_events WHERE agent_session_id = ? AND user_id = ? ORDER BY created_at ASC, id ASC LIMIT ?` : `SELECT id, event_type, payload_json, upstream_event_id, created_at FROM h5_session_stream_events WHERE agent_session_id = ? AND user_id = ? AND created_at > ? ORDER BY created_at ASC, id ASC LIMIT ?`, afterCreatedAt == null ? [sessionId, userId, limit] : [sessionId, userId, afterCreatedAt, limit], ); return { events: rows.map((row) => ({ id: row.id, eventType: row.event_type, payload: safeJsonParse(row.payload_json, null), upstreamEventId: row.upstream_event_id ?? null, createdAt: Number(row.created_at), })), cursorMiss, }; } async function getLatestEventForUser(userId, sessionId) { const [rows] = await pool.query( `SELECT id, event_type, payload_json, upstream_event_id, created_at FROM h5_session_stream_events WHERE agent_session_id = ? AND user_id = ? ORDER BY created_at DESC, id DESC LIMIT 1`, [sessionId, userId], ); const row = rows[0]; if (!row) return null; return { id: row.id, eventType: row.event_type, payload: safeJsonParse(row.payload_json, null), upstreamEventId: row.upstream_event_id ?? null, createdAt: Number(row.created_at), }; } return { appendEvent, listEventsForUser, getLatestEventForUser, }; }