/** The lifecycle status of a {@link Resource}. */ type ResourceStatus = 'idle' | 'running' | 'completed' | 'failed'; /** Optional progress for a long-running fetch (uploads, chunked work). */ interface ResourceProgress { completed: number; total: number; } /** The reactive state a {@link Resource} exposes. */ interface ResourceState { status: ResourceStatus; /** The last successful value. Kept across a re-run (stale-while-revalidate) and on failure. */ data: T | undefined; /** The rejection from the most recent failed run. */ error: unknown; /** Latest reported progress while running, or `undefined`. */ progress: ResourceProgress | undefined; /** * The input of the LATEST run — the value passed to {@link Resource.run} as * `run(input, fetcher)`. Set for `running`, `completed`, AND `failed` (same * stale-guard rule as the rest of the state), so an effect can branch on * `status === 'failed'` and still know which request failed. `undefined` in * `idle`, and for the no-input `run(fetcher)` form. */ input: I | undefined; /** * A monotonic counter that increments only when `data` actually CHANGES (by * the resource's `equals`, default `Object.is`). Compare it against the value * you last painted to skip a redundant re-render — e.g. a 30s poll returning * identical data leaves `revision` untouched, so you can bail before wiping * scroll / sort / hover state. Starts at `0`. */ revision: number; } /** Construction options for {@link resource}. */ interface ResourceOptions { /** * Derive a cache key from a run's `input`. When set, the resource keeps the * last successful value PER key: starting a run for a key that was loaded * before paints its cached slice immediately (still `running`) while the fetch * revalidates in the background; a never-loaded key starts with no `data`. * Without `cacheKey`, a run keeps the previous run's `data` (single-slot * stale-while-revalidate), as before. */ cacheKey?: (input: I) => string; /** * Equality used to decide whether `data` changed (drives `value.revision`). * Default `Object.is`. Pass a structural comparison to dedup a poll that * returns a fresh-but-equal object. */ equals?: (a: T, b: T) => boolean; } /** * The fetcher passed to {@link Resource.run}. You own the transport. It receives * a `report(completed, total)` callback for optional progress — ignore it if you * don't need progress (a plain `() => Promise` is assignable here). */ type ResourceFetcher = (report: (completed: number, total: number) => void) => Promise; /** * An async-state container. Its `value` is a tracking read; drive UI off * `value.status`. `I` is the run-input type — parametrize it (`resource()`) * to carry a typed `run(input, fetcher)` input through to `value.input`. */ interface Resource { /** Tracking read of the current {@link ResourceState}. */ readonly value: ResourceState; /** * Run `fetcher`, driving `idle`/`running` → `completed`/`failed` and guarding * against stale responses (only the latest run resolves the state). Never * rejects — a failure lands in `value.error`; resolves with the data (or * `undefined` on failure) for callers who want to await it. */ run(fetcher: ResourceFetcher): Promise; /** * Run `fetcher` for a given `input`, exposing it as `value.input` for the * `running`/`completed`/`failed` states of THIS run — so a failure handler can * recover which request failed. Same stale guard: only the latest run resolves. */ run(input: I, fetcher: ResourceFetcher): Promise; /** Reset to `idle` (clearing data/error/progress/input, and the per-key cache) and invalidate any in-flight run. */ reset(): void; /** * Read-only: the cached value for a `cacheKey` key, or `undefined` if that key * isn't cached (or no `cacheKey` was given). Lets a consumer ask "is this slice * cached?" without running it (which would mutate state). */ cached(key: string): T | undefined; /** Read-only: the keys currently in the per-input cache (`.length` is the cache size). */ cachedKeys(): string[]; /** Evict the per-input cache — one `key`, or the whole cache when called with no argument. Does NOT change `value`. */ clearCache(key?: string): void; } /** Create an async-state {@link Resource}. No per-instance framework state — it's a closure over a signal. */ declare function resource(options?: ResourceOptions): Resource; export { type Resource, type ResourceFetcher, type ResourceOptions, type ResourceProgress, type ResourceState, type ResourceStatus, resource };