/** * Shared async surface — the types, the `match` dispatch table, and the * option-bag dev warning used by `useData`, `useAction`, and `all()`. * * Deliberately free of the data-cell engine and the SSR blob: `useAction` * and `all()` import only this module, so an app that never calls `useData` * tree-shakes the entire keyed-data layer (including the `__SIGX_ASYNC__` * pickup) out of its bundle. */ import type { ComponentSetupContext } from '../component-types.js'; export interface AsyncFetcherContext { /** * Pass it straight to fetch(): fetch(url, { signal }) * * Reads: aborted only when this cell is the fetch's sole consumer and * the run is superseded (keyed fetches may be SHARED — dedupe). * Actions: never aborted (an aborted POST is not an undone POST). */ signal: AbortSignal; } /** One fetcher shape everywhere: (trigger's argument, ctx). */ export type Fetcher = (arg: Arg, ctx: AsyncFetcherContext) => Promise; export type AsyncStateName = 'idle' | 'pending' | 'ready' | 'refreshing' | 'errored'; /** * The presence pair: `hasValue` is the discriminant that makes `value` a `T`. * * `value !== null` cannot answer "is there a value?" for a nullable `T` — a * fetch that legitimately resolves `null` (a "not found" read) is a VALUE * (#485). `if (x.hasValue) x.value // T` is the type-safe form of that * question, everywhere this pair appears. */ export type ValuePresence = { readonly value: T; readonly hasValue: true; } | { readonly value: null; readonly hasValue: false; }; /** * Second parameter of the `error` arm. `value`/`hasValue` are the surviving * last-good (the same thing the state's own `value` holds during `'errored'`) * — a legitimately-null last-good has `hasValue: true`. */ export type ErrorArmContext = { readonly retry: () => void; } & ValuePresence; export interface MatchArms { /** Conditional fetch not started ("Type to search…"). Defaults to `pending`. */ idle?: () => R; /** Nothing to show yet. Omitted ⇒ renders nothing while pending. */ pending?: () => R; /** * Fetch failed. "Keep content + toast" reads `ctx.value`/`ctx.hasValue` * (the surviving last-good); the common case destructures just * `(e, { retry })`. Omitted ⇒ undefined + bubble to errorScope / app * onError. */ error?: (e: Error, ctx: ErrorArmContext) => R; /** * The happy path. Reached when the cell HAS a value — which for a nullable * `T` includes a legitimately `null` one, so this is "the value is * present", not "the value is non-null" (#485). */ ready: (v: T) => R; } /** Methods shared by every {@link AsyncState} member. */ export interface AsyncStateBase { match(arms: MatchArms): R | undefined; /** Re-run in place. NEVER rejects — failures land on `.error`. */ refresh(): Promise; } export interface AsyncIdle extends AsyncStateBase { readonly state: 'idle'; readonly value: null; readonly hasValue: false; readonly error: null; readonly loading: false; } export interface AsyncPending extends AsyncStateBase { readonly state: 'pending'; readonly value: null; readonly hasValue: false; readonly error: null; readonly loading: true; } export interface AsyncReady extends AsyncStateBase { readonly state: 'ready'; readonly value: T; readonly hasValue: true; readonly error: null; readonly loading: false; } export interface AsyncRefreshing extends AsyncStateBase { readonly state: 'refreshing'; readonly value: T; readonly hasValue: true; readonly error: null; readonly loading: false; } /** * SWR-through-error: the last-good value survives a failed same-key fetch, so * this member genuinely splits on presence — `hasValue: true` after a failed * refresh of settled data, `false` when the cell never succeeded. */ export type AsyncErrored = AsyncStateBase & { readonly state: 'errored'; readonly error: Error; readonly loading: false; } & ValuePresence; /** * Reactive — reads inside a render fn subscribe like any signal. * * A discriminated union over one STABLE object: `if (x.hasValue) x.value // T` * and `if (x.state === 'ready') x.value // T` both narrow. Narrowing is a * per-read snapshot — the underlying state moves on, so re-check after an * `await` (render fns re-run and re-narrow on every change; this only matters * in event handlers and async code). * * Invariants every producer upholds: `value` is the SWR last-good — kept * across same-key refresh() AND across a failed fetch, CLEARED on key * change; `loading` is `state === 'pending'` ONLY ("nothing to show yet" — * refresh indicators read `'refreshing'`). The state/presence/error * combinations are additionally dev-checked in `matchAsyncState`; `loading` * is not (derive it from the state name and it cannot lie). */ export type AsyncState = AsyncIdle | AsyncPending | AsyncReady | AsyncRefreshing | AsyncErrored; /** * The WIDE shape an engine implements — build one of these (getters over your * own state machine) and return it `as AsyncState` at the seam; the union * is how CONSUMERS see it, not a shape TypeScript can check a stable getter * object against. Invariants the cast asserts, dev-warned in * `matchAsyncState`: idle/pending ⇒ `hasValue` false & `error` null; * ready/refreshing ⇒ `hasValue` true & `error` null; errored ⇒ `error` * non-null. `loading` must be `state === 'pending'` — not runtime-checked * (`matchAsyncState` never sees it); derive it from the state name, as every * in-tree producer does, and it cannot disagree. * * @internal — the §7 pack contract surface. */ export interface AsyncStateImpl { readonly state: AsyncStateName; readonly value: T | null; readonly hasValue: boolean; readonly error: Error | null; readonly loading: boolean; match(arms: MatchArms): R | undefined; refresh(): Promise; } /** Brand identifying engine-made cells — `all()` uses it to tell the object form from a single-member tuple. @internal */ export declare const CELL: unique symbol; /** @internal */ export declare function isCell(v: unknown): boolean; /** * The state→arm dispatch table (shared by client cells, actions, `all()`, * and the server renderer's provider). * * In dev it also checks the {@link AsyncStateImpl} state/presence/error * invariants — the honesty check behind every producer's `as AsyncState` * cast, third-party engines included. (`loading` is outside the view and * outside the check — see {@link AsyncStateImpl}.) * * @internal */ export declare function matchAsyncState(view: { state: AsyncStateName; value: T | null; hasValue: boolean; error: Error | null; retry: () => void; /** Actions keep the last success visible while 'pending' — data cells never do. */ pendingKeepsValue?: boolean; /** Called when the cell is errored and no `error` arm was given. */ onUnhandledError?: (e: Error) => void; }, arms: MatchArms): R | undefined; /** Coerce a rejection reason to an Error (non-Error throws are wrapped). @internal */ export declare function normalizeError(e: unknown): Error; /** * Build the missing-error-arm reporter for one cell: a one-time dev warning * plus one bubble to errorScope / app `onError` per distinct error instance * (a cell re-rendering with the same error must not re-report it). * * @internal */ export declare function makeUnhandledReporter(instance: ComponentSetupContext | null, label: string): (e: Error) => void; /** * Declare option keys as handled so the default engine's unknown-option * warning stays quiet for them. Called by packs that wrap the async engine. * * @internal */ export declare function registerHandledAsyncOptionKeys(...keys: string[]): void; /** * Dev warning of the default engine: an option key nobody handles is almost * always a missing plugin install (e.g. a cache pack). The whole bag still * flows through the provider seam untouched — this never validates or strips. * * @internal */ export declare function warnUnknownOptions(fnName: string, options: object | undefined, coreKeys: ReadonlySet): void; /** * A never-aborting stand-in for environments without `AbortController` * (embedded runtimes). Fetchers can pass it to APIs unconditionally. * * @internal */ export declare function inertAbortSignal(): AbortSignal; /** Create an AbortController when the platform has one. @internal */ export declare function makeAbortController(): AbortController | null; //# sourceMappingURL=shared.d.ts.map