@apollo/client-react-streaming
Version:
This package provides building blocks to create framework-level integration of Apollo Client with React's streaming SSR. See the [@apollo/client-integration-nextjs](https://github.com/apollographql/apollo-client-integrations/tree/main/packages/nextjs) pac
1 lines • 8.32 kB
Source Map (JSON)
{"version":3,"sources":["../src/stream-utils/JSONTransformStreams.tsx","../src/stream-utils/createInjectionTransformStream.tsx","../src/stream-utils/pipeReaderToResponse.ts"],"names":[],"mappings":";AAAO,IAAM,mBAAN,cAAkC,gBAAkC;AAAA,EACzE,cAAc;AACZ,UAAM;AAAA,MACJ,UAAU,OAAO,YAAY;AAC3B,mBAAW,QAAQ,KAAK,UAAU,KAAK,CAAC;AAAA,MAC1C;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,IAAM,mBAAN,cAAkC,gBAGvC;AAAA,EACA,cAAc;AACZ,UAAM;AAAA,MACJ,UAAU,OAAO,YAAY;AAC3B,YAAI,OAAO,UAAU,UAAU;AAC7B,kBAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAAA,QACxC;AACA,mBAAW,QAAQ,KAAK,MAAM,KAAK,CAAC;AAAA,MACtC;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AChBA,SAAS,sBAAsB;AAC/B,YAAY,WAAW;AAQhB,SAAS,iCAwCd;AACA,MAAI,mBAAiD,CAAC;AAEtD,iBAAe,qBAAqB;AAClC,UAAM,aAAa,CAAC,GAAG,gBAAgB;AACvC,uBAAmB,CAAC;AACpB,WAAO;AAAA,MACL,0DACG,WAAW,IAAI,CAAC,UAAU,MACzB,oCAAO,gBAAN,EAAe,KAAK,KAAI,SAAS,CAAE,CACrC,CACH;AAAA,IACF;AAAA,EACF;AAEA,MAAI,eAAe;AACnB,MAAI,qBAAqB;AACzB,MAAI,kBAAkB;AACtB,QAAM,cAAc,IAAI,YAAY;AACpC,QAAM,cAAc,IAAI,YAAY;AACpC,QAAM,WAAW;AAIjB,QAAM,aAAa,SAAS;AAE5B,QAAM,kBAAkB,IAAI,gBAAgB;AAAA,IAC1C,MAAM,UAAU,OAAO,YAAY;AAEjC,UAAI,oBAAoB;AACtB,mBAAW,QAAQ,KAAK;AACxB;AAAA,MACF;AAEA,UAAI,CAAC,cAAc;AACjB,cAAM,UACJ,kBAAkB,YAAY,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAC9D,cAAM,QAAQ,QAAQ,QAAQ,QAAQ;AACtC,YAAI,UAAU,IAAI;AAChB,gBAAM,sBACJ,QAAQ,MAAM,GAAG,KAAK,IACrB,MAAM,mBAAmB,IAC1B,QAAQ,MAAM,KAAK;AACrB,qBAAW,QAAQ,YAAY,OAAO,mBAAmB,CAAC;AAC1D,+BAAqB;AACrB,uBAAa,MAAM;AACjB,iCAAqB;AAAA,UACvB,CAAC;AACD,yBAAe;AAAA,QACjB,OAAO;AACL,4BAAkB,QAAQ,MAAM,CAAC,UAAU;AAC3C,qBAAW,QAAQ,YAAY,OAAO,QAAQ,MAAM,GAAG,CAAC,UAAU,CAAC,CAAC;AAAA,QACtE;AAAA,MACF,OAAO;AACL,mBAAW,QAAQ,YAAY,OAAO,MAAM,mBAAmB,CAAC,CAAC;AACjE,mBAAW,QAAQ,KAAK;AACxB,6BAAqB;AACrB,qBAAa,MAAM;AACjB,+BAAqB;AAAA,QACvB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB,CAAC,aAAa,iBAAiB,KAAK,QAAQ;AAAA,EAChE;AACF;;;AC7GA,eAAsB,qBACpB,QACA,KAKA;AACA,MAAI;AAEF,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,MAAM;AACR,YAAI,IAAI;AACR;AAAA,MACF,OAAO;AACL,YAAI,MAAM,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,EACF,SAAS,GAAQ;AACf,QAAI,QAAQ,CAAC;AAAA,EACf;AACF","sourcesContent":["export class JSONEncodeStream<T> extends TransformStream<T, JsonString<T>> {\n constructor() {\n super({\n transform(chunk, controller) {\n controller.enqueue(JSON.stringify(chunk));\n },\n });\n }\n}\n\nexport class JSONDecodeStream<T> extends TransformStream<\n JsonString<T> | AllowSharedBufferSource,\n T\n> {\n constructor() {\n super({\n transform(chunk, controller) {\n if (typeof chunk !== \"string\") {\n chunk = new TextDecoder().decode(chunk);\n }\n controller.enqueue(JSON.parse(chunk));\n },\n });\n }\n}\n\nexport type JsonString<Encoded> = string & { __jsonString?: [Encoded] };\n","/**\n * The logic for `createInjectionTransformStream` was strongly inspired by `createHeadInsertionTransformStream`\n * from https://github.com/vercel/next.js/blob/6481c92038cce43056005c07f80f2938faf29c29/packages/next/src/server/node-web-streams-helper.ts\n *\n * released under a MIT license (https://github.com/vercel/next.js/blob/6481c92038cce43056005c07f80f2938faf29c29/packages/next/license.md)\n * by Vercel, Inc., marked Copyright (c) 2023 Vercel, Inc.\n */\n\nimport { renderToString } from \"react-dom/server\";\nimport * as React from \"react\";\n\n/**\n * > This export is only available in streaming SSR Server environments\n *\n * Used to create a `TransformStream` that can be used for piping a React stream rendered by\n * `renderToReadableStream` and using the callback to insert chunks of HTML between React Chunks.\n */\nexport function createInjectionTransformStream(): {\n /**\n * @example\n * ```js\n * const { injectIntoStream, transformStream } = createInjectionTransformStream();\n * const App = render({ assets, injectIntoStream });\n * const reactStream = await renderToReadableStream(App, { bootstrapModules }));\n * await pipeReaderToResponse(\n * reactStream.pipeThrough(transformStream).getReader(),\n * response\n * );\n * ```\n */\n transformStream: TransformStream;\n /**\n * `injectIntoStream` method that can be injected into your React application, to be made available to\n *\n * @example\n * ```js title=\"setup\"\n * // create a Context for injection of `injectIntoStream`\n * const InjectionContext = React.createContext<\n * (callback: () => React.ReactNode) => void\n * >(() => {});\n * // to be used in your application\n * export const InjectionContextProvider = InjectionContext.Provider;\n * // make it accessible to `WrapApolloProvider`\n * export const WrappedApolloProvider = WrapApolloProvider(\n * buildManualDataTransport({\n * useInsertHtml() {\n * return React.useContext(InjectionContext);\n * },\n * })\n * );\n * ```\n * Then in your applications SSR render, pass this function to `InjectionContextProvider`:\n * ```js\n * <InjectionContextProvider value={injectIntoStream}>\n * ```\n */\n injectIntoStream: (callback: () => React.ReactNode) => void;\n} {\n let queuedInjections: Array<() => React.ReactNode> = [];\n\n async function renderInjectedHtml() {\n const injections = [...queuedInjections];\n queuedInjections = [];\n return renderToString(\n <>\n {injections.map((callback, i) => (\n <React.Fragment key={i}>{callback()}</React.Fragment>\n ))}\n </>\n );\n }\n\n let headInserted = false;\n let currentlyStreaming = false;\n let tailOfLastChunk = \"\";\n const textDecoder = new TextDecoder();\n const textEncoder = new TextEncoder();\n const HEAD_END = \"</head>\";\n // while the head has not fully been inserted, always move the last few\n // bytes of a chunk into the next chunk, so we can ensure that `</head>`\n // is not chopped into e.g. `</he` and `ad>`.\n const KEEP_BYTES = HEAD_END.length;\n\n const transformStream = new TransformStream({\n async transform(chunk, controller) {\n // While react is flushing chunks, we don't apply insertions\n if (currentlyStreaming) {\n controller.enqueue(chunk);\n return;\n }\n\n if (!headInserted) {\n const content =\n tailOfLastChunk + textDecoder.decode(chunk, { stream: true });\n const index = content.indexOf(HEAD_END);\n if (index !== -1) {\n const insertedHeadContent =\n content.slice(0, index) +\n (await renderInjectedHtml()) +\n content.slice(index);\n controller.enqueue(textEncoder.encode(insertedHeadContent));\n currentlyStreaming = true;\n setImmediate(() => {\n currentlyStreaming = false;\n });\n headInserted = true;\n } else {\n tailOfLastChunk = content.slice(-KEEP_BYTES);\n controller.enqueue(textEncoder.encode(content.slice(0, -KEEP_BYTES)));\n }\n } else {\n controller.enqueue(textEncoder.encode(await renderInjectedHtml()));\n controller.enqueue(chunk);\n currentlyStreaming = true;\n setImmediate(() => {\n currentlyStreaming = false;\n });\n }\n },\n });\n\n return {\n transformStream,\n injectIntoStream: (callback) => queuedInjections.push(callback),\n };\n}\n","/**\n * > This export is only available in streaming SSR Server environments\n *\n * Used to pipe a `ReadableStreamDefaultReader` to a `ServerResponse`.\n *\n * @example\n * ```js\n * const { injectIntoStream, transformStream } = createInjectionTransformStream();\n * const App = render({ assets, injectIntoStream });\n * const reactStream = await renderToReadableStream(App, { bootstrapModules }));\n * await pipeReaderToResponse(\n * reactStream.pipeThrough(transformStream).getReader(),\n * response\n * );\n * ```\n */\nexport async function pipeReaderToResponse(\n reader: ReadableStreamDefaultReader<any>,\n res: {\n write: (chunk: any) => void;\n end: () => void;\n destroy: (e: Error) => void;\n }\n) {\n try {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n res.end();\n return;\n } else {\n res.write(value);\n }\n }\n } catch (e: any) {\n res.destroy(e);\n }\n}\n"]}