@tanstack/react-query-next-experimental 5.100.14 → 5.101.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/legacy/HydrationStreamProvider.cjs +2 -2
- package/build/legacy/HydrationStreamProvider.cjs.map +1 -1
- package/build/legacy/HydrationStreamProvider.js +3 -3
- package/build/legacy/HydrationStreamProvider.js.map +1 -1
- package/build/legacy/ReactQueryStreamedHydration.cjs +1 -1
- package/build/legacy/ReactQueryStreamedHydration.cjs.map +1 -1
- package/build/legacy/ReactQueryStreamedHydration.js +2 -2
- package/build/legacy/ReactQueryStreamedHydration.js.map +1 -1
- package/build/modern/HydrationStreamProvider.cjs +2 -2
- package/build/modern/HydrationStreamProvider.cjs.map +1 -1
- package/build/modern/HydrationStreamProvider.js +3 -3
- package/build/modern/HydrationStreamProvider.js.map +1 -1
- package/build/modern/ReactQueryStreamedHydration.cjs +1 -1
- package/build/modern/ReactQueryStreamedHydration.cjs.map +1 -1
- package/build/modern/ReactQueryStreamedHydration.js +2 -2
- package/build/modern/ReactQueryStreamedHydration.js.map +1 -1
- package/package.json +3 -3
- package/src/HydrationStreamProvider.tsx +3 -3
- package/src/ReactQueryStreamedHydration.tsx +2 -2
|
@@ -55,7 +55,7 @@ function createHydrationStreamProvider() {
|
|
|
55
55
|
}
|
|
56
56
|
);
|
|
57
57
|
const [stream] = React.useState(() => {
|
|
58
|
-
if (!import_react_query.isServer) {
|
|
58
|
+
if (!import_react_query.environmentManager.isServer()) {
|
|
59
59
|
return {
|
|
60
60
|
push() {
|
|
61
61
|
}
|
|
@@ -87,7 +87,7 @@ function createHydrationStreamProvider() {
|
|
|
87
87
|
count.current++
|
|
88
88
|
);
|
|
89
89
|
});
|
|
90
|
-
if (!import_react_query.isServer) {
|
|
90
|
+
if (!import_react_query.environmentManager.isServer()) {
|
|
91
91
|
const win = window;
|
|
92
92
|
if (!((_a = win[id]) == null ? void 0 : _a.initialized)) {
|
|
93
93
|
const onEntries = (...serializedEntries) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { environmentManager } from '@tanstack/react-query'\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\nimport { htmlEscapeJsonString } from './htmlescape'\n\nconst serializedSymbol = Symbol('serialized')\n\ninterface DataTransformer {\n serialize: (object: any) => any\n deserialize: (object: any) => any\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData\n}\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>\n deserialize: (obj: Serialized<TData>) => TData\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void\n }\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n )\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = htmlEscapeJsonString(JSON.stringify(id))\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n )\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!environmentManager.isServer()) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>\n }\n return []\n })\n const count = React.useRef(0)\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []))\n\n if (!stream.length) {\n return null\n }\n // console.log(`pushing ${stream.length} entries`)\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',')\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ]\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!environmentManager.isServer()) {\n const win = window as any\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n props.onEntries(entries)\n }\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n }\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n )\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yBAAmC;AACnC,wBAAsC;AACtC,YAAuB;AACvB,wBAAqC;AAuI7B;AAlFD,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAqBvC;AA5FL;AA8FI,UAAM,KAAK,OAAa,YAAM,CAAC;AAC/B,UAAM,aAAS,wCAAqB,KAAK,UAAU,EAAE,CAAC;AAEtD,UAAM,CAAC,WAAW,IAAU;AAAA,MAC1B,MACG,MAAM,eAAe;AAAA;AAAA,QAEpB,WAAW,CAAC,QAAa;AAAA,QACzB,aAAa,CAAC,QAAa;AAAA,MAC7B;AAAA,IACJ;AAGA,UAAM,CAAC,MAAM,IAAU,eAAwB,MAAM;AACnD,UAAI,CAAC,sCAAmB,SAAS,GAAG;AAClC,eAAO;AAAA,UACL,OAAO;AAAA,UAEP;AAAA,QACF;AAAA,MACF;AACA,aAAO,CAAC;AAAA,IACV,CAAC;AACD,UAAM,QAAc,aAAO,CAAC;AAC5B,iDAAsB,MAAM;AAtHhC,UAAAA;AAwHM,aAAO,KAAK,KAAIA,MAAA,MAAM,YAAN,gBAAAA,IAAA,gBAAqB,CAAC,CAAE;AAExC,UAAI,CAAC,OAAO,QAAQ;AAClB,eAAO;AAAA,MACT;AAEA,YAAM,sBAAsB,OACzB,IAAI,CAAC,UAAU,YAAY,UAAU,KAAK,CAAC,EAC3C,IAAI,CAAC,UAAU,KAAK,UAAU,KAAK,CAAC,EACpC,KAAK,GAAG;AAIX,aAAO,SAAS;AAEhB,YAAM,OAAsB;AAAA,QAC1B,UAAU,MAAM,cAAc,MAAM;AAAA,QACpC,UAAU,MAAM,cAAU,wCAAqB,mBAAmB,CAAC;AAAA,MACrE;AACA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,MAAM;AAAA,UACb,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,QAJK,MAAM;AAAA,MAKb;AAAA,IAEJ,CAAC;AAQD,QAAI,CAAC,sCAAmB,SAAS,GAAG;AAClC,YAAM,MAAM;AACZ,UAAI,GAAC,SAAI,EAAE,MAAN,mBAAS,cAAa;AAEzB,cAAM,YAAY,IAAI,sBAAiD;AACrE,gBAAM,UAAU,kBAAkB;AAAA,YAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,UACpC;AACA,gBAAM,UAAU,OAAO;AAAA,QACzB;AAEA,cAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,kBAAU,GAAG,SAAS;AAGtB,YAAI,EAAE,IAAI;AAAA,UACR,aAAa;AAAA,UACb,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAGA,WACE,4CAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,GACnC,gBAAM,UACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":["_a"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/HydrationStreamProvider.tsx
|
|
4
|
-
import {
|
|
4
|
+
import { environmentManager } from "@tanstack/react-query";
|
|
5
5
|
import { useServerInsertedHTML } from "next/navigation";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { htmlEscapeJsonString } from "./htmlescape.js";
|
|
@@ -22,7 +22,7 @@ function createHydrationStreamProvider() {
|
|
|
22
22
|
}
|
|
23
23
|
);
|
|
24
24
|
const [stream] = React.useState(() => {
|
|
25
|
-
if (!isServer) {
|
|
25
|
+
if (!environmentManager.isServer()) {
|
|
26
26
|
return {
|
|
27
27
|
push() {
|
|
28
28
|
}
|
|
@@ -54,7 +54,7 @@ function createHydrationStreamProvider() {
|
|
|
54
54
|
count.current++
|
|
55
55
|
);
|
|
56
56
|
});
|
|
57
|
-
if (!isServer) {
|
|
57
|
+
if (!environmentManager.isServer()) {
|
|
58
58
|
const win = window;
|
|
59
59
|
if (!((_a = win[id]) == null ? void 0 : _a.initialized)) {
|
|
60
60
|
const onEntries = (...serializedEntries) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { environmentManager } from '@tanstack/react-query'\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\nimport { htmlEscapeJsonString } from './htmlescape'\n\nconst serializedSymbol = Symbol('serialized')\n\ninterface DataTransformer {\n serialize: (object: any) => any\n deserialize: (object: any) => any\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData\n}\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>\n deserialize: (obj: Serialized<TData>) => TData\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void\n }\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n )\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = htmlEscapeJsonString(JSON.stringify(id))\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n )\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!environmentManager.isServer()) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>\n }\n return []\n })\n const count = React.useRef(0)\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []))\n\n if (!stream.length) {\n return null\n }\n // console.log(`pushing ${stream.length} entries`)\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',')\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ]\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!environmentManager.isServer()) {\n const win = window as any\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n props.onEntries(entries)\n }\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n }\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n )\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n }\n}\n"],"mappings":";;;AAEA,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,YAAY,WAAW;AACvB,SAAS,4BAA4B;AAuI7B;AAlFD,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAqBvC;AA5FL;AA8FI,UAAM,KAAK,OAAa,YAAM,CAAC;AAC/B,UAAM,SAAS,qBAAqB,KAAK,UAAU,EAAE,CAAC;AAEtD,UAAM,CAAC,WAAW,IAAU;AAAA,MAC1B,MACG,MAAM,eAAe;AAAA;AAAA,QAEpB,WAAW,CAAC,QAAa;AAAA,QACzB,aAAa,CAAC,QAAa;AAAA,MAC7B;AAAA,IACJ;AAGA,UAAM,CAAC,MAAM,IAAU,eAAwB,MAAM;AACnD,UAAI,CAAC,mBAAmB,SAAS,GAAG;AAClC,eAAO;AAAA,UACL,OAAO;AAAA,UAEP;AAAA,QACF;AAAA,MACF;AACA,aAAO,CAAC;AAAA,IACV,CAAC;AACD,UAAM,QAAc,aAAO,CAAC;AAC5B,0BAAsB,MAAM;AAtHhC,UAAAA;AAwHM,aAAO,KAAK,KAAIA,MAAA,MAAM,YAAN,gBAAAA,IAAA,gBAAqB,CAAC,CAAE;AAExC,UAAI,CAAC,OAAO,QAAQ;AAClB,eAAO;AAAA,MACT;AAEA,YAAM,sBAAsB,OACzB,IAAI,CAAC,UAAU,YAAY,UAAU,KAAK,CAAC,EAC3C,IAAI,CAAC,UAAU,KAAK,UAAU,KAAK,CAAC,EACpC,KAAK,GAAG;AAIX,aAAO,SAAS;AAEhB,YAAM,OAAsB;AAAA,QAC1B,UAAU,MAAM,cAAc,MAAM;AAAA,QACpC,UAAU,MAAM,UAAU,qBAAqB,mBAAmB,CAAC;AAAA,MACrE;AACA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,MAAM;AAAA,UACb,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,QAJK,MAAM;AAAA,MAKb;AAAA,IAEJ,CAAC;AAQD,QAAI,CAAC,mBAAmB,SAAS,GAAG;AAClC,YAAM,MAAM;AACZ,UAAI,GAAC,SAAI,EAAE,MAAN,mBAAS,cAAa;AAEzB,cAAM,YAAY,IAAI,sBAAiD;AACrE,gBAAM,UAAU,kBAAkB;AAAA,YAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,UACpC;AACA,gBAAM,UAAU,OAAO;AAAA,QACzB;AAEA,cAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,kBAAU,GAAG,SAAS;AAGtB,YAAI,EAAE,IAAI;AAAA,UACR,aAAa;AAAA,UACb,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAGA,WACE,oBAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,GACnC,gBAAM,UACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":["_a"]}
|
|
@@ -42,7 +42,7 @@ var stream = (0, import_HydrationStreamProvider.createHydrationStreamProvider)()
|
|
|
42
42
|
function ReactQueryStreamedHydration(props) {
|
|
43
43
|
const queryClient = (0, import_react_query.useQueryClient)(props.queryClient);
|
|
44
44
|
const [trackedKeys] = React.useState(() => /* @__PURE__ */ new Set());
|
|
45
|
-
if (import_react_query.isServer) {
|
|
45
|
+
if (import_react_query.environmentManager.isServer()) {
|
|
46
46
|
queryClient.getQueryCache().subscribe((event) => {
|
|
47
47
|
switch (event.type) {
|
|
48
48
|
case "added":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n
|
|
1
|
+
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n environmentManager,\n hydrate,\n useQueryClient,\n} from '@tanstack/react-query'\nimport * as React from 'react'\nimport { createHydrationStreamProvider } from './HydrationStreamProvider'\nimport type { HydrationStreamProviderProps } from './HydrationStreamProvider'\nimport type {\n DehydrateOptions,\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/react-query'\n\nconst stream = createHydrationStreamProvider<DehydratedState>()\n\n/**\n * This component is responsible for:\n * - hydrating the query client on the server\n * - dehydrating the query client on the server\n */\nexport function ReactQueryStreamedHydration(props: {\n children: React.ReactNode\n queryClient?: QueryClient\n nonce?: string\n options?: {\n hydrate?: HydrateOptions\n dehydrate?: DehydrateOptions\n }\n transformer?: HydrationStreamProviderProps<DehydratedState>['transformer']\n}) {\n const queryClient = useQueryClient(props.queryClient)\n\n /**\n * We need to track which queries were added/updated during the render\n */\n const [trackedKeys] = React.useState(() => new Set<string>())\n\n // <server only>\n if (environmentManager.isServer()) {\n // Do we need to care about unsubscribing? I don't think so to be honest\n queryClient.getQueryCache().subscribe((event) => {\n switch (event.type) {\n case 'added':\n case 'updated':\n // console.log('tracking', event.query.queryHash, 'b/c of a', event.type)\n trackedKeys.add(event.query.queryHash)\n }\n })\n }\n // </server only>\n\n return (\n <stream.Provider\n // Happens on server:\n onFlush={() => {\n /**\n * Dehydrated state of the client where we only include the queries that were added/updated since the last flush\n */\n const shouldDehydrate =\n props.options?.dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const dehydratedState = dehydrate(queryClient, {\n ...props.options?.dehydrate,\n shouldDehydrateQuery(query) {\n return trackedKeys.has(query.queryHash) && shouldDehydrate(query)\n },\n })\n trackedKeys.clear()\n\n if (!dehydratedState.queries.length) {\n return []\n }\n\n return [dehydratedState]\n }}\n // Happens in browser:\n onEntries={(entries) => {\n for (const hydratedState of entries) {\n hydrate(queryClient, hydratedState, props.options?.hydrate)\n }\n }}\n // Handle BigInts etc using superjson\n transformer={props.transformer}\n nonce={props.nonce}\n >\n {props.children}\n </stream.Provider>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yBAMO;AACP,YAAuB;AACvB,qCAA8C;AAgD1C;AAvCJ,IAAM,aAAS,8DAA+C;AAOvD,SAAS,4BAA4B,OASzC;AACD,QAAM,kBAAc,mCAAe,MAAM,WAAW;AAKpD,QAAM,CAAC,WAAW,IAAU,eAAS,MAAM,oBAAI,IAAY,CAAC;AAG5D,MAAI,sCAAmB,SAAS,GAAG;AAEjC,gBAAY,cAAc,EAAE,UAAU,CAAC,UAAU;AAC/C,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AAAA,QACL,KAAK;AAEH,sBAAY,IAAI,MAAM,MAAM,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAGA,SACE;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MAEC,SAAS,MAAM;AA5DrB;AAgEQ,cAAM,oBACJ,iBAAM,YAAN,mBAAe,cAAf,mBAA0B,yBAC1B;AAEF,cAAM,sBAAkB,8BAAU,aAAa;AAAA,UAC7C,IAAG,WAAM,YAAN,mBAAe;AAAA,UAClB,qBAAqB,OAAO;AAC1B,mBAAO,YAAY,IAAI,MAAM,SAAS,KAAK,gBAAgB,KAAK;AAAA,UAClE;AAAA,QACF,CAAC;AACD,oBAAY,MAAM;AAElB,YAAI,CAAC,gBAAgB,QAAQ,QAAQ;AACnC,iBAAO,CAAC;AAAA,QACV;AAEA,eAAO,CAAC,eAAe;AAAA,MACzB;AAAA,MAEA,WAAW,CAAC,YAAY;AAnF9B;AAoFQ,mBAAW,iBAAiB,SAAS;AACnC,0CAAQ,aAAa,gBAAe,WAAM,YAAN,mBAAe,OAAO;AAAA,QAC5D;AAAA,MACF;AAAA,MAEA,aAAa,MAAM;AAAA,MACnB,OAAO,MAAM;AAAA,MAEZ,gBAAM;AAAA;AAAA,EACT;AAEJ;","names":[]}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import {
|
|
5
5
|
defaultShouldDehydrateQuery,
|
|
6
6
|
dehydrate,
|
|
7
|
+
environmentManager,
|
|
7
8
|
hydrate,
|
|
8
|
-
isServer,
|
|
9
9
|
useQueryClient
|
|
10
10
|
} from "@tanstack/react-query";
|
|
11
11
|
import * as React from "react";
|
|
@@ -15,7 +15,7 @@ var stream = createHydrationStreamProvider();
|
|
|
15
15
|
function ReactQueryStreamedHydration(props) {
|
|
16
16
|
const queryClient = useQueryClient(props.queryClient);
|
|
17
17
|
const [trackedKeys] = React.useState(() => /* @__PURE__ */ new Set());
|
|
18
|
-
if (isServer) {
|
|
18
|
+
if (environmentManager.isServer()) {
|
|
19
19
|
queryClient.getQueryCache().subscribe((event) => {
|
|
20
20
|
switch (event.type) {
|
|
21
21
|
case "added":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n
|
|
1
|
+
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n environmentManager,\n hydrate,\n useQueryClient,\n} from '@tanstack/react-query'\nimport * as React from 'react'\nimport { createHydrationStreamProvider } from './HydrationStreamProvider'\nimport type { HydrationStreamProviderProps } from './HydrationStreamProvider'\nimport type {\n DehydrateOptions,\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/react-query'\n\nconst stream = createHydrationStreamProvider<DehydratedState>()\n\n/**\n * This component is responsible for:\n * - hydrating the query client on the server\n * - dehydrating the query client on the server\n */\nexport function ReactQueryStreamedHydration(props: {\n children: React.ReactNode\n queryClient?: QueryClient\n nonce?: string\n options?: {\n hydrate?: HydrateOptions\n dehydrate?: DehydrateOptions\n }\n transformer?: HydrationStreamProviderProps<DehydratedState>['transformer']\n}) {\n const queryClient = useQueryClient(props.queryClient)\n\n /**\n * We need to track which queries were added/updated during the render\n */\n const [trackedKeys] = React.useState(() => new Set<string>())\n\n // <server only>\n if (environmentManager.isServer()) {\n // Do we need to care about unsubscribing? I don't think so to be honest\n queryClient.getQueryCache().subscribe((event) => {\n switch (event.type) {\n case 'added':\n case 'updated':\n // console.log('tracking', event.query.queryHash, 'b/c of a', event.type)\n trackedKeys.add(event.query.queryHash)\n }\n })\n }\n // </server only>\n\n return (\n <stream.Provider\n // Happens on server:\n onFlush={() => {\n /**\n * Dehydrated state of the client where we only include the queries that were added/updated since the last flush\n */\n const shouldDehydrate =\n props.options?.dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const dehydratedState = dehydrate(queryClient, {\n ...props.options?.dehydrate,\n shouldDehydrateQuery(query) {\n return trackedKeys.has(query.queryHash) && shouldDehydrate(query)\n },\n })\n trackedKeys.clear()\n\n if (!dehydratedState.queries.length) {\n return []\n }\n\n return [dehydratedState]\n }}\n // Happens in browser:\n onEntries={(entries) => {\n for (const hydratedState of entries) {\n hydrate(queryClient, hydratedState, props.options?.hydrate)\n }\n }}\n // Handle BigInts etc using superjson\n transformer={props.transformer}\n nonce={props.nonce}\n >\n {props.children}\n </stream.Provider>\n )\n}\n"],"mappings":";;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,YAAY,WAAW;AACvB,SAAS,qCAAqC;AAgD1C;AAvCJ,IAAM,SAAS,8BAA+C;AAOvD,SAAS,4BAA4B,OASzC;AACD,QAAM,cAAc,eAAe,MAAM,WAAW;AAKpD,QAAM,CAAC,WAAW,IAAU,eAAS,MAAM,oBAAI,IAAY,CAAC;AAG5D,MAAI,mBAAmB,SAAS,GAAG;AAEjC,gBAAY,cAAc,EAAE,UAAU,CAAC,UAAU;AAC/C,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AAAA,QACL,KAAK;AAEH,sBAAY,IAAI,MAAM,MAAM,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAGA,SACE;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MAEC,SAAS,MAAM;AA5DrB;AAgEQ,cAAM,oBACJ,iBAAM,YAAN,mBAAe,cAAf,mBAA0B,yBAC1B;AAEF,cAAM,kBAAkB,UAAU,aAAa;AAAA,UAC7C,IAAG,WAAM,YAAN,mBAAe;AAAA,UAClB,qBAAqB,OAAO;AAC1B,mBAAO,YAAY,IAAI,MAAM,SAAS,KAAK,gBAAgB,KAAK;AAAA,UAClE;AAAA,QACF,CAAC;AACD,oBAAY,MAAM;AAElB,YAAI,CAAC,gBAAgB,QAAQ,QAAQ;AACnC,iBAAO,CAAC;AAAA,QACV;AAEA,eAAO,CAAC,eAAe;AAAA,MACzB;AAAA,MAEA,WAAW,CAAC,YAAY;AAnF9B;AAoFQ,mBAAW,iBAAiB,SAAS;AACnC,kBAAQ,aAAa,gBAAe,WAAM,YAAN,mBAAe,OAAO;AAAA,QAC5D;AAAA,MACF;AAAA,MAEA,aAAa,MAAM;AAAA,MACnB,OAAO,MAAM;AAAA,MAEZ,gBAAM;AAAA;AAAA,EACT;AAEJ;","names":[]}
|
|
@@ -54,7 +54,7 @@ function createHydrationStreamProvider() {
|
|
|
54
54
|
}
|
|
55
55
|
);
|
|
56
56
|
const [stream] = React.useState(() => {
|
|
57
|
-
if (!import_react_query.isServer) {
|
|
57
|
+
if (!import_react_query.environmentManager.isServer()) {
|
|
58
58
|
return {
|
|
59
59
|
push() {
|
|
60
60
|
}
|
|
@@ -85,7 +85,7 @@ function createHydrationStreamProvider() {
|
|
|
85
85
|
count.current++
|
|
86
86
|
);
|
|
87
87
|
});
|
|
88
|
-
if (!import_react_query.isServer) {
|
|
88
|
+
if (!import_react_query.environmentManager.isServer()) {
|
|
89
89
|
const win = window;
|
|
90
90
|
if (!win[id]?.initialized) {
|
|
91
91
|
const onEntries = (...serializedEntries) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { environmentManager } from '@tanstack/react-query'\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\nimport { htmlEscapeJsonString } from './htmlescape'\n\nconst serializedSymbol = Symbol('serialized')\n\ninterface DataTransformer {\n serialize: (object: any) => any\n deserialize: (object: any) => any\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData\n}\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>\n deserialize: (obj: Serialized<TData>) => TData\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void\n }\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n )\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = htmlEscapeJsonString(JSON.stringify(id))\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n )\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!environmentManager.isServer()) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>\n }\n return []\n })\n const count = React.useRef(0)\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []))\n\n if (!stream.length) {\n return null\n }\n // console.log(`pushing ${stream.length} entries`)\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',')\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ]\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!environmentManager.isServer()) {\n const win = window as any\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n props.onEntries(entries)\n }\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n }\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n )\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yBAAmC;AACnC,wBAAsC;AACtC,YAAuB;AACvB,wBAAqC;AAuI7B;AAlFD,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAqBvC;AAED,UAAM,KAAK,OAAa,YAAM,CAAC;AAC/B,UAAM,aAAS,wCAAqB,KAAK,UAAU,EAAE,CAAC;AAEtD,UAAM,CAAC,WAAW,IAAU;AAAA,MAC1B,MACG,MAAM,eAAe;AAAA;AAAA,QAEpB,WAAW,CAAC,QAAa;AAAA,QACzB,aAAa,CAAC,QAAa;AAAA,MAC7B;AAAA,IACJ;AAGA,UAAM,CAAC,MAAM,IAAU,eAAwB,MAAM;AACnD,UAAI,CAAC,sCAAmB,SAAS,GAAG;AAClC,eAAO;AAAA,UACL,OAAO;AAAA,UAEP;AAAA,QACF;AAAA,MACF;AACA,aAAO,CAAC;AAAA,IACV,CAAC;AACD,UAAM,QAAc,aAAO,CAAC;AAC5B,iDAAsB,MAAM;AAE1B,aAAO,KAAK,GAAI,MAAM,UAAU,KAAK,CAAC,CAAE;AAExC,UAAI,CAAC,OAAO,QAAQ;AAClB,eAAO;AAAA,MACT;AAEA,YAAM,sBAAsB,OACzB,IAAI,CAAC,UAAU,YAAY,UAAU,KAAK,CAAC,EAC3C,IAAI,CAAC,UAAU,KAAK,UAAU,KAAK,CAAC,EACpC,KAAK,GAAG;AAIX,aAAO,SAAS;AAEhB,YAAM,OAAsB;AAAA,QAC1B,UAAU,MAAM,cAAc,MAAM;AAAA,QACpC,UAAU,MAAM,cAAU,wCAAqB,mBAAmB,CAAC;AAAA,MACrE;AACA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,MAAM;AAAA,UACb,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,QAJK,MAAM;AAAA,MAKb;AAAA,IAEJ,CAAC;AAQD,QAAI,CAAC,sCAAmB,SAAS,GAAG;AAClC,YAAM,MAAM;AACZ,UAAI,CAAC,IAAI,EAAE,GAAG,aAAa;AAEzB,cAAM,YAAY,IAAI,sBAAiD;AACrE,gBAAM,UAAU,kBAAkB;AAAA,YAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,UACpC;AACA,gBAAM,UAAU,OAAO;AAAA,QACzB;AAEA,cAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,kBAAU,GAAG,SAAS;AAGtB,YAAI,EAAE,IAAI;AAAA,UACR,aAAa;AAAA,UACb,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAGA,WACE,4CAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,GACnC,gBAAM,UACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/HydrationStreamProvider.tsx
|
|
4
|
-
import {
|
|
4
|
+
import { environmentManager } from "@tanstack/react-query";
|
|
5
5
|
import { useServerInsertedHTML } from "next/navigation";
|
|
6
6
|
import * as React from "react";
|
|
7
7
|
import { htmlEscapeJsonString } from "./htmlescape.js";
|
|
@@ -21,7 +21,7 @@ function createHydrationStreamProvider() {
|
|
|
21
21
|
}
|
|
22
22
|
);
|
|
23
23
|
const [stream] = React.useState(() => {
|
|
24
|
-
if (!isServer) {
|
|
24
|
+
if (!environmentManager.isServer()) {
|
|
25
25
|
return {
|
|
26
26
|
push() {
|
|
27
27
|
}
|
|
@@ -52,7 +52,7 @@ function createHydrationStreamProvider() {
|
|
|
52
52
|
count.current++
|
|
53
53
|
);
|
|
54
54
|
});
|
|
55
|
-
if (!isServer) {
|
|
55
|
+
if (!environmentManager.isServer()) {
|
|
56
56
|
const win = window;
|
|
57
57
|
if (!win[id]?.initialized) {
|
|
58
58
|
const onEntries = (...serializedEntries) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { environmentManager } from '@tanstack/react-query'\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\nimport { htmlEscapeJsonString } from './htmlescape'\n\nconst serializedSymbol = Symbol('serialized')\n\ninterface DataTransformer {\n serialize: (object: any) => any\n deserialize: (object: any) => any\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData\n}\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>\n deserialize: (obj: Serialized<TData>) => TData\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void\n }\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n )\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = htmlEscapeJsonString(JSON.stringify(id))\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n )\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!environmentManager.isServer()) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>\n }\n return []\n })\n const count = React.useRef(0)\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []))\n\n if (!stream.length) {\n return null\n }\n // console.log(`pushing ${stream.length} entries`)\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',')\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ]\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!environmentManager.isServer()) {\n const win = window as any\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n props.onEntries(entries)\n }\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n }\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n )\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n }\n}\n"],"mappings":";;;AAEA,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,YAAY,WAAW;AACvB,SAAS,4BAA4B;AAuI7B;AAlFD,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAqBvC;AAED,UAAM,KAAK,OAAa,YAAM,CAAC;AAC/B,UAAM,SAAS,qBAAqB,KAAK,UAAU,EAAE,CAAC;AAEtD,UAAM,CAAC,WAAW,IAAU;AAAA,MAC1B,MACG,MAAM,eAAe;AAAA;AAAA,QAEpB,WAAW,CAAC,QAAa;AAAA,QACzB,aAAa,CAAC,QAAa;AAAA,MAC7B;AAAA,IACJ;AAGA,UAAM,CAAC,MAAM,IAAU,eAAwB,MAAM;AACnD,UAAI,CAAC,mBAAmB,SAAS,GAAG;AAClC,eAAO;AAAA,UACL,OAAO;AAAA,UAEP;AAAA,QACF;AAAA,MACF;AACA,aAAO,CAAC;AAAA,IACV,CAAC;AACD,UAAM,QAAc,aAAO,CAAC;AAC5B,0BAAsB,MAAM;AAE1B,aAAO,KAAK,GAAI,MAAM,UAAU,KAAK,CAAC,CAAE;AAExC,UAAI,CAAC,OAAO,QAAQ;AAClB,eAAO;AAAA,MACT;AAEA,YAAM,sBAAsB,OACzB,IAAI,CAAC,UAAU,YAAY,UAAU,KAAK,CAAC,EAC3C,IAAI,CAAC,UAAU,KAAK,UAAU,KAAK,CAAC,EACpC,KAAK,GAAG;AAIX,aAAO,SAAS;AAEhB,YAAM,OAAsB;AAAA,QAC1B,UAAU,MAAM,cAAc,MAAM;AAAA,QACpC,UAAU,MAAM,UAAU,qBAAqB,mBAAmB,CAAC;AAAA,MACrE;AACA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,MAAM;AAAA,UACb,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,QAJK,MAAM;AAAA,MAKb;AAAA,IAEJ,CAAC;AAQD,QAAI,CAAC,mBAAmB,SAAS,GAAG;AAClC,YAAM,MAAM;AACZ,UAAI,CAAC,IAAI,EAAE,GAAG,aAAa;AAEzB,cAAM,YAAY,IAAI,sBAAiD;AACrE,gBAAM,UAAU,kBAAkB;AAAA,YAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,UACpC;AACA,gBAAM,UAAU,OAAO;AAAA,QACzB;AAEA,cAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,kBAAU,GAAG,SAAS;AAGtB,YAAI,EAAE,IAAI;AAAA,UACR,aAAa;AAAA,UACb,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAGA,WACE,oBAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,GACnC,gBAAM,UACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
|
|
@@ -42,7 +42,7 @@ var stream = (0, import_HydrationStreamProvider.createHydrationStreamProvider)()
|
|
|
42
42
|
function ReactQueryStreamedHydration(props) {
|
|
43
43
|
const queryClient = (0, import_react_query.useQueryClient)(props.queryClient);
|
|
44
44
|
const [trackedKeys] = React.useState(() => /* @__PURE__ */ new Set());
|
|
45
|
-
if (import_react_query.isServer) {
|
|
45
|
+
if (import_react_query.environmentManager.isServer()) {
|
|
46
46
|
queryClient.getQueryCache().subscribe((event) => {
|
|
47
47
|
switch (event.type) {
|
|
48
48
|
case "added":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n
|
|
1
|
+
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n environmentManager,\n hydrate,\n useQueryClient,\n} from '@tanstack/react-query'\nimport * as React from 'react'\nimport { createHydrationStreamProvider } from './HydrationStreamProvider'\nimport type { HydrationStreamProviderProps } from './HydrationStreamProvider'\nimport type {\n DehydrateOptions,\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/react-query'\n\nconst stream = createHydrationStreamProvider<DehydratedState>()\n\n/**\n * This component is responsible for:\n * - hydrating the query client on the server\n * - dehydrating the query client on the server\n */\nexport function ReactQueryStreamedHydration(props: {\n children: React.ReactNode\n queryClient?: QueryClient\n nonce?: string\n options?: {\n hydrate?: HydrateOptions\n dehydrate?: DehydrateOptions\n }\n transformer?: HydrationStreamProviderProps<DehydratedState>['transformer']\n}) {\n const queryClient = useQueryClient(props.queryClient)\n\n /**\n * We need to track which queries were added/updated during the render\n */\n const [trackedKeys] = React.useState(() => new Set<string>())\n\n // <server only>\n if (environmentManager.isServer()) {\n // Do we need to care about unsubscribing? I don't think so to be honest\n queryClient.getQueryCache().subscribe((event) => {\n switch (event.type) {\n case 'added':\n case 'updated':\n // console.log('tracking', event.query.queryHash, 'b/c of a', event.type)\n trackedKeys.add(event.query.queryHash)\n }\n })\n }\n // </server only>\n\n return (\n <stream.Provider\n // Happens on server:\n onFlush={() => {\n /**\n * Dehydrated state of the client where we only include the queries that were added/updated since the last flush\n */\n const shouldDehydrate =\n props.options?.dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const dehydratedState = dehydrate(queryClient, {\n ...props.options?.dehydrate,\n shouldDehydrateQuery(query) {\n return trackedKeys.has(query.queryHash) && shouldDehydrate(query)\n },\n })\n trackedKeys.clear()\n\n if (!dehydratedState.queries.length) {\n return []\n }\n\n return [dehydratedState]\n }}\n // Happens in browser:\n onEntries={(entries) => {\n for (const hydratedState of entries) {\n hydrate(queryClient, hydratedState, props.options?.hydrate)\n }\n }}\n // Handle BigInts etc using superjson\n transformer={props.transformer}\n nonce={props.nonce}\n >\n {props.children}\n </stream.Provider>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yBAMO;AACP,YAAuB;AACvB,qCAA8C;AAgD1C;AAvCJ,IAAM,aAAS,8DAA+C;AAOvD,SAAS,4BAA4B,OASzC;AACD,QAAM,kBAAc,mCAAe,MAAM,WAAW;AAKpD,QAAM,CAAC,WAAW,IAAU,eAAS,MAAM,oBAAI,IAAY,CAAC;AAG5D,MAAI,sCAAmB,SAAS,GAAG;AAEjC,gBAAY,cAAc,EAAE,UAAU,CAAC,UAAU;AAC/C,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AAAA,QACL,KAAK;AAEH,sBAAY,IAAI,MAAM,MAAM,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAGA,SACE;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MAEC,SAAS,MAAM;AAIb,cAAM,kBACJ,MAAM,SAAS,WAAW,wBAC1B;AAEF,cAAM,sBAAkB,8BAAU,aAAa;AAAA,UAC7C,GAAG,MAAM,SAAS;AAAA,UAClB,qBAAqB,OAAO;AAC1B,mBAAO,YAAY,IAAI,MAAM,SAAS,KAAK,gBAAgB,KAAK;AAAA,UAClE;AAAA,QACF,CAAC;AACD,oBAAY,MAAM;AAElB,YAAI,CAAC,gBAAgB,QAAQ,QAAQ;AACnC,iBAAO,CAAC;AAAA,QACV;AAEA,eAAO,CAAC,eAAe;AAAA,MACzB;AAAA,MAEA,WAAW,CAAC,YAAY;AACtB,mBAAW,iBAAiB,SAAS;AACnC,0CAAQ,aAAa,eAAe,MAAM,SAAS,OAAO;AAAA,QAC5D;AAAA,MACF;AAAA,MAEA,aAAa,MAAM;AAAA,MACnB,OAAO,MAAM;AAAA,MAEZ,gBAAM;AAAA;AAAA,EACT;AAEJ;","names":[]}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import {
|
|
5
5
|
defaultShouldDehydrateQuery,
|
|
6
6
|
dehydrate,
|
|
7
|
+
environmentManager,
|
|
7
8
|
hydrate,
|
|
8
|
-
isServer,
|
|
9
9
|
useQueryClient
|
|
10
10
|
} from "@tanstack/react-query";
|
|
11
11
|
import * as React from "react";
|
|
@@ -15,7 +15,7 @@ var stream = createHydrationStreamProvider();
|
|
|
15
15
|
function ReactQueryStreamedHydration(props) {
|
|
16
16
|
const queryClient = useQueryClient(props.queryClient);
|
|
17
17
|
const [trackedKeys] = React.useState(() => /* @__PURE__ */ new Set());
|
|
18
|
-
if (isServer) {
|
|
18
|
+
if (environmentManager.isServer()) {
|
|
19
19
|
queryClient.getQueryCache().subscribe((event) => {
|
|
20
20
|
switch (event.type) {
|
|
21
21
|
case "added":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n
|
|
1
|
+
{"version":3,"sources":["../../src/ReactQueryStreamedHydration.tsx"],"sourcesContent":["'use client'\n\nimport {\n defaultShouldDehydrateQuery,\n dehydrate,\n environmentManager,\n hydrate,\n useQueryClient,\n} from '@tanstack/react-query'\nimport * as React from 'react'\nimport { createHydrationStreamProvider } from './HydrationStreamProvider'\nimport type { HydrationStreamProviderProps } from './HydrationStreamProvider'\nimport type {\n DehydrateOptions,\n DehydratedState,\n HydrateOptions,\n QueryClient,\n} from '@tanstack/react-query'\n\nconst stream = createHydrationStreamProvider<DehydratedState>()\n\n/**\n * This component is responsible for:\n * - hydrating the query client on the server\n * - dehydrating the query client on the server\n */\nexport function ReactQueryStreamedHydration(props: {\n children: React.ReactNode\n queryClient?: QueryClient\n nonce?: string\n options?: {\n hydrate?: HydrateOptions\n dehydrate?: DehydrateOptions\n }\n transformer?: HydrationStreamProviderProps<DehydratedState>['transformer']\n}) {\n const queryClient = useQueryClient(props.queryClient)\n\n /**\n * We need to track which queries were added/updated during the render\n */\n const [trackedKeys] = React.useState(() => new Set<string>())\n\n // <server only>\n if (environmentManager.isServer()) {\n // Do we need to care about unsubscribing? I don't think so to be honest\n queryClient.getQueryCache().subscribe((event) => {\n switch (event.type) {\n case 'added':\n case 'updated':\n // console.log('tracking', event.query.queryHash, 'b/c of a', event.type)\n trackedKeys.add(event.query.queryHash)\n }\n })\n }\n // </server only>\n\n return (\n <stream.Provider\n // Happens on server:\n onFlush={() => {\n /**\n * Dehydrated state of the client where we only include the queries that were added/updated since the last flush\n */\n const shouldDehydrate =\n props.options?.dehydrate?.shouldDehydrateQuery ??\n defaultShouldDehydrateQuery\n\n const dehydratedState = dehydrate(queryClient, {\n ...props.options?.dehydrate,\n shouldDehydrateQuery(query) {\n return trackedKeys.has(query.queryHash) && shouldDehydrate(query)\n },\n })\n trackedKeys.clear()\n\n if (!dehydratedState.queries.length) {\n return []\n }\n\n return [dehydratedState]\n }}\n // Happens in browser:\n onEntries={(entries) => {\n for (const hydratedState of entries) {\n hydrate(queryClient, hydratedState, props.options?.hydrate)\n }\n }}\n // Handle BigInts etc using superjson\n transformer={props.transformer}\n nonce={props.nonce}\n >\n {props.children}\n </stream.Provider>\n )\n}\n"],"mappings":";;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,YAAY,WAAW;AACvB,SAAS,qCAAqC;AAgD1C;AAvCJ,IAAM,SAAS,8BAA+C;AAOvD,SAAS,4BAA4B,OASzC;AACD,QAAM,cAAc,eAAe,MAAM,WAAW;AAKpD,QAAM,CAAC,WAAW,IAAU,eAAS,MAAM,oBAAI,IAAY,CAAC;AAG5D,MAAI,mBAAmB,SAAS,GAAG;AAEjC,gBAAY,cAAc,EAAE,UAAU,CAAC,UAAU;AAC/C,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AAAA,QACL,KAAK;AAEH,sBAAY,IAAI,MAAM,MAAM,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH;AAGA,SACE;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MAEC,SAAS,MAAM;AAIb,cAAM,kBACJ,MAAM,SAAS,WAAW,wBAC1B;AAEF,cAAM,kBAAkB,UAAU,aAAa;AAAA,UAC7C,GAAG,MAAM,SAAS;AAAA,UAClB,qBAAqB,OAAO;AAC1B,mBAAO,YAAY,IAAI,MAAM,SAAS,KAAK,gBAAgB,KAAK;AAAA,UAClE;AAAA,QACF,CAAC;AACD,oBAAY,MAAM;AAElB,YAAI,CAAC,gBAAgB,QAAQ,QAAQ;AACnC,iBAAO,CAAC;AAAA,QACV;AAEA,eAAO,CAAC,eAAe;AAAA,MACzB;AAAA,MAEA,WAAW,CAAC,YAAY;AACtB,mBAAW,iBAAiB,SAAS;AACnC,kBAAQ,aAAa,eAAe,MAAM,SAAS,OAAO;AAAA,QAC5D;AAAA,MACF;AAAA,MAEA,aAAa,MAAM;AAAA,MACnB,OAAO,MAAM;AAAA,MAEZ,gBAAM;AAAA;AAAA,EACT;AAEJ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-query-next-experimental",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.101.1",
|
|
4
4
|
"description": "Hydration utils for React Query in the NextJs app directory",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
"next": "^16.0.1",
|
|
45
45
|
"npm-run-all2": "^5.0.0",
|
|
46
46
|
"react": "^19.2.1",
|
|
47
|
-
"@tanstack/react-query": "5.
|
|
47
|
+
"@tanstack/react-query": "5.101.1"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"next": "^13 || ^14 || ^15 || ^16",
|
|
51
51
|
"react": "^18 || ^19",
|
|
52
|
-
"@tanstack/react-query": "^5.
|
|
52
|
+
"@tanstack/react-query": "^5.101.1"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"clean": "premove ./build ./coverage ./dist-ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { environmentManager } from '@tanstack/react-query'
|
|
4
4
|
import { useServerInsertedHTML } from 'next/navigation'
|
|
5
5
|
import * as React from 'react'
|
|
6
6
|
import { htmlEscapeJsonString } from './htmlescape'
|
|
@@ -106,7 +106,7 @@ export function createHydrationStreamProvider<TShape>() {
|
|
|
106
106
|
|
|
107
107
|
// <server stuff>
|
|
108
108
|
const [stream] = React.useState<Array<TShape>>(() => {
|
|
109
|
-
if (!isServer) {
|
|
109
|
+
if (!environmentManager.isServer()) {
|
|
110
110
|
return {
|
|
111
111
|
push() {
|
|
112
112
|
// no-op on the client
|
|
@@ -154,7 +154,7 @@ export function createHydrationStreamProvider<TShape>() {
|
|
|
154
154
|
// the initial render so children have access to the data immediately
|
|
155
155
|
// This is important to avoid the client suspending during the initial render
|
|
156
156
|
// if the data has not yet been hydrated.
|
|
157
|
-
if (!isServer) {
|
|
157
|
+
if (!environmentManager.isServer()) {
|
|
158
158
|
const win = window as any
|
|
159
159
|
if (!win[id]?.initialized) {
|
|
160
160
|
// Client: consume cache:
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import {
|
|
4
4
|
defaultShouldDehydrateQuery,
|
|
5
5
|
dehydrate,
|
|
6
|
+
environmentManager,
|
|
6
7
|
hydrate,
|
|
7
|
-
isServer,
|
|
8
8
|
useQueryClient,
|
|
9
9
|
} from '@tanstack/react-query'
|
|
10
10
|
import * as React from 'react'
|
|
@@ -42,7 +42,7 @@ export function ReactQueryStreamedHydration(props: {
|
|
|
42
42
|
const [trackedKeys] = React.useState(() => new Set<string>())
|
|
43
43
|
|
|
44
44
|
// <server only>
|
|
45
|
-
if (isServer) {
|
|
45
|
+
if (environmentManager.isServer()) {
|
|
46
46
|
// Do we need to care about unsubscribing? I don't think so to be honest
|
|
47
47
|
queryClient.getQueryCache().subscribe((event) => {
|
|
48
48
|
switch (event.type) {
|