Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
144 commits
Select commit Hold shift + click to select a range
723ed06
perf(fetchium): write an entity once per streamed event, not two or t…
abedell-phantom Sep 10, 2026
2082975
Merge branch 'main' into perf/remove-redundant-entity-saves
abedell-phantom Sep 10, 2026
180cd36
cleanup comment
abedell-phantom Sep 11, 2026
25b394a
Merge branch 'main' into perf/remove-redundant-entity-saves
abedell-phantom Sep 11, 2026
cfc2582
test(fetchium): pin zero writes for a streamed event that changes not…
abedell-phantom Sep 11, 2026
a404c28
fix(fetchium): correct the comment on the skipped child save
abedell-phantom Sep 11, 2026
545c5b1
test(fetchium): pin zero child writes when a live array gains an unch…
abedell-phantom Sep 11, 2026
d2218fe
docs(fetchium): note the zero-write outcome in the changeset
abedell-phantom Sep 11, 2026
b88c349
test(fetchium): size the refetch delay to observed CI jitter, not to 10s
abedell-phantom Sep 11, 2026
0b91282
fix(fetchium): keep write-skipping consistent with the store (pre-0.6.0)
abedell-phantom Sep 14, 2026
9f24e5d
fix(fetchium): don't write event-created entities that nothing routes
abedell-phantom Sep 14, 2026
adeebde
fix(fetchium): AsyncQueryStore readers must not offer onDelete
abedell-phantom Sep 14, 2026
1818694
perf(fetchium): snapshot entities without walking their proxies (#59)
abedell-phantom Sep 11, 2026
12c58d8
perf: don't notify or persist when applied data is unchanged (#60)
abedell-phantom Sep 11, 2026
e4419a1
Merge branch 'perf/reapply-59-60' into fix/store-consistent-write-ski…
abedell-phantom Sep 29, 2026
d42041d
chore(fetchium): rewrite this change's changesets for React developers
abedell-phantom Oct 6, 2026
90f0b12
chore(fetchium): rewrite this change's changesets for React developers
abedell-phantom Oct 6, 2026
fd3a663
perf(fetchium): quiet query reactivation and focus-aware polling
abedell-phantom Oct 3, 2026
7c59b32
fix(fetchium): credit the reactivation grace only for data a subscrip…
abedell-phantom Oct 4, 2026
9984ad9
perf(fetchium): hydrate synchronously from a sync store and start the…
abedell-phantom Oct 3, 2026
0fa1c0c
Merge branch 'fix/store-consistent-write-skipping' into perf/remove-r…
abedell-phantom Sep 29, 2026
32325c3
fix(fetchium): skip a staggered reactivation refetch made redundant d…
abedell-phantom Oct 4, 2026
e28c770
chore(fetchium): rewrite this change's changesets for React developers
abedell-phantom Oct 6, 2026
6bc090b
chore(fetchium): rewrite this change's changesets for React developers
abedell-phantom Oct 6, 2026
84c80aa
perf(fetchium): microtask refetches, prefetch/retain leases, useSuspe…
abedell-phantom Oct 3, 2026
1e0b42b
perf(fetchium): skip live value notify when a reducer returns the cur…
abedell-phantom Oct 3, 2026
8ed72a8
perf(fetchium): start prefetch/retain fetches synchronously
abedell-phantom Oct 3, 2026
b053d83
fix(fetchium): do not retry 4xx client errors; add shouldRetry
abedell-phantom Oct 3, 2026
6702797
fix(fetchium): notify a live value after every reducer run again
abedell-phantom Oct 4, 2026
b8528e4
fix(fetchium): suspend on the current fetch when a suspense hold's fe…
abedell-phantom Oct 4, 2026
bc0b555
fix(fetchium): give a remount a new attempt after an abandoned suspen…
abedell-phantom Oct 4, 2026
9003c44
fix(fetchium): resubscribe a never-loaded query before refetching it
abedell-phantom Oct 4, 2026
642baa9
fix(fetchium): bound lease lifetimes and clean them up on destroy
abedell-phantom Oct 4, 2026
86577fa
fix(fetchium): retry every failed attempt by default; keep shouldRetr…
abedell-phantom Oct 5, 2026
a7fcbd3
chore(fetchium): add a changeset for the reactivation and polling opt…
abedell-phantom Oct 6, 2026
887d5f8
chore(fetchium): rewrite this change's changesets for React developers
abedell-phantom Oct 6, 2026
7f784a8
fix(fetchium): optional store hooks for acknowledged, merged and chec…
abedell-phantom Oct 6, 2026
c9b4dfe
fix(fetchium): close the snapshot, hydration and event-persistence re…
abedell-phantom Oct 6, 2026
cb4a211
perf(fetchium): skip the per-snapshot key-cache lookup and an event's…
abedell-phantom Oct 6, 2026
f67c516
perf(fetchium): take the extra store work back out of streamed events
abedell-phantom Oct 6, 2026
e68c875
perf(fetchium): stop re-running a shared entity's consumers, copying …
abedell-phantom Oct 6, 2026
e583e75
chore(fetchium): major changeset listing the behavior changes an upgr…
abedell-phantom Oct 6, 2026
4897103
fix(fetchium): keep merging an event-built entity whose events carrie…
abedell-phantom Oct 6, 2026
bbc2772
fix(fetchium): don't turn a same-task unmount or an offline switch in…
abedell-phantom Oct 6, 2026
94a754e
chore(fetchium): shorten this change's changesets for app developers
abedell-phantom Oct 6, 2026
8bf023e
fix(fetchium): fetch and store the current params when a Signal param…
abedell-phantom Oct 6, 2026
8be1a63
chore(fetchium): rewrite this change's changesets for React developers
abedell-phantom Oct 6, 2026
ca682cf
fix(fetchium): keep a lease's release aborting the fetch it started
abedell-phantom Oct 6, 2026
c724114
fix(fetchium): keep nested values reactive when they are read on thei…
abedell-phantom Oct 6, 2026
ce9ef92
test(fetchium): benchmark reads through nested value wrappers
abedell-phantom Oct 6, 2026
0523f54
fix(fetchium): keep the query context's push hook out of Object.keys
abedell-phantom Oct 6, 2026
a383a46
fix(fetchium): tolerate app context values under the new config names
abedell-phantom Oct 6, 2026
0b4f62b
fix(fetchium): stop a slow suspense retry render from refetching in a…
abedell-phantom Oct 6, 2026
15d0aa1
chore(fetchium): changeset for the config-context and suspense-hold f…
abedell-phantom Oct 6, 2026
c152d9b
fix(fetchium): close the timing gaps around kept, deferred and late f…
abedell-phantom Oct 6, 2026
68e8f9b
perf(fetchium): hand out a new wrapper for a changed nested value ins…
abedell-phantom Oct 6, 2026
cf3eb39
chore(fetchium): changeset for the round-one timing fixes
abedell-phantom Oct 6, 2026
05c5b45
fix(fetchium): settle every awaiter when destroy(), an offline restar…
abedell-phantom Oct 6, 2026
47be6a8
fix(fetchium): keep each params key's cached data under its own record
abedell-phantom Oct 6, 2026
410e006
fix(fetchium): reject an aborted topic fetch with an AbortError when …
abedell-phantom Oct 6, 2026
e9a3d08
fix(fetchium): evict a query's previous root only while the entity ma…
abedell-phantom Oct 6, 2026
d967fe8
fix(fetchium): keep a topic query pending, not rejected, when it is u…
abedell-phantom Oct 6, 2026
9666263
perf(fetchium): merge a streamed update into a stored record without …
abedell-phantom Oct 6, 2026
5660a02
fix(fetchium): keep any query pending, not rejected, when it is unmou…
abedell-phantom Oct 6, 2026
9ffa42b
chore(fetchium): shorten this change's changesets for app developers
abedell-phantom Oct 6, 2026
8eba14e
chore(fetchium): rewrite this change's changesets for React developers
abedell-phantom Oct 6, 2026
7a9f94a
fix(fetchium): re-read formatted fields in the snapshot fast path
abedell-phantom Oct 6, 2026
1660e65
Merge branch 'perf/reapply-59-60' into fix/store-consistent-write-ski…
abedell-phantom Oct 6, 2026
5c78478
Merge branch 'perf/remove-redundant-entity-saves' into perf/sync-hydr…
abedell-phantom Oct 6, 2026
69f0742
Merge branch 'fix/store-consistent-write-skipping' into perf/remove-r…
abedell-phantom Oct 6, 2026
15dabd0
fix(fetchium): fetch an invalidated query whatever its staleTime
abedell-phantom Oct 6, 2026
222b7d0
Merge branch 'perf/sync-hydrate-first-fetch' into perf/quiet-reactiva…
abedell-phantom Oct 6, 2026
d5b1a9c
fix(fetchium): close three gaps in the reactivation grace and stagger
abedell-phantom Oct 6, 2026
cc302e1
Merge branch 'perf/fast-delivery' into fix/perf-stack-regressions
abedell-phantom Oct 6, 2026
f8cb9ec
Merge branch 'perf/quiet-reactivation' into perf/fast-delivery
abedell-phantom Oct 6, 2026
e4c983d
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 6, 2026
281e428
test(fetchium): cover the async write order of a created child and it…
abedell-phantom Oct 6, 2026
b4df2bc
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 6, 2026
bb1aae2
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
ada6ef4
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
fcfadea
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
19d9d68
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
c224b20
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
2dc709e
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
7fca8d6
Merge branch 'perf/quiet-reactivation' into perf/fast-delivery
abedell-phantom Oct 7, 2026
140df97
Merge branch 'perf/sync-hydrate-first-fetch' into perf/quiet-reactiva…
abedell-phantom Oct 7, 2026
1572ffc
Merge branch 'fix/store-consistent-write-skipping' into perf/remove-r…
abedell-phantom Oct 7, 2026
a1c11ef
Merge branch 'perf/remove-redundant-entity-saves' into perf/sync-hydr…
abedell-phantom Oct 7, 2026
febd864
Merge branch 'perf/reapply-59-60' into fix/store-consistent-write-ski…
abedell-phantom Oct 7, 2026
6e7ef1b
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
3213594
chore(fetchium): tighten comments
abedell-phantom Oct 7, 2026
ebab339
Merge branch 'perf/fast-delivery' into fix/perf-stack-regressions
abedell-phantom Oct 7, 2026
8bc5f20
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 7, 2026
f412cb9
chore(fetchium): drop PR numbers from a test header
abedell-phantom Oct 7, 2026
0531c6a
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 7, 2026
aa417f3
fix(fetchium): release a retain() lease when starting one of its quer…
abedell-phantom Oct 7, 2026
2aba512
Merge branch 'perf/fast-delivery' into fix/perf-stack-regressions
abedell-phantom Oct 7, 2026
99982a5
fix(fetchium): keep store bookkeeping right after a failed write or a…
abedell-phantom Oct 7, 2026
fbe4670
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 7, 2026
ae6aa5c
fix(fetchium): drop a response that arrives after destroy()
abedell-phantom Oct 7, 2026
c3e458b
chore(fetchium): cut and shorten comments
abedell-phantom Oct 7, 2026
c33c1bb
chore(fetchium): cut and shorten comments
abedell-phantom Oct 7, 2026
b40ebdc
chore(fetchium): cut and shorten comments
abedell-phantom Oct 7, 2026
a103d63
chore(fetchium): cut and shorten comments
abedell-phantom Oct 7, 2026
feeacdc
chore(fetchium): cut and shorten comments
abedell-phantom Oct 7, 2026
fe48f0d
chore(fetchium): cut and shorten comments
abedell-phantom Oct 7, 2026
d7da9e3
Merge branch 'perf/reapply-59-60' into fix/store-consistent-write-ski…
abedell-phantom Oct 7, 2026
1ad691b
Merge branch 'fix/store-consistent-write-skipping' into perf/remove-r…
abedell-phantom Oct 7, 2026
7dc0afe
Merge branch 'perf/remove-redundant-entity-saves' into perf/sync-hydr…
abedell-phantom Oct 7, 2026
aec1847
Merge branch 'perf/sync-hydrate-first-fetch' into perf/quiet-reactiva…
abedell-phantom Oct 7, 2026
10958e9
chore(fetchium): cut and shorten comments
abedell-phantom Oct 7, 2026
15a2722
Merge branch 'perf/quiet-reactivation' into perf/fast-delivery
abedell-phantom Oct 7, 2026
9a8fea3
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 7, 2026
d468448
Merge branch 'perf/fast-delivery' into fix/perf-stack-regressions
abedell-phantom Oct 7, 2026
8d0adf6
chore(fetchium): note Infinity in the reactivationGraceMs doc
abedell-phantom Oct 7, 2026
8cccf20
fix(fetchium): keep a throwing getConfig() from stalling Signalium's …
abedell-phantom Oct 7, 2026
cb300b4
fix(fetchium): recover a query once its getConfig() stops throwing
abedell-phantom Oct 7, 2026
1d0f6d0
fix(fetchium): stop all store writes and requests after destroy()
abedell-phantom Oct 7, 2026
96a5200
fix(fetchium): keep event writes and store listeners safe when the st…
abedell-phantom Oct 7, 2026
1ae384d
Merge branch 'perf/fast-delivery' into fix/perf-stack-regressions
abedell-phantom Oct 7, 2026
1aa7958
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 7, 2026
dd60079
fix(fetchium): keep a newer abort's flag when a replaced fetch lands …
abedell-phantom Oct 7, 2026
efd435a
fix(fetchium): cancel a queued debounce when starting a fetch immedia…
abedell-phantom Oct 7, 2026
aed1986
Merge branch 'perf/sync-hydrate-first-fetch' into perf/quiet-reactiva…
abedell-phantom Oct 7, 2026
181236c
Merge branch 'perf/quiet-reactivation' into perf/fast-delivery
abedell-phantom Oct 7, 2026
27ea7d8
fix(fetchium): cancel any queued debounced run when starting a fetch …
abedell-phantom Oct 7, 2026
208a412
Merge branch 'perf/fast-delivery' into fix/perf-stack-regressions
abedell-phantom Oct 7, 2026
389386f
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 7, 2026
a0f6208
fix(fetchium): pass a mutation response's status to shouldRetry
abedell-phantom Oct 7, 2026
04a4609
fix(fetchium): refetch changed params and pending invalidations on re…
abedell-phantom Oct 7, 2026
86cd229
Merge fix for review comments into perf/quiet-reactivation
abedell-phantom Oct 7, 2026
29899c7
Merge branch 'perf/quiet-reactivation' into perf/fast-delivery
abedell-phantom Oct 7, 2026
5db8976
Merge fix for review comment into perf/fast-delivery
abedell-phantom Oct 7, 2026
1bf0674
Merge branch 'perf/fast-delivery' into fix/perf-stack-regressions
abedell-phantom Oct 7, 2026
550f149
Merge branch 'fix/perf-stack-regressions' into fix/stack-timing-and-r…
abedell-phantom Oct 7, 2026
c1fef15
perf(fetchium): warm V8's enum cache for an entity shape at registration
abedell-phantom Oct 7, 2026
3ecb2ff
docs(fetchium): make the perf stack's docs and changesets concise
abedell-phantom Oct 7, 2026
036e2bb
docs(fetchium): separate hooks from component(), and qualify abort an…
abedell-phantom Oct 8, 2026
d8e7cfe
Merge branch 'main' into docs/concise-perf-stack-74
abedell-phantom Oct 8, 2026
9fc8f10
Merge branch 'main' into docs/concise-perf-stack-74
abedell-phantom Oct 8, 2026
551a713
docs(fetchium): make the 1.0.0 changelog entry concise
abedell-phantom Oct 8, 2026
97989d2
docs(fetchium): restore three qualifications the rewrite dropped
abedell-phantom Oct 8, 2026
2b2d83f
docs(fetchium): trim source JSDoc on perf-stack config and store hooks
abedell-phantom Oct 8, 2026
b0c6056
docs(fetchium): shorten the React section of the agent plugin doc
abedell-phantom Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions docs/src/app/api/fetchium-react/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ description: API reference for the fetchium React integration.

React hooks for using Fetchium queries in React components. Built on top of Signalium's `useReactive` hook.

Use these hooks in ordinary React function components. Inside a Signalium `component()`, which is already reactive, call `fetchQuery` directly instead:

```tsx
import { component } from 'signalium/react';
import { fetchQuery } from 'fetchium';

const UserList = component(() => {
const query = fetchQuery(GetUsers);
if (!query.isReady) return <div>Loading...</div>;
return <div>{query.value.total} users</div>;
});
```

```ts
import { useQuery, useSuspenseQuery } from 'fetchium/react';
```
Expand Down Expand Up @@ -60,12 +73,11 @@ The resolved `QueryResult<T>` includes pagination helpers:
#### Requirements

- A `QueryClient` must be provided via `QueryClientContext` using Signalium's `ContextProvider`.
- The component must be wrapped in a Signalium `component()` or use `useReactive` for the reactive system to function.
- Call it from an ordinary React function component, not inside a Signalium `component()`.

#### Example

```tsx
import { component } from 'signalium/react';
import { useQuery } from 'fetchium/react';

class GetUsers extends RESTQuery {
Expand All @@ -77,7 +89,7 @@ class GetUsers extends RESTQuery {
};
}

const UserList = component(() => {
function UserList() {
const query = useQuery(GetUsers);

if (query.isPending) {
Expand All @@ -100,17 +112,18 @@ const UserList = component(() => {
</ul>
</div>
);
});
}
```

#### With parameters

```tsx
const UserProfile = component(({ userId }: { userId: string }) => {
function UserProfile({ userId }: { userId: string }) {
const query = useQuery(GetUser, { id: userId });
if (!query.isReady) return null;

return <div>{query.value.name}</div>;
});
}
```

#### With reactive parameters
Expand All @@ -120,20 +133,21 @@ import { signal } from 'signalium';

const searchTerm = signal('');

const SearchResults = component(() => {
function SearchResults() {
const query = useQuery(SearchUsers, { q: searchTerm });
if (!query.isReady) return null;

// Component re-renders when searchTerm changes and the query refetches
// Re-renders when searchTerm changes and the query refetches
return <div>{query.value.results.length} results</div>;
});
}
```

#### Notes

- `useQuery` is a thin wrapper around Signalium v3's `useReactive`, which is deep-by-default. It returns a **structurally-shared snapshot** of the query result, so memoized children that receive subtrees as props keep stable references when the underlying data is unchanged.
- Fetchium registers a custom snapshot for entity proxies so the snapshot walks into entities (instead of returning them by reference), giving you correct re-rendering on entity field changes.
- The snapshot is a plain-object copy of the query result to prevent accidental mutation of the entity cache. Treat it as read-only: in development builds the snapshot and every nested object and array in it are frozen, so an in-place `sort()`, a `push()` or an assignment in render throws a `TypeError` at that line. Production snapshots are not frozen, and a mutation there is carried forward until the entity changes. Sort a copy (`[...items].sort()`) or use `draft()` from `fetchium` if you need a mutable copy for mutations.
- `useQuery` never suspends. Reading `.value` while pending returns `undefined`. Use [`useSuspenseQuery`](#usesuspensequery) to suspend on a cold miss.
- The snapshot is a read-only copy of the query result. Development builds freeze it, so mutating it throws a `TypeError`. Sort a copy (`[...items].sort()`) or use `draft()` from `fetchium` when you need a mutable copy.
- `useQuery` never suspends: `.value` is `undefined` until the first value arrives. Use [`useSuspenseQuery`](#usesuspensequery) to suspend.

---

Expand All @@ -146,13 +160,11 @@ function useSuspenseQuery<T extends Query>(
): ReadyReactivePromise<QueryResult<T>>;
```

`useQuery` for components under a React `<Suspense>` boundary. It suspends only on a **cold miss**: the query has never produced a value, neither in memory nor, with a synchronous store, in the persisted cache. Anything else renders at once, including a stale value whose refetch is in flight. Refetches never suspend. Because the hook only returns once a value exists, `value` is always defined.
Like `useQuery`, but suspends while the query has no value yet. Refetches don't suspend, so `value` is always defined. A failed first fetch is thrown to the nearest error boundary, and resetting the boundary retries it.
Comment thread
abedell-phantom marked this conversation as resolved.

A cold fetch that fails is thrown to the nearest error boundary. Resetting the boundary (remounting the component) tries the fetch again.
Changing params to ones with no value yet suspends again. Wrap the change in `startTransition` to keep showing the previous result.

Changing params to ones with no value yet is a cold miss, so the component suspends. Wrap the change in `startTransition` to keep showing the previous result.

Call it from a plain function component. Like `useQuery`, it cannot run inside a Signalium `component()`, which is a reactive context.
Like `useQuery`, call it from an ordinary React component, not inside a Signalium `component()`.

```tsx
import { Suspense } from 'react';
Expand All @@ -167,5 +179,3 @@ function UserName({ id }: { id: number }) {
<UserName id={42} />
</Suspense>;
```

While suspended, the client keeps the query active, since React discards a suspended render without subscribing to anything. The component's commit takes over.
Loading
Loading