import type { GitHubEvent } from "./events.ts"; export interface StoredGitHubEvent extends GitHubEvent { readonly sequence: number; } export interface EventLogState { readonly sequence: number; readonly deliveries: ReadonlyArray; readonly events: ReadonlyArray; } export interface AppendEventResult { readonly state: EventLogState; readonly duplicate: boolean; readonly storedEvent: StoredGitHubEvent | null; } const emptyState: EventLogState = { sequence: 0, deliveries: [], events: [], }; export function appendEvent( current: EventLogState | undefined, event: GitHubEvent, retention: number, ): AppendEventResult { const state = current ?? emptyState; if (state.deliveries.includes(event.deliveryId)) { return { state, duplicate: true, storedEvent: null }; } const storedEvent = { ...event, sequence: state.sequence + 1 }; return { state: { sequence: storedEvent.sequence, deliveries: [...state.deliveries, event.deliveryId].slice(-retention), events: [...state.events, storedEvent].slice(-retention), }, duplicate: false, storedEvent, }; } export interface ReadEventsOptions { readonly after?: number; readonly pullRequestNumber?: number; } export interface ReadEventsResult { readonly expired: boolean; readonly future: boolean; readonly earliestSequence: number; readonly latestSequence: number; readonly events: ReadonlyArray; } export type ResumeCursorStatus = "ok" | "expired" | "future"; export function resumeCursorStatus( earliestSequence: number | null, latestSequence: number, after: number | undefined, ): ResumeCursorStatus { if (after === undefined) return "ok"; if (after > latestSequence) return "future"; if (earliestSequence !== null && after < earliestSequence - 1) return "expired"; return "ok"; } export function readEvents( state: EventLogState | undefined, options: ReadEventsOptions, ): ReadEventsResult { const current = state ?? emptyState; const earliestSequence = current.events[0]?.sequence ?? current.sequence; const after = options.after; const cursorStatus = resumeCursorStatus( current.events.length > 0 ? earliestSequence : null, current.sequence, after, ); const expired = cursorStatus === "expired"; const future = cursorStatus === "future"; const events = expired || future ? [] : current.events.filter( (event) => (after === undefined || event.sequence > after) && (options.pullRequestNumber === undefined || event.pullRequestNumbers.includes(options.pullRequestNumber)), ); return { expired, future, earliestSequence, latestSequence: current.sequence, events, }; }