Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(core): Fix subscriptions via fetch (#3244)

authored by

Phil Pluckthun and committed by
GitHub
65be20d5 13cbfd32

+256 -6
+5
.changeset/few-snails-burn.md
··· 1 + --- 2 + '@urql/core': patch 3 + --- 4 + 5 + Add missing `fetchSubscriptions` entry to `OperationContext`. The Client’s `fetchSubscriptions` now works properly and can be used to execute subscriptions as multipart/event-stream requests.
+5
.changeset/lazy-needles-give.md
··· 1 + --- 2 + '@urql/core': patch 3 + --- 4 + 5 + Fix `fetchSource` not working for subscriptions since `hasNext` isn’t necessarily set.
+1 -1
examples/with-defer-stream-directives/README.md
··· 17 17 This example contains: 18 18 19 19 - The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx) 20 - - A local `polka` server set up to test deferred and streamed results in [`server/`](server/). 20 + - A local `graphql-yoga` server set up to test deferred and streamed results in [`server/`](server/).
-1
examples/with-defer-stream-directives/package.json
··· 27 27 "devDependencies": { 28 28 "@apollo/server": "^4.4.1", 29 29 "@vitejs/plugin-react": "^3.1.0", 30 - "graphql-helix": "^1.13.0", 31 30 "graphql-yoga": "^3.7.1", 32 31 "npm-run-all": "^4.1.5", 33 32 "vite": "^4.2.0"
+25
examples/with-subscriptions-via-fetch/README.md
··· 1 + # With Subscriptions via Fetch 2 + 3 + This example shows `urql` in use with subscriptions running via a plain `fetch` 4 + HTTP request to GraphQL Yoga. This uses the [GraphQL Server-Sent 5 + Events](https://the-guild.dev/blog/graphql-over-sse) protocol, which means that 6 + the request streams in more results via a single HTTP response. 7 + 8 + This example also includes Graphcache ["Cache 9 + Updates"](https://formidable.com/open-source/urql/docs/graphcache/cache-updates/) 10 + to update a list with incoming items from the subscriptions. 11 + 12 + To run this example install dependencies and run the `start` script: 13 + 14 + ```sh 15 + yarn install 16 + yarn run start 17 + # or 18 + npm install 19 + npm run start 20 + ``` 21 + 22 + This example contains: 23 + 24 + - The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx) 25 + - A local `graphql-yoga` server set up to test subscriptions in [`server/`](server/).
+27
examples/with-subscriptions-via-fetch/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>with-defer-stream-directives</title> 7 + <style> 8 + body { 9 + margin: 0; 10 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 11 + 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 12 + 'Helvetica Neue', sans-serif; 13 + -webkit-font-smoothing: antialiased; 14 + -moz-osx-font-smoothing: grayscale; 15 + } 16 + 17 + code { 18 + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 19 + monospace; 20 + } 21 + </style> 22 + </head> 23 + <body> 24 + <div id="root"></div> 25 + <script type="module" src="/src/index.jsx"></script> 26 + </body> 27 + </html>
+24
examples/with-subscriptions-via-fetch/package.json
··· 1 + { 2 + "name": "with-subscriptions-via-fetch", 3 + "version": "0.0.0", 4 + "private": true, 5 + "scripts": { 6 + "server": "node server/graphql-yoga.js", 7 + "client": "vite", 8 + "start": "run-p client server" 9 + }, 10 + "dependencies": { 11 + "@urql/core": "^4.0.9", 12 + "@urql/exchange-graphcache": "^6.1.1", 13 + "graphql": "^16.6.0", 14 + "react": "^18.2.0", 15 + "react-dom": "^18.2.0", 16 + "urql": "^4.0.3" 17 + }, 18 + "devDependencies": { 19 + "@vitejs/plugin-react": "^3.1.0", 20 + "graphql-yoga": "^3.7.1", 21 + "npm-run-all": "^4.1.5", 22 + "vite": "^4.2.0" 23 + } 24 + }
+7
examples/with-subscriptions-via-fetch/server/graphql-yoga.js
··· 1 + const { createYoga } = require('graphql-yoga'); 2 + const { createServer } = require('http'); 3 + const { schema } = require('./schema'); 4 + 5 + const yoga = createYoga({ schema }); 6 + const server = createServer(yoga); 7 + server.listen(3004);
+48
examples/with-subscriptions-via-fetch/server/schema.js
··· 1 + const { 2 + GraphQLList, 3 + GraphQLObjectType, 4 + GraphQLSchema, 5 + GraphQLString, 6 + } = require('graphql'); 7 + 8 + const Alphabet = new GraphQLObjectType({ 9 + name: 'Alphabet', 10 + fields: { 11 + char: { 12 + type: GraphQLString, 13 + }, 14 + }, 15 + }); 16 + 17 + const schema = new GraphQLSchema({ 18 + query: new GraphQLObjectType({ 19 + name: 'Query', 20 + fields: () => ({ 21 + list: { 22 + type: new GraphQLList(Alphabet), 23 + resolve() { 24 + return [{ char: 'Where are my letters?' }]; 25 + }, 26 + }, 27 + }), 28 + }), 29 + subscription: new GraphQLObjectType({ 30 + name: 'Subscription', 31 + fields: () => ({ 32 + alphabet: { 33 + type: Alphabet, 34 + resolve(root) { 35 + return root; 36 + }, 37 + subscribe: async function* () { 38 + for (let letter = 65; letter <= 90; letter++) { 39 + await new Promise(resolve => setTimeout(resolve, 500)); 40 + yield { char: String.fromCharCode(letter) }; 41 + } 42 + }, 43 + }, 44 + }), 45 + }), 46 + }); 47 + 48 + module.exports = { schema };
+37
examples/with-subscriptions-via-fetch/src/App.jsx
··· 1 + import React from 'react'; 2 + import { Client, Provider, fetchExchange } from 'urql'; 3 + 4 + import { cacheExchange } from '@urql/exchange-graphcache'; 5 + 6 + import Songs from './Songs'; 7 + 8 + const cache = cacheExchange({ 9 + keys: { 10 + Alphabet: data => data.char, 11 + }, 12 + updates: { 13 + Subscription: { 14 + alphabet(parent, _args, cache) { 15 + const list = cache.resolve('Query', 'list') || []; 16 + list.push(parent.alphabet); 17 + cache.link('Query', 'list', list); 18 + }, 19 + }, 20 + }, 21 + }); 22 + 23 + const client = new Client({ 24 + url: 'http://localhost:3004/graphql', 25 + fetchSubscriptions: true, 26 + exchanges: [cache, fetchExchange], 27 + }); 28 + 29 + function App() { 30 + return ( 31 + <Provider value={client}> 32 + <Songs /> 33 + </Provider> 34 + ); 35 + } 36 + 37 + export default App;
+59
examples/with-subscriptions-via-fetch/src/Songs.jsx
··· 1 + import React from 'react'; 2 + import { gql, useQuery, useSubscription } from 'urql'; 3 + 4 + const LIST_QUERY = gql` 5 + query List_Query { 6 + list { 7 + char 8 + } 9 + } 10 + `; 11 + 12 + const SONG_SUBSCRIPTION = gql` 13 + subscription App_Subscription { 14 + alphabet { 15 + char 16 + } 17 + } 18 + `; 19 + 20 + const ListQuery = () => { 21 + const [listResult] = useQuery({ 22 + query: LIST_QUERY, 23 + }); 24 + return ( 25 + <div> 26 + <h3>List</h3> 27 + {listResult?.data?.list.map(i => ( 28 + <div key={i.char}>{i.char}</div> 29 + ))} 30 + </div> 31 + ); 32 + }; 33 + 34 + const SongSubscription = () => { 35 + const [songsResult] = useSubscription( 36 + { query: SONG_SUBSCRIPTION }, 37 + (prev = [], data) => [...prev, data.alphabet] 38 + ); 39 + 40 + return ( 41 + <div> 42 + <h3>Song</h3> 43 + {songsResult?.data?.map(i => ( 44 + <div key={i.char}>{i.char}</div> 45 + ))} 46 + </div> 47 + ); 48 + }; 49 + 50 + const LocationsList = () => { 51 + return ( 52 + <> 53 + <ListQuery /> 54 + <SongSubscription /> 55 + </> 56 + ); 57 + }; 58 + 59 + export default LocationsList;
+6
examples/with-subscriptions-via-fetch/src/index.jsx
··· 1 + import React from 'react'; 2 + import { createRoot } from 'react-dom/client'; 3 + 4 + import App from './App'; 5 + 6 + createRoot(document.getElementById('root')).render(<App />);
+7
examples/with-subscriptions-via-fetch/vite.config.js
··· 1 + import { defineConfig } from 'vite'; 2 + import react from '@vitejs/plugin-react'; 3 + 4 + // https://vitejs.dev/config/ 5 + export default defineConfig({ 6 + plugins: [react()], 7 + });
+1
packages/core/src/client.ts
··· 568 568 569 569 const baseOpts = { 570 570 url: opts.url, 571 + fetchSubscriptions: opts.fetchSubscriptions, 571 572 fetchOptions: opts.fetchOptions, 572 573 fetch: opts.fetch, 573 574 preferGetMethod: !!opts.preferGetMethod,
+4 -4
packages/core/src/internal/fetchSource.ts
··· 64 64 } catch (error) { 65 65 if (!payload) throw error; 66 66 } 67 - if (payload && !payload.hasNext) break; 67 + if (payload && payload.hasNext === false) break; 68 68 } 69 69 } 70 - if (payload && payload.hasNext) { 70 + if (payload && payload.hasNext !== false) { 71 71 yield { hasNext: false }; 72 72 } 73 73 } ··· 95 95 } catch (error) { 96 96 if (!payload) throw error; 97 97 } 98 - if (payload && !payload.hasNext) break; 98 + if (payload && payload.hasNext === false) break; 99 99 } 100 - if (payload && payload.hasNext) { 100 + if (payload && payload.hasNext !== false) { 101 101 yield { hasNext: false }; 102 102 } 103 103 }