diff --git a/.agents/skills/data-client-manager/SKILL.md b/.agents/skills/data-client-manager/SKILL.md
index b5a4193d09a1..2be2a36335e4 100644
--- a/.agents/skills/data-client-manager/SKILL.md
+++ b/.agents/skills/data-client-manager/SKILL.md
@@ -31,6 +31,8 @@ Minimal working examples for each use case live in [references/managers.md](refe
| Custom transport subscriptions | "Reading and Consuming Actions" in [Manager.md](references/Manager.md) | consume `SUBSCRIBE`/`UNSUBSCRIBE` without calling `next` |
| Auth: logout on 401, reset store on deauth | [LogoutManager.md](references/LogoutManager.md) | `handleLogout(controller)` + `controller.resetEntireStore()` |
+Do not start channel work before `SUBSCRIBE`; do not treat `SUBSCRIBE` as SSR hydration (see `references/ssr.md` in skill "data-client-react").
+
## References
For detailed API documentation, see the [references](references/) directory:
@@ -158,7 +160,7 @@ export default class CustomSubsManager implements Manager {
import { DataProvider, getDefaultManagers } from '@data-client/react';
import ReactDOM from 'react-dom';
-const managers = [...getDefaultManagers(), new MyManager()];
+const managers = () => [...getDefaultManagers(), new MyManager()];
ReactDOM.createRoot(document.body).render(
@@ -166,3 +168,5 @@ ReactDOM.createRoot(document.body).render(
,
);
```
+
+Pass `managers` as a function: it runs once per provider (and once per request with `@data-client/react/nextjs`). Arrays are still accepted by the browser `DataProvider` but are transitional and will be removed; migrate existing `managers={[...]}` to `managers={() => [...]}`.
diff --git a/.agents/skills/data-client-react/SKILL.md b/.agents/skills/data-client-react/SKILL.md
index cd689281727a..8983b4af26e5 100644
--- a/.agents/skills/data-client-react/SKILL.md
+++ b/.agents/skills/data-client-react/SKILL.md
@@ -1,6 +1,6 @@
---
name: data-client-react
-description: Use @data-client/react hooks for data fetching, mutations, and rendering - useSuspense, useFetch, useQuery, useCache, useLive, useDLE, useSubscription, useController, DataProvider, AsyncBoundary, useLoading, useDebounce. Use when reading/rendering remote data, triggering mutations, doing optimistic updates, real-time subscriptions, or wiring Suspense/error boundaries in React.
+description: Use @data-client/react hooks for data fetching, mutations, and rendering - useSuspense, useFetch, useQuery, useCache, useLive, useDLE, useSubscription, useController, DataProvider, AsyncBoundary, useLoading, useDebounce. Use when reading/rendering remote data, triggering mutations, doing optimistic updates, real-time subscriptions, wiring Suspense/error boundaries, or SSR/Next.js hydration in React.
license: Apache 2.0
---
## Rendering
@@ -30,6 +30,8 @@ const comments = use(commentsPromise);
For API definitions (like TodoResource), apply the skill "data-client-rest".
+Under SSR the hooks are identical; only the provider entry and how the store is seeded differ. Read [ssr](references/ssr.md) when wiring server rendering or debugging hydration.
+
## Mutations
```ts
@@ -151,6 +153,7 @@ For detailed API documentation, see the [references](references/) directory:
- [useLoading](references/useLoading.md);[_useLoading.md](references/_useLoading.md) - Track async mutation state
- [useDebounce](references/useDebounce.md) - Debounce values
- [DataProvider](references/DataProvider.md) - Root provider
+- [ssr](references/ssr.md) - SSR entries and hydration (Next.js, `renderToPipeableStream`)
- [data-dependency](references/data-dependency.md) - Rendering guide
- [mutations](references/mutations.md);[_VoteDemo.md](references/_VoteDemo.md) - Mutations guide
- [Actions](references/Actions.md) - Store action types (FETCH, SET, etc.)
diff --git a/.agents/skills/data-client-react/references/ssr.md b/.agents/skills/data-client-react/references/ssr.md
new file mode 100644
index 000000000000..730ac70216dc
--- /dev/null
+++ b/.agents/skills/data-client-react/references/ssr.md
@@ -0,0 +1,122 @@
+# SSR and streamed hydration
+
+Only what differs from a browser-only app. Components, hooks, Managers, and mutations are unchanged: `useSuspense` / `useLive` stay in the component that renders the data. The difference is how the store is seeded.
+
+## Contents
+
+- [Which entry](#which-entry)
+- [Next.js App Router](#nextjs-app-router)
+- [renderToPipeableStream (Express, Anansi)](#rendertopipeablestream-express-anansi)
+- [Next.js Pages Router](#nextjs-pages-router)
+- [Not shipped](#not-shipped)
+
+## Which entry
+
+| Host | Import | How the store is seeded |
+| --- | --- | --- |
+| Browser SPA | `@data-client/react` | Public `DataProvider`; hooks read `StateContext` |
+| Next.js App Router | `@data-client/react/nextjs` | Adapter-owned streamed snapshot: inert baseline in the shell, then a `StateDelta` per committed server revision |
+| `renderToPipeableStream` (Express, Anansi) | `@data-client/react/ssr` | One-shot snapshot passed as `initialState`. Unstable `__INTERNAL__.StreamingDataProvider` if the host already owns a snapshot store |
+| Next.js Pages Router | `@data-client/ssr/nextjs` | One-shot snapshot |
+
+Do not dispatch a root `HYDRATE` action. Stream publication is adapter-internal.
+
+## Next.js App Router
+
+```tsx title="app/layout.tsx"
+import { DataProvider } from '@data-client/react/nextjs';
+import { AsyncBoundary } from '@data-client/react';
+
+export default function RootLayout({ children }) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+```
+
+Differences from the browser `DataProvider`:
+
+- No `initialState` prop. The store is seeded from the stream; do not pass or replace state per delta.
+- `managers` must be a **function**. The server builds a store per request, so an array is rejected. Server-side `cleanup()` is not run per request; do not hold resources in server managers. A function cannot cross the RSC boundary, so custom managers go in a `'use client'` wrapper:
+
+```tsx title="app/Provider.tsx"
+'use client';
+import { getDefaultManagers } from '@data-client/react';
+import { DataProvider } from '@data-client/react/nextjs';
+
+const managers = () => [...getDefaultManagers(), new MyManager()];
+
+export default function Provider({ children }: { children: React.ReactNode }) {
+ return {children};
+}
+```
+
+- `nonce` prop: state is streamed in inline `` ended the tag
+early, corrupting the page and allowing markup injection. `<`, `>`, `&` and the U+2028/U+2029 line
+terminators are now escaped; the payload stays valid JSON.
diff --git a/.changeset/streamed-ssr-hydration.md b/.changeset/streamed-ssr-hydration.md
new file mode 100644
index 000000000000..4a9208905029
--- /dev/null
+++ b/.changeset/streamed-ssr-hydration.md
@@ -0,0 +1,24 @@
+---
+'@data-client/core': minor
+'@data-client/react': minor
+'@data-client/vue': patch
+---
+
+Stream SSR store state incrementally through adapter-owned composition.
+
+The shell carries an inert baseline. Each later committed server revision emits a
+StateDelta. Next.js App Router folds that piece into an adapter-owned snapshot
+and publishes it from the receiver layout effect. Several components in one
+flush share one delta; a nested child can become readable before its parent; a
+later delta may overlap an entity already in the store (three-way merge).
+SUBSCRIBE after commit starts live updates and does not gate REST.
+
+Public DataProvider keeps the master store and StateContext reads. Streaming
+reducers and snapshot stores stay in ./nextjs and an unstable
+`@data-client/react/ssr` `__INTERNAL__` composition seam. HTML insertion order
+is not an RSC clock; a Client Component may start before its delta script runs,
+and a miss then fetches like any client render. Per-key waiters, revision
+visibility, schema-aware streamed merge, and reset-aware overlay are not in this
+release.
+
+New exports: StateDelta, StateBaseline, NextDataProviderProps.
diff --git a/docs/core/api/Controller.md b/docs/core/api/Controller.md
index 76d2b7ecea0b..a04e56dbd7e1 100644
--- a/docs/core/api/Controller.md
+++ b/docs/core/api/Controller.md
@@ -316,7 +316,7 @@ import { unAuth } from '../authentication';
const testKey = (key: string) => key.startsWith(`GET ${myDomain}`);
-const managers = [
+const managers = () => [
new LogoutManager({
handleLogout(controller) {
// call custom unAuth function we defined
diff --git a/docs/core/api/DataProvider.md b/docs/core/api/DataProvider.md
index 7988ce2220d3..fd582bae90fe 100644
--- a/docs/core/api/DataProvider.md
+++ b/docs/core/api/DataProvider.md
@@ -21,7 +21,7 @@ in the React tree.
```typescript
interface ProviderProps {
children: ReactNode;
- managers?: Manager[];
+ managers?: Manager[] | (() => Manager[]);
initialState?: State;
Controller?: typeof Controller;
devButton?:
@@ -71,12 +71,36 @@ export interface State {
Instead of starting with an empty cache, you can provide your own initial state. This can
be useful for testing, or rehydrating the cache state when using server side rendering.
-### managers?: Manager[] {#managers}
+Hooks read that state through `StateContext`. The Next.js App Router provider from
+`@data-client/react/nextjs` fills this in from the stream; see the
+[SSR guide](../guides/ssr.md#nextjs) for its props.
-List of [Manager](./Manager.md)s use. This is the main extensibility point of the provider.
+### managers?: Manager[] | (() => Manager[]) {#managers}
+
+List of [Manager](./Manager.md)s to use, or a function that creates them. This is the main
+extensibility point of the provider.
[getDefaultManagers()](./getDefaultManagers.md) can be used to extend the default managers.
+Both forms are resolved once when the provider mounts. Prefer the function: the same definition
+works with the [Next.js provider](../guides/ssr.md#managers), which only accepts a function, and
+each `DataProvider` on a page gets its own instances (managers keep a reference to the store they
+were attached to).
+
+:::warning Arrays are transitional
+
+Passing `Manager[]` keeps working for now but will be removed in a future release. Migrate to the
+function form when you can:
+
+```tsx
+// Before:
+const managers = [...getDefaultManagers(), new MyManager()];
+// After:
+const managers = () => [...getDefaultManagers(), new MyManager()];
+```
+
+:::
+
Default Production:
```typescript
diff --git a/docs/core/api/DevToolsManager.md b/docs/core/api/DevToolsManager.md
index 921615825e0c..8112990e81f1 100644
--- a/docs/core/api/DevToolsManager.md
+++ b/docs/core/api/DevToolsManager.md
@@ -37,10 +37,11 @@ import {
} from '@data-client/react';
import ReactDOM from 'react-dom';
-const managers = getDefaultManagers({
- // highlight-next-line
- devToolsManager: { trace: true },
-});
+const managers = () =>
+ getDefaultManagers({
+ // highlight-next-line
+ devToolsManager: { trace: true },
+ });
ReactDOM.createRoot(document.body).render(
@@ -65,7 +66,7 @@ import {
} from '@data-client/react';
import ReactDOM from 'react-dom';
-const managers =
+const managers = () =>
process.env.NODE_ENV !== 'production'
? [
// highlight-start
diff --git a/docs/core/api/LogoutManager.md b/docs/core/api/LogoutManager.md
index 7e96a1b06b84..a2f3dc0d578f 100644
--- a/docs/core/api/LogoutManager.md
+++ b/docs/core/api/LogoutManager.md
@@ -40,7 +40,7 @@ import {
import ReactDOM from 'react-dom';
// highlight-next-line
-const managers = [new LogoutManager(), ...getDefaultManagers()];
+const managers = () => [new LogoutManager(), ...getDefaultManagers()];
ReactDOM.createRoot(document.body).render(
@@ -62,7 +62,7 @@ import {
import { AppRegistry } from 'react-native';
// highlight-next-line
-const managers = [new LogoutManager(), ...getDefaultManagers()];
+const managers = () => [new LogoutManager(), ...getDefaultManagers()];
const Root = () => (
@@ -82,7 +82,7 @@ import { LogoutManager, getDefaultManagers } from '@data-client/react';
import { DataProvider } from '@data-client/react/nextjs';
// highlight-next-line
-const managers = [new LogoutManager(), ...getDefaultManagers()];
+const managers = () => [new LogoutManager(), ...getDefaultManagers()];
export default function Provider({
children,
@@ -124,7 +124,7 @@ import {
import { useColorScheme } from '@/hooks/useColorScheme';
// highlight-next-line
-const managers = [new LogoutManager(), ...getDefaultManagers()];
+const managers = () => [new LogoutManager(), ...getDefaultManagers()];
export default function Provider({
children,
@@ -175,7 +175,7 @@ export default function RootLayout() {
```ts
import { unAuth } from '../authentication';
-const managers = [
+const managers = () => [
new LogoutManager({
handleLogout(controller) {
// call custom unAuth function we defined
@@ -198,7 +198,7 @@ import { unAuth } from '../authentication';
// highlight-next-line
const testKey = (key: string) => key.startsWith(`GET ${myDomain}`);
-const managers = [
+const managers = () => [
new LogoutManager({
handleLogout(controller) {
// call custom unAuth function we defined
diff --git a/docs/core/api/Manager.md b/docs/core/api/Manager.md
index 57fa120c4878..f0f771a0eb96 100644
--- a/docs/core/api/Manager.md
+++ b/docs/core/api/Manager.md
@@ -65,9 +65,18 @@ Provides any cleanup of dangling resources after manager is no longer in use.
## Adding managers to Reactive Data Client {#adding}
-Use the [managers](../api/DataProvider.md#managers) prop of [DataProvider](../api/DataProvider.md). Be
-sure to hoist to _module level_ or wrap in a _useMemo()_ to ensure they are not recreated. Managers
-have internal state, so it is important to not constantly recreate them.
+Use the [managers](../api/DataProvider.md#managers) prop of [DataProvider](../api/DataProvider.md)
+with a function that creates them. The provider calls it once when it mounts, so each store gets
+its own instances and the same definition works for the [Next.js provider](../guides/ssr.md#managers).
+
+:::note Migrating from arrays
+
+`managers` also still accepts an array of instances. This form is transitional and will be removed
+in a future release; change `managers={[...]}` to `managers={() => [...]}` when you can. Managers
+have internal state, so an array must be hoisted to _module level_ or wrapped in _useMemo()_ to
+avoid recreating them.
+
+:::
[...getDefaultManagers(), new MyManager()];
ReactDOM.createRoot(document.body).render(
@@ -102,7 +111,7 @@ ReactDOM.createRoot(document.body).render(
import { DataProvider, getDefaultManagers } from '@data-client/react';
import { AppRegistry } from 'react-native';
-const managers = [...getDefaultManagers(), new MyManager()];
+const managers = () => [...getDefaultManagers(), new MyManager()];
const Root = () => (
@@ -116,13 +125,16 @@ AppRegistry.registerComponent('MyApp', () => Root);
+The server renders every request with its own store, so the Next.js provider takes a function
+instead of instances: it runs once per request on the server and once in the browser.
+
```tsx title="app/Provider.tsx"
'use client';
import { getDefaultManagers } from '@data-client/react';
import { DataProvider } from '@data-client/react/nextjs';
// highlight-next-line
-const managers = [...getDefaultManagers(), new MyManager()];
+const managers = () => [...getDefaultManagers(), new MyManager()];
export default function Provider({
children,
@@ -160,7 +172,7 @@ import {
import { useColorScheme } from '@/hooks/useColorScheme';
// highlight-next-line
-const managers = [...getDefaultManagers(), new MyManager()];
+const managers = () => [...getDefaultManagers(), new MyManager()];
export default function Provider({
children,
diff --git a/docs/core/api/PollingSubscription.md b/docs/core/api/PollingSubscription.md
index f33adb0edf32..7790497b894f 100644
--- a/docs/core/api/PollingSubscription.md
+++ b/docs/core/api/PollingSubscription.md
@@ -25,7 +25,7 @@ import {
} from '@data-client/react';
import ReactDOM from 'react-dom';
-const managers = [
+const managers = () => [
new NetworkManager(),
new SubscriptionManager(PollingSubscription)
]
diff --git a/docs/core/api/getDefaultManagers.md b/docs/core/api/getDefaultManagers.md
index 28f3fdb665d0..324cc351479f 100644
--- a/docs/core/api/getDefaultManagers.md
+++ b/docs/core/api/getDefaultManagers.md
@@ -27,10 +27,11 @@ import {
import ReactDOM from 'react-dom';
// highlight-start
-const managers = getDefaultManagers({
- // set fallback expiry time to an hour
- networkManager: { dataExpiryLength: 1000 * 60 * 60 },
-});
+const managers = () =>
+ getDefaultManagers({
+ // set fallback expiry time to an hour
+ networkManager: { dataExpiryLength: 1000 * 60 * 60 },
+ });
// highlight-end
ReactDOM.createRoot(document.body).render(
@@ -86,7 +87,8 @@ A class that implements `SubscriptionConstructable` like [PollingSubscription](.
For example, we can enable the [trace](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#trace) option to help track down where actions are dispatched from. This has a large performance impact, so it is normally disabled.
```ts
-const managers = getDefaultManagers({
+const managers = () =>
+ getDefaultManagers({
// highlight-next-line
devToolsManager: { trace: true },
});
@@ -99,7 +101,8 @@ Sending manager instances allows us to customize managers using inheritance.
```ts
import { IdlingNetworkManager } from '@data-client/react';
-const managers = getDefaultManagers({
+const managers = () =>
+ getDefaultManagers({
networkManager: new IdlingNetworkManager(),
});
```
@@ -112,7 +115,8 @@ until animations are complete. This works in web using [requestIdleCallback](htt
Using `null` will remove managers completely. [NetworkManager](./NetworkManager.md) cannot be removed this way.
```ts
-const managers = getDefaultManagers({
+const managers = () =>
+ getDefaultManagers({
devToolsManager: null,
subscriptionManager: null,
});
diff --git a/docs/core/concepts/managers.md b/docs/core/concepts/managers.md
index d2f0049d9308..4489764c9004 100644
--- a/docs/core/concepts/managers.md
+++ b/docs/core/concepts/managers.md
@@ -258,6 +258,7 @@ export default class PersistManager implements Manager {
```tsx
import { get } from 'idb-keyval';
+const managers = () => [...getDefaultManagers(), new PersistManager()];
const initialState = await get('data-client');
createRoot(document.body).render(
diff --git a/docs/core/diagrams/_streamed_hydration.mdx b/docs/core/diagrams/_streamed_hydration.mdx
new file mode 100644
index 000000000000..5eae41e70b0f
--- /dev/null
+++ b/docs/core/diagrams/_streamed_hydration.mdx
@@ -0,0 +1,142 @@
+Thick-border nodes on the overview are black boxes. Each zoom opens **one** box.
+
+```mermaid
+flowchart LR
+ Baseline["Baseline
shell paints now"]
+ subgraph pieces["Pieces arrive"]
+ direction TB
+ FirstFlush["First flush"]
+ NestedIsland["Nested island"]
+ LaterOverlap["Later overlap"]
+ end
+ Hydrate["Island hydrates"]
+ Subscribe["Subscribe for live updates"]
+ Insertion["RSC vs renderToPipeableStream"]
+
+ Baseline --> pieces
+ Insertion -.-> pieces
+ pieces --> Hydrate
+ Hydrate --> Subscribe
+
+ style FirstFlush stroke-width:3px
+ style NestedIsland stroke-width:3px
+ style LaterOverlap stroke-width:3px
+ style Hydrate stroke-width:3px
+ style Insertion stroke-width:3px
+```
+
+| Piece | Server revision | Endpoints in the piece | Entities | Who hydrates from it |
+| --- | --- | --- | --- | --- |
+| **Baseline** | empty request store | — | — | nobody; shell only |
+| **First flush** | one flush, two islands | `getTickers`, `getSymbolInfo(BTC)` | `Ticker:BTC`, `Ticker:ETH`, `Symbol:BTC` | Watchlist + SymbolHeader |
+| **Nested island** | nested child first | `getTrades(BTC)` | `Trade:…` | Tape, before parent Book exists |
+| **Later overlap** | disjoint parent later | `getOrderBook(BTC)` | `Book:BTC`, plus `Ticker:BTC` again (constructed nested ticker) | Book; live three-way merge vs tick |
+
+### Island hydrates
+
+Opens **Island hydrates**. Shared path for every piece.
+
+```mermaid
+flowchart TB
+ Server["Request store"]
+ Baseline["Baseline"]
+ Delta["StateDelta"]
+ Snapshot["Hydration snapshot"]
+ LiveStore["Live store"]
+ Island["Island"]
+ SubMgr["SubscriptionManager"]
+
+ Server --> Baseline
+ Server --> Delta
+ Baseline -->|"seed once"| Snapshot
+ Delta -->|"fold this piece"| Snapshot
+ Snapshot -->|"HYDRATE"| LiveStore
+ Island -->|"useSuspense"| Snapshot
+ Snapshot -->|"cache hit"| Island
+ Island -->|"after commit SUBSCRIBE"| SubMgr
+ SubMgr -->|"ticks"| LiveStore
+```
+
+### First flush
+
+Opens **First flush**. One server revision, two islands, one `StateDelta`.
+
+```mermaid
+flowchart LR
+ Revision["One server revision"]
+ Delta["One StateDelta"]
+ Watchlist["Watchlist
getTickers"]
+ SymbolHeader["SymbolHeader
getSymbolInfo"]
+ Market["Market still pending"]
+
+ Revision -->|"SET_RESPONSE ×2"| Delta
+ Delta --> Watchlist
+ Delta --> SymbolHeader
+ Watchlist -.-> Market
+ SymbolHeader -.-> Market
+```
+
+### Nested island
+
+Opens **Nested island**. Tree order is not stream order.
+
+```mermaid
+flowchart TB
+ Market["Market"]
+ Book["Book — still pending"]
+ Tape["Tape / Trades"]
+ Delta["Trades StateDelta"]
+
+ Market --- Book
+ Market --- Tape
+ Delta --> Tape
+```
+
+### Later overlap
+
+Opens **Later overlap**. A later disjoint piece writes an entity the first flush already put in the store.
+
+```mermaid
+flowchart LR
+ FirstFlush["First flush wrote Ticker:BTC"]
+ LiveTick["Live tick may already own that slot"]
+ BookPiece["Later book piece writes Book:BTC
and Ticker:BTC again"]
+ Merge["Three-way merge"]
+ LiveStore["Live store"]
+
+ FirstFlush --> LiveStore
+ FirstFlush --> LiveTick
+ LiveTick --> Merge
+ BookPiece --> Merge
+ Merge --> LiveStore
+```
+
+### RSC vs renderToPipeableStream
+
+Opens **RSC vs renderToPipeableStream**. Two clocks, not two products.
+
+```mermaid
+flowchart TB
+ subgraph htmlStream["Streaming HTML"]
+ Insert["useServerInsertedHTML"]
+ RTP["renderToPipeableStream"]
+ Chunk["HTML chunk
baseline, then StateDelta + island markup"]
+ Insert -->|"Next App Router"| Chunk
+ RTP -->|"state piece colocated
with the revealing Suspense"| Chunk
+ end
+ subgraph rsc["RSC"]
+ Payload["RSC payload"]
+ Client["Client Component can start
before the HTML delta runs"]
+ Payload --> Client
+ end
+ Snapshot["Hydration snapshot"]
+ Chunk --> Snapshot
+ Client -->|"useSuspense"| Snapshot
+```
+
+| Wire | Host | How a piece gets on the wire | What it cannot promise |
+| --- | --- | --- | --- |
+| **Streaming HTML + RSC** | `@data-client/react/nextjs` | `useServerInsertedHTML()` writes the inert baseline, then a `StateDelta` script per flush, into the **HTML** stream | It cannot order that script against **RSC**. A Client Component may start before the delta that describes it has executed. |
+| **`renderToPipeableStream`** | `@data-client/react/ssr` (Express, Anansi) | Intended: a state-piece colocated in the revealing Suspense/island so React emits the delta and the dependent markup as one ordered subtree | Not shipped in this release — generic `/ssr` remains a one-shot snapshot. Parser/hydration order is still not a contract. |
+
+The figures are the **intended** client clock. This release does not throw per-key waiters or fold independently of the receiver layout effect.
diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md
index 9d8e40914114..706e1545b6ce 100644
--- a/docs/core/getting-started/debugging.md
+++ b/docs/core/getting-started/debugging.md
@@ -134,10 +134,11 @@ import {
} from '@data-client/react';
import ReactDOM from 'react-dom';
-const managers = getDefaultManagers({
- // highlight-next-line
- devToolsManager: { trace: true },
-});
+const managers = () =>
+ getDefaultManagers({
+ // highlight-next-line
+ devToolsManager: { trace: true },
+ });
ReactDOM.createRoot(document.body).render(
diff --git a/docs/core/guides/ssr.md b/docs/core/guides/ssr.md
index e3dda2158484..e58d8ad8522e 100644
--- a/docs/core/guides/ssr.md
+++ b/docs/core/guides/ssr.md
@@ -6,6 +6,7 @@ sidebar_label: Server Side Rendering
import PkgTabs from '@site/src/components/PkgTabs';
import StackBlitz from '@site/src/components/StackBlitz';
+import StreamedHydration from '../diagrams/\_streamed_hydration.mdx';
@@ -18,6 +19,44 @@ Client takes this one step further by pre-populating the data store. Unlike othe
Reactive Data Client becomes interactive the moment the page is visible, making [data mutations](../getting-started/mutations.md) instantaneous. Additionally there is no need for additional data fetches that increase server
load and slow client hydration, potentially causing application stutters.
+## Incremental streamed hydration {#streamed-hydration}
+
+The store is not a second document that waits for the page. It is the same stream, in generations:
+
+1. The shell carries an **inert baseline**. First paint is not delayed for Data Client.
+2. Each later committed server revision emits a **StateDelta**. The Next.js adapter folds queued pieces into its own snapshot and publishes them into the live store from the receiver layout effect. Public [`DataProvider`](../api/DataProvider.md) reads stay on [`StateContext`](../api/DataProvider.md); streaming is adapter-owned.
+3. Several islands that finish in one flush share one delta. A nested child may become readable before its parent. A later island may write an entity already present from an earlier delta; the three-way merge keeps slots the client already changed.
+4. If that fold has already happened, [`useSuspense()`](../api/useSuspense.md) hits. If **RSC** starts the island first, a miss fetches like any client render.
+5. After the island commits, [`useLive()`](../api/useLive.md) / [`useSubscription()`](../api/useSubscription.md) dispatch [`SUBSCRIBE`](../api/Actions.md#subscribe) so WebSocket or polling may start. Subscription is not proof the server delta arrived.
+
+Next.js App Router emits this protocol. Pages Router (`@data-client/ssr/nextjs`) and generic `@data-client/react/ssr` (Express, Anansi, [`renderToPipeableStream`](https://react.dev/reference/react-dom/server/renderToPipeableStream)) remain a one-shot document snapshot, plus an unstable internal composition seam for hosts that already own a snapshot store.
+
+[`useServerInsertedHTML()`](https://nextjs.org/docs/app/api-reference/functions/use-server-inserted-html) writes into the **HTML** stream. It does not order **RSC**. A Client Component may start before its delta script runs and before the receiver layout effect. Script-before-HTML is insertion order, not a zero-refetch guarantee.
+
+```
+MarketPage
+├── Shell provider, chrome — no Data Client read
+├── Watchlist useLive(getTickers)
+├── SymbolHeader useLive(getSymbolInfo, { symbol: 'BTC' })
+└── Market Suspense (late)
+ ├── Book useLive(getOrderBook, { symbol: 'BTC' })
+ └── Tape nested Suspense
+ └── Trades useLive(getTrades, { symbol: 'BTC' })
+```
+
+The figures below are **one concept each**: an overview with black boxes, then a zoom that opens that box. They describe the **intended** client clock (per-key waiters and fold-on-script). This release ships the wire (baseline + `StateDelta`) and an adapter-owned receiver. Live publication is still per-delta from a layout effect.
+
+
+
+### Open questions {#streamed-hydration-open}
+
+- **Per-key waiters** and **fold-on-script-arrival** (independent of `StreamedStateReceiver`’s layout effect) remain future client-clock work. Until they land, an RSC-first miss fetches like any client render.
+- A render-pure way for a progressively revealed island to read the server revision it was rendered from, without a live `StateContext` identity change that client-renders still-pending siblings, is not shipped.
+- Per-delta live publication (`flushSync` plus a live store write) is preserved adapter behavior, not the concurrent happy path.
+- Streamed deltas write whole entity/meta slots without schema merge (`writeDelta`). Overlay of a snapshot onto live state does not compare `lastReset`.
+- Incremental baseline-plus-delta for generic `renderToPipeableStream` / Anansi is not a stable public API. `@data-client/react/ssr` keeps one-shot helpers; hosts can compose an unstable internal `StreamingDataProvider` if they already own a snapshot store.
+- A document-wide [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event) wait is an acceptable interim, not the long-term contract. Missing-baseline still suspends while the document is loading.
+
## NextJS SSR {#nextjs}
### App Router
@@ -50,6 +89,101 @@ export default function RootLayout({ children }) {
}
```
+Async Server Components anywhere between the provider and your Client Components are fine.
+Each committed server revision emits a `StateDelta` that the Next.js adapter folds into its
+snapshot and publishes into the live store from the receiver layout effect. HTML insertion
+order is not an RSC clock: a Client Component may start before its delta script runs, and a
+miss then fetches like any client render.
+
+```tsx title="app/[userId]/layout.tsx"
+export default async function UserLayout({ children, params }) {
+ // resolves after the shell has already been sent
+ const { userId } = await params;
+ return ;
+}
+```
+
+#### Props
+
+```typescript
+interface NextDataProviderProps {
+ children: ReactNode;
+ managers?: () => Manager[];
+ nonce?: string;
+ Controller?: typeof Controller;
+ gcPolicy?: GCInterface;
+ devButton?: DevToolsPosition | null;
+}
+```
+
+`Controller` applies on both server and client. `gcPolicy` applies in the browser only: a
+request-scoped server store has nothing to collect.
+
+##### managers {#managers}
+
+The server builds a store per request, so [Managers](../api/Manager.md) must be created per
+request as well. It takes a **function**, called once per
+request on the server and once in the browser (the [browser DataProvider](../api/DataProvider.md#managers)
+accepts the same function; its array form is transitional):
+
+```tsx title="app/Provider.tsx"
+'use client';
+import { getDefaultManagers } from '@data-client/react';
+import { DataProvider } from '@data-client/react/nextjs';
+
+// highlight-next-line
+const managers = () => [...getDefaultManagers(), new MyManager()];
+
+export default function Provider({ children }: { children: React.ReactNode }) {
+ return {children};
+}
+```
+
+Manager instances shared across requests would mix up users' data, so an array is rejected.
+Server-side managers should not hold resources: their `cleanup()` is not run per request.
+
+##### nonce {#nonce}
+
+State is streamed in inline `',
+ line: 'a\u2028b\u2029c',
+ amp: 'x & y > z',
+ __proto__: { polluted: true },
+ };
+
+ it('keeps the payload inside its script tag', () => {
+ const escaped = escapeJsonForScript(JSON.stringify(hostile));
+ expect(escaped).not.toContain('<');
+ expect(escaped).not.toContain('>');
+ expect(escaped).not.toContain('&');
+ expect(escaped).not.toContain('\u2028');
+ expect(escaped).not.toContain('\u2029');
+ });
+
+ it('remains valid JSON with the same content', () => {
+ const parsed = JSON.parse(escapeJsonForScript(JSON.stringify(hostile)));
+ expect(parsed).toEqual(JSON.parse(JSON.stringify(hostile)));
+ expect(parsed.title).toBe(hostile.title);
+ });
+
+ it('works as the argument of an inline JSON.parse string literal', () => {
+ const literal = escapeJsonForScript(
+ JSON.stringify(JSON.stringify(hostile)),
+ );
+ const parsed = new Function(`return JSON.parse(${literal})`)();
+ expect(parsed).toEqual(JSON.parse(JSON.stringify(hostile)));
+ expect(({} as any).polluted).toBeUndefined();
+ expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
+ });
+
+ it('is applied by ServerData', () => {
+ const html = renderToString(
+ ,
+ );
+ const open = html.indexOf(' m instanceof NetworkManager,
- ) as any;
- if (networkManager === undefined)
- throw new Error('managers must include a NetworkManager');
- const reducer = createReducer(controller);
- const enhancer = applyMiddleware(
- // redux 5's types are wrong and do not allow any return typing from next, which is incorrect.
- // `next: (action: unknown) => unknown`: allows any action, but disallows all return types.
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- ...applyManager(managers, controller),
- PromiseifyMiddleware,
- );
- const store = createStore(reducer, initialState as any, enhancer);
- initManager(managers, controller, store.getState())();
+ const { store, controller, networkManager } = createServerStore(managers);
const selector = (state: any) => state;
diff --git a/packages/react/src/server/createServerStore.ts b/packages/react/src/server/createServerStore.ts
new file mode 100644
index 000000000000..515179d0a77b
--- /dev/null
+++ b/packages/react/src/server/createServerStore.ts
@@ -0,0 +1,38 @@
+import {
+ Controller,
+ NetworkManager,
+ initialState,
+ createReducer,
+ applyManager,
+ initManager,
+} from '@data-client/core';
+import type { Manager } from '@data-client/core';
+
+import { PromiseifyMiddleware } from './redux/index.js';
+import { createStore, applyMiddleware } from './redux/redux.js';
+import { NetworkManager as ReactNetworkManager } from '../managers/index.js';
+
+/** Redux-style store for one server render; managers must include a NetworkManager */
+export default function createServerStore(
+ managers?: Manager[],
+ ControllerClass: typeof Controller = Controller,
+) {
+ const controller = new ControllerClass();
+ managers = managers ?? [new ReactNetworkManager()];
+ const networkManager = managers.find(m => m instanceof NetworkManager) as
+ NetworkManager | undefined;
+ if (networkManager === undefined)
+ throw new Error('managers must include a NetworkManager');
+ const reducer = createReducer(controller);
+ const enhancer = applyMiddleware(
+ // redux 5's types are wrong and do not allow any return typing from next, which is incorrect.
+ // `next: (action: unknown) => unknown`: allows any action, but disallows all return types.
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ ...applyManager(managers, controller),
+ PromiseifyMiddleware,
+ );
+ const store = createStore(reducer, initialState as any, enhancer);
+ initManager(managers, controller, store.getState())();
+ return { store, controller, networkManager };
+}
diff --git a/packages/react/src/server/escapeJsonForScript.ts b/packages/react/src/server/escapeJsonForScript.ts
new file mode 100644
index 000000000000..33e72937cfe7
--- /dev/null
+++ b/packages/react/src/server/escapeJsonForScript.ts
@@ -0,0 +1,17 @@
+const ESCAPES: Record = {
+ '<': '\\u003c',
+ '>': '\\u003e',
+ '&': '\\u0026',
+ '\u2028': '\\u2028',
+ '\u2029': '\\u2029',
+};
+
+/**
+ * Makes JSON text safe to inline inside a `', close);
+ cursor = closeEnd === -1 ? close : closeEnd + 1;
+
+ if (attrs.includes('id="data-client-data"')) {
+ payloads.push({
+ index: open,
+ baseline: JSON.parse(content),
+ attrs,
+ });
+ } else if (
+ content.startsWith('(function(){var q=self.__DATA_CLIENT_DELTAS__')
+ ) {
+ const before = self.__DATA_CLIENT_DELTAS__?.length ?? 0;
+ new Function('self', content)(self);
+ expect(self.__DATA_CLIENT_DELTAS__).toHaveLength(before + 1);
+ payloads.push({
+ index: open,
+ delta: self.__DATA_CLIENT_DELTAS__![before],
+ attrs,
+ });
+ }
+ }
+ return payloads;
+}
+
+function fold(payloads: Payload[]): State {
+ let state = payloads[0]?.baseline ?? initialState;
+ for (const { delta } of payloads.slice(1))
+ state = applyStateDelta(state, delta!);
+ return state;
+}
+
+/** What a JSON transport can carry: no `optimistic`, no `undefined` */
+const serializable = (state: State) =>
+ JSON.parse(JSON.stringify({ ...state, optimistic: undefined }));
+
+/** The first payload that carries `key`, or undefined */
+const payloadWith = (payloads: Payload[], key: string) =>
+ payloads.find(
+ p =>
+ (p.baseline && key in p.baseline.endpoints) ||
+ p.delta?.endpoints.some(change => change.key === key),
+ );
+
+describe('Next.js DataProvider streaming', () => {
+ function setup() {
+ const capture = new CaptureManager();
+ const managers = () => [new NetworkManager(), capture];
+ return { capture, managers };
+ }
+
+ it('streams data fetched by a descendant that renders after the shell', async () => {
+ const { capture, managers } = setup();
+ const getTodo = makeGetTodo();
+ const html = await renderPage(
+
+
+
+
+
+
+ ,
+ );
+
+ expect(html).toContain('todo 1
');
+ const payloads = readPayloads(html);
+ // the shell had nothing yet
+ expect(payloads[0].baseline!.entities).toEqual({});
+ expect(payloads[0].index).toBeLessThan(html.indexOf(''));
+ // the data arrives before the HTML that was rendered from it
+ const key = getTodo.key({ id: '1' });
+ const carrier = payloadWith(payloads, key)!;
+ expect(carrier.delta).toBeDefined();
+ expect(carrier.index).toBeLessThan(html.indexOf('todo 1
'));
+ for (const payload of payloads)
+ expect(payload.attrs).toContain('nonce="n0nce"');
+ expect(serializable(fold(payloads))).toEqual(
+ serializable(capture.controller.getState()),
+ );
+ });
+
+ it('emits one delta per flush for staggered descendants', async () => {
+ const { capture, managers } = setup();
+ const getTodo = makeGetTodo();
+ const html = await renderPage(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+ );
+ const payloads = readPayloads(html);
+ for (const id of ['0', '1', '2']) {
+ const text = `todo ${id}
`;
+ expect(html).toContain(text);
+ const carrier = payloadWith(payloads, getTodo.key({ id }))!;
+ expect(carrier).toBeDefined();
+ expect(carrier.index).toBeLessThan(html.indexOf(text));
+ }
+ // later deltas only carry what changed
+ const deltas = payloads.filter(p => p.delta);
+ expect(deltas.length).toBeGreaterThanOrEqual(2);
+ for (const { delta } of deltas)
+ expect(delta!.entities.length).toBeLessThanOrEqual(1);
+ expect(serializable(fold(payloads))).toEqual(
+ serializable(capture.controller.getState()),
+ );
+ });
+
+ it('encodes removals from invalidate and a reset mid-stream', async () => {
+ const { capture, managers } = setup();
+ const getTodo = makeGetTodo();
+ const gate = openAfter(40);
+ gate.then(() => {
+ capture.controller.invalidate(getTodo, { id: '0' });
+ });
+ const secondGate = openAfter(80);
+ secondGate.then(() => {
+ capture.controller.resetEntireStore();
+ });
+ const html = await renderPage(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+ );
+ const payloads = readPayloads(html);
+ const deltas = payloads.map(p => p.delta).filter(Boolean) as StateDelta[];
+ const key0 = getTodo.key({ id: '0' });
+ const removal = deltas
+ .flatMap(d => d.endpoints)
+ .find(c => c.key === key0 && !c.value?.endpoint);
+ expect(removal?.value?.meta?.invalidated).toBe(true);
+ expect(deltas.some(d => d.reset !== undefined)).toBe(true);
+ const final = capture.controller.getState();
+ expect(final.lastReset).toBeGreaterThan(0);
+ expect(serializable(fold(payloads))).toEqual(serializable(final));
+ expect(html).toContain('todo 2
');
+ });
+
+ it('keeps streaming when a request fails', async () => {
+ const { capture, managers } = setup();
+ const getTodo = makeGetTodo({ '1': 'FAIL' });
+ const errors: unknown[] = [];
+ const html = await renderPage(
+
+
+
+
+
+
+
+
+
+ ,
+ {
+ onError: e => {
+ errors.push(e);
+ },
+ },
+ );
+ expect(html).toContain('todo 0
');
+ expect(errors.map(e => (e as Error).message)).toEqual(['todo 1 failed']);
+ const payloads = readPayloads(html);
+ const final = capture.controller.getState();
+ expect(final.meta[getTodo.key({ id: '1' })].error).toBeDefined();
+ expect(serializable(fold(payloads))).toEqual(serializable(final));
+ });
+
+ it('emits only the baseline for a page without requests', async () => {
+ const html = await renderPage(
+
+ static
+ ,
+ );
+ const payloads = readPayloads(html);
+ expect(payloads).toHaveLength(1);
+ expect(payloads[0].baseline).toEqual(
+ JSON.parse(JSON.stringify(initialState)),
+ );
+ expect(html).not.toContain('__DATA_CLIENT_DELTAS__');
+ });
+
+ it('static generation flushes the complete state once', async () => {
+ const { capture, managers } = setup();
+ const getTodo = makeGetTodo();
+ const html = await renderPage(
+
+
+
+
+
+
+ ,
+ { staticGeneration: true },
+ );
+ const payloads = readPayloads(html);
+ expect(payloads).toHaveLength(1);
+ expect(payloads[0].baseline!.endpoints[getTodo.key({ id: '1' })]).toBe('1');
+ expect(serializable(payloads[0].baseline!)).toEqual(
+ serializable(capture.controller.getState()),
+ );
+ });
+
+ it('isolates concurrent requests using a managers factory', async () => {
+ const getTodo = makeGetTodo({ a: 'alpha', b: 'beta' });
+ const page = (id: string) =>
+ renderPage(
+ [new NetworkManager()]}>
+
+
+
+
+
+ ,
+ );
+ const [a, b] = await Promise.all([page('a'), page('b')]);
+ expect(a).toContain('alpha
');
+ expect(a).not.toContain('beta');
+ expect(b).toContain('beta
');
+ expect(b).not.toContain('alpha');
+ expect(JSON.stringify(fold(readPayloads(a)).entities)).not.toContain(
+ 'beta',
+ );
+ expect(JSON.stringify(fold(readPayloads(b)).entities)).not.toContain(
+ 'alpha',
+ );
+ });
+
+ it('escapes payloads so API data cannot break out of the script', async () => {
+ const { capture, managers } = setup();
+ const evil = '\u2028';
+ const getTodo = makeGetTodo({ '1': evil });
+ const html = await renderPage(
+
+
+
+
+
+
+
+
+
+ ,
+ );
+ const payloads = readPayloads(html);
+ const folded = fold(payloads);
+ expect((folded.entities.Todo!['1'] as Todo).title).toBe(evil);
+ // the hostile pk is dropped rather than assigned onto the table prototype
+ expect(Object.getPrototypeOf(folded.entities.Todo)).toBe(Object.prototype);
+ expect(({} as any).title).toBeUndefined();
+ const scripts = html.match(/` can no longer break out of its tag
+- `nonce` prop on the Next.js [DataProvider](/docs/guides/ssr#nonce) for Content-Security-Policy
+
+**[Breaking Changes:](/blog/2026/09/11/v0.19-streamed-nextjs-ssr-managers-factory#migration-guide)**
+
+- [Next.js `DataProvider` `managers` is a function](/blog/2026/09/11/v0.19-streamed-nextjs-ssr-managers-factory#nextjs-managers) - `managers={[...]}` → `managers={() => [...]}`. Affects `@data-client/react/nextjs` only.
+
+{/* truncate */}
+
+## Incremental streamed hydration {#streamed-ssr}
+
+The shell carries an inert baseline. Each later committed server revision emits a `StateDelta`.
+The Next.js adapter folds that piece into its snapshot and publishes it into the live store from
+the receiver layout effect. Public `DataProvider` keeps `StateContext` reads.
+
+```tsx title="app/[userId]/page.tsx"
+export default async function TodoPage({ params }) {
+ const { userId } = await params; // resolves after the shell was sent
+ return ; // 'use client' + useSuspense
+}
+```
+
+On Next.js, RSC may run a Client Component before the HTML delta and before that layout
+effect. A miss then fetches like any client render. Per-key waiters that would suppress that
+refetch are not in this release. The figures are in the
+[SSR guide](/docs/guides/ssr#streamed-hydration). Next.js App Router is the first adapter;
+generic `renderToPipeableStream` remains a one-shot document snapshot.
+
+Nothing changes in how you write components. See the [SSR guide](/docs/guides/ssr#nextjs) for the
+`nonce` prop and limitations.
+
+## `managers` as a function {#managers-factory}
+
+[DataProvider](/docs/api/DataProvider#managers) accepts a function that creates the managers. It is
+called once when the provider mounts.
+
+```tsx
+import { DataProvider, getDefaultManagers } from '@data-client/react';
+
+// highlight-next-line
+const managers = () => [...getDefaultManagers(), new MyManager()];
+
+
+
+;
+```
+
+This is the form the library is moving to. The Next.js provider builds a store per request and can
+only take a function; in the browser it also gives each `DataProvider` on a page its own instances
+instead of one hoisted array bound to whichever store mounted last.
+
+:::warning Arrays are transitional
+
+`managers={[...]}` still works on the browser `DataProvider`, but will be removed in a future
+release. Migrate to `managers={() => [...]}` as soon as you can.
+
+:::
+
+## Migration guide
+
+
+
+### @data-client/react 0.19
+
+#### For all users
+
+Optional now, required later: pass `managers` as a function.
+
+
+
+```tsx title="Before"
+import { DataProvider, getDefaultManagers } from '@data-client/react';
+
+const managers = [...getDefaultManagers(), new MyManager()];
+
+
+
+;
+```
+
+```tsx title="After"
+import { DataProvider, getDefaultManagers } from '@data-client/react';
+
+const managers = () => [...getDefaultManagers(), new MyManager()];
+
+
+
+;
+```
+
+
+
+#### For Next.js App Router users {#nextjs-managers}
+
+Skip this section if you do not pass `managers` to the `@data-client/react/nextjs` provider.
+
+The same change is required here: an array throws with a migration hint. The function runs once per
+request on the server and once in the browser.
+
+
+
+```tsx title="Before"
+'use client';
+import { getDefaultManagers } from '@data-client/react';
+import { DataProvider } from '@data-client/react/nextjs';
+
+const managers = [...getDefaultManagers(), new MyManager()];
+
+export default function Provider({ children }) {
+ return {children};
+}
+```
+
+```tsx title="After"
+'use client';
+import { getDefaultManagers } from '@data-client/react';
+import { DataProvider } from '@data-client/react/nextjs';
+
+const managers = () => [...getDefaultManagers(), new MyManager()];
+
+export default function Provider({ children }) {
+ return {children};
+}
+```
+
+
+
+### Upgrade support
+
+As usual, if you have any troubles or questions, feel free to join our [](https://discord.gg/wXGV27xm6t) or [file a bug](https://github.com/reactive/data-client/issues/new/choose)
diff --git a/website/src/components/HomepageFeatures.tsx b/website/src/components/HomepageFeatures.tsx
index d68234be891e..c6256afc0dfa 100644
--- a/website/src/components/HomepageFeatures.tsx
+++ b/website/src/components/HomepageFeatures.tsx
@@ -9,7 +9,7 @@ import ChemicalCompositionSvg from '../../static/img/chemical-composition.svg';
import FastCarSvg from '../../static/img/fast-car.svg';
import GrowingBarChartSvg from '../../static/img/growing-bar-chart.svg';
-type SvgComponent = React.ComponentType>;
+type SvgComponent = typeof FastCarSvg;
interface BaseFeature {
description: React.ReactNode;
diff --git a/yarn.lock b/yarn.lock
index 549f78c3ed51..7394ca0027fd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3245,7 +3245,7 @@ __metadata:
react-dom: "npm:^19.2.1"
rollup-plugins: "workspace:*"
peerDependencies:
- "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0
+ "@data-client/react": ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0
"@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0
react: ^16.14.0 || ^17.0.0 || ^18.0.0-0 || ^19.0.0
peerDependenciesMeta:
@@ -3305,11 +3305,14 @@ __metadata:
"@react-navigation/native": ^6.0.0 || ^7.0.0
"@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-0 || ^19.0.0
react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0-0 || ^19.0.0
peerDependenciesMeta:
"@react-navigation/native":
optional: true
"@types/react":
optional: true
+ react-dom:
+ optional: true
languageName: unknown
linkType: soft
@@ -3352,7 +3355,7 @@ __metadata:
react-test-renderer: "npm:*"
rollup-plugins: "workspace:*"
peerDependencies:
- "@data-client/react": ^0.12.15 || ^0.13.0 || ^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0
+ "@data-client/react": ^0.12.15 || ^0.13.0 || ^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0 || ^0.19.0
"@testing-library/react-hooks": ^8.0.0
"@testing-library/react-native": ^13.0.0
"@types/react": ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-0 || ^19.0.0