UNPKG

jotai-eager

Version:

Jōtai utilities that help with asynchronous atoms

286 lines (278 loc) 13 kB
import { Atom, ExtractAtomValue, Getter, Setter, WritableAtom } from 'jotai/vanilla'; type AwaitAtomsValues<TTuple extends readonly [Atom<unknown>, ...Atom<unknown>[]]> = { [Index in keyof TTuple]: Awaited<ExtractAtomValue<TTuple[Index]>>; }; /** * Awaits all `deps` if necessary, then runs `op` given all deps in the same order. * If computing the value fails (throws), a rejected Promise is returned no matter if * the processing happened synchronously or not. * * @param deps An array of atoms whose values will be awaited and passed to the operation function. * @param op A function that takes the awaited values of the dependencies and returns a new value. It is called synchronously if all dependencies are fulfilled, otherwise asynchronously. * @returns An atom that resolves to the result of the operation or a promise of the result if dependencies are pending. * @deprecated Use `eagerAtom` for new code, as it provides a better developer experience, and matches more closely to how regualr atoms are defined. * * @example * ```ts * import { atom } from 'jotai'; * import { derive } from 'jotai-eager'; * * const aAtom = atom(async () => 1); * const bAtom = atom(async () => 2); * const sumAtom = derive([aAtom, bAtom], (a, b) => a + b); * ``` */ declare function derive<TDeps extends readonly [Atom<unknown>, ...Atom<unknown>[]], TValue>(deps: TDeps, op: (...depValues: AwaitAtomsValues<TDeps>) => TValue): Atom<TValue | Promise<Awaited<TValue>>>; type AwaitedAll<T extends readonly unknown[]> = { [K in keyof T]: Awaited<T[K] extends Atom<infer Value> ? Value : T[K]>; }; interface EagerGetter { /** * Retrieves the atom's fulfilled value. * If the value is not yet available, it interrupts the execution * of the eager atom until it's available. */ <Value>(atom: Atom<Value>): Awaited<Value>; /** * Retrieves the Promise's fulfilled value. * If the value is not yet available, it interrupts the execution * of the eager atom until it's available. */ await<T>(promiseOrValue: T): Awaited<T>; /** * Retrieves the fulfilled value of all passed in Promises. * If the values are not yet available, it interrupts the execution * of the eager atom until they're available. */ awaitAll<T extends readonly unknown[]>(args: T): AwaitedAll<T>; /** * Retrieves the fulfilled value of all passed in atoms. * If the values are not yet available, it interrupts the execution * of the eager atom until they're available. */ all<T extends readonly Atom<unknown>[]>(atoms: T): AwaitedAll<T>; } type Read<Value> = (get: EagerGetter) => Value; type Write<Args extends unknown[], Result> = (get: Getter, set: Setter, ...args: Args) => Result; type AsyncReadFunctionError = 'ERROR: The `read` function of eager atoms cannot be asynchronous, or return a Promise.'; /** * A drop-in replacement for vanilla atoms with custom async read functions, that * removes unnecessary suspensions. The read function is written as if it was * synchronous, which allows for: * - eager computation of the atom's value in case all of its dependencies are fulfilled * (which is not the case for vanilla async atoms). * - interrupting computation if a dependency is not yet fulfilled. * The `get` parameter provides methods like `all()` to await multiple atoms simultaneously * and `await()` for non-atom promises. * * @param read A synchronous function that computes the atom's value using the eager getter, which can await dependencies directly. * @param write An optional function to handle writes to the atom, receiving the standard getter, setter, and arguments. * @returns An atom that resolves to the computed value or a promise of the result if dependencies are pending. For writable atoms, includes write functionality. * * @example * ```ts * import { atom } from 'jotai'; * import { eagerAtom } from 'jotai-eager'; * * const petsAtom = atom(Promise.resolve(['cat', 'dog'])); * const filterAtom = atom('cat'); * const filteredPetsAtom = eagerAtom((get) => { * const pets = get(petsAtom); * const filter = get(filterAtom); * return pets.filter(name => name.includes(filter)); * }); * ``` */ declare function eagerAtom<Value, Args extends unknown[], Result>(...args: [Value] extends [PromiseLike<unknown>] ? [AsyncReadFunctionError] : [read: Read<Value>, write: Write<Args, Result>]): WritableAtom<Promise<Value> | Value, Args, Result>; declare function eagerAtom<Value>(...args: [Value] extends [PromiseLike<unknown>] ? [AsyncReadFunctionError] : [read: Read<Value>]): Atom<Promise<Value> | Value>; /** * Only useful if the eager atom's read function involves a try {} catch {}. Can be used to * detect whether a thrown value originates from `jotai-eager`, in which case should be rethrown. * * @param error The error thrown during eager atom computation to check. * @returns True if the error is a suspension trigger from jotai-eager, indicating computation should be paused; otherwise, false. * * @example * ```ts * import { eagerAtom, isEagerError } from 'jotai-eager'; * * const myAtom = eagerAtom((get) => { * try { * // some computation * } catch (e) { * if (isEagerError(e)) { * throw e; // Rethrow to let jotai-eager handle * } * // Handle other errors * } * }); * ``` */ declare function isEagerError(error: unknown): boolean; type Loadable<Value> = { state: 'loading'; } | { state: 'hasError'; error: unknown; } | { state: 'hasData'; data: Awaited<Value>; }; /** * Wraps an atom to provide a loadable state, representing its value as 'loading', 'hasError', or 'hasData'. * Shares a Promise cache between all jotai-eager APIs, further minimizing suspensions. * * @param anAtom The atom whose value should be wrapped in a loadable state. * @returns An atom that returns a Loadable object indicating the current state of the input atom's value. * * @example * ```ts * import { atom } from 'jotai'; * import { loadable } from 'jotai-eager'; * * const asyncAtom = atom(async () => 'data'); * const loadableAtom = loadable(asyncAtom); * // loadableAtom returns: { state: 'loading' } | { state: 'hasError', error: unknown } | { state: 'hasData', data: 'data' } * ``` */ declare function loadable<Value>(anAtom: Atom<Value>): Atom<Loadable<Value>>; /** * Executes `process` with `data` as input synchronously if `data` is known, meaning * it is not an unresolved promise of the value. * * @param data The data to process, now or later (soon). * @param process The processing function that takes the awaited data and returns a result. * @returns The result of processing the data synchronously if available, or a promise of the result if data is pending. * * @example * ```ts * import { atom } from 'jotai'; * import { soon } from 'jotai-eager'; * * const dataAtom = atom(Promise.resolve('hello')); * const resultAtom = atom((get) => { * const data = get(dataAtom); * return soon(data, (resolved) => resolved.toUpperCase()); * }); * ``` */ declare function soon<TInput, TOutput>(data: TInput, process: (knownData: Awaited<TInput>) => TOutput): TOutput | Promise<Awaited<TOutput>>; /** * Executes `process` with `data` as input synchronously if `data` is known, meaning * it is not an unresolved promise of the value. * * @param process The processing function that takes the awaited data and returns a result. * @returns A function that can be called with `data`, and returns the result (or promise of result) from running `process` on `data`. * * @example * ```ts * import { atom } from 'jotai'; * import { soon } from 'jotai-eager'; * * const processFn = soon((str: string) => str.toUpperCase()); * const result = processFn(Promise.resolve('hello')); // Promise<'HELLO'> or 'HELLO' * ``` */ declare function soon<TInput, TOutput>(process: (knownData: NoInfer<Awaited<TInput>>) => TOutput): (data: TInput) => TOutput | Promise<Awaited<TOutput>>; type PromiseOrValue<T> = Promise<T> | T; type SoonAll<T extends readonly unknown[]> = PromiseOrValue<{ [Index in keyof T]: Awaited<T[Index]>; }>; /** * Given array `values`, if all elements are known (are not unresolved promises), * returns an array of the same length with Awaited `values`. Otherwise, it returns a * promise to that array. * * @param values An array of values or promises to await collectively. * @returns An array of the awaited values if all are resolved synchronously, or a promise resolving to that array if any are pending. * * @example * ```ts * import { soonAll } from 'jotai-eager'; * * const values = [Promise.resolve(1), 2, Promise.resolve(3)]; * const result = soonAll(values); // [1, 2, 3] or Promise<[1, 2, 3]> * ``` */ declare function soonAll<T extends readonly unknown[] | []>(values: T): SoonAll<T>; declare function soonAll<T extends readonly unknown[]>(values: T): SoonAll<T>; interface WithPendingContext<Value> { get: Getter; prev: Awaited<Value> | undefined; pending: PromiseLike<Awaited<Value>>; } /** * Wraps an atom to handle pending states by returning a fallback value while the atom's value is unresolved, optionally using a custom fallback function. * Intended as an alternative to Jotai's `unwrap`, providing enhanced pending state handling. * * @param anAtom The atom to wrap, which may resolve asynchronously. * @returns An atom that returns the resolved value or undefined while pending. * * @example * ```ts * import { atom } from 'jotai'; * import { withPending } from 'jotai-eager'; * * const asyncAtom = atom(Promise.resolve('data')); * const wrappedAtom = withPending(asyncAtom); * // wrappedAtom returns 'data' when resolved, or undefined while pending * ``` */ declare function withPending<Value, Args extends unknown[], Result>(anAtom: WritableAtom<Value, Args, Result>): WritableAtom<Awaited<Value> | undefined, Args, Result>; /** * Wraps an atom to handle pending states by returning a fallback value while the atom's value is unresolved, optionally using a custom fallback function. * Intended as an alternative to Jotai's `unwrap`, providing enhanced pending state handling. * * @param anAtom The atom to wrap, which may resolve asynchronously. * @param fallback A function that receives context (getter, previous value, pending promise) and returns a value to use while pending. * @returns An atom that returns the resolved value or the fallback while pending. * * @example * ```ts * import { atom } from 'jotai'; * import { withPending } from 'jotai-eager'; * * const asyncAtom = atom(Promise.resolve('data')); * const wrappedAtom = withPending(asyncAtom, () => 'Loading...'); * // wrappedAtom returns 'data' when resolved, or 'Loading...' while pending * ``` */ declare function withPending<Value, Args extends unknown[], Result, PendingValue>(anAtom: WritableAtom<Value, Args, Result>, fallback: (ctx: WithPendingContext<Value>) => PendingValue): WritableAtom<Awaited<Value> | PendingValue, Args, Result>; /** * Wraps an atom to handle pending states by returning a fallback value while the atom's value is unresolved, optionally using a custom fallback function. * Intended as an alternative to Jotai's `unwrap`, providing enhanced pending state handling. * * @param anAtom The atom to wrap, which may resolve asynchronously. * @returns An atom that returns the resolved value or undefined while pending. * * @example * ```ts * import { atom } from 'jotai'; * import { withPending } from 'jotai-eager'; * * const asyncAtom = atom(Promise.resolve('data')); * const wrappedAtom = withPending(asyncAtom); * // wrappedAtom returns 'data' when resolved, or undefined while pending * ``` */ declare function withPending<Value>(anAtom: Atom<Value>): Atom<Awaited<Value> | undefined>; /** * Wraps an atom to handle pending states by returning a fallback value while the atom's value is unresolved, optionally using a custom fallback function. * Intended as an alternative to Jotai's `unwrap`, providing enhanced pending state handling. * * @param anAtom The atom to wrap, which may resolve asynchronously. * @param fallback A function that receives context (getter, previous value, pending promise) and returns a value to use while pending. * @returns An atom that returns the resolved value or the fallback while pending. * * @example * ```ts * import { atom } from 'jotai'; * import { withPending } from 'jotai-eager'; * * const asyncAtom = atom(Promise.resolve('data')); * const wrappedAtom = withPending(asyncAtom, () => 'Loading...'); * // wrappedAtom returns 'data' when resolved, or 'Loading...' while pending * ``` */ declare function withPending<Value, PendingValue>(anAtom: Atom<Value>, fallback: (ctx: WithPendingContext<Value>) => PendingValue): Atom<Awaited<Value> | PendingValue>; export { derive, eagerAtom, isEagerError, loadable, soon, soonAll, withPending };