beadboard/src/lib/realtime.ts

82 lines
2.1 KiB
TypeScript

import { canonicalizeWindowsPath, windowsPathKey } from './pathing';
export type IssuesChangeKind = 'changed' | 'renamed';
export interface IssuesChangedEvent {
id: number;
projectRoot: string;
changedPath?: string;
kind: IssuesChangeKind;
at: string;
}
interface Subscriber {
projectKey?: string;
listener: (event: IssuesChangedEvent) => void;
}
export interface SubscribeOptions {
projectRoot?: string;
}
export class IssuesEventBus {
private nextEventId = 1;
private readonly subscribers = new Map<number, Subscriber>();
private nextSubscriberId = 1;
emit(projectRoot: string, changedPath?: string, kind: IssuesChangeKind = 'changed'): IssuesChangedEvent {
const canonicalProjectRoot = canonicalizeWindowsPath(projectRoot);
const projectKey = windowsPathKey(canonicalProjectRoot);
const event: IssuesChangedEvent = {
id: this.nextEventId,
projectRoot: canonicalProjectRoot,
changedPath: changedPath ? canonicalizeWindowsPath(changedPath) : undefined,
kind,
at: new Date().toISOString(),
};
this.nextEventId += 1;
for (const subscriber of this.subscribers.values()) {
if (!subscriber.projectKey || subscriber.projectKey === projectKey) {
subscriber.listener(event);
}
}
return event;
}
subscribe(listener: (event: IssuesChangedEvent) => void, options: SubscribeOptions = {}): () => void {
const id = this.nextSubscriberId;
this.nextSubscriberId += 1;
this.subscribers.set(id, {
listener,
projectKey: options.projectRoot ? windowsPathKey(options.projectRoot) : undefined,
});
return () => {
this.subscribers.delete(id);
};
}
getSubscriberCount(): number {
return this.subscribers.size;
}
resetForTests(): void {
this.subscribers.clear();
this.nextSubscriberId = 1;
this.nextEventId = 1;
}
}
export const issuesEventBus = new IssuesEventBus();
export function toSseFrame(event: IssuesChangedEvent): string {
return `id: ${event.id}\nevent: issues\ndata: ${JSON.stringify(event)}\n\n`;
}
export const SSE_HEARTBEAT_FRAME = ': heartbeat\n\n';
export const SSE_CONNECTED_FRAME = ': connected\n\n';