/**
 * PersistentChannel — a durable, convergent view over one broadcast channel.
 *
 * Wraps a {@link RealtimeRoom} channel as a **grow-only set** keyed by message
 * id: every record is deduplicated by id and ordered by `(ts, id)`, so the same
 * message arriving from any source (optimistic local send, live broadcast, or a
 * relay replay on reconnect) lands exactly once and in a stable order.
 *
 * There is no single source of truth. Each peer holds its own replica (a
 * pluggable {@link ChannelStore} — `localStorage`, IndexedDB, …) and the relay
 * holds another. They converge on reconnect because union + id-dedup is
 * commutative and idempotent. The store is an instant first-paint cache, not an
 * authority: a refresh repaints from it immediately, then merges whatever the
 * relay replays.
 *
 *   const chat = PersistentChannel.create(room, 'chat', {
 *     store: localStorageChannelStore(`chat:${roomUrl}`),
 *     resolveMeta: (e) => presenceById()[e.from],   // capture name/color
 *   });
 *
 *   chat.messages.subscribe((msgs) => render(msgs)); // deduped + sorted
 *   chat.send({ text });                             // optimistic + broadcast + persist
 *
 * Only the channel's "message" event (configurable) is persisted; transient
 * events like typing indicators stay the caller's concern via `room.on`.
 */
import { Signal } from '../client/reactive.js';
import type { RealtimeRoom } from './room.js';
import type { BroadcastEvent } from './types.js';
/** A persisted member of the grow-only set. */
export interface ChannelRecord<T = unknown> {
    /** Stable dedup key (sender-assigned message id). */
    id: string;
    /** Originating peer id. */
    from: string;
    /** Sender clock (epoch ms) — primary sort key. */
    ts: number;
    /** Application payload (e.g. `{ text }`). */
    payload: T;
    /** Optional sender metadata captured at send/receipt (name, color, …). */
    meta?: Record<string, unknown>;
}
/**
 * Durable backing store for a peer's local replica. Implementations may be sync
 * (`localStorage`) or async (IndexedDB/OPFS); `load` may return a promise.
 */
export interface ChannelStore<T = unknown> {
    load(): ChannelRecord<T>[] | Promise<ChannelRecord<T>[]>;
    save(records: ChannelRecord<T>[]): void | Promise<void>;
}
export interface PersistentChannelOptions<T = unknown> {
    /** Broadcast event name treated as a persisted message. Default `'message'`. */
    event?: string;
    /** Durable replica for this peer. Omit for in-memory only. */
    store?: ChannelStore<T>;
    /** Keep at most the N most recent records. Default 200. */
    max?: number;
    /** Capture sender metadata for an inbound message (e.g. from presence). */
    resolveMeta?: (event: BroadcastEvent) => Record<string, unknown> | undefined;
    /** Injectable clock for local sends. Default `Date.now`. */
    now?: () => number;
}
export declare const DEFAULT_MAX_RECORDS = 200;
export declare class PersistentChannel<T = unknown> {
    /** Reactive, deduped, `(ts, id)`-sorted message list. */
    readonly messages: Signal<ChannelRecord<T>[]>;
    private readonly room;
    private readonly channel;
    private readonly event;
    private readonly store?;
    private readonly max;
    private readonly resolveMeta?;
    private readonly now;
    private records;
    private byId;
    private unsubscribe;
    private disposed;
    private constructor();
    /** Create a persistent view over `room`'s `channel`. */
    static create<T = unknown>(room: RealtimeRoom, channel: string, opts?: PersistentChannelOptions<T>): PersistentChannel<T>;
    /**
     * Optimistically record locally, broadcast to peers, and persist. Returns the
     * stable message id. The room dedups its own id, so the broadcast never
     * echoes back into this channel.
     */
    send(payload: T, meta?: Record<string, unknown>): string;
    /** Current records (deduped + sorted). Read without subscribing. */
    snapshot(): ChannelRecord<T>[];
    /** Stop listening. Does not clear the durable store. */
    dispose(): void;
    private handleEvent;
    private hydrate;
    /** Insert one record and publish if it was new. */
    private commitOne;
    /** Pure insert with dedup + cap. Returns true when a new record was added. */
    private add;
    private commit;
}
/**
 * A {@link ChannelStore} backed by Web Storage (`localStorage` by default).
 * Degrades to in-memory if storage is unavailable or over quota — durability is
 * best-effort, since the relay replica remains authoritative for catch-up.
 */
export declare function localStorageChannelStore<T = unknown>(key: string, storage?: Storage | undefined): ChannelStore<T>;
//# sourceMappingURL=persistent-channel.d.ts.map