aps-agent/server/app/events.ts

47 lines
1.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 会话内事件总线(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;
}
}