import { Observable } from "rxjs"; import type { IResource, TCacheEntryAddedContext, TProjectionResourceOptions } from "../../../query/types/index.js"; /** * Engine behind `api.unstable_createProjectionResource`. * * The projection resource itself is an ordinary {@link IResource} caching one entry * per id-set, so agents, React hooks, SWR and plugin augmentation work * unchanged. This runtime plugs into that resource (as its `queryFn` + * `onCacheEntryAdded`) and deduplicates the traffic underneath. * * The item cache is reactive (a keyed signal of per-id boxes) and the outer * queryFn returns a *stream*: once the initial fetches land, the run projects * the watched ids over the item cache and keeps emitting for as long as the * entry lives. Cross-set consistency falls out of that projection — when one * set's refresh distributes fresh items, every overlapping live entry re-emits * with them (rebasing its active optimistic patches), with no write-back pass. * * - a shared per-id item cache is consulted first — only the ids that are * neither cached nor already in flight reach the wrapped resource * (`makeArgs(missingIds)`); * - a run whose ids are all covered by cache/in-flight batches performs no * request at all; * - a refresh run bypasses the item cache and refetches every requested id, * without joining requests begun before it — they may carry pre-refresh * data (detected via the entry's machine, which `_execute` moves to * `refreshing` before calling `queryFn`); * - items are reference-counted by the entries whose args mention them and * evicted once the last such entry is removed (retention GC / reset). */ export declare class ProjectionRuntime { private readonly _wrapped; private readonly _parseData; private readonly _makeArgs; private readonly _parseArgs; private readonly _serializeId; /** The outer resource; late-bound because it is created around this runtime. */ private _resource; /** * Reactive item cache: serialized id → boxed item. The box distinguishes a * cached `undefined`-ish item from absence; the keyed signal gives each * open run's projection fine-grained per-id reactivity. */ private readonly _items; /** How many live outer entries reference each serialized id. */ private readonly _refCounts; /** * Serialized id → the in-flight batch fetch covering it, resolving with * the serialized ids the response actually covered. */ private readonly _inFlight; /** One warning per projection resource about set-local patch semantics. */ private _didWarnSetLocalPatch; constructor(options: TProjectionResourceOptions); /** Bind the outer resource once `Api.createResource` has built it. */ attach(resource: IResource): void; /** * The outer resource's queryFn — a stream per run: * * 1. On subscribe, the ids missing from the item cache are fetched through * the wrapped resource; ids already in flight are awaited instead of * re-requested. A refresh run bypasses both: it must observe the server * state as of the refresh call, so it issues a fresh request for every * requested id — joining a request begun before the refresh could * settle it with pre-refresh data. The fresh request replaces the * in-flight registrations, so runs started later join it as usual. * 2. Once the initial fetches land, the run emits the assembled `TItem[]` * and stays subscribed to the watched ids: whenever another run * distributes a fresh instance of one of them, the projection re-emits. * The stream never completes — it is torn down with the run * (refresh/retry resubscribe, entry eviction unsubscribes). * 3. A failed fetch errors the stream; so does a response that did not * cover every requested id ({@link ProjectionItemMissingError}). * * The abort signal is intentionally ignored: a batch fetch may be shared by * several id-set entries, so one entry's teardown must not cancel it — the * torn-down run simply unsubscribes and ignores the late result. */ queryFn: (args: TArgs, _abortSignal: AbortSignal) => Observable; /** * Reference-count the ids of every outer entry so items survive exactly as * long as some live entry mentions them, and are evicted with the last one. */ onCacheEntryAdded: (args: TArgs, ctx: TCacheEntryAddedContext) => void; private _warnSetLocalPatch; /** * Fetch one batch of ids through the wrapped resource and register it as * in-flight for each id (replacing any previous registration — later runs * join this request). Resolves with the serialized ids the response * actually covered. */ private _fetchBatch; /** * Spread a batch response over the reactive item cache. Live projections * watching the touched ids re-emit on their own; the writes are batched so * one response produces a single emission per affected entry. * * Returns the serialized ids the response covered — independent of whether * each item was actually (re)cached (unreferenced or identical instances * are skipped but still covered). */ private _distribute; }