/** * Concurrency control for Infinite Row Model page fetches. * * The Server-Side Row Model's * {@link import('../server/server-request-controller').ServerRequestController} * deliberately keeps **one** request in flight and aborts the previous one on * every new request — correct when each request replaces the whole viewport. * Infinite scrolling is the opposite: several pages are legitimately in flight * at once (the one you are looking at, plus prefetch), and they must not cancel * each other. * * This queue therefore owns: * * - **Deduplication.** A page already in flight is never requested twice; the * second caller joins the first request's promise. * - **A concurrency cap.** Excess pages wait in a queue and start as slots free, * so a fast scroll cannot open dozens of connections. * - **Per-page cancellation.** Each page has its own `AbortController`, so * abandoning one page (scrolled away, or the query changed) cancels exactly * that request. * - **Retry with delay**, reusing the promise-bridging shape of the SSRM * controller so a datasource's `success` / `fail` / thrown / rejected paths * are all normalised identically. * - **Generation stamping.** Every request records the generation it was issued * under; a response arriving after the query changed is dropped instead of * being applied to a dataset it does not belong to. * * The queue knows nothing about the grid — it only drives a datasource — so it * is framework-free and unit-testable. * * @packageDocumentation */ import type { ServerSideDatasource, ServerSideRequest, ServerSideResult } from '../../types/server-side.types'; /** Outcome of one page fetch. */ export interface PageFetchOutcome { /** Zero-based page index. */ readonly page: number; /** The rows returned, or `null` when the fetch failed or was discarded. */ readonly result: ServerSideResult | null; /** The error, when the fetch failed after exhausting retries. */ readonly error?: unknown; /** Retry attempts made. */ readonly attempts: number; /** `true` when the response was discarded as stale or aborted. */ readonly discarded: boolean; } /** Callbacks the queue fires as a page moves through its lifecycle. */ export interface PageFetchHooks { /** Invoked immediately before each attempt (0-based). */ onAttempt?(page: number, attempt: number): void; } /** Everything needed to issue one page request. */ export interface PageFetchTask { /** Zero-based page index. */ readonly page: number; /** The request to hand to the datasource. */ readonly request: ServerSideRequest; /** Generation this request belongs to. */ readonly generation: number; } /** Runs page fetches with dedup, a concurrency cap, per-page abort and retry. */ export declare class InfiniteRequestQueue { private readonly maxConcurrent; private readonly maxRetries; private readonly retryDelay; /** Pages currently being fetched, by page index. */ private readonly inFlight; /** Promises for in-flight pages, so a duplicate request joins rather than re-issues. */ private readonly joins; /** Pages waiting for a concurrency slot, in request order. */ private readonly waiting; /** Slots currently reserved. Held separately from `inFlight` so a slot is claimed the moment it is granted. */ private active; private destroyed; /** * @param maxConcurrent - Requests allowed in flight at once. * @param maxRetries - Retry attempts before a page is reported as failed. * @param retryDelay - Milliseconds between attempts. */ constructor(maxConcurrent: number, maxRetries: number, retryDelay: number); /** Pages currently being fetched. */ get inFlightCount(): number; /** Pages waiting for a slot. */ get queuedCount(): number; /** `true` when the page is already being fetched. */ isInFlight(page: number): boolean; /** * Fetches a page, or joins the in-flight request for it. * * @param task - The page, request and generation. * @param datasource - Datasource to drive. * @param isCurrent - Predicate telling the queue whether `generation` is * still the active one; consulted after every await so a * superseded response is dropped rather than applied. * @param hooks - Optional lifecycle callbacks. * @returns The outcome. Never rejects — failures are reported in the outcome. */ fetch(task: PageFetchTask, datasource: ServerSideDatasource, isCurrent: (generation: number) => boolean, hooks?: PageFetchHooks): Promise; /** * Aborts a single page's request, if it is in flight. * * @param page - Zero-based page index. */ abortPage(page: number): void; /** * Aborts every in-flight request and clears the waiting queue. * * Used when the query changes: those responses describe a dataset that is no * longer displayed. */ abortAll(): void; /** Aborts everything and refuses further work. */ destroy(): void; /** Awaits a concurrency slot, then runs the request with retries. */ private run; /** * Resolves once a concurrency slot is free. * * The slot is reserved **synchronously** on grant. Deriving availability from * `inFlight.size` instead would let every caller in a burst pass the check * before any of them had recorded itself — `inFlight` is only populated after * the await — and the cap would never bind. */ private acquireSlot; /** Returns a slot and starts the next waiter. */ private releaseSlot; /** Releases waiters while slots remain. */ private pump; } //# sourceMappingURL=infinite-request-queue.d.ts.map