/** * Refreshable Stream * * Helper estándar para vistas que consumen datos de Firestore. Soporta DOS * modos — el caller decide cuál vía la `factory`: * * - **one-shot** (recomendado por defecto): `() => from(svc.getAllOnce())`. * Carga una vez y completa. Más barato — sin listener vivo. * - **real-time**: `() => svc.getAll()`. Listener Firestore que auto-actualiza. * Solo donde importa (inbox de notificaciones, badges). * * En ambos modos la primera suscripción se gatea por `firebaseAuthReady` * (sesión de Firebase Auth lista) — así el listener/lectura nunca se adjunta * antes de que `request.auth` esté disponible en las reglas de Firestore, lo * que cierra la ventana de `permission-denied` en cold start de PWA. * * El `retry` con backoff corto es solo red de seguridad para reconexiones * transitorias (no para cubrir cold start — eso lo cubre el gate de auth). */ import { Injector, Signal } from '@angular/core'; import { Observable } from 'rxjs'; /** Opciones de configuración para `createRefreshableStream`. */ export interface RefreshableStreamOptions { /** * Valor emitido cuando la factory falla tras agotar los reintentos. * Default: `null`. */ fallback?: T; /** * Número máximo de reintentos ante un error de la factory. * Backoff corto — NO cubre cold start (eso lo hace el gate de auth), solo * reconexiones transitorias. Default: 4. */ retryCount?: number; /** * Delay base del backoff (ms). El delay efectivo crece exponencialmente * acotado: 500ms → 1s → 2s ... Default: 500. */ retryBaseDelayMs?: number; /** * Si `false`, NO espera a `firebaseAuthReady` antes de la primera * suscripción. Útil para streams que no leen Firestore. Default: `true`. */ gateOnFirebaseAuth?: boolean; /** * Timeout (ms) del gate de Firebase Auth. Si la sesión de Firebase Auth no * se establece en este tiempo, el stream procede igual (la factory correrá * y, si no hay permiso, terminará en estado `error` — NUNCA en skeleton * infinito). Muy por encima de cualquier handshake real. Default: 20000. */ authGateTimeoutMs?: number; /** * Timeout (ms) de la PRIMERA emisión de la factory. Si la factory no emite * ni un valor ni un error en este tiempo (caso típico: un servicio Firestore * que nunca se inicializó → `EMPTY` → silencio total), el stream pasa a * estado `error` en vez de quedarse en skeleton perpetuo. Default: 20000. */ firstEmitTimeoutMs?: number; /** * Injector explícito. Solo necesario si `createRefreshableStream` se llama * fuera de un injection context (raro). Por defecto usa `inject(Injector)`. */ injector?: Injector; } /** * Resultado de `createRefreshableStream`. */ export interface RefreshableStream { /** Datos actuales. `null` mientras la primera emisión no ha llegado. */ readonly data: Signal; /** `true` mientras se espera la primera emisión de la suscripción actual. */ readonly loading: Signal; /** `true` si la factory falló tras agotar los reintentos. */ readonly error: Signal; /** * Re-suscribe la factory desde cero (nuevo listener / nueva lectura). * Es lo que registra el `PageRefreshService` como handler de pull-to-refresh, * y también el "reconectar manual" si un listener murió. */ reload: () => void; } /** * Crea un stream refrescable a partir de una factory de Observable. * * DEBE llamarse en un injection context (field initializer o constructor) — * usa `toSignal`/`toObservable`/`inject` internamente. * * @param factory - Función que produce el Observable a consumir. El caller * decide el modo: `() => from(svc.getAllOnce())` (one-shot) o * `() => svc.getAll()` (real-time). * @param options - Configuración opcional (fallback, retry, gate). * * @example * ```typescript * // one-shot (recomendado por defecto) * private readonly stream = createRefreshableStream( * () => from(this.svc.getAllOnce()), * ); * * // real-time (inbox) * private readonly stream = createRefreshableStream( * () => this.notifs.getAll(50), * ); * * readonly items = this.stream.data; * readonly loading = this.stream.loading; * * ionViewWillEnter() { * this.pageRefresh.register(() => this.stream.reload()); * } * ``` */ export declare function createRefreshableStream(factory: () => Observable, options?: RefreshableStreamOptions): RefreshableStream;