We have recently adopted nanostores and nanostores/query on a fresh Astro SSG project, and even though I really enjoy it, I had to spend quite a lot of time reading source code to grasp all possible atom states and related call return values, as they were not clear to me. Working with an LLM that was aware of the source code, I assembled JSDoc documentation to document "what to expect" and would like to propose adding that to the library as well. Obviously, this should be carefully re-checked and the wording might be awkward, but the main idea should be clear. Thanks for creating this masterpiece.
Fetcher Store:
/**
* ========================================================================
* Fetcher Store State Documentation
* ========================================================================
*
* A `fetcherStore` is a MapStore that tracks the state of an asynchronous
* data-fetching operation. Its state transitions are managed by Nano Stores'
- * lifecycle events (`onStart`, `onStop`) and its interaction with a global cache.
*
* @typedef {object} FetcherStoreValue
* @property {boolean} loading - `true` if a fetch operation is currently in-flight.
* @property {any} [data] - The successfully resolved data from the last fetch.
* @property {any} [error] - The error object from the last failed fetch.
* @property {Promise<any>} [promise] - The in-flight promise of the current fetch operation.
*/
/**
* --------------------------------
* Initial State & "Off" State
* --------------------------------
* This is the state of the store before its first subscription, or after its
* last subscriber has been removed. In this state, the store is completely
* passive and holds no data locally.
*
* @type {FetcherStoreValue}
* @example { loading: false }
*/
/**
* --------------------------------
* Active States
* --------------------------------
*/
/**
* ### State 1: Loading (No Cache)
* Occurs on the first mount for a given key when no cache exists, or when the
* cache has been invalidated (`.invalidate()`).
*
* @type {FetcherStoreValue}
* @example { loading: true, promise: Promise<...> }
*/
/**
* ### State 2: Loading (Stale-While-Revalidate)
* Occurs on mount when a valid but "stale" (past `dedupeTime`) cache entry
* exists. The UI can show the old `data` immediately while a new fetch
* runs in the background.
*
* @type {FetcherStoreValue}
* @example { data: [stale_data], loading: true, promise: Promise<...> }
*/
/**
* ### State 3: Success (Deduplicated)
* Occurs on mount when a valid and "fresh" (within `dedupeTime`) cache entry
* exists. The data is served directly from the cache and no network request
* is made.
*
* @type {FetcherStoreValue}
* @example { data: [cached_data], loading: false }
*/
/**
* ### State 4: Success (Freshly Fetched)
* The state after a successful network request.
*
* @type {FetcherStoreValue}
* @example { data: [fresh_data], loading: false }
*/
/**
* --------------------------------
* Edge Case States
* --------------------------------
*/
/**
* ### Edge Case A: Revalidation Failure
* Occurs when a revalidation fetch fails while the store already holds valid
* (but now stale) data. This allows the UI to show the last known good data
* alongside an error indicator, preventing a blank screen.
*
* @type {FetcherStoreValue}
* @example { data: [stale_data], error: [the_error], loading: false }
*/
/**
* ### Edge Case B: Conditional Fetching Disabled
* Occurs when one of the dynamic keys of a `fetcherStore` becomes falsy
* (e.g., `null`, `undefined`, `false`). The store reverts to its initial state
* even if it remains subscribed in the UI, effectively pausing the fetch.
*
* @type {FetcherStoreValue}
* @example { loading: false }
*/
Mutator Store:
/**
* ========================================================================
* Mutator Store State Documentation
* ========================================================================
*
* A `mutatorStore` is a MapStore that tracks the state of an active mutation
* (e.g., a POST or PUT request). Its state is simpler and is only changed by
* direct calls to its `.mutate()` method.
*
* @typedef {object} MutatorStoreValue
* @property {boolean} loading - `true` if a mutation is currently in-flight.
* @property {Function} mutate - The function to trigger the mutation.
* @property {any} [data] - The successfully resolved data from the last mutation.
* @property {any} [error] - The error object from the last failed mutation.
*/
/**
* --------------------------------
* Initial State & "Off" State
* --------------------------------
* This is the state of the store before `.mutate()` has been called, or after
* its last subscriber has been removed. Any `data` or `error` from a previous
* run is cleared.
*
* @type {MutatorStoreValue}
* @example { mutate: [Function], loading: false }
*/
/**
* --------------------------------
* Active States
* --------------------------------
*/
/**
* ### State 1: Loading
* The state immediately after `.mutate()` is called and before the async
* operation completes. `data` and `error` from any previous run are cleared.
*
* @type {MutatorStoreValue}
* @example { mutate: [Function], loading: true }
*/
/**
* ### State 2: Success
* The state after the mutation's async operation resolves successfully.
* The `data` field contains the return value of the operation.
*
* @type {MutatorStoreValue}
* @example { mutate: [Function], loading: false, data: [the_result] }
*/
/**
* ### State 3: Error
* The state after the mutation's async operation rejects.
* The `error` field contains the thrown error.
*
* @type {MutatorStoreValue}
* @example { mutate: [Function], loading: false, error: [the_error] }
*/
/**
* --------------------------------
* Edge Case States
* --------------------------------
*/
/**
* ### Edge Case A: Result Discarded After Unsubscribe
* If the last subscriber to a `mutatorStore` unmounts while a mutation is
* in-flight, the final result (`data` or `error`) of that mutation will be
* discarded. The store will remain in its clean "off" state. This prevents
* race conditions where a result from a stale operation could affect a newly
* mounted component.
*
* @type {MutatorStoreValue}
* @example { mutate: [Function], loading: false }
*/
/**
* ### Edge Case B: Promise Resolution on `.mutate()`
* The `Promise` returned by the `.mutate()` method itself has specific behaviors.
*
* - **On Throttle:** If a mutation is already in-flight, subsequent calls (by
* default) are throttled. The returned `Promise` will resolve immediately
* with `undefined`.
*
* - **On Error:** If the mutation function throws an error, the returned
* `Promise` will still *resolve* (not reject) with a value of `undefined`.
* The error is caught internally and set on the store's `.error` property.
* This prevents unhandled promise rejections.
*
* - **Important:** To retrieve error information from a `.mutate()` call when
* the promise resolves to `undefined`, the store must have at least one active
* listener. Without a listener, the error state will not be set and you won't
* be able to access it via `.get().error`. This is typically not an issue in
* components using `useStore`, but when calling `.mutate()` outside of components,
* you need to manually add a listener first:
*
* @example
* const unlisten = $mutatorStore.listen(() => {});
* try {
* const result = await $mutatorStore.mutate();
*
* // Check for throttle or error
* if (result === undefined || $mutatorStore.get().error) {
* throw $mutatorStore.get().error ?? new Error("Throttled");
* }
*
* return result;
* } finally {
* unlisten();
* }
*/
We have recently adopted nanostores and nanostores/query on a fresh
Astro SSGproject, and even though I really enjoy it, I had to spend quite a lot of time reading source code to grasp all possible atom states and related call return values, as they were not clear to me. Working with an LLM that was aware of the source code, I assembled JSDoc documentation to document "what to expect" and would like to propose adding that to the library as well. Obviously, this should be carefully re-checked and the wording might be awkward, but the main idea should be clear. Thanks for creating this masterpiece.Fetcher Store:
Mutator Store: