127 lines
3.4 KiB
JavaScript
127 lines
3.4 KiB
JavaScript
import crypto from 'node:crypto';
|
|
|
|
function safeJsonParse(value, fallback = null) {
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function mapEventRow(row) {
|
|
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),
|
|
};
|
|
}
|
|
|
|
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;
|
|
let cursorEvent = null;
|
|
if (afterEventId) {
|
|
const [cursorRows] = await pool.query(
|
|
`SELECT id, event_type, payload_json, upstream_event_id, 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 {
|
|
cursorEvent = mapEventRow(cursorRows[0]);
|
|
afterCreatedAt = cursorEvent.createdAt;
|
|
}
|
|
}
|
|
|
|
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(mapEventRow),
|
|
cursorMiss,
|
|
cursorEvent,
|
|
};
|
|
}
|
|
|
|
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 mapEventRow(row);
|
|
}
|
|
|
|
return {
|
|
appendEvent,
|
|
listEventsForUser,
|
|
getLatestEventForUser,
|
|
};
|
|
}
|