- {messages.map((msg) => (
+ {query.value.messages.map((msg) => (
{msg.author.name}: {msg.text}
diff --git a/docs/src/app/data/caching/page.md b/docs/src/app/data/caching/page.md
index c3582a0..feacbd1 100644
--- a/docs/src/app/data/caching/page.md
+++ b/docs/src/app/data/caching/page.md
@@ -133,7 +133,7 @@ class GetUser extends RESTQuery {
}
```
-`maxCount` limits how many distinct parameter combinations are stored for this query class. When the limit is exceeded, the oldest entries are evicted. The default is `50`. Changing `maxCount` between releases resizes the queue already persisted for the class the first time the store touches that class after starting: growing keeps every entry, shrinking evicts the oldest entries that no longer fit.
+`maxCount` limits how many distinct parameter combinations are stored for this query class. When the limit is exceeded, the oldest entries are evicted. The default is `50`.
---
diff --git a/docs/src/app/guides/error-handling/page.md b/docs/src/app/guides/error-handling/page.md
index c44362b..8eeb44e 100644
--- a/docs/src/app/guides/error-handling/page.md
+++ b/docs/src/app/guides/error-handling/page.md
@@ -147,7 +147,7 @@ class GetUser extends RESTQuery {
The default retry delay uses exponential backoff --- each successive attempt waits longer than the last (roughly 1s, 2s, 4s, ...). This avoids hammering a struggling server with rapid retries.
-Every failed attempt is retried, whatever its status, including `4xx` responses. When you know an error is permanent, opt out with `shouldRetry`, either per query in `retry` or for every query on the `QueryClient`. It receives the error, the attempt index and the attempt's HTTP status when known: the response the adapter received (a REST error response whose body doesn't match `result`), or `status`, `statusCode` or `response.status` on the thrown error. A query's `shouldRetry` overrides the client's.
+Every failed attempt is retried, including `4xx` responses. Use `shouldRetry` to stop retrying errors you know are permanent. It gets the error, the attempt index and the HTTP status when known. Set it per query in `retry`, or for every query on the `QueryClient`.
```ts
class GetUser extends RESTQuery {
@@ -161,7 +161,7 @@ class GetUser extends RESTQuery {
}
```
-`getErrorStatus(error)` reads the status from an error, if you need it elsewhere.
+`getErrorStatus(error)` reads the status from an error.
To disable retries entirely:
@@ -311,7 +311,7 @@ if (result.isRejected) {
### Opting into throw behavior
-If you _want_ error boundaries to catch query failures --- for example, when using React Suspense or when you prefer a centralized error UI --- throw the error yourself. Reading `.value` does not throw: on a rejected query it holds the last successful value, or `undefined` if there never was one.
+To let an error boundary handle query failures, throw the error yourself. Reading `.value` doesn't throw.
```tsx
import { ErrorBoundary } from 'react-error-boundary';
@@ -319,7 +319,6 @@ import { ErrorBoundary } from 'react-error-boundary';
function UserProfile({ userId }: { userId: number }) {
const result = useQuery(GetUser, { id: userId });
- // Hand a failure with nothing to show to the ErrorBoundary above
if (result.isRejected && !result.isReady) throw result.error;
if (!result.isReady) return
;
@@ -339,7 +338,7 @@ function App() {
}
```
-`useSuspenseQuery` from `fetchium/react` does this for you: it suspends while a query with no value loads, and throws a failed cold fetch to the error boundary.
+`useSuspenseQuery` does this for you when the query has no value yet.
This is a conscious opt-in. The default explicit-checking pattern (`isRejected` + `error`) is recommended for most use cases because it gives you the most flexibility. Error boundaries are best reserved for catching truly unexpected failures that shouldn't be handled inline.
diff --git a/docs/src/app/setup/project-setup/page.md b/docs/src/app/setup/project-setup/page.md
index b7ca2b8..7e2761b 100644
--- a/docs/src/app/setup/project-setup/page.md
+++ b/docs/src/app/setup/project-setup/page.md
@@ -289,8 +289,8 @@ Fetchium uses a compile-time constant `IS_DEV` to enable additional runtime chec
- **Parse validation warnings** --- detailed logs when response data doesn't match your type definitions
- **Entity write protection** --- throws when you accidentally try to mutate an entity property directly (entities are read-only)
-- **Frozen snapshots** --- the snapshots `useQuery` and `useReactive` hand out are frozen, so mutating one (an in-place `sort()`, an assignment in render) throws a `TypeError` at the offending line instead of silently corrupting what the next render shows
-- **Stale-snapshot detection** --- a snapshot taken at an unchanged entity version re-reads only the fields whose values live elsewhere; development builds re-read the rest too and, if one changed, serve the re-read value, log the discrepancy through the client's `log.error`, and raise it once per typename and field as an uncaught error
+- **Frozen snapshots** --- mutating a snapshot from `useQuery` throws a `TypeError`
+- **Stale-snapshot detection** --- logs an error when a snapshot misses an entity change
- **Reference validation** --- catches common mistakes like using conditional logic in class field definitions
In production builds, all `IS_DEV` code paths are tree-shaken, so there is zero runtime cost.
diff --git a/packages/fetchium/CHANGELOG.md b/packages/fetchium/CHANGELOG.md
index 4206483..5a5fdf5 100644
--- a/packages/fetchium/CHANGELOG.md
+++ b/packages/fetchium/CHANGELOG.md
@@ -4,79 +4,54 @@
### Major Changes
-- 46e237f: Breaking changes. Most apps need no code changes, but tests that depend on loading frames, render counts or timers may.
- - **Results are frozen in development.** Mutating query data throws a `TypeError`; copy it first (`[...data.items].sort()`).
- - **Cached data shows on the first render** with `SyncQueryStore`. Update tests that wait for a loading state or count renders.
- - **Fetches start sooner.** The first fetch and refetches without `debounce` start on a microtask, not after `setTimeout(0)`; update tests that advance timers to see them.
- - **Identical data doesn't re-render.** A refetch, poll or streamed update with the same data skips the render; read `isFetching` instead of counting renders.
- - **Nested values change identity only when they change.** `t.object`, `t.record` and live-collection values are new objects after a change, so memoized children re-render; drop workarounds that forced it.
- - **`t.record` and `t.result` fields apply updates** instead of keeping their old value.
- - **Changing a Signal param mid-fetch aborts that fetch** and fetches the new params; the old response is dropped.
- - **`QueryClient.destroy()` aborts fetches in flight.** Code that awaits a query during teardown gets an `AbortError`; catch it.
- - **Awaiting a query can wait for a remount.** If the last component using a query unmounts before its first data arrives, `await fetchQuery(...)` waits for the next mount, `gcTime` collection or `destroy()` instead of rejecting with an `AbortError`.
- - **New reserved `QueryClientConfig` names:** `shouldRetry`, `reactivationGraceMs`, `reactivationStaggerMs`, `activity` and `pollResumeJitterMs` are read as options; rename custom context values that use them.
- - **`retry: {}` and `retry: { retryDelay }` now retry** like `retry: true`; use `retry: false` for no retries.
- - **`AsyncQueryStore` honors `cache.maxCount`.** Check the value if you set one.
- - **Custom `QueryStore`s** get new optional methods (`mergeEntity`, `onDelete`, `onPersisted`, `isSettled`, `hasQueuedDeletes`, `hasEntity`); without them nothing changes.
+- 46e237f: Most apps need no changes. Tests that depend on loading frames, render counts or timers may.
+ - Query results are frozen in development. Copy data before mutating it.
+ - With `SyncQueryStore`, cached data shows on the first render.
+ - The first fetch and refetches without `debounce` start on a microtask, not after `setTimeout(0)`.
+ - Identical data no longer re-renders. Check `isFetching` instead of counting renders. Dates, typed arrays and class instances always count as changed.
+ - Nested `t.object`, `t.record` and live-collection values are new objects when they change.
+ - `t.record` and `t.result` fields apply updates.
+ - Changing a Signal param mid-fetch aborts that fetch.
+ - After `QueryClient.destroy()`, queries reject with an `AbortError` and send no requests.
+ - If the last reader unmounts before a query's first data, `await fetchQuery(...)` waits for the next mount, collection or `destroy()`. A topic query mounted and unmounted in the same task still rejects.
+ - `shouldRetry`, `reactivationGraceMs`, `reactivationStaggerMs`, `activity` and `pollResumeJitterMs` are reserved `QueryClientConfig` names.
+ - `retry: {}` and `retry: { retryDelay }` now retry like `retry: true`.
+ - `AsyncQueryStore` honors `cache.maxCount`.
### Minor Changes
-- 82d67fc: New `queryClient.prefetch(Query, params, { ttl })` and `queryClient.retain(fn, { ttl })` start queries before any component reads them. A component that mounts while one is held reuses its request and renders the data on its first render if it has arrived. Both return a `release` function; `prefetch` lasts 10 seconds by default and `retain` until released.
-
- New `useSuspenseQuery` in `fetchium/react`. It suspends only while the query has no data yet, and sends a failed first fetch to the error boundary.
- - A refetch without `debounce` starts on a microtask instead of after `setTimeout(0)`.
- - Reading `.value` on a pending or failed query doesn't suspend or throw. The docs said it did.
-
-- 46e004f: New `QueryClient` options that make coming back to a screen, or to the app, quieter. All are off by default.
- - `reactivationGraceMs` (also per query): a query that mounts again with data younger than this doesn't refetch. Data a live subscription kept current counts as fresh.
- - `reactivationStaggerMs`: refetches from queries that mount together are spread across this window instead of all starting at once.
- - `activity: { isActive(), subscribe(listener) }`: `poll()` stops while the app is inactive and resumes when it's active again. On React Native, wrap `AppState`. `pollResumeJitterMs` spreads out polls that were overdue on resume.
- - Pass the topic to `TopicQueryAdapter.sendMutationEvent(event, topic)` so that topic's query counts the push as fresh data.
-
-- 82d67fc: New optional `shouldRetry(error, attempt, status)` on `QueryClientConfig` and on a query's or mutation's `retry` config. Return `false` to stop retrying an error you know is permanent, such as a 404.
-
- `status` is the HTTP status when it is known, and `getErrorStatus` is exported. Without the hook, retries work as before. `retry.retries` is now optional.
-
-- 5c6824d: A refetch, poll or streamed update that returns the same data no longer re-renders your components. `isPending` and `isFetching` still change as before.
- - Values that can't be compared reliably (a `Date`, a typed array, a class instance) always count as changed.
- - `t.record(...)` fields now apply updates. Previously a record field kept its old value.
-
-### Patch Changes
-
-- a23ad4b: Fixed spurious errors and wrong data when a query mounts, unmounts or changes params quickly.
- - Unmounting and remounting a query, in the same task or mid-fetch (switching a chart timeframe and back), no longer shows an `AbortError`. The query shows its cached data, or stays pending, while it refetches. One exception: a topic query with no data that mounts and unmounts in the same task shows the `AbortError` on its next mount until its data arrives (before, it stayed pending forever).
- - Changing a Signal param mid-fetch fetches the new params instead of showing the old params' response. A query whose result is not an entity now caches each params value separately; a cold start could show another params value's data.
- - `QueryClient.destroy()` aborts every fetch in flight, and code awaiting one gets an `AbortError`.
-
-- 33458ce: `AsyncQueryStore` now honors a query's `cache.maxCount`, as `SyncQueryStore` does. It used to always keep the default of 50 keys.
-- a23ad4b: `useSuspenseQuery` no longer refetches in a loop when a slow retry render takes a while to reach the component. The error goes to the error boundary, and resetting the boundary retries.
-
- Custom `QueryClientConfig` keys still reach queries as `this.context`, except the reserved option names `shouldRetry`, `reactivationGraceMs`, `reactivationStaggerMs`, `activity` and `pollResumeJitterMs`. An invalid value for one of these options is ignored instead of breaking queries.
-
-- 5c6824d: Query results rebuild faster after an update: entities whose data didn't change are not read again.
-
- List results also keep each row's object identity when the list is inserted into or re-sorted, so memoized rows don't re-render. Development builds still re-read everything to check the result, so the speedup shows in production builds only.
-
-- a23ad4b: Fixed memoized children (`component()` or `React.memo`) showing a stale value when given a nested value such as `token.price` as a prop. A nested `t.object`, `t.record` or live-collection value is now a new object when it changes, and keeps its identity when it doesn't.
-- 5c636ff: Streamed updates write each changed entity to the store once instead of two or three times, and an update that changes nothing writes nothing.
-
- A `create` event for an entity that no live collection shows is no longer written to the store, where it was never cleaned up.
-
-- 46e237f: Fixed stale data in components when a second query loads entities that are already in memory, and streamed updates that were lost on restart.
- - Development builds freeze query results, so mutating one (such as calling `sort()` on a list in render) throws a `TypeError`. Copy the data first, and treat production results as read-only too.
- - In development, the stale-data check logs an error instead of throwing and blocking other components' updates.
- - Custom stores can add the new optional `QueryStore.mergeEntity`, so an entity built from streamed updates doesn't overwrite stored fields those updates didn't include. Without it, writes work as before.
-
-- 33458ce: Fixed components not updating when an entity changes inside a union that has an array or record member, such as `t.union(t.array(t.entity(A)), t.object({ ... }))`.
-- 33458ce: Fixed cached queries failing to load after a restart ("the query cache may be corrupted or invalid") when a query had more than `cache.maxCount` cached params and refetched unchanged data.
- - Custom stores: `QueryStore` has a new optional `onDelete(listener)`. Call the listener with every id the store deletes on its own. A store without it keeps writing every update, as before.
- - A `t.array(t.entity(X))` field whose typename two entity classes share now shows an item as soon as it gains the fields `X` requires.
-
-- 0600009: With `SyncQueryStore`, a cached query renders its data on the first render instead of showing a loading frame first.
- - The first fetch starts on a microtask instead of after `setTimeout(0)`. Tests that wait for a loading state or count renders may need updating.
- - A cached entry that can't be loaded is treated as a cache miss and fetched. Previously a fresh-looking entry could leave the query pending without fetching.
-
-- a23ad4b: Faster streamed updates with `SyncQueryStore` for entities that aren't in memory, which is common when two entity classes share a typename.
+- 82d67fc: New `queryClient.prefetch(Query, params, { ttl })` and `queryClient.retain(fn, { ttl })` start queries before a component reads them. A component that mounts meanwhile reuses the request.
+ - New `useSuspenseQuery` in `fetchium/react`. It suspends only while the query has no value.
+ - Refetches without `debounce` start on a microtask instead of after `setTimeout(0)`.
+- 46e004f: New `QueryClient` options, all off by default:
+ - `reactivationGraceMs` (also per query): skip the refetch when a query remounts with data younger than this.
+ - `reactivationStaggerMs`: spread the refetches of queries that remount together across this window.
+ - `activity` and `pollResumeJitterMs`: pause `poll()` while the app is inactive and spread overdue polls on resume.
+ - `TopicQueryAdapter.sendMutationEvent(event, topic)` counts the event as fresh data for that topic's query.
+- 82d67fc: New `shouldRetry(error, attempt, status)` option on `QueryClientConfig` and `retry` configs. Return `false` to stop retrying, for example on a 404. `getErrorStatus` is exported and `retry.retries` is now optional.
+- 5c6824d: A refetch, poll or streamed update with identical data no longer re-renders components. Dates, typed arrays and class instances always count as changed. `t.record` fields now apply updates.
+
+### Patch Changes
+
+- a23ad4b: Fixed errors and wrong data when a query mounts, unmounts or changes params quickly.
+ - Remounting a query after an aborted fetch shows its cached data, or stays pending, instead of an `AbortError`. A topic query with no data that mounts and unmounts in the same task is the exception: it shows the `AbortError` until its data arrives, and its first awaiter rejects.
+ - Changing a Signal param mid-fetch fetches the new params. Each params value of a non-entity result is cached separately.
+ - `QueryClient.destroy()` aborts fetches in flight. Queries read afterwards reject with an `AbortError` and send no requests.
+- 33458ce: `AsyncQueryStore` now honors a query's `cache.maxCount` instead of always keeping 50 entries.
+- a23ad4b: Fixed `useSuspenseQuery` refetching in a loop when a retry render is slow.
+ - A query whose `getConfig()` throws now rejects with that error instead of breaking other queries.
+ - `shouldRetry`, `reactivationGraceMs`, `reactivationStaggerMs`, `activity` and `pollResumeJitterMs` are reserved `QueryClientConfig` names. Other keys still reach queries as `this.context`.
+- 5c6824d: Query results rebuild faster after an update in production builds, and list rows keep their identity when the list is re-sorted or inserted into.
+- a23ad4b: Fixed memoized children not updating when passed a nested value such as `token.price`. Nested `t.object`, `t.record` and live-collection values are new objects when they change.
+- 5c636ff: Streamed updates write each changed entity once. With a store that implements `onDelete`, unchanged updates write nothing.
+- 46e237f: Fixed stale data when a query loads entities already in memory, and streamed updates lost on restart.
+ - Development builds freeze query results. Copy data before mutating it.
+ - Custom stores can implement the new optional `QueryStore.mergeEntity`.
+- 33458ce: Fixed components not updating when an entity changes inside a union with an array or record member.
+- 33458ce: Fixed cached queries failing to load after a restart when a query had more than `cache.maxCount` cached entries.
+ - Custom stores can implement the new optional `QueryStore.onDelete(listener)`.
+- 0600009: With `SyncQueryStore`, cached data shows on the first render, and the first fetch starts on a microtask. A cache entry that fails to load is refetched.
+- a23ad4b: Faster streamed updates with `SyncQueryStore` for entities not in memory.
## 0.5.2
diff --git a/packages/fetchium/plugin/agents/fetchium.md b/packages/fetchium/plugin/agents/fetchium.md
index 2d8189a..004f7ea 100644
--- a/packages/fetchium/plugin/agents/fetchium.md
+++ b/packages/fetchium/plugin/agents/fetchium.md
@@ -137,9 +137,9 @@ function UserProfile() {
}
```
-`useQuery` returns `ReactivePromise
` with: `value`, `isReady`, `isPending`, `isResolved`, `isRejected`, `error`. It never suspends; `value` is `undefined` until the first value arrives. `useSuspenseQuery` (same arguments) suspends on a cold miss only, never for a refetch, and throws a failed cold fetch to the error boundary.
+`useQuery` returns `ReactivePromise` with: `value`, `isReady`, `isPending`, `isResolved`, `isRejected`, `error`. It never suspends; `value` is `undefined` until data arrives. `useSuspenseQuery` takes the same arguments and suspends only while there's no data. A failed first fetch goes to the error boundary.
-To start a destination's query when the user taps, call `queryClient.prefetch(QueryClass, params, { ttl })` before navigating; the screen's `useQuery` then joins the running query. `queryClient.retain(() => [fetchQuery(A), fetchQuery(B)], { ttl })` keeps a set of queries active until its returned `release()` is called.
+Call `queryClient.prefetch(QueryClass, params, { ttl })` when the user commits to a navigation, and `queryClient.retain(fn, { ttl })` to keep a screen's queries active. Both return `release()`. See the API reference.
## React + Signalium Usage
diff --git a/packages/fetchium/src/EntityInstance.ts b/packages/fetchium/src/EntityInstance.ts
index d1a82ae..53156d8 100644
--- a/packages/fetchium/src/EntityInstance.ts
+++ b/packages/fetchium/src/EntityInstance.ts
@@ -497,15 +497,7 @@ function reportDrift(source: EntitySnapshotSource, key: string, what: string): v
snapshotDriftHandler(error, source.instance._queryClient);
}
-/**
- * Checks the fast path's assumptions: `data` only changes through a `notify()`
- * that bumps `version`, and every field outside `keys.dynamic` is a pure
- * function of `data`. Returns the static fields that drifted, if any.
- * Dynamic fields are skipped, since re-reading them is not identity-stable.
- *
- * The re-read cancels the fast path's savings, so the speedup only shows in
- * production builds.
- */
+/** Dev-only: returns the static fields whose cached value no longer matches a re-read of `data`. */
function verifyStaticFields(
source: EntitySnapshotSource,
keys: EntityKeys,
diff --git a/packages/fetchium/src/QueryClient.ts b/packages/fetchium/src/QueryClient.ts
index 4735402..6033245 100644
--- a/packages/fetchium/src/QueryClient.ts
+++ b/packages/fetchium/src/QueryClient.ts
@@ -53,40 +53,21 @@ export interface QueryClientConfig {
};
evictionMultiplier?: number;
/**
- * Milliseconds. A query that reactivates (a watcher returns, or a paused
- * scope resumes) with data younger than this is not refetched, even if stale.
- * Subscription pushes count as fresh data. Queries can override it with
- * `reactivationGraceMs`. Reconnects, `refetch()`, invalidation, `markStale()`
- * and a failed last fetch still refetch. Default: 0. `Infinity` never
- * refetches on reactivation.
+ * Ms a reactivated query's data counts as fresh, skipping the refetch. Reconnects,
+ * `refetch()`, invalidation and failed fetches still refetch. Queries can override it.
+ * Default 0.
*/
reactivationGraceMs?: number;
- /**
- * Decides whether a failed query or mutation attempt is retried, for example
- * to stop on a 4xx. A query's or mutation's own `retry.shouldRetry` overrides
- * it. Default: every failed attempt is retried.
- */
+ /** Decides whether a failed attempt is retried. A query's or mutation's `retry.shouldRetry` wins. */
shouldRetry?: ShouldRetry;
/**
- * Milliseconds. Reactivation refetches that start in the same task (for
- * example every query on a screen that just resumed) are spread evenly
- * across this window, in activation order, instead of all starting at once.
- * Queries of an adapter that `coalescesRequests` are not spread. Values that
- * are not finite and positive count as 0. Default: 0.
+ * Ms window over which refetches from queries that reactivate together are spread, instead of
+ * all starting at once. Default 0.
*/
reactivationStaggerMs?: number;
- /**
- * Foreground/background source. When set, `poll()` stops its timers while
- * the app is inactive and resumes them when it becomes active again. Values
- * without `isActive` and `subscribe` are ignored. Default: undefined.
- */
+ /** Foreground/background source. `poll()` stops while it reports inactive. */
activity?: ActivitySource;
- /**
- * Milliseconds. A `poll()` tick that is overdue on resume, or whose timer
- * fires over a second late, runs at a random point within this window
- * instead of immediately. Values that are not finite and positive count as
- * 0. Default: 0.
- */
+ /** Ms window over which `poll()` ticks that are overdue on resume are randomized. Default 0. */
pollResumeJitterMs?: number;
}
diff --git a/packages/fetchium/src/query-types.ts b/packages/fetchium/src/query-types.ts
index e3eec41..3b0f578 100644
--- a/packages/fetchium/src/query-types.ts
+++ b/packages/fetchium/src/query-types.ts
@@ -100,11 +100,10 @@ export interface QueryStore {
saveEntity(entityKey: number, value: unknown, refIds?: Set): void;
/**
- * Writes the fields streamed events supplied, merged over the stored record
- * if one exists (references derived from the merged value). Without it the
- * client calls `saveEntity`, dropping fields the events did not carry.
- * With `ifStored`, nothing is written when no record is stored, and a store
- * with `onDelete` reports the key as dropped.
+ * Merges streamed fields over the stored record, deriving `refIds` from the
+ * merged value. Without it, `saveEntity` overwrites the record. With
+ * `ifStored`, writes nothing if no record exists and, if the store has
+ * `onDelete`, reports the key as dropped.
*/
mergeEntity?(entityKey: number, fields: unknown, refIds?: Set, ifStored?: boolean): void;
@@ -115,36 +114,29 @@ export interface QueryStore {
purgeStaleQueries?(): MaybePromise;
/**
- * Called with the key of every record the store drops on its own (eviction,
- * cascade, purge). May return an unsubscribe, called from `destroy()`.
+ * Reports every record the store drops on its own (eviction, cascade, purge).
* Without it, entities are written on every apply.
*/
onDelete?(listener: (key: number) => void): void | (() => void);
/**
- * Async stores: called with each entity key once its write is applied. The
- * client treats an entity as persisted only after this acknowledgement.
+ * Async stores: reports each entity key once its write is applied. Without it, an entity
+ * counts as persisted when `saveEntity` returns.
*/
onPersisted?(listener: (key: number) => void): void | (() => void);
/**
- * Async stores: whether every queued operation has been processed. While
- * `false` the client does not skip unchanged entity writes. Absent means
- * always settled.
+ * Async stores: whether every queued operation has been processed. Absent means settled.
+ * While `false`, unchanged entity writes aren't skipped.
*/
isSettled?(): boolean;
- /**
- * Async stores: a narrower `isSettled` that ignores the client's own entity
- * writes, reporting only queued operations that could delete a record.
- * Takes precedence over `isSettled` when present.
- */
+ /** Like `isSettled`, but ignores the client's own entity writes. Wins over `isSettled` if both are set. */
hasQueuedDeletes?(): boolean;
/**
- * Whether the store holds a record for this key, or `undefined` if it can't
- * tell yet. An event for an entity not in memory is written as an `ifStored`
- * merge unless this returns `false`. Without `mergeEntity`, only when `true`.
+ * Whether the store holds a record for this key, or `undefined` if unknown. When unknown,
+ * streamed events are merged only if the store has `mergeEntity`.
*/
hasEntity?(key: number): boolean | undefined;
}
diff --git a/packages/fetchium/src/retry.ts b/packages/fetchium/src/retry.ts
index 280aa8a..85c63b5 100644
--- a/packages/fetchium/src/retry.ts
+++ b/packages/fetchium/src/retry.ts
@@ -1,10 +1,8 @@
import type { ResolvedRetryConfig } from './query.js';
/**
- * Decides whether a failed attempt is retried. Consulted only while retries
- * remain. `attempt` starts at 0. `status` comes from the error (`status`,
- * `statusCode`, `response.status`) or the adapter's `ctx.response`, and is
- * undefined for network errors.
+ * Decides whether a failed attempt is retried. Called only while retries remain. `attempt`
+ * starts at 0; `status` is undefined for network errors.
*/
export type ShouldRetry = (error: unknown, attempt: number, status: number | undefined) => boolean;
diff --git a/packages/fetchium/src/stores/async.ts b/packages/fetchium/src/stores/async.ts
index 2c6518c..8f393cc 100644
--- a/packages/fetchium/src/stores/async.ts
+++ b/packages/fetchium/src/stores/async.ts
@@ -175,7 +175,6 @@ export class AsyncQueryStore implements QueryStore {
console.error('Ignoring a message the store does not understand:', msg);
return;
}
- // Enqueue the message for serial processing
this.enqueueMessage(msg);
}