event-store.ts 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. // 事件管道:分配 seq → 落库(瞬时事件除外)→ 内存缓冲 → 广播(docs/design/02-architecture.md §3.3)
  2. import { EventEmitter } from 'node:events'
  3. import { and, asc, eq, gt, sql } from 'drizzle-orm'
  4. import { TRANSIENT_EVENT_TYPES, type AgentEvent, type AgentEventType, type EventPayloads } from '@agent-server/shared'
  5. import type { DB } from '../db'
  6. import { events, sessions } from '../db/schema'
  7. import { nowIso } from '../lib/ids'
  8. const BUFFER_SIZE = 2000
  9. export class EventStore {
  10. private bus = new EventEmitter()
  11. /** 每个会话最近的事件(含瞬时事件),用于 SSE 断线续传 */
  12. private buffers = new Map<string, AgentEvent[]>()
  13. constructor(private db: DB) {
  14. this.bus.setMaxListeners(0)
  15. }
  16. append<T extends AgentEventType>(sessionId: string, runId: string | null, type: T, payload: EventPayloads[T]): AgentEvent {
  17. const ts = nowIso()
  18. const persist = !TRANSIENT_EVENT_TYPES.has(type)
  19. const event = this.db.transaction(tx => {
  20. const row = tx.update(sessions)
  21. .set({ lastSeq: sql`${sessions.lastSeq} + 1`, ...(persist ? { updatedAt: ts } : {}) })
  22. .where(eq(sessions.id, sessionId))
  23. .returning({ seq: sessions.lastSeq })
  24. .get()
  25. if (!row) throw new Error(`会话不存在:${sessionId}`)
  26. if (persist) tx.insert(events).values({ sessionId, seq: row.seq, runId, type, payload, ts }).run()
  27. return { seq: row.seq, type, sessionId, runId, ts, payload } as AgentEvent
  28. })
  29. const buf = this.buffers.get(sessionId) ?? []
  30. buf.push(event)
  31. if (buf.length > BUFFER_SIZE) buf.splice(0, buf.length - BUFFER_SIZE)
  32. this.buffers.set(sessionId, buf)
  33. this.bus.emit(sessionId, event)
  34. this.bus.emit('*', event)
  35. return event
  36. }
  37. /** 读取 seq > after 的事件:优先使用内存缓冲(包含瞬时事件),不足时回落到数据库 */
  38. list(sessionId: string, after = 0, limit = 5000): AgentEvent[] {
  39. const buf = this.buffers.get(sessionId)
  40. if (buf?.length && buf[0]!.seq <= after + 1) return buf.filter(e => e.seq > after).slice(0, limit)
  41. const rows = this.db.select().from(events)
  42. .where(and(eq(events.sessionId, sessionId), gt(events.seq, after)))
  43. .orderBy(asc(events.seq))
  44. .limit(limit)
  45. .all()
  46. const persisted = rows.map(r => ({ seq: r.seq, type: r.type, sessionId: r.sessionId, runId: r.runId, ts: r.ts, payload: r.payload }) as AgentEvent)
  47. if (!buf?.length) return persisted
  48. // 数据库部分 + 缓冲中更新的部分(缓冲区里才有瞬时事件)
  49. const last = persisted.at(-1)?.seq ?? after
  50. return [...persisted, ...buf.filter(e => e.seq > last)].slice(0, limit)
  51. }
  52. subscribe(sessionId: string | '*', fn: (e: AgentEvent) => void): () => void {
  53. this.bus.on(sessionId, fn)
  54. return () => this.bus.off(sessionId, fn)
  55. }
  56. dropBuffer(sessionId: string) {
  57. this.buffers.delete(sessionId)
  58. }
  59. }