diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index e4ac4bfde8..1ed14f5360 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -12,14 +12,15 @@ "debug": "node --inspect-brk server" }, "dependencies": { + "@apollo/client": "^3.10.4", "@tanstack/react-router": "^1.34.3", - "@tanstack/start": "^1.34.3", "@tanstack/router-devtools": "^1.34.4", "@tanstack/router-vite-plugin": "^1.34.1", - "redaxios": "^0.5.0", + "@tanstack/start": "^1.34.3", "get-port": "^7.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "redaxios": "^0.5.0", "superjson": "^2.2.1" }, "devDependencies": { diff --git a/examples/react/basic-ssr-streaming-file-based/src/apollo.ts b/examples/react/basic-ssr-streaming-file-based/src/apollo.ts new file mode 100644 index 0000000000..554cb879b0 --- /dev/null +++ b/examples/react/basic-ssr-streaming-file-based/src/apollo.ts @@ -0,0 +1,9 @@ +import { ApolloClient, InMemoryCache } from '@apollo/client' + +export function makeClient() { + const client = new ApolloClient({ + uri: 'https://flyby-router-demo.herokuapp.com/', + cache: new InMemoryCache(), + }) + return client +} diff --git a/examples/react/basic-ssr-streaming-file-based/src/entry-server.tsx b/examples/react/basic-ssr-streaming-file-based/src/entry-server.tsx index 95c6d85774..d9d8e4bec7 100644 --- a/examples/react/basic-ssr-streaming-file-based/src/entry-server.tsx +++ b/examples/react/basic-ssr-streaming-file-based/src/entry-server.tsx @@ -30,6 +30,8 @@ export async function render(opts: { router.update({ history: memoryHistory, context: { + // is this the official way to do this? + ...router.options.context, head: opts.head, }, }) diff --git a/examples/react/basic-ssr-streaming-file-based/src/router.tsx b/examples/react/basic-ssr-streaming-file-based/src/router.tsx index 07c33c6d9f..36e1e6dcb6 100644 --- a/examples/react/basic-ssr-streaming-file-based/src/router.tsx +++ b/examples/react/basic-ssr-streaming-file-based/src/router.tsx @@ -1,15 +1,28 @@ import { createRouter as createReactRouter } from '@tanstack/react-router' -import { routeTree } from './routeTree.gen' import SuperJSON from 'superjson' +import { ApolloProvider, createQueryPreloader } from '@apollo/client' +import { routeTree } from './routeTree.gen' +import { makeClient } from './apollo' export function createRouter() { + const apolloClient = makeClient() return createReactRouter({ routeTree, context: { head: '', + preloadQuery: createQueryPreloader(apolloClient), + apolloClient, + }, + Wrap({ children }) { + return {children} }, defaultPreload: 'intent', + // Maybe this is already possible if we provided some kind of + // `transformer: apolloTransformer(client)` + // here. + // Can a transformer return Promises? + // Maybe even streams or async iterables? transformer: SuperJSON, }) } diff --git a/examples/react/basic-ssr-streaming-file-based/src/routerContext.tsx b/examples/react/basic-ssr-streaming-file-based/src/routerContext.tsx index ee92aa087e..4b7fbb92f7 100644 --- a/examples/react/basic-ssr-streaming-file-based/src/routerContext.tsx +++ b/examples/react/basic-ssr-streaming-file-based/src/routerContext.tsx @@ -1,3 +1,9 @@ +import type { ApolloClient, PreloadQueryFunction } from '@apollo/client' export type RouterContext = { head: string + preloadQuery: PreloadQueryFunction + // userland code doesn't need this, but maybe it would + // be good to make `context` available to the deserialization + // process, so I'm already putting this here + apolloClient: ApolloClient } diff --git a/examples/react/basic-ssr-streaming-file-based/src/routes/posts.tsx b/examples/react/basic-ssr-streaming-file-based/src/routes/posts.tsx index 2a60a7da09..128f0453fc 100644 --- a/examples/react/basic-ssr-streaming-file-based/src/routes/posts.tsx +++ b/examples/react/basic-ssr-streaming-file-based/src/routes/posts.tsx @@ -1,5 +1,7 @@ import * as React from 'react' -import { createFileRoute, Link, Outlet } from '@tanstack/react-router' +import { Link, Outlet, createFileRoute } from '@tanstack/react-router' +import { gql, useReadQuery } from '@apollo/client' +import type { TypedDocumentNode } from '@apollo/client' export type PostType = { id: string @@ -7,41 +9,75 @@ export type PostType = { body: string } +const GET_LOCATIONS: TypedDocumentNode<{ + locations: Array<{ + id: string + name: string + description: string + photo: string + }> +}> = gql` + query GetLocations { + locations { + id + name + description + photo + } + } +` + export const Route = createFileRoute('/posts')({ - loader: async () => { - console.log('Fetching posts...') - await new Promise((r) => - setTimeout(r, 300 + Math.round(Math.random() * 300)), - ) - return fetch('https://jsonplaceholder.typicode.com/posts') - .then((d) => d.json() as Promise) - .then((d) => d.slice(0, 10)) + loader: async ({ context: { preloadQuery } }) => { + console.log('Fetching locations...') + return { + /** + * This creates a `QueryRef` object. + * This object is not serializable by default, + * so we'd love to be able to hook into the + * serialization process to provide our own serialization. + * + * The serialized value will contain static values and a Promise, + * although an AsyncIterator or some kind of Stream would be + * preferrable. + */ + locationsRef: preloadQuery(GET_LOCATIONS), + } }, component: PostsComponent, }) function PostsComponent() { - const posts = Route.useLoaderData() + /** + * `locationsRef` needs to be deserialized here. + * But it doesn't only need to be deserialized: + * The deserialization also has a side effect. + * + * The QueryRef needs to be registered with the ApolloClient. + * + */ + const { locationsRef } = Route.useLoaderData() + + const { data } = useReadQuery(locationsRef) return (
    - {posts?.map((post) => { - return ( -
  • - -
    {post.title.substring(0, 20)}
    - -
  • - ) - })} + {data.locations.map(({ id, name, description, photo }) => ( +
    +

    {name}

    + location-reference +
    + About this location: +

    {description}

    +
    +
    + ))}
  • =6.9.0'} @@ -4118,6 +4163,14 @@ packages: fast-json-stringify: 5.8.0 dev: false + /@graphql-typed-document-node/core@3.2.0(graphql@16.8.1): + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + graphql: 16.8.1 + dev: false + /@hono/node-server@1.4.1: resolution: {integrity: sha512-7jB8iMs6T2FhREs4Ugk+7rzn7d5aC6wEX3FAy67ZafzcQqqBVggcLkFPCMauaFJJyjc+bFvMOdFxJXKYsBM6MQ==} engines: {node: '>=18.14.1'} @@ -6419,6 +6472,41 @@ packages: '@xtuc/long': 4.2.2 dev: false + /@wry/caches@1.0.1: + resolution: {integrity: sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==} + engines: {node: '>=8'} + dependencies: + tslib: 2.6.2 + dev: false + + /@wry/context@0.7.4: + resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==} + engines: {node: '>=8'} + dependencies: + tslib: 2.6.2 + dev: false + + /@wry/equality@0.5.7: + resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==} + engines: {node: '>=8'} + dependencies: + tslib: 2.6.2 + dev: false + + /@wry/trie@0.4.3: + resolution: {integrity: sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==} + engines: {node: '>=8'} + dependencies: + tslib: 2.6.2 + dev: false + + /@wry/trie@0.5.0: + resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==} + engines: {node: '>=8'} + dependencies: + tslib: 2.6.2 + dev: false + /@xtuc/ieee754@1.2.0: resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} dev: false @@ -9280,6 +9368,21 @@ packages: /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + /graphql-tag@2.12.6(graphql@16.8.1): + resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + dependencies: + graphql: 16.8.1 + tslib: 2.6.2 + dev: false + + /graphql@16.8.1: + resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + dev: false + /gzip-size@7.0.0: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -9350,6 +9453,12 @@ packages: hasBin: true dev: true + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + /homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -11071,6 +11180,15 @@ packages: yargs-parser: 21.1.1 dev: false + /optimism@0.18.0: + resolution: {integrity: sha512-tGn8+REwLRNFnb9WmcY5IfpOqeX2kpaYJ1s6Ae3mn12AeydLkR3j+jSCmVQFoXqU8D41PAJ1RG1rCRNWmNZVmQ==} + dependencies: + '@wry/caches': 1.0.1 + '@wry/context': 0.7.4 + '@wry/trie': 0.4.3 + tslib: 2.6.2 + dev: false + /optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -11459,7 +11577,6 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} @@ -11582,7 +11699,6 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: true /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -11880,6 +11996,21 @@ packages: jsesc: 0.5.0 dev: true + /rehackt@0.1.0(@types/react@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==} + peerDependencies: + '@types/react': '*' + react: '*' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + dependencies: + '@types/react': 18.3.1 + react: 18.3.1 + dev: false + /remove-accents@0.4.2: resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} dev: false @@ -11945,6 +12076,11 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /response-iterator@0.2.6: + resolution: {integrity: sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==} + engines: {node: '>=0.8'} + dev: false + /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -12623,6 +12759,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + dev: false + /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true @@ -12872,6 +13013,13 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-invariant@0.10.3: + resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} + engines: {node: '>=8'} + dependencies: + tslib: 2.6.2 + dev: false + /tsconfck@3.0.3(typescript@5.4.5): resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==} engines: {node: ^18 || >=20} @@ -13973,6 +14121,16 @@ packages: commander: 9.5.0 dev: true + /zen-observable-ts@1.2.5: + resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==} + dependencies: + zen-observable: 0.8.15 + dev: false + + /zen-observable@0.8.15: + resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} + dev: false + /zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'}