aps-agent/server/app/events.ts

47 lines
1.7 KiB
TypeScript
Raw Normal View History

/**
* 会话内事件总线(A1.3)。
*
* 职责:给事件分配会话内单调递增的 seq、向订阅者广播、支持从某个 seq 之后回放(断点续传)。
* A3.7 会话落库后,回放改为读存储,这里只保留实时广播;内存缓冲仅作为 A1 的过渡实现。
*/
import { PROTOCOL_VERSION, type PlatformEvent } from '@aps/contracts';
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
/** 发布事件的入参:v 与 seq 由总线分配,ts 可由调用方指定 */
export type EventInput = DistributiveOmit<PlatformEvent, 'v' | 'seq' | 'ts'> & { ts?: number };
const REPLAY_BUFFER = 500;
export class EventBus {
private seq = 0;
private readonly buffer: PlatformEvent[] = [];
private readonly listeners = new Set<(event: PlatformEvent) => void>();
publish(input: EventInput): PlatformEvent {
this.seq += 1;
const event = { ...input, v: PROTOCOL_VERSION, seq: this.seq, ts: input.ts ?? Date.now() } as PlatformEvent;
this.buffer.push(event);
if (this.buffer.length > REPLAY_BUFFER) this.buffer.shift();
for (const listener of this.listeners) listener(event);
return event;
}
/** 从 since(不含)之后回放 */
replay(since = 0): PlatformEvent[] {
return this.buffer.filter((event) => event.seq > since);
}
/** 先回放再订阅:避免回放与订阅之间漏事件 */
subscribe(since: number, listener: (event: PlatformEvent) => void): () => void {
for (const event of this.replay(since)) listener(event);
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
get lastSeq(): number {
return this.seq;
}
}