export type DeliveryStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'abandoned'; export interface BusEvent { id: number; type: TType; payload: TPayload; emittedAt: number; emittedBy: string | null; dedupKey: string | null; replyTo: string | null; replyFor: number | null; } export interface Subscriber { id: number; name: string; pattern: string; likePattern: string; segmentCount: number; handler: string; maxAttempts: number; timeoutMs: number; registeredAt: number; registeredBy: string | null; } export interface Delivery { eventId: number; subscriberId: number; status: DeliveryStatus; attempts: number; nextAttemptAt: number | null; startedAt: number | null; finishedAt: number | null; lastError: string | null; } /** One subscriber's share of the failed/abandoned backlog. */ export interface FailedBySubscriber { subscriber: string; count: number; latestFinishedAt: number | null; } /** * A subscriber whose most recent deliveries have ALL failed: every failed or * abandoned delivery that finished after its last success. */ export interface FailingStreak { subscriber: string; /** How many deliveries have failed since the last one that succeeded. */ failures: number; /** When the first failure of the streak finished (ms). */ since: number; /** When the newest failure finished (ms). */ latest: number; /** When a delivery last succeeded (ms), or null when none is on record. */ lastSucceededAt: number | null; } export interface DispatcherHeartbeat { dispatcherId: string; lastHeartbeat: number; startedAt: number; pid: number; version: string | null; } export interface LiveDispatcher { dispatcherId: string; pid: number; version: string | null; lastHeartbeatAgeMs: number; startedAgoMs: number; } export interface BusHealth { status: | 'healthy' | 'recovering' | 'stuck' /** Registered, process alive, heartbeat stale, nothing running: the loop is dead. */ | 'wedged' /** * Alive and still beating, just slowly — a heartbeat starved by box load * (celilo#1399). Indistinguishable from `wedged` in one sample: both show an * advancing AGE. Only `healthConfirmed()` can tell them apart, by watching * whether the heartbeat TIMESTAMP moves. Recovers on its own; no remedy. */ | 'lagging' | 'no_dispatcher' | 'duplicate_dispatcher'; lastHeartbeatAgeMs: number | null; stuckRunningCount: number; oldestPendingAgeMs: number | null; /** Dispatchers whose process still exists. >1 means a duplicate is running. */ dispatcherCount: number; /** Operator action that clears this status. Set for `wedged`. */ remedy?: string; dispatchers: LiveDispatcher[]; } export interface EmitOptions { dedupKey?: string; emittedBy?: string; replyTo?: string; replyFor?: number; } export interface SubscribeOptions { name: string; pattern: string; handler: string; maxAttempts?: number; timeoutMs?: number; registeredBy?: string; } export interface QueryOptions { /** * Hard timeout in ms. 0 = wait forever. Default 5_000. * "Wait forever" is what the celilo interview flow wants — there's * no meaningful timeout for "operator hasn't typed yet." */ timeoutMs?: number; /** * Polling interval for cross-process replies, in ms. Default 250. * The bus only fires in-process watches synchronously; cross-process * replies (a responder running in a separate process replying via * bus.reply) are only visible by polling the events table. */ pollIntervalMs?: number; expect?: 'first' | 'all' | { count: number }; emittedBy?: string; } export interface DispatcherOptions { pollIntervalMs?: number; concurrency?: number; staleHandlerMultiplier?: number; haltOnRecovery?: boolean; } export interface RetentionOptions { eventsRetentionDays?: number; failedRetentionDays?: number; }