/** * Event ring buffer + sink composition: the "event stream" * without any infrastructure dependency. * * `RingBufferEvents` is a `SyncularServerEvents` sink that retains the last * N events in memory and exposes a `query({type?, sinceMs?, clientId?, * actorId?, limit})` plus a `subscribe(listener)` hook for live tails. It * composes with any other sink through `composeEvents(...sinks)`, so a host * can keep `consoleJsonEvents()` (or a Sentry adapter) AND feed the console * event tail from the same emissions. Fire-and-forget discipline is * preserved: a throwing member sink never affects the others (each emit is * guarded), and the ring itself never throws through. */ import { type SyncularServerEvent, type SyncularServerEvents } from './events.js'; export interface RingEventQuery { /** Restrict to one event `type` (e.g. `push.applied`). */ readonly type?: SyncularServerEvent['type']; /** Only events with `atMs >= sinceMs`. */ readonly sinceMs?: number; /** Restrict to events carrying this `clientId` (not every type does). */ readonly clientId?: string; /** Restrict to events carrying this `actorId` (not every type does). */ readonly actorId?: string; /** Newest-first cap (default: the whole retained buffer). */ readonly limit?: number; } /** * The one matching rule shared by `RingBufferEvents.query` and live * subscribers (the admin SSE tail): `type` matches exactly; `clientId` / * `actorId` match only events that carry the field (events without a * client/actor identity never match an identity filter). */ export declare function matchesRingQuery(event: SyncularServerEvent, query: Omit): boolean; export declare const DEFAULT_RING_CAPACITY = 1000; /** * In-memory ring of the most recent events. When full, the oldest event is * dropped as a new one arrives (bounded memory — the §1.4 anti-goal * discipline applied to observability). Events are stored by reference; * they are already frozen-shape JSON-able objects by the events contract, * so no copy is made on emit. */ export declare class RingBufferEvents implements SyncularServerEvents { #private; constructor(options?: { readonly capacity?: number; }); emit(event: SyncularServerEvent): void; /** * Subscribe to every event as it lands in the ring (the push half the * SSE tail needs). Returns the unsubscribe function. Listeners run * synchronously on the emit path under the same fire-and-forget contract * as sinks: a throwing listener is swallowed. */ subscribe(listener: (event: SyncularServerEvent) => void): () => void; /** The configured maximum number of retained events. */ get capacity(): number; /** The number of events currently retained. */ get size(): number; /** * Newest-first slice of the retained events. Filters by `type` and * `sinceMs`, then caps to `limit`. Returns a fresh array — the caller may * mutate it freely. */ query(query?: RingEventQuery): SyncularServerEvent[]; /** Drop all retained events. */ clear(): void; } /** * Fan one emission out to several sinks. Each member emit is guarded, so a * throwing sink never affects the others or the request path (the same * fire-and-forget contract as `emitEvent`). Composing zero sinks yields a * silent no-op sink. */ export declare function composeEvents(...sinks: readonly SyncularServerEvents[]): SyncularServerEvents;