import { tailContainerLogs } from "./containers.js"; import { ContainerRuntimeInfo } from "./types.js"; /** * Why this exists / when the user is at risk * * Every place that wants to consume a managed container's log stream * — the plugin's `onContainerLog` callback, an SSE client, a future * health-watcher — would otherwise spawn its own `podman logs -f` * child. Two end-users opening the Logs modal on the same container * doubles the number of `logs -f` processes the runtime daemon has * to serve; combined with `onContainerLog` it triples. * * The broker fans out a single underlying tail across N subscribers. * First subscribe spawns the child; last unsubscribe stops it. * Subscribers add themselves with a per-line callback and an * optional `onClose` (used by SSE handlers to flush a final * `event: end` frame before closing the response). * * Lifecycle interaction with the wrapper * * Auto-recreate inside `ensureRunning` removes the live * container and recursively re-enters. The broker's underlying * tail child sees its target disappear and exits naturally. If * subscribers are still attached, the broker auto-respawns after * a 1s delay so SSE-only consumers (no plugin re-subscribe event * to ride on) recover transparently. Subscribers see a brief * gap; no zombie children. * * `containers.remove(name)` closes the broker explicitly * (`close('container-removed')`), notifying every subscriber. * * `plugin.stop()` closes every broker (`'plugin-stopped'`). * * Closed brokers refuse further subscriptions (return a no-op * unsubscribe); callers must allocate a fresh broker via the * wrapper's `getOrCreateBroker` after delete-from-Map. This * matches the "broker is throw-away after close" model. */ export interface LogSubscriber { /** Per-line callback. Errors caught by the broker; never crash * the fan-out for other subscribers. */ onLine: (line: string) => void; /** Called once when the broker is force-closed. SSE handlers * flush their `event: end` frame and `end()` the response. */ onClose?: (reason: "container-removed" | "plugin-stopped") => void; } export interface LogStreamBroker { /** Returns an unsubscribe function. First subscribe lazily * spawns the underlying tail; the unsubscribe that drops the * count to zero stops it. After a `close()`, returns a no-op * unsubscribe (caller should allocate a fresh broker). */ subscribe(sub: LogSubscriber): () => void; /** Current subscriber count. Used by the wrapper to decide * whether to delete the broker from its Map after an SSE * client disconnects. */ subscriberCount(): number; /** Force-close: stops the tail, notifies every subscriber via * `onClose`, drops the subscriber set. Idempotent. */ close(reason: "container-removed" | "plugin-stopped"): void; isClosed(): boolean; } /** * Factory for a per-container `LogStreamBroker`. `runtime` and * `name` are captured for the eventual `tailContainerLogs` call. * `spawnTail` is injectable for tests. * * `onSubscriberError` is invoked when a subscriber's `onLine` * throws — kept out of the fan-out so one buggy subscriber can't * silence the others. Defaults to swallowing the error (the * broker has no logger; the wrapper provides one). */ export declare function createLogStreamBroker(runtime: ContainerRuntimeInfo, name: string, options?: { startTail?: number; /** Test injection: defaults to `tailContainerLogs`. */ spawnTail?: typeof tailContainerLogs; /** Surfaces `podman logs` stderr and process-spawn failures. */ onTailError?: (msg: string) => void; /** Surfaces per-subscriber `onLine` throws. */ onSubscriberError?: (err: unknown, subscriberIndex: number) => void; /** Test injection: shorter delay before auto-respawning the tail * after onExit. Defaults to `RESPAWN_DELAY_MS` (1s) in prod. */ respawnDelayMs?: number; }): LogStreamBroker; //# sourceMappingURL=log-stream-broker.d.ts.map