@tanstack/react-query-next-experimental 5.17.19 → 5.18.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 +3 -2
- package/build/legacy/HydrationStreamProvider.cjs.map +1 -1
- package/build/legacy/HydrationStreamProvider.js +3 -2
- package/build/legacy/HydrationStreamProvider.js.map +1 -1
- package/build/legacy/htmlescape.cjs +43 -0
- package/build/legacy/htmlescape.cjs.map +1 -0
- package/build/legacy/htmlescape.d.cts +4 -0
- package/build/legacy/htmlescape.d.ts +4 -0
- package/build/legacy/htmlescape.js +17 -0
- package/build/legacy/htmlescape.js.map +1 -0
- package/build/modern/HydrationStreamProvider.cjs +3 -2
- package/build/modern/HydrationStreamProvider.cjs.map +1 -1
- package/build/modern/HydrationStreamProvider.js +3 -2
- package/build/modern/HydrationStreamProvider.js.map +1 -1
- package/build/modern/htmlescape.cjs +43 -0
- package/build/modern/htmlescape.cjs.map +1 -0
- package/build/modern/htmlescape.d.cts +4 -0
- package/build/modern/htmlescape.d.ts +4 -0
- package/build/modern/htmlescape.js +17 -0
- package/build/modern/htmlescape.js.map +1 -0
- package/package.json +3 -3
- package/src/HydrationStreamProvider.tsx +3 -2
- package/src/htmlescape.ts +24 -0
|
@@ -36,6 +36,7 @@ __export(HydrationStreamProvider_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(HydrationStreamProvider_exports);
|
|
37
37
|
var import_navigation = require("next/navigation");
|
|
38
38
|
var React = __toESM(require("react"), 1);
|
|
39
|
+
var import_htmlescape = require("./htmlescape.cjs");
|
|
39
40
|
var serializedSymbol = Symbol("serialized");
|
|
40
41
|
function createHydrationStreamProvider() {
|
|
41
42
|
const context = React.createContext(
|
|
@@ -43,7 +44,7 @@ function createHydrationStreamProvider() {
|
|
|
43
44
|
);
|
|
44
45
|
function UseClientHydrationStreamProvider(props) {
|
|
45
46
|
const id = `__RQ${React.useId()}`;
|
|
46
|
-
const idJSON = JSON.stringify(id);
|
|
47
|
+
const idJSON = (0, import_htmlescape.htmlEscapeJsonString)(JSON.stringify(id));
|
|
47
48
|
const [transformer] = React.useState(
|
|
48
49
|
() => props.transformer ?? {
|
|
49
50
|
// noop
|
|
@@ -71,7 +72,7 @@ function createHydrationStreamProvider() {
|
|
|
71
72
|
stream.length = 0;
|
|
72
73
|
const html = [
|
|
73
74
|
`window[${idJSON}] = window[${idJSON}] || [];`,
|
|
74
|
-
`window[${idJSON}].push(${serializedCacheArgs});`
|
|
75
|
+
`window[${idJSON}].push(${(0, import_htmlescape.htmlEscapeJsonString)(serializedCacheArgs)});`
|
|
75
76
|
];
|
|
76
77
|
return /* @__PURE__ */ React.createElement(
|
|
77
78
|
"script",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\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\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 // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = 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 (typeof window !== 'undefined') {\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 stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${serializedCacheArgs});`,\n ]\n return (\n <script\n key={count.current++}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,wBAAsC;AACtC,YAAuB;
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\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\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 // 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 (typeof window !== 'undefined') {\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 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 dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,wBAAsC;AACtC,YAAuB;AACvB,wBAAqC;AAErC,IAAM,mBAAmB,OAAO,YAAY;AA+CrC,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAiBvC;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,OAAO,WAAW,aAAa;AACjC,eAAO;AAAA,UACL,OAAO;AAAA,UAEP;AAAA,QACF;AAAA,MACF;AACA,aAAO,CAAC;AAAA,IACV,CAAC;AACD,UAAM,QAAc,aAAO,CAAC;AAC5B,iDAAsB,MAAM;AA7GhC;AA+GM,aAAO,KAAK,KAAI,WAAM,YAAN,mCAAqB,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;AAGX,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,UACC,KAAK,MAAM;AAAA,UACX,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,MACF;AAAA,IAEJ,CAAC;AAID,UAAM,eAAqB,aAAO,MAAM,SAAS;AACjD,IAAM,gBAAU,MAAM;AACpB,mBAAa,UAAU,MAAM;AAAA,IAC/B,CAAC;AAED,IAAM,gBAAU,MAAM;AAEpB,YAAM,YAAY,IAAI,sBAAiD;AACrE,cAAM,UAAU,kBAAkB;AAAA,UAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,QACpC;AACA,qBAAa,QAAQ,OAAO;AAAA,MAC9B;AAEA,YAAM,MAAM;AAEZ,YAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,gBAAU,GAAG,SAAS;AAGtB,UAAI,EAAE,IAAI;AAAA,QACR,MAAM;AAAA,MACR;AAEA,aAAO,MAAM;AAEX,YAAI,EAAE,IAAI,CAAC;AAAA,MACb;AAAA,IACF,GAAG,CAAC,IAAI,WAAW,CAAC;AAGpB,WACE,oCAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,KACnC,MAAM,QACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/HydrationStreamProvider.tsx
|
|
4
4
|
import { useServerInsertedHTML } from "next/navigation";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { htmlEscapeJsonString } from "./htmlescape.js";
|
|
6
7
|
var serializedSymbol = Symbol("serialized");
|
|
7
8
|
function createHydrationStreamProvider() {
|
|
8
9
|
const context = React.createContext(
|
|
@@ -10,7 +11,7 @@ function createHydrationStreamProvider() {
|
|
|
10
11
|
);
|
|
11
12
|
function UseClientHydrationStreamProvider(props) {
|
|
12
13
|
const id = `__RQ${React.useId()}`;
|
|
13
|
-
const idJSON = JSON.stringify(id);
|
|
14
|
+
const idJSON = htmlEscapeJsonString(JSON.stringify(id));
|
|
14
15
|
const [transformer] = React.useState(
|
|
15
16
|
() => props.transformer ?? {
|
|
16
17
|
// noop
|
|
@@ -38,7 +39,7 @@ function createHydrationStreamProvider() {
|
|
|
38
39
|
stream.length = 0;
|
|
39
40
|
const html = [
|
|
40
41
|
`window[${idJSON}] = window[${idJSON}] || [];`,
|
|
41
|
-
`window[${idJSON}].push(${serializedCacheArgs});`
|
|
42
|
+
`window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`
|
|
42
43
|
];
|
|
43
44
|
return /* @__PURE__ */ React.createElement(
|
|
44
45
|
"script",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\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\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 // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = 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 (typeof window !== 'undefined') {\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 stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${serializedCacheArgs});`,\n ]\n return (\n <script\n key={count.current++}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,6BAA6B;AACtC,YAAY,WAAW;
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\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\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 // 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 (typeof window !== 'undefined') {\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 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 dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,6BAA6B;AACtC,YAAY,WAAW;AACvB,SAAS,4BAA4B;AAErC,IAAM,mBAAmB,OAAO,YAAY;AA+CrC,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAiBvC;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,OAAO,WAAW,aAAa;AACjC,eAAO;AAAA,UACL,OAAO;AAAA,UAEP;AAAA,QACF;AAAA,MACF;AACA,aAAO,CAAC;AAAA,IACV,CAAC;AACD,UAAM,QAAc,aAAO,CAAC;AAC5B,0BAAsB,MAAM;AA7GhC;AA+GM,aAAO,KAAK,KAAI,WAAM,YAAN,mCAAqB,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;AAGX,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,UACC,KAAK,MAAM;AAAA,UACX,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,MACF;AAAA,IAEJ,CAAC;AAID,UAAM,eAAqB,aAAO,MAAM,SAAS;AACjD,IAAM,gBAAU,MAAM;AACpB,mBAAa,UAAU,MAAM;AAAA,IAC/B,CAAC;AAED,IAAM,gBAAU,MAAM;AAEpB,YAAM,YAAY,IAAI,sBAAiD;AACrE,cAAM,UAAU,kBAAkB;AAAA,UAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,QACpC;AACA,qBAAa,QAAQ,OAAO;AAAA,MAC9B;AAEA,YAAM,MAAM;AAEZ,YAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,gBAAU,GAAG,SAAS;AAGtB,UAAI,EAAE,IAAI;AAAA,QACR,MAAM;AAAA,MACR;AAEA,aAAO,MAAM;AAEX,YAAI,EAAE,IAAI,CAAC;AAAA,MACb;AAAA,IACF,GAAG,CAAC,IAAI,WAAW,CAAC;AAGpB,WACE,oCAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,KACnC,MAAM,QACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/htmlescape.ts
|
|
21
|
+
var htmlescape_exports = {};
|
|
22
|
+
__export(htmlescape_exports, {
|
|
23
|
+
ESCAPE_REGEX: () => ESCAPE_REGEX,
|
|
24
|
+
htmlEscapeJsonString: () => htmlEscapeJsonString
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(htmlescape_exports);
|
|
27
|
+
var ESCAPE_LOOKUP = {
|
|
28
|
+
"&": "\\u0026",
|
|
29
|
+
">": "\\u003e",
|
|
30
|
+
"<": "\\u003c",
|
|
31
|
+
"\u2028": "\\u2028",
|
|
32
|
+
"\u2029": "\\u2029"
|
|
33
|
+
};
|
|
34
|
+
var ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|
35
|
+
function htmlEscapeJsonString(str) {
|
|
36
|
+
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
37
|
+
}
|
|
38
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
39
|
+
0 && (module.exports = {
|
|
40
|
+
ESCAPE_REGEX,
|
|
41
|
+
htmlEscapeJsonString
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=htmlescape.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/htmlescape.ts"],"sourcesContent":["// --------------------------------------------------------------------------------\n//\n// copied from\n// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts\n// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md\n//\n// --------------------------------------------------------------------------------\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\n\nconst ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nexport const ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\nexport function htmlEscapeJsonString(str: string): string {\n return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]!)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,IAAM,gBAA6C;AAAA,EACjD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,UAAU;AAAA,EACV,UAAU;AACZ;AAEO,IAAM,eAAe;AAErB,SAAS,qBAAqB,KAAqB;AACxD,SAAO,IAAI,QAAQ,cAAc,CAAC,UAAU,cAAc,KAAK,CAAE;AACnE;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/htmlescape.ts
|
|
2
|
+
var ESCAPE_LOOKUP = {
|
|
3
|
+
"&": "\\u0026",
|
|
4
|
+
">": "\\u003e",
|
|
5
|
+
"<": "\\u003c",
|
|
6
|
+
"\u2028": "\\u2028",
|
|
7
|
+
"\u2029": "\\u2029"
|
|
8
|
+
};
|
|
9
|
+
var ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|
10
|
+
function htmlEscapeJsonString(str) {
|
|
11
|
+
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
ESCAPE_REGEX,
|
|
15
|
+
htmlEscapeJsonString
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=htmlescape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/htmlescape.ts"],"sourcesContent":["// --------------------------------------------------------------------------------\n//\n// copied from\n// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts\n// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md\n//\n// --------------------------------------------------------------------------------\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\n\nconst ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nexport const ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\nexport function htmlEscapeJsonString(str: string): string {\n return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]!)\n}\n"],"mappings":";AAWA,IAAM,gBAA6C;AAAA,EACjD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,UAAU;AAAA,EACV,UAAU;AACZ;AAEO,IAAM,eAAe;AAErB,SAAS,qBAAqB,KAAqB;AACxD,SAAO,IAAI,QAAQ,cAAc,CAAC,UAAU,cAAc,KAAK,CAAE;AACnE;","names":[]}
|
|
@@ -36,6 +36,7 @@ __export(HydrationStreamProvider_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(HydrationStreamProvider_exports);
|
|
37
37
|
var import_navigation = require("next/navigation");
|
|
38
38
|
var React = __toESM(require("react"), 1);
|
|
39
|
+
var import_htmlescape = require("./htmlescape.cjs");
|
|
39
40
|
var serializedSymbol = Symbol("serialized");
|
|
40
41
|
function createHydrationStreamProvider() {
|
|
41
42
|
const context = React.createContext(
|
|
@@ -43,7 +44,7 @@ function createHydrationStreamProvider() {
|
|
|
43
44
|
);
|
|
44
45
|
function UseClientHydrationStreamProvider(props) {
|
|
45
46
|
const id = `__RQ${React.useId()}`;
|
|
46
|
-
const idJSON = JSON.stringify(id);
|
|
47
|
+
const idJSON = (0, import_htmlescape.htmlEscapeJsonString)(JSON.stringify(id));
|
|
47
48
|
const [transformer] = React.useState(
|
|
48
49
|
() => props.transformer ?? {
|
|
49
50
|
// noop
|
|
@@ -70,7 +71,7 @@ function createHydrationStreamProvider() {
|
|
|
70
71
|
stream.length = 0;
|
|
71
72
|
const html = [
|
|
72
73
|
`window[${idJSON}] = window[${idJSON}] || [];`,
|
|
73
|
-
`window[${idJSON}].push(${serializedCacheArgs});`
|
|
74
|
+
`window[${idJSON}].push(${(0, import_htmlescape.htmlEscapeJsonString)(serializedCacheArgs)});`
|
|
74
75
|
];
|
|
75
76
|
return /* @__PURE__ */ React.createElement(
|
|
76
77
|
"script",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\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\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 // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = 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 (typeof window !== 'undefined') {\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 stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${serializedCacheArgs});`,\n ]\n return (\n <script\n key={count.current++}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,wBAAsC;AACtC,YAAuB;
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\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\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 // 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 (typeof window !== 'undefined') {\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 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 dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,wBAAsC;AACtC,YAAuB;AACvB,wBAAqC;AAErC,IAAM,mBAAmB,OAAO,YAAY;AA+CrC,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAiBvC;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,OAAO,WAAW,aAAa;AACjC,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;AAGX,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,UACC,KAAK,MAAM;AAAA,UACX,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,MACF;AAAA,IAEJ,CAAC;AAID,UAAM,eAAqB,aAAO,MAAM,SAAS;AACjD,IAAM,gBAAU,MAAM;AACpB,mBAAa,UAAU,MAAM;AAAA,IAC/B,CAAC;AAED,IAAM,gBAAU,MAAM;AAEpB,YAAM,YAAY,IAAI,sBAAiD;AACrE,cAAM,UAAU,kBAAkB;AAAA,UAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,QACpC;AACA,qBAAa,QAAQ,OAAO;AAAA,MAC9B;AAEA,YAAM,MAAM;AAEZ,YAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,gBAAU,GAAG,SAAS;AAGtB,UAAI,EAAE,IAAI;AAAA,QACR,MAAM;AAAA,MACR;AAEA,aAAO,MAAM;AAEX,YAAI,EAAE,IAAI,CAAC;AAAA,MACb;AAAA,IACF,GAAG,CAAC,IAAI,WAAW,CAAC;AAGpB,WACE,oCAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,KACnC,MAAM,QACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/HydrationStreamProvider.tsx
|
|
4
4
|
import { useServerInsertedHTML } from "next/navigation";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { htmlEscapeJsonString } from "./htmlescape.js";
|
|
6
7
|
var serializedSymbol = Symbol("serialized");
|
|
7
8
|
function createHydrationStreamProvider() {
|
|
8
9
|
const context = React.createContext(
|
|
@@ -10,7 +11,7 @@ function createHydrationStreamProvider() {
|
|
|
10
11
|
);
|
|
11
12
|
function UseClientHydrationStreamProvider(props) {
|
|
12
13
|
const id = `__RQ${React.useId()}`;
|
|
13
|
-
const idJSON = JSON.stringify(id);
|
|
14
|
+
const idJSON = htmlEscapeJsonString(JSON.stringify(id));
|
|
14
15
|
const [transformer] = React.useState(
|
|
15
16
|
() => props.transformer ?? {
|
|
16
17
|
// noop
|
|
@@ -37,7 +38,7 @@ function createHydrationStreamProvider() {
|
|
|
37
38
|
stream.length = 0;
|
|
38
39
|
const html = [
|
|
39
40
|
`window[${idJSON}] = window[${idJSON}] || [];`,
|
|
40
|
-
`window[${idJSON}].push(${serializedCacheArgs});`
|
|
41
|
+
`window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`
|
|
41
42
|
];
|
|
42
43
|
return /* @__PURE__ */ React.createElement(
|
|
43
44
|
"script",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\nimport { useServerInsertedHTML } from 'next/navigation'\nimport * as React from 'react'\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\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 // unique id for the cache provider\n const id = `__RQ${React.useId()}`\n const idJSON = 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 (typeof window !== 'undefined') {\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 stream.length = 0\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${serializedCacheArgs});`,\n ]\n return (\n <script\n key={count.current++}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,6BAA6B;AACtC,YAAY,WAAW;
|
|
1
|
+
{"version":3,"sources":["../../src/HydrationStreamProvider.tsx"],"sourcesContent":["'use client'\n\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\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 // 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 (typeof window !== 'undefined') {\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 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 dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n )\n })\n // </server stuff>\n\n // <client stuff>\n const onEntriesRef = React.useRef(props.onEntries)\n React.useEffect(() => {\n onEntriesRef.current = props.onEntries\n })\n\n React.useEffect(() => {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n )\n onEntriesRef.current(entries)\n }\n\n const win = window as any\n // Register cache consumer\n const winStream: Array<Serialized<TShape>> = win[id] ?? []\n\n onEntries(...winStream)\n\n // Register our own consumer\n win[id] = {\n push: onEntries,\n }\n\n return () => {\n // Cleanup after unmount\n win[id] = []\n }\n }, [id, transformer])\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,6BAA6B;AACtC,YAAY,WAAW;AACvB,SAAS,4BAA4B;AAErC,IAAM,mBAAmB,OAAO,YAAY;AA+CrC,SAAS,gCAAwC;AACtD,QAAM,UAAgB;AAAA,IACpB;AAAA,EACF;AAUA,WAAS,iCAAiC,OAiBvC;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,OAAO,WAAW,aAAa;AACjC,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;AAGX,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,UACC,KAAK,MAAM;AAAA,UACX,yBAAyB;AAAA,YACvB,QAAQ,KAAK,KAAK,EAAE;AAAA,UACtB;AAAA;AAAA,MACF;AAAA,IAEJ,CAAC;AAID,UAAM,eAAqB,aAAO,MAAM,SAAS;AACjD,IAAM,gBAAU,MAAM;AACpB,mBAAa,UAAU,MAAM;AAAA,IAC/B,CAAC;AAED,IAAM,gBAAU,MAAM;AAEpB,YAAM,YAAY,IAAI,sBAAiD;AACrE,cAAM,UAAU,kBAAkB;AAAA,UAAI,CAAC,eACrC,YAAY,YAAY,UAAU;AAAA,QACpC;AACA,qBAAa,QAAQ,OAAO;AAAA,MAC9B;AAEA,YAAM,MAAM;AAEZ,YAAM,YAAuC,IAAI,EAAE,KAAK,CAAC;AAEzD,gBAAU,GAAG,SAAS;AAGtB,UAAI,EAAE,IAAI;AAAA,QACR,MAAM;AAAA,MACR;AAEA,aAAO,MAAM;AAEX,YAAI,EAAE,IAAI,CAAC;AAAA,MACb;AAAA,IACF,GAAG,CAAC,IAAI,WAAW,CAAC;AAGpB,WACE,oCAAC,QAAQ,UAAR,EAAiB,OAAO,EAAE,QAAQ,GAAG,KACnC,MAAM,QACT;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/htmlescape.ts
|
|
21
|
+
var htmlescape_exports = {};
|
|
22
|
+
__export(htmlescape_exports, {
|
|
23
|
+
ESCAPE_REGEX: () => ESCAPE_REGEX,
|
|
24
|
+
htmlEscapeJsonString: () => htmlEscapeJsonString
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(htmlescape_exports);
|
|
27
|
+
var ESCAPE_LOOKUP = {
|
|
28
|
+
"&": "\\u0026",
|
|
29
|
+
">": "\\u003e",
|
|
30
|
+
"<": "\\u003c",
|
|
31
|
+
"\u2028": "\\u2028",
|
|
32
|
+
"\u2029": "\\u2029"
|
|
33
|
+
};
|
|
34
|
+
var ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|
35
|
+
function htmlEscapeJsonString(str) {
|
|
36
|
+
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
37
|
+
}
|
|
38
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
39
|
+
0 && (module.exports = {
|
|
40
|
+
ESCAPE_REGEX,
|
|
41
|
+
htmlEscapeJsonString
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=htmlescape.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/htmlescape.ts"],"sourcesContent":["// --------------------------------------------------------------------------------\n//\n// copied from\n// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts\n// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md\n//\n// --------------------------------------------------------------------------------\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\n\nconst ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nexport const ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\nexport function htmlEscapeJsonString(str: string): string {\n return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]!)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,IAAM,gBAA6C;AAAA,EACjD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,UAAU;AAAA,EACV,UAAU;AACZ;AAEO,IAAM,eAAe;AAErB,SAAS,qBAAqB,KAAqB;AACxD,SAAO,IAAI,QAAQ,cAAc,CAAC,UAAU,cAAc,KAAK,CAAE;AACnE;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/htmlescape.ts
|
|
2
|
+
var ESCAPE_LOOKUP = {
|
|
3
|
+
"&": "\\u0026",
|
|
4
|
+
">": "\\u003e",
|
|
5
|
+
"<": "\\u003c",
|
|
6
|
+
"\u2028": "\\u2028",
|
|
7
|
+
"\u2029": "\\u2029"
|
|
8
|
+
};
|
|
9
|
+
var ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|
10
|
+
function htmlEscapeJsonString(str) {
|
|
11
|
+
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
ESCAPE_REGEX,
|
|
15
|
+
htmlEscapeJsonString
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=htmlescape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/htmlescape.ts"],"sourcesContent":["// --------------------------------------------------------------------------------\n//\n// copied from\n// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts\n// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md\n//\n// --------------------------------------------------------------------------------\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\n\nconst ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nexport const ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\nexport function htmlEscapeJsonString(str: string): string {\n return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]!)\n}\n"],"mappings":";AAWA,IAAM,gBAA6C;AAAA,EACjD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,UAAU;AAAA,EACV,UAAU;AACZ;AAEO,IAAM,eAAe;AAErB,SAAS,qBAAqB,KAAqB;AACxD,SAAO,IAAI,QAAQ,cAAc,CAAC,UAAU,cAAc,KAAK,CAAE;AACnE;","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.18.1",
|
|
4
4
|
"description": "Hydration utils for React Query in the NextJs app directory",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"@vitejs/plugin-react": "^4.2.1",
|
|
38
38
|
"next": "^14.0.0",
|
|
39
39
|
"react": "^18.2.0",
|
|
40
|
-
"@tanstack/react-query": "5.
|
|
40
|
+
"@tanstack/react-query": "5.18.1"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"next": "^13 || ^14",
|
|
44
44
|
"react": "^18.0.0",
|
|
45
|
-
"@tanstack/react-query": "^5.
|
|
45
|
+
"@tanstack/react-query": "^5.18.1"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"clean": "rimraf ./build && rimraf ./coverage",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useServerInsertedHTML } from 'next/navigation'
|
|
4
4
|
import * as React from 'react'
|
|
5
|
+
import { htmlEscapeJsonString } from './htmlescape'
|
|
5
6
|
|
|
6
7
|
const serializedSymbol = Symbol('serialized')
|
|
7
8
|
|
|
@@ -83,7 +84,7 @@ export function createHydrationStreamProvider<TShape>() {
|
|
|
83
84
|
}) {
|
|
84
85
|
// unique id for the cache provider
|
|
85
86
|
const id = `__RQ${React.useId()}`
|
|
86
|
-
const idJSON = JSON.stringify(id)
|
|
87
|
+
const idJSON = htmlEscapeJsonString(JSON.stringify(id))
|
|
87
88
|
|
|
88
89
|
const [transformer] = React.useState(
|
|
89
90
|
() =>
|
|
@@ -124,7 +125,7 @@ export function createHydrationStreamProvider<TShape>() {
|
|
|
124
125
|
|
|
125
126
|
const html: Array<string> = [
|
|
126
127
|
`window[${idJSON}] = window[${idJSON}] || [];`,
|
|
127
|
-
`window[${idJSON}].push(${serializedCacheArgs});`,
|
|
128
|
+
`window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,
|
|
128
129
|
]
|
|
129
130
|
return (
|
|
130
131
|
<script
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// --------------------------------------------------------------------------------
|
|
2
|
+
//
|
|
3
|
+
// copied from
|
|
4
|
+
// https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/src/server/htmlescape.ts
|
|
5
|
+
// License: https://github.com/vercel/next.js/blob/6bc07792a4462a4bf921a72ab30dc4ab2c4e1bda/packages/next/license.md
|
|
6
|
+
//
|
|
7
|
+
// --------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
// This utility is based on https://github.com/zertosh/htmlescape
|
|
10
|
+
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
|
|
11
|
+
|
|
12
|
+
const ESCAPE_LOOKUP: { [match: string]: string } = {
|
|
13
|
+
'&': '\\u0026',
|
|
14
|
+
'>': '\\u003e',
|
|
15
|
+
'<': '\\u003c',
|
|
16
|
+
'\u2028': '\\u2028',
|
|
17
|
+
'\u2029': '\\u2029',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const ESCAPE_REGEX = /[&><\u2028\u2029]/g
|
|
21
|
+
|
|
22
|
+
export function htmlEscapeJsonString(str: string): string {
|
|
23
|
+
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]!)
|
|
24
|
+
}
|