import type { Observable } from "rxjs"; import type { ArgsOrVoidOrSkip, TResourceSnapshot } from "../../query/index.js"; import type { ReadonlySignal } from "../../signals/types/index.js"; import type { TMapError } from "./api.js"; import type { IQueryCacheEntry, TCacheEntryAddedContext, TQueryStartedContext } from "./cache.js"; import type { Args, ArgsOrVoid, Keyed } from "./common.js"; import type { TResourceAgentState, TRetrying } from "./state.js"; export interface IResource { /** * @deprecated Use {@link prefetch}: `trigger(args)` ≈ `prefetch(args)`, * `trigger(args, true)` ≈ `prefetch(args, { force: true })`. Not an exact * match on an `error`-state entry: `prefetch` retries it in both modes, * while `trigger` left it untouched. And unlike `trigger`, every * `prefetch` call — cache hits included — holds a keepalive subscription * until it settles and then restarts the entry's retention countdown. * Will be removed in a future release. */ trigger(args: Args, doForce?: boolean): void; refresh(args: Args): void; getEntry(args: ArgsOrVoid, doInitiate: true): IQueryCacheEntry; getEntry(args: ArgsOrVoid, doInitiate?: boolean): IQueryCacheEntry | null; getEntry$(args: ArgsOrVoid, doInitiate?: boolean): ReadonlySignal | null>; getEntries(): IterableIterator>; createAgent(): IResourceAgent; serialize(args: Args): string; toKeyed(args: Args): Keyed; getState(args: ArgsOrVoid): IResourceLiteState; pack(args: Args): TPackedResource; /** Resolve with cached data, loading it first when absent. Rejects on failure/abort. */ ensure(args: Args, options?: TResourceFetchOptions): Promise; /** Resolve with the result of a fresh query. Rejects on failure/abort. */ fetch(args: Args, options?: TResourceFetchOptions): Promise; /** Fire-and-forget cache warm-up; creates the entry synchronously, never rejects. */ prefetch(args: Args, options?: TResourcePrefetchOptions): Promise; } /** * Options for the imperative {@link IResource.ensure} / {@link IResource.fetch} * methods. */ export interface TResourceFetchOptions { /** * Detaches the caller from the awaited query when aborted: the returned * promise rejects with the signal's reason. The underlying query is left * running for any other consumers and is torn down by retention GC only once * no consumer remains — aborting one caller never cancels a shared in-flight * request. {@link IResource.prefetch} is intentionally not abort-aware. */ signal?: AbortSignal; } /** Options for {@link IResource.prefetch}. */ export interface TResourcePrefetchOptions { /** * When `true`, warms the cache with *fresh* data: an existing entry is * refreshed (or retried after an error) instead of being reused as-is — * the fire-and-forget counterpart of {@link IResource.fetch}. */ force?: boolean; } /** * Inert descriptor binding a resource to a set of arguments. Produced by * {@link IResource.pack} — lets a consumer hand "what to read, with which args" * back to the library without executing anything. Discriminated by `kind`; * see {@link TPacked} for the command counterpart. */ export interface TPackedResource { kind: "resource"; resource: IResource; args: Args; } /** No cache entry exists for the given arguments. */ export interface TResourceLiteIdleState { status: "idle"; data: null; error: null; args: null; isLoading: false; isInitialLoading: false; isRefreshing: false; isRetrying: false; isRefreshError: false; isSuccess: false; isError: false; } interface TResourceLitePendingBase { status: "pending"; data: null; args: TArgs; isLoading: true; isInitialLoading: true; isRefreshing: false; isRefreshError: false; isSuccess: false; isError: false; } /** Initial load in flight: no data yet. With `isRetrying`, `error` holds the retried failure. */ export type TResourceLitePendingState = TResourceLitePendingBase & TRetrying; /** Query succeeded: `data` is present, no error. */ export interface TResourceLiteSuccessState { status: "success"; data: TData; error: null; args: TArgs; isLoading: false; isInitialLoading: false; isRefreshing: false; isRetrying: false; isRefreshError: false; isSuccess: true; isError: false; } /** Initial query failed: `error` is present, no data. */ export interface TResourceLiteErrorState { status: "error"; data: null; error: TError; args: TArgs; isLoading: false; isInitialLoading: false; isRefreshing: false; isRetrying: false; isRefreshError: false; isSuccess: false; isError: true; } interface TResourceLiteRefreshingBase { status: "refreshing"; data: TData; args: TArgs; isLoading: true; isInitialLoading: false; isRefreshing: true; isRefreshError: false; isSuccess: false; isError: false; } /** Background refresh in flight; stale `data` stays available. With `isRetrying`, `error` holds the retried failure. */ export type TResourceLiteRefreshingState = TResourceLiteRefreshingBase & TRetrying; /** Background refresh failed; stale `data` is preserved. */ export interface TResourceLiteRefreshErrorState { status: "refresh-error"; data: TData; error: TError; args: TArgs; isLoading: false; isInitialLoading: false; isRefreshing: false; isRetrying: false; isRefreshError: true; isSuccess: false; isError: true; } export type IResourceLiteState = TResourceLiteIdleState | TResourceLitePendingState | TResourceLiteSuccessState | TResourceLiteErrorState | TResourceLiteRefreshingState | TResourceLiteRefreshErrorState; export interface IResourceAgent { state$: ReadonlySignal>; start(): void; set(args: ArgsOrVoidOrSkip, mark?: boolean): void; /** * Take over `source`'s data as this agent's SWR fallback — what `set` keeps * from the previous args on a single agent, for consumers that replace the * agent instead (one agent per args). Reads `source` once; it is not kept. */ adoptPrevious(source: IResourceAgent): void; retry(): void; refresh(): void; /** * Promise resolving once the agent leaves the initial-loading phase — data * became available (success / refreshing / refresh-error / stale SWR) or the * query failed with nothing to fall back on. Never rejects. Used by the * Suspense hook to wake React after a suspended render. */ whenSettled(): Promise; get args(): TArgs | null; } /** * What a resource's queryFn may return. * * - `Promise` — a one-shot query: resolves once, settles the run. * - `Observable` — a streaming query: the first emission settles the * run (pending → success), every subsequent emission updates the entry's * data in place (active optimistic patches are rebased onto it), an error * after data lands in `refresh-error` with the data kept, and completion * simply ends the live phase — the entry keeps the last emission. The * subscription is torn down when the entry is evicted or the run is * superseded (refresh / retry resubscribe); completing without a single * emission fails the run with `EmptyStreamError`. */ export type TQueryFnResult = Promise | Observable; export interface TResourceOptions { queryFn: (args: TArgs, abortSignal: AbortSignal) => TQueryFnResult; key?: string; retentionTime?: number | false; serializeArgs?: (args: TArgs) => string; onCacheEntryAdded?: (args: TArgs, ctx: TCacheEntryAddedContext) => void; onQueryStarted?: (args: TArgs, ctx: TQueryStartedContext) => void | Promise; snapshotValidTime?: number | false; /** * When `false`, the resource neither contributes entries to `getSnapshot()` * nor hydrates from `initialSnapshot`, regardless of its `key`. For derived * resources whose data is owned elsewhere (projection resources set this * automatically). Defaults to `true`. */ snapshotable?: boolean; sync?: boolean; /** * Suppresses the one-time warning logged when an optimistic patch is * created while a query stream is open. While the stream lives, every * emission rebases over active patches and a committed patch dissolves * into the next emission's data — set this to `true` once that interplay * is intended. Defaults to `false`. */ allowStreamPatches?: boolean; } export interface IResourceConfig { queryFn: (args: TArgs, abortSignal: AbortSignal) => TQueryFnResult; key?: string; retentionTime: number | false; serializeArgs: (args: TArgs) => string; /** * Normalizes raw query errors before they enter the machine. The Api always * supplies one (identity when the consumer configured no `mapError`); * defaults to identity if constructed directly. See {@link TMapError}. */ mapError?: TMapError; onCacheEntryAdded?: (args: TArgs, ctx: TCacheEntryAddedContext) => void; onQueryStarted?: (args: TArgs, ctx: TQueryStartedContext) => void | Promise; /** Pre-populated entries from snapshot hydration (key → snapshot meta). */ snapshot?: TResourceSnapshot; /** When `false`, the resource is skipped by `Snapshoter.getSnapshot`. Defaults to `true`. */ snapshotable?: boolean; /** Cross-tab sync hook: called before queryFn to check if another tab has cached data. */ beforeQuery?: (resourceKey: string, entryKey: string) => Promise<{ data: TData; } | null>; /** See {@link TResourceOptions.allowStreamPatches}. Defaults to `false`. */ allowStreamPatches?: boolean; } export {};