/** Keep only the handles each in-flight reader actually obtained. */
export declare function indexReaders<T>(current: () => T, close: (handle: T) => void): {
    get(): T;
    retire(handle: T): void;
    run<R>(fn: () => R | Promise<R>): Promise<R>;
};
export interface BusyWait {
    /** Whether an error means "another process is rebuilding" (worth waiting out). */
    isBusy: (error: unknown) => boolean;
    /** Total budget across retries. */
    maxWaitMs: number;
    now?: () => number;
    sleep?: (ms: number) => Promise<void>;
}
/** PHREN_INDEX_BUSY_WAIT_MS, default 5000, capped at 30000; 0 disables the wait. */
export declare function indexBusyWaitMs(env?: NodeJS.ProcessEnv): number;
/**
 * Run `fn`, and while it fails only because another process holds the rebuild
 * lock, retry with jittered backoff until `maxWaitMs` is spent. Any other
 * error, or the last busy error once the budget is gone, propagates.
 * Another process's rebuild usually leaves a cache entry for the same store
 * state, so the retry after it is a cheap cache load rather than a second build.
 */
export declare function retryWhileBusy<T>(fn: () => Promise<T>, wait: BusyWait): Promise<T>;
/** Coalesce local freshness checks without a timer or any remote polling. */
export declare function localIndexRefresh(options: {
    isFresh: () => boolean;
    refresh: () => Promise<void>;
    runExclusive: (fn: () => Promise<void>) => Promise<unknown>;
    now?: () => number;
    intervalMs?: number;
    /** Wait out another process's rebuild instead of failing on the first busy lock. */
    busyWait?: BusyWait;
}): {
    ensureFresh: () => Promise<void>;
};
