Repository: urql-graphql/urql
Branch: main
Commit: 71f049c6abbb
Files: 671
Total size: 2.5 MB
Directory structure:
gitextract_87_w7_87/
├── .changeset/
│ ├── README.md
│ ├── config.json
│ ├── late-boats-listen.md
│ └── shiny-pets-give.md
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE/
│ │ ├── RFC.md
│ │ ├── bug_report.yaml
│ │ └── config.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── actions/
│ │ ├── discord-message/
│ │ │ ├── action.mjs
│ │ │ └── action.yml
│ │ └── pnpm-run/
│ │ ├── action.mjs
│ │ └── action.yml
│ └── workflows/
│ ├── ci.yml
│ ├── mirror.yml
│ └── release.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│ ├── README.md
│ ├── advanced/
│ │ ├── README.md
│ │ ├── authentication.md
│ │ ├── authoring-exchanges.md
│ │ ├── auto-populate-mutations.md
│ │ ├── debugging.md
│ │ ├── persistence-and-uploads.md
│ │ ├── retry-operations.md
│ │ ├── server-side-rendering.md
│ │ ├── subscriptions.md
│ │ └── testing.md
│ ├── api/
│ │ ├── README.md
│ │ ├── auth-exchange.md
│ │ ├── core.md
│ │ ├── execute-exchange.md
│ │ ├── graphcache.md
│ │ ├── preact.md
│ │ ├── refocus-exchange.md
│ │ ├── request-policy-exchange.md
│ │ ├── retry-exchange.md
│ │ ├── svelte.md
│ │ ├── urql.md
│ │ └── vue.md
│ ├── architecture.md
│ ├── basics/
│ │ ├── README.md
│ │ ├── core.md
│ │ ├── document-caching.md
│ │ ├── errors.md
│ │ ├── react-preact.md
│ │ ├── solid-start.md
│ │ ├── solid.md
│ │ ├── svelte.md
│ │ ├── typescript-integration.md
│ │ ├── ui-patterns.md
│ │ └── vue.md
│ ├── comparison.md
│ ├── graphcache/
│ │ ├── README.md
│ │ ├── cache-updates.md
│ │ ├── errors.md
│ │ ├── local-directives.md
│ │ ├── local-resolvers.md
│ │ ├── normalized-caching.md
│ │ ├── offline.md
│ │ └── schema-awareness.md
│ └── showcase.md
├── examples/
│ ├── README.md
│ ├── pnpm-workspace.yaml
│ ├── with-apq/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── LocationsList.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-defer-stream-directives/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── server/
│ │ │ ├── apollo-server.js
│ │ │ ├── graphql-yoga.js
│ │ │ └── schema.js
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── Songs.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-graphcache-pagination/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── PaginatedNpmSearch.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-graphcache-updates/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── client.js
│ │ │ ├── index.jsx
│ │ │ └── pages/
│ │ │ ├── Links.jsx
│ │ │ └── LoginForm.jsx
│ │ └── vite.config.js
│ ├── with-infinite-pagination/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── SearchResults.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-multipart/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── FileUpload.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-next/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── layout.tsx
│ │ │ ├── non-rsc/
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── next-env.d.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── with-pagination/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── PaginatedNpmSearch.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-react/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── PokemonList.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-react-native/
│ │ ├── App.js
│ │ ├── README.md
│ │ ├── app.json
│ │ ├── index.js
│ │ ├── package.json
│ │ └── src/
│ │ └── screens/
│ │ └── PokemonList.js
│ ├── with-refresh-auth/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── authStore.js
│ │ │ ├── client.js
│ │ │ ├── index.jsx
│ │ │ └── pages/
│ │ │ ├── LoginForm.jsx
│ │ │ └── Profile.jsx
│ │ └── vite.config.js
│ ├── with-retry/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── Color.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-solid/
│ │ ├── .eslintrc.js
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── PokemonList.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-solid-start/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── app.config.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── app.tsx
│ │ │ ├── entry-client.tsx
│ │ │ ├── entry-server.tsx
│ │ │ └── routes/
│ │ │ └── index.tsx
│ │ └── tsconfig.json
│ ├── with-subscriptions-via-fetch/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── server/
│ │ │ ├── graphql-yoga.js
│ │ │ └── schema.js
│ │ ├── src/
│ │ │ ├── App.jsx
│ │ │ ├── Songs.jsx
│ │ │ └── index.jsx
│ │ └── vite.config.js
│ ├── with-svelte/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.svelte
│ │ │ ├── PokemonList.svelte
│ │ │ └── main.js
│ │ └── vite.config.mjs
│ └── with-vue3/
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── src/
│ │ ├── App.vue
│ │ ├── PokemonList.vue
│ │ └── main.js
│ └── vite.config.js
├── exchanges/
│ ├── auth/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── authExchange.test.ts
│ │ │ ├── authExchange.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── context/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── context.test.ts
│ │ │ ├── context.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── execute/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── execute.test.ts
│ │ │ ├── execute.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── graphcache/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── benchmarks/
│ │ │ ├── 10000Reads.html
│ │ │ ├── 10000ReadsComplex.html
│ │ │ ├── 10000Writes.html
│ │ │ ├── 10000WritesComplex.html
│ │ │ ├── 1000Reads.html
│ │ │ ├── 1000ReadsComplex.html
│ │ │ ├── 1000Writes.html
│ │ │ ├── 1000WritesComplex.html
│ │ │ ├── 100Reads.html
│ │ │ ├── 100ReadsComplex.html
│ │ │ ├── 100Writes.html
│ │ │ ├── 100WritesComplex.html
│ │ │ ├── 50000Reads.html
│ │ │ ├── 50000Writes.html
│ │ │ ├── 5000Reads.html
│ │ │ ├── 5000Writes.html
│ │ │ ├── 500Reads.html
│ │ │ ├── 500Writes.html
│ │ │ ├── addTodo.html
│ │ │ ├── benchmarks.js
│ │ │ ├── entities.js
│ │ │ ├── makeEntries.js
│ │ │ ├── operations.js
│ │ │ ├── package.json
│ │ │ ├── readMe.md
│ │ │ ├── updateTodo.html
│ │ │ └── urqlClient.js
│ │ ├── cypress/
│ │ │ ├── fixtures/
│ │ │ │ └── example.json
│ │ │ ├── plugins/
│ │ │ │ └── index.js
│ │ │ └── support/
│ │ │ ├── component-index.html
│ │ │ └── component.js
│ │ ├── cypress.config.js
│ │ ├── e2e-tests/
│ │ │ ├── query.spec.tsx
│ │ │ └── updates.spec.tsx
│ │ ├── help.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── ast/
│ │ │ │ ├── graphql.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── node.ts
│ │ │ │ ├── schema.ts
│ │ │ │ ├── schemaPredicates.test.ts
│ │ │ │ ├── schemaPredicates.ts
│ │ │ │ ├── traversal.test.ts
│ │ │ │ ├── traversal.ts
│ │ │ │ ├── variables.test.ts
│ │ │ │ └── variables.ts
│ │ │ ├── cacheExchange-types.test.ts
│ │ │ ├── cacheExchange.test.ts
│ │ │ ├── cacheExchange.ts
│ │ │ ├── default-storage/
│ │ │ │ └── index.ts
│ │ │ ├── extras/
│ │ │ │ ├── index.ts
│ │ │ │ ├── relayPagination.test.ts
│ │ │ │ ├── relayPagination.ts
│ │ │ │ ├── simplePagination.test.ts
│ │ │ │ └── simplePagination.ts
│ │ │ ├── helpers/
│ │ │ │ ├── help.ts
│ │ │ │ └── operation.ts
│ │ │ ├── index.ts
│ │ │ ├── offlineExchange.test.ts
│ │ │ ├── offlineExchange.ts
│ │ │ ├── operations/
│ │ │ │ ├── invalidate.ts
│ │ │ │ ├── query.test.ts
│ │ │ │ ├── query.ts
│ │ │ │ ├── shared.test.ts
│ │ │ │ ├── shared.ts
│ │ │ │ ├── write.test.ts
│ │ │ │ └── write.ts
│ │ │ ├── store/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── store.test.ts.snap
│ │ │ │ ├── data.test.ts
│ │ │ │ ├── data.ts
│ │ │ │ ├── keys.ts
│ │ │ │ ├── store.test.ts
│ │ │ │ └── store.ts
│ │ │ ├── test-utils/
│ │ │ │ ├── altered_root_schema.json
│ │ │ │ ├── examples-1.test.ts
│ │ │ │ ├── examples-2.test.ts
│ │ │ │ ├── examples-3.test.ts
│ │ │ │ ├── relayPagination_schema.json
│ │ │ │ ├── simple_schema.json
│ │ │ │ ├── suite.test.ts
│ │ │ │ └── utils.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── persisted/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── persistedExchange.test.ts
│ │ │ ├── persistedExchange.ts
│ │ │ ├── sha256.ts
│ │ │ └── test-utils.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── populate/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── helpers/
│ │ │ │ ├── help.ts
│ │ │ │ ├── node.ts
│ │ │ │ └── traverse.ts
│ │ │ ├── index.ts
│ │ │ ├── populateExchange.test.ts
│ │ │ └── populateExchange.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── refocus/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── refocusExchange.test.ts
│ │ │ └── refocusExchange.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── request-policy/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── requestPolicyExchange.test.ts
│ │ │ └── requestPolicyExchange.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── retry/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── retryExchange.test.ts
│ │ │ └── retryExchange.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ └── throw-on-error/
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jsr.json
│ ├── package.json
│ ├── src/
│ │ ├── index.ts
│ │ ├── throwOnErrorExchange.test.ts
│ │ └── throwOnErrorExchange.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── package.json
├── packages/
│ ├── core/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── __snapshots__/
│ │ │ │ └── client.test.ts.snap
│ │ │ ├── client.test.ts
│ │ │ ├── client.ts
│ │ │ ├── exchanges/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ ├── fetch.test.ts.snap
│ │ │ │ │ └── subscription.test.ts.snap
│ │ │ │ ├── cache.test.ts
│ │ │ │ ├── cache.ts
│ │ │ │ ├── compose.test.ts
│ │ │ │ ├── compose.ts
│ │ │ │ ├── debug.test.ts
│ │ │ │ ├── debug.ts
│ │ │ │ ├── fallback.test.ts
│ │ │ │ ├── fallback.ts
│ │ │ │ ├── fetch.test.ts
│ │ │ │ ├── fetch.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── map.test.ts
│ │ │ │ ├── map.ts
│ │ │ │ ├── ssr.test.ts
│ │ │ │ ├── ssr.ts
│ │ │ │ ├── subscription.test.ts
│ │ │ │ └── subscription.ts
│ │ │ ├── gql.test.ts
│ │ │ ├── gql.ts
│ │ │ ├── index.ts
│ │ │ ├── internal/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── fetchSource.test.ts.snap
│ │ │ │ ├── fetchOptions.test.ts
│ │ │ │ ├── fetchOptions.ts
│ │ │ │ ├── fetchSource.test.ts
│ │ │ │ ├── fetchSource.ts
│ │ │ │ └── index.ts
│ │ │ ├── test-utils/
│ │ │ │ ├── index.ts
│ │ │ │ └── samples.ts
│ │ │ ├── types.ts
│ │ │ └── utils/
│ │ │ ├── __snapshots__/
│ │ │ │ └── error.test.ts.snap
│ │ │ ├── collectTypenames.test.ts
│ │ │ ├── collectTypenames.ts
│ │ │ ├── error.test.ts
│ │ │ ├── error.ts
│ │ │ ├── formatDocument.test.ts
│ │ │ ├── formatDocument.ts
│ │ │ ├── graphql.ts
│ │ │ ├── hash.test.ts
│ │ │ ├── hash.ts
│ │ │ ├── index.ts
│ │ │ ├── operation.ts
│ │ │ ├── request.test.ts
│ │ │ ├── request.ts
│ │ │ ├── result.test.ts
│ │ │ ├── result.ts
│ │ │ ├── streamUtils.ts
│ │ │ ├── variables.test.ts
│ │ │ └── variables.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── introspection/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── getIntrospectedSchema.ts
│ │ │ ├── index.ts
│ │ │ └── minifyIntrospectionQuery.ts
│ │ └── tsconfig.json
│ ├── next-urql/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── DataHydrationContext.ts
│ │ │ ├── Provider.ts
│ │ │ ├── htmlescape.ts
│ │ │ ├── index.ts
│ │ │ ├── rsc.ts
│ │ │ ├── useQuery.ts
│ │ │ └── useUrqlValue.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── preact-urql/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── Mutation.test.tsx
│ │ │ │ ├── Mutation.ts
│ │ │ │ ├── Query.test.tsx
│ │ │ │ ├── Query.ts
│ │ │ │ ├── Subscription.test.tsx
│ │ │ │ ├── Subscription.ts
│ │ │ │ └── index.ts
│ │ │ ├── context.ts
│ │ │ ├── hooks/
│ │ │ │ ├── constants.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── useMutation.test.tsx
│ │ │ │ ├── useMutation.ts
│ │ │ │ ├── useQuery.test.tsx
│ │ │ │ ├── useQuery.ts
│ │ │ │ ├── useRequest.ts
│ │ │ │ ├── useSource.ts
│ │ │ │ ├── useSubscription.test.tsx
│ │ │ │ └── useSubscription.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── react-urql/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── core/
│ │ │ ├── index.d.ts
│ │ │ ├── index.esm.js
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── cypress/
│ │ │ ├── fixtures/
│ │ │ │ └── example.json
│ │ │ └── support/
│ │ │ ├── component-index.html
│ │ │ └── component.js
│ │ ├── cypress.config.js
│ │ ├── e2e-tests/
│ │ │ └── useQuery.spec.tsx
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── Mutation.test.tsx
│ │ │ │ ├── Mutation.ts
│ │ │ │ ├── Query.test.tsx
│ │ │ │ ├── Query.ts
│ │ │ │ ├── Subscription.ts
│ │ │ │ └── index.ts
│ │ │ ├── context.ts
│ │ │ ├── hooks/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ ├── useMutation.test.tsx.snap
│ │ │ │ │ ├── useQuery.test.tsx.snap
│ │ │ │ │ └── useSubscription.test.tsx.snap
│ │ │ │ ├── cache.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── state.ts
│ │ │ │ ├── useMutation.test.tsx
│ │ │ │ ├── useMutation.ts
│ │ │ │ ├── useQuery.spec.ts
│ │ │ │ ├── useQuery.test.tsx
│ │ │ │ ├── useQuery.ts
│ │ │ │ ├── useRequest.test.ts
│ │ │ │ ├── useRequest.ts
│ │ │ │ ├── useSubscription.test.tsx
│ │ │ │ └── useSubscription.ts
│ │ │ ├── index.ts
│ │ │ └── test-utils/
│ │ │ └── ssr.test.tsx
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── site/
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── plugins/
│ │ │ ├── assets-fix/
│ │ │ │ └── node.api.js
│ │ │ ├── monorepo-fix/
│ │ │ │ └── node.api.js
│ │ │ ├── preact/
│ │ │ │ └── node.api.js
│ │ │ └── react-router/
│ │ │ └── browser.api.js
│ │ ├── public/
│ │ │ ├── browserconfig.xml
│ │ │ └── site.webmanifest
│ │ ├── src/
│ │ │ ├── analytics.js
│ │ │ ├── app.js
│ │ │ ├── assets/
│ │ │ │ ├── anchor.js
│ │ │ │ └── chevron.js
│ │ │ ├── components/
│ │ │ │ ├── body-copy.js
│ │ │ │ ├── button.js
│ │ │ │ ├── footer.js
│ │ │ │ ├── header.js
│ │ │ │ ├── link.js
│ │ │ │ ├── loading.js
│ │ │ │ ├── markdown.js
│ │ │ │ ├── mdx.js
│ │ │ │ ├── navigation.js
│ │ │ │ ├── panel.js
│ │ │ │ ├── scroll-to-top.js
│ │ │ │ ├── secondary-title.js
│ │ │ │ ├── section-title.js
│ │ │ │ ├── sidebar-search-input.js
│ │ │ │ ├── sidebar.js
│ │ │ │ └── wrapper.js
│ │ │ ├── constants.js
│ │ │ ├── google-analytics.js
│ │ │ ├── google-tag-manager.js
│ │ │ ├── html.js
│ │ │ ├── index.js
│ │ │ ├── screens/
│ │ │ │ ├── 404/
│ │ │ │ │ ├── 404.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── docs/
│ │ │ │ │ ├── article.js
│ │ │ │ │ ├── header.js
│ │ │ │ │ └── index.js
│ │ │ │ └── home/
│ │ │ │ ├── _content.js
│ │ │ │ ├── features.js
│ │ │ │ ├── get-started.js
│ │ │ │ ├── hero.js
│ │ │ │ ├── index.js
│ │ │ │ └── more-oss.js
│ │ │ └── styles/
│ │ │ ├── global.js
│ │ │ └── theme.js
│ │ ├── static.config.js
│ │ └── vercel.json
│ ├── solid-start-urql/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── context.test.tsx
│ │ │ ├── context.ts
│ │ │ ├── createMutation.test.ts
│ │ │ ├── createMutation.ts
│ │ │ ├── createQuery.test.tsx
│ │ │ ├── createQuery.ts
│ │ │ ├── createSubscription.test.ts
│ │ │ ├── createSubscription.ts
│ │ │ ├── index.ts
│ │ │ └── utils.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── solid-urql/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── context.ts
│ │ │ ├── createMutation.test.ts
│ │ │ ├── createMutation.ts
│ │ │ ├── createQuery.test.tsx
│ │ │ ├── createQuery.ts
│ │ │ ├── createSubscription.test.ts
│ │ │ ├── createSubscription.ts
│ │ │ ├── index.ts
│ │ │ ├── suspense.test.tsx
│ │ │ └── utils.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── storage-rn/
│ │ ├── CHANGELOG.md
│ │ ├── LICENCE
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── makeAsyncStorage.test.ts
│ │ │ └── makeAsyncStorage.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── svelte-urql/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── jsr.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── common.ts
│ │ │ ├── context.ts
│ │ │ ├── index.ts
│ │ │ ├── mutationStore.test.ts
│ │ │ ├── mutationStore.ts
│ │ │ ├── queryStore.test.ts
│ │ │ ├── queryStore.ts
│ │ │ ├── subscriptionStore.test.ts
│ │ │ └── subscriptionStore.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ └── vue-urql/
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jsr.json
│ ├── package.json
│ ├── src/
│ │ ├── index.ts
│ │ ├── useClient.test.ts
│ │ ├── useClient.ts
│ │ ├── useClientHandle.ts
│ │ ├── useMutation.test.ts
│ │ ├── useMutation.ts
│ │ ├── useQuery.test.ts
│ │ ├── useQuery.ts
│ │ ├── useSubscription.test.ts
│ │ ├── useSubscription.ts
│ │ └── utils.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── pnpm-workspace.yaml
├── scripts/
│ ├── actions/
│ │ ├── build-all.mjs
│ │ ├── lib/
│ │ │ ├── commands.mjs
│ │ │ ├── constants.mjs
│ │ │ ├── github.mjs
│ │ │ └── packages.mjs
│ │ └── pack-all.mjs
│ ├── babel/
│ │ ├── transform-debug-target.mjs
│ │ ├── transform-invariant-warning.mjs
│ │ └── transform-pipe.mjs
│ ├── changesets/
│ │ ├── changelog.js
│ │ ├── jsr.mjs
│ │ └── version.mjs
│ ├── eslint/
│ │ └── preset.js
│ ├── prepare/
│ │ ├── index.js
│ │ └── postinstall.js
│ ├── rollup/
│ │ ├── cleanup-plugin.mjs
│ │ ├── config.mjs
│ │ ├── plugins.mjs
│ │ └── settings.mjs
│ └── vitest/
│ └── setup.js
├── tsconfig.json
├── vercel.json
└── vitest.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .changeset/README.md
================================================
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md)
================================================
FILE: .changeset/config.json
================================================
{
"$schema": "https://unpkg.com/@changesets/config@0.3.0/schema.json",
"changelog": "../scripts/changesets/changelog.js",
"commit": false,
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "minor",
"snapshot": {
"prereleaseTemplate": "{tag}-{commit}",
"useCalculatedVersion": true
},
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true,
"updateInternalDependents": "out-of-range"
}
}
================================================
FILE: .changeset/late-boats-listen.md
================================================
---
'@urql/solid-start': minor
---
Fix SSR runtime failures caused by importing SolidStart's `action` API at module load time by reading `action` from `Provider` context instead.
================================================
FILE: .changeset/shiny-pets-give.md
================================================
---
'@urql/solid-start': patch
---
Fix `createSubscription` to use `@urql/solid-start` context instead of re-exporting the Solid-only implementation from `@urql/solid`.
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 2
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = 100
trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0
================================================
FILE: .gitattributes
================================================
* text=auto
================================================
FILE: .github/CODEOWNERS
================================================
/.github/ @urql-graphql/core
/.changeset/config.json @urql-graphql/core
/scripts/actions/* @urql-graphql/core
/scripts/prepare/* @urql-graphql/core
/scripts/rollup/* @urql-graphql/core
/scripts/changesets/* @urql-graphql/core
================================================
FILE: .github/ISSUE_TEMPLATE/RFC.md
================================================
---
name: 'RFC'
about: Propose an enhancement / feature and start a discussion
title: 'RFC: Your Proposal'
labels: "future \U0001F52E"
---
## Summary
## Proposed Solution
## Requirements
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
name: "\U0001F41E Bug report"
description: Report an issue with urql
labels: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: bug-description
attributes:
label: Describe the bug
description: Please describe your bug clearly and concisely.
placeholder: Bug description
validations:
required: true
- type: input
id: reproduction
attributes:
label: Reproduction
description: Please provide a link to a reproduction. Templates can be found in the [examples folder](https://github.com/urql-graphql/urql/tree/main/examples). A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required. If a report is vague (e.g. just a generic error message) and if no reproduction is provided the issue will be auto-closed.
placeholder: Reproduction
validations:
required: true
- type: textarea
id: urql-version
attributes:
label: Urql version
description: The versions of the relevant urql packages you are using
placeholder: urql v2.0.0
validations:
required: true
- type: checkboxes
id: checkboxes
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: I can confirm that this is a bug report, and not a feature request, RFC, question, or discussion, for which [GitHub Discussions](https://github.com/urql-graphql/urql/discussions) should be used
required: true
- label: Read the [docs](https://formidable.com/open-source/urql/docs/).
required: true
- label: Follow our [Code of Conduct](https://github.com/urql-graphql/urql/blob/main/CODE_OF_CONDUCT.md)
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
- name: Ask a question
url: https://github.com/urql-graphql/urql/discussions
about: Ask questions and discuss with other community members
- name: Join the Discord
url: https://urql.dev/discord
about: Chat with maintainers and other community members
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Summary
## Set of changes
================================================
FILE: .github/actions/discord-message/action.mjs
================================================
import * as core from '@actions/core';
import * as github from '@actions/github';
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL;
const octokit = github.getOctokit(GITHUB_TOKEN);
const formatBody = input => {
const titleRe = /(?:^|\n)#+[^\n]+/g;
const updatedDepsRe = /\n-\s*Updated dependencies[\s\S]+\n(\n\s+-[\s\S]+)*/gi;
const markdownLinkRe = /\[([^\]]+)\]\(([^\)]+)\)/g;
const creditRe = new RegExp(
`Submitted by (?:undefined|${markdownLinkRe.source})`,
'ig'
);
const repeatedNewlineRe = /(?:\n[ ]*)*(\n[ ]*)/g;
return input
.replace(titleRe, '')
.replace(updatedDepsRe, '')
.replace(creditRe, (_match, text, url) => {
if (!text || /@kitten|@JoviDeCroock/i.test(text)) return '';
return `Submitted by [${text}](${url})`;
})
.replace(markdownLinkRe, (_match, text, url) => `[${text}](<${url}>)`)
.replace(repeatedNewlineRe, (_match, text) => (text ? ` ${text}` : '\n'))
.trim();
};
async function getReleaseBody(name, version) {
const tag = `${name}@${version}`;
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
const result = await octokit.rest.repos.getReleaseByTag({ owner, repo, tag });
const release = result.status === 200 ? result.data : undefined;
if (!release || !release.body) return;
const title = `:package: [${tag}](<${release.html_url}>)`;
const body = formatBody(release.body);
if (!body) return;
return `${title}\n${body}`;
}
async function main() {
const inputPackages = core.getInput('publishedPackages');
let packages;
try {
packages = JSON.parse(inputPackages);
} catch (e) {
console.error('invalid JSON in publishedPackages input.');
return;
}
// Get releases
const releasePromises = packages.map(entry => {
return getReleaseBody(entry.name, entry.version);
});
const content = (await Promise.allSettled(releasePromises))
.map(x => x.status === 'fulfilled' && x.value)
.filter(Boolean)
.join('\n\n');
// Send message through a discord webhook or bot
const response = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ content }),
});
if (!response.ok) {
console.error(
'Something went wrong while sending the discord webhook.',
response.status
);
console.error(await response.text());
}
}
main().then().catch(console.error);
================================================
FILE: .github/actions/discord-message/action.yml
================================================
name: 'Send a discord message'
description: 'Send a discord message as a result of an urql publish.'
inputs:
publishedPackages:
description: >
A JSON array to present the published packages. The format is `[{"name": "@xx/xx", "version": "1.2.0"}, {"name": "@xx/xy", "version": "0.8.9"}]`
runs:
using: 'node20'
main: 'action.mjs'
================================================
FILE: .github/actions/pnpm-run/action.mjs
================================================
import { execa } from 'execa';
const run = execa('pnpm', ['run', process.env.INPUT_COMMAND], {
cwd: process.cwd(),
});
run.stdout.pipe(process.stdout);
run.stderr.pipe(process.stderr);
run
.then(result => process.exit(result.exitCode))
.catch(error => process.exit(error.exitCode || -1));
================================================
FILE: .github/actions/pnpm-run/action.yml
================================================
name: 'Run a pnpm command'
description: 'Locally run a forked pnpm command as an action.'
inputs:
command:
description: 'Command'
default: 'help'
runs:
using: 'node20'
main: 'action.mjs'
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
pull_request:
pull_request_review:
types: [submitted, edited]
branches: changeset-release/main
jobs:
check:
name: Checks
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
run_install: false
- name: Get pnpm store directory
id: pnpm-store
run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Use pnpm store
uses: actions/cache@v4
id: pnpm-cache
with:
path: |
~/.cache/Cypress
${{ steps.pnpm-store.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install Dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: TypeScript
run: pnpm run check
- name: Linting
run: pnpm run lint
- name: Unit Tests
run: pnpm run test
- name: Check for slow types
run: pnpm jsr:dryrun
react-e2e:
name: React E2E
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
run_install: false
- name: Get pnpm store directory
id: pnpm-store
run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Use pnpm store
uses: actions/cache@v4
id: pnpm-cache
with:
path: |
~/.cache/Cypress
${{ steps.pnpm-store.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install Dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Build
run: pnpm -F @urql/core build && pnpm -F urql build
- name: e2e tests 🧪
uses: cypress-io/github-action@v6
with:
install: false
command: pnpm cypress run --component
working-directory: packages/react-urql
graphcache-e2e:
name: Graphcache E2E
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
run_install: false
- name: Get pnpm store directory
id: pnpm-store
run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Use pnpm store
uses: actions/cache@v4
id: pnpm-cache
with:
path: |
~/.cache/Cypress
${{ steps.pnpm-store.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install Dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Build
run: pnpm -F "@urql/core" -F urql -F "@urql/exchange-execute" build
- name: e2e tests 🧪
uses: cypress-io/github-action@v6
with:
install: false
command: pnpm cypress run --component
working-directory: exchanges/graphcache
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
node: [0, 1, 2]
env:
NODE_TOTAL: 3
NODE_INDEX: ${{matrix.node}}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
run_install: false
- name: Get pnpm store directory
id: pnpm-store
run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Use pnpm store
uses: actions/cache@v4
id: pnpm-cache
with:
path: |
~/.cache/Cypress
${{ steps.pnpm-store.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install Dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Build
run: pnpm build
- name: Pack
uses: ./.github/actions/pnpm-run
with:
command: pack
================================================
FILE: .github/workflows/mirror.yml
================================================
# Mirrors to https://tangled.sh/@kitten.sh (knot.kitten.sh)
name: Mirror (Git Backup)
on:
push:
branches:
- main
jobs:
mirror:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Mirror
env:
MIRROR_SSH_KEY: ${{ secrets.MIRROR_SSH_KEY }}
GIT_SSH_COMMAND: 'ssh -o StrictHostKeyChecking=yes'
run: |
mkdir -p ~/.ssh
echo "$MIRROR_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H knot.kitten.sh >> ~/.ssh/known_hosts
git remote add mirror "git@knot.kitten.sh:kitten.sh/${GITHUB_REPOSITORY#*/}"
git push --mirror mirror
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: write
id-token: write
issues: write
repository-projects: write
deployments: write
packages: write
pull-requests: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: "https://registry.npmjs.org"
- name: Update npm
run: npm install -g npm@latest
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
run_install: false
- name: Get pnpm store directory
id: pnpm-store
run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Use pnpm store
uses: actions/cache@v4
id: pnpm-cache
with:
path: |
~/.cache/Cypress
${{ steps.pnpm-store.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install Dependencies
run: pnpm install
- name: PR or Publish
id: changesets
uses: changesets/action@v1.5.3
with:
version: pnpm changeset:version
publish: pnpm changeset:publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Notify discord
id: discord-msg
if: steps.changesets.outputs.published == 'true'
uses: ./.github/actions/discord-message
with:
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
- name: Publish Prerelease
if: steps.changesets.outputs.published != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git reset --hard origin/main
pnpm changeset version --no-git-tag --snapshot canary
pnpm changeset publish --no-git-tag --snapshot canary --tag canary
================================================
FILE: .gitignore
================================================
/.idea
/.vscode
**/node_modules
*.log
.rts2_cache*
.husky
dist/
build/
coverage/
package-lock.json
.DS_Store
.next
packages/*/LICENSE
exchanges/*/LICENSE
# TODO: Figure out how to remove these:
tmp/
dist/
examples/yarn.lock
examples/pnpm-lock.yaml
examples/package-lock.json
examples/*/public
examples/*/yarn.lock
examples/*/pnpm-lock.yaml
examples/*/package-lock.json
examples/*/ios/
examples/*/android/
examples/*/.watchmanconfig
examples/*/metro.config.js
examples/*/babel.config.js
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team:
- phil@0no.co
- grant.sander@formidable.com
- jovi@preact.dev
All complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: CONTRIBUTING.md
================================================
# Development
Thanks for contributing! We want to ensure that `urql` evolves and fulfills
its idea of extensibility and flexibility by seeing continuous improvements
and enhancements, no matter how small or big they might be.
If you're about to add a new exchange, please consider publishing it as
a separate package.
## How to contribute?
We follow fairly standard but lenient rules around pull requests and issues.
Please pick a title that describes your change briefly, optionally in the imperative
mood if possible.
If you have an idea for a feature or want to fix a bug, consider opening an issue
first. We're also happy to discuss and help you open a PR and get your changes
in!
- If you have a question, try [creating a GitHub Discussions thread.](https://github.com/urql-graphql/urql/discussions/new)
- If you think you've found a bug, [open a new issue.](https://github.com/urql-graphql/urql/issues/new/choose)
- or, if you found a bug you'd like to fix, [open a PR.](https://github.com/urql-graphql/urql/compare)
- If you'd like to propose a change [open an RFC issue.](https://github.com/urql-graphql/urql/issues/new?labels=future+%F0%9F%94%AE&template=RFC.md&title=RFC%3A+Your+Proposal) You can read more about the RFC process [below](#how-do-i-propose-changes).
### What are the issue conventions?
There are **no strict conventions**, but we do have two templates in place that will fit most
issues, since questions and other discussion start on GitHub Discussions. The bug template is fairly
standard and the rule of thumb is to try to explain **what you expected** and **what you got
instead.** Following this makes it very clear whether it's a known behavior, an unexpected issue,
or an undocumented quirk.
We do ask that issues _aren’t_ created for questions, or where a bug is likely to be either caused
by misusage or misconfiguration. In short, if you can’t provide a reproduction of the issue, then
it may be the case that you’ve got a question instead.
If you need a template for creating a reproduction, all of our examples can be opened in isolated
sandboxes or modified as you see fit: https://github.com/urql-graphql/urql/tree/main/examples
### How do I propose changes?
We follow an **RFC proposal process**. This allows anyone to propose a new feature or a change, and
allows us to communicate our current planned features or changes, so any technical discussion,
progress, or upcoming changes are always **documented transparently.** You can [find the RFC
template](https://github.com/urql-graphql/urql/issues/new/choose) in our issue creator.
All RFCs are added to the [RFC Lifecycle board.](https://github.com/urql-graphql/urql/projects/3)
This board tracks where an RFC stands and who's working on it until it's completed. Bugs and PRs may
end up on there too if no corresponding RFC exists or was necessary. RFCs are typically first added
to "In Discussion" until we believe they're ready to be worked on. This step may either be short,
skipped, or rather long, if no plan is in place for a change yet. So if you see a way to help,
please leave some suggestions.
### What are the PR conventions?
This also comes with **no strict conventions**. We only ask you to follow the PR template we have
in place more strictly here than the templates for issues, since it asks you to list a summary
(maybe even with a short explanation) and a list of technical changes.
If you're **resolving** an issue please don't forget to add `Resolve #123` to the description so that
it's automatically linked, so that there's no ambiguity and which issue is being addressed (if any)
You'll find that a comment by the "Changeset" bot may pop up. If you don't know what a **changeset**
is and why it's asking you to document your changes, read on at ["How do I document a change for the
changelog"](#how-do-i-document-a-change-for-the-changelog)
We also typically **name** our PRs with a slightly descriptive title, e.g. `(shortcode) - Title`,
where shortcode is either the name of a package, e.g. `(core)` and the title is an imperative mood
description, e.g. "Update X" or "Refactor Y."
## How do I set up the project?
Luckily it's not hard to get started. You can install dependencies
[using `pnpm`](https://pnpm.io/installation#using-corepack).
Please don't use `npm` or `yarn` to respect the lockfile.
```sh
pnpm install
```
There are multiple commands you can run in the root folder to test your changes:
```sh
# TypeScript checks:
pnpm run check
# Linting (prettier & eslint):
pnpm run lint
# Unit Tests (for all packages):
pnpm run test
# Builds (for all packages):
pnpm run build
```
You can find the main packages in `packages/*` and the addon exchanges in `exchanges/*`.
Each package also has its own scripts that are common and shared between all packages.
```sh
# Unit Tests for the current package:
pnpm run test
# Linting (prettier & eslint):
pnpm run lint
# Build the current package:
pnpm run build
# TypeScript checks for the current package:
pnpm run check
```
While you can run `build` globally in the interest of time it's advisable to only run it
on the packages you're working on. Note that TypeScript checks don't require any packages
to be built.
## How do I test my changes?
It's always good practice to run the tests when making changes. If you're unsure which packages
may be affected by your new tests or changes you may run `pnpm test` in the root of
the repository.
If your editor is not set up with type checks you may also want to run `pnpm run check` on your
changes.
Additionally you can head to any example in the `examples/` folder
and run them. There you'll also need to install their dependencies as they're isolated projects,
without a lockfile and without linking to packages in the monorepos.
All examples are started using the `package.json`'s `start` script.
## How do I lint my code?
We ensure consistency in `urql`'s codebase using `eslint` and `prettier`.
They are run on a `precommit` hook, so if something's off they'll try
to automatically fix up your code, or display an error.
If you have them set up in your editor, even better!
## How do I document a change for the changelog?
This project uses [changesets](https://github.com/atlassian/changesets). This means that for
every PR there must be documentation for what has been changed and which package is affected.
You can document a change by running `changeset`, which will ask you which packages
have changed and whether the change is major/minor/patch. It will then ask you to write
a change entry as markdown.
```sh
# In the root of the urql repository call:
pnpm changeset
```
This will create a new "changeset file" in the `.changeset` folder, which you should commit and
push, so that it's added to your PR.
This will eventually end up in the package's `CHANGELOG.md` file when we do a release.
You won't need to add a changeset if you're simply making "non-visible" changes to the docs or other
files that aren't published to the npm registry.
[Read more about adding a `changeset` here.](https://github.com/atlassian/changesets/blob/master/docs/adding-a-changeset.md#i-am-in-a-multi-package-repository-a-mono-repo)
## How do I release new versions of our packages?
Hold up, that's **automated**! Since we use `changeset` to document our changes, which determines what
goes into the changelog and what kind of version bump a change should make, you can also use the
tool to check what's currently posed to change after a release batch using: `pnpm changeset status`.
We have a [GitHub Actions workflow](./.github/workflow/release.yml) which is triggered whenever new
changes are merged. It will always open a **"Version Packages" PR** which is kept up-to-date. This PR
documents all changes that are made and will show in its description what all new changelogs are
going to contain for their new entries.
Once a "Version Packages" PR is approved by a contributor and merged, the action will automatically
take care of creating the release, publishing all updated packages to the npm registry, and creating
appropriate tags on GitHub too.
This process is automated, but the changelog should be checked for errors.
As to **when** to merge the automated PR and publish? Maybe not after every change. Typically there
are two release batches: hotfixes and release batches. We expect that a hotfix for a single package
should go out as quickly as possible if it negatively affects users. For **release batches**
however, it's common to assume that if one change is made to a package that more will follow in the
same week. So waiting for **a day or two** when other changes are expected will make sense to keep the
fatigue as low as possible for downstream maintainers.
## How do I upgrade all dependencies?
It may be a good idea to keep all dependencies on the `urql` repository **up-to-date** every now and
then. Typically we do this by running `pnpm update --interactive --latest` and checking one-by-one
which dependencies will need to be bumped. In case of any security issues it may make sense to
just run `pnpm update [package]`.
While this is rare with `pnpm`, upgrading some transitive dependencies may accidentally duplicate
them if two packages depend on different compatible version ranges. This can be fixed by running:
```sh
npx pnpm-deduplicate
pnpm install
```
It's common to then **create a PR** (with a changeset documenting the packages that need to reflect
new changes if any `dependencies` have changed) with the name of
"(chore) - Upgrade direct and transitive dependencies" or something similar.
## How do I add a new package?
First of all we need to know **where** to put the package.
- Exchanges should be added to `exchanges/` and the folder should be the plain
name of the exchange. Since the `package.json:name` is following the convention
of `@urql/exchange-*` the folder should just be without this conventional prefix.
- All other packages should be added to `packages/`. Typically all packages should
be named `@urql/*` and their folders should be named exactly this without the
prefix or `*-urql`. Optionally if the package will be named `*-urql` then the folder
can take on the same name.
When adding a new package, start by **copying** a `package.json` file from another project.
You may want to alter the following fields first:
- `name`
- `version` (either start at `0.1.0` or `1.0.0`)
- `description`
- `repository.directory`
- `keywords`
Make sure to also alter the `devDependencies`, `peerDependencies`, and `dependencies` to match
the new package's needs.
**The `main` and `module` fields follow a convention:**
All output bundles will always be output in the `./dist` folder by `rollup`, which is set up in
the `build` script. Their filenames are a "kebab case" (dash-cased) version of the `name` field with
an appropriate extension (`.esm.js` for `module` and `.cjs.js` for `main`).
If your entrypoint won't be at `src/index.ts` you may alter it. But the `types` field has to match
the same file relative to the `dist/types` folder, where `rollup` will output the TypeScript
declaration files.
When setting up your package make sure to create a `src/index.ts` file
(or any other file which you've pointed `package.json:source` to). Also don't forget to
copy over the `tsconfig.json` from another package (You won't need to change it).
The `scripts.prepare` task is set up to check your new `package.json` file for correctness. So in
case you get anything wrong, you'll get a short error when running `pnpm` after setting your new
project up. Just in case! 😄
Afterwards you can check whether everything is working correctly by running:
```sh
pnpm install
pnpm run check
```
At this point, **don't publish** the package or a prerelease yourself if you can avoid it. If you can't
or have already, we'll need to get the **rights** fixed by adding the package to the `@urql` scope.
Typically what we do is:
```sh
npm access grant read-write urql:developers [package]
```
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018–2020 Formidable,
Copyright (c) urql GraphQL Team and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
A highly customisable and versatile GraphQL client
## ✨ Features
- 📦 **One package** to get a working GraphQL client in React, Preact, Vue, Solid and Svelte
- ⚙️ Fully **customisable** behaviour [via "exchanges"](https://formidable.com/open-source/urql/docs/advanced/authoring-exchanges/)
- 🗂 Logical but simple default behaviour and document caching
- 🌱 Normalized caching via [`@urql/exchange-graphcache`](https://formidable.com/open-source/urql/docs/graphcache)
- 🔬 Easy debugging with the [`urql` devtools browser extensions](https://formidable.com/open-source/urql/docs/advanced/debugging/)
`urql` is a GraphQL client that exposes a set of helpers for several frameworks. It's built to be highly customisable and versatile so
you can take it from getting started with your first GraphQL project all the way to building complex apps and experimenting with GraphQL clients.
**📃 For more information, [check out the docs](https://formidable.com/open-source/urql/docs/).**
## 💙 [Sponsors](https://github.com/sponsors/urql-graphql)
## 🙌 Contributing
**The urql project was founded by [Formidable](https://formidable.com/) and is actively developed
by the urql GraphQL team.**
If you'd like to get involved, [check out our Contributor's guide.](https://github.com/urql-graphql/urql/blob/main/CONTRIBUTING.md)
## 📦 [Releases](https://github.com/urql-graphql/urql/releases)
All new releases and updates are listed on GitHub with full changelogs. Each package in this
repository further contains an independent `CHANGELOG.md` file with the historical changelog, for
instance, [here’s `@urql/core`’s
changelog](https://github.com/urql-graphql/urql/blob/main/packages/core/CHANGELOG.md).
If you’re upgrading to v4, [check out our migration guide, posted as an
issue.](https://github.com/urql-graphql/urql/issues/3114)
New releases are prepared using
[changesets](https://github.com/urql-graphql/urql/blob/main/CONTRIBUTING.md#how-do-i-document-a-change-for-the-changelog),
which are changelog entries added to each PR, and we have “Version Packages” PRs that once merged
will release new versions of `urql` packages. You can use `@canary` releases from `npm` if you’d
like to get a preview of the merged changes.
## 📃 [Documentation](https://urql.dev/goto/docs)
The documentation contains everything you need to know about `urql`, and contains several sections in order of importance
when you first get started:
- **[Basics](https://formidable.com/open-source/urql/docs/basics/)** — contains the ["Getting Started" guide](https://formidable.com/open-source/urql/docs/#where-to-start) and all you need to know when first using `urql`.
- **[Architecture](https://formidable.com/open-source/urql/docs/architecture/)** — explains how `urql` functions and is built.
- **[Advanced](https://formidable.com/open-source/urql/docs/advanced/)** — covers more uncommon use-cases and things you don't immediately need when getting started.
- **[Graphcache](https://formidable.com/open-source/urql/docs/graphcache/)** — documents ["Normalized Caching" support](https://formidable.com/open-source/urql/docs/graphcache/normalized-caching/) which enables more complex apps and use-cases.
- **[API](https://formidable.com/open-source/urql/docs/api/)** — the API documentation for each individual package.
Furthermore, all APIs and packages are self-documented using TSDocs. If you’re using a language
server for TypeScript, the documentation for each API should pop up in your editor when hovering
`urql`’s code and APIs.
_You can find the raw markdown files inside this repository's `docs` folder._
================================================
FILE: docs/README.md
================================================
---
title: Overview
order: 1
---
# Overview
`urql` is a highly customizable and versatile GraphQL client with which you add on features like
normalized caching as you grow. It's built to be both easy to use for newcomers to
GraphQL, and extensible, to grow to support dynamic single-app applications and highly
customized GraphQL infrastructure. In short, `urql` prioritizes usability and adaptability.
As you're adopting GraphQL, `urql` becomes your primary data layer and can handle content-heavy
pages through ["Document Caching"](./basics/document-caching.md) as well as dynamic and data-heavy
apps through ["Normalized Caching"](./graphcache/normalized-caching.md).
`urql` can be understood as a collection of connected parts and packages.
When we only need to install a single package for our framework of choice. We're then able to
declaratively send GraphQL requests to our API. All framework packages — like `urql` (for React),
`@urql/preact`, `@urql/svelte`, `@urql/solid`/`@urql/solid-start` and `@urql/vue` — wrap the [core package,
`@urql/core`](./basics/core.md), which we can imagine as the brain
of `urql` with most of its logic. As we progress with implementing `urql` into our application,
we're later able to extend it by adding ["addon packages", which we call
_Exchanges_](./advanced/authoring-exchanges.md)
If at this point you're still unsure of whether to use `urql`, [have a look at the **Comparison**
page](./comparison.md) and check whether `urql` supports all features you're looking for.
## Where to start
We have **Getting Started** guides for:
- [**React/Preact**](./basics/react-preact.md) covers how to work with the bindings for React/Preact.
- [**Vue**](./basics/vue.md) covers how to work with the bindings for Vue 3.
- [**Svelte**](./basics/svelte.md) covers how to work with the bindings for Svelte.
- [**Solid**](./basics/solid.md) covers how to work with the bindings for Solid.
- [**SolidStart**](./basics/solid-start.md) covers how to work with the bindings for SolidStart.
- [**Core Package**](./basics/core.md) covers the shared "core APIs" and how we can use them directly
in Node.js or imperatively.
Each of these sections will walk you through the specific instructions for the framework bindings,
including how to install and set them up, how to write queries, and how to send mutations.
## Following the Documentation
This documentation is split into groups or sections that cover different levels of usage or areas of
interest.
- **Basics** is the section where we'll want to start learning about `urql` as it contains "Getting
Started" guides for our framework of choice.
- **Architecture** then explains more about how `urql` functions, what it's made up of, and covers
the main aspects of the `Client` and exchanges.
- **Advanced** covers all more uncommon use-cases and contains guides that we won't need immediately
when we get started with `urql`.
- **Graphcache** documents one of the most important addons to `urql`, which adds ["Normalized
Caching" support](./graphcache/normalized-caching.md) to the `Client` and enables more complex
use-cases, smarter caching, and more dynamic apps to function.
- **Showcase** aims to list users of `urql`, third-party packages, and other helpful resources,
like tutorials and guides.
- **API** contains a detailed documentation on each package's APIs. The documentation links to each
of these as appropriate, but if we're unsure of how to use a utility or package, we can go here
directly to look up how to use a specific API.
We hope you grow to love `urql`!
================================================
FILE: docs/advanced/README.md
================================================
---
title: Advanced
order: 4
---
# Advanced
In this chapter we'll dive into various topics of "advanced" `urql` usage. This is admittedly a
catch-all chapter of various use-cases that can only be covered after [the "Architecture"
chapter.](../architecture.md)
- [**Subscriptions**](./subscriptions.md) covers how to use `useSubscription` and how to set up GraphQL subscriptions with
`urql`.
- [**Persistence & Uploads**](./persistence-and-uploads.md) teaches us how to set up Automatic
Persisted Queries and File Uploads using the two respective packages.
- [**Server-side Rendering**](./server-side-rendering.md) guides us through how to set up server-side rendering and rehydration.
- [**Debugging**](./debugging.md) shows us the [`urql`
devtools](https://github.com/urql-graphql/urql-devtools/) and how to add our own debug events
for its event view.
- [**Retrying operations**](./retry-operations.md) shows the `retryExchange` which allows you to retry operations when they've failed.
- [**Authentication**](./authentication.md) describes how to implement authentication using the `authExchange`
- [**Testing**](./testing.md) covers how to test components that use `urql` particularly in React.
- [**Authoring Exchanges**](./authoring-exchanges.md) describes how to implement exchanges from
scratch and how they work internally. This is a good basis to understanding how some
features in this section function.
- [**Auto-populate Mutations**](./auto-populate-mutations.md) presents the `populateExchange` addon, which can make it easier to
update normalized data after mutations.
================================================
FILE: docs/advanced/authentication.md
================================================
---
title: Authentication
order: 6
---
# Authentication
Most APIs include some type of authentication, usually in the form of an auth token that is sent with each request header.
The purpose of the [`authExchange`](../api/auth-exchange.md) is to provide a flexible API that facilitates the typical
JWT-based authentication flow.
> **Note:** [You can find a code example for `@urql/exchange-auth` in an example in the `urql` repository.](https://github.com/urql-graphql/urql/tree/main/examples/with-refresh-auth)
## Typical Authentication Flow
**Initial login** — the user opens the application and authenticates for the first time. They enter their credentials and receive an auth token.
The token is saved to storage that is persisted though sessions, e.g. `localStorage` on the web or `AsyncStorage` in React Native. The token is
added to each subsequent request in an auth header.
**Resume** — the user opens the application after having authenticated in the past. In this case, we should already have the token in persisted
storage. We fetch the token from storage and add to each request, usually as an auth header.
**Forced log out due to invalid token** — the user's session could become invalid for a variety reasons: their token expired, they requested to be
signed out of all devices, or their session was invalidated remotely. In this case, we would want to
also log them out in the application, so they
could have the opportunity to log in again. To do this, we want to clear any persisted storage, and redirect them to the application home or login page.
**User initiated log out** — when the user chooses to log out of the application, we usually send a logout request to the API, then clear any tokens
from persisted storage, and redirect them to the application home or login page.
**Refresh (optional)** — this is not always implemented; if your API supports it, the
user will receive both an auth token, and a refresh token.
The auth token is usually valid for a shorter duration of time (e.g. 1 week) than the refresh token
(e.g. 6 months), and the latter can be used to request a new
auth token if the auth token has expired. The refresh logic is triggered either when the JWT is known to be invalid (e.g. by decoding it and inspecting the expiry date),
or when an API request returns with an unauthorized response. For graphQL APIs, it is usually an error code, instead of a 401 HTTP response, but both can be supported.
When the token has been successfully refreshed (this can be done as a mutation to the graphQL API or a request to a different API endpoint, depending on implementation),
we will save the new token in persisted storage, and retry the failed request with the new auth header. The user should be logged out and persisted storage cleared if
the refresh fails or if the re-executing the query with the new token fails with an auth error for the second time.
## Installation & Setup
First, install the `@urql/exchange-auth` alongside `urql`:
```sh
yarn add @urql/exchange-auth
# or
npm install --save @urql/exchange-auth
```
You'll then need to add the `authExchange`, that this package exposes to your `Client`. The `authExchange` is an asynchronous exchange, so it must be placed
in front of all `fetchExchange`s but after all other synchronous exchanges, like the `cacheExchange`.
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
import { authExchange } from '@urql/exchange-auth';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [
cacheExchange,
authExchange(async utils => {
return {
/* config... */
};
}),
fetchExchange,
],
});
```
You pass an initialization function to the `authExchange`. This function is called by the exchange
when it first initializes. It'll let you receive an object of utilities and you must return
a (promisified) object of configuration options.
Let's discuss each of the [configuration options](../api/auth-exchange.md#options) and how to use them in turn.
### Configuring the initializer function (initial load)
The initializer function must return a promise of a configuration object and hence also gives you an
opportunity to fetch your authentication state from storage.
```js
async function initializeAuthState() {
const token = localStorage.getItem('token');
const refreshToken = localStorage.getItem('refreshToken');
return { token, refreshToken };
}
authExchange(async utils => {
let { token, refreshToken } = initializeAuthState();
return {
/* config... */
};
});
```
The first step here is to retrieve our tokens from a kind of storage, which may be asynchronous as
well, as illustrated by `initializeAuthState`.
In React Native, this is very similar, but because persisted storage in React Native is always
asynchronous and promisified, we would await our tokens. This works because the
function that `authExchange` is async, i.e. must return a `Promise`.
```js
async function initializeAuthState() {
const token = await AsyncStorage.getItem(TOKEN_KEY);
const refreshToken = await AsyncStorage.getItem(REFRESH_KEY);
return { token, refreshToken };
}
authExchange(async utils => {
let { token, refreshToken } = initializeAuthState();
return {
/* config... */
};
});
```
### Configuring `addAuthToOperation`
The purpose of `addAuthToOperation` is to apply an auth state to each request. Here, we'll use the
tokens we retrieved from storage and add them to our operations.
In this example, we're using a utility we're passed, `appendHeaders`. This utility is a simply
shortcut to quickly add HTTP headers via `fetchOptions` to an `Operation`, however, we may as well
be editing the `Operation` context here using `makeOperation`.
```js
authExchange(async utils => {
let token = await AsyncStorage.getItem(TOKEN_KEY);
let refreshToken = await AsyncStorage.getItem(REFRESH_KEY);
return {
addAuthToOperation(operation) {
if (!token) return operation;
return utils.appendHeaders(operation, {
Authorization: `Bearer ${token}`,
});
},
// ...
};
});
```
First, we check that we have a non-null `token`. Then we apply it to the request using the
`appendHeaders` utility as an `Authorization` header.
We could also be using `makeOperation` here to update the context in any other way, such as:
```js
import { makeOperation } from '@urql/core';
makeOperation(operation.kind, operation, {
...operation.context,
someAuthThing: token,
});
```
### Configuring `didAuthError`
This function lets the `authExchange` know what is defined to be an API error for your API.
`didAuthError` is called by `authExchange` when it receives an `error` on an `OperationResult`, which
is of type [`CombinedError`](../api/core.md#combinederror).
We can for example check the error's `graphQLErrors` array in `CombinedError` to determine if an auth
error has occurred. While your API may implement this differently, an authentication error on an
execution result may look a little like this if your API uses `extensions.code` on errors:
```js
{
data: null,
errors: [
{
message: 'Unauthorized: Token has expired',
extensions: {
code: 'FORBIDDEN'
},
}
]
}
```
If you're building a new API, using `extensions` on errors is the recommended approach to add
metadata to your errors. We'll be able to determine whether any of the GraphQL errors were due
to an unauthorized error code, which would indicate an auth failure:
```js
authExchange(async utils => {
// ...
return {
// ...
didAuthError(error, _operation) {
return error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
},
};
});
```
For some GraphQL APIs, the authentication error is only communicated via a 401 HTTP status as is
common in RESTful APIs, which is suboptimal, but which we can still write a check for.
```js
authExchange(async utils => {
// ...
return {
// ...
didAuthError(error, _operation) {
return error.response?.status === 401;
},
};
});
```
If `didAuthError` returns `true`, it will trigger the `authExchange` to trigger the logic for asking
for re-authentication via `refreshAuth`.
### Configuring `refreshAuth` (triggered after an auth error has occurred)
If the API doesn't support any sort of token refresh, this is where we could simply log the user out.
```js
authExchange(async utils => {
// ...
return {
// ...
async refreshAuth() {
logout();
},
};
});
```
Here, `logout()` is a placeholder that is called when we got an error, so that we can redirect to a
login page again and clear our tokens from local storage or otherwise.
If we had a way to refresh our token using a refresh token, we can attempt to get a new token for the
user first:
```js
authExchange(async utils => {
let token = localStorage.getItem('token');
let refreshToken = localStorage.getItem('refreshToken');
return {
// ...
async refreshAuth() {
const result = await utils.mutate(REFRESH, { refreshToken });
if (result.data?.refreshLogin) {
// Update our local variables and write to our storage
token = result.data.refreshLogin.token;
refreshToken = result.data.refreshLogin.refreshToken;
localStorage.setItem('token', token);
localStorage.setItem('refreshToken', refreshToken);
} else {
// This is where auth has gone wrong and we need to clean up and redirect to a login page
localStorage.clear();
logout();
}
},
};
});
```
Here we use the special `mutate` utility method provided by the `authExchange` to do the token
refresh. This is a useful method to use if your GraphQL API expects you to make a GraphQL mutation
to update your authentication state. It will send the mutation and bypass all authentication and
prior exchanges.
If your authentication is not handled via GraphQL but a REST endpoint, you can use the `fetch` API
here however instead of a mutation.
All other requests will be paused while `refreshAuth` runs, so we won't have to deal with multiple
authentication errors or refreshes at once.
### Configuring `willAuthError`
`willAuthError` is an optional parameter and is run _before_ a request is made.
We can use it to trigger an authentication error and let the `authExchange` run our `refreshAuth`
function without the need to first let a request fail with an authentication error. For example, we
can use this to predict an authentication error, for instance, because of expired JWT tokens.
```js
authExchange(async utils => {
// ...
return {
// ...
willAuthError(_operation) {
// Check whether `token` JWT is expired
return false;
},
};
});
```
This can be really useful when we know when our authentication state is invalid and want to prevent
even sending any operation that we know will fail with an authentication error.
However, we have to be careful on how we define this function, if some queries or login mutations
are sent to our API without being logged in. In these cases, it's better to either detect the
mutations we'd like to allow or return `false` when a token isn't set in storage yet.
If we'd like to detect a mutation that will never fail with an authentication error, we could for
instance write the following logic:
```js
authExchange(async utils => {
// ...
return {
// ...
willAuthError(operation) {
if (
operation.kind === 'mutation' &&
// Here we find any mutation definition with the "login" field
operation.query.definitions.some(definition => {
return (
definition.kind === 'OperationDefinition' &&
definition.selectionSet.selections.some(node => {
// The field name is just an example, since signup may also be an exception
return node.kind === 'Field' && node.name.value === 'login';
})
);
})
) {
return false;
} else if (false /* is JWT expired? */) {
return true;
} else {
return false;
}
},
};
});
```
Alternatively, you may decide to let all operations through if your token isn't set in storage, i.e.
if you have no prior authentication state.
## Handling Logout by reacting to Errors
We can also handle authentication errors in a `mapExchange` instead of the `authExchange`.
To do this, we'll need to add the `mapExchange` to the exchanges array, _before_ the `authExchange`.
The order is very important here:
```js
import { createClient, cacheExchange, fetchExchange, mapExchange } from 'urql';
import { authExchange } from '@urql/exchange-auth';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [
cacheExchange,
mapExchange({
onError(error, _operation) {
const isAuthError = error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
if (isAuthError) {
logout();
}
},
}),
authExchange(async utils => {
return {
/* config */
};
}),
fetchExchange,
],
});
```
The `mapExchange` will only receive an auth error when the auth exchange has already tried and failed
to handle it. This means we have either failed to refresh the token, or there is no token refresh
functionality. If we receive an auth error in the `mapExchange`'s `onError` function
(as defined in the `didAuthError` configuration section above), then we can be confident that it is
an authentication error that the `authExchange` isn't able to recover from, and the user should be
logged out.
## Cache Invalidation on Logout
If we're dealing with multiple authentication states at the same time, e.g. logouts, we need to
ensure that the `Client` is reinitialized whenever the authentication state changes.
Here's an example of how we may do this in React if necessary:
```jsx
import { createClient, Provider } from 'urql';
const App = ({ isLoggedIn }: { isLoggedIn: boolean | null }) => {
const client = useMemo(() => {
if (isLoggedIn === null) {
return null;
}
return createClient({ /* config */ });
}, [isLoggedIn]);
if (!client) {
return null;
}
return {
{/* app content */}
}
}
```
When the application launches, the first thing we do is check whether the user has any authentication
tokens in persisted storage. This will tell us whether to show the user the logged in or logged out view.
The `isLoggedIn` prop should always be updated based on authentication state change. For instance, we may set it to
`true` after the user has authenticated and their tokens have been added to storage, and set it to
`false` once the user has been logged out and their tokens have been cleared. It's important to clear
or add tokens to a storage _before_ updating the prop in order for the auth exchange to work
correctly.
This pattern of creating a new `Client` when changing authentication states is especially useful
since it will also recreate our client-side cache and invalidate all cached data.
================================================
FILE: docs/advanced/authoring-exchanges.md
================================================
---
title: Authoring Exchanges
order: 8
---
# Exchange Author Guide
As we've learned [on the "Architecture" page](../architecture.md) page, `urql`'s `Client` structures
its data as an event hub. We have an input stream of operations, which are instructions for the
`Client` to provide a result. These results then come from an output stream of operation results.
_Exchanges_ are responsible for performing the important transform from the operations (input) stream
to the results stream. Exchanges are handler functions that deal with these input and
output streams. They're one of `urql`'s key components, and are needed to implement vital pieces of
logic such as caching, fetching, deduplicating requests, and more. In other words, Exchanges are
handlers that fulfill our GraphQL requests and can change the stream of operations or results.
In this guide we'll learn more about how exchanges work and how we can write our own exchanges.
## An Exchange Signature
Exchanges are akin to [middleware in
Redux](https://redux.js.org/advanced/middleware) due to the way that they apply transforms.
```ts
import { Client, Operation, OperationResult } from '@urql/core';
type ExchangeInput = { forward: ExchangeIO; client: Client };
type Exchange = (input: ExchangeInput) => ExchangeIO;
type ExchangeIO = (ops$: Source) => Source;
```
The first parameter to an exchange is a `forward` function that refers to the next Exchange in the
chain. The second second parameter is the `Client` being used. Exchanges always return an `ExchangeIO`
function (this applies to the `forward` function as well), which accepts the source of
[_Operations_](../api/core.md#operation) and returns a source of [_Operation
Results_](../api/core.md#operationresult).
- [Read more about streams on the "Architecture" page.](../architecture.md#stream-patterns-in-urql)
- [Read more about the _Exchange_ type signature on the API docs.](../api/core.md#exchange)
## Using Exchanges
The `Client` accepts an `exchanges` option that. Initially, we may choose to just
set this to two very standard exchanges — `cacheExchange` and `fetchExchange`.
In essence these exchanges build a pipeline that runs in the order they're passed; _Operations_ flow
in from the start to the end, and _Results_ are returned through the chain in reverse.
Suppose we pass the `cacheExchange` and then the `fetchExchange` to the `exchanges`.
**First,** operations are checked against the cache. Depending on the `requestPolicy`,
cached results can be resolved from here instead, which would mean that the cache sends back the
result, and the operation doesn't travel any further in the chain.
**Second,** operations are sent to the API, and the result is turned into an `OperationResult`.
**Lastly,** operation results then travel through the exchanges in _reverse order_, which is because
exchanges are a pipeline where all operations travel forward deeper into the exchange chain, and
then backwards. When these results pass through the cache then the `cacheExchange` stores the
result.
```js
import { Client, fetchExchange, cacheExchange } from 'urql';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
We can add more exchanges to this chain, for instance, we can add the `mapExchange`, which can call a
callback whenever it sees [a `CombinedError`](../basics/errors.md) occur on a result.
```js
import { Client, fetchExchange, cacheExchange, mapExchange } from 'urql';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [
cacheExchange,
mapExchange({
onError(error) {
console.error(error);
},
}),
fetchExchange,
],
});
```
This is an example for adding a synchronous exchange to the chain that only reacts to results. It
doesn't add any special behavior for operations travelling through it. An example for an
asynchronous exchange that looks at both operations and results [we may look at the `retryExchange`
which retries failed operations.](../advanced/retry-operations.md)
## The Rules of Exchanges
Before we can start writing some exchanges, there are a couple of consistent patterns and limitations that
must be adhered to when writing an exchange. We call these the "rules of Exchanges", which
also come in useful when trying to learn what Exchanges actually are.
For reference, this is a basic template for an exchange:
```js
const noopExchange = ({ client, forward }) => {
return operations$ => {
// <-- The ExchangeIO function
const operationResult$ = forward(operations$);
return operationResult$;
};
};
```
This exchange does nothing else than forward all operations and return all results. Hence, it's
called a `noopExchange` — an exchange that doesn't do anything.
### Forward and Return Composition
When you create a `Client` and pass it an array of exchanges, `urql` composes them left-to-right.
If we look at our previous `noopExchange` example in context, we can track what it does if it is located between the `cacheExchange` and the `fetchExchange`.
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
const noopExchange = ({ client, forward }) => {
return operations$ => {
// <-- The ExchangeIO function
// We receive a stream of Operations from `cacheExchange` which
// we can modify before...
const forwardOperations$ = operations$;
// ...calling `forward` with the modified stream. The `forward`
// function is the next exchange's `ExchangeIO` function, in this
// case `fetchExchange`.
const operationResult$ = forward(operations$);
// We get back `fetchExchange`'s stream of results, which we can
// also change before returning, which is what `cacheExchange`
// will receive when calling `forward`.
return operationResult$;
};
};
const client = new Client({
exchanges: [cacheExchange, noopExchange, fetchExchange],
});
```
### How to Avoid Accidentally Dropping Operations
Typically the `operations$` stream will send you `query`, `mutation`,
`subscription`, and `teardown`. There is no constraint for new operations
to be added later on or a custom exchange adding new operations altogether.
This means that you have to take "unknown" operations into account and
not `filter` operations too aggressively.
```js
import { pipe, filter, merge } from 'wonka';
// DON'T: drop unknown operations
({ forward }) =>
operations$ => {
// This doesn't handle operations that aren't queries
const queries = pipe(
operations$,
filter(op => op.kind === 'query')
);
return forward(queries);
};
// DO: forward operations that you don't handle
({ forward }) =>
operations$ => {
const queries = pipe(
operations$,
filter(op => op.kind === 'query')
);
const rest = pipe(
operations$,
filter(op => op.kind !== 'query')
);
return forward(merge([queries, rest]));
};
```
If operations are grouped and/or filtered by what the exchange is handling, then it's also important to
make that any streams of operations not handled by the exchange should also be forwarded.
### Synchronous first, Asynchronous last
By default exchanges and Wonka streams are as predictable as possible.
Every operator in Wonka runs synchronously until asynchronicity is introduced.
This may happen when using a timing utility from Wonka, like
[`delay`](https://wonka.kitten.sh/api/operators#delay) or
[`throttle`](https://wonka.kitten.sh/api/operators#throttle)
This can also happen because the exchange inherently does something asynchronous, like fetching some
data or using a promise.
When writing exchanges, some will inevitably be asynchronous. For example if
they're fetching results, performing authentication, or other tasks
that you have to wait for.
This can cause problems, because the behavior in `urql` is built
to be _synchronous_ first. This is very helpful for suspense mode and allowing components receive cached data on their initial
mount without rerendering.
This why **all exchanges should be ordered synchronous first and
asynchronous last**.
What we for instance repeat as the default setup in our docs is this:
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
new Client({
// ...
exchanges: [cacheExchange, fetchExchange];
});
```
The `cacheExchange` is completely synchronous.
The `fetchExchange` is asynchronous since it makes a `fetch` request and waits for a server response.
If we put an asynchronous exchange in front of the `cacheExchange`, that would be unexpected, and
since all results would then be delayed, nothing would ever be "cached" and instead always take some
amount of time to be returned.
When you're adding more exchanges, it's often crucial
to put them in a specific order. For instance, an authentication exchange
will need to go before the `fetchExchange`, a secondary cache will probably have to
go in front of the default cache exchange.
To ensure the correct behavior of suspense mode and
the initialization of our hooks, it's vital to order exchanges
so that synchronous ones come before asynchronous ones.
================================================
FILE: docs/advanced/auto-populate-mutations.md
================================================
---
title: Auto-populate Mutations
order: 9
---
# Automatically populating Mutations
The `populateExchange` allows you to auto-populate selection sets in your mutations using the
`@populate` directive. In combination with [Graphcache](../graphcache/README.md) this is a useful
tool to update the data in your application automatically following a mutation, when your app grows,
and it becomes harder to track all fields that have been queried before.
> **NOTE:** The `populateExchange` is _experimental_! Certain patterns and usage paths
> like GraphQL field arguments aren't covered yet, and the exchange hasn't been extensively used
> yet.
## Installation and Setup
The `populateExchange` can be installed via the `@urql/exchange-populate` package.
```sh
yarn add @urql/exchange-populate
# or
npm install --save @urql/exchange-populate
```
Afterwards we can set the `populateExchange` up by adding it to our list of `exchanges` in the
client options.
```ts
import { Client, fetchExchange } from '@urql/core';
import { populateExchange } from '@urql/exchange-populate';
const client = new Client({
// ...
exchanges: [populateExchange({ schema }), cacheExchange, fetchExchange],
});
```
The `populateExchange` should be placed in front of the `cacheExchange`, especially if you're using
[Graphcache](../graphcache/README.md), since it won't understand the `@populate` directive on its
own. It should also be placed in front the `cacheExchange` to avoid unnecessary work.
Adding the `populateExchange` now enables us to use the `@populate` directive in our mutations.
The `schema` option is the introspection result for your backend graphql schema, more information
about how to get your schema can be found [in the "Schema Awareness" Page of the Graphcache documentation.](../graphcache/schema-awareness.md#getting-your-schema).
## Example usage
Consider the following queries, which have been requested in other parts of your application:
```graphql
# Query 1
{
todos {
id
name
}
}
# Query 2
{
todos {
id
createdAt
}
}
```
Without the `populateExchange` you may write a mutation like the following which returns a newly created todo item:
```graphql
# Without populate
mutation addTodo(id: ID!) {
addTodo(id: $id) {
id # To update Query 1 & 2
name # To update Query 1
createdAt # To update Query 2
}
}
```
By using `populateExchange`, you no longer need to manually specify the selection set required to update your other queries. Instead you can just add the `@populate` directive.
```graphql
# With populate
mutation addTodo(id: ID!) {
addTodo(id: $id) @populate
}
```
### Choosing when to populate
You may not want to populate your whole mutation response. To reduce your payload, pass populate lower in your query.
```graphql
mutation addTodo(id: ID!) {
addTodo(id: $id) {
id
user @populate
}
}
```
### Using aliases
If you find yourself using multiple queries with variables, it may be necessary to
[use aliases](https://graphql.org/learn/queries/#aliases) to allow merging of queries.
> **Note:** This caveat may change in the future or this restriction may be lifted.
**Invalid usage**
```graphql
# Query 1
{
todos(first: 10) {
id
name
}
}
# Query 2
{
todos(last: 20) {
id
createdAt
}
}
```
**Usage with aliases**
```graphql
# Query 1
{
firstTodos: todos(first: 10) {
id
name
}
}
# Query 2
{
lastTodos: todos(last: 20) {
id
createdAt
}
}
```
================================================
FILE: docs/advanced/debugging.md
================================================
---
title: Debugging
order: 4
---
# Debugging
We've tried to make debugging in `urql` as seamless as possible by creating tools for users of `urql`
and those creating their own exchanges.
## Devtools
It's easiest to debug `urql` with the [`urql` devtools.](https://github.com/urql-graphql/urql-devtools/)
It offers tools to inspect internal ["Debug Events"](#debug-events) as they happen, to explore data
as your app is seeing it, and to quickly trigger GraphQL queries.
[For instructions on how to set up the devtools, check out `@urql/devtools`'s readme in its
repository.](https://github.com/urql-graphql/urql-devtools)

## Debug events
The "Debug Events" are internally what displays more information to the user on the devtools'
"Events" tab than just [Operations](../api/core.md#operation) and [Operation
Results](../api/core.md#operationresult).
Events may be fired inside exchanges to add additional development logging to an exchange.
The `fetchExchange` for instance will fire a `fetchRequest` event when a request is initiated and
either a `fetchError` or `fetchSuccess` event when a result comes back from the GraphQL API.
The [Devtools](#browser-devtools) aren't the only way to observe these internal events.
Anyone can start listening to these events for debugging events by calling the
[`Client`'s](../api/core.md#client) `client.subscribeToDebugTarget()` method.
Unlike `Operation`s these events are fire-and-forget events that are only used for debugging. Hence,
they shouldn't be used for anything but logging and not for messaging. **Debug events are also
entirely disabled in production.**
### Subscribing to Debug Events
Internally the `devtoolsExchange` calls the `client.subscribeToDebugTarget`, but if we're looking to
build custom debugging tools, it's also possible to call this function directly and to replace the
`devtoolsExchange`.
```
const { unsubscribe } = client.subscribeToDebugTarget(event => {
if (event.source === 'cacheExchange')
return;
console.log(event); // { type, message, operation, data, source, timestamp }
});
```
As demonstrated above, the `client.subscribeToDebugTarget` accepts a callback function and returns
a subscription with an `unsubscribe` method. We've seen this pattern in the prior ["Stream Patterns"
section on the "Architecture" page.](../architecture.md)
## Adding your own Debug Events
Debug events are a means of sharing implementation details to consumers of an exchange. If you're
creating an exchange and want to share relevant information with the `devtools`, then you may want
to start adding your own events.
#### Dispatching an event
[On the "Authoring Exchanges" page](./authoring-exchanges.md) we've learned about the [`ExchangeInput`
object](../api/core.md#exchangeinput), which comes with a `client` and a `forward` property.
It also contains a `dispatchDebug` property.
It is called with an object containing the following properties:
| Prop | Type | Description |
| ----------- | ----------- | ------------------------------------------------------------------------------------- |
| `type` | `string` | A unique type identifier for the Debug Event. |
| `message` | `string` | A human readable description of the event. |
| `operation` | `Operation` | The [`Operation`](../api/core.md#operation) that the event targets. |
| `data` | `?object` | This is an optional payload to include any data that may become useful for debugging. |
For instance, we may call `dispatchDebug` with our `fetchRequest` event. This is the event that the
`fetchExchange` uses to notify us that a request has commenced:
```ts
export const fetchExchange: Exchange = ({ forward, dispatchDebug }) => {
// ...
return ops$ => {
return pipe(
ops$,
// ...
mergeMap(operation => {
dispatchDebug({
type: 'fetchRequest',
message: 'A network request has been triggered',
operation,
data: {
/* ... */
},
});
// ...
})
);
};
};
```
If we're adding new events that aren't included in the main `urql` repository and are using
TypeScript, we may also declare a fixed type for the `data` property, so we can guarantee a
consistent payload for our Debug Events. This also prevents accidental conflicts.
```ts
// urql.d.ts
import '@urql/core';
declare module '@urql/core' {
interface DebugEventTypes {
customEventType: { somePayload: string };
}
}
```
Read more about extending types, like `urql`'s `DebugEventTypes` on the [TypeScript docs on
declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html).
### Tips
Lastly, in summary, here are a few tips, that are important when we're adding new Debug Events to
custom exchanges:
- ✅ **Share internal details**: Frequent debug messages on key events inside your exchange are very
useful when later inspecting them, e.g. in the `devtools`.
- ✅ **Create unique event types** : Key events should be easily identifiable and have a unique
names.
- ❌ **Don't listen to debug events inside your exchange**: While it's possible to call
`client.subscribeToDebugTarget` in an exchange it's only valuable when creating a debugging
exchange, like the `devtoolsExchange`.
- ❌ **Don't send warnings in debug events**: Informing your user about warnings isn't effective
when the event isn't seen. You should still rely on `console.warn` so all users see your important
warnings.
================================================
FILE: docs/advanced/persistence-and-uploads.md
================================================
---
title: Persistence & Uploads
order: 1
---
# Persisted Queries and Uploads
`urql` supports (Automatic) Persisted Queries, and File Uploads via GraphQL
Multipart requests. For persisted queries to work, some setup work is needed,
while File Upload support is built into `@urql/core@4`.
## Automatic Persisted Queries
Persisted Queries allow us to send requests to the GraphQL API that can easily be cached on the fly,
both by the GraphQL API itself and potential CDN caching layers. This is based on the unofficial
[GraphQL Persisted Queries
Spec](https://github.com/apollographql/apollo-link-persisted-queries#apollo-engine).
With Automatic Persisted Queries the client hashes the GraphQL query and turns it into an SHA256
hash and sends this hash instead of the full query. If the server has seen this GraphQL query before
it will recognise it by its hash and process the GraphQL API request as usual, otherwise it may
respond using a `PersistedQueryNotFound` error. In that case the client is supposed to instead send
the full GraphQL query, and the hash together, which will cause the query to be "registered" with the
server.
Additionally, we could also decide to send these hashed queries as GET requests instead of POST
requests. If we only send the persisted queries with hashes as GET requests then they become a lot
easier for a CDN to cache, as by default most caches would not cache POST requests automatically.
In `urql`, we may use the `@urql/exchange-persisted` package's `persistedExchange` to
enable support for Automatic Persisted Queries. This exchange works alongside other fetch or
subscription exchanges by adding metadata for persisted queries to each GraphQL
request by modifying the `extensions` object of operations.
> **Note:** [You can find a code example for `@urql/exchange-persisted` in an example in the `urql` repository.](https://github.com/urql-graphql/urql/tree/main/examples/with-apq)
### Installation & Setup
First install `@urql/exchange-persisted` alongside `urql`:
```sh
yarn add @urql/exchange-persisted
# or
npm install --save @urql/exchange-persisted
```
You'll then need to add the `persistedExchange` function, that this package exposes,
to your `exchanges`, in front of exchanges that communicate with the API:
```js
import { Client, fetchExchange, cacheExchange } from 'urql';
import { persistedExchange } from '@urql/exchange-persisted';
const client = new Client({
url: 'http://localhost:1234/graphql',
exchanges: [
cacheExchange,
persistedExchange({
preferGetForPersistedQueries: true,
}),
fetchExchange,
],
});
```
As we can see, typically it's recommended to set `preferGetForPersistedQueries` to `true`
to encourage persisted queries to use GET requests instead of POST so that CDNs can do their job.
When set to `true` or `'within-url-limit'`, persisted queries will use GET requests if the
resulting URL doesn't exceed the 2048 character limit.
The `fetchExchange` can see the modifications that the `persistedExchange` is
making to operations, and understands to leave out the `query` from any request
as needed. The same should be happening to the `subscriptionExchange`, if you're
using it for queries.
### Customizing Hashing
The `persistedExchange` also accepts a `generateHash` option. This may be used to swap out the
exchange's default method of generating SHA256 hashes. By default, the exchange will use the
built-in [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) when it's
available, and in Node.js it'll use the [Node Crypto Module](https://nodejs.org/api/crypto.html)
instead.
If you're using [the `graphql-persisted-document-loader` for
Webpack](https://github.com/leoasis/graphql-persisted-document-loader), for instance, then you will
already have a loader generating SHA256 hashes for you at compile time. In that case we could swap
out the `generateHash` function with a much simpler one that uses the `generateHash` function's
second argument, a GraphQL `DocumentNode` object.
```js
persistedExchange({
generateHash: (_, document) => document.documentId,
});
```
If you're using **React Native** then you may not have access to the Web Crypto API, which means
that you have to provide your own SHA256 function to the `persistedExchange`. Luckily, we can do
so easily by using the first argument `generateHash` receives, a GraphQL query as a string.
```js
import sha256 from 'hash.js/lib/hash/sha/256';
persistedExchange({
async generateHash(query) {
return sha256().update(query).digest('hex');
},
});
```
Additionally, if the API only expects persisted queries and not arbitrary ones and all queries are
pre-registered against the API then the `persistedExchange` may be put into a **non-automatic**
persisted queries mode by giving it the `enforcePersistedQueries: true` option. This disables any
retry logic and assumes that persisted queries will be handled like regular GraphQL requests.
## File Uploads
GraphQL server APIs commonly support the [GraphQL Multipart Request
spec](https://github.com/jaydenseric/graphql-multipart-request-spec) to allow for File Uploads
directly with a GraphQL API.
If a GraphQL API supports this, we can pass a [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File)
or a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) directly into our variables and
define the corresponding scalar for our variable, which is often called `File` or `Upload`.
In a browser, the `File` object may often be retrieved via a
[file input](https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications),
for example.
> **Note:** If you are using your own version of `File` and `Blob` ensure you are properly extending the
> so it can be properly identified as a file.
The `@urql/core@4` package supports File Uploads natively, so we won't have to do any installation
or setup work. When `urql` sees a `File` or a `Blob` anywhere in your `variables`, it switches to
a `multipart/form-data` request, converts the request to a `FormData` object, according to the
GraphQL Multipart Request specification, and sends it off to the API.
> **Note:** Previously, this worked by installing the `@urql/multipart-fetch-exchange` package.
> however, this package has been deprecated and file uploads are now built into `@urql/core@4`.
[You can find a code example for file uploads in an example in the `urql` repository.](https://github.com/urql-graphql/urql/tree/main/examples/with-multipart)
================================================
FILE: docs/advanced/retry-operations.md
================================================
---
title: Retrying Operations
order: 5
---
# Retrying Operations
The `retryExchange` lets us retry specific operation, by default it will
retry only network errors, but we can specify additional options to add
functionality.
> **Note:** [You can find a code example for `@urql/exchange-retry` in an example in the `urql` repository.](https://github.com/urql-graphql/urql/tree/main/examples/with-retry)
## Installation and Setup
First install `@urql/exchange-retry` alongside `urql`:
```sh
yarn add @urql/exchange-retry
# or
npm install --save @urql/exchange-retry
```
You'll then need to add the `retryExchange`, exposed by this package, to your `urql` Client:
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
import { retryExchange } from '@urql/exchange-retry';
// None of these options have to be added, these are the default values.
const options = {
initialDelayMs: 1000,
maxDelayMs: 15000,
randomDelay: true,
maxNumberAttempts: 2,
retryIf: err => err && err.networkError,
};
// Note the position of the retryExchange - it should be placed prior to the
// fetchExchange and after the cacheExchange for it to function correctly
const client = new Client({
url: 'http://localhost:1234/graphql',
exchanges: [
cacheExchange,
retryExchange(options), // Use the retryExchange factory to add a new exchange
fetchExchange,
],
});
```
We want to place the `retryExchange` before the `fetchExchange` so that retries are only performed _after_ the operation has passed through the cache and has attempted to fetch.
## The Options
There are a set of optional options that allow for fine-grained control over the `retry` mechanism.
We have the `initialDelayMs` to specify at what interval the `retrying` should start, this means that if we specify `1000` that when our `operation` fails we'll wait 1 second and then retry it.
Next up is the `maxDelayMs`, our `retryExchange` will keep increasing the time between retries, so we don't spam our server with requests it can't complete, this option ensures we don't exceed a certain threshold. This time between requests will increase with a random `back-off` factor multiplied by the `initialDelayMs`, read more about the [thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem).
Talking about increasing the `delay` randomly, `randomDelay` allows us to disable this. When this option is set to `false` we'll only increase the time between attempts with the `initialDelayMs`. This means if we fail the first time we'll have 1 second wait, next fail we'll have 2 seconds and so on.
We can declare the maximum number of attempts (including the initial request) with `maxNumberAttempts`, otherwise, it defaults to 2 (which means one retry). If you want it to retry indefinitely, you can simply pass in `Number.POSITIVE_INFINITY`.
[For more information on the available options check out the API Docs.](../api/retry-exchange.md)
## Reacting to Different Errors
We can introduce specific triggers for the `retryExchange` to start retrying operations,
let's look at an example:
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
import { retryExchange } from '@urql/exchange-retry';
const client = new Client({
url: 'http://localhost:1234/graphql',
exchanges: [
cacheExchange,
retryExchange({
retryIf: error => {
return !!(error.graphQLErrors.length > 0 || error.networkError);
},
}),
fetchExchange,
],
});
```
In the above example we'll retry when we have `graphQLErrors` or a `networkError`, we can go
more granular and check for certain errors in `graphQLErrors`.
## Failover / Fallback
In case of a network error, e.g., when part the infrastructure is down, but a fallback GraphQL endpoint is available, e.g., from a different provider on a different domain, the `retryWith` option allows for client-side failover. This could also be used in case of a `graphQLError`, for example, when APIs are deployed via a windowing strategy, i.e., a newer version at URL X, while an older one remains at Y.
Note that finer granularity depending on custom requirements may be applicable, and that this does not allow for balancing load.
```js
const fallbackUrl = 'http://localhost:1337/anotherGraphql';
const options = {
initialDelayMs: 1000,
maxDelayMs: 15000,
randomDelay: true,
maxNumberAttempts: 2,
retryWith: (error, operation) => {
if (error.networkError) {
const context = { ...operation.context, url: fallbackUrl };
return { ...operation, context };
}
return null;
},
};
```
================================================
FILE: docs/advanced/server-side-rendering.md
================================================
---
title: Server-side Rendering
order: 3
---
# Server-side Rendering
In server-side rendered applications we often need to set our application up so that data will be
fetched on the server-side and later sent down to the client for hydration. `urql` supports this
through the `ssrExchange.`
## The SSR Exchange
The `ssrExchange` has two functions. On the server-side it's able to gather all results as they're
being fetched, which can then be serialized and sent to the client. On the client-side it's able to
use these serialized results to rehydrate and render the application without refetching this data.
To start out with the `ssrExchange` we have to add the exchange to our `Client`:
```js
import { Client, cacheExchange, fetchExchange, ssrExchange } from '@urql/core';
const isServerSide = typeof window === 'undefined';
// The `ssrExchange` must be initialized with `isClient` and `initialState`
const ssr = ssrExchange({
isClient: !isServerSide,
initialState: !isServerSide ? window.__URQL_DATA__ : undefined,
});
const client = new Client({
exchanges: [
cacheExchange,
ssr, // Add `ssr` in front of the `fetchExchange`
fetchExchange,
],
});
```
The `ssrExchange` must be initialized with the `isClient` and `initialState` options. The `isClient`
option tells the exchange whether it's on the server- or client-side. In our example we use `typeof window` to determine this, but in Webpack environments you may also be able to use `process.browser`.
Optionally, we may also choose to enable `staleWhileRevalidate`. When enabled this flag will ensure that although a result may have been rehydrated from our SSR result, another
refetch `network-only` operation will be issued, to update stale data. This is useful for statically generated sites (SSG) that may ship stale data to our application initially.
The `initialState` option should be set to the serialized data you retrieve on your server-side.
This data may be retrieved using methods on `ssrExchange()`. You can retrieve the serialized data
after server-side rendering using `ssr.extractData()`:
```js
// Extract and serialise the data like so from the `ssr` instance
// we've previously created by calling `ssrExchange()`
const data = JSON.stringify(ssr.extractData());
const markup = ''; // The render code for our framework goes here
const html = `
${markup}
`;
```
This will provide `__URQL_DATA__` globally, which we've used in our first example to inject data into
the `ssrExchange` on the client-side.
Alternatively you can also call `restoreData` as long as this call happens synchronously before the
`client` starts receiving queries.
```js
const isServerSide = typeof window === 'undefined';
const ssr = ssrExchange({ isClient: !isServerSide });
if (!isServerSide) {
ssr.restoreData(window.__URQL_DATA__);
}
```
## Using `react-ssr-prepass`
In the previous examples we've set up the `ssrExchange`, however with React this still requires us
to manually execute our queries before rendering a server-side React app [using `renderToString`
or `renderToNodeStream`](https://reactjs.org/docs/react-dom-server.html#rendertostring).
For React, `urql` has a "Suspense mode" that [allows data fetching to interrupt
rendering](https://reactjs.org/docs/concurrent-mode-suspense.html). However, Suspense is
not supported by React during server-side rendering.
Using [the `react-ssr-prepass` package](https://github.com/FormidableLabs/react-ssr-prepass) however,
we can implement a prerendering step before we let React server-side render, which allows us to
automatically fetch all data that the app requires with Suspense. This technique is commonly
referred to as a "two-pass approach", since our React element is traversed twice.
To set this up, first we'll install `react-ssr-prepass`. It has a peer dependency on `react-is`
and `react`.
```sh
yarn add react-ssr-prepass react-is react-dom
# or
npm install --save react-ssr-prepass react-is react-dom
```
Next, we'll modify our server-side code and add `react-ssr-prepass` in front of `renderToString`.
```jsx
import { renderToString } from 'react-dom/server';
import prepass from 'react-ssr-prepass';
import {
Client,
cacheExchange,
fetchExchange,
ssrExchange,
Provider,
} from 'urql';
const handleRequest = async (req, res) => {
// ...
const ssr = ssrExchange({ isClient: false });
const client = new Client({
url: 'https://??',
suspense: true, // This activates urql's Suspense mode on the server-side
exchanges: [cacheExchange, ssr, fetchExchange]
});
const element = (
);
// Using `react-ssr-prepass` this prefetches all data
await prepass(element);
// This is the usual React SSR rendering code
const markup = renderToString(element);
// Extract the data after prepass and rendering
const data = JSON.stringify(ssr.extractData());
res.status(200).send(`
${markup}
`);
};
```
It's important to set enable the `suspense` option on the `Client`, which switches it to support
React suspense.
### With Preact
If you're using Preact instead of React, there's a drop-in replacement package for
`react-ssr-prepass`, which is called `preact-ssr-prepass`. It only has a peer dependency on Preact,
and we can install it like so:
```sh
yarn add preact-ssr-prepass preact
# or
npm install --save preact-ssr-prepass preact
```
All above examples for `react-ssr-prepass` will still be the same, except that instead of
using the `urql` package we'll have to import from `@urql/preact`, and instead of `react-ssr-prepass`
we'll have to import from. `preact-ssr-prepass`.
## Next.js
If you're using [Next.js](https://nextjs.org/) you can save yourself a lot of work by using
`@urql/next`. The `@urql/next` package is set to work with Next 13.
To set up `@urql/next`, first we'll install `@urql/next` and `urql` as
peer dependencies:
```sh
yarn add @urql/next urql graphql
# or
npm install --save @urql/next urql graphql
```
We now have two ways to leverage `@urql/next`, one being part of a Server component
or being part of the general `app/` folder.
In a server component we will import from `@urql/next/rsc`
```ts
// app/page.tsx
import React from 'react';
import { cacheExchange, createClient, fetchExchange, gql } from '@urql/core';
import { registerUrql } from '@urql/next/rsc';
const makeClient = () => {
return createClient({
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
exchanges: [cacheExchange, fetchExchange],
});
};
const { getClient } = registerUrql(makeClient);
export default async function Home() {
const result = await getClient().query(PokemonsQuery, {});
return (
This is rendered as part of an RSC
{result.data.pokemons.map((x: any) => (
{x.name}
))}
);
}
```
When we aren't leveraging server components we will import the things we will
need to do a bit more setup, we go to the `client` component's layout file and
structure it as the following.
```tsx
// app/client/layout.tsx
'use client';
import { useMemo } from 'react';
import { UrqlProvider, ssrExchange, cacheExchange, fetchExchange, createClient } from '@urql/next';
export default function Layout({ children }: React.PropsWithChildren) {
const [client, ssr] = useMemo(() => {
const ssr = ssrExchange({
isClient: typeof window !== 'undefined',
});
const client = createClient({
url: 'https://trygql.formidable.dev/graphql/web-collections',
exchanges: [cacheExchange, ssr, fetchExchange],
suspense: true,
});
return [client, ssr];
}, []);
return (
{children}
);
}
```
It is important that we pass both a client as well as the `ssrExchange` to the `Provider`
this way we will be able to restore the data that Next streams to the client later on
when we are hydrating.
The next step is to query data in your client components by means of the `useQuery`
method defined in `@urql/next`.
```tsx
// app/client/page.tsx
'use client';
import Link from 'next/link';
import { Suspense } from 'react';
import { useQuery, gql } from '@urql/next';
export default function Page() {
return (
);
}
const PokemonsQuery = gql`
query {
pokemons(limit: 10) {
id
name
}
}
`;
function Pokemons() {
const [result] = useQuery({ query: PokemonsQuery });
return (
This is rendered as part of SSR
{result.data.pokemons.map((x: any) => (
{x.name}
))}
);
}
```
The data queried in the above component will be rendered on the server
and re-hydrated back on the client. When using multiple Suspense boundaries
these will also get flushed as they complete and re-hydrated.
> When data is used throughout the application we advise against
> rendering this as part of a server-component so you can benefit
> from the client-side cache.
### Invalidating data from a server-component
When data is rendered by a server component but you dispatch a mutation
from a client component the server won't automatically know that the
server-component on the client needs refreshing. You can forcefully
tell the server to do so by using the Next router and calling `.refresh()`.
```tsx
import { useRouter } from 'next/navigation';
const Todo = () => {
const router = useRouter();
const executeMutation = async () => {
await updateTodo();
router.refresh();
};
};
```
### Disabling RSC fetch caching
You can pass `fetchOptions: { cache: "no-store" }` to the `createClient`
constructor to avoid running into cached fetches with server-components.
## Legacy Next.js (pages)
If you're using [Next.js](https://nextjs.org/) with the classic `pages` you can instead use `next-urql`.
To set up `next-urql`, first we'll install `next-urql` with `react-is` and `urql` as peer dependencies:
```sh
yarn add next-urql react-is urql graphql
# or
npm install --save next-urql react-is urql graphql
```
The peer dependency on `react-is` is inherited from `react-ssr-prepass` requiring it.
Note that if you are using Next before v9.4 you'll need to polyfill fetch, this can be
done through [`isomorphic-unfetch`](https://www.npmjs.com/package/isomorphic-unfetch).
We're now able to wrap any page or `_app.js` using the `withUrqlClient` higher-order component. If
we wrap `_app.js` we won't have to wrap any individual page.
```js
// pages/index.js
import React from 'react';
import { useQuery } from 'urql';
import { withUrqlClient } from 'next-urql';
const Index = () => {
const [result] = useQuery({
query: '{ test }',
});
// ...
};
export default withUrqlClient((_ssrExchange, ctx) => ({
// ...add your Client options here
url: 'http://localhost:3000/graphql',
}))(Index);
```
The `withUrqlClient` higher-order component function accepts the usual `Client` options as
an argument. This may either just be an object, or a function that receives the Next.js'
`getInitialProps` context.
One added caveat is that these options may not include the `exchanges` option because `next-urql`
injects the `ssrExchange` automatically at the right location. If you're setting up custom exchanges
you'll need to instead provide them in the `exchanges` property of the returned client object.
```js
import { cacheExchange, fetchExchange } from '@urql/core';
import { withUrqlClient } from 'next-urql';
export default withUrqlClient(ssrExchange => ({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, ssrExchange, fetchExchange],
}))(Index);
```
Unless the component that is being wrapped already has a `getInitialProps` method, `next-urql` won't add its own SSR logic, which automatically fetches queries during
server-side rendering. This can be explicitly enabled by passing the `{ ssr: true }` option as a second argument to `withUrqlClient`.
When you are using `getStaticProps`, `getServerSideProps`, or `getStaticPaths`, you should opt-out of `Suspense` by setting the `neverSuspend` option to `true` in your `withUrqlClient` configuration.
During the prepass of your component tree `next-urql` can't know how these functions will alter the props passed to your page component. This injection
could change the `variables` used in your `useQuery`. This will lead to error being thrown during the subsequent `toString` pass, which isn't supported in React 16.
### SSR with { ssr: true }
The `withUrqlClient` only wraps our component tree with the context provider by default.
To enable SSR, the easiest way is specifying the `{ ssr: true }` option as a second
argument to `withUrqlClient`:
```js
import { cacheExchange, fetchExchange } from '@urql/core';
import { withUrqlClient } from 'next-urql';
export default withUrqlClient(
ssrExchange => ({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, ssrExchange, fetchExchange],
}),
{ ssr: true } // Enables server-side rendering using `getInitialProps`
)(Index);
```
Be aware that wrapping the `_app` component using `withUrqlClient` with the `{ ssr: true }`
option disables Next's ["Automatic Static
Optimization"](https://nextjs.org/docs/advanced-features/automatic-static-optimization) for
**all our pages**. It is thus preferred to enable server-side rendering on a per-page basis.
### SSR with getStaticProps or getServerSideProps
Enabling server-side rendering using `getStaticProps` and `getServerSideProps` is a little
more involved, but has two major benefits:
1. allows **direct schema execution** for performance optimisation
2. allows performing extra operations in those functions
To make the functions work with the `withUrqlClient` wrapper, return the `urqlState` prop
with the extracted data from the `ssrExchange`:
```js
import { withUrqlClient, initUrqlClient } from 'next-urql';
import { ssrExchange, cacheExchange, fetchExchange, useQuery } from 'urql';
const TODOS_QUERY = `
query { todos { id text } }
`;
function Todos() {
const [res] = useQuery({ query: TODOS_QUERY });
return (
{res.data.todos.map(todo => (
{todo.id} - {todo.text}
))}
);
}
export async function getStaticProps(ctx) {
const ssrCache = ssrExchange({ isClient: false });
const client = initUrqlClient(
{
url: 'your-url',
exchanges: [cacheExchange, ssrCache, fetchExchange],
},
false
);
// This query is used to populate the cache for the query
// used on this page.
await client.query(TODOS_QUERY).toPromise();
return {
props: {
// urqlState is a keyword here so withUrqlClient can pick it up.
urqlState: ssrCache.extractData(),
},
revalidate: 600,
};
}
export default withUrqlClient(
ssr => ({
url: 'your-url',
})
// Cannot specify { ssr: true } here so we don't wrap our component in getInitialProps
)(Todos);
```
The above example will make sure the page is rendered as a static-page, It's important that
you fully pre-populate your cache so in our case we were only interested in getting our todos,
if there are child components relying on data you'll have to make sure these are fetched as well.
The `getServerSideProps` and `getStaticProps` functions only run on the **server-side** — any
code used in them is automatically stripped away from the client-side bundle using the
[next-code-elimination tool](https://next-code-elimination.vercel.app/). This allows **executing
our schema directly** using `@urql/exchange-execute` if we have access to our GraphQL server:
```js
import { withUrqlClient, initUrqlClient } from 'next-urql';
import { ssrExchange, cacheExchange, fetchExchange, useQuery } from 'urql';
import { executeExchange } from '@urql/exchange-execute';
import { schema } from '@/server/graphql'; // our GraphQL server's executable schema
const TODOS_QUERY = `
query { todos { id text } }
`;
function Todos() {
const [res] = useQuery({ query: TODOS_QUERY });
return (
{res.data.todos.map(todo => (
{todo.id} - {todo.text}
))}
);
}
export async function getServerSideProps(ctx) {
const ssrCache = ssrExchange({ isClient: false });
const client = initUrqlClient(
{
url: '', // not needed without `fetchExchange`
exchanges: [
cacheExchange,
ssrCache,
executeExchange({ schema }), // replaces `fetchExchange`
],
},
false
);
await client.query(TODOS_QUERY).toPromise();
return {
props: {
urqlState: ssrCache.extractData(),
},
};
}
export default withUrqlClient(ssr => ({
url: 'your-url',
}))(Todos);
```
Direct schema execution skips one network round trip by accessing your resolvers directly
instead of performing a `fetch` API call.
### Stale While Revalidate
If we choose to use Next's static site generation (SSG or ISG) we may be embedding data in our initial payload that's stale on the client. In this case, we may want to update this data immediately after rehydration.
We can pass `staleWhileRevalidate: true` to `withUrqlClient`'s second option argument to Switch it to a mode where it'll refresh its rehydrated data immediately by issuing another network request.
```js
export default withUrqlClient(
ssr => ({
url: 'your-url',
}),
{ staleWhileRevalidate: true }
)(...);
```
Now, although on rehydration we'll receive the stale data from our `ssrExchange` first, it'll also immediately issue another `network-only` operation to update the data.
During this revalidation our stale results will be marked using `result.stale`. While this is similar to what we see with `cache-and-network` without server-side rendering, it isn't quite the same. Changing the request policy wouldn't actually refetch our data on rehydration as the `ssrExchange` is simply a replacement of a full network request. Hence, this flag allows us to treat this case separately.
### Resetting the client instance
In rare scenario's you possibly will have to reset the client instance (reset all cache, ...), this
is an uncommon scenario, and we consider it "unsafe" so evaluate this carefully for yourself.
When this does seem like the appropriate solution any component wrapped with `withUrqlClient` will receive the `resetUrqlClient`
property, when invoked this will create a new top-level client and reset all prior operations.
## Vue Suspense
In Vue 3 a [new feature was introduced](https://vuedose.tips/go-async-in-vue-3-with-suspense/) that
natively allows components to suspend while data is loading, which works universally on the server
and on the client, where a replacement loading template is rendered on a parent while data is
loading.
We've previously seen how we can change our usage of `useQuery`'s `PromiseLike` result to [make use
of Vue Suspense on the "Queries" page.](../basics/vue.md#vue-suspense)
Any component's `setup()` function can be updated to instead be an `async setup()` function, in
other words, to return a `Promise` instead of directly returning its data. This means that we can
update any `setup()` function to make use of Suspense.
On the server-side we can then use `@vue/server-renderer`'s `renderToString`, which will return a
`Promise` that resolves when all suspense-related loading is completed.
```jsx
import { createSSRApp } = from 'vue'
import { renderToString } from '@vue/server-renderer';
import urql, {
createClient,
cacheExchange,
fetchExchange,
ssrExchange
} from '@urql/vue';
const handleRequest = async (req, res) => {
// This is where we'll put our root component
const app = createSSRApp(Root)
// NOTE: All we care about here is that the SSR Exchange is included
const ssr = ssrExchange({ isClient: false });
app.use(urql, {
exchanges: [cacheExchange, ssr, fetchExchange]
});
const markup = await renderToString(app);
const data = JSON.stringify(ssr.extractData());
res.status(200).send(`
${markup}
`);
};
```
This effectively renders our Vue app on the server-side and provides the client-side data for
rehydration that we've set up in the above [SSR Exchange section](#the-ssr-exchange) to use.
================================================
FILE: docs/advanced/subscriptions.md
================================================
---
title: Subscriptions
order: 0
---
# Subscriptions
One feature of `urql` that was not mentioned in the ["Basics" sections](../basics/README.md) is `urql`'s
APIs and ability to handle GraphQL subscriptions.
## The Subscription Exchange
To add support for subscriptions we need to add the `subscriptionExchange` to our `Client`.
```js
import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [
cacheExchange,
fetchExchange,
subscriptionExchange({
forwardSubscription,
}),
],
});
```
Read more about Exchanges and how they work [on the "Authoring Exchanges"
page.](./authoring-exchanges.md) or what they are [on the "Architecture"
page.](../architecture.md)
In the above example, we add the `subscriptionExchange` to the `Client` with the default exchanges
added before it. The `subscriptionExchange` is a factory that accepts additional options and returns
the actual `Exchange` function. It does not make any assumption over the transport protocol and
scheme that is used. Instead, we need to pass a `forwardSubscription` function.
The `forwardSubscription` is called when the `subscriptionExchange` receives an `Operation`, so
typically, when you’re executing a GraphQL subscription. This will call the `forwardSubscription`
function with a GraphQL request body, in the same shape that a GraphQL HTTP API may receive it as
JSON input.
If you’re using TypeScript, you may notice that the input that `forwardSubscription` receives has
an optional `query` property. This is because of persisted query support. For some transports, the
`query` property may have to be defaulted to an empty string, which matches the GraphQL over HTTP
specification more closely.
When we define this function it must return an "Observable-like" object, which needs to follow the
[Observable spec](https://github.com/tc39/proposal-observable), which comes down to having an
object with a `.subscribe()` method accepting an observer.
### Setting up `graphql-ws`
For backends supporting `graphql-ws`, we recommend using the [graphql-ws](https://github.com/enisdenjo/graphql-ws) client.
```js
import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';
import { createClient as createWSClient } from 'graphql-ws';
const wsClient = createWSClient({
url: 'ws://localhost/graphql',
});
const client = new Client({
url: '/graphql',
exchanges: [
cacheExchange,
fetchExchange,
subscriptionExchange({
forwardSubscription(request) {
const input = { ...request, query: request.query || '' };
return {
subscribe(sink) {
const unsubscribe = wsClient.subscribe(input, sink);
return { unsubscribe };
},
};
},
}),
],
});
```
In this example, we're creating a `SubscriptionClient`, are passing in a URL and some parameters,
and are using the `SubscriptionClient`'s `request` method to create a Subscription Observable, which
we return to the `subscriptionExchange` inside `forwardSubscription`.
[Read more on the `graphql-ws` README.](https://github.com/enisdenjo/graphql-ws/blob/master/README.md)
### Setting up `subscriptions-transport-ws`
For backends supporting `subscriptions-transport-ws`, [Apollo's `subscriptions-transport-ws`
package](https://github.com/apollographql/subscriptions-transport-ws) can be used.
> The `subscriptions-transport-ws` package isn't actively maintained. If your API supports the new protocol or you can swap the package out, consider using [`graphql-ws`](#setting-up-graphql-ws) instead.
```js
import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';
import { SubscriptionClient } from 'subscriptions-transport-ws';
const subscriptionClient = new SubscriptionClient('ws://localhost/graphql', { reconnect: true });
const client = new Client({
url: '/graphql',
exchanges: [
cacheExchange,
fetchExchange,
subscriptionExchange({
forwardSubscription: request => subscriptionClient.request(request),
}),
],
});
```
In this example, we're creating a `SubscriptionClient`, are passing in a URL and some parameters,
and are using the `SubscriptionClient`'s `request` method to create a Subscription Observable, which
we return to the `subscriptionExchange` inside `forwardSubscription`.
[Read more about `subscription-transport-ws` on its README.](https://github.com/apollographql/subscriptions-transport-ws/blob/master/README.md)
### Using `fetch` for subscriptions
Some GraphQL backends (for example GraphQL Yoga) support built-in transport protocols that
can execute subscriptions via a simple HTTP fetch call.
In fact, this is how `@defer` and `@stream` directives are supported. These transports can
also be used for subscriptions.
```js
import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';
const client = new Client({
url: '/graphql',
fetchSubscriptions: true,
exchanges: [cacheExchange, fetchExchange],
});
```
In this example, we only need to enable `fetchSubscriptions: true` on the `Client`, and the
`fetchExchange` will be used to send subscriptions to the API. If your API supports this transport,
it will stream results back to the `fetchExchange`.
[You can find a code example of subscriptions via `fetch` in an example in the `urql` repository.](https://github.com/urql-graphql/urql/tree/main/examples/with-subscriptions-via-fetch)
## React & Preact
The `useSubscription` hooks comes with a similar API to `useQuery`, which [we've learned about in
the "Queries" page in the "Basics" section.](../basics/react-preact.md#queries)
Its usage is extremely similar in that it accepts options, which may contain `query` and
`variables`. However, it also accepts a second argument, which is a reducer function, similar to
what you would pass to `Array.prototype.reduce`.
It receives the previous set of data that this function has returned or `undefined`.
As the second argument, it receives the event that has come in from the subscription.
You can use this to accumulate the data over time, which is useful for a
list for example.
In the following example, we create a subscription that informs us of
new messages. We will concatenate the incoming messages so that we
can display all messages that have come in over the subscription across
events.
```js
import React from 'react';
import { useSubscription } from 'urql';
const newMessages = `
subscription MessageSub {
newMessages {
id
from
text
}
}
`;
const handleSubscription = (messages = [], response) => {
return [response.newMessages, ...messages];
};
const Messages = () => {
const [res] = useSubscription({ query: newMessages }, handleSubscription);
if (!res.data) {
return
No new messages
;
}
return (
{res.data.map(message => (
{message.from}: "{message.text}"
))}
);
};
```
As we can see, the `res.data` is being updated and transformed by
the `handleSubscription` function. This works over time, so as
new messages come in, we will append them to the list of previous
messages.
[Read more about the `useSubscription` API in the API docs for it.](../api/urql.md#usesubscription)
## Svelte
The `subscriptionStore` function in `@urql/svelte` comes with a similar API to `query`, which [we've
learned about in the "Queries" page in the "Basics" section.](../basics/svelte.md#queries)
Its usage is extremely similar in that it accepts an `operationStore`, which will typically contain
our GraphQL subscription query.
In the following example, we create a subscription that informs us of new messages.
```js
{#if !$messages.data}
No new messages
{:else}
{#each $messages.data.newMessages as message}
{message.from}: "{message.text}"
{/each}
{/if}
```
As we can see, `$messages.data` is being updated and transformed by the `$messages` subscriptionStore. This works over time, so as new messages come in, we will append them to
the list of previous messages.
`subscriptionStore` optionally accepts a second argument, a handler function, allowing custom update behavior from the subscription.
[Read more about the `subscription` API in the API docs for it.](../api/svelte.md#subscriptionstore)
## Vue
The `useSubscription` API is very similar to `useQuery`, which [we've learned about in
the "Queries" page in the "Basics" section.](../basics/vue.md#queries)
Its usage is extremely similar in that it accepts options, which may contain `query` and
`variables`. However, it also accepts a second argument, which is a reducer function, similar to
what you would pass to `Array.prototype.reduce`.
It receives the previous set of data that this function has returned or `undefined`.
As the second argument, it receives the event that has come in from the subscription.
You can use this to accumulate the data over time, which is useful for a
list for example.
In the following example, we create a subscription that informs us of
new messages. We will concatenate the incoming messages so that we
can display all messages that have come in over the subscription across
events.
```jsx
Oh no... {{error}}
{{ msg.from }}: "{{ msg.text }}"
```
As we can see, the `result.data` is being updated and transformed by
the `handleSubscription` function. This works over time, so as
new messages come in, we will append them to the list of previous
messages.
[Read more about the `useSubscription` API in the API docs for it.](../api/vue.md#usesubscription)
## One-off Subscriptions
When you're using subscriptions directly without `urql`'s framework bindings, you can use the
`Client`'s `subscription` method for one-off subscriptions. This method is similar to the ones for
mutations and subscriptions [that we've seen before on the "Core Package" page.](../basics/core.md)
This method will always [returns a Wonka stream](../architecture.md#the-wonka-library) and doesn't
have a `.toPromise()` shortcut method, since promises won't return the multiple values that a
subscription may deliver. Let's convert the above example to one without framework code, as we may
use subscriptions in a Node.js environment.
```js
import { gql } from '@urql/core';
const MessageSub = gql`
subscription MessageSub {
newMessages {
id
from
text
}
}
`;
const { unsubscribe } = client.subscription(MessageSub).subscribe(result => {
console.log(result); // { data: ... }
});
```
================================================
FILE: docs/advanced/testing.md
================================================
---
title: Testing
order: 7
---
# Testing
Testing with `urql` can be done in a multitude of ways. The most effective and straightforward
method is to mock the `Client` to force your components into a fixed state during testing.
The following examples demonstrate this method of testing for React and the `urql` package only,
however the pattern itself can be adapted for any framework-bindings of `urql`.
## Mocking the client
For the most part, urql's hooks are just adapters for talking to the urql client.
The way in which they do this is by making calls to the client via context.
- `useQuery` calls `executeQuery`
- `useMutation` calls `executeMutation`
- `useSubscription` calls `executeSubscription`
In the section ["Stream Patterns" on the "Architecture" page](../architecture.md) we've seen, that
all methods on the client operate with and return streams. These streams are created using
[the Wonka library](../architecture.md#the-wonka-library), and we're able to create streams
ourselves to mock the different states of our operations, e.g. fetching, errors, or success with data.
You'll probably use one of these utility functions to create streams:
- `never`: This stream doesn’t emit any values and never completes, which puts our `urql` code in a permanent `fetching: true` state.
- `fromValue`: This utility function accepts a value and emits it immediately, which we can use to mock a result from the server.
- `makeSubject`: Allows us to create a source and imperatively push responses, which is useful to test subscription and simulate changes, i.e. multiple states.
Creating a mock `Client` is pretty quick as we'll create an object that contains the `Client`'s methods that the React `urql` hooks use. We'll mock the appropriate `execute` functions that we need to mock a set of hooks. After we've created the mock `Client` we can wrap components with the `Provider` from `urql` and pass it.
Here's an example client mock being used while testing a component.
```tsx
import { mount } from 'enzyme';
import { Provider } from 'urql';
import { never } from 'wonka';
import { MyComponent } from './MyComponent';
it('renders', () => {
const mockClient = {
executeQuery: jest.fn(() => never),
executeMutation: jest.fn(() => never),
executeSubscription: jest.fn(() => never),
};
const wrapper = mount(
);
});
```
## Testing calls to the client
Once you have your mock setup, calls to the client can be tested.
```tsx
import { mount } from 'enzyme';
import { Provider } from 'urql';
import { MyComponent } from './MyComponent';
it('skips the query', () => {
mount(
);
expect(mockClient.executeQuery).toBeCalledTimes(0);
});
```
Testing mutations and subscriptions also work in a similar fashion.
```tsx
import { mount } from 'enzyme';
import { Provider } from 'urql';
import { MyComponent } from './MyComponent';
it('triggers a mutation', () => {
const wrapper = mount(
);
const variables = { name: 'Carla' };
wrapper.find('input').simulate('change', { currentTarget: { value: variables.name } });
wrapper.find('button').simulate('click');
expect(mockClient.executeMutation).toBeCalledTimes(1);
expect(mockClient.executeMutation).toBeCalledWith(expect.objectContaining({ variables }), {});
});
```
## Forcing states
For testing render output, or creating fixtures, you may want to force the state of your components.
### Fetching
Fetching states can be simulated by returning a stream, which never returns. Wonka provides a utility for this, aptly called `never`.
Here's a fixture, which stays in the _fetching_ state.
```tsx
import { Provider } from 'urql';
import { never } from 'wonka';
import { MyComponent } from './MyComponent';
const fetchingState = {
executeQuery: () => never,
};
export default (
);
```
### Response (success)
Response states are simulated by providing a stream, which contains a network response. For single responses, Wonka's `fromValue` function can do this for us.
**Example snapshot test of response state**
```tsx
import { mount } from 'enzyme';
import { Provider } from 'urql';
import { fromValue } from 'wonka';
import { MyComponent } from './MyComponent';
it('matches snapshot', () => {
const responseState = {
executeQuery: () =>
fromValue({
data: {
posts: [
{ id: 1, title: 'Post title', content: 'This is a post' },
{ id: 3, title: 'Final post', content: 'Final post here' },
],
},
}),
};
const wrapper = mount(
);
expect(wrapper).toMatchSnapshot();
});
```
### Response (error)
Error responses are similar to success responses, only the value in the stream is changed.
```tsx
import { Provider, CombinedError } from 'urql';
import { fromValue } from 'wonka';
const errorState = {
executeQuery: () =>
fromValue({
error: new CombinedError({
networkError: Error('something went wrong!'),
}),
}),
};
```
### Handling multiple hooks
Returning different values for many `useQuery` calls can be done by introducing conditionals into the mocked client functions.
```tsx
import { fromValue } from 'wonka';
let mockClient;
beforeEach(() => {
mockClient = () => {
executeQuery: ({ query }) => {
if (query === GET_USERS) {
return fromValue(usersResponse);
}
if (query === GET_POSTS) {
return fromValue(postsResponse);
}
};
};
});
```
The above client we've created mocks all three operations — queries, mutations and subscriptions — to always remain in the `fetching: true` state.
Generally when we're _hoisting_ our mocked client and reuse it across multiple tests we have to be
mindful not to instantiate the mocks outside of Jest's lifecycle functions (like `it`, `beforeEach`,
`beforeAll` and such) as it may otherwise reset our mocked functions' return values or
implementation.
## Subscriptions
Testing subscriptions can be done by simulating the arrival of new data over time. To do this we may use the `interval` utility from Wonka, which emits values on a timer, and for each value we can map over the response that we'd like to mock.
If you prefer to have more control on when the new data is arriving you can use the `makeSubject` utility from Wonka. You can see more details in the next section.
Here's an example of testing a list component, which uses a subscription.
```tsx
import { OperationContext, makeOperation } from '@urql/core';
import { mount } from 'enzyme';
import { Provider } from 'urql';
import { MyComponent } from './MyComponent';
it('should update the list', done => {
const mockClient = {
executeSubscription: jest.fn(query =>
pipe(
interval(200),
map((i: number) => ({
// To mock a full result, we need to pass a mock operation back as well
operation: makeOperation('subscription', query, {} as OperationContext),
data: { posts: { id: i, title: 'Post title', content: 'This is a post' } },
}))
)
),
};
let index = 0;
const wrapper = mount(
);
setTimeout(() => {
expect(wrapper.find('.list').children()).toHaveLength(index + 1); // See how many items are in the list
index++;
if (index === 2) done();
}, 200);
});
```
## Simulating changes
Simulating multiple responses can be useful, particularly testing `useEffect` calls dependent on changing query responses.
For this, a _subject_ is the way to go. In short, it's a stream that you can push responses to. The `makeSubject` function from Wonka is what you'll want to use for this purpose.
Below is an example of simulating subsequent responses (such as a cache update/refetch) in a test.
```tsx
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { Provider } from 'urql';
import { makeSubject } from 'wonka';
import { MyComponent } from './MyComponent';
const { source: stream, next: pushResponse } = makeSubject();
it('shows notification on updated data', () => {
const mockedClient = {
executeQuery: jest.fn(() => stream),
};
const wrapper = mount(
);
// First response
act(() => {
pushResponse({
data: {
posts: [{ id: 1, title: 'Post title', content: 'This is a post' }],
},
});
});
expect(wrapper.find('dialog').exists()).toBe(false);
// Second response
act(() => {
pushResponse({
data: {
posts: [
{ id: 1, title: 'Post title', content: 'This is a post' },
{ id: 1, title: 'Post title', content: 'This is a post' },
],
},
});
});
expect(wrapper.find('dialog').exists()).toBe(true);
});
```
================================================
FILE: docs/api/README.md
================================================
---
title: API
order: 9
---
# API
`urql` is a collection of multiple packages. You'll likely be using one of the framework bindings
package or exchange packages, which are all listed in this section.
Most of these packages will refer to or use utilities and types from the `@urql/core` package. [Read
more about the core package on the "Core" page.](../basics/core.md)
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
- [`@urql/core` API docs](./core.md)
- [`urql` React API docs](./urql.md)
- [`@urql/preact` Preact API docs](./preact.md)
- [`@urql/svelte` Svelte API docs](./svelte.md)
- [`@urql/exchange-graphcache` API docs](./graphcache.md)
- [`@urql/exchange-retry` API docs](./retry-exchange.md)
- [`@urql/exchange-execute` API docs](./execute-exchange.md)
- [`@urql/exchange-request-policy` API docs](./request-policy-exchange.md)
- [`@urql/exchange-auth` API docs](./auth-exchange.md)
- [`@urql/exchange-refocus` API docs](./refocus-exchange.md)
================================================
FILE: docs/api/auth-exchange.md
================================================
---
title: '@urql/exchange-auth'
order: 10
---
# Authentication Exchange
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
The `@urql/exchange-auth` package contains an addon `authExchange` for `urql` that aims to make it
easy to implement complex authentication and reauthentication flows as are typically found with JWT
token based API authentication.
## Installation and Setup
First install `@urql/exchange-auth` alongside `urql`:
```sh
yarn add @urql/exchange-auth
# or
npm install --save @urql/exchange-auth
```
You'll then need to add the `authExchange`, that this package exposes to your `Client`. The
`authExchange` is an asynchronous exchange, so it must be placed in front of all `fetchExchange`s
but after all other synchronous exchanges, like the `cacheExchange`.
```js
import { createClient, cacheExchange, fetchExchange } from 'urql';
import { authExchange } from '@urql/exchange-auth';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [
cacheExchange,
authExchange(async utils => {
return {
/* config... */
};
}),
fetchExchange,
],
});
```
The `authExchange` accepts an initialization function. This function is called when your exchange
and `Client` first start up, and must return an object of options wrapped in a `Promise`, which is
used to configure how your authentication method works.
You can use this function to first retrieve your authentication state from a kind
of local storage, or to call your API to validate your authentication state first.
The relevant configuration options, returned to the `authExchange`, then determine
how the `authExchange` behaves:
- `addAuthToOperation` must be provided to tell `authExchange` how to add authentication information
to an operation, e.g. how to add the authentication state to an operation's fetch headers.
- `willAuthError` may be provided to detect expired tokens or tell whether an operation will likely
fail due to an authentication error.
- `didAuthError` may be provided to let the `authExchange` detect authentication errors from the
API on results.
- `refreshAuth` is called when an authentication error occurs and gives you an opportunity to update
your authentication state. Afterwards, the `authExchange` will retry your operation.
[Read more examples in the documentation given here.](../advanced/authentication.md)
================================================
FILE: docs/api/core.md
================================================
---
title: '@urql/core'
order: 0
---
# @urql/core
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
The `@urql/core` package is the basis of all framework bindings. Each bindings-package,
like [`urql` for React](./urql.md) or [`@urql/preact`](./preact.md), will reuse the core logic and
reexport all exports from `@urql/core`.
Therefore if you're not accessing utilities directly, aren't in a Node.js environment, and are using
framework bindings, you'll likely want to import from your framework bindings package directly.
[Read more about `urql`'s core on the "Core Package" page.](../basics/core.md)
## Client
The `Client` manages all operations and ongoing requests to the exchange pipeline.
It accepts several options on creation.
`@urql/core` also exposes `createClient()` that is just a convenient alternative to calling `new Client()`.
| Input | Type | Description |
| ----------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `exchanges` | `Exchange[]` | An array of `Exchange`s that the client should use |
| `url` | `string` | The GraphQL API URL as used by `fetchExchange` |
| `fetchOptions` | `RequestInit \| () => RequestInit` | Additional `fetchOptions` that `fetch` in `fetchExchange` should use to make a request |
| `fetch` | `typeof fetch` | An alternative implementation of `fetch` that will be used by the `fetchExchange` instead of `window.fetch` |
| `suspense` | `?boolean` | Activates the experimental React suspense mode, which can be used during server-side rendering to prefetch data |
| `requestPolicy` | `?RequestPolicy` | Changes the default request policy that will be used. By default, this will be `cache-first`. |
| `preferGetMethod` | `?boolean \| 'force' \| 'within-url-limit'` | This is picked up by the `fetchExchange` and will force all queries (not mutations) to be sent using the HTTP GET method instead of POST if the length of the resulting URL doesn't exceed 2048 characters. When `'force'` is passed a GET request is always sent regardless of how long the resulting URL is. |
### client.executeQuery
Accepts a [`GraphQLRequest`](#graphqlrequest) and optionally `Partial`, and returns a
[`Source`](#operationresult) — a stream of query results that can be subscribed to.
Internally, subscribing to the returned source will create an [`Operation`](#operation), with
`kind` set to `'query'`, and dispatch it on the
exchanges pipeline. If no subscribers are listening to this operation anymore and unsubscribe from
the query sources, the `Client` will dispatch a "teardown" operation.
- [Instead of using this method directly, you may want to use the `client.query` shortcut
instead.](#clientquery)
- [See `createRequest` for a utility that creates `GraphQLRequest` objects.](#createrequest)
### client.executeSubscription
This is functionally the same as `client.executeQuery`, but creates operations for subscriptions
instead, with `kind` set to `'subscription'`.
### client.executeMutation
This is functionally the same as `client.executeQuery`, but creates operations for mutations
instead, with `kind` set to `'mutation'`.
A mutation source is always guaranteed to only respond with a single [`OperationResult`](#operationresult) and then complete.
### client.query
This is a shorthand method for [`client.executeQuery`](#clientexecutequery), which accepts a query
(`DocumentNode | string`) and variables separately and creates a [`GraphQLRequest`](#graphqlrequest) [`createRequest`](#createrequest) automatically.
The returned `Source` will also have an added `toPromise` method, so the stream can
be conveniently converted to a promise.
```js
import { pipe, subscribe } from 'wonka';
const { unsubscribe } = pipe(
client.query('{ test }', {
/* vars */
}),
subscribe(result => {
console.log(result); // OperationResult
})
);
// or with toPromise, which also limits this to one result
client
.query('{ test }', {
/* vars */
})
.toPromise()
.then(result => {
console.log(result); // OperationResult
});
```
[Read more about how to use this API on the "Core Package"
page.](../basics/core.md#one-off-queries-and-mutations)
### client.mutation
This is similar to [`client.query`](#clientquery), but dispatches mutations instead.
[Read more about how to use this API on the "Core Package"
page.](../basics/core.md#one-off-queries-and-mutations)
### client.subscription
This is similar to [`client.query`](#clientquery), but does not provide a `toPromise()` helper method on the streams it returns.
[Read more about how to use this API on the "Subscriptions" page.](../advanced/subscriptions.md)
### client.reexecuteOperation
This method is commonly used in _Exchanges_ to reexecute an [`Operation`](#operation) on the
`Client`. It will only reexecute when there are still subscribers for the given
[`Operation`](#operation).
For an example, this method is used by the `cacheExchange` when an
[`OperationResult`](#operationresult) is invalidated in the cache and needs to be refetched.
### client.readQuery
This method is typically used to read data synchronously from a cache. It returns an [`OperationResult`](#operationresult) if a value is returned immediately or `null` if no value is returned while cancelling all side effects.
## CombinedError
The `CombinedError` is used in `urql` to normalize network errors and `GraphQLError`s if anything
goes wrong during a GraphQL request.
| Input | Type | Description |
| --------------- | -------------------------------- | ---------------------------------------------------------------------------------- |
| `networkError` | `?Error` | An unexpected error that might've occurred when trying to send the GraphQL request |
| `graphQLErrors` | `?Array` | GraphQL Errors (if any) that were returned by the GraphQL API |
| `response` | `?any` | The raw response object (if any) from the `fetch` call |
[Read more about errors in `urql` on the "Error" page.](../basics/errors.md)
## Types
### GraphQLRequest
This often comes up as the **input** for every GraphQL request.
It consists of `query` and optionally `variables`.
| Prop | Type | Description |
| ----------- | -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `key` | `number` | A unique key that identifies this exact combination of `query` and `variables`, which is derived using a stable hash. |
| `query` | `DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| `variables` | `?object` | The variables to be used with the GraphQL request. |
The `key` property is a hash of both the `query` and the `variables`, to uniquely
identify the request. When `variables` are passed it is ensured that they're stably stringified so
that the same variables in a different order will result in the same `key`, since variables are
order-independent in GraphQL.
[A `GraphQLRequest` may be manually created using the `createRequest` helper.](#createrequest)
### OperationType
This determines what _kind of operation_ the exchanges need to perform.
This is one of:
- `'subscription'`
- `'query'`
- `'mutation'`
- `'teardown'`
The `'teardown'` operation is special in that it instructs exchanges to cancel
any ongoing operations with the same key as the `'teardown'` operation that is
received.
### Operation
The input for every exchange that informs GraphQL requests.
It extends the [`GraphQLRequest` type](#graphqlrequest) and contains these additional properties:
| Prop | Type | Description |
| --------- | ------------------ | --------------------------------------------- |
| `kind` | `OperationType` | The type of GraphQL operation being executed. |
| `context` | `OperationContext` | Additional metadata passed to exchange. |
An `Operation` also contains the `operationName` property, which is a deprecated alias of the `kind`
property and outputs a deprecation warning if it's used.
### RequestPolicy
This determines the strategy that a cache exchange should use to fulfill an operation.
When you implement a custom cache exchange it's recommended that these policies are
handled.
- `'cache-first'` (default)
- `'cache-only'`
- `'network-only'`
- `'cache-and-network'`
[Read more about request policies on the "Document Caching" page.](../basics/document-caching.md#request-policies)
### OperationContext
The context often carries options or metadata for individual exchanges, but may also contain custom
data that can be passed from almost all API methods in `urql` that deal with
[`Operation`s](#operation).
Some of these options are set when the `Client` is initialised, so in the following list of
properties you'll likely see some options that exist on the `Client` as well.
| Prop | Type | Description |
| --------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `fetchOptions` | `?RequestInit \| (() => RequestInit)` | Additional `fetchOptions` that `fetch` in `fetchExchange` should use to make a request. |
| `fetch` | `typeof fetch` | An alternative implementation of `fetch` that will be used by the `fetchExchange` instead of `window.fetch` |
| `requestPolicy` | `RequestPolicy` | An optional [request policy](../basics/document-caching.md#request-policies) that should be used specifying the cache strategy. |
| `url` | `string` | The GraphQL endpoint, when using GET you should use absolute url's |
| `meta` | `?OperationDebugMeta` | Metadata that is only available in development for devtools. |
| `suspense` | `?boolean` | Whether suspense is enabled. |
| `preferGetMethod` | `?boolean \| 'force' \| 'within-url-limit'` | Instructs the `fetchExchange` to use HTTP GET for queries. |
| `additionalTypenames` | `?string[]` | Allows you to tell the operation that it depends on certain typenames (used in document-cache.) |
It also accepts additional, untyped parameters that can be used to send more
information to custom exchanges.
### OperationResult
The result of every GraphQL request, i.e. an `Operation`. It's very similar to what comes back from
a typical GraphQL API, but slightly enriched and normalized.
| Prop | Type | Description |
| ------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `operation` | `Operation` | The operation that this is a result for |
| `data` | `?any` | Data returned by the specified query |
| `error` | `?CombinedError` | A [`CombinedError`](#combinederror) instances that wraps network or `GraphQLError`s (if any) |
| `extensions` | `?Record` | Extensions that the GraphQL server may have returned. |
| `stale` | `?boolean` | A flag that may be set to `true` by exchanges to indicate that the `data` is incomplete or out-of-date, and that the result will be updated soon. |
### ExchangeInput
This is the input that an [`Exchange`](#exchange) receives when it's initialized by the
[`Client`](#client)
| Input | Type | Description |
| --------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `forward` | `ExchangeIO` | The unction responsible for receiving an observable operation and returning a result |
| `client` | `Client` | The urql application-wide client library. Each execute method starts a GraphQL request and returns a stream of results. |
### Exchange
An exchange represents abstractions of small chunks of logic in `urql`.
They're small building blocks and similar to "middleware".
[Read more about _Exchanges_ on the "Authoring Exchanges" page.](../advanced/authoring-exchanges.md)
An exchange is defined to be a function that receives [`ExchangeInput`](#exchangeinput) and returns
an `ExchangeIO` function. The `ExchangeIO` function in turn will receive a stream of operations, and
must return a stream of results. If the exchange is purely transforming data, like the
`mapExchange` for instance, it'll call `forward`, which is the next Exchange's `ExchangeIO`
function to get a stream of results.
```js
type ExchangeIO = (Source) => Source;
type Exchange = ExchangeInput => ExchangeIO;
```
[If you haven't yet seen streams you can read more about "Stream Patterns" on the "Architecture"
page.](../architecture.md)
## Exchanges
### cacheExchange
The `cacheExchange` as [described on the "Document Caching" page.](../basics/document-caching.md). It's of type `Exchange`.
### subscriptionExchange
The `subscriptionExchange` as [described on the "Subscriptions" page.](../advanced/subscriptions.md). It's of type `Options => Exchange`.
It accepts a single input: `{ forwardSubscription }`. This is a function that
receives an enriched operation and must return an Observable-like object that
streams `GraphQLResult`s with `data` and `errors`.
The `forwardSubscription` function is commonly connected to the [`subscriptions-transport-ws`
package](https://github.com/apollographql/subscriptions-transport-ws).
### ssrExchange
The `ssrExchange` as [described on the "Server-side Rendering"
page.](../advanced/server-side-rendering.md).
It's of type `Options => Exchange`.
It accepts three inputs, `initialState` which is completely
optional and populates the server-side rendered data with
a rehydrated cache, `isClient` which can be set to
`true` or `false` to tell the `ssrExchange` whether to
write to (server-side) or read from (client-side) the cache, and
`staleWhileRevalidate` which will treat rehydrated data as stale
and refetch up-to-date data by reexecuring the operation using a `network-only` requests policy.
By default, `isClient` defaults to `true` when the `Client.suspense`
mode is disabled and to `false` when the `Client.suspense` mode
is enabled.
This can be used to extract data that has been queried on
the server-side, which is also described in the Basics section,
and is also used on the client-side to restore server-side
rendered data.
When called, this function creates an `Exchange`, which also has
two methods on it:
- `.restoreData(data)` which can be used to inject data, typically
on the client-side.
- `.extractData()` which is typically used on the server-side to
extract the server-side rendered data.
Basically, the `ssrExchange` is a small cache that collects data
during the server-side rendering pass, and allows you to populate
the cache on the client-side with the same data.
During React rehydration this cache will be emptied, and it will
become inactive and won't change the results of queries after
rehydration.
It needs to be used _after_ other caching Exchanges like the
`cacheExchange`, but before any _asynchronous_ Exchange like
the `fetchExchange`.
### debugExchange
An exchange that writes incoming `Operation`s to `console.log` and
writes completed `OperationResult`s to `console.log`.
This exchange is disabled in production and is based on the `mapExchange`.
If you'd like to customise it, you can replace it with a custom `mapExchange`.
### fetchExchange
The `fetchExchange` of type `Exchange` is responsible for sending operations of type `'query'` and
`'mutation'` to a GraphQL API using `fetch`.
### mapExchange
The `mapExchange` allows you to:
- react to or replace operations with `onOperation`,
- react to or replace results with `onResult`,
- and; react to errors in results with `onError`.
It can therefore be used to quickly react to the core events in the `Client` without writing a custom
exchange, effectively allowing you to ship your own `debugExchange`.
```ts
mapExchange({
onOperation(operation) {
console.log('operation', operation);
},
onResult(result) {
console.log('result', result);
},
});
```
It can also be used to react only to errors, which is the same as checking for `result.error`:
```ts
mapExchange({
onError(error, operation) {
console.log(`The operation ${operation.key} has errored with:`, error);
},
});
```
Lastly, it can be used to map operations and results, which may be useful to update the
`OperationContext` or perform other standard tasks that require you to wait for a result:
```ts
import { mapExchange, makeOperation } from '@urql/core';
mapExchange({
async onOperation(operation) {
// NOTE: This is only for illustration purposes
return makeOperation(operation.kind, operation, {
...operation.context,
test: true,
});
},
async onResult(result) {
// NOTE: This is only for illustration purposes
if (result.data === undefined) result.data = null;
return result;
},
});
```
### errorExchange (deprecated)
An exchange that lets you inspect errors. This can be useful for logging, or reacting to
different types of errors (e.g. logging the user out in case of a permission error).
In newer versions of `@urql/core`, it's identical to the `mapExchange` and its export has been
replaced as the `mapExchange` also allows you to pass an `onError` function.
## Utilities
### gql
This is a `gql` tagged template literal function, similar to the one that's also commonly known from
`graphql-tag`. It can be used to write GraphQL documents in a tagged template literal and returns a
parsed `DocumentNode` that's primed against the `createRequest`'s cache for `key`s.
```js
import { gql } from '@urql/core';
const SharedFragment = gql`
fragment UserFrag on User {
id
name
}
`;
gql`
query {
user
...UserFrag
}
${SharedFragment}
`;
```
Unlike `graphql-tag`, this function outputs a warning in development when names of fragments in the
document are duplicated. It does not output warnings when fragment names were duplicated globally
however.
### stringifyVariables
This function is a variation of `JSON.stringify` that sorts any object's keys that is being
stringified to ensure that two objects with a different order of keys will be stably stringified to
the same string.
```js
stringifyVariables({ a: 1, b: 2 }); // {"a":1,"b":2}
stringifyVariables({ b: 2, a: 1 }); // {"a":1,"b":2}
```
### createRequest
This utility accepts a GraphQL query of type `string | DocumentNode` and optionally an object of
variables, and returns a [`GraphQLRequest` object](#graphqlrequest).
Since the [`client.executeQuery`](#clientexecutequery) and other execute methods only accept
[`GraphQLRequest`s](#graphqlrequest), this helper is commonly used to create that request first. The
[`client.query`](#clientquery) and [`client.mutation`](#clientmutation) methods use this helper as
well to create requests.
The helper takes care of creating a unique `key` for the `GraphQLRequest`. This is a hash of the
`query` and `variables` if they're passed. The `variables` will be stringified using
[`stringifyVariables`](#stringifyvariables), which outputs a stable JSON string.
Additionally, this utility will ensure that the `query` reference will remain stable. This means
that if the same `query` will be passed in as a string or as a fresh `DocumentNode`, then the output
will always have the same `DocumentNode` reference.
### makeOperation
This utility is used to either turn a [`GraphQLRequest` object](#graphqlrequest) into a new
[`Operation` object](#operation) or to copy an `Operation`. It adds the `kind` property, and the
`operationName` alias that outputs a deprecation warning.
It accepts three arguments:
- An `Operation`'s `kind` (See [`OperationType`](#operationtype)
- A [`GraphQLRequest` object](#graphqlrequest) or another [`Operation`](#operation) that should be
copied.
- and; optionally a [partial `OperationContext` object.](#operationcontext). This argument may be
left out if the context is to be copied from the operation that may be passed as a second argument.
Hence some valid uses of the utility are:
```js
// Create a new operation from scratch
makeOperation('query', createRequest(query, variables), client.createOperationContext(opts));
// Turn an operation into a 'teardown' operation
makeOperation('teardown', operation);
// Copy an existing operation while modifying its context
makeOperation(operation.kind, operation, {
...operation.context,
preferGetMethod: true,
});
```
### makeResult
This is a helper function that converts a GraphQL API result to an
[`OperationResult`](#operationresult).
It accepts an [`Operation`](#operation), the API result, and optionally the original `FetchResponse`
for debugging as arguments, in that order.
### makeErrorResult
This is a helper function that creates an [`OperationResult`](#operationresult) for GraphQL API
requests that failed with a generic or network error.
It accepts an [`Operation`](#operation), the error, and optionally the original `FetchResponse`
for debugging as arguments, in that order.
### formatDocument
This utility is used by the [`cacheExchange`](#cacheexchange) and by
[Graphcache](../graphcache/README.md) to add `__typename` fields to GraphQL `DocumentNode`s.
### composeExchanges
This utility accepts an array of `Exchange`s and composes them into a single one.
It chains them in the order that they're given, left to right.
```js
function composeExchanges(Exchange[]): Exchange;
```
This can be used to combine some exchanges and is also used by [`Client`](#client)
to handle the `exchanges` input.
================================================
FILE: docs/api/execute-exchange.md
================================================
---
title: '@urql/exchange-execute'
order: 6
---
# Execute Exchange
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
The `@urql/exchange-execute` package contains an addon `executeExchange` for `urql` that may be used to
execute queries against a local schema. It is therefore a drop-in replacement for the default
_fetchExchange_ and useful for the server-side, debugging, or testing.
## Installation and Setup
First install `@urql/exchange-execute` alongside `urql`:
```sh
yarn add @urql/exchange-execute
# or
npm install --save @urql/exchange-execute
```
You'll then need to add the `executeExchange`, exposed by this package, to your `Client`.
It'll typically replace the `fetchExchange` or similar exchanges and must be used last if possible,
since it'll handle operations and return results.
```js
import { createClient, cacheExchange } from 'urql';
import { executeExchange } from '@urql/exchange-execute';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [
cacheExchange,
executeExchange({
/* config */
}),
],
});
```
The `executeExchange` accepts an object of options, which are all similar to the arguments that
`graphql/execution/execute` accepts. Typically you'd pass it the `schema` option, some resolvers
if your schema isn't already executable as `fieldResolver` / `typeResolver` / `rootValue`,
and a `context` value or function.
## Options
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `schema` | This is of type `GraphQLSchema` and accepts either a schema that is or isn't executable. This field is _required_ while all other fields are _optional_. |
| `rootValue` | The root value that `graphql`'s `execute` will use when starting to execute the schema. |
| `fieldResolver` | A given field resolver function. Creating an executable schema may be easier than providing this, but this resolver will be passed on to `execute` as expected. |
| `typeResolver` | A given type resolver function. Creating an executable schema may be easier than providing this, but this resolver will be passed on to `execute` as expected. |
| `context` | This may either be a function that receives an [`Operation`](./core.md#operation) and returns the context value, or just a plain context value. Similarly to a GraphQL server this is useful as all resolvers will have access to your `context` |
================================================
FILE: docs/api/graphcache.md
================================================
---
title: '@urql/exchange-graphcache'
order: 4
---
# @urql/exchange-graphcache
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
The `@urql/exchange-graphcache` package contains an addon `cacheExchange` for `urql` that may be
used to replace the default [`cacheExchange`](./core.md#cacheexchange), which switches `urql` from
using ["Document Caching"](../basics/document-caching.md) to ["Normalized
Caching"](../graphcache/normalized-caching.md).
[Read more about how to use and configure _Graphcache_ in the "Graphcache"
section](../graphcache/README.md)
## cacheExchange
The `cacheExchange` function, as exported by `@urql/exchange-graphcache`, accepts a single object of
options and returns an [`Exchange`](./core.md#exchange).
| Input | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `keys` | A mapping of key generator functions for types that are used to override the default key generation that _Graphcache_ uses to normalize data for given types. |
| `resolvers` | A nested mapping of resolvers, which are used to override the record or entity that _Graphcache_ resolves for a given field for a type. |
| `directives` | A mapping of directives, which are functions accepting directive arguments and returning a resolver, which can be referenced by `@localDirective` or `@_localDirective` in queries. |
| `updates` | A nested mapping of updater functions for mutation and subscription fields, which may be used to add side-effects that update other parts of the cache when the given subscription or mutation field is written to the cache. |
| `optimistic` | A mapping of mutation fields to resolvers that may be used to provide _Graphcache_ with an optimistic result for a given mutation field that should be applied to the cached data temporarily. |
| `schema` | A serialized GraphQL schema that is used by _Graphcache_ to resolve partial data, interfaces, and enums. The schema also used to provide helpful warnings for [schema awareness](../graphcache/schema-awareness.md). |
| `storage` | A persisted storage interface that may be provided to preserve cache data for [offline support](../graphcache/offline.md). |
| `globalIDs` | A boolean or list of typenames that have globally unique ids, this changes how graphcache internally keys the entities. This can be useful for complex interface relationships. |
| `logger` | A function that will be invoked for warning/debug/... logs |
The `@urql/exchange-graphcache` package also exports the `offlineExchange`; which is identical to
the `cacheExchange` but activates [offline support](../graphcache/offline.md) when the `storage` option is passed.
### `keys` option
This is a mapping of typenames to `KeyGenerator` functions.
```ts
interface KeyingConfig {
[typename: string]: (data: Data) => null | string;
}
```
It may be used to alter how _Graphcache_ generates the key it uses for normalization for individual
types. The key generator function may also always return `null` when a type should always be
embedded.
[Read more about how to set up `keys` in the "Key Generation" section of the "Normalized Caching"
page.](../graphcache/normalized-caching.md#key-generation)
### `resolvers` option
This configuration is a mapping of typenames to field names to `Resolver` functions.
A resolver may be defined to override the entity or record that a given field on a type should
resolve on the cache.
```ts
interface ResolverConfig {
[typeName: string]: {
[fieldName: string]: Resolver;
};
}
```
A `Resolver` receives four arguments when it's called: `parent`, `args`, `cache`, and
`info`.
| Argument | Type | Description |
| -------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `parent` | `Data` | The parent entity that the given field is on. |
| `args` | `object` | The arguments for the given field the updater is executed on. |
| `cache` | `Cache` | The cache using which data can be read or written. [See `Cache`.](#cache) |
| `info` | `Info` | Additional metadata and information about the current operation and the current field. [See `Info`.](#info) |
We can use the arguments it receives to either return new data based on just the arguments and other
cache information, but we may also read information about the parent and return new data for the
current field.
```js
{
Todo: {
createdAt(parent, args, cache) {
// Read `createdAt` on the parent but return a Date instance
const date = cache.resolve(parent, 'createdAt');
return new Date(date);
}
}
}
```
[Read more about how to set up `resolvers` on the "Computed Queries"
page.](../graphcache/local-resolvers.md)
### `updates` option
The `updates` configuration is a mapping of `'Mutation' | 'Subscription'` to field names to
`UpdateResolver` functions. An update resolver may be defined to add side-effects that run when a
given mutation field or subscription field is written to the cache. These side-effects are helpful
to update data in the cache that is implicitly changed on the GraphQL API, that _Graphcache_ can't
know about automatically.
For mutation fields that don't have an updater, Graphcache has a fallback: if a returned entity
isn't currently found in the cache, it assumes a create-mutation and invalidates cached
entities of that type. This behavior was introduced in Graphcache v7 and is skipped once an updater
for the mutation field is added.
```ts
interface UpdatesConfig {
Mutation: {
[fieldName: string]: UpdateResolver;
};
Subscription: {
[fieldName: string]: UpdateResolver;
};
}
```
An `UpdateResolver` receives four arguments when it's called: `result`, `args`, `cache`, and
`info`.
| Argument | Type | Description |
| -------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `result` | `any` | Always the entire `data` object from the mutation or subscription. |
| `args` | `object` | The arguments for the given field the updater is executed on. |
| `cache` | `Cache` | The cache using which data can be read or written. [See `Cache`.](#cache) |
| `info` | `Info` | Additional metadata and information about the current operation and the current field. [See `Info`.](#info) |
It's possible to derive more information about the current update using the `info` argument. For
instance this metadata contains the current `fieldName` of the updater which may be used to make an
updater function more reusable, along with `parentKey` and other key fields. It also contains
`variables` and `fragments` which remain the same for the entire write operation, and additionally
it may have the `error` field set to describe whether the current field is `null` because the API
encountered a `GraphQLError`.
[Read more about how to set up `updates` on the "Custom Updates"
page.](../graphcache/cache-updates.md)
### `optimistic` option
The `optimistic` configuration is a mapping of Mutation field names to `OptimisticMutationResolver`
functions, which return optimistic mutation results for given fields. These results are used by
_Graphcache_ to optimistically update the cache data, which provides an immediate and temporary
change to its data before a mutation completes.
```ts
interface OptimisticMutationConfig {
[mutationFieldName: string]: OptimisticMutationResolver;
}
```
A `OptimisticMutationResolver` receives three arguments when it's called: `variables`, `cache`, and
`info`.
| Argument | Type | Description |
| -------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `args` | `object` | The arguments that the given mutation field received. |
| `cache` | `Cache` | The cache using which data can be read or written. [See `Cache`.](#cache) |
| `info` | `Info` | Additional metadata and information about the current operation and the current field. [See `Info`.](#info) |
[Read more about how to set up `optimistic` on the "Custom Updates"
page.](../graphcache/cache-updates.md)
### `schema` option
The `schema` option may be used to pass a `IntrospectionQuery` data to _Graphcache_, in other words
it's used to provide schema information to it. This schema is then used to resolve and return
partial results when querying, which are results that the cache can partially resolve as long as no
required fields are missing.
[Read more about how to use the `schema` option on the "Schema Awareness"
page.](../graphcache/schema-awareness.md)
### `storage` option
The `storage` option is an interface of methods that are used by the `offlineExchange` to persist
the cache's data to persisted storage on the user's device. it
> **NOTE:** Offline Support is currently experimental! It hasn't been extensively tested yet and
> may not always behave as expected. Please try it out with caution!
| Method | Type | Description |
| ----------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `writeData` | `(delta: SerializedEntries) => Promise` | This provided method must be able to accept an object of key-value entries that will be persisted to the storage. This method is called as a batch of updated entries becomes ready. |
| `readData` | `() => Promise` | This provided method must be able to return a single combined object of previous key-value entries that have been previously preserved using `writeData`. It's only called on startup. |
| `writeMetadata` | `(json: SerializedRequest[]) => void` | This provided method must be able to persist metadata for the cache. For backwards compatibility it should be able to accept any JSON data. |
| `readMetadata` | `() => Promise` | This provided method must be able to read the persisted metadata that has previously been written using `writeMetadata`. It's only called on startup. |
| `onOnline` | `(cb: () => void) => void` | This method must be able to accept a callback that is called when the user's device comes back online. |
| `onCacheHydrated` | `() => void` | This method will be called when the `cacheExchange` has finished hydrating the data coming from storage. |
These options are split into three parts:
- The `writeMetadata` and `readMetadata` methods are used to persist in-progress optimistic
mutations to a storage so that they may be retried if the app has been closed while some
optimistic mutations were still in progress.
- The `writeData` and `readData` methods are used to persist any cache data. This is the normalized
data that _Graphcache_ usually keeps in memory. The `cacheExchange` will frequently call
`writeData` with a partial object of its cache data, which `readData` must then be able to return
in a single combined object on startup. We call the partial objects that `writeData` is called
with "deltas".
- The `onOnline` method is only used to receive a trigger that determines whether the user's device
has come back online, which is used to retry optimistic mutations that have previously failed due
to being offline.
The `storage` option may also be used with the `cacheExchange` instead of the `offlineExchange`, but
will then only use `readData` and `writeData` to persist its cache data. This is not full offline
support, but will rather be "persistence support".
[Read more about how to use the `storage` option on the "Offline Support"
page.](../graphcache/offline.md)
## Cache
An instance of the `Cache` interface is passed to every resolvers and updater function. It may be
used to read cached data or write cached data, which may be used in combination with the
[`cacheExchange` configuration](#cacheexchange) to alter the default behaviour of _Graphcache_.
### keyOfEntity
The `cache.keyOfEntity` method may be called with a partial `Data` object and will return the key
for that object, or `null` if it's not keyable.
An object may not be keyable if it's missing the `__typename` or `id` (which falls back to `_id`)
fields. This method does take the [`keys` configuration](#keys-option) into account.
```js
cache.keyOfEntity({ __typename: 'Todo', id: 1 }); // 'Todo:1'
cache.keyOfEntity({ __typename: 'Query' }); // 'Query'
cache.keyOfEntity({ __typename: 'Unknown' }); // null
```
There's an alternative method, `cache.keyOfField` which generates a key for a given field. This is
only rarely needed but similar to `cache.keyOfEntity`. This method accepts a field name and
optionally a field's arguments.
```js
cache.keyOfField('todo'); // 'todo'
cache.keyOfField('todo', { id: 1 }); // 'todo({"id":1})'
```
Internally, these are the keys that records and links are stored on per entity.
### resolve
This method retrieves a value or link for a given field, given a partially keyable `Data` object or
entity, a field name, and optionally the field's arguments. Internally this method accesses the
cache by using `cache.keyOfEntity` and `cache.keyOfField`.
```js
// This may resolve a link:
cache.resolve({ __typename: 'Query' }, 'todo', { id: 1 }); // 'Todo:1'
// This may also resolve records / scalar values:
cache.resolve({ __typename: 'Todo', id: 1 }, 'id'); // 1
// You can also chain multiple calls to `cache.resolve`!
cache.resolve(cache.resolve({ __typename: 'Query' }, 'todo', { id: 1 }), 'id'); // 1
```
As you can see in the last example of this code snippet, the `Data` object can also be replaced by
an entity key, which makes it possible to pass a key from `cache.keyOfEntity` or another call to
`cache.resolve` instead of the partial entity.
> **Note:** Because `cache.resolve` may return either a scalar value or another entity key, it may
> be dangerous to use in some cases. It's a good idea to make sure first whether the field you're
> reading will be a key or a value.
The `cache.resolve` method may also be called with a field key as generated by `cache.keyOfField`.
```js
cache.resolve({ __typename: 'Query' }, cache.keyOfField('todo', { id: 1 })); // 'Todo:1'
```
This specialized case is likely only going to be useful in combination with
[`cache.inspectFields`](#inspectfields).
### inspectFields
The `cache.inspectFields` method may be used to interrogate the cache about all available fields on
a specific entity. It accepts a partial entity or an entity key, like [`cache.resolve`](#resolve)'s
first argument.
When calling the method this returns an array of `FieldInfo` objects, one per field (including
differing arguments) that is known to the cache. The `FieldInfo` interface has three properties:
`fieldKey`, `fieldName`, and `arguments`:
| Argument | Type | Description |
| ----------- | ---------------- | ------------------------------------------------------------------------------- |
| `fieldName` | `string` | The field's name (without any arguments, just the name) |
| `arguments` | `object \| null` | The field's arguments, or `null` if the field doesn't have any arguments |
| `fieldKey` | `string` | The field's cache key, which is similar to what `cache.keyOfField` would return |
This works on any given entity. When calling this method the cache works in reverse on its data
structure, by parsing the entity's individual field keys.
p
```js
cache.inspectFields({ __typename: 'Query' });
/*
[
{ fieldName: 'todo', arguments: { id: 1 }, fieldKey: 'id({"id":1})' },
{ fieldName: 'todo', arguments: { id: 2 }, fieldKey: 'id({"id":2})' },
...
]
*/
```
### readFragment
`cache.readFragment` accepts a GraphQL `DocumentNode` as the first argument and a partial entity or
an entity key as the second, like [`cache.resolve`](#resolve)'s first argument.
The method will then attempt to read the entity according to the fragment entirely from the cached
data. If any data is uncached and missing it'll return `null`.
```js
import { gql } from '@urql/core';
cache.readFragment(
gql`
fragment _ on Todo {
id
text
}
`,
{ id: 1 }
); // Data or null
```
Note that the `__typename` may be left out on the partial entity if the fragment isn't on an
interface or union type, since in that case the `__typename` is already present on the fragment
itself.
If any fields on the fragment require variables, you can pass them as the third argument like so:
```js
import { gql } from '@urql/core';
cache.readFragment(
gql`
fragment _ on User {
id
permissions(byGroupId: $groupId)
}
`,
{ id: 1 }, // this identifies the fragment (User) entity
{ groupId: 5 } // any additional field variables
);
```
If you need a specific fragment in a document containing multiple you can leverage
the fourth argument like this:
```js
import { gql } from '@urql/core';
cache.readFragment(
gql`
fragment todoFields on Todo {
id
}
fragment userFields on User {
id
}
`,
{ id: 1 }, // this identifies the fragment (User) entity
undefined,
'userFields' // if not passed we take the first fragment, in this case todoFields
);
```
[Read more about using `readFragment` on the ["Local Resolvers"
page.](../graphcache/local-resolvers.md#reading-a-fragment)
### readQuery
The `cache.readQuery` method is similar to `cache.readFragment`, but instead of reading a fragment
from cache, it reads an entire query. The only difference between how these two methods are used is
`cache.readQuery`'s input, which is an object instead of two arguments.
The method accepts a `{ query, variables }` object as the first argument, where `query` may either
be a `DocumentNode` or a `string` and variables may optionally be an object.
```js
cache.readQuery({
query: `
query ($id: ID!) {
todo(id: $id) { id, text }
}
`,
variables: {
id: 1
}
); // Data or null
```
[Read more about using `readQuery` on the ["Local Resolvers"
page.](../graphcache/local-resolvers.md#reading-a-query)
### link
Corresponding to [`cache.resolve`](#resolve), the `cache.link` method allows
links in the cache to be updated. While the `cache.resolve` method reads both
records and links from the cache, the `cache.link` method will only ever write
links as fragments (See [`cache.writeFragment`](#writefragment) below) are more
suitable for updating scalar data in the cache.
The arguments for `cache.link` are identical to [`cache.resolve`](#resolve) and
the field's arguments are optional. However, the last argument must always be
a link, meaning `null`, an entity key, a keyable entity, or a list of these.
In other words, `cache.link` accepts an entity to write to as its first argument,
with the same arguments as `cache.keyOfEntity`. It then accepts one or two arguments
that are passed to `cache.keyOfField` to get the targeted field key. And lastly,
you may pass a list or a single entity (or an entity key).
```js
// Link Query.todo field to a todo item
cache.link({ __typename: 'Query' }, 'todo', { __typename: 'Todo', id: 1 });
// You may also pass arguments instead:
cache.link({ __typename: 'Query' }, 'todo', { id: 1 }, { __typename: 'Todo', id: 1 });
// Or use entity keys instead of the entities themselves:
cache.link('Query', 'todo', cache.keyOfEntity({ __typename: 'Todo', id: 1 }));
```
The method may [output a
warning](../graphcache/errors.md#12-cant-generate-a-key-for-writefragment-or-link) when any of the
entities were passed as objects but aren't keyable, which is useful when a scalar or a non-keyable
object have been passed to `cache.link` accidentally.
### writeFragment
Corresponding to [`cache.readFragment`](#readfragments), the `cache.writeFragment` method allows
data in the cache to be updated.
The arguments for `cache.writeFragment` are identical to [`cache.readFragment`](#readfragment),
however the second argument, `data`, should not only contain properties that are necessary to derive
an entity key from the given data, but also the fields that will be written:
```js
import { gql } from '@urql/core';
cache.writeFragment(
gql`
fragment _ on Todo {
text
}
`,
{ id: 1, text: 'New Todo Text' }
);
```
In the example we can see that the `writeFragment` method returns `undefined`. Furthermore we pass
`id` in our `data` object so that an entity key can be written, but the fragment itself doesn't have
to include these fields.
If you need a specific fragment in a document containing multiple you can leverage
the fourth argument like this:
```js
import { gql } from '@urql/core';
cache.writeFragment(
gql`
fragment todoFields on Todo {
id
text
}
fragment userFields on User {
id
name
}
`,
{ id: 1, name: 'New Name' }
undefined,
'userFields' // if not passed we take the first fragment, in this case todoFields
);
```
[Read more about using `writeFragment` on the ["Custom Updates"
page.](../graphcache/cache-updates.md#cachewritefragment)
### updateQuery
Similarly to [`cache.writeFragment`](#writefragment), there's an analogous method for
[`cache.readQuery`](#readquery) that may be used to update query data.
The `cache.updateQuery` method accepts the same `{ query, variables }` object input as its first
argument, which is the query we'd like to write to the cache. As a second argument the method
accepts an updater function. This function will be called with the query data that is already in the
cache (which may be `null` if the data is uncached) and must return the new data that should be
written to the cache.
```js
const TodoQuery = `
query ($id: ID!) {
todo(id: $id) { id, text }
}
`;
cache.updateQuery({ query: TodoQuery, variables: { id: 1 } }, data => {
if (!data) return null;
data.todo.text = 'New Todo Text';
return data;
});
```
As we can see, our updater may return `null` to cancel updating any data, which we do in case the
query data is uncached.
We can also see that data can simply be mutated and doesn't have to be altered immutably. This is
because all data from the cache is already a deep copy and hence we can do to it whatever we want.
[Read more about using `updateQuery` on the "Custom Updates"
page.](../graphcache/cache-updates.md#cacheupdatequery)
### invalidate
The `cache.invalidate` method can be used to delete (i.e. "evict") an entity from the cache
entirely. This will cause it to disappear from all queries in _Graphcache_.
Its arguments are identical to [`cache.resolve`](#resolve).
Since deleting an entity will lead to some queries containing missing and uncached data, calling
`invalidate` may lead to additional GraphQL requests being sent, unless you're using [_Graphcache_'s
"Schema Awareness" feature](../graphcache/schema-awareness.md), which takes optional fields into
account.
This method accepts a partial entity or an entity key as its first argument, similar to
[`cache.resolve`](#resolve)'s first argument.
```js
cache.invalidate({ __typename: 'Todo', id: 1 }); // Invalidates Todo:1
```
Additionally `cache.invalidate` may be used to delete specific fields only, which can be useful when
for instance a list is supposed to be evicted from cache, where a full invalidation may be
impossible. This is often the case when a field on the root `Query` needs to be deleted.
This method therefore accepts two additional arguments, similar to [`cache.resolve`](#resolve).
```js
// Invalidates `Query.todos` with the `first: 10` argument:
cache.invalidate('Query', 'todos', { first: 10 });
```
## Info
This is a metadata object that is passed to every resolver and updater function. It contains basic
information about the current GraphQL document and query, and also some information on the current
field that a given resolver or updater is called on.
| Argument | Type | Description |
| ---------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parent` | `Data` | The field's parent entity's data, as it was written or read up until now, which means it may be incomplete. [Use `cache.resolve`](#resolve) to read from it. |
| `parentTypeName` | `string` | The field's parent entity's typename |
| `parentKey` | `string` | The field's parent entity's cache key (if any) |
| `parentFieldKey` | `string` | The current key's cache key, which is the parent entity's key combined with the current field's key (This is mostly obsolete) |
| `fieldName` | `string` | The current field's name |
| `fragments` | `{ [name: string]: FragmentDefinitionNode }` | A dictionary of fragments from the current GraphQL document |
| `variables` | `object` | The current GraphQL operation's variables (may be an empty object) |
| `error` | `GraphQLError \| undefined` | The current GraphQLError for a given field. This will always be `undefined` for resolvers and optimistic updaters, but may be present for updaters when the API has returned an error for a given field. |
| `partial` | `?boolean` | This may be set to `true` at any point in time (by your custom resolver or by _Graphcache_) to indicate that some data is uncached and missing |
| `optimistic` | `?boolean` | This is only `true` when an optimistic mutation update is running |
> **Note:** Using `info` is regarded as a last resort. Please only use information from it if
> there's no other solution to get to the metadata you need. We don't regard the `Info` API as
> stable and may change it with a simple minor version bump.
## The `/extras` import
The `extras` subpackage is published with _Graphcache_ and contains helpers and utilities that don't
have to be included in every app or aren't needed by all users of _Graphcache_.
All utilities from extras may be imported from `@urql/exchange-graphcache/extras`.
Currently the `extras` subpackage only contains the [pagination resolvers that have been mentioned
on the "Computed Queries" page.](../graphcache/local-resolvers.md#pagination)
### simplePagination
Accepts a single object of optional options and returns a resolver that can be inserted into the
[`cacheExchange`'s](#cacheexchange) [`resolvers` configuration.](#resolvers-option)
| Argument | Type | Description |
| ---------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `offsetArgument` | `?string` | The field arguments' property, as passed to the resolver, that contains the current offset, i.e. the number of items to be skipped. Defaults to `'skip'`. |
| `limitArgument` | `?string` | The field arguments' property, as passed to the resolver, that contains the current page size limit, i.e. the number of items on each page. Defaults to `'limit'`. |
| `mergeMode` | `'after' \| 'before'` | This option defines whether pages are merged before or after preceding ones when paginating. Defaults to `'after'`. |
Once set up, the resulting resolver is able to automatically concatenate all pages of a given field
automatically. Queries to this resolvers will from then on only return the infinite, combined list
of all pages.
[Read more about `simplePagination` on the "Computed Queries"
page.](../graphcache/local-resolvers.md#simple-pagination)
### relayPagination
Accepts a single object of optional options and returns a resolver that can be inserted into the
[`cacheExchange`'s](#cacheexchange) [`resolvers` configuration.](#resolvers-option)
| Argument | Type | Description |
| ----------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mergeMode` | `'outwards' \| 'inwards'` | With Relay pagination, pages can be queried forwards and backwards using `after` and `before` cursors. This option defines whether pages that have been queried backwards should be concatenated before (outwards) or after (inwards) all pages that have been queried forwards. |
Once set up, the resulting resolver is able to automatically concatenate all pages of a given field
automatically. Queries to this resolvers will from then on only return the infinite, combined list
of all pages.
[Read more about `relayPagnation` on the "Computed Queries"
page.](../graphcache/local-resolvers.md#relay-pagination)
## The `/default-storage` import
The `default-storage` subpackage is published with _Graphcache_ and contains a default storage
interface that may be used with the [`storage` option.](#storage-option)
It contains the `makeDefaultStorage` export which is a factory function that accepts a few options
and returns a full [storage interface](#storage-option). This storage by default persists to
[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).
| Argument | Type | Description |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idbName` | `string` | The name of the IndexedDB database that is used and created if needed. By default this is set to `"graphcache-v3"` |
| `maxAge` | `number` | The maximum age of entries that the storage should use in whole days. By default the storage will discard entries that are older than seven days. |
================================================
FILE: docs/api/preact.md
================================================
---
title: '@urql/preact'
order: 2
---
# @urql/preact
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
The `@urql/preact` API is the same as the React `urql` API.
Please refer to [the "urql" API docs](./urql.md) for details on the Preact API.
================================================
FILE: docs/api/refocus-exchange.md
================================================
---
title: '@urql/exchange-refocus'
order: 11
---
# Refocus Exchange
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
`@urql/exchange-refocus` is an exchange for the `urql` that tracks currently active operations and redispatches them when the
window regains focus
## Quick Start Guide
First install `@urql/exchange-refocus` alongside `urql`:
```sh
yarn add @urql/exchange-refocus
# or
npm install --save @urql/exchange-refocus
```
Then add it to your `Client`, preferably in front of your `cacheExchange`
```js
import { createClient, cacheExchange, fetchExchange } from 'urql';
import { refocusExchange } from '@urql/exchange-refocus';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [refocusExchange(), cacheExchange, fetchExchange],
});
```
================================================
FILE: docs/api/request-policy-exchange.md
================================================
---
title: '@urql/exchange-request-policy'
order: 9
---
# Request Policy Exchange
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
The `@urql/exchange-request-policy` package contains an addon `requestPolicyExchange` for `urql`
that may be used to upgrade [Operations' Request Policies](./core.md#requestpolicy) on a
time-to-live basis.
[Read more about request policies on the "Document Caching" page.](../basics/document-caching.md#request-policies)
This exchange will conditionally upgrade `cache-first` and `cache-only` operations to use
`cache-and-network`, so that the client gets an opportunity to update its cached data, when the
operation hasn't been seen within the given `ttl` time. This is often preferable to setting the
default policy to `cache-and-network` to avoid an unnecessarily high amount of requests to be sent
to the API when switching pages.
## Installation and Setup
First install `@urql/exchange-request-policy` alongside `urql`:
```sh
yarn add @urql/exchange-request-policy
# or
npm install --save @urql/exchange-request-policy
```
Then add it to your `Client`, preferably in front of the `cacheExchange` and in front of any asynchronous
exchanges, like the `fetchExchange`:
```js
import { createClient, cacheExchange, fetchExchange } from 'urql';
import { requestPolicyExchange } from '@urql/exchange-request-policy';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [
requestPolicyExchange({
/* config */
}),
cacheExchange,
fetchExchange,
],
});
```
## Options
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ttl` | The "time-to-live" until an `Operation` will be upgraded to the `cache-and-network` policy in milliseconds. By default 5 minutes is set. |
| `shouldUpgrade` | An optional function that receives an `Operation` as the only argument and may return `true` or `false` depending on whether an operation should be upgraded. This can be used to filter out operations that should never be upgraded to `cache-and-network`. |
================================================
FILE: docs/api/retry-exchange.md
================================================
---
title: '@urql/exchange-retry'
order: 5
---
# Retry Exchange
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
The `@urql/exchange-retry` package contains an addon `retryExchange` for `urql` that may be used to
let failed operations be retried, typically when a previous operation has failed with a network
error.
[Read more about how to use and configure the `retryExchange` on the "Retry Operations"
page.](../advanced/retry-operations.md)
## Options
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `initialDelayMs` | Specify at what interval the `retrying` should start, this means that if we specify `1000` that when our `operation` fails we'll wait 1 second and then retry it. |
| `maxDelayMs` | The maximum delay between retries. The `retryExchange` will keep increasing the time between retries so that the server doesn't receive simultaneous requests it can't complete. This time between requests will increase with a random `back-off` factor applied to the `initialDelayMs`, read more about the [thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem). |
| `randomDelay` | Allows the randomized delay described above to be disabled. When this option is set to `false` there will be exactly a `initialDelayMs` wait between each retry. |
| `maxNumberAttempts` | Defines the maximum number of attempts (including the initial request). For example, `2` means one retry after the initial attempt. |
| `retryIf` | Apply a custom test to the returned error to determine whether it should be retried. |
| `retryWith` | Apply a transform function allowing you to selectively replace a retried `Operation` or return a nullish value. This will act like `retryIf` where a truthy value retries (`retryIf` takes precedence and overrides this function.) |
================================================
FILE: docs/api/svelte.md
================================================
---
title: '@urql/svelte'
order: 3
---
# Svelte API
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
## queryStore
The `queryStore` factory accepts properties as inputs and returns a Svelte pausable, readable store
of results, with type `OperationResultStore & Pausable`.
| Argument | Type | Description |
| --------------- | -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `client` | `Client` | The [`Client`](./core.md#Client) to use for the operation. |
| `query` | `string \| DocumentNode \` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| `variables` | `?object` | The variables to be used with the GraphQL request. |
| `requestPolicy` | `?RequestPolicy` | An optional [request policy](./core.md#requestpolicy) that should be used specifying the cache strategy. |
| `pause` | `?boolean` | A boolean flag instructing [execution to be paused](../basics/vue.md#pausing-usequery). |
| `context` | `?object` | Holds the contextual information for the query. |
This store is pausable, which means that the result has methods on it to `pause()` or `resume()`
the subscription of the operation.
[Read more about how to use the `queryStore` API on the "Queries" page.](../basics/svelte.md#queries)
## mutationStore
The `mutationStore` factory accepts properties as inputs and returns a Svelte readable store of a result.
| Argument | Type | Description |
| ----------- | -------------------------- | ---------------------------------------------------------------------------------- |
| `client` | `Client` | The [`Client`](./core.md#Client) to use for the operation. |
| `query` | `string \| DocumentNode \` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| `variables` | `?object` | The variables to be used with the GraphQL request. |
| `context` | `?object` | Holds the contextual information for the query. |
[Read more about how to use the `mutation` API on the "Mutations"
page.](../basics/svelte.md#mutations)
## subscriptionStore
The `subscriptionStore` utility function accepts the same inputs as `queryStore` does as its first
argument, [see above](#querystore).
The function also optionally accepts a second argument, a `handler` function. This function has the
following type signature:
```js
type SubscriptionHandler = (previousData: R | undefined, data: T) => R;
```
This function will be called with the previous data (or `undefined`) and the new data that's
incoming from a subscription event, and may be used to "reduce" the data over time, altering the
value of `result.data`.
[Read more about how to use the `subscription` API on the "Subscriptions"
page.](../advanced/subscriptions.md#svelte)
## OperationResultStore
A Svelte Readble store of an [`OperationResult`](./core.md#operationresult).
This store will be updated as the incoming data changes.
| Prop | Type | Description |
| ------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data` | `?any` | Data returned by the specified query |
| `error` | `?CombinedError` | A [`CombinedError`](./core.md#combinederror) instances that wraps network or `GraphQLError`s (if any) |
| `extensions` | `?Record` | Extensions that the GraphQL server may have returned. |
| `stale` | `boolean` | A flag that may be set to `true` by exchanges to indicate that the `data` is incomplete or out-of-date, and that the result will be updated soon. |
| `fetching` | `boolean` | A flag that indicates whether the operation is currently in progress, which means that the `data` and `error` is out-of-date for the given inputs. |
## Pausable
The `queryStore` and `subscriptionStore`'s stores are pausable. This means they inherit the
following properties from the `Pausable` store.
| Prop | Type | Description |
| ----------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `isPaused$` | `Readable` | A Svelte readable store indicating whether the operation is currently paused. Essentially, this is equivalent to `!fetching` |
| `pause()` | `pause(): void` | This method pauses the ongoing operation. |
| `resume()` | `resume(): void` | This method resumes the previously paused operation. |
## Context API
In `urql`'s Svelte bindings, the [`Client`](./core.md#client) is passed into the factories for
stores above manually. This is to cater to greater flexibility. However, for convenience's sake,
instead of keeping a `Client` singleton, we may also use [Svelte's Context
API](https://svelte.dev/tutorial/context-api).
`@urql/svelte` provides wrapper functions around Svelte's [`setContext`](https://svelte.dev/docs#run-time-svelte-setcontext) and
[`getContext`](https://svelte.dev/docs#run-time-svelte-getcontext) functions:
- `setContextClient`
- `getContextClient`
- `initContextClient` (a shortcut for `createClient` + `setContextClient`)
================================================
FILE: docs/api/urql.md
================================================
---
title: urql (React)
order: 1
---
# React API
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
## useQuery
Accepts a single required options object as an input with the following properties:
| Prop | Type | Description |
| --------------- | ------------------------ | -------------------------------------------------------------------------------------------------------- |
| `query` | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| `variables` | `?object` | The variables to be used with the GraphQL request. |
| `requestPolicy` | `?RequestPolicy` | An optional [request policy](./core.md#requestpolicy) that should be used specifying the cache strategy. |
| `pause` | `?boolean` | A boolean flag instructing [execution to be paused](../basics/react-preact.md#pausing-usequery). |
| `context` | `?object` | Holds the contextual information for the query. |
This hook returns a tuple of the shape `[result, executeQuery]`.
- The `result` is an object with the shape of an [`OperationResult`](./core.md#operationresult) with
an added `fetching: boolean` property, indicating whether the query is being fetched.
- The `executeQuery` function optionally accepts
[`Partial`](./core.md#operationcontext) and reexecutes the current query when
it's called. When `pause` is set to `true` this executes the query, overriding the otherwise
paused hook.
[Read more about how to use the `useQuery` API on the "Queries" page.](../basics/react-preact.md#queries)
## useMutation
Accepts a single `query` argument of type `string | DocumentNode` and returns a tuple of the shape
`[result, executeMutation]`.
- The `result` is an object with the shape of an [`OperationResult`](./core.md#operationresult) with
an added `fetching: boolean` property, indicating whether the mutation is being executed.
- The `executeMutation` function accepts variables and optionally
[`Partial`](./core.md#operationcontext) and may be used to start executing a
mutation. It returns a `Promise` resolving to an [`OperationResult`](./core.md#operationresult).
[Read more about how to use the `useMutation` API on the "Mutations"
page.](../basics/react-preact.md#mutations)
## useSubscription
Accepts a single required options object as an input with the following properties:
| Prop | Type | Description |
| ----------- | ------------------------ | ------------------------------------------------------------------------------------------------ |
| `query` | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| `variables` | `?object` | The variables to be used with the GraphQL request. |
| `pause` | `?boolean` | A boolean flag instructing [execution to be paused](../basics/react-preact.md#pausing-usequery). |
| `context` | `?object` | Holds the contextual information for the query. |
The hook optionally accepts a second argument, which may be a handler function with a type signature
of:
```js
type SubscriptionHandler = (previousData: R | undefined, data: T) => R;
```
This function will be called with the previous data (or `undefined`) and the new data that's
incoming from a subscription event, and may be used to "reduce" the data over time, altering the
value of `result.data`.
This hook returns a tuple of the shape `[result, executeSubscription]`.
- The `result` is an object with the shape of an [`OperationResult`](./core.md#operationresult).
- The `executeSubscription` function optionally accepts
[`Partial`](./core.md#operationcontext) and restarts the current subscription when
it's called. When `pause` is set to `true` this starts the subscription, overriding the otherwise
paused hook.
The `fetching: boolean` property on the `result` may change to `false` when the server proactively
ends the subscription. By default, `urql` is unable able to start subscriptions, since this requires
some additional setup.
[Read more about how to use the `useSubscription` API on the "Subscriptions"
page.](../advanced/subscriptions.md)
## Query Component
This component is a wrapper around [`useQuery`](#usequery), exposing a [render prop
API](https://reactjs.org/docs/render-props.html) for cases where hooks aren't desirable.
The API of the `Query` component mirrors the API of [`useQuery`](#usequery). The props that ``
accepts are the same as `useQuery`'s options object.
A function callback must be passed to `children` that receives the query result and must return a
React element. The second argument of the hook's tuple, `executeQuery` is passed as an added property
on the query result.
## Mutation Component
This component is a wrapper around [`useMutation`](#usemutation), exposing a [render prop
API](https://reactjs.org/docs/render-props.html) for cases where hooks aren't desirable.
The `Mutation` component accepts a `query` prop, and a function callback must be passed to `children`
that receives the mutation result and must return a React element. The second argument of
`useMutation`'s returned tuple, `executeMutation` is passed as an added property on the mutation
result object.
## Subscription Component
This component is a wrapper around [`useSubscription`](#usesubscription), exposing a [render prop
API](https://reactjs.org/docs/render-props.html) for cases where hooks aren't desirable.
The API of the `Subscription` component mirrors the API of [`useSubscription`](#usesubscription).
The props that `` accepts are the same as `useSubscription`'s options object, with an
added, optional `handler` prop that may be passed, which for the `useSubscription` hook is instead
the second argument.
A function callback must be passed to `children` that receives the subscription result and must
return a React element. The second argument of the hook's tuple, `executeSubscription` is passed as
an added property on the subscription result.
## Context
`urql` is used in React by adding a provider around where the [`Client`](./core.md#client) is
supposed to be used. Internally this means that `urql` creates a
[React Context](https://reactjs.org/docs/context.html).
All created parts of this context are exported by `urql`, namely:
- `Context`
- `Provider`
- `Consumer`
To keep examples brief, `urql` creates a default client with the `url` set to `'/graphql'`. This
client will be used when no `Provider` wraps any of `urql`'s hooks. However, to prevent this default
client from being used accidentally, a warning is output in the console for the default client.
### useClient
`urql` also exports a `useClient` hook, which is a convenience wrapper like the following:
```js
import React from 'react';
import { Context } from 'urql';
const useClient = () => React.useContext(Context);
```
However, this hook is also responsible for outputting the default client warning that's mentioned
above, and should thus be preferred over manually using `useContext` with `urql`'s `Context`.
================================================
FILE: docs/api/vue.md
================================================
---
title: '@urql/vue'
order: 3
---
# Vue API
> **Note:** These API docs are deprecated as we now keep TSDocs in all published packages.
> You can view TSDocs while using these packages in your editor, as long as it supports the
> TypeScript Language Server.
> We're planning to replace these API docs with a separate web app soon.
## useQuery
Accepts a single required options object as an input with the following properties:
| Prop | Type | Description |
| --------------- | ------------------------ | -------------------------------------------------------------------------------------------------------- |
| `query` | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| `variables` | `?object` | The variables to be used with the GraphQL request. |
| `requestPolicy` | `?RequestPolicy` | An optional [request policy](./core.md#requestpolicy) that should be used specifying the cache strategy. |
| `pause` | `?boolean` | A boolean flag instructing [execution to be paused](../basics/vue.md#pausing-usequery). |
| `context` | `?object` | Holds the contextual information for the query. |
Each of these inputs may also be [reactive](https://v3.vuejs.org/api/refs-api.html) (e.g. a `ref`)
and are allowed to change over time which will issue a new query.
This function returns an object with the shape of an [`OperationResult`](./core.md#operationresult)
with an added `fetching` property, indicating whether the query is currently being fetched and an
`isPaused` property which will indicate whether `useQuery` is currently paused and won't
automatically start querying.
All of the properties on this result object are also marked as
[reactive](https://v3.vuejs.org/api/refs-api.html) using `ref` and will update accordingly as the
query is executed.
The result furthermore carries several utility methods:
| Method | Description |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pause()` | This will pause automatic querying, which is equivalent to setting `pause.value = true` |
| `resume()` | This will resume a paused automatic querying, which is equivalent to setting `pause.value = false` |
| `executeQuery(opts)` | This will execute a new query with the given partial [`Partial`](./core.md#operationcontext) regardless of whether the query is currently paused or not. This also returns the result object again for chaining. |
Furthermore the returned result object of `useQuery` is also a `PromiseLike`, which allows you to
take advantage of [Vue 3's experimental Suspense feature.](https://vuedose.tips/go-async-in-vue-3-with-suspense/)
When the promise is used, e.g. you `await useQuery(...)` then the `PromiseLike` will only resolve
once a result from the API is available.
[Read more about how to use the `useQuery` API on the "Queries" page.](../basics/vue.md#queries)
## useMutation
Accepts a single `query` argument of type `string | DocumentNode` and returns a result object with
the shape of an [`OperationResult`](./core.md#operationresult) with an added `fetching` property.
All of the properties on this result object are also marked as
[reactive](https://v3.vuejs.org/api/refs-api.html) using `ref` and will update accordingly as the
mutation is executed.
The object also carries a special `executeMutation` method, which accepts variables and optionally a
[`Partial`](./core.md#operationcontext) and may be used to start executing a
mutation. It returns a `Promise` resolving to an [`OperationResult`](./core.md#operationresult)
[Read more about how to use the `useMutation` API on the "Mutations"
page.](../basics/vue.md#mutations)
## useSubscription
Accepts a single required options object as an input with the following properties:
| Prop | Type | Description |
| ----------- | ------------------------ | --------------------------------------------------------------------------------------- |
| `query` | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| `variables` | `?object` | The variables to be used with the GraphQL request. |
| `pause` | `?boolean` | A boolean flag instructing [execution to be paused](../basics/vue.md#pausing-usequery). |
| `context` | `?object` | Holds the contextual information for the subscription. |
Each of these inputs may also be [reactive](https://v3.vuejs.org/api/refs-api.html) (e.g. a `ref`)
and are allowed to change over time which will issue a new query.
`useSubscription` also optionally accepts a second argument, which may be a handler function with
a type signature of:
```js
type SubscriptionHandler = (previousData: R | undefined, data: T) => R;
```
This function will be called with the previous data (or `undefined`) and the new data that's
incoming from a subscription event, and may be used to "reduce" the data over time, altering the
value of `result.data`.
This function returns an object with the shape of an [`OperationResult`](./core.md#operationresult)
with an added `fetching` property, indicating whether the subscription is currently running and an
`isPaused` property which will indicate whether `useSubscription` is currently paused.
All of the properties on this result object are also marked as
[reactive](https://v3.vuejs.org/api/refs-api.html) using `ref` and will update accordingly as the
query is executed.
The result furthermore carries several utility methods:
| Method | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pause()` | This will pause the subscription, which is equivalent to setting `pause.value = true` |
| `resume()` | This will resume the subscription, which is equivalent to setting `pause.value = false` |
| `executeSubscription(opts)` | This will start a new subscription with the given partial [`Partial`](./core.md#operationcontext) regardless of whether the subscription is currently paused or not. This also returns the result object again for chaining. |
[Read more about how to use the `useSubscription` API on the "Subscriptions"
page.](../advanced/subscriptions.md#vue)
## useClientHandle
The `useClientHandle()` function may, like the other `use*` functions, be called either in
`setup()` or another lifecycle hook, and returns a so called "client handle". Using this `handle` we
can access the [`Client`](./core.md#client) directly via the `client` property or call the other
`use*` functions as methods, which will be directly bound to this `client`. This may be useful when
chaining these methods inside an `async setup()` lifecycle function.
| Method | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `client` | Contains the raw [`Client`](./core.md#client) reference, which allows the `Client` to be used directly. |
| `useQuery(...)` | Accepts the same arguments as the `useQuery` function, but will always use the `Client` from the handle's context. |
| `useMutation(...)` | Accepts the same arguments as the `useMutation` function, but will always use the `Client` from the handle's context. |
| `useSubscription(...)` | Accepts the same arguments as the `useSubscription` function, but will always use the `Client` from the handle's context. |
## Context API
In Vue the [`Client`](./core.md#client) is provided either to your app or to a parent component of a
given subtree and is then subsequently injected whenever one of the above composition functions is
used.
You can provide the `Client` from any of your components using the `provideClient` function.
Alternatively, `@urql/vue` also has a default export of a [Vue Plugin function](https://v3.vuejs.org/guide/plugins.html#using-a-plugin).
Both `provideClient` and the plugin function either accept an [instance of
`Client`](./core.md#client) or the same options that `createClient` accepts as inputs.
================================================
FILE: docs/architecture.md
================================================
---
title: Architecture
order: 3
---
# Architecture
`urql` is a highly customizable and flexible GraphQL client.
As you use it in your app, it's split into three parts:
- Bindings — such as for React, Preact, Vue, or Svelte — which interact with `@urql/core`'s
`Client`.
- The Client — as created [with the core `@urql/core` package](./basics/core.md), which interacts with "exchanges" to execute GraphQL
operations, and which you can also use directly.
- Exchanges, which provide functionality like fetching or caching to the `Client`.
By default, `urql` aims to provide the minimal amount of features that allow us to build an app
quickly. However, `urql` has also been designed to be a GraphQL Client
that grows with our usage and demands. As we go from building our smallest or first GraphQL apps to
utilising its full functionality, we have tools at our disposal to extend and customize `urql` to
our liking.
## Using GraphQL Clients
You may have worked with a GraphQL API previously and noticed that using GraphQL in your app can be
as straightforward as sending a plain HTTP request with your query to fetch some data.
GraphQL also provides an opportunity to abstract away a lot of the manual work that goes with
sending these queries and managing the data. Ultimately, this lets you focus on building
your app without having to handle the technical details of state management in detail.
Specifically, `urql` simplifies three common aspects of using GraphQL:
- Sending queries and mutations and receiving results _declaratively_
- Abstracting _caching_ and state management internally
- Providing a central point of _extensibility_ and integration with your API
In the following sections we'll talk about the way that `urql` solves these three problems and how the logic is abstracted away internally.
## Requests and Operations on the Client
If `urql` was a train it would take several stops to arrive at its terminus, our API. It starts with us
defining queries or mutations by writing in GraphQL's query language.
Any GraphQL request can be abstracted into its query documents and its variables.
```js
import { gql } from '@urql/core';
const query = gql`
query ($name: String!) {
helloWorld(name: $name)
}
`;
const request = createRequest(query, {
name: 'Urkel',
});
```
In `urql`, these GraphQL requests are treated as unique objects and each GraphQL request will have
a `key` generated for them. This `key` is a hash of the query document and the variables you provide
and are set on the `key` property of a [`GraphQLRequest`](./api/core.md#graphqlrequest).
Whenever we decide to send our GraphQL requests to a GraphQL API we start by using `urql`'s
[`Client`](./api/core.md#client).
The `Client` accepts several options to configure its behaviour and the behaviour of exchanges,
like the `fetchExchange`. For instance, we can pass it a `url` which the `fetchExchange` will
use to make a `fetch` call to our GraphQL API.
```js
import { Client, cacheExchange, fetchExchange } from '@urql/core';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
Above, we're defining a `Client` that is ready to accept our requests. It will apply basic
document caching and will send uncached requests to the `url` we pass it.
The bindings that we've seen in [the "Basics" section](./basics/README.md), like `useQuery` for
React for example, interact with [the `Client`](./api/core.md#client) directly and are a thin
abstraction.
Some methods can be called on it directly however, as seen [on the "Core Usage"
page](./basics/core.md#one-off-queries-and-mutations).
```js
// Given our request and client defined above, we can call
const subscription = client.executeQuery(request).subscribe(result => {
console.log(result.data);
});
```
As we've seen, `urql` defines our query documents and variables as
[`GraphQLRequest`s](./api/core.md#graphqlrequest). However, since we have more metadata that is
needed, like our `url` option on the `Client`, `urql` internally creates [`Operation`s](./api/core.md#operation)
each time a request is executed. The operations are then forwarded to the exchanges, like the
`cacheExchange` and `fetchExchange`.
An "Operation" is an extension of `GraphQLRequest`s. Not only do they carry the `query`, `variables`,
and a `key` property, they will also identify the `kind` of operation that is executed, like
`"query"` or `"mutation"`, and they contain the `Client`'s options on `operation.context`.

This means, once we hand over a GraphQL request to the `Client`, it will create an `Operation`,
and then hand it over to the exchanges until a result comes back.
As shown in the diagram, each operation is like an event or signal for a GraphQL request to start,
and the exchanges will eventually send back a corresponding result.
However, because the cache can send updates to us whenever it detects a change, or you could cancel
a GraphQL request before it finishes, a special "teardown" `Operation` also exists, which cancels
ongoing requests.
## The Client and Exchanges
To reiterate, when we use `urql`'s bindings for our framework of choice, methods are called on the
`Client`, but we never see the operations that are created in the background from our bindings. We
call a method like `client.executeQuery` (or it's called for us in the bindings), an operation is
issued internally when we subscribe with a callback, and later, we're given results.

While we know that, for us, we're only interested in a single [`Operation`](./api/core.md#operation)
and its [`OperationResult`s](./api/core.md#operationresult) at a time, the `Client` treats these as
one big stream. The `Client` sees an incoming flow of all of our operations.
As we've learned before, each operation carries a `key` and each result we receive carries the
original `operation`. Because an `OperationResult` also carries an `operation` property the `Client`
will always know which results correspond to an individual operation.
However, internally, all of our operations are processed at the same time concurrently. However, from
our perspective:
- We subscribe to a "stream" and expect to get results on a callback
- The `Client` issues the operation, and we'll receive some results back eventually as either the
cache responds (synchronously), or the request gets sent to our API.
- We eventually unsubscribe, and the `Client` issues a "teardown" operation with the same `key` as
the original operation, which concludes our flow.
The `Client` itself doesn't actually know what to do with operations. Instead, it sends them through
"exchanges". Exchanges are akin to [middleware in Redux](https://redux.js.org/advanced/middleware)
and have access to all operations and all results. Multiple exchanges are chained to process our
operations and to execute logic on them, one of them being the `fetchExchange`, which as the name
implies sends our requests to our API.
### How operations get to exchanges
We now know how we get to operations and to the `Client`:
- Any bindings or calls to the `Client` create an **operation**
- This operation identifies itself as either a `"query"`, `"mutation"` or `"subscription"` and has a
unique `key`.
- This operation is sent into the **exchanges** and eventually ends up at the `fetchExchange`
(or a similar exchange)
- The operation is sent to the API and a **result** comes back, which is wrapped in an `OperationResult`
- The `Client` filters the `OperationResult` by the `operation.key` and — via a callback — gives us
a **stream of results**.
To come back to our train analogy from earlier, an operation, like a train, travels from one end
of the track to the terminus — our API. The results then come back on the same path as they're just
travelling the same line in reverse.
### The Exchanges
By default, the `Client` doesn't do anything with GraphQL requests. It contains only the logic to
manage and differentiate between active and inactive requests and converts them to operations.
To actually do something with our GraphQL requests, it needs _exchanges_, which are like plugins
that you can pass to create a pipeline of how GraphQL operations are executed.
By default, you may want to add the `cacheExchange` and the `fetchExchange` from `@urql/core`:
- `cacheExchange`: Caches GraphQL results with ["Document Caching"](./basics/document-caching.md)
- `fetchExchange`: Executes GraphQL requests with a `fetch` HTTP call
```js
import { Client, cacheExchange, fetchExchange } from '@urql/core';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
As we can tell, exchanges define not only how GraphQL requests are executed and handled, but also
get control over caching. Exchanges can be used to change almost any behaviour in the `Client`,
although internally they only handle incoming & outgoing requests and incoming & outgoing results.
Some more exchanges that we can use with our `Client` are:
- [`mapExchange`](./api/core.md#mapexchange): Allows changing and reacting to operations, results, and errors
- [`ssrExchange`](./advanced/server-side-rendering.md): Allows for a server-side renderer to
collect results for client-side rehydration.
- [`retryExchange`](./advanced/retry-operations.md): Allows operations to be retried on errors
- [`persistedExchange`](./advanced/persistence-and-uploads.md#automatic-persisted-queries): Provides support for Automatic
Persisted Queries
- [`authExchange`](./advanced/authentication.md): Allows refresh authentication to be implemented easily.
- [`requestPolicyExchange`](./api/request-policy-exchange.md): Automatically refreshes results given a TTL.
- `devtoolsExchange`: Provides the ability to use the [urql-devtools](https://github.com/urql-graphql/urql-devtools)
We can even swap out our [document cache](./basics/document-caching.md), which is implemented by
`@urql/core`'s `cacheExchange`, with `urql`'s [normalized cache,
Graphcache](./graphcache/README.md).
[Read more about exchanges and how to write them from scratch on the "Authoring Exchanges"
page.](./advanced/authoring-exchanges.md)
## Stream Patterns in `urql`
In the previous sections we've learned a lot about how the `Client` works, but we've always learned
it in vague terms — for instance, we've learned that we get a "stream of results" or `urql` sees all
operations as "one stream of operations" that it sends to the exchanges.
But, **what are streams?**
Generally we refer to _streams_ as abstractions that allow us to program with asynchronous events
over time. Within the context of JavaScript we're specifically thinking in terms of
[Observables](https://github.com/tc39/proposal-observable)
and [Reactive Programming with Observables.](http://reactivex.io/documentation/observable.html)
These concepts may sound intimidating, but from a high-level view what we're talking about can be
thought of as a combination of promises and iterables (e.g. arrays). We're dealing with multiple
events, but our callback is called over time. It's like calling `forEach` on an array but expecting
the results to come in asynchronously.
As a user, if we're using the one framework bindings that we've seen in [the "Basics"
section](./basics/README.md), we may never see these streams in action or may never use them even,
since the bindings internally use them for us. But if we [use the `Client`
directly](./basics/core.md#one-off-queries-and-mutations) or write exchanges then we'll see streams
and will have to deal with their API.
### Stream patterns with the client
When we call methods on the `Client` like [`client.executeQuery`](./api/core.md#clientexecutequery)
or [`client.query`](./api/core.md#clientquery) then these will return a "stream" of results.
It's normal for GraphQL subscriptions to deliver multiple results, however, even GraphQL queries can
give you multiple results in `urql`. This is because operations influence one another. When a cache
invalidates a query, this query may refetch, and a new result is delivered to your application.
Multiple results mean that once you subscribe to a GraphQL query via the `Client`, you may receive
new results in the future.
```js
import { gql } from '@urql/core';
const QUERY = gql`
query Test($id: ID!) {
getUser(id: $id) {
id
name
}
}
`;
client.query(QUERY, { id: 'test' }).subscribe(result => {
console.log(result); // { data: ... }
});
```
Read more about the available APIs on the `Client` in the [Core API docs](./api/core.md).
Internally, these streams and all exchanges are written using a library called
[`wonka`](https://wonka.kitten.sh/basics/background), which is a tiny Observable-like
library. It is used to write exchanges and when we interact with the `Client` it is used internally
as well.
================================================
FILE: docs/basics/README.md
================================================
---
title: Basics
order: 2
---
# Basics
In this chapter we'll explain the basics of `urql` and how to get started with using it without any
prior knowledge.
- [**React/Preact**](./react-preact.md) covers how to work with the bindings for React/Preact.
- [**Vue**](./vue.md) covers how to work with the bindings for Vue 3.
- [**Svelte**](./svelte.md) covers how to work with the bindings for Svelte.
- [**Core Package**](./core.md) defines why a shared package exists that contains the main
logic of `urql`, and how we can use it directly in Node.js.
After reading the page for your bindings and the "Core" page you may want to the next two pages in
this section of the documentation:
- [**Document Caching**](./document-caching.md) explains the default cache mechanism of `urql`, as opposed to the opt-in
[Normalized Cache](../graphcache/normalized-caching.md).
- [**Errors**](../basics/errors.md) contains information on error handling in `urql`.
- [**UI-Patterns**](../basics/ui-patterns.md) presents some common UI-patterns with `urql`.
================================================
FILE: docs/basics/core.md
================================================
---
title: Core / Node.js
order: 3
---
# Core and Node.js Usage
The `@urql/core` package contains `urql`'s `Client`, some common utilities, and some default
_Exchanges_. These are the shared, default parts of `urql` that we will be using no matter which
framework we're interacting with.
All framework bindings — meaning `urql`, `@urql/preact`, `@urql/svelte`, and `@urql/vue` — reexport
all exports of our `@urql/core` core library. This means that if we want to use `urql`'s `Client`
imperatively or with Node.js we'd use `@urql/core`'s utilities or the `Client` directly.
In other words, if we're using framework bindings then writing `import { Client } from "@urql/vue"`
for instance is the same as `import { Client } from "@urql/core"`.
This means that we can use the core utilities and exports that are shared between all bindings
directly or install `@urql/core` separately. We can even use `@urql/core` directly without any
framework bindings.
## Installation
As we said above, if we are using bindings then those will already have installed `@urql/core` as
they depend on it. They also all re-export all exports from `@urql/core`, so we can use those
regardless of which bindings we've installed. However, it's also possible to explicitly install
`@urql/core` or use it standalone, e.g. in a Node.js environment.
```sh
yarn add @urql/core
# or
npm install --save @urql/core
```
Since all bindings and all exchanges depend on `@urql/core`, we may sometimes run into problems
where the package manager installs _two versions_ of `@urql/core`, which is a duplication problem.
This can cause type errors in TypeScript or cause some parts of our application to bundle two
different versions of the package or use slightly different utilities. We can fix this by
deduplicating our dependencies.
```sh
# npm
npm dedupe
# pnpm
pnpm dedupe
# yarn
npx yarn-deduplicate && yarn
```
## GraphQL Tags
A notable utility function is the `gql` tagged template literal function, which is a drop-in
replacement for `graphql-tag`, if you're coming from other GraphQL clients.
Wherever `urql` accepts a query document, we can either pass a string or a `DocumentNode`. `gql` is
a utility that allows a `DocumentNode` to be created directly, and others to be interpolated into
it, which is useful for fragments for instance. This function will often also mark GraphQL documents
for syntax highlighting in most code editors.
In most examples we may have passed a string to define a query document, like so:
```js
const TodosQuery = `
query {
todos {
id
title
}
}
`;
```
We may also use the `gql` tag function to create a `DocumentNode` directly:
```js
import { gql } from '@urql/core';
const TodosQuery = gql`
query {
todos {
id
title
}
}
`;
```
Since all framework bindings also re-export `@urql/core`, we may also import `gql` from `'urql'`,
`'@urql/svelte'` and other bindings directly.
We can also start interpolating other documents into the tag function. This is useful to compose
fragment documents into a larger query, since it's common to define fragments across components of
an app to spread out data dependencies. If we accidentally use a duplicate fragment name in a
document, `gql` will log a warning, since GraphQL APIs won't accept duplicate names.
```js
import { gql } from '@urql/core';
const TodoFragment = gql`
fragment SmallTodo on Todo {
id
title
}
`;
const TodosQuery = gql`
query {
todos {
...TodoFragment
}
}
${TodoFragment}
`;
```
This usage will look familiar when coming from the `graphql-tag` package. The `gql` API is
identical, and its output is approximately the same. The two packages are also intercompatible.
However, one small change in `@urql/core`'s implementation is that your fragment names don't
have to be globally unique, since it's possible to create some one-off fragments occasionally,
especially for `@urql/exchange-graphcache`'s configuration.
It also pre-generates a "hash key" for the `DocumentNode` which is what `urql` does anyway, thus
avoiding some extra work compared to when the `graphql-tag` package is used with `urql`.
## Using the `urql` Client
The `Client` is the main "hub" and store for everything that `urql` does. It is used by all
framework bindings and from the other pages in the "Basics" section we can see that creating a
`Client` comes up across all bindings and use-cases for `urql`.
[Read more about the `Client` and `urql`'s architecture on the "Architecture"
page.](../architecture.md)
### Setting up the `Client`
The `@urql/core` package exports a `Client` class, which we can use to
create the GraphQL client. This central `Client` manages all of our GraphQL requests and results.
```js
import { Client, cacheExchange, fetchExchange } from '@urql/core';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
At the bare minimum we'll need to pass an API's `url`, and the `fetchExchange`,
when we create a `Client` to get started.
Another common option is `fetchOptions`. This option allows us to customize the options that will be
passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object, or
a function returning an options object.
In the following example we'll add a token to each `fetch` request that our `Client` sends to our
GraphQL API.
```js
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const token = getToken();
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
```
### The `Client`s options
As we've seen above, the most important options for the `Client` are `url` and `exchanges`.
The `url` option is used by the `fetchExchange` to send GraphQL requests to an API.
The `exchanges` option is of particular importance however because it tells the `Client` what to do
with our GraphQL requests:
```js
import { Client, cacheExchange, fetchExchange } from '@urql/core';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
For instance, here, the `Client`'s caching and fetching features are only available because we're
passing it exchanges. In the above example, the `Client` will try to first read a GraphQL request
from a local cache, and if this request isn't cached it'll make an HTTP request.
The caching in `urql` is also implemented as an exchange, so for instance, the behavior described
on the ["Document Caching" page](./document-caching.md) is all contained within the `cacheExchange`
above.
Later, [in the "Advanced" section](../advanced/README.md) we'll see many more features that `urql`
supports by adding new exchanges to this list. On [the "Architecture" page](../architecture.md)
we'll also learn more about what exchanges are and why they exist.
### One-off Queries and Mutations
When you're using `urql` to send one-off queries or mutations — rather than in full framework code,
where updates are important — it's common to convert the streams that we get to promises. The
`client.query` and `client.mutation` methods have a shortcut to do just that.
```js
const QUERY = `
query Test($id: ID!) {
getUser(id: $id) {
id
name
}
}
`;
client
.query(QUERY, { id: 'test' })
.toPromise()
.then(result => {
console.log(result); // { data: ... }
});
```
In the above example we're executing a query on the client, are passing some variables and are
calling the `toPromise()` method on the return value to execute the request immediately and get the
result as a promise. This may be useful when we don't plan on cancelling queries, or we don't
care about future updates to this data and are just looking to query a result once.
This can also be written using async/await by simply awaiting the return value of `client.query`:
```js
const QUERY = `
query Test($id: ID!) {
getUser(id: $id) {
id
name
}
}
`;
async function query() {
const result = await client.query(QUERY, { id: 'test' });
console.log(result); // { data: ... }
}
```
The same can be done for mutations by calling the `client.mutation` method instead of the
`client.query` method.
It's worth noting that promisifying a query result will always only give us _one_ result, because
we're not calling `subscribe`. This means that we'll never see cache updates when we're asking for
a single result like we do above.
#### Reading only cache data
Similarly there's a way to read data from the cache synchronously, provided that the cache has
received a result for a given query before. The `Client` has a `readQuery` method, which is a
shortcut for just that.
```js
const QUERY = `
query Test($id: ID!) {
getUser(id: $id) {
id
name
}
}
`;
const result = client.readQuery(QUERY, { id: 'test' });
result; // null or { data: ... }
```
In the above example we call `readQuery` and receive a result immediately. This result will be
`null` if the `cacheExchange` doesn't have any results cached for the given query.
### Subscribing to Results
GraphQL Clients are by their nature "reactive", meaning that when we execute a query, we expect to
get future results for this query. [On the "Document Caching" page](./document-caching.md) we'll
learn how mutations can invalidate results in the cache. This process (and others just like it) can
cause our query to be refetched.
In essence, if we're subscribing to results rather than using a promise, like we've seen above, then
we're able to see future changes for our query's results. If a mutation causes a query to be
refetched from our API in the background then we'll see a new result. If we execute a query
somewhere else then we'll get notified of the new API result as well, as long as we're subscribed.
```js
const QUERY = `
query Test($id: ID!) {
getUser(id: $id) {
id
name
}
}
`;
const { unsubscribe } = client.query(QUERY, { id: 'test' }).subscribe(result => {
console.log(result); // { data: ... }
});
```
This code example is similar to the one before. However, instead of sending a one-off query, we're
subscribing to the query. Internally, this causes the `Client` to do the same, but the
subscription means that our callback may be called repeatedly. We may get future results as well as
the first one.
This also works synchronously. As we've seen before `client.readQuery` can give us a result
immediately if our cache already has a result for the given query. The same principle applies here!
Our callback will be called synchronously if the cache already has a result.
Once we're not interested in any results anymore, we need to clean up after ourselves by calling
`unsubscribe`. This stops the subscription and makes sure that the `Client` doesn't actively update
the query anymore or refetches it. We can think of this pattern as being very similar to events or
event hubs.
We're using [the Wonka library for our streams](https://wonka.kitten.sh/basics/background), which
we'll learn more about [on the "Architecture" page](../architecture.md). But we can think of this as
React's effects being called over time, or as `window.addEventListener`.
## Common Utilities in Core
The `@urql/core` package contains other utilities that are shared between multiple addon packages.
This is a short but non-exhaustive list. It contains,
- [`CombinedError`](../api/core.md#combinederror) - our abstraction to combine one or more `GraphQLError`(s) and a `NetworkError`
- `makeResult` and `makeErrorResult` - utilities to create _Operation Results_
- [`createRequest`](../api/core.md#createrequest) - a utility function to create a request from a
query, and some variables (which generate a stable _Operation Key_)
There are other utilities not mentioned here. Read more about the `@urql/core` API in the [API docs](../api/core.md).
## Reading on
This concludes the introduction for using `@urql/core` without any framework bindings. This showed
just a couple of ways to use `gql` or the `Client`, however you may also want to learn more about
[how to use `urql`'s streams](../architecture.md#stream-patterns-in-urql). Furthermore, apart from the framework
binding introductions, there are some other pages that provide more information on how to get fully
set up with `urql`:
- [How does the default "document cache" work?](./document-caching.md)
- [How are errors handled and represented?](./errors.md)
- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
================================================
FILE: docs/basics/document-caching.md
================================================
---
title: Document Caching
order: 4
---
# Document Caching
By default, `urql` uses a concept called _Document Caching_. It will avoid sending the same requests
to a GraphQL API repeatedly by caching the result of each query.
This works like the cache in a browser. `urql` creates a key for each request that is sent based on
a query and its variables.
The default _document caching_ logic is implemented in the default `cacheExchange`. We'll learn more
about ["Exchanges" on the "Architecture" page.](../architecture.md)
## Operation Keys

Once a result comes in it's cached indefinitely by its key. This means that each unique request
can have exactly one cached result.
However, we also need to invalidate the cached results so that requests are sent again and updated,
when we know that some results are out-of-date. With document caching we assume that a result may
be invalidated by a mutation that executes on data that has been queried previously.
In GraphQL the client can request additional type information by adding the `__typename` field to a
query's _selection set_. This field returns the name of the type for an object in the results, and
we use it to detect commonalities and data dependencies between queries and mutations.

In short, when we send a mutation that contains types that another query's results contains as well,
that query's result is removed from the cache.
This is an aggressive form of cache invalidation. However, it works well for content-driven sites,
while it doesn't deal with normalized data or IDs.
## Request Policies
The _request policy_ that is defined will alter what the default document cache does. By default, the
cache will prefer cached results and will otherwise send a request, which is called `cache-first`.
In total there are four different policies that we can use:
- `cache-first` (the default) prefers cached results and falls back to sending an API request when
no prior result is cached.
- `cache-and-network` returns cached results but also always sends an API request, which is perfect
for displaying data quickly while keeping it up-to-date.
- `network-only` will always send an API request and will ignore cached results.
- `cache-only` will always return cached results or `null`.
The `cache-and-network` policy is particularly useful, since it allows us to display data instantly
if it has been cached, but also refreshes data in our cache in the background. This means though
that `fetching` will be `false` for cached results although an API request may still be ongoing in
the background.
For this reason there's another field on results, `result.stale`, which indicates that the cached
result is either outdated or that another request is being sent in the background.
[Read more about which request policies are available in the API
docs.](../api/core.md#requestpolicy-type)
## Document Cache Gotchas
This cache has a small trade-off! If we request a list of data, and the API returns an empty list,
then the cache won't be able to see the `__typename` of said list and invalidate it.
There are two ways to fix this issue, supplying `additionalTypenames` to the context of your query or [switch to "Normalized Caching"
instead](../graphcache/normalized-caching.md).
### Adding typenames
This will elaborate about the first fix for empty lists, the `additionalTypenames`.
Example where this would occur:
```js
const query = `query { todos { id name } }`;
const result = { todos: [] };
```
At this point we don't know what types are possible for this query, so a best practice when using
the default cache is to add `additionalTypenames` for this query.
```js
// Keep the reference stable.
const context = useMemo(() => ({ additionalTypenames: ['Todo'] }), []);
const [result] = useQuery({ query, context });
```
Now the cache will know when to invalidate this query even when the list is empty.
We may also use this feature for mutations, since occasionally mutations must invalidate data that
isn't directly connected to a mutation by a `__typename`.
```js
const [result, execute] = useMutation(`mutation($name: String!) { createUser(name: $name) }`);
const onClick = () => {
execute({ name: 'newName' }, { additionalTypenames: ['Wallet'] });
};
```
Now our `mutation` knows that when it completes it has an additional type to invalidate.
================================================
FILE: docs/basics/errors.md
================================================
---
title: Errors
order: 5
---
# Error handling
When we use a GraphQL API there are two kinds of errors we may encounter: Network Errors and GraphQL
Errors from the API. Since it's common to encounter either of them, there's a
[`CombinedError`](../api/core.md#combinederror) class that can hold and abstract either.
We may encounter a `CombinedError` when using `urql` wherever an `error` may be returned, typically
in results from the API. The `CombinedError` can have one of two properties that describe what went
wrong.
- The `networkError` property will contain any error that stopped `urql` from making a network
request.
- The `graphQLErrors` property may be an array that contains [normalized `GraphQLError`s as they
were received in the `errors` array from a GraphQL API.](https://graphql.org/graphql-js/error/)
Additionally, the `message` of the error will be generated and combined from the errors for
debugging purposes.

It's worth noting that an `error` can coexist and be returned in a successful request alongside
`data`. This is because in GraphQL a query can have partially failed but still contain some data.
In that case `CombinedError` will be passed to us with `graphQLErrors`, while `data` may still be
set.
================================================
FILE: docs/basics/react-preact.md
================================================
---
title: React/Preact Bindings
order: 0
---
# React/Preact
This guide covers how to install and setup `urql` and the `Client`, as well as query and mutate data,
with React and Preact. Since the `urql` and `@urql/preact` packages share most of their API and are
used in the same way, when reading the documentation on React, all examples are essentially the same,
except that we'd want to use the `@urql/preact` package instead of the `urql` package.
## Getting started
### Installation
Installing `urql` is as quick as you'd expect, and you won't need any other packages to get started
with at first. We'll install the package with our package manager of choice.
```sh
yarn add urql
# or
npm install --save urql
```
To use `urql` with Preact, we have to install `@urql/preact` instead of `urql` and import from
that package instead. Otherwise all examples for Preact will be the same.
Most libraries related to GraphQL also need the `graphql` package to be installed as a peer
dependency, so that they can adapt to your specific versioning requirements. That's why we'll need
to install `graphql` alongside `urql`.
Both the `urql` and `graphql` packages follow [semantic versioning](https://semver.org) and all
`urql` packages will define a range of compatible versions of `graphql`. Watch out for breaking
changes in the future however, in which case your package manager may warn you about `graphql` being
out of the defined peer dependency range.
### Setting up the `Client`
The `urql` and `@urql/preact` packages export a `Client` class, which we can use to
create the GraphQL client. This central `Client` manages all of our GraphQL requests and results.
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client`
to get started.
Another common option is `fetchOptions`. This option allows us to customize the options that will be
passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object, or
a function returning an options object.
In the following example we'll add a token to each `fetch` request that our `Client` sends to our
GraphQL API.
```js
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const token = getToken();
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
```
### Providing the `Client`
To make use of the `Client` in React & Preact we will have to provide it via the
[Context API](https://reactjs.org/docs/context.html). This may be done with the help of
the `Provider` export.
```jsx
import { Client, Provider, cacheExchange, fetchExchange } from 'urql';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
const App = () => (
);
```
Now every component and element inside and under the `Provider` can use GraphQL queries that
will be sent to our API.
## Queries
Both libraries offer a `useQuery` hook and a `Query` component. The latter accepts the same
parameters, but we won't cover it in this guide. [Look it up in the API docs if you prefer
render-props components.](../api/urql.md#query-component)
### Run a first query
For the following examples, we'll imagine that we're querying data from a GraphQL API that contains
todo items. Let's dive right into it!
```jsx
import { gql, useQuery } from 'urql';
const TodosQuery = gql`
query {
todos {
id
title
}
}
`;
const Todos = () => {
const [result, reexecuteQuery] = useQuery({
query: TodosQuery,
});
const { data, fetching, error } = result;
if (fetching) return
Loading...
;
if (error) return
Oh no... {error.message}
;
return (
{data.todos.map(todo => (
{todo.title}
))}
);
};
```
Here we have implemented our first GraphQL query to fetch todos. We see that `useQuery` accepts
options and returns a tuple. In this case we've set the `query` option to our GraphQL query. The
tuple we then get in return is an array that contains a result object, and a re-execute function.
The result object contains several properties. The `fetching` field indicates whether the hook is
loading data, `data` contains the actual `data` from the API's result, and `error` is set when either
the request to the API has failed or when our API result contained some `GraphQLError`s, which
we'll get into later on the ["Errors" page](./errors.md).
### Variables
Typically we'll also need to pass variables to our queries, for instance, if we are dealing with
pagination. For this purpose the `useQuery` hook also accepts a `variables` option, which we can use
to supply variables to our query.
```jsx
const TodosListQuery = gql`
query ($from: Int!, $limit: Int!) {
todos(from: $from, limit: $limit) {
id
title
}
}
`;
const Todos = ({ from, limit }) => {
const [result, reexecuteQuery] = useQuery({
query: TodosListQuery,
variables: { from, limit },
});
// ...
};
```
As when we're sending GraphQL queries manually using `fetch`, the variables will be attached to the
`POST` request body that is sent to our GraphQL API.
Whenever the `variables` (or the `query`) option on the `useQuery` hook changes `fetching` will
switch to `true`, and a new request will be sent to our API, unless a result has already been cached
previously.
### Pausing `useQuery`
In some cases we may want `useQuery` to execute a query when a pre-condition has been met, and not
execute the query otherwise. For instance, we may be building a form and want a validation query to
only take place when a field has been filled out.
Since hooks in React can't just be commented out, the `useQuery` hook accepts a `pause` option that
temporarily _freezes_ all changes and stops requests.
In the previous example we've defined a query with mandatory arguments. The `$from` and `$limit`
variables have been defined to be non-nullable `Int!` values.
Let's pause the query we've just
written to not execute when these variables are empty, to prevent `null` variables from being
executed. We can do this by setting the `pause` option to `true`:
```jsx
const Todos = ({ from, limit }) => {
const shouldPause = from === undefined || from === null || limit === undefined || limit === null;
const [result, reexecuteQuery] = useQuery({
query: TodosListQuery,
variables: { from, limit },
pause: shouldPause,
});
// ...
};
```
Now whenever the mandatory `$from` or `$limit` variables aren't supplied the query won't be executed.
This also means that `result.data` won't change, which means we'll still have access to our old data
even though the variables may have changed.
### Request Policies
As has become clear in the previous sections of this page, the `useQuery` hook accepts more options
than just `query` and `variables`. Another option we should touch on is `requestPolicy`.
The `requestPolicy` option determines how results are retrieved from our `Client`'s cache. By
default, this is set to `cache-first`, which means that we prefer to get results from our cache, but
are falling back to sending an API request.
Request policies aren't specific to `urql`'s React API, but are a common feature in its core. [You
can learn more about how the cache behaves given the four different policies on the "Document
Caching" page.](../basics/document-caching.md)
```jsx
const [result, reexecuteQuery] = useQuery({
query: TodosListQuery,
variables: { from, limit },
requestPolicy: 'cache-and-network',
});
```
Specifically, a new request policy may be passed directly to the `useQuery` hook as an option.
This policy is then used for this specific query. In this case, `cache-and-network` is used and
the query will be refreshed from our API even after our cache has given us a cached result.
Internally, the `requestPolicy` is just one of several "**context** options". The `context`
provides metadata apart from the usual `query` and `variables` we may pass. This means that
we may also change the `Client`'s default `requestPolicy` by passing it there.
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
// every operation will by default use cache-and-network rather
// than cache-first now:
requestPolicy: 'cache-and-network',
});
```
### Context Options
As mentioned, the `requestPolicy` option on `useQuery` is a part of `urql`'s context options.
In fact, there are several more built-in context options, and the `requestPolicy` option is
one of them. Another option we've already seen is the `url` option, which determines our
API's URL. These options aren't limited to the `Client` and may also be passed per query.
```jsx
import { useMemo } from 'react';
import { useQuery } from 'urql';
const Todos = ({ from, limit }) => {
const [result, reexecuteQuery] = useQuery({
query: TodosListQuery,
variables: { from, limit },
context: useMemo(
() => ({
requestPolicy: 'cache-and-network',
url: 'http://localhost:3000/graphql?debug=true',
}),
[]
),
});
// ...
};
```
As we can see, the `context` property for `useQuery` accepts any known `context` option and can be
used to alter them per query rather than globally. The `Client` accepts a subset of `context`
options, while the `useQuery` option does the same for a single query.
[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
### Reexecuting Queries
The `useQuery` hook updates and executes queries whenever its inputs, like the `query` or
`variables` change, but in some cases we may find that we need to programmatically trigger a new
query. This is the purpose of the `reexecuteQuery` function, which is the second item in the tuple
that `useQuery` returns.
Triggering a query programmatically may be useful in a couple of cases. It can for instance be used
to refresh the hook's data. In these cases we may also override the `requestPolicy` of our query just
once and set it to `network-only` to skip the cache.
```jsx
const Todos = ({ from, limit }) => {
const [result, reexecuteQuery] = useQuery({
query: TodosListQuery,
variables: { from, limit },
});
const refresh = () => {
// Refetch the query and skip the cache
reexecuteQuery({ requestPolicy: 'network-only' });
};
};
```
Calling `refresh` in the above example will execute the query again forcefully, and will skip the
cache, since we're passing `requestPolicy: 'network-only'`.
Furthermore the `reexecuteQuery` function can also be used to programmatically start a query even
when `pause` is set to `true`, which would usually stop all automatic queries. This can be used to
perform one-off actions, or to set up polling.
```jsx
import { useEffect } from 'react';
import { useQuery } from 'urql';
const Todos = ({ from, limit }) => {
const [result, reexecuteQuery] = useQuery({
query: TodosListQuery,
variables: { from, limit },
pause: true,
});
useEffect(() => {
if (result.fetching) return;
// Set up to refetch in one second, if the query is idle
const timerId = setTimeout(() => {
reexecuteQuery({ requestPolicy: 'network-only' });
}, 1000);
return () => clearTimeout(timerId);
}, [result.fetching, reexecuteQuery]);
// ...
};
```
There are some more tricks we can use with `useQuery`. [Read more about its API in the API docs for
it.](../api/urql.md#usequery)
## Mutations
Both libraries offer a `useMutation` hook and a `Mutation` component. The latter accepts the same
parameters, but we won't cover it in this guide. [Look it up in the API docs if you prefer
render-props components.](../api/urql.md#mutation-component)
### Sending a mutation
Let's again pick up an example with an imaginary GraphQL API for todo items, and dive into an
example! We'll set up a mutation that _updates_ a todo item's title.
```jsx
const UpdateTodo = `
mutation ($id: ID!, $title: String!) {
updateTodo (id: $id, title: $title) {
id
title
}
}
`;
const Todo = ({ id, title }) => {
const [updateTodoResult, updateTodo] = useMutation(UpdateTodo);
};
```
Similar to the `useQuery` output, `useMutation` returns a tuple. The first item in the tuple again
contains `fetching`, `error`, and `data` — it's identical since this is a common pattern of how
`urql` presents [operation results](../api/core.md#operationresult).
Unlike the `useQuery` hook, the `useMutation` hook doesn't execute automatically. At this point in
our example, no mutation will be performed. To execute our mutation we instead have to call the
execute function — `updateTodo` in our example — which is the second item in the tuple.
### Using the mutation result
When calling our `updateTodo` function we have two ways of getting to the result as it comes back
from our API. We can either use the first value of the returned tuple, our `updateTodoResult`, or
we can use the promise that `updateTodo` returns.
```jsx
const Todo = ({ id, title }) => {
const [updateTodoResult, updateTodo] = useMutation(UpdateTodo);
const submit = newTitle => {
const variables = { id, title: newTitle || '' };
updateTodo(variables).then(result => {
// The result is almost identical to `updateTodoResult` with the exception
// of `result.fetching` not being set.
// It is an OperationResult.
});
};
};
```
The result is useful when your UI has to display progress on the mutation, and the returned
promise is particularly useful when you're adding side effects that run after the mutation has
completed.
### Handling mutation errors
It's worth noting that the promise we receive when calling the execute function will never
reject. Instead it will always return a promise that resolves to a result.
If you're checking for errors, you should use `result.error` instead, which will be set
to a `CombinedError` when any kind of errors occurred while executing your mutation.
[Read more about errors on our "Errors" page.](./errors.md)
```jsx
const Todo = ({ id, title }) => {
const [updateTodoResult, updateTodo] = useMutation(UpdateTodo);
const submit = newTitle => {
const variables = { id, title: newTitle || '' };
updateTodo(variables).then(result => {
if (result.error) {
console.error('Oh no!', result.error);
}
});
};
};
```
There are some more tricks we can use with `useMutation`.
[Read more about its API in the API docs for it.](../api/urql.md#usemutation)
## Reading on
This concludes the introduction for using `urql` with React or Preact. The rest of the documentation
is mostly framework-agnostic and will apply to either `urql` in general, or the `@urql/core` package,
which is the same between all framework bindings. Hence, next we may want to learn more about one of
the following to learn more about the internals:
- [How does the default "document cache" work?](./document-caching.md)
- [How are errors handled and represented?](./errors.md)
- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
================================================
FILE: docs/basics/solid-start.md
================================================
---
title: SolidStart Bindings
order: 3
---
# SolidStart
This guide covers how to use `@urql/solid-start` with SolidStart applications. The `@urql/solid-start` package integrates urql with SolidStart's native data fetching primitives like `query()`, `action()`, `createAsync()`, and `useAction()`.
> **Note:** This guide is for SolidStart applications with SSR. If you're building a client-side only SolidJS app, see the [Solid guide](./solid.md) instead. See the [comparison section](#solidjs-vs-solidstart) below for key differences between the packages.
## Getting started
### Installation
Installing `@urql/solid-start` requires both the package and its peer dependencies:
```sh
yarn add @urql/solid-start @urql/solid @urql/core graphql
# or
npm install --save @urql/solid-start @urql/solid @urql/core graphql
# or
pnpm add @urql/solid-start @urql/solid @urql/core graphql
```
The `@urql/solid-start` package depends on `@urql/solid` for shared utilities and re-exports some primitives that work identically on both client and server.
### Setting up the `Client`
The `@urql/solid-start` package exports a `Client` class from `@urql/core`. This central `Client` manages all of our GraphQL requests and results.
```js
import { createClient, cacheExchange, fetchExchange } from '@urql/solid-start';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client`.
For server-side requests, you'll often want to customize `fetchOptions` to include headers like cookies or authorization tokens:
```js
import { getRequestEvent } from 'solid-js/web';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const event = getRequestEvent();
return {
headers: {
cookie: event?.request.headers.get('cookie') || '',
},
};
},
});
```
### Providing the `Client`
To make use of the `Client` in SolidStart we will provide it via Solid's Context API using the `Provider` export. The Provider also needs the `query` and `action` functions from `@solidjs/router`:
```jsx
// src/root.tsx or src/app.tsx
import { Router, action, query } from '@solidjs/router';
import { FileRoutes } from '@solidjs/start/router';
import { Suspense } from 'solid-js';
import { createClient, Provider, cacheExchange, fetchExchange } from '@urql/solid-start';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
export default function App() {
return (
(
{props.children}
)}
>
);
}
```
Now every route and component inside the `Provider` can use GraphQL queries and mutations that will be sent to our API. The `query` and `action` functions are provided in context so that `createQuery` and `createMutation` can access them automatically.
## Queries
The `@urql/solid-start` package offers a `createQuery` primitive that integrates with SolidStart's `query()` and `createAsync()` primitives for optimal server-side rendering and streaming.
### Run a first query
For the following examples, we'll imagine that we're querying data from a GraphQL API that contains todo items.
```jsx
// src/routes/todos.tsx
import { Suspense, For, Show } from 'solid-js';
import { createAsync } from '@solidjs/router';
import { gql } from '@urql/core';
import { createQuery } from '@urql/solid-start';
const TodosQuery = gql`
query {
todos {
id
title
}
}
`;
export default function Todos() {
const queryTodos = createQuery(TodosQuery, 'todos-list');
const result = createAsync(() => queryTodos());
return (
Loading...}>
{(todo) =>
{todo.title}
}
);
}
```
The `createQuery` primitive integrates with SolidStart's data fetching system:
1. It wraps SolidStart's `query()` function to execute URQL queries with proper router context
2. The `query` function is automatically retrieved from the URQL context (no manual injection needed)
3. The second parameter is a cache key (string) for SolidStart's router
4. The returned function is wrapped with `createAsync()` to get the reactive result
5. `createQuery` must be called inside a component where it has access to the context
The query automatically executes on both the server (during SSR) and the client, with SolidStart handling serialization and hydration.
### Variables
Typically we'll also need to pass variables to our queries. Pass variables as an option in the fourth parameter:
```jsx
// src/routes/todos/[page].tsx
import { Suspense, For, Show } from 'solid-js';
import { useParams, createAsync } from '@solidjs/router';
import { gql } from '@urql/core';
import { createQuery } from '@urql/solid-start';
const TodosListQuery = gql`
query ($from: Int!, $limit: Int!) {
todos(from: $from, limit: $limit) {
id
title
}
}
`;
export default function TodosPage() {
const params = useParams();
const queryTodos = createQuery(TodosListQuery, 'todos-paginated', {
variables: {
from: parseInt(params.page) * 10,
limit: 10,
},
});
const result = createAsync(() => queryTodos());
return (
Loading...}>
{(todo) =>
{todo.title}
}
);
}
```
For dynamic variables that change based on reactive values, you'll need to recreate the query function when dependencies change.
### Request Policies
The `requestPolicy` option determines how results are retrieved from the cache:
```jsx
const queryTodos = createQuery(TodosQuery, 'todos-list', {
requestPolicy: 'cache-and-network',
});
const result = createAsync(() => queryTodos());
```
Available policies:
- `cache-first` (default): Prefer cached results, fall back to network
- `cache-only`: Only use cached results, never send network requests
- `network-only`: Always send a network request, ignore cache
- `cache-and-network`: Return cached results immediately, then fetch from network
[Learn more about request policies on the "Document Caching" page.](./document-caching.md)
### Revalidation
There are two approaches to revalidating data in SolidStart with urql:
1. **urql's cache invalidation** - Invalidates specific queries or entities in urql's cache, causing automatic refetches
2. **SolidStart's revalidation** - Uses SolidStart's router revalidation to reload route data
Both approaches work well, and you can choose based on your needs. urql's invalidation is more granular and works at the query level, while SolidStart's revalidation works at the route level.
#### Manual Revalidation with urql
You can manually revalidate queries using urql's cache invalidation with the `keyFor` helper. This invalidates specific queries in urql's cache and triggers automatic refetches:
```jsx
// src/routes/todos.tsx
import { Suspense, For, Show } from 'solid-js';
import { createAsync } from '@solidjs/router';
import { gql, keyFor } from '@urql/core';
import { createQuery, useClient } from '@urql/solid-start';
const TodosQuery = gql`
query {
todos {
id
title
}
}
`;
export default function Todos() {
const client = useClient();
const queryTodos = createQuery(TodosQuery, 'todos-list');
const result = createAsync(() => queryTodos());
const handleRefresh = () => {
// Invalidate the todos query using keyFor
const key = keyFor(TodosQuery);
client.reexecuteOperation(client.createRequestOperation('query', {
key,
query: TodosQuery
}));
};
return (
Loading...}>
{(todo) =>
{todo.title}
}
);
}
```
#### Manual Revalidation with SolidStart
Alternatively, you can use SolidStart's built-in `revalidate` function to reload route data. This is useful when you want to refresh all queries on a specific route:
```jsx
// src/routes/todos.tsx
import { Suspense, For, Show } from 'solid-js';
import { createAsync, revalidate } from '@solidjs/router';
import { gql } from '@urql/core';
import { createQuery } from '@urql/solid-start';
const TodosQuery = gql`
query {
todos {
id
title
}
}
`;
export default function Todos() {
const queryTodos = createQuery(TodosQuery, 'todos-list');
const result = createAsync(() => queryTodos());
const handleRefresh = async () => {
// Revalidate the current route - refetches all queries on this page
await revalidate();
};
return (
Loading...}>
{(todo) =>
{todo.title}
}
);
}
```
#### Revalidation After Mutations
A common pattern is to revalidate after a mutation succeeds. You can choose either approach:
**Using urql's cache invalidation:**
```jsx
// src/routes/todos/new.tsx
import { useNavigate } from '@solidjs/router';
import { gql, keyFor } from '@urql/core';
import { createMutation, useClient } from '@urql/solid-start';
const TodosQuery = gql`
query {
todos {
id
title
}
}
`;
const CreateTodo = gql`
mutation ($title: String!) {
createTodo(title: $title) {
id
title
}
}
`;
export default function NewTodo() {
const navigate = useNavigate();
const client = useClient();
const [state, createTodo] = createMutation(CreateTodo);
const handleSubmit = async (e: Event) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const title = formData.get('title') as string;
const result = await createTodo({ title });
if (!result.error) {
// Invalidate todos query using keyFor
const key = keyFor(TodosQuery);
client.reexecuteOperation(client.createRequestOperation('query', {
key,
query: TodosQuery
}));
navigate('/todos');
}
};
return (
);
}
```
**Using SolidStart's revalidation:**
```jsx
// src/routes/todos/new.tsx
import { useNavigate } from '@solidjs/router';
import { gql } from '@urql/core';
import { createMutation } from '@urql/solid-start';
import { revalidate } from '@solidjs/router';
const CreateTodo = gql`
mutation ($title: String!) {
createTodo(title: $title) {
id
title
}
}
`;
export default function NewTodo() {
const navigate = useNavigate();
const [state, createTodo] = createMutation(CreateTodo);
const handleSubmit = async (e: Event) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const title = formData.get('title') as string;
const result = await createTodo({ title });
if (!result.error) {
// Revalidate the /todos route to refetch all its queries
await revalidate('/todos');
navigate('/todos');
}
};
return (
);
}
```
#### Automatic Revalidation with Actions
When using SolidStart actions, you can configure automatic revalidation by returning the appropriate response:
```jsx
import { action, revalidate } from '@solidjs/router';
import { gql } from '@urql/core';
const createTodoAction = action(async (formData: FormData) => {
const title = formData.get('title') as string;
// Perform mutation
const result = await client.mutation(CreateTodo, { title }).toPromise();
if (!result.error) {
// Revalidate multiple routes if needed
await revalidate(['/todos', '/']);
}
return result;
});
```
#### Choosing Between Approaches
**Use urql's `keyFor` and `reexecuteOperation` when:**
- You need to refetch a specific query after a mutation
- You want fine-grained control over which queries to refresh
- You're working with multiple queries on the same route and only want to refetch one
**Use SolidStart's `revalidate` when:**
- You want to refresh all data on a route
- You're navigating to a different route and want to ensure fresh data
- You want to leverage SolidStart's routing system for cache management
Both approaches are valid and can even be used together depending on your application's needs.
### Context Options
Context options can be passed to customize the query behavior:
```jsx
const queryTodos = createQuery(TodosQuery, 'todos-list', {
context: {
requestPolicy: 'cache-and-network',
fetchOptions: {
headers: {
'X-Custom-Header': 'value',
},
},
},
});
const result = createAsync(() => queryTodos());
```
[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
## Mutations
The `@urql/solid-start` package offers a `createMutation` primitive that integrates with SolidStart's `action()` and `useAction()` primitives.
### Sending a mutation
Mutations in SolidStart are executed using actions. Here's an example of updating a todo item:
```jsx
// src/routes/todos/[id]/edit.tsx
import { gql } from '@urql/core';
import { createMutation } from '@urql/solid-start';
import { useParams, useNavigate } from '@solidjs/router';
import { Show } from 'solid-js';
const UpdateTodo = gql`
mutation ($id: ID!, $title: String!) {
updateTodo(id: $id, title: $title) {
id
title
}
}
`;
export default function EditTodo() {
const params = useParams();
const navigate = useNavigate();
const [state, updateTodo] = createMutation(UpdateTodo);
const handleSubmit = async (e: Event) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const title = formData.get('title') as string;
const result = await updateTodo({
id: params.id,
title,
});
if (!result.error) {
navigate(`/todos/${params.id}`);
}
};
return (
);
}
```
The `createMutation` primitive returns a tuple:
1. A reactive state object containing `fetching`, `error`, and `data`
2. An execute function that triggers the mutation
You can optionally provide a custom `key` parameter to control how mutations are cached by SolidStart's router:
```jsx
const [state, updateTodo] = createMutation(UpdateTodo, 'update-todo-mutation');
```
### Progressive enhancement with actions
SolidStart actions work with and without JavaScript enabled. Here's how to set up a mutation that works progressively:
```jsx
import { action, redirect } from '@solidjs/router';
import { gql } from '@urql/core';
import { createMutation } from '@urql/solid-start';
const CreateTodo = gql`
mutation ($title: String!) {
createTodo(title: $title) {
id
title
}
}
`;
export default function NewTodo() {
const [state, createTodo] = createMutation(CreateTodo);
const handleSubmit = async (formData: FormData) => {
const title = formData.get('title') as string;
const result = await createTodo({ title });
if (!result.error) {
return redirect('/todos');
}
};
return (
);
}
```
### Using mutation results
The mutation state is reactive and updates automatically as the mutation progresses:
```jsx
const [state, updateTodo] = createMutation(UpdateTodo);
createEffect(() => {
if (state.data) {
console.log('Mutation succeeded:', state.data);
}
if (state.error) {
console.error('Mutation failed:', state.error);
}
if (state.fetching) {
console.log('Mutation in progress...');
}
});
```
The execute function also returns a promise that resolves to the result:
```jsx
const [state, updateTodo] = createMutation(UpdateTodo);
const handleUpdate = async () => {
const result = await updateTodo({ id: '1', title: 'Updated' });
if (result.error) {
console.error('Oh no!', result.error);
} else {
console.log('Success!', result.data);
}
};
```
### Handling mutation errors
Mutation promises never reject. Instead, check the `error` field on the result:
```jsx
const [state, updateTodo] = createMutation(UpdateTodo);
const handleUpdate = async () => {
const result = await updateTodo({ id: '1', title: 'Updated' });
if (result.error) {
// CombinedError with network or GraphQL errors
console.error('Mutation failed:', result.error);
// Check for specific error types
if (result.error.networkError) {
console.error('Network error:', result.error.networkError);
}
if (result.error.graphQLErrors.length > 0) {
console.error('GraphQL errors:', result.error.graphQLErrors);
}
}
};
```
[Read more about error handling on the "Errors" page.](./errors.md)
## Subscriptions
For GraphQL subscriptions, `@urql/solid-start` provides a `createSubscription` primitive that uses the same SolidStart `Provider` context as `createQuery` and `createMutation`:
```jsx
import { gql } from '@urql/core';
import { createSubscription } from '@urql/solid-start';
import { createSignal, For } from 'solid-js';
const NewTodos = gql`
subscription {
newTodos {
id
title
}
}
`;
export default function TodoSubscription() {
const [todos, setTodos] = createSignal([]);
const handleSubscription = (previousData, newData) => {
setTodos(current => [...current, newData.newTodos]);
return newData;
};
const [result] = createSubscription(
{
query: NewTodos,
},
handleSubscription
);
return (
Live Updates
{todo =>
{todo.title}
}
);
}
```
Note that GraphQL subscriptions typically require WebSocket support. You'll need to configure your client with a subscription exchange like `subscriptionExchange` from `@urql/core`.
## Server-Side Rendering
SolidStart automatically handles server-side rendering and hydration. The `createQuery` primitive works seamlessly on both server and client:
1. On the server, queries execute during SSR and their results are serialized
2. On the client, SolidStart hydrates the data without refetching
3. Subsequent navigations use the standard cache policies
### SSR Considerations
When using `createQuery` in SolidStart:
- Queries execute on the server during initial page load
- Results are automatically streamed to the client
- The client hydrates with the server data
- No manual script injection or data serialization needed
- SolidStart handles all the complexity automatically
### Handling cookies and authentication
For authenticated requests, forward cookies and headers from the server request:
```jsx
import { getRequestEvent } from 'solid-js/web';
import { createClient, cacheExchange, fetchExchange } from '@urql/solid-start';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const event = getRequestEvent();
const headers: Record = {};
// Forward cookies for authenticated requests
if (event) {
const cookie = event.request.headers.get('cookie');
if (cookie) {
headers.cookie = cookie;
}
}
return { headers };
},
});
```
## SolidJS vs SolidStart
### When to Use Each Package
| Use Case | Package | Why |
| ------------------ | ------------------- | ---------------------------------------------------------------- |
| Client-side SPA | `@urql/solid` | Optimized for client-only apps, uses SolidJS reactivity patterns |
| SolidStart SSR App | `@urql/solid-start` | Integrates with SolidStart's routing, SSR, and action system |
### Key Differences
#### Queries
**@urql/solid** (Client-side):
```tsx
import { createQuery } from '@urql/solid';
const [result] = createQuery({ query: TodosQuery });
// Returns: [Accessor, Accessor]
```
**@urql/solid-start** (SSR):
```tsx
import { createQuery } from '@urql/solid-start';
import { createAsync } from '@solidjs/router';
const queryTodos = createQuery(TodosQuery, 'todos');
const todos = createAsync(() => queryTodos());
// Returns: Accessor
// Works with SSR and SolidStart's caching
```
#### Mutations
**@urql/solid** (Client-side):
```tsx
import { createMutation } from '@urql/solid';
const [result, executeMutation] = createMutation(AddTodoMutation);
await executeMutation({ title: 'New Todo' });
// Returns: [Accessor, ExecuteMutation]
```
**@urql/solid-start** (SSR with Actions):
```tsx
import { createMutation } from '@urql/solid-start';
import { useAction, useSubmission } from '@solidjs/router';
const addTodoAction = createMutation(AddTodoMutation, 'add-todo');
const addTodo = useAction(addTodoAction);
const submission = useSubmission(addTodoAction);
await addTodo({ title: 'New Todo' });
// Integrates with SolidStart's action system for progressive enhancement
```
### Why Different APIs?
- **SSR Support**: SolidStart queries run on the server and stream to the client
- **Router Integration**: Automatic caching and invalidation with SolidStart's router
- **Progressive Enhancement**: Actions work without JavaScript enabled
- **Suspense**: Native support for SolidJS Suspense boundaries
### Migration
If you're moving from a SolidJS SPA to SolidStart:
1. Change imports from `@urql/solid` to `@urql/solid-start`
2. Wrap queries with `createAsync()`
3. Update mutations to use the action pattern with `useAction()` and `useSubmission()`
For more details, see the [Solid bindings documentation](./solid.md).
## Reading on
This concludes the introduction for using `@urql/solid-start` with SolidStart. For more information:
- [Solid bindings documentation](./solid.md) - for client-only features
- [How does the default "document cache" work?](./document-caching.md)
- [How are errors handled and represented?](./errors.md)
- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
================================================
FILE: docs/basics/solid.md
================================================
---
title: Solid Bindings
order: 3
---
# Solid
This guide covers how to install and setup `@urql/solid` and the `Client`, as well as query and mutate data with Solid. The `@urql/solid` package provides reactive primitives that integrate seamlessly with Solid's fine-grained reactivity system.
> **Note:** This guide is for client-side SolidJS applications. If you're building a SolidStart application with SSR, see the [SolidStart guide](./solid-start.md) instead. The packages use different APIs optimized for their respective use cases.
## Getting started
### Installation
Installing `@urql/solid` is quick and you won't need any other packages to get started with at first. We'll install the package with our package manager of choice.
```sh
yarn add @urql/solid graphql
# or
npm install --save @urql/solid graphql
# or
pnpm add @urql/solid graphql
```
Most libraries related to GraphQL also need the `graphql` package to be installed as a peer dependency, so that they can adapt to your specific versioning requirements. That's why we'll need to install `graphql` alongside `@urql/solid`.
Both the `@urql/solid` and `graphql` packages follow [semantic versioning](https://semver.org) and all `@urql/solid` packages will define a range of compatible versions of `graphql`. Watch out for breaking changes in the future however, in which case your package manager may warn you about `graphql` being out of the defined peer dependency range.
### Setting up the `Client`
The `@urql/solid` package exports a `Client` class from `@urql/core`, which we can use to create the GraphQL client. This central `Client` manages all of our GraphQL requests and results.
```js
import { createClient, cacheExchange, fetchExchange } from '@urql/solid';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client` to get started.
Another common option is `fetchOptions`. This option allows us to customize the options that will be passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object, or a function returning an options object.
In the following example we'll add a token to each `fetch` request that our `Client` sends to our GraphQL API.
```js
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const token = getToken();
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
```
### Providing the `Client`
To make use of the `Client` in Solid we will have to provide it via Solid's Context API. This may be done with the help of the `Provider` export.
```jsx
import { render } from 'solid-js/web';
import { createClient, Provider, cacheExchange, fetchExchange } from '@urql/solid';
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
const App = () => (
);
render(() => , document.getElementById('root'));
```
Now every component inside and under the `Provider` can use GraphQL queries that will be sent to our API.
## Queries
The `@urql/solid` package offers a `createQuery` primitive that integrates with Solid's fine-grained reactivity system.
### Run a first query
For the following examples, we'll imagine that we're querying data from a GraphQL API that contains todo items. Let's dive right into it!
```jsx
import { Suspense, For } from 'solid-js';
import { gql } from '@urql/core';
import { createQuery } from '@urql/solid';
const TodosQuery = gql`
query {
todos {
id
title
}
}
`;
const Todos = () => {
const [result] = createQuery({
query: TodosQuery,
});
return (
Loading...}>
{(todo) =>
{todo.title}
}
);
};
```
Here we have implemented our first GraphQL query to fetch todos. We see that `createQuery` accepts options and returns a tuple. In this case we've set the `query` option to our GraphQL query. The tuple we then get in return is an array where the first item is an accessor function that returns the result object.
The result object contains several properties. The `fetching` field indicates whether the query is loading data, `data` contains the actual `data` from the API's result, and `error` is set when either the request to the API has failed or when our API result contained some `GraphQLError`s, which we'll get into later on the ["Errors" page](./errors.md).
### Variables
Typically we'll also need to pass variables to our queries, for instance, if we are dealing with pagination. For this purpose `createQuery` also accepts a `variables` option, which can be reactive.
```jsx
const TodosListQuery = gql`
query ($from: Int!, $limit: Int!) {
todos(from: $from, limit: $limit) {
id
title
}
}
`;
const Todos = (props) => {
const [result] = createQuery({
query: TodosListQuery,
variables: () => ({ from: props.from, limit: props.limit }),
});
// ...
};
```
The `variables` option can be passed as a static object or as an accessor function that returns the variables. When using an accessor, the query will automatically re-execute when the variables change.
```jsx
import { Suspense, For, createSignal } from 'solid-js';
import { gql } from '@urql/core';
import { createQuery } from '@urql/solid';
const TodosListQuery = gql`
query ($from: Int!, $limit: Int!) {
todos(from: $from, limit: $limit) {
id
title
}
}
`;
const Todos = () => {
const [from, setFrom] = createSignal(0);
const limit = 10;
const [result] = createQuery({
query: TodosListQuery,
variables: () => ({ from: from(), limit }),
});
return (
Loading...}>
{(todo) =>
{todo.title}
}
);
};
```
Whenever the variables change, `fetching` will switch to `true`, and a new request will be sent to our API, unless a result has already been cached previously.
### Pausing `createQuery`
In some cases we may want `createQuery` to execute a query when a pre-condition has been met, and not execute the query otherwise. For instance, we may be building a form and want a validation query to only take place when a field has been filled out.
The `createQuery` primitive accepts a `pause` option that temporarily stops the query from executing.
```jsx
const Todos = (props) => {
const shouldPause = () => props.from == null || props.limit == null;
const [result] = createQuery({
query: TodosListQuery,
variables: () => ({ from: props.from, limit: props.limit }),
pause: shouldPause,
});
// ...
};
```
Now whenever the mandatory variables aren't supplied the query won't be executed. This also means that `result().data` won't change, which means we'll still have access to our old data even though the variables may have changed.
### Request Policies
The `createQuery` primitive accepts a `requestPolicy` option that determines how results are retrieved from our `Client`'s cache. By default, this is set to `cache-first`, which means that we prefer to get results from our cache, but are falling back to sending an API request.
Request policies aren't specific to `@urql/solid`, but are a common feature in urql's core. [You can learn more about how the cache behaves given the four different policies on the "Document Caching" page.](./document-caching.md)
```jsx
const [result] = createQuery({
query: TodosListQuery,
variables: () => ({ from: props.from, limit: props.limit }),
requestPolicy: 'cache-and-network',
});
```
The `requestPolicy` can be passed as a static string or as an accessor function. When using `cache-and-network`, the query will be refreshed from our API even after our cache has given us a cached result.
### Context Options
The `requestPolicy` option is part of urql's context options. In fact, there are several more built-in context options. These options can be passed via the `context` parameter.
```jsx
const [result] = createQuery({
query: TodosListQuery,
variables: () => ({ from: props.from, limit: props.limit }),
context: () => ({
requestPolicy: 'cache-and-network',
url: 'http://localhost:3000/graphql?debug=true',
}),
});
```
[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
### Reexecuting Queries
The `createQuery` primitive updates and executes queries automatically when reactive inputs change, but in some cases we may need to programmatically trigger a new query. This is the purpose of the second item in the tuple that `createQuery` returns.
```jsx
const Todos = () => {
const [result, reexecuteQuery] = createQuery({
query: TodosListQuery,
variables: { from: 0, limit: 10 },
});
const refresh = () => {
// Refetch the query and skip the cache
reexecuteQuery({ requestPolicy: 'network-only' });
};
return (
Loading...}>
{(todo) =>
{todo.title}
}
);
};
```
Calling `refresh` in the above example will execute the query again forcefully, and will skip the cache, since we're passing `requestPolicy: 'network-only'`.
## Mutations
The `@urql/solid` package offers a `createMutation` primitive for executing GraphQL mutations.
### Sending a mutation
Let's again pick up an example with an imaginary GraphQL API for todo items. We'll set up a mutation that updates a todo item's title.
```jsx
import { gql } from '@urql/core';
import { createMutation } from '@urql/solid';
const UpdateTodo = gql`
mutation ($id: ID!, $title: String!) {
updateTodo (id: $id, title: $title) {
id
title
}
}
`;
const Todo = (props) => {
const [result, updateTodo] = createMutation(UpdateTodo);
const handleSubmit = (newTitle) => {
updateTodo({ id: props.id, title: newTitle });
};
return (
Updating...
Error: {result().error.message}
{/* Your form UI here */}
);
};
```
Similar to `createQuery`, `createMutation` returns a tuple. The first item is an accessor that returns the result object containing `fetching`, `error`, and `data` — identical to query results. The second item is the execute function that triggers the mutation.
Unlike `createQuery`, `createMutation` doesn't execute automatically. We must call the execute function with the mutation variables.
### Using the mutation result
The mutation result is available both through the reactive accessor and through the promise returned by the execute function.
```jsx
const Todo = (props) => {
const [result, updateTodo] = createMutation(UpdateTodo);
const handleSubmit = (newTitle) => {
const variables = { id: props.id, title: newTitle };
updateTodo(variables).then((result) => {
// The result is almost identical to result() from the accessor
// It is an OperationResult.
if (!result.error) {
console.log('Todo updated!', result.data);
}
});
};
return (
Updating...
{/* Your form UI here */}
);
};
```
The reactive accessor is useful when your UI needs to display progress on the mutation, and the returned promise is particularly useful for side effects that run after the mutation completes.
### Handling mutation errors
The promise returned by the execute function will never reject. Instead it will always return a promise that resolves to a result.
If you're checking for errors, you should use `result.error`, which will be set to a `CombinedError` when any kind of errors occurred while executing your mutation. [Read more about errors on our "Errors" page.](./errors.md)
```jsx
const Todo = (props) => {
const [result, updateTodo] = createMutation(UpdateTodo);
const handleSubmit = (newTitle) => {
const variables = { id: props.id, title: newTitle };
updateTodo(variables).then((result) => {
if (result.error) {
console.error('Oh no!', result.error);
}
});
};
// ...
};
```
## Subscriptions
The `@urql/solid` package offers a `createSubscription` primitive for handling GraphQL subscriptions with Solid's reactive system.
### Setting up a subscription
GraphQL subscriptions allow you to receive real-time updates from your GraphQL API. Here's an example of how to set up a subscription:
```jsx
import { gql } from '@urql/core';
import { createSubscription } from '@urql/solid';
const NewTodos = gql`
subscription {
newTodos {
id
title
}
}
`;
const TodoSubscription = () => {
const [result] = createSubscription({
query: NewTodos,
});
return (
Waiting for updates...
Error: {result().error.message}
New todo: {result().data.newTodos.title}
);
};
```
### Handling subscription data
Unlike queries and mutations, subscriptions can emit multiple results over time. You can use a `handler` function to accumulate or process subscription events:
```jsx
import { createSignal } from 'solid-js';
const TodoSubscription = () => {
const [todos, setTodos] = createSignal([]);
const handleSubscription = (previousData, newData) => {
setTodos(current => [...current, newData.newTodos]);
return newData;
};
const [result] = createSubscription(
{
query: NewTodos,
},
handleSubscription
);
return (
{(todo) =>
{todo.title}
}
);
};
```
The handler function receives the previous data and the new data from the subscription, allowing you to accumulate results or transform them as needed.
## Reading on
This concludes the introduction for using `@urql/solid` with Solid. The rest of the documentation is mostly framework-agnostic and will apply to either `urql` in general, or the `@urql/core` package, which is the same between all framework bindings. Hence, next we may want to learn more about one of the following:
- [How does the default "document cache" work?](./document-caching.md)
- [How are errors handled and represented?](./errors.md)
- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
================================================
FILE: docs/basics/svelte.md
================================================
---
title: Svelte Bindings
order: 2
---
# Svelte
## Getting started
This "Getting Started" guide covers how to install and set up `urql` and provide a `Client` for
Svelte. The `@urql/svelte` package, which provides bindings for Svelte, doesn't fundamentally
function differently from `@urql/preact` or `urql` and uses the same [Core Package and
`Client`](./core.md).
### Installation
Installing `@urql/svelte` is quick and no other packages are immediately necessary.
```sh
yarn add @urql/svelte
# or
npm install --save @urql/svelte
```
Most libraries related to GraphQL also need the `graphql` package to be installed as a peer
dependency, so that they can adapt to your specific versioning requirements. That's why we'll need
to install `graphql` alongside `@urql/svelte`.
Both the `@urql/svelte` and `graphql` packages follow [semantic versioning](https://semver.org) and
all `@urql/svelte` packages will define a range of compatible versions of `graphql`. Watch out
for breaking changes in the future however, in which case your package manager may warn you about
`graphql` being out of the defined peer dependency range.
Note: if using Vite as your bundler, you might stumble upon the error `Function called outside component initialization`, which will prevent the page from loading. To fix it, you must add `@urql/svelte` to Vite's configuration property [`optimizeDeps.exclude`](https://vitejs.dev/config/#dep-optimization-options):
```js
{
optimizeDeps: {
exclude: ['@urql/svelte'],
}
// other properties
}
```
### Setting up the `Client`
The `@urql/svelte` package exports a `Client` class, which we can use to create
the GraphQL client. This central `Client` manages all of our GraphQL requests and results.
```js
import { Client, cacheExchange, fetchExchange } from '@urql/svelte';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
At the bare minimum we'll need to pass an API's `url` and `exchanges`
when we create a `Client` to get started.
Another common option is `fetchOptions`. This option allows us to customize the options that will be
passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object or
a function returning an options object.
In the following example we'll add a token to each `fetch` request that our `Client` sends to our
GraphQL API.
```js
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const token = getToken();
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
```
### Providing the `Client`
To make use of the `Client` in Svelte we will have to provide it via the
[Context API](https://svelte.dev/tutorial/context-api). From a parent component to its child
components. This will share one `Client` with the rest of our app, if we for instance provide the
`Client`
```html
```
The `setContextClient` method internally calls [Svelte's `setContext`
function](https://svelte.dev/docs#run-time-svelte-setcontext). The `@urql/svelte` package also exposes a `getContextClient`
function that uses [`getContext`](https://svelte.dev/docs#run-time-svelte-getcontext) to retrieve the `Client` in
child components. This is used to input the client into `@urql/svelte`'s API.
## Queries
We'll implement queries using the `queryStore` function from `@urql/svelte`.
The `queryStore` function creates a [Svelte Writable store](https://svelte.dev/docs#writable).
You can use it to initialise a data container in `urql`. This store holds on to our query inputs,
like the GraphQL query and variables, which we can change to launch new queries. It also exposes
the query's eventual result, which we can then observe.
### Run a first query
For the following examples, we'll imagine that we're querying data from a GraphQL API that contains
todo items. Let's dive right into it!
```js
{#if $todos.fetching}
Loading...
{:else if $todos.error}
Oh no... {$todos.error.message}
{:else}
{#each $todos.data.todos as todo}
{todo.title}
{/each}
{/if}
```
Here we have implemented our first GraphQL query to fetch todos. We're first creating a
`queryStore` which will start our GraphQL query.
The `todos` store can now be used like any other Svelte store using a
[reactive auto-subscription](https://svelte.dev/tutorial/auto-subscriptions) in Svelte. This means
that we prefix `$todos` with a dollar symbol, which automatically subscribes us to its changes.
### Variables
Typically we'll also need to pass variables to our queries, for instance, if we are dealing with
pagination. For this purpose the `queryStore` also accepts a `variables` argument, which we can
use to supply variables to our query.
```js
...
```
> Note that we prefix the variable with `$` so Svelte knows that this store is reactive
As when we're sending GraphQL queries manually using `fetch`, the variables will be attached to the
`POST` request body that is sent to our GraphQL API.
The `queryStore` also supports being actively changed. This will hook into Svelte's reactivity
model as well and cause the `query` utility to start a new operation.
```js
```
### Pausing Queries
In some cases we may want our queries to not execute until a pre-condition has been met. Since the
`query` operation exists for the entire component lifecycle however, it can't just be stopped and
started at will. Instead, the `queryStore` accepts a key named `pause` that will tell the store that
is starts out as paused.
For instance, we may start out with a paused store and then unpause it once a callback is invoked:
```html
Unpause
```
### Request Policies
The `queryStore` also accepts another key apart from `query` and `variables`. Optionally
you may pass a `requestPolicy`.
The `requestPolicy` option determines how results are retrieved from our `Client`'s cache. By
default, this is set to `cache-first`, which means that we prefer to get results from our cache, but
are falling back to sending an API request.
Request policies aren't specific to `urql`'s Svelte bindings, but are a common feature in its core.
[You can learn more about how the cache behaves given the four different policies on the "Document
Caching" page.](../basics/document-caching.md)
```js
...
```
As we can see, the `requestPolicy` is easily changed by passing it directly as a "context option"
when creating a `queryStore`.
Internally, the `requestPolicy` is just one of several "**context** options". The `context`
provides metadata apart from the usual `query` and `variables` we may pass. This means that
we may also change the `Client`'s default `requestPolicy` by passing it there.
```js
import { Client, cacheExchange, fetchExchange } from '@urql/svelte';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
// every operation will by default use cache-and-network rather
// than cache-first now:
requestPolicy: 'cache-and-network',
});
```
### Context Options
As mentioned, the `requestPolicy` option that we're passing to the `queryStore` is a part of
`urql`'s context options. In fact, there are several more built-in context options, and the
`requestPolicy` option is one of them. Another option we've already seen is the `url` option, which
determines our API's URL.
```js
...
```
As we can see, the `context` argument for `queryStore` accepts any known `context` option and
can be used to alter them per query rather than globally. The `Client` accepts a subset of `context`
options, while the `queryStore` argument does the same for a single query. They're then merged
for your operation and form a full `Context` object for each operation, which means that any given
query is able to override them as needed.
[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
### Reexecuting queries
Sometimes we'll need to arbitrarly reexecute a query to check for new data on the server, this can be done through:
```jsx
```
We use the `requestPolicy` with value `network-only` so we don't hit our cache and dispatch a refresh,
if it updates the data the `todos` will be updated due to our cache updating.
### Reading on
There are some more tricks we can use with `queryStore`.
[Read more about its API in the API docs for it.](../api/svelte.md#queryStore)
## Mutations
The `mutationStore` function is similar to the `queryStore` function but is triggered manually and
can accept a [`GraphQLRequest` object](../api/core.md#graphqlrequest).
### Sending a mutation
Let's again pick up an example with an imaginary GraphQL API for todo items, and dive into an
example! We'll set up a mutation that _updates_ a todo item's title.
```html
```
This small call to `mutationStore` accepts a `query` property (besides the `variables` property) and
returns the `OperationResult` as a store.
Unlike the `query` function, we don't want the mutation to start automatically hence we enclose it in
a function. The `result` will be updated with the `fetching`, `data`, ... as a normal query would which
you can in-turn use in your UI.
### Handling mutation errors
It's worth noting that the promise we receive when calling the execute function will never
reject. Instead it will always return a promise that resolves to an `mutationStore`, even if the
mutation has failed.
If you're checking for errors, you should use `mutationStore.error` instead, which will be set
to a `CombinedError` when any kind of errors occurred while executing your mutation.
[Read more about errors on our "Errors" page.](./errors.md)
```jsx
mutateTodo({ id, title: newTitle }).then(result => {
if (result.error) {
console.error('Oh no!', result.error);
}
});
```
## Reading on
This concludes the introduction for using `urql` with Svelte. The rest of the documentation
is mostly framework-agnostic and will apply to either `urql` in general, or the `@urql/core` package,
which is the same between all framework bindings. Hence, next we may want to learn more about one of
the following to learn more about the internals:
- [How does the default "document cache" work?](./document-caching.md)
- [How are errors handled and represented?](./errors.md)
- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
================================================
FILE: docs/basics/typescript-integration.md
================================================
---
title: TypeScript integration
order: 7
---
# URQL and TypeScript
URQL, with the help of [GraphQL Code Generator](https://www.the-guild.dev/graphql/codegen), can leverage the typed-design of GraphQL Schemas to generate TypeScript types on the flight.
## Getting started
### Installation
To get and running, install the following packages:
```sh
yarn add -D graphql typescript @graphql-codegen/cli @graphql-codegen/client-preset
# or
npm install -D graphql typescript @graphql-codegen/cli @graphql-codegen/client-preset
```
Then, add the following script to your `package.json`:
```json
{
"scripts": {
"codegen": "graphql-codegen"
}
}
```
Now, let's create a configuration file for our current framework setup:
### Configuration
#### React project configuration
Create the following `codegen.ts` configuration file:
```ts
import { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: '',
documents: ['src/**/*.tsx'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client',
plugins: [],
},
},
};
export default config;
```
#### Vue project configuration
Create the following `codegen.ts` configuration file:
```ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: '',
documents: ['src/**/*.vue'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client',
config: {
useTypeImports: true,
},
plugins: [],
},
},
};
export default config;
```
## Typing queries, mutations and subscriptions
Now that your project is properly configured, let's start codegen in watch mode:
```sh
yarn codegen
# or
npm run codegen
```
This will generate a `./src/gql` folder that exposes a `graphql()` function.
Let's use this `graphql()` function to write our GraphQL Queries, Mutations and Subscriptions.
Here, an example with the React bindings, however, the usage remains the same for Vue and Svelte bindings:
```tsx
import React from 'react';
import { useQuery } from 'urql';
import './App.css';
import Film from './Film';
import { graphql } from '../src/gql';
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`);
function App() {
// `data` is typed!
const [{ data }] = useQuery({
query: allFilmsWithVariablesQueryDocument,
variables: { first: 10 },
});
return (
);
}
export default App;
```
_Examples with Vue are available [in the GraphQL Code Generator repository](https://github.com/dotansimha/graphql-code-generator/tree/master/examples/vue/urql)_.
Using the generated `graphql()` function to write your GraphQL document results in instantly typed result and variables for queries, mutations and subscriptions!
Let's now see how to go further with GraphQL fragments.
## Getting further with Fragments
> Using GraphQL Fragments helps to explicitly declaring the data dependencies of your UI component and safely accessing only the data it needs.
Our `` component relies on the `FilmItem` definition, passed through the `film` props:
```tsx
// ...
import Film from './Film';
import { graphql } from '../src/gql';
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`);
function App() {
// ...
return (
);
}
// ...
```
GraphQL Code Generator generates type helpers to type your component props based on Fragments (for example, the `film=` prop) and retrieve your fragment's data (see example below).
Again, here is an example with the React bindings:
```tsx
import { FragmentType, useFragment } from './gql/fragment-masking';
import { graphql } from '../src/gql';
// again, we use the generated `graphql()` function to write GraphQL documents 👀
export const FilmFragment = graphql(/* GraphQL */ `
fragment FilmItem on Film {
id
title
releaseDate
producers
}
`);
const Film = (props: {
// `film` property has the correct type 🎉
film: FragmentType;
}) => {
// `film` is of type `FilmFragment`, with no extraneous properties ⚡️
const film = useFragment(FilmFragment, props.film);
return (
{film.title}
{film.releaseDate}
);
};
export default Film;
```
_Examples with Vue are available [in the GraphQL Code Generator repository](https://github.com/dotansimha/graphql-code-generator/tree/master/examples/vue/urql)_.
You will notice that our `` component leverages 2 imports from our generated code (from `../src/gql`): the `FragmentType` type helper and the `useFragment()` function.
- we use `FragmentType` to get the corresponding Fragment TypeScript type
- later on, we use `useFragment()` to retrieve the properly film property
================================================
FILE: docs/basics/ui-patterns.md
================================================
---
title: UI-Patterns
order: 6
---
# UI Patterns
> This page is incomplete. You can help us expanding it by suggesting more patterns or asking us about common problems you're facing on [GitHub Discussions](https://github.com/urql-graphql/urql/discussions).
Generally, `urql`'s API surface is small and compact. Some common problems that we're facing when building apps may look like they're not a built-in feature, however, there are several patterns that even a lean UI can support.
This page is a collection of common UI patterns and problems we may face with GraphQL and how we can tackle them in
`urql`. These examples will be written in React but apply to any other framework.
## Infinite scrolling
"Infinite Scrolling" is the approach of loading more data into a page's list without splitting that list up across multiple pages.
There are a few ways of going about this. In our [normalized caching chapter on the topic](../graphcache/local-resolvers.md#pagination)
we see an approach with `urql`'s normalized cache, which is suitable to get started quickly. However, this approach also requires some UI code as well to keep track of pages.
Let's have a look at how we can create a UI implementation that makes use of this normalized caching feature.
```js
import React from 'react';
import { useQuery, gql } from 'urql';
const PageQuery = gql`
query Page($first: Int!, $after: String) {
todos(first: $first, after: $after) {
nodes {
id
name
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
const SearchResultPage = ({ variables, isLastPage, onLoadMore }) => {
const [{ data, fetching, error }] = useQuery({ query: PageQuery, variables });
const todos = data?.todos;
return (
);
};
```
Here we keep an array of all `variables` we've encountered and use them to render their
respective `result` page. This only rerenders the additional page rather than having a long
list that constantly changes. [You can find a full code example of this pattern in our example folder on the topic of pagination.](https://github.com/urql-graphql/urql/tree/main/examples/with-pagination)
This code doesn't take changing variables into account, which will affect the cursors. For an
example that takes full infinite scrolling into account, [you can find a full code example of an
extended pattern in our example folder on the topic of infinite pagination.](https://github.com/urql-graphql/urql/tree/main/examples/with-infinite-pagination)
## Prefetching data
We sometimes find it necessary to load data for a new page before that page is opened, for instance while a JS bundle is still loading. We may
do this with help of the `Client`, by calling methods without using the React bindings directly.
```js
import React from 'react';
import { useClient, gql } from 'urql';
const TodoQuery = gql`
query Todo($id: ID!) {
todo(id: $id) {
id
name
}
}
`;
const Component = () => {
const client = useClient();
const router = useRouter();
const transitionPage = React.useCallback(async id => {
const loadJSBundle = import('./page.js');
const loadData = client.query(TodoQuery, { id }).toPromise();
await Promise.all([loadJSBundle, loadData]);
router.push(`/todo/${id}`);
}, []);
return transitionPage('1')}>Go to todo 1;
};
```
Here we're calling `client.query` to prepare a query when the transition begins.
We then call `toPromise()` on this query which activates it. Our `Client` and its cache share results, which means that we've already kicked off or even completed the query before we're on the new page.
## Lazy query
It's often required to "lazily" start a query, either at a later point or imperatively. This means that we don't start a query when a new component is mounted immediately.
Parts of `urql` that automatically start, like the `useQuery` hook, have a concept of a [`pause` option.](./react-preact.md#pausing-usequery) This option is used to prevent the hook from automatically starting a new query.
```js
import React from 'react';
import { useQuery, gql } from 'urql';
const TodoQuery = gql`
query Todos {
todos {
id
name
}
}
`;
const Component = () => {
const [result, fetch] = useQuery({ query: TodoQuery, pause: true });
const router = useRouter();
return Load todos;
};
```
We can unpause the hook to start fetching, or, like in this example, call its returned function to manually kick off the query.
## Reacting to focus and stale time
In urql we leverage our extensibility pattern named "Exchanges" to manipulate the way
data comes in and goes out of our client.
- [Stale time](https://github.com/urql-graphql/urql/tree/main/exchanges/request-policy)
- [Focus](https://github.com/urql-graphql/urql/tree/main/exchanges/refocus)
When we want to introduce one of these patterns we add the package and add it to the `exchanges`
property of our `Client`. In the case of these two we'll have to add it before the cache
else our requests will never get upgraded.
```js
import { Client, cacheExchange, fetchExchange } from 'urql';
import { refocusExchange } from '@urql/exchange-refocus';
const client = new Client({
url: 'some-url',
exchanges: [refocusExchange(), cacheExchange, fetchExchange],
});
```
That's all we need to do to react to these patterns.
================================================
FILE: docs/basics/vue.md
================================================
---
title: Vue Bindings
order: 1
---
# Vue
## Getting started
The `@urql/vue` bindings have been written with [Vue
3](https://github.com/vuejs/vue-next/releases/tag/v3.0.0) in mind and use Vue's newer [Composition
API](https://v3.vuejs.org/guide/composition-api-introduction.html). This gives the `@urql/vue`
bindings capabilities to be more easily integrated into your existing `setup()` functions.
### Installation
Installing `@urql/vue` is quick and no other packages are immediately necessary.
```sh
yarn add @urql/vue graphql
# or
npm install --save @urql/vue graphql
```
Most libraries related to GraphQL also need the `graphql` package to be installed as a peer
dependency, so that they can adapt to your specific versioning requirements. That's why we'll need
to install `graphql` alongside `@urql/vue`.
Both the `@urql/vue` and `graphql` packages follow [semantic versioning](https://semver.org) and
all `@urql/vue` packages will define a range of compatible versions of `graphql`. Watch out
for breaking changes in the future however, in which case your package manager may warn you about
`graphql` being out of the defined peer dependency range.
### Setting up the `Client`
The `@urql/vue` package exports a `Client` class, which we can use to create
the GraphQL client. This central `Client` manages all of our GraphQL requests and results.
```js
import { Client, cacheExchange, fetchExchange } from '@urql/vue';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
```
At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client`
to get started.
Another common option is `fetchOptions`. This option allows us to customize the options that will be
passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object or
a function returning an options object.
In the following example we'll add a token to each `fetch` request that our `Client` sends to our
GraphQL API.
```js
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
fetchOptions: () => {
const token = getToken();
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
```
### Providing the `Client`
To make use of the `Client` in Vue we will have to provide from a parent component to its child
components. This will share one `Client` with the rest of our app. In `@urql/vue` there are two
different ways to achieve this.
The first method is to use `@urql/vue`'s `provideClient` function. This must be called in any of
your parent components and accepts either a `Client` directly or just the options that you'd pass to
`Client`.
```html
```
Alternatively we may use the exported `install` function and treat `@urql/vue` as a plugin by
importing its default export and using it [as a plugin](https://v3.vuejs.org/guide/plugins.html#using-a-plugin).
```js
import { createApp } from 'vue';
import Root from './App.vue';
import urql, { cacheExchange, fetchExchange } from '@urql/vue';
const app = createApp(Root);
app.use(urql, {
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
});
app.mount('#app');
```
The plugin also accepts `Client`'s options or a `Client` as its inputs.
## Queries
We'll implement queries using the `useQuery` function from `@urql/vue`.
### Run a first query
For the following examples, we'll imagine that we're querying data from a GraphQL API that contains
todo items. Let's dive right into it!
```jsx
Loading...
Oh no... {{error}}
{{ todo.title }}
```
Here we have implemented our first GraphQL query to fetch todos. We see that `useQuery` accepts
options and returns a result object. In this case we've set the `query` option to our GraphQL query.
The result object contains several properties. The `fetching` field indicates whether we're currently
loading data, `data` contains the actual `data` from the API's result, and `error` is set when either
the request to the API has failed or when our API result contained some `GraphQLError`s, which
we'll get into later on the ["Errors" page](./errors.md).
All of these properties on the result are derived from the [shape of
`OperationResult`](../api/core.md#operationresult) and are marked as [reactive
](https://v3.vuejs.org/guide/reactivity-fundamentals.html), which means they may
update while the query is running, which will automatically update your UI.
### Variables
Typically we'll also need to pass variables to our queries, for instance, if we are dealing with
pagination. For this purpose `useQuery` also accepts a `variables` input, which we can
use to supply variables to our query.
```jsx
...
```
As when we're sending GraphQL queries manually using `fetch`, the variables will be attached to the
`POST` request body that is sent to our GraphQL API.
All inputs that are passed to `useQuery` may also be [reactive
state](https://v3.vuejs.org/guide/reactivity-fundamentals.html). This means that both the inputs and
outputs of `useQuery` are reactive and may change over time.
```jsx
{{ todo.title }}
Next Page
```
### Pausing `useQuery`
In some cases we may want `useQuery` to execute a query when a pre-condition has been met, and not
execute the query otherwise. For instance, we may be building a form and want a validation query to
only take place when a field has been filled out.
Since with Vue 3's Composition API we won't just conditionally call `useQuery` we can instead pass a
reactive `pause` input to `useQuery`.
In the previous example we've defined a query with mandatory arguments. The `$from` and `$limit`
variables have been defined to be non-nullable `Int!` values.
Let's pause the query we've just written to not execute when these variables are empty, to
prevent `null` variables from being executed. We can do this by computing `pause` to become `true`
whenever these variables are falsy:
```js
import { reactive } from 'vue'
import { gql, useQuery } from '@urql/vue';
export default {
props: ['from', 'limit'],
setup({ from, limit }) {
const shouldPause = computed(() => from == null || limit == null);
return useQuery({
query: gql`
query ($from: Int!, $limit: Int!) {
todos(from: $from, limit: $limit) {
id
title
}
}
`,
variables: { from, limit },
pause: shouldPause
});
}
};
```
Now whenever the mandatory `$from` or `$limit` variables aren't supplied the query won't be executed.
This also means that `result.data` won't change, which means we'll still have access to our old data
even though the variables may have changed.
It's worth noting that depending on whether `from` and `limit` are reactive or not you may have to
change how `pause` is computed. But there's also an imperative alternative to this API. Not only
does the result you get back from `useQuery` have an `isPaused` ref, it also has `pause()` and
`resume()` methods.
```jsx
Loading...
Toggle Query
```
This means that no matter whether you're in or outside of `setup()` or rather supplying the inputs
to `useQuery` or using the outputs, you'll have access to ways to pause or unpause the query.
### Request Policies
As has become clear in the previous sections of this page, the `useQuery` hook accepts more options
than just `query` and `variables`. Another option we should touch on is `requestPolicy`.
The `requestPolicy` option determines how results are retrieved from our `Client`'s cache. By
default this is set to `cache-first`, which means that we prefer to get results from our cache, but
are falling back to sending an API request.
Request policies aren't specific to `urql`'s Vue bindings, but are a common feature in its core.
[You can learn more about how the cache behaves given the four different policies on the "Document
Caching" page.](../basics/document-caching.md)
```js
import { useQuery } from '@urql/vue';
export default {
setup() {
return useQuery({
query: TodosQuery,
requestPolicy: 'cache-and-network',
});
},
};
```
Specifically, a new request policy may be passed directly to `useQuery` as an option.
This policy is then used for this specific query. In this case, `cache-and-network` is used and
the query will be refreshed from our API even after our cache has given us a cached result.
Internally, the `requestPolicy` is just one of several "**context** options". The `context`
provides metadata apart from the usual `query` and `variables` we may pass. This means that
we may also change the `Client`'s default `requestPolicy` by passing it there.
```js
import { Client, cacheExchange, fetchExchange } from '@urql/vue';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange, fetchExchange],
// every operation will by default use cache-and-network rather
// than cache-first now:
requestPolicy: 'cache-and-network',
});
```
### Context Options
As mentioned, the `requestPolicy` option on `useQuery` is a part of `urql`'s context options.
In fact, there are several more built-in context options, and the `requestPolicy` option is
one of them. Another option we've already seen is the `url` option, which determines our
API's URL. These options aren't limited to the `Client` and may also be passed per query.
```jsx
import { useQuery } from '@urql/vue';
export default {
setup() {
return useQuery({
query: TodosQuery,
context: {
requestPolicy: 'cache-and-network',
url: 'http://localhost:3000/graphql?debug=true',
},
});
},
};
```
As we can see, the `context` property for `useQuery` accepts any known `context` option and can be
used to alter them per query rather than globally. The `Client` accepts a subset of `context`
options, while the `useQuery` option does the same for a single query.
[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext)
### Reexecuting Queries
The `useQuery` hook updates and executes queries whenever its inputs, like the `query` or
`variables` change, but in some cases we may find that we need to programmatically trigger a new
query. This is the purpose of the `executeQuery` method which is a method on the result object
that `useQuery` returns.
Triggering a query programmatically may be useful in a couple of cases. It can for instance be used
to refresh data that is currently being displayed. In these cases we may also override the
`requestPolicy` of our query just once and set it to `network-only` to skip the cache.
```js
import { gql, useQuery } from '@urql/vue';
export default {
setup() {
const result = useQuery({
query: gql`
{
todos {
id
title
}
}
`,
});
return {
data: result.data,
fetching: result.fetching,
error: result.error,
refresh() {
result.executeQuery({
requestPolicy: 'network-only',
});
},
};
},
};
```
Calling `refresh` in the above example will execute the query again forcefully, and will skip the
cache, since we're passing `requestPolicy: 'network-only'`.
Furthermore the `executeQuery` function can also be used to programmatically start a query even
when `pause` is set to `true`, which would usually stop all automatic queries. This can be used to
perform one-off actions, or to set up polling.
### Vue Suspense
In Vue 3 a [new feature was introduced](https://vuedose.tips/go-async-in-vue-3-with-suspense/) that
natively allows components to suspend while data is loading, which works universally on the server
and on the client, where a replacement loading template is rendered on a parent while data is
loading.
Any component's `setup()` function can be updated to instead be an `async setup()` function, in
other words, to return a `Promise` instead of directly returning its data. This means that we can
update any `setup()` function to make use of Suspense.
The `useQuery`'s returned result supports this, since it is a `PromiseLike`. We can update one of
our examples to have a suspending component by changing our usage of `useQuery`:
```jsx
{{ todo.title }}
```
As we can see, `await useQuery(...)` here suspends the component and what we render will not have to
handle the loading states of `useQuery` at all. Instead in Vue Suspense we'll have to wrap a parent
component in a "Suspense boundary." This boundary is what switches a parent to a loading state while
parts of its children are fetching data. The suspense promise is in essence "bubbling up" until it
finds a "Suspense boundary".
```
Loading...
```
As long as any parent component is wrapping our component which uses `async setup()` in this
boundary, we'll get Vue Suspense to work correctly and trigger this loading state. When a child
suspends this component will switch to using its `#fallback` template rather than its `#default`
template.
### Chaining calls in Vue Suspense
As shown [above](#vue-suspense), in Vue Suspense the `async setup()` lifecycle function can be used
to set up queries in advance, wait for them to have fetched some data, and then let the component
render as usual.
However, because the `async setup()` function can be used with `await`-ed promise calls, we may run
into situations where we're trying to call functions like `useQuery()` after we've already awaited
another promise and will be outside of the synchronous scope of the `setup()` lifecycle. This means
that the `useQuery` (and `useSubscription` & `useMutation`) functions won't have access to the
`Client` anymore that we'd have set up using `provideClient`.
To prevent this, we can create something called a "client handle" using the `useClientHandle`
function.
```js
import { gql, useClientHandle } from '@urql/vue';
export default {
async setup() {
const handle = useClientHandle();
await Promise.resolve(); // NOTE: This could be any await call
const result = await handle.useQuery({
query: gql`
{
todos {
id
title
}
}
`,
});
return { data: result.data };
},
};
```
As we can see, when we use `handle.useQuery()` we're able to still create query results although we've
interrupted the synchronous `setup()` lifecycle with a `Promise.resolve()` delay. This would also
allow us to create chained queries by using
[`computed`](https://v3.vuejs.org/guide/reactivity-computed-watchers.html#computed-values) to use an
output from a preceding result in a next `handle.useQuery()` call.
### Reading on
There are some more tricks we can use with `useQuery`. [Read more about its API in the API docs for
it.](../api/vue.md#usequery)
## Mutations
The `useMutation` function is similar to `useQuery` but is triggered manually and accepts
only a `DocumentNode` or `string` as an input.
### Sending a mutation
Let's again pick up an example with an imaginary GraphQL API for todo items, and dive into an
example! We'll set up a mutation that _updates_ a todo item's title.
```js
import { gql, useMutation } from '@urql/vue';
export default {
setup() {
const { executeMutation: updateTodo } = useMutation(gql`
mutation ($id: ID!, $title: String!) {
updateTodo(id: $id, title: $title) {
id
title
}
}
`);
return { updateTodo };
},
};
```
Similar to the `useQuery` output, `useMutation` returns a result object, which reflects the data of
an executed mutation. That means it'll contain the familiar `fetching`, `error`, and `data`
properties — it's identical since this is a common pattern of how `urql`
presents [operation results](../api/core.md#operationresult).
Unlike the `useQuery` hook, the `useMutation` hook doesn't execute automatically. At this point in
our example, no mutation will be performed. To execute our mutation we instead have to call the
`executeMutation` method on the result with some variables.
### Using the mutation result
When calling our `updateTodo` function we have two ways of getting to the result as it comes back
from our API. We can either use the result itself, since all properties related to the last
[operation result](../api/core.md#operationresult) are marked as [reactive
](https://v3.vuejs.org/guide/reactivity-fundamentals.html) — or we can use the promise that the
`executeMutation` method returns when it's called:
```js
import { gql, useMutation } from '@urql/vue';
export default {
setup() {
const updateTodoResult = useMutation(gql`
mutation ($id: ID!, $title: String!) {
updateTodo(id: $id, title: $title) {
id
title
}
}
`);
return {
updateTodo(id, title) {
const variables = { id, title: title || '' };
updateTodoResult.executeMutation(variables).then(result => {
// The result is almost identical to `updateTodoResult` with the exception
// of `result.fetching` not being set and its properties not being reactive.
// It is an OperationResult.
});
},
};
},
};
```
The reactive result that `useMutation` returns is useful when your UI has to display progress or
results on the mutation, and the returned promise is particularly useful when you're adding
side-effects that run after the mutation has completed.
### Handling mutation errors
It's worth noting that the promise we receive when calling the execute function will never
reject. Instead it will always return a promise that resolves to a result.
If you're checking for errors, you should use `result.error` instead, which will be set
to a `CombinedError` when any kind of errors occurred while executing your mutation.
[Read more about errors on our "Errors" page.](./errors.md)
```js
import { gql, useMutation } from '@urql/vue';
export default {
setup() {
const updateTodoResult = useMutation(gql`
mutation ($id: ID!, $title: String!) {
updateTodo(id: $id, title: $title) {
id
title
}
}
`);
return {
updateTodo(id, title) {
const variables = { id, title: title || '' };
updateTodoResult.executeMutation(variables).then(result => {
if (result.error) {
console.error('Oh no!', result.error);
}
});
},
};
},
};
```
There are some more tricks we can use with `useMutation`.
[Read more about its API in the API docs for it.](../api/vue.md#usemutation)
## Reading on
This concludes the introduction for using `urql` with Vue. The rest of the documentation
is mostly framework-agnostic and will apply to either `urql` in general or the `@urql/core` package,
which is the same between all framework bindings. Hence, next we may want to learn more about one of
the following to learn more about the internals:
- [How does the default "document cache" work?](./document-caching.md)
- [How are errors handled and represented?](./errors.md)
- [A quick overview of `urql`'s architecture and structure.](../architecture.md)
- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)
================================================
FILE: docs/comparison.md
================================================
---
title: Comparison
order: 8
---
# Comparison
> This comparison page aims to be detailed, unbiased, and up-to-date. If you see any information that
> may be inaccurate or could be improved otherwise, please feel free to suggest changes.
The most common question that you may encounter with GraphQL is what client to choose when you are
getting started. We aim to provide an unbiased and detailed comparison of several options on this
page, so that you can make an **informed decision**.
All options come with several drawbacks and advantages, and all of these clients have been around
for a while now. A little known fact is that `urql` in its current form and architecture has already
existed since February 2019, and its normalized cache has been around since September 2019.
Overall, we would recommend to make your decision based on whether your required features are
supported, which patterns you'll use (or restrictions thereof), and you may want to look into
whether all the parts and features you're interested in are well maintained.
## Comparison by Features
This section is a list of commonly used features of a GraphQL client and how it's either supported
or not by our listed alternatives. We're using Relay and Apollo to compare against as the other most
common choices of GraphQL clients.
All features are marked to indicate the following:
- ✅ Supported 1st-class and documented.
- 🔶 Supported and documented, but requires custom user-code to implement.
- 🟡 Supported, but as an unofficial 3rd-party library. (Provided it's commonly used)
- 🛑 Not officially supported or documented.
### Core Features
| | urql | Apollo | Relay |
| ------------------------------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| Extensible on a network level | ✅ Exchanges | ✅ Links | ✅ Network Layers |
| Extensible on a cache / control flow level | ✅ Exchanges | 🛑 | 🛑 |
| Base Bundle Size | **10kB** (11kB with bindings) | ~50kB (55kB with React hooks) | 45kB (66kB with bindings) |
| Devtools | ✅ | ✅ | ✅ |
| Subscriptions | 🔶 [Docs](./advanced/subscriptions.md) | 🔶 [Docs](https://www.apollographql.com/docs/react/data/subscriptions/#setting-up-the-transport) | 🔶 [Docs](https://relay.dev/docs/guided-tour/updating-data/graphql-subscriptions/#configuring-the-network-layer) |
| Client-side Rehydration | ✅ [Docs](./advanced/server-side-rendering.md) | ✅ [Docs](https://www.apollographql.com/docs/react/performance/server-side-rendering) | 🛑 |
| Polled Queries | 🔶 | ✅ | ✅ |
| Lazy Queries | ✅ | ✅ | ✅ |
| Stale while Revalidate / Cache and Network | ✅ | ✅ | ✅ |
| Focus Refetching | ✅ `@urql/exchange-refocus` | 🛑 | 🛑 |
| Stale Time Configuration | ✅ `@urql/exchange-request-policy` | ✅ | 🛑 |
| Persisted Queries | ✅ `@urql/exchange-persisted` | ✅ `apollo-link-persisted-queries` | 🔶 |
| Batched Queries | 🛑 | ✅ `apollo-link-batch-http` | 🟡 `react-relay-network-layer` |
| Live Queries | ✅ (via Incremental Delivery) | 🛑 | ✅ |
| Defer & Stream Directives | ✅ | ✅ / 🛑 (`@defer` is supported in >=3.7.0, `@stream` is not yet supported) | 🟡 (unreleased) |
| Switching to `GET` method | ✅ | ✅ | 🟡 `react-relay-network-layer` |
| File Uploads | ✅ | 🟡 `apollo-upload-client` | 🛑 |
| Retrying Failed Queries | ✅ `@urql/exchange-retry` | ✅ `apollo-link-retry` | ✅ `DefaultNetworkLayer` |
| Easy Authentication Flows | ✅ `@urql/exchange-auth` | 🛑 (no docs for refresh-based authentication) | 🟡 `react-relay-network-layer` |
| Automatic Refetch after Mutation | ✅ (with document cache) | 🛑 | ✅ |
Typically these are all additional addon features that you may expect from a GraphQL client, no
matter which framework you use it with. It's worth mentioning that all three clients support some
kind of extensibility API, which allows you to change when and how queries are sent to an API. These
are easy to use primitives particularly in Apollo, with links, and in `urql` with exchanges. The
major difference in `urql` is that all caching logic is abstracted in exchanges too, which makes
it easy to swap the caching logic or other behavior out (and hence makes `urql` slightly more
customizable.)
A lot of the added exchanges for persisted queries, file uploads, retrying, and other features are
implemented by the urql-team, while there are some cases where first-party support isn't provided
in Relay or Apollo. This doesn't mean that these features can't be used with these clients, but that
you'd have to lean on community libraries or maintaining/implementing them yourself.
One thing of note is our lack of support for batched queries in `urql`. We explicitly decided not to
support this in our [first-party
packages](https://github.com/urql-graphql/urql/issues/800#issuecomment-626342821) as the benefits
are not present anymore in most cases with HTTP/2 and established patterns by Relay that recommend
hoisting all necessary data requirements to a page-wide query.
### Framework Bindings
| | urql | Apollo | Relay |
| ------------------------------ | -------------- | ------------------- | ------------------ |
| React Bindings | ✅ | ✅ | ✅ |
| React Concurrent Hooks Support | ✅ | ✅ | ✅ |
| React Suspense | ✅ | 🛑 | ✅ |
| Next.js Integration | ✅ `next-urql` | 🟡 | 🔶 |
| Preact Support | ✅ | 🔶 | 🔶 |
| Svelte Bindings | ✅ | 🟡 `svelte-apollo` | 🟡 `svelte-relay` |
| Vue Bindings | ✅ | 🟡 `vue-apollo` | 🟡 `vue-relay` |
| Angular Bindings | 🛑 | 🟡 `apollo-angular` | 🟡 `relay-angular` |
| Initial Data on mount | ✅ | ✅ | ✅ |
### Caching and State
| | urql | Apollo | Relay |
| ------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------- | ---------------------------------------------- |
| Caching Strategy | Document Caching, Normalized Caching with `@urql/exchange-graphcache` | Normalized Caching | Normalized Caching (schema restrictions apply) |
| Added Bundle Size | +8kB (with Graphcache) | +0 (default) | +0 (default) |
| Automatic Garbage Collection | ✅ | 🔶 | ✅ |
| Local State Management | 🛑 | ✅ | ✅ |
| Pagination Support | 🔶 | 🔶 | ✅ |
| Optimistic Updates | ✅ | ✅ | ✅ |
| Local Updates | ✅ | ✅ | ✅ |
| Out-of-band Cache Updates | 🛑 (stays true to server data) | ✅ | ✅ |
| Local Resolvers and Redirects | ✅ | ✅ | 🛑 |
| Complex Resolvers (nested non-normalized return values) | ✅ | 🛑 | 🛑 |
| Commutativity Guarantees | ✅ | 🛑 | ✅ |
| Partial Results | ✅ | ✅ | 🛑 |
| Safe Partial Results (schema-based) | ✅ | 🔶 (experimental via `useFragment`) | 🛑 |
| Persistence Support | ✅ | ✅ `apollo-cache-persist` | 🟡 `@wora/relay-store` |
| Offline Support | ✅ | 🛑 | 🟡 `@wora/relay-offline` |
`urql` is the only of the three clients that doesn't pick [normalized
caching](./graphcache/normalized-caching.md) as its default caching strategy. Typically this is seen
by users as easier and quicker to get started with. All entries in this table for `urql` typically
refer to the optional `@urql/exchange-graphcache` package.
Once you need the same features that you'll find in Relay and Apollo, it's possible to migrate to
Graphcache. Graphcache is also slightly different from Apollo's cache and more opinionated as it
doesn't allow arbitrary cache updates to be made.
Local state management is not provided by choice, but could be implemented as an exchange. For more details, [see discussion here](https://github.com/urql-graphql/urql/issues/323#issuecomment-756226783).
`urql` is the only library that provides [Offline Support](./graphcache/offline.md) out of the
box as part of Graphcache's feature set. There are a number of options for Apollo and Relay including
writing your own logic for offline caching, which can be particularly successful in Relay, but for
`@urql/exchange-graphcache` we chose to include it as a feature since it also strengthened other
guarantees that the cache makes.
Relay does in fact have similar guarantees as [`urql`'s Commutativity
Guarantees](./graphcache/normalized-caching/#deterministic-cache-updates),
which are more evident when applying list updates out of order under more complex network
conditions.
## About Bundle Size
`urql` is known and often cited as a "lightweight GraphQL client," which is one of its advantages
but not its main goal. It manages to be this small by careful size management, just like other
libraries like Preact.
You may find that adding features like `@urql/exchange-persisted-fetch` and
`@urql/exchange-graphcache` only slightly increases your bundle size as we're aiming to reduce bloat,
but often this comparison is hard to make. When you start comparing bundle sizes of these three
GraphQL clients you should keep in mind that:
- Some dependencies may be external and the above sizes listed are total minified+gzipped sizes
- `@urql/core` imports from `wonka` for stream utilities and `@0no-co/graphql.web` for GraphQL query
language utilities
- Other GraphQL clients may import other exernal dependencies.
- All `urql` packages reuse parts of `@urql/core` and `wonka`, which means adding all their total
sizes up doesn't give you a correct result of their total expected bundle size.
================================================
FILE: docs/graphcache/README.md
================================================
---
title: Graphcache
order: 5
---
# Graphcache
In `urql`, caching is fully configurable via [exchanges](../architecture.md), and the default
`cacheExchange` in `urql` offers a ["Document Cache"](../basics/document-caching.md), which is
usually enough for sites that heavily rely on static content. However as an app grows more
complex it's likely that the data and state that `urql` manages, will also grow more complex and
introduce interdependencies between data.
To solve this problem most GraphQL clients resort to caching data in a normalized format, similar to
how [data is often structured in
Redux.](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape/)
In `urql`, normalized caching is an opt-in feature, which is provided by the
`@urql/exchange-graphcache` package, _Graphcache_ for short.
## Features
The following pages introduce different features in _Graphcache_, which together make it a compelling
alternative to the standard [document cache](../basics/document-caching.md) that `urql` uses by
default.
- 🔁 [**Fully reactive, normalized caching.**](./normalized-caching.md) _Graphcache_ stores data in
a normalized data structure. Query, mutation and subscription results may update one another if
they share data, and the app will rerender or refetch data accordingly. This often allows your app
to make fewer API requests, since data may already be in the cache.
- 💾 [**Custom cache resolvers**](./local-resolvers.md) Since all queries are fully resolved in the
cache before and after they're sent, you can add custom resolvers that enable you to format data,
implement pagination, or implement cache redirects.
- 💭 [**Subscription and Mutation updates**](./cache-updates.md) You can implement update functions
that tell _Graphcache_ how to update its data after a mutation has been executed, or whenever a
subscription sends a new event. This allows the cache to reactively update itself without queries
having to perform a refetch.
- 🏃 [**Optimistic mutation updates**](./cache-updates.md) When implemented, optimistic updates can
provide the data that the GraphQL API is expected to send back before the request succeeds, which
allows the app to instantly render an update while the GraphQL mutation is executed in the
background.
- 🧠 [**Opt-in schema awareness**](./schema-awareness.md) _Graphcache_ also optionally accepts your
entire schema, which allows it to resolve _partial data_ before making a request to the GraphQL
API, allowing an app to render everything that's cached before receiving all missing data. It also
allows _Graphcache_ to output more helpful warnings and to handle interfaces and enums correctly
without heuristics.
- 📡 [**Offline support**](./offline.md) _Graphcache_ can persist and rehydrate its entire state,
allowing an offline application to be built that is able to execute queries against the cache
although the device is offline.
- 🐛 [**Errors and warnings**](./errors.md). All potential errors are documented with information on
how you may be able to fix them.
## Installation and Setup
We can add _Graphcache_ by installing the `@urql/exchange-graphcache` package.
Using the package won't increase your bundle size by as much as platforms like
[Bundlephobia](https://bundlephobia.com/result?p=@urql/exchange-graphcache) may suggest, since it
shares the dependency on `wonka` and `@urql/core` with the framework bindings package, e.g. `urql`
or `@urql/preact`, that you're already using.
```sh
yarn add @urql/exchange-graphcache
# or
npm install --save @urql/exchange-graphcache
```
The package exports the `cacheExchange` which replaces the default `cacheExchange` in `@urql/core`.
This new `cacheExchange` must be instantiated using some options, which are used to customise
_Graphcache_ as introduced in the ["Features" section above.](#features) However, you can get started
without passing any options.
```js
import { Client, fetchExchange } from 'urql';
import { cacheExchange } from '@urql/exchange-graphcache';
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cacheExchange({}), fetchExchange],
});
```
This will automatically enable normalized caching, and you may find that in a lot of cases,
_Graphcache_ already does what you'd expect it to do without any additional configuration. We'll
explore how to customize and set up different parts of _Graphcache_ on the following pages.
[Read more about "Normalized Caching" on the next page.](./normalized-caching.md)
================================================
FILE: docs/graphcache/cache-updates.md
================================================
---
title: Cache Updates
order: 4
---
# Cache Updates
As we've learned [on the page on "Normalized
Caching"](./normalized-caching.md#normalizing-relational-data), when Graphcache receives an API
result it will traverse and store all its data to its cache in a normalized structure. Each entity
that is found in a result will be stored under the entity's key.
A query's result is represented as a graph, which can also be understood as a tree structure,
starting from the root `Query` entity, which then connects to other entities via links, which are
relations stored as keys, where each entity has records that store scalar values, which are the
tree's leafs. On the previous page, on ["Local Resolvers"](./local-resolvers.md), we've seen how
resolvers can be attached to fields to manually resolve other entities (or transform record fields).
Local Resolvers passively _compute_ results and change how Graphcache traverses and sees its locally
cached data, however, for **mutations** and **subscriptions** we cannot passively compute data.
When Graphcache receives a mutation or subscription result it still traverses it using the query
document as we've learned when reading about how Graphcache stores normalized data,
[quote](./normalized-caching.md/#storing-normalized-data):
> Any mutation or subscription can also be written to this data structure. Once Graphcache finds a
> keyable entity in their results it's written to its relational table, which may update other
> queries in our application.
This means that mutations and subscriptions still write and update entities in the cache. These
updates are then reflected on all active queries that our app uses. However, there are limitations to this.
While resolvers can be used to passively change data for queries, for mutations
and subscriptions we sometimes have to write **updaters** to update links and relations.
This is often necessary when a given mutation or subscription deliver a result that is more granular
than the cache needs to update all affected entities.
Previously, we've learned about cache updates [on the "Normalized Caching"
page](./normalized-caching.md#manual-cache-updates).
The `updates` option on `cacheExchange` accepts a map for `Mutation` or `Subscription` keys on which
we can add "updater functions" to react to mutation or subscription results. These `updates`
functions look similar to ["Local Resolvers"](./local-resolvers.md) that we've seen in the last
section and similar to [GraphQL.js' resolvers on the
server-side](https://www.graphql-tools.com/docs/resolvers/).
```js
cacheExchange({
updates: {
Mutation: {
mutationField: (result, args, cache, info) => {
// ...
},
},
Subscription: {
subscriptionField: (result, args, cache, info) => {
// ...
},
},
},
});
```
## Default mutation invalidation
Starting in [Graphcache v7](https://github.com/urql-graphql/urql/blob/main/exchanges/graphcache/CHANGELOG.md#700),
mutations without a configured `updates.Mutation.` updater have a fallback behavior:
- If the mutation returns an entity that can't be found in the cache yet, Graphcache treats this as
a "create" mutation.
- Graphcache then invalidates cached entities of the same `__typename` as the one returned from the mutation, which can trigger related queries to refetch.
As soon as you define an updater for that mutation field, this fallback behavior no longer runs and
your updater fully controls what happens after the mutation write.
An "updater" may be attached to a `Mutation` or `Subscription` field and accepts four positional
arguments, which are the same as [the resolvers' arguments](./local-resolvers.md):
- `result`: The full API result that's being written to the cache. Typically we'd want to
avoid coupling by only looking at the current field that the updater is attached to, but it's
worth noting that we can access any part of the result.
- `args`: The arguments that the field has been called with, which will be replaced with an empty
object if the field hasn't been called with any arguments.
- `cache`: The `cache` instance, which gives us access to methods allowing us to interact with the
local cache. Its full API can be found [in the API docs](../api/graphcache.md#cache). On this page
we use it frequently to read from and write to the cache.
- `info`: This argument shouldn't be used frequently, but it contains running information about the
traversal of the query document. It allows us to make resolvers reusable or to retrieve
information about the entire query. Its full API can be found [in the API
docs](../api/graphcache.md#info).
The cache updaters return value is disregarded (and typed as `void` in TypeScript), which makes any
method that they call on the `cache` instance a side effect, which may trigger additional cache
changes and updates all affected queries as we modify them.
## Why do we need cache updates?
When we’re designing a GraphQL schema well, we won’t need to write many cache updaters for
Graphcache.
For example, we may have a mutation to update a username on a `User`, which can trivially
update the cache without us writing an updater because it resolves the `User`.
```graphql
query User($id: ID!) {
user(id: $id) {
__typename # "User"
id
username
}
}
mutation UpdateUsername($id: ID!, $username: String!) {
updateUser(id: $id, username: $username) {
__typename # "User"
id
username
}
}
```
In the above example, `Query.user` returns a `User`, which is then updated by a mutation on
`Mutation.updateUser`. Since the mutation also queries the `User`, the updated username will
automatically be applied by Graphcache. If the mutation field didn’t return a `User`, then this
wouldn’t be possible, and while we can write an updater in Graphcache for it, we should consider
this poor schema design.
An updater instead becomes absolutely necessary when a mutation can’t reasonably return what has
changed or when we can’t manually define a selection set that’d be even able to select all fields
that may update. Some examples may include:
- `Mutation.deleteUser`, since we’ll need to invalidate an entity
- `Mutation.createUser`, since a list may now have to include a new entity
- `Mutation.createBook`, since a given entity, e.g. `User` may have a field `User.books` that now
needs to be updated.
In short, we may need to write a cache updater for any **relation** (i.e. link) that we can’t query
via our GraphQL mutation directly, since there’ll be changes to our data that Graphcache won’t be
able to see and store.
In a later section on this page, [we’ll learn about the `cache.link` method.](#writing-links-individually)
This method is used to update a field to point at a different entity. In other words, `cache.link`
is used to update a relation from one entity field to one or more other child entities.
This is the most common update we’ll need and it’s preferable to always try to use `cache.link`,
unless we need to update a scalar.
## Manually updating entities
If a mutation field's result isn't returning the full entity it updates then it becomes impossible
for Graphcache to update said entity automatically. For instance, we may have a mutation like the
following:
```graphql
mutation UpdateTodo($todoId: ID!, $date: String!) {
updateTodoDate(id: $todoId, date: $date)
}
```
In this hypothetical case instead of `Mutation.updateDate` resolving to the full `Todo` object type
it instead results in a scalar. This could be fixed by changing the `Mutation` in our API's schema
to instead return the full `Todo` entity, which would allow us to run the mutation as such, which
updates the `Todo` in our cache automatically:
```graphql
mutation UpdateTodo($todoId: ID!, $date: String!) {
updateTodoDate(id: $todoId, date: $date) {
...Todo_date
}
}
fragment Todo_date on Todo {
id
updatedAt
}
```
However, if this isn't possible we can instead write an updater that updates our `Todo` entity
manually by using the `cache.writeFragment` method:
```js
import { gql } from '@urql/core';
cacheExchange({
updates: {
Mutation: {
updateTodoDate(_result, args, cache, _info) {
const fragment = gql`
fragment _ on Todo {
id
updatedAt
}
`;
cache.writeFragment(fragment, { id: args.id, updatedAt: args.date });
},
},
},
});
```
The `cache.writeFragment` method is similar to the `cache.readFragment` method that we've seen [on
the "Local Resolvers" page before](./local-resolvers.md#reading-a-fragment). Instead of reading data
for a given fragment it instead writes data to the cache.
> **Note:** In the above example, we've used
> [the `gql` tag function](../api/core.md#gql) because `writeFragment` only accepts
> GraphQL `DocumentNode`s as inputs, and not strings.
### Cache Updates outside updaters
Cache updates are **not** possible outside `updates`'s functions. If we attempt to store the `cache`
in a variable and call its methods outside any `updates` functions (or functions, like `resolvers`)
then Graphcache will throw an error.
Methods like these cannot be called outside the `cacheExchange`'s `updates` functions, because
all updates are isolated to be _reactive_ to mutations and subscription events. In Graphcache,
out-of-band updates aren't permitted because the cache attempts to only represent the server's
state. This limitation keeps the data of the cache true to the server data we receive from API
results and makes its behaviour much more predictable.
If we still manage to call any of the cache's methods outside its callbacks in its configuration,
we will receive [a "(2) Invalid Cache Call" error](./errors.md#2-invalid-cache-call).
### Updaters on arbitrary types
Cache updates **may** be configured for arbitrary types and not just for `Mutation` or
`Subscription` fields. However, this can potentially be **dangerous** and is an easy trap
to fall into. It is allowed though because it allows for some nice tricks and workarounds.
Given an updater on an arbitrary type, e.g. `Todo.author`, we can chain updates onto this field
whenever it’s written. The updater can then be triggerd by Graphcache during _any_ operation;
mutations, queries, and subscriptions. When this update is triggered, it allows us to add more
arbitrary updates onto this field.
> **Note:** If you’re looking to use this because you’re nesting mutations onto other object types,
> e.g. `Mutation.author.updateName`, please consider changing your schema first before using this.
> Namespacing mutations is not recommended and changes the execution order to be concurrent rather
> than sequential when you use multiple nested mutation fields.
## Updating lists or links
Mutations that create new entities are pretty common, and it's not uncommon to attempt to update the
cache when a mutation result for these "creation" mutations come back, since this avoids an
additional roundtrip to our APIs.
While it's possible for these mutations to return any affected entities that carry the lists as
well, often these lists live on fields on or below the `Query` root type, which means that we'd be
sending a rather large API result. For large amounts of pages this is especially infeasible.
Instead, most schemas opt to instead just return the entity that's just been created:
```graphql
mutation NewTodo($text: String!) {
createTodo(id: $todoId, text: $text) {
id
text
}
}
```
If we have a corresponding field on `Query.todos` that contains all of our `Todo` entities then this
means that we'll need to create an updater that automatically adds the `Todo` to our list:
```js
cacheExchange({
updates: {
Mutation: {
createTodo(result, _args, cache, _info) {
const TodoList = gql`
{
todos {
id
}
}
`;
cache.updateQuery({ query: TodoList }, data => {
return {
...data,
todos: [...data.todos, result.createTodo],
};
});
},
},
},
});
```
Here we use the `cache.updateQuery` method, which is similar to the [`cache.readQuery` method](./local-resolvers.md#reading-a-query) that
we've seen on the "Local Resolvers" page before.
This method accepts a callback, which will give us the `data` of the query, as read from the locally
cached data, and we may return an updated version of this data. While we may want to instinctively
opt for immutably copying and modifying this data, we're actually allowed to mutate it directly,
since it's just a copy of the data that's been read by the cache.
This `data` may also be `null` if the cache doesn't actually have enough locally cached information
to fulfil the query. This is important because resolvers aren't actually applied to cache methods in
updaters. All resolvers are ignored, so it becomes impossible to accidentally commit transformed data
to our cache. We could safely add a resolver for `Todo.createdAt` and wouldn't have to worry about
an updater accidentally writing it to the cache's internal data structure.
### Writing links individually
As long as we're only updating links (as in 'relations') then we may also use the [`cache.link`
method](../api/graphcache.md#link). This method is the "write equivalent" of [the `cache.resolve`
method, as seen on the "Local Resolvers" page before.](./local-resolvers.md#resolving-other-fields)
We can use this method to update any relation in our cache, so the example above could also be
rewritten to use `cache.link` and `cache.resolve` rather than `cache.updateQuery`.
```js
cacheExchange({
updates: {
Mutation: {
createTodo(result, _args, cache, _info) {
const todos = cache.resolve('Query', 'todos');
if (Array.isArray(todos)) {
cache.link('Query', 'todos', [...todos, result.createTodo]);
}
},
},
},
});
```
This method can be combined with more than just `cache.resolve`, for instance, it's a good fit with
`cache.inspectFields`. However, when you're writing records (as in 'scalar' values)
`cache.writeFragment` and `cache.updateQuery` are still the only methods that you can use.
But since this kind of data is often written automatically by the normalized cache, often updating a
link is the only modification we may want to make.
## Updating many unknown links
In the previous section we've seen how to update data, like a list, when a mutation result enters
the cache. However, we've used a rather simple example when we've looked at a single list on a known
field.
In many schemas pagination is quite common, and when we for instance delete a todo then knowing the
lists to update becomes unknowable. We cannot know ahead of time how many pages (and its variables)
we've already accessed. This knowledge in fact _shouldn't_ be available to Graphcache. Querying the
`Client` is an entirely separate concern that's often colocated with some part of our
UI code.
```graphql
mutation RemoveTodo($id: ID!) {
removeTodo(id: $id)
}
```
Suppose we have the above mutation, which deletes a `Todo` entity by its ID. Our app may query a list
of these items over many pages with separate queries being sent to our API, which makes it hard to
know the fields that should be checked:
```graphql
query PaginatedTodos($skip: Int) {
todos(skip: $skip) {
id
text
}
}
```
Instead, we can **introspect an entity's fields** to find the fields we may want to update
dynamically. This is possible thanks to [the `cache.inspectFields`
method](../api/graphcache.md#inspectfields). This method accepts a key, or a keyable entity like the
`cache.keyOfEntity` method that [we've seen on the "Local Resolvers"
page](./local-resolvers.md#resolving-by-keys) or the `cache.resolve` method's first argument.
```js
cacheExchange({
updates: {
Mutation: {
removeTodo(_result, args, cache, _info) {
const TodoList = gql`
query (skip: $skip) {
todos(skip: $skip) { id }
}
`;
const fields = cache
.inspectFields('Query')
.filter(field => field.fieldName === 'todos')
.forEach(field => {
cache.updateQuery(
{
query: TodoList,
variables: { skip: field.arguments.skip },
},
data => {
data.todos = data.todos.filter(todo => todo.id !== args.id);
return data;
}
);
});
},
},
},
});
```
To implement an updater for our example's `removeTodo` mutation field we may use the
`cache.inspectFields('Query')` method to retrieve a list of all fields on the `Query` root entity.
This list will contain all known fields on the `"Query"` entity. Each field is described as an
object with three properties:
- `fieldName`: The field's name; in this case we're filtering for all `todos` listing fields.
- `arguments`: The arguments for the given field, since each field that accepts arguments can be
accessed multiple times with different arguments. In this example we're looking at
`arguments.skip` to find all unique pages.
- `fieldKey`: This is the field's key, which can come in useful to retrieve a field using
`cache.resolve(entityKey, fieldKey)` to prevent the arguments from having to be stringified
repeatedly.
To summarise, we filter the list of fields in our example down to only the `todos` fields and
iterate over each of our `arguments` for the `todos` field to filter all lists to remove the `Todo`
from them.
### Inspecting arbitrary entities
We're not required to only inspecting fields on the `Query` root entity. Instead, we can inspect
fields on any entity by passing a different partial, keyable entity or key to `cache.inspectFields`.
For instance, if we had a `Todo` entity and wanted to get all of its known fields then we could pass
in a partial `Todo` entity just as well:
```js
cache.inspectFields({
__typename: 'Todo',
id: args.id,
});
```
## Invalidating Entities
Admittedly, it's sometimes almost impossible to write updaters for all mutations. It's often even
hard to predict what our APIs may do when they receive a mutation. An update of an entity may change
the sorting of a list, or remove an item from a list in a way we can't predict, since we don't have
access to a full database to run the API locally.
In cases like these it may be advisable to trigger a refetch instead and let the cache update itself
by sending queries that have invalidated data associated to them to our API again. This process is
called **invalidation** since it removes data from Graphcache's locally cached data.
We may use the cache's [`cache.invalidate` method](../api/graphcache.md#invalidate) to either
invalidate entire entities or individual fields. It has the same signature as [the `cache.resolve`
method](../api/graphcache.md#resolve), which we've already seen [on the "Local Resolvers" page as
well](./local-resolvers.md#resolving-other-fields). We can simplify the previous update we've written
with a call to `cache.invalidate`:
```js
cacheExchange({
updates: {
Mutation: {
removeTodo(_result, args, cache, _info) {
cache.invalidate({
__typename: 'Todo',
id: args.id,
});
},
},
},
});
```
Like any other cache update, this will cause all queries that use this `Todo` entity to be updated
against the cache. Since we've invalidated the `Todo` item they're using these queries will be
refetched and sent to our API.
If we're using ["Schema Awareness"](./schema-awareness.md) then these queries' results may actually
be temporarily updated with a partial result, but in general we should observe that queries with
data that has been invalidated will be refetched as some of their data isn't cached anymore.
### Invalidating individual fields
We may also want to only invalidate individual fields, since maybe not all queries have to be
immediately updated. We can pass a field (and optional arguments) to the `cache.invalidate` method
as well to only invalidate a single field.
For instance, we can use this to invalidate our lists instead of invalidating the entity itself.
This can be useful if we know that modifying an entity will cause our list to be sorted differently,
for instance.
```js
cacheExchange({
updates: {
Mutation: {
updateTodo(_result, args, cache, _info) {
const key = 'Query';
const fields = cache
.inspectFields(key)
.filter(field => field.fieldName === 'todos')
.forEach(field => {
cache.invalidate(key, field.fieldKey);
// or alternatively:
cache.invalidate(key, field.fieldName, field.arguments);
});
},
},
},
});
```
In this example we've attached an updater to a `Mutation.updateTodo` field. We react to this
mutation by enumerating all `todos` listing fields using `cache.inspectFields` and targetedly
invalidate only these fields, which causes all queries using these listing fields to be refetched.
### Invalidating a type
We can also invalidate all the entities of a given type, this could be handy in the case of a
list update or when you aren't sure what entity is affected.
This can be done by only passing the relevant `__typename` to the `invalidate` function.
```js
cacheExchange({
updates: {
Mutation: {
deleteTodo(_result, args, cache, _info) {
cache.invalidate('Todo');
},
},
},
});
```
## Optimistic updates
If we know what result a mutation may return, why wait for the GraphQL API to fulfill our mutations?
In addition to the `updates` configuration, we can also pass an `optimistic` option to the
`cacheExchange`. This option is a factory function that allows us to create a "virtual" result for a
mutation. This temporary result can be applied immediately to the cache to give our users the
illusion that mutations were executed immediately, which is a great method to reduce waiting time
and to make our apps feel snappier.
This technique is often used with one-off mutations that are assumed to succeed, like starring a
repository, or liking a tweet. In such cases it's often desirable to make the interaction feel
as instant as possible.
The `optimistic` configuration is similar to our `resolvers` or `updates` configuration, except that
it only receives a single map for mutation fields. We can attach optimistic functions to any
mutation field to make it generate an optimistic that is applied to the cache while the `Client`
waits for a response from our API. An "optimistic" function accepts three positional arguments,
which are the same as the resolvers' or updaters' arguments, except for the first one:
The `optimistic` functions receive the same arguments as `updates` functions, except for `parent`,
since we don't have any server data to work with:
- `args`: The arguments that the field has been called with, which will be replaced with an empty
object if the field hasn't been called with any arguments.
- `cache`: The `cache` instance, which gives us access to methods allowing us to interact with the
local cache. Its full API can be found [in the API docs](../api/graphcache.md#cache). On this page
we use it frequently to read from and write to the cache.
- `info`: This argument shouldn't be used frequently, but it contains running information about the
traversal of the query document. It allows us to make resolvers reusable or to retrieve
information about the entire query. Its full API can be found [in the API
docs](../api/graphcache.md#info).
The usual `parent` argument isn't present since optimistic functions don't have any server data to
handle or deal with and instead create this data. When a mutation is run that contains one or more
optimistic mutation fields, Graphcache picks these up and generates immediate changes, which it
applies to the cache. The `resolvers` functions also trigger as if the results were real server
results.
This modification is temporary. Once a result from the API comes back it's reverted, which leaves us
in a state where the cache can apply the "real" result to the cache.
> Note: While optimistic mutations are waiting for results from the API all queries that may alter
> our optimistic data are paused (or rather queued up) and all optimistic mutations will be reverted
> at the same time. This means that optimistic results can stack but will never accidentally be
> confused with "real" data in your configuration.
In the following example we assume that we'd like to implement an optimistic result for a
`favoriteTodo` mutation, like such:
```graphql
mutation FavoriteTodo(id: $id) {
favoriteTodo(id: $id) {
id
favorite
updatedAt
}
}
```
The mutation is rather simple and all we have to do is create a function
that imitates the result that the API is assumed to send back:
```js
const cache = cacheExchange({
optimistic: {
favoriteTodo(args, cache, info) {
return {
__typename: 'Todo',
id: args.id,
favorite: true,
};
},
},
});
```
This optimistic mutation will be applied to the cache. If any `updates` configuration exists for
`Mutation.favoriteTodo` then it will be executed using the optimistic result.
Once the mutation result comes back from our API this temporary change will be rolled back and
discarded.
In the above example optimistic mutation function we also see that `updatedAt` is not present in our
optimistic return value. That’s because we don’t always have to (or can) match our mutations’
selection sets perfectly. Instead, Graphcache will skip over fields and use cached fields for any we
leave out. This can even work on nested entities and fields.
However, leaving out fields can sometimes cause the optimistic update to not apply when we
accidentally cause any query that needs to update accordingly to only be partially cached. In other
words, if our optimistic updates cause a cache miss, we won’t see them being applied.
Sometimes we may need to apply optimistic updates to fields that accept arguments. For instance, our
`favorite` field may have a date cut-off:
```graphql
mutation FavoriteTodo(id: $id) {
favoriteTodo(id: $id) {
id
favorite(since: ONE_MONTH_AGO)
updatedAt
}
}
```
To solve this, we can return a method on the optimistic result our `optimistic` update function
returns:
```js
const cache = cacheExchange({
optimistic: {
favoriteTodo(args, cache, info) {
return {
__typename: 'Todo',
id: args.id,
favorite(_args, cache, info) {
return true;
},
},
},
},
});
```
The function signature and arguments it receives is identical to the toplevel optimistic function
you define, and is basically like a nested optimistic function.
### Variables for Optimistic Updates
Sometimes it's not possible for us to retrieve all data that an optimistic update requires to create
a "fake result" from the cache or from all existing variables.
This is why Graphcache allows for a small escape hatch for these scenarios, which allows us to access
additional variables, which we may want to pass from our UI code to the mutation. For instance, given
a mutation like the following we may add more variables than the mutation specifies:
```graphql
mutation UpdateTodo($id: ID!, $text: ID!) {
updateTodo(id: $id, text: $text) {
id
text
}
}
```
In the above mutation we've only defined an `$id` and `$text` variable. Graphcache typically filters
variables using our query document definitions, which means that our API will never receive any
variables other than the ones we've defined.
However, we're able to pass additional variables to our mutation, e.g. `{ extra }`, and since
`$extra` isn't defined it will be filtered once the mutation is sent to the API. An optimistic
mutation however will still be able to access this variable, like so:
```js
cacheExchange({
updates: {
Mutation: {
updateTodo(_result, _args, _cache, info) {
const extraVariable = info.variables.extra;
},
},
},
});
```
### Reading on
[On the next page we'll learn about "Schema Awareness".](./schema-awareness.md)
================================================
FILE: docs/graphcache/errors.md
================================================
---
title: Errors
order: 8
---
# Help!
**This document lists out all errors and warnings in `@urql/exchange-graphcache`.**
Any unexpected behaviour or condition will be marked by an error or warning
in development. This will output as a helpful little message. Sometimes, however, this
message may not actually tell you about everything that's going on.
This is a supporting document that explains every error and attempts to give more
information on how you may be able to fix some issues or avoid these errors/warnings.
## (1) Invalid GraphQL document
> Invalid GraphQL document: All GraphQL documents must contain an OperationDefinition
> node for a query, subscription or mutation.
There are multiple places where you're passing in GraphQL documents, either through
methods on `Cache` (e.g. `cache.updateQuery`) or via `urql` using the `Client` or
hooks like `useQuery`.
Your queries must always contain a main operation, one of: query, mutation, or
subscription. This error occurs when this is missing, because the `DocumentNode`
is maybe empty or only contains fragments.
## (2) Invalid Cache call
> Invalid Cache call: The cache may only be accessed or mutated during
> operations like write or query, or as part of its resolvers, updaters,
> or optimistic configs.
If you're somehow accessing the `Cache` (an instance of `Store`) outside any
of the usual operations then this error will be thrown.
Please make sure that you're only calling methods on the `cache` as part of
configs that you pass to your `cacheExchange`. Outside these functions the cache
must not be changed.
However when you're not using the `cacheExchange` and are trying to use the
`Store` on its own, then you may run into issues where its global state wasn't
initialised correctly.
This is a safe-guard to prevent any asynchronous work to take place, or to
avoid mutating the cache outside any normal operation.
## (3) Invalid Object type
> Invalid Object type: The type `???` is not an object in the defined schema,
> but the GraphQL document is traversing it.
When you're passing an introspected schema to the cache exchange, it is
able to check whether all your queries are valid.
This error occurs when an unknown type is found as part of a query or
fragment.
Check whether your schema is up-to-date or whether you're using an invalid
typename somewhere, maybe due to a typo.
## (4) Invalid field
> Invalid field: The field `???` does not exist on `???`,
> but the GraphQL document expects it to exist.
> Traversal will continue, however this may lead to undefined behavior!
Similarly to the previous warning, when you're passing an introspected
schema to the cache exchange, it is able to check whether all your queries are valid.
This warning occurs when an unknown field is found on a selection set as part
of a query or fragment.
Check whether your schema is up-to-date or whether you're using an invalid
field somewhere, maybe due to a typo.
As the warning states, this won't lead any operation to abort, or an error
to be thrown!
## (5) Invalid Abstract type
> Invalid Abstract type: The type `???` is not an Interface or Union type
> in the defined schema, but a fragment in the GraphQL document is using it
> as a type condition.
When you're passing an introspected schema to the cache exchange, it becomes
able to deterministically check whether an entity in the cache matches a fragment's
type condition.
This applies to full fragments (`fragment _ on Interface`) or inline fragments
(`... on Interface`), that apply to interfaces instead of to a concrete object typename.
Check whether your schema is up-to-date or whether you're using an invalid
field somewhere, maybe due to a typo.
## (6) readFragment(...) was called with an empty fragment
> readFragment(...) was called with an empty fragment.
> You have to call it with at least one fragment in your GraphQL document.
You probably have called `cache.readFragment` with a GraphQL
document that doesn't contain a main fragment.
This error occurs when no main fragment can be found, because the `DocumentNode`
is maybe empty or does not contain fragments.
When you're calling a fragment method, please ensure that you're only passing fragments
in your GraphQL document. The first fragment will be used to start writing data.
This also occurs when you pass in a `fragmentName` but a fragment with the given name
can't be found in the `DocumentNode`.
## (7) Can't generate a key for readFragment(...)
> Can't generate a key for readFragment(...).
> You have to pass an `id` or `_id` field or create a custom `keys` config for `???`.
You probably have called `cache.readFragment` with data that the cache can't generate a
key for.
This may either happen because you're missing the `id` or `_id` field or some other
fields for your custom `keys` config.
Please make sure that you include enough properties on your data so that `readFragment`
can generate a key.
## (8) Invalid resolver data
> Invalid resolver value: The resolver at `???` returned an invalid typename that
> could not be reconciled with the cache.
This error may occur when you provide a cache resolver for a field using `resolvers` config.
The value that you returns needs to contain a `__typename` field and this field must
match the `__typename` field that exists in the cache, if any. This is because it's not
possible to return a different type for a single field.
Please check your schema for the type that your resolver has to return, then add a
`__typename` field to your returned resolver value that matches this type.
## (9) Invalid resolver value
> Invalid resolver value: The field at `???` is a scalar (number, boolean, etc),
> but the GraphQL query expects a selection set for this field.
The GraphQL query that has been walked contains a selection set at the place where
your resolver is located.
This means that a full entity object needs to be returned, but instead the cache
received a number, boolean, or another scalar from your resolver.
Please check that your resolvers return scalars where there's no selection set,
and entities where there is one.
## (10) writeOptimistic(...) was called with an operation that isn't a mutation
> writeOptimistic(...) was called with an operation that is not a mutation.
> This case is unsupported and should never occur.
This should never happen, please open an issue if it does. This occurs when `writeOptimistic`
attempts to write an optimistic result for a query or subscription, instead of a mutation.
## (11) writeFragment(...) was called with an empty fragment
> writeFragment(...) was called with an empty fragment.
> You have to call it with at least one fragment in your GraphQL document.
You probably have called `cache.writeFragment` with a GraphQL
document that doesn't contain a main fragment.
This error occurs when no main fragment can be found, because the `DocumentNode`
is maybe empty or does not contain fragments.
When you're calling a fragment method, please ensure that you're only passing fragments
in your GraphQL document. The first fragment will be used to start writing data.
This also occurs when you pass in a `fragmentName` but a fragment with the given name
can't be found in the `DocumentNode`.
## (12) Can't generate a key for writeFragment(...) or link(...)
> Can't generate a key for writeFragment(...) [or link(...) data.
> You have to pass an `id` or `_id` field or create a custom `keys` config for `???`.
You probably have called `cache.writeFragment` or `cache.link` with data that the cache
can't generate a key for.
This may either happen because you're missing the `id` or `_id` field or some other
fields for your custom `keys` config.
Please make sure that you include enough properties on your data so that `writeFragment`
or `cache.link` can generate a key. On `cache.link` the entities must either be
an existing entity key, or a keyable entity.
## (13) Invalid undefined
> Invalid undefined: The field at `???` is `undefined`, but the GraphQL query expects a
> scalar (number, boolean, etc) / selection set for this field.
As data is written to the cache, this warning is issued when `undefined` is encountered.
GraphQL results should never contain an `undefined` value, so this warning will let you
know the part of your result that did contain `undefined`.
## (14) Couldn't find \_\_typename when writing.
> Couldn't find `__typename` when writing.
> If you're writing to the cache manually have to pass a `__typename` property on each entity in your data.
You probably have called `cache.writeFragment` or `cache.updateQuery` with data that is missing a
`__typename` field for an entity where your document contains a selection set. The cache won't be
able to generate a key for entities that are missing the `__typename` field.
Please make sure that you include enough properties on your data so that `write` can generate a key.
## (15) Invalid key
> Invalid key: The GraphQL query at the field at `???` has a selection set,
> but no key could be generated for the data at this field.
> You have to request `id` or `_id` fields for all selection sets or create a
> custom `keys` config for `???`.
> Entities without keys will be embedded directly on the parent entity.
> If this is intentional, create a `keys` config for `???` that always returns null.
This error occurs when the cache can't generate a key for an entity. The key
would then effectively be `null`, and the entity won't be cached by a key.
Conceptually this means that an entity won't be normalized but will indeed
be cached by the parent's key and field, which is displayed in the first
part of the warning.
This may mean that you forgot to include an `id` or `_id` field.
But if your entity at that place doesn't have any `id` fields, then you may
have to create a custom `keys` config. This `keys` function either needs to
return a unique ID for your entity, or it needs to explicitly return `null` to silence
this warning.
## (16) Heuristic Fragment Matching
> Heuristic Fragment Matching: A fragment is trying to match against the `???` type,
> but the type condition is `???`. Since GraphQL allows for interfaces `???` may be
> an interface.
> A schema needs to be defined for this match to be deterministic, otherwise
> the fragment will be matched heuristically!
This warning is issued on fragment matching. Fragment matching is the process
of matching a fragment against a piece of data in the cache and that data's `__typename`
field.
When the `__typename` field doesn't match the fragment's type, then we may be
dealing with an interface and/or enum. In such a case the fragment may _still match_
if it's referring to an interface (`... on Interface`). Graphcache is supposed to be
usable without much config, so what it does in this case is apply a heuristic match.
In a heuristic fragment match we check whether all fields on the fragment are present
in the cache, which is then treated as a fragment match.
When you pass an introspected schema to the cache, this warning will never be displayed
as the cache can then do deterministic fragment matching using schema information.
## (17) Invalid type
> Invalid type: The type `???` is used with @populate but does not exist.
When you're using the populate exchange with an introspected schema and add the
`@populate` directive to fields it first checks whether the type is valid and
exists on the schema.
If the field does not have enough type information because it doesn't exist
on the schema or does not match expectations then this warning is logged.
Check whether your schema is up-to-date or whether you're using an invalid
field somewhere, maybe due to a typo.
## (18) Invalid TypeInfo state
> Invalid TypeInfo state: Found no flat schema type when one was expected.
When you're using the populate exchange with an introspected schema, it will
start collecting used fragments and selection sets on all of your queries.
This error may occur if it hits unexpected types or inexistent types when doing so.
Check whether your schema is up-to-date or whether you're using an invalid
field somewhere, maybe due to a typo.
Please open an issue if it happens on a query that you expect to be supported
by the `populateExchange`.
## (19) Can't generate a key for invalidate(...)
> Can't generate a key for invalidate(...).
> You need to pass in a valid key (**typename:id) or an object with the "**typename" property and an "id" or "\_id" property.
You probably have called `cache.invalidate` with data that the cache can't generate a key for.
This may either happen because you're missing the `__typename` and `id` or `_id` field or if the last two
aren't applicable to this entity a custom `keys` entry.
## (20) Invalid Object type
> Invalid Object type: The type `???` is not an object in the defined schema,
> but the `keys` option is referencing it.
When you're passing an introspected schema to the cache exchange, it is
able to check whether your `opts.keys` is valid.
This error occurs when an unknown type is found in `opts.keys`.
Check whether your schema is up-to-date, or whether you're using an invalid
typename in `opts.keys`, maybe due to a typo.
## (21) Invalid updates type
> Invalid updates field: The type `???` is not an object in the defined schema,
> but the `updates` config is referencing it.
When you're passing an introspected schema to the cache exchange, it is
able to check whether your `opts.updates` config is valid.
This error occurs when an unknown type is found in the `opts.updates` config.
Check whether your schema is up-to-date, or whether you've got a typo in `opts.updates`.
## (22) Invalid updates field
> Invalid updates field: `???` on `???` is not in the defined schema,
> but the `updates` config is referencing it.
When you're passing an introspected schema to the cache exchange, it is
able to check whether your `opts.updates` config is valid.
This error occurs when an unknown field is found in `opts.updates[typename]`.
Check whether your schema is up-to-date, or whether you're using an invalid
field name in `opts.updates`, maybe due to a typo.
## (23) Invalid resolver
> Invalid resolver: `???` is not in the defined schema, but the `resolvers`
> option is referencing it.
When you're passing an introspected schema to the cache exchange, it is
able to check whether your `opts.resolvers` is valid.
This error occurs when an unknown query, type or field is found in `opts.resolvers`.
Check whether your schema is up-to-date, or whether you've got a typo in `opts.resolvers`.
## (24) Invalid optimistic mutation
> Invalid optimistic mutation field: `???` is not a mutation field in the defined schema,
> but the `optimistic` option is referencing it.
When you're passing an introspected schema to the cache exchange, it is
able to check whether your `opts.optimistic` is valid.
This error occurs when a field in `opts.optimistic` is not in the schema's `Mutation` fields.
Check whether your schema is up-to-date, or whether you've got a typo in `Mutation` or `opts.optimistic`.
## (25) Invalid root traversal
> Invalid root traversal: A selection was being read on `???` which is an uncached root type.
> The `Mutation` and `Subscription` types are special Operation Root Types and cannot be read back
> from the cache.
In GraphQL every schema has three [Operation Root
Types](https://spec.graphql.org/June2018/#sec-Root-Operation-Types). The `Query` type is the only
one that is cached in Graphcache's normalized cache, since it's the root of all normalized cache
data, i.e. all data is linked and connects back to the `Query` type.
The `Subscription` and `Mutation` types are special and uncached; they may link to entities that
will be updated in the normalized cache data, but are themselves not cached, since they're never
directly queried.
When your schema treats `Mutation` or `Subscription` like regular entity types you may get this
warning. This may happen because you've used the default reserved names `Mutation` or `Subscription`
for entities rather than as special Operation Root Types, and haven't specified this in the schema.
Hence this issue can often be fixed by either enabling
[Schema Awareness](./schema-awareness.md) or by
adding a `schema` definition to your GraphQL Schema like so:
```graphql
schema {
query: Query
mutation: YourMutation
subscription: YourSubscription
}
```
Where `YourMutation` and `YourSubscription` are your custom Operation Root Types, instead of relying
on the default names `"Mutation"` and `"Subscription"`.
## (26) Invalid abstract resolver
> Invalid resolver: `???` does not map to a concrete type in the schema,
> but the resolvers option is referencing it. Implement the resolver for the types that `??` instead.
When you're passing an introspected schema to the cache exchange, it is
able to check whether your `opts.resolvers` is valid.
This error occurs when you are using an `interface` or `union` rather than an
implemented type for these.
Check the type mentioned and change it to one of the specific types.
## (27) Invalid Cache write
> Invalid Cache write: You may not write to the cache during cache reads.
> Accesses to `cache.writeFragment`, `cache.updateQuery`, and `cache.link` may
> not be made inside `resolvers` for instance.
If you're using the `Cache` inside your `cacheExchange` config you receive it
either inside callbacks that are called when the cache is queried (e.g.
`resolvers`) or when data is written to the cache (e.g. `updates`). You may not
write to the cache when it's being queried.
Please make sure that you're not calling `cache.updateQuery`,
`cache.writeFragment`, or `cache.link` inside `resolvers`.
## (28) Resolver and directive match the same field
When you have a resolver defined on a field you shouln't be combining it with a directive as the directive
will apply and the resolver will be void.
================================================
FILE: docs/graphcache/local-directives.md
================================================
---
title: Local Directives
order: 3
---
# Local Directives
Previously, we've learned about local resolvers [on the "Normalized Caching"
page](./normalized-caching.md#manually-resolving-entities) and [the "Local Resolvers" page](./local-resolvers.md).
Resolvers allow us to change the data that Graphcache resolvers for a given field on a given type.
This, in turn, allows us to change which links and data are returned in a query’s result, which
otherwise may not be cached or be returned in a different shape.
Resolvers are useful to globally change how a field behaves, for instance, to tell Graphcache that
a `Query.item(id: $id)` field returns an item of type `Item` with the `id` field, or to transform
a value before it’s used in the UI.
However, resolvers are limited to changing the behaviour globally, not to change a field’s behaviour
per query. This is why **local directives** exist.
## Adding client-only directives
Any directive in our GraphQL documents that’s prefixed with an underscore character (`_`) will be
filtered by `@urql/core`. This means that our GraphQL API never sees it and it becomes
a “client-only directive”.
No matter whether we prefix a directive or not however, we can define local resolvers for directives
in Graphcache’s configuration and make conditional local resolvers.
```js
cacheExchange({
directives: {
pagination(directiveArgs) {
// This resolver is called for @_pagination directives
return (parent, args, cache, info) => {
return null;
};
},
},
});
```
Once we define a directive on the `directives` configuration object, we can reference it in our
GraphQL queries.
As per the above example, if we now reference `@_pagination` in a query, the resolver that’s
returned in the configuration will be applied to the field, just like a local resolver.
We can also reference the directive using `@pagination`, however, this will mean that it’s
also sent to the API, so this usually isn’t what we want.
## Client-controlled Nullability
Graphcache comes with two directives built-in by default. The `optional` and `required` directives.
These directives can be used as an alternative to [the Schema Awareness
feature’s](./schema-awareness.md) ability to generate partial results.
If we were to write a query that contains `@_optional` on a field, then the field is always allowed to be
nullable. In case it’s not cached, Graphcache will be able to replace it with a `null`
value.
Similarly, if we annotate a field with `@_required`, the value is not optional and, even if the
cache knows the value is set to `null`, it will become required and Graphcache will either cascade
to the next higher parent field annotated with `@_optional`, or will mark a query as a cache-miss.
## Pagination
Previously, in [the “Local Resolvers” page’s Pagination section](./local-resolvers.md#pagination) we
defined a local resolver to add infinite pagination to a given type’s field.
If we add the `simplePagination` or `relayPagination` helpers as directives instead, we can still
use our schema’s pagination field as normal, and instead, only use infinite pagination as required.
```js
import { simplePagination } from '@urql/exchange-graphcache/extras';
import { relayPagination } from '@urql/exchange-graphcache/extras';
cacheExchange({
directives: {
simplePagination: options => simplePagination({ ...options }),
relayPagination: options => relayPagination({ ...options }),
},
});
```
Defining directives for our resolver factory functions means that we can now use them selectively.
```graphql
{
todos(first: 10) @_relayPagination(mergeMode: "outwards") {
id
text
}
}
```
### Reading on
[On the next page we'll learn about "Cache Updates".](./cache-updates.md)
================================================
FILE: docs/graphcache/local-resolvers.md
================================================
---
title: Local Resolvers
order: 2
---
# Local Resolvers
Previously, we've learned about local resolvers [on the "Normalized Caching"
page](./normalized-caching.md#manually-resolving-entities). They allow us to change the data that
Graphcache reads as it queries against its local cache, return links that would otherwise not be
cached, or even transform scalar records on the fly.
The `resolvers` option on `cacheExchange` accepts a map of types with a nested map of fields, which
means that we can add local resolvers to any field of any type. For example:
```js
cacheExchange({
resolvers: {
Todo: {
updatedAt: parent => new Date(parent.updatedAt),
},
},
});
```
In the above example, what Graphcache does when it encounters the `updatedAt` field on `Todo` types.
Similarly to how Graphcache knows [how to generate
keys](./normalized-caching.md#custom-keys-and-non-keyable-entities) and looks up our custom `keys`
configuration functions per `__typename`, it also uses our `resolvers` configuration on each field
it queries from its locally cached data.
A local resolver function in Graphcache has a similar signature to [GraphQL.js' resolvers on the
server-side](https://www.graphql-tools.com/docs/resolvers/), so their shape should look familiar to
us.
```js
{
TypeName: {
fieldName: (parent, args, cache, info) => {
return null; // new value
},
},
}
```
A resolver may be attached to any type's field and accepts four positional arguments:
- `parent`: The object on which the field will be added to, which contains the data as it's being
queried. It will contain the current field's raw value if it's a scalar, which allows us to
manipulate scalar values, like `parent.updatedAt` in the previous example.
- `args`: The arguments that the field is being called with, which will be replaced with an empty
object if the field hasn't been called with any arguments. For example, if the field is queried as
`name(capitalize: true)` then `args` would be `{ capitalize: true }`.
- `cache`: Unlike in GraphQL.js this will not be the context, but a `cache` instance, which gives us
access to methods allowing us to interact with the local cache. Its full API can be found [in the
API docs](../api/graphcache.md#cache).
- `info`: This argument shouldn't be used frequently, but it contains running information about the
traversal of the query document. It allows us to make resolvers reusable or to retrieve
information about the entire query. Its full API can be found [in the API
docs](../api/graphcache.md#info).
The local resolvers may return any value that fits the query document's shape, however we must
ensure that what we return matches the types of our schema. It, for instance, isn't possible to turn a
record field into a link, i.e. replace a scalar with an entity. Instead, local resolvers are useful
to transform records, like dates in our previous example, or to imitate server-side logic to allow
Graphcache to retrieve more data from its cache without sending a query to our API.
Furthermore, while we see on this page that we get access to methods like `cache.resolve` and other
methods to read from our cache, only ["Cache Updates"](./cache-updates.md) get to write and change
the cache. If you call `cache.updateQuery`, `cache.writeFragment`, or `cache.link` in resolvers,
you‘ll get an error, since it‘s not possible to update the cache while reading from it.
When writing a resolver you’ll mostly use `cache.resolve`, which can be chained, to read field
values from the cache. When a field points to another entity we may get a key, but resolvers are
allowed to return keys or partial entities containing keys.
> **Note:** This essentially means that resolvers can return either scalar values for fields without
> selection sets, and either partial entities or keys for fields with selection sets, i.e.
> links / relations. When we return `null`, this will be interpreted a the literal GraphQL Null scalar,
> while returning `undefined` will cause a cache miss.
## Transforming Records
As we've explored in the ["Normalized Caching" page's section on
records](./normalized-caching.md#storing-normalized-data), "records" are scalars and any fields in
your query without selection sets. This could be a field with a string value, number, or any other
field that resolves to a [scalar type](https://graphql.org/learn/schema/#scalar-types) rather than
another entity i.e. object type.
At the beginning of this page we've already seen an example of a local resolver that we've attached
to a record field where we've added a resolver to a `Todo.updatedAt` field:
```js
cacheExchange({
resolvers: {
Todo: {
updatedAt: parent => new Date(parent.updatedAt),
},
},
});
```
A query that contains this field may look like `{ todo { updatedAt } }`, which clearly shows us that
this field is a scalar since it doesn't have any selection set on the `updatedAt` field. In our
example, we access this field's value and parse it as a `new Date()`.
This shows us that it doesn't matter for scalar fields what kind of value we return. We may parse
strings into more granular JS-native objects or replace values entirely.
We may also run into situations where we'd like to generalise the resolver and not make it dependent
on the exact field it's being attached to. In these cases, the [`info`
object](../api/graphcache.md#info) can be very helpful as it provides us information about the
current query traversal, and the part of the query document the cache is processing. The
`info.fieldName` property is one of these properties and lets us know the field that the resolver is
operating on. Hence, we can create a reusable resolver like so:
```js
const transformToDate = (parent, _args, _cache, info) => new Date(parent[info.fieldName]);
cacheExchange({
resolvers: {
Todo: { updatedAt: transformToDate },
},
});
```
The resolver is now much more reusable, which is particularly handy if we're creating resolvers that
we'd like to apply to multiple fields. The [`info` object has several more
fields](../api/graphcache.md#info) that are all similarly useful to abstract our resolvers.
We also haven't seen yet how to handle a field's arguments.
If we have a field that accepts arguments we can use those as well as they're passed to us with the
second argument of a resolver:
```js
cacheExchange({
resolvers: {
Todo: {
text: (parent, args) => {
return args.capitalize && parent.text ? parent.text.toUpperCase() : parent.text;
},
},
},
});
```
This is actually unlikely to be of use with records and scalar values as our API will have to be
able to use these arguments just as well. In other words, while you may be able to pass any
arguments to a field in your query, your GraphQL API's schema must accept these arguments in the
first place. However, this is still useful if we're trying to imitate what the API is doing, which
will become more relevant in the following examples and sections.
## Resolving Entities
We've already briefly seen that resolvers can be used to replace a link in Graphcache's local data
on the ["Normalized Caching" page](./normalized-caching.md#manually-resolving-entities).
Given that Graphcache [stores entities in a normalized data
structure](./normalized-caching.md#storing-normalized-data) there may be multiple fields on a given
schema that can be used to get to the same entity. For instance, the schema may allow for the same
entity to be looked up by an ID while this entity may also appear somewhere else in a list or on an
entirely different field.
When links (or relations) like these are cached by Graphcache it is able to look up the entities
automatically, e.g. if we've sent a `{ todo(id: 1) { id } }` query to our API once then Graphcache
will have seen that this field leads to the entity it returns and can query it automatically from
its cache.
However, if we have a list like `{ todos { id } }` we may have seen and cached a specific entity,
but as we browse the app and query for `{ todo(id: 1) { id } }`, Graphcache isn't able to
automatically find this entity even if it has cached it already and will send a request to our API.
In many cases we can create a local resolvers to instead tell the cache where to look for a specific
entity by returning partial information for it. Any resolver on a relational field, meaning any
field that links to an object type (or a list of object types) in the schema, may return a partial
entity that tells the cache how to resolve it. Hence, we're able to implement a resolver for the
previously shown `todo(id: $id)` field as such:
```js
cacheExchange({
resolvers: {
Query: {
todo: (_, args) => ({ __typename: 'Todo', id: args.id }),
},
},
});
```
The `__typename` field is required. Graphcache will [use its keying
logic](./normalized-caching.md#custom-keys-and-non-keyable-entities), and your custom `keys`
configuration to generate a key for this entity and will then be able to look this entity up in its
local cache. As with regular queries, the resolver is known to return a link since the `todo(id: $id) { id }` will be used with a selection set, querying fields on the entity.
### Resolving by keys
Resolvers can also directly return keys. We've previously learned [on the "Normalized Caching"
page](./normalized-caching.md#custom-keys-and-non-keyable-entities) that the key for our example above
would look something like `"Todo:1"` for `todo(id: 1)`. While it isn't advisable to create keys
manually in your resolvers, if you returned a key directly this would still work.
Essentially, returning `{ __typename, id }` may sometimes be the same as returning the key manually.
The `cache` that we receive as an argument on resolvers has a method for this logic, [the
`cache.keyOfEntity` method](../api/graphcache.md#keyofentity).
While it doesn't make much sense in this case, our example can be rewritten as:
```js
cacheExchange({
resolvers: {
Query: {
todo: (_, args, cache) => cache.keyOfEntity({ __typename: 'Todo', id: args.id }),
},
},
});
```
And while it's not advisable to create keys ourselves, the resolvers' `cache` and `info` arguments
give us ample opportunities to use and pass around keys.
One example is the `info.parentKey` property. This property [on the `info`
object](../api/graphcache.md#info) will always be set to the key of the entity that the resolver is
currently run on. For instance, for the above resolver it may be `"Query"`, for for a resolver on
`Todo.updatedAt` it may be `"Todo:1"`.
## Resolving other fields
In the above two examples we've seen how a resolver can replace Graphcache's logic, which usually
reads links and records only from its locally cached data. We've seen how a field on a record can
use `parent[fieldName]` to access its cached record value and transform it and how a resolver for a
link can return a partial entity [or a key](#resolving-by-keys).
However sometimes we'll need to resolve data from other fields in our resolvers.
> **Note:** For records, if the other field is on the same `parent` entity, it may seem logical to access it on
> `parent[otherFieldName]` as well, however the `parent` object will only be sparsely populated with
> fields that the cache has already queried prior to reaching the resolver.
> In the previous example, where we've created a resolver for `Todo.updatedAt` and accessed
> `parent.updatedAt` to transform its value the `parent.updatedAt` field is essentially a shortcut
> that allows us to get to the record quickly.
Instead we can use [the `cache.resolve` method](../api/graphcache.md#resolve). This method
allows us to access Graphcache's cached data directly. It is used to resolve records or links on any
given entity and accepts three arguments:
- `entity`: This is the entity on which we'd like to access a field. We may either pass a keyable,
partial entity, e.g. `{ __typename: 'Todo', id: 1 }` or a key. It takes the same inputs as [the
`cache.keyOfEntity` method](../api/graphcache.md#keyofentity), which we've seen earlier in the
["Resolving by keys" section](#resolving-by-keys). It also accepts `null` which causes it to
return `null`, which is useful for chaining multiple `resolve` calls for deeply accessing a field.
- `fieldName`: This is the field's name we'd like to access. If we're looking for the record on
`Todo.updatedAt` we would pass `"updatedAt"` and would receive the record value for this field. If
we pass a field that is a _link_ to another entity then we'd pass that field's name (e.g.
`"author"` for `Todo.author`) and `cache.resolve` will return a key instead of a record value.
- `fieldArgs`: Optionally, as the third argument we may pass the field's arguments, e.g. `{ id: 1 }`
if we're trying to access `todo(id: 1)` for instance.
This means that we can rewrite our original `Todo.updatedAt` example as follows, if we'd like to
avoid using the `parent[fieldName]` shortcut:
```js
cacheExchange({
resolvers: {
Todo: {
updatedAt: (parent, _args, cache) => new Date(cache.resolve(parent, 'updatedAt')),
},
},
});
```
When we call `cache.resolve(parent, "updatedAt")`, the cache will look up the `"updatedAt"` field on
the `parent` entity, i.e. on the current `Todo` entity.
> **Note:** We've also previously learned that `parent` may not contain all fields that the entity may have and
> may hence be missing its keyable fields, like `id`, so why does this then work?
> It works because `cache.resolve(parent)` is a shortcut for `cache.resolve(info.parentKey)`.
Like the `info.fieldName` property `info.parentKey` gives us information about the current state of
Graphcache's query operation. In this case, `info.parentKey` tells us what the parent's key is.
However, since `cache.resolve(parent)` is much more intuitive we can write that instead since this
is a supported shortcut.
From this follows that we may also use `cache.resolve` to access other fields. Let's suppose we'd
want `updatedAt` to default to the entity's `createdAt` field when it's actually `null`. In such a
case we could write a resolver like so:
```js
cacheExchange({
resolvers: {
Todo: {
updatedAt: (parent, _args, cache) => parent.updatedAt || cache.resolve(parent, 'createdAt'),
},
},
});
```
As we can see, we're effortlessly able to access other records from the cache, provided these fields
are actually cached. If they aren't `cache.resolve` will return `null` instead.
Beyond records, we're also able to resolve links and hence jump to records from another entity.
Let's suppose we have an `author { id, createdAt }` field on the `Todo` and would like
`Todo.createdAt` to simply copy the author's `createdAt` field. We can chain `cache.resolve` calls
to get to this value:
```js
cacheExchange({
resolvers: {
Todo: {
createdAt: (parent, _args, cache) =>
cache.resolve(cache.resolve(parent, 'author') /* "Author:1" */, 'createdAt'),
},
},
});
```
The return value of `cache.resolve` changes depending on what data the cache has stored. While it
may return records for fields without selection sets, in other cases it may give you the key of
other entities ("links") instead. It can even give you arrays of keys or records when the field's
value contains a list.
When a value is not present in the cache, `cache.resolve` will instead return `undefined` to signal
that a value is uncached. Similarly, a resolver may return `undefined` to tell Graphcache that the
field isn’t cached and that a call to the API is necessary.
`cache.resolve` is a pretty flexible method that allows us to access arbitrary values from our cache,
however, we have to be careful about what value will be resolved by it, since the cache can't know
itself what type of value it may return.
The last trick this method allows you to apply is to access arbitrary fields on the root `Query`
type. If we call `cache.resolve("Query", ...)` then we're also able to access arbitrary fields
starting from the root `Query` of the cached data. (If you're using [Schema
Awareness](./schema-awareness.md) the name `"Query"` may vary for you depending on your schema.)
We're not constrained to accessing fields on the `parent` of a resolver but can also attempt to
break out and access fields on any other entity we know of.
## Resolving Partial Data
Local resolvers also allow for more advanced use-cases when it comes to links and object types.
Previously we've seen how a resolver is able to link up a given field to an entity, which causes
this field to resolve an entity directly instead of it being checked against any cached links:
```js
cacheExchange({
resolvers: {
Query: {
todo: (_, args) => ({ __typename: 'Todo', id: args.id }),
},
},
});
```
In this example, while `__typename` and `id` are required to make this entity keyable, we're also
able to add on more fields to this object to override values later on in our selection.
For instance, we can write a resolver that links `Query.todo` directly to our `Todo` entity but also
only updates the `createdAt` field directly in the same resolver, if it is indeed accessed via the
`Query.todo` field:
```js
cacheExchange({
resolvers: {
Query: {
todo: (_, args) => ({
__typename: 'Todo',
id: args.id,
createdAt: new Date().toString(),
}),
},
},
});
```
Here we've replaced the `createdAt` value of the `Todo` when it's accessed via this manual resolver.
If it was accessed someplace else, for instance via a `Query.todos` listing field, this override
wouldn't apply.
We can even apply overrides to nested fields, which helps us to create complex resolvers for other
use cases like pagination.
[Read more on the topic of "Pagination" in the section below.](#pagination)
## Computed Queries
We've now seen how the `cache` has several powerful methods, like [the `cache.resolve`
method](../api/graphcache.md#resolve), which allow us to access any data in the cache while writing
resolvers for individual fields.
Additionally the cache has more methods that allow us to access more data at a time, like
`cache.readQuery` and `cache.readFragment`.
### Reading a query
At any point, the `cache` allows us to read entirely separate queries in our resolvers, which starts
a separate virtual operation in our resolvers. When we call `cache.readQuery` with a query and
variables we can execute an entirely new GraphQL query against our cached data:
```js
import { gql } from '@urql/core';
import { cacheExchange } from '@urql/exchange-graphcache';
const cache = cacheExchange({
updates: {
Mutation: {
addTodo: (result, args, cache) => {
const data = cache.readQuery({ query: Todos, variables: { from: 0, limit: 10 } });
},
},
},
});
```
This way we'll get the stored data for the `TodosQuery` for the given `variables`.
[Read more about `cache.readQuery` in the Graphcache API docs.](../api/graphcache.md#readquery)
### Reading a fragment
The store also allows us to read a fragment for any given entity. The `cache.readFragment` method
accepts a `fragment` and an `id`. This looks like the following.
```js
import { gql } from '@urql/core';
import { cacheExchange } from '@urql/exchange-graphcache';
const cache = cacheExchange({
resolvers: {
Query: {
Todo: (parent, args, cache) => {
return cache.readFragment(
gql`
fragment _ on Todo {
id
text
}
`,
{ id: 1 }
);
},
},
},
});
```
> **Note:** In the above example, we've used
> [the `gql` tag function](../api/core.md#gql) because `readFragment` only accepts
> GraphQL `DocumentNode`s as inputs, and not strings.
This way we'll read the entire fragment that we've passed for the `Todo` for the given key, in this
case `{ id: 1 }`.
[Read more about `cache.readFragment` in the Graphcache API docs.](../api/graphcache.md#readfragment)
### Cache methods outside of `resolvers`
The cache read methods are not possible outside of GraphQL operations. This means these methods will
be limited to the different `Graphcache` configuration methods.
## Living with limitations of Local Resolvers
Local Resolvers are powerful tools using which we can tell Graphcache what to do with a certain
field beyond using results it’s seen on prior API results. However, it’s limitations come from this
very intention they were made for.
Resolvers are meant to augment Graphcache and teach it what to do with some fields. Sometimes this
is trivial and simple (like most examples on this page), but other times, fields are incredibly
complex to reproduce and hence resolvers become more complex.
This section is not exhaustive, but documents some of the more commonly asked for features of
resolvers. However, beyond the cases listed below, resolvers are limited and:
- can't manipulate or see other fields on the current entity, or fields above it.
- can't update the cache (they're only “computations” but don't change the cache)
- can't change the query document that's sent to the API
### Writing reusable resolvers
As we've seen before in the ["Transforming Records" section above](#transforming-records), we can
write generic resolvers by using the fourth argument that resolvers receive, the `ResolveInfo`
object.
This `info` object gives our resolvers some context on where they’re being executed and gives it
information about the current field and its surroundings.
For instance, while Graphcache has a convenience helper to access a current record on the parent
object for scalar values, it doesn't for links. Hence, if we're trying to read relationships we have
to use `cache.resolve`.
```js
cacheExchange({
resolvers: {
Todo: {
// This works:
updatedAt: parent => parent.updatedAt,
// This won't work:
author: parent => parent.author,
},
},
});
```
The `info` object actually gives us two ways of accessing the original field's value:
```js
const resolver = (parent, args, cache, info) => {
// This is the full version
const original = cache.resolve(info.parentKey, info.fieldName, args);
// But we can replace `info.parentKey` with `parent` as a shortcut
const original = cache.resolve(parent, info.fieldName, args);
// And we can also avoid re-using arguments by using `fieldKey`
const original = cache.resolve(parent, info.fieldKey);
};
```
Apart from telling us how to access the originally cached field value, we can also get more
information from `info` about our field. For instance, we can:
- Read the current field's name using `info.fieldName`
- Read the current field's key using `info.parentFieldKey`
- Read the current parent entity's key using `info.parentKey`
- Read the current parent entity's typename using `info.parentTypename`
- Access the current operation's raw variables using `info.variables`
- Access the current operation's raw fragments using `info.fragments`
### Causing cache misses and partial misses
When we write resolvers we provide Graphcache with a value for the current field, or rather with
"behavior", that it will execute no matter whether this field is also cached or not.
This means that, unless our resolver returns `undefined`, if the query doesn't have any other cache
misses, Graphcache will consider the field a cache hit and will, unless other cache misses occur,
not make a network request.
> **Note:** An exception for this is [Schema Awareness](./schema-awareness.md), which can
> automatically cause partial cache misses.
However, sometimes we may want a resolver to return a result, while still sending a GraphQL API
request in the background to update our resolver’s values.
To achieve this we can update the `info.partial` field.
```js
cacheExchange({
resolvers: {
Todo: {
author(parent, args, cache, info) {
const author = cache.resolve(parent, info.fieldKey);
if (author === null) {
info.partial = true;
}
return author;
},
},
},
});
```
Suppose we have a field that our GraphQL schema _sometimes_ returns a `null` value for, but that may
be upated with a value in the future. In the above example, we wrote a resolver that sets
`info.partial = true` if a field’s value is `null`. This causes Graphcache to consider the result
“partial and stale” and will cause it to make a background request to the API, while still
delivering the outdated result.
### Conditionally applying resolvers
We may not always want a resolver to be used. While sometimes this can be dangerous (if your
resolver affects the shape and types of your fields), in other cases this is necessary.
For instance, if your resolver handles infinite-scroll pagination, like the examples [in the next
section](#pagination), then you may not always want to apply this resolver.
For this reason, Graphcache also supports [“local directives”, which are introduced on the next docs
page.](./local-directives.md)
## Pagination
`Graphcache` offers some preset `resolvers` to help us out with endless scrolling pagination, also
known as "infinite pagination". It comes with two more advanced but generalised resolvers that can
be applied to two specific pagination use-cases.
They're not meant to implement infinite pagination for _any app_, instead they're useful when we'd
like to add infinite pagination to an app quickly to try it out or if we're unable to replace it
with separate components per page in environments like React Native, where a `FlatList` would
require a flat, infinite list of items.
> **Note:** If you don't need a flat array of results, you can also achieve infinite pagination
> with only UI code. [You can find a code example of UI infinite pagination in our example folder.](https://github.com/urql-graphql/urql/tree/main/examples/with-pagination)
[You can find a code example of infinite pagination with Graphcahce in our example folder.](https://github.com/urql-graphql/urql/tree/main/examples/with-graphcache-pagination).
Please keep in mind that this patterns has some limitations when you're handling cache updates.
Deleting old pages from the cache selectively may be difficult, so the UI pattern in the above
note is preferred.
### Simple Pagination
Given we have a schema that uses some form of `offset` and `limit` based pagination, we can use the
`simplePagination` exported from `@urql/exchange-graphcache/extras` to achieve an endless scroller.
This helper will concatenate all queries performed to one long data structure.
```js
import { cacheExchange } from '@urql/exchange-graphcache';
import { simplePagination } from '@urql/exchange-graphcache/extras';
const cache = cacheExchange({
resolvers: {
Query: {
todos: simplePagination(),
},
},
});
```
This form of pagination accepts an object as an argument, we can specify two
options in here `limitArgument` and `offsetArgument` these will default to `limit`
and `skip` respectively. This way we can use the keywords that are in our queries.
We may also add the `mergeMode` option, which defaults to `'after'` and can otherwise
be set to `'before'`. This will handle in which order pages are merged when paginating.
The default `after` mode assumes that pages that come in last should be merged
_after_ the first pages. The `'before'` mode assumes that pages that come in last
should be merged _before_ the first pages, which can be helpful in a reverse
endless scroller (E.g. Chat App).
Example series of requests:
```
// An example where mergeMode: after works better
skip: 0, limit: 3 => 1, 2, 3
skip: 3, limit: 3 => 4, 5, 6
mergeMode: after => 1, 2, 3, 4, 5, 6 ✔️
mergeMode: before => 4, 5, 6, 1, 2, 3
// An example where mergeMode: before works better
skip: 0, limit: 3 => 4, 5, 6
skip: 3, limit: 3 => 1, 2, 3
mergeMode: after => 4, 5, 6, 1, 2, 3
mergeMode: before => 1, 2, 3, 4, 5, 6 ✔️
```
### Relay Pagination
Given we have a [relay-compatible schema](https://facebook.github.io/relay/graphql/connections.htm)
on our backend, we can offer the possibility of endless data resolving.
This means that when we fetch the next page in our data
received in `useQuery` we'll see the previous pages as well. This is useful for
endless scrolling.
We can achieve this by importing `relayPagination` from `@urql/exchange-graphcache/extras`.
```js
import { cacheExchange } from '@urql/exchange-graphcache';
import { relayPagination } from '@urql/exchange-graphcache/extras';
const cache = cacheExchange({
resolvers: {
Query: {
todos: relayPagination(),
},
// Or if the pagination happens in a nested field:
User: {
todos: relayPagination(),
},
},
});
```
`relayPagination` accepts an object of options, for now we are offering one
option and that is the `mergeMode`. This defaults to `inwards` and can otherwise
be set to `outwards`. This will handle how pages are merged when we paginate
forwards and backwards at the same time. outwards pagination assumes that pages
that come in last should be merged before the first pages, so that the list
grows outwards in both directions. The default inwards pagination assumes that
pagination last pages is part of the same list and come after first pages.
Hence it merges pages so that they converge in the middle.
Example series of requests:
```
first: 1 => node 1, endCursor: a
first: 1, after: a => node 2, endCursor: b
...
last: 1 => node 99, startCursor: c
last: 1, before: c => node 89, startCursor: d
```
With inwards merging the nodes will be in this order: `[1, 2, ..., 89, 99]`
And with outwards merging: `[..., 89, 99, 1, 2, ...]`
The helper happily supports schema that return nodes rather than
individually-cursored edges. For each paginated type, we must either
always request nodes, or always request edges -- otherwise the lists
cannot be stiched together.
### Reading on
[On the next page we'll learn about "Cache Directives".](./local-directives.md)
================================================
FILE: docs/graphcache/normalized-caching.md
================================================
---
title: Normalized Caching
order: 1
---
# Normalized Caching
In GraphQL, like its name suggests, we create schemas that express the relational nature of our
data. When we create and query against a `Query` type we walk a graph that starts at the root
`Query` type and walks through relational types. Rather than querying for normalized data, in
GraphQL our queries request a specific shape of denormalized data, a view into our relational data
that can be re-normalized automatically.
As the GraphQL API walks our query documents it may read from a relational database and _entities_
and scalar values are copied into a JSON document that matches our query document. The type
information of our entities isn't lost however. A query document may still ask the GraphQL API about
what entity it's dealing with using the `__typename` field, which dynamically introspects an
entity's type. This means that GraphQL clients can automatically re-normalize data as results come
back from the API by using the `__typename` field and keyable fields like an `id` or `_id` field,
which are already common conventions in GraphQL schemas. In other words, normalized caches can build
up a relational database of tables in-memory for our application.
For our apps normalized caches can enable more sophisticated use-cases, where different API requests
update data in other parts of the app and automatically update data in our cache as we query our
GraphQL API. Normalized caches can essentially keep the UI of our applications up-to-date when
relational data is detected across multiple queries, mutations, or subscriptions.
## Normalizing Relational Data
As previously mentioned, a GraphQL schema creates a tree of types where our application's data
always starts from the `Query` root type and is modified by other data that's incoming from either a
selection on `Mutation` or `Subscription`. All data that we query from the `Query` type will contain
relations between "entities", JSON objects that are hierarchical.
A normalized cache seeks to turn this denormalized JSON blob back into a relational data structure,
which stores all entities by a key that can be looked up directly. Since GraphQL documents give the
API a strict specification on how it traverses a schema, the JSON data that the cache receives from
the API will always match the GraphQL query document that has been used to query this data.
A common misconception is that normalized caches in GraphQL store data by the query document somehow,
however, the only thing a normalized cache cares about is that it can use our GraphQL query documents
to walk the structure of the JSON data it received from the API.
```graphql
{
__typename
todo(id: 1) {
__typename
id
title
author {
__typename
id
name
}
}
}
```
```json
{
"__typename": "Query",
"todo": {
"__typename": "Todo",
"id": 1,
"title": "implement graphcache",
"author": {
"__typename": "Author",
"id": 1,
"name": "urql-team"
}
}
}
```
Above, we see an example of a GraphQL query document and a corresponding JSON result from a GraphQL
API. In GraphQL, we never lose access to the underlying types of the data. Normalized caches can
ask for the `__typename` field in selection sets automatically and will find out which type a JSON
object corresponds to.
Generally, a normalized cache must do one of two things with a query document like the above:
- It must be able to walk the query document and JSON data of the result and cache the data,
normalizing it in the process and storing it in relational tables.
- It must later be able to walk the query document and recreate this JSON data just by reading data
from its cache, by reading entries from its in-memory relational tables.
While the normalized cache can't know the exact type of each field, thanks to the GraphQL query
language it can make a couple of assumptions. The normalized cache can walk the query document. Each
field that has no selection set (like `title` in the above example) must be a "record", a field that
may only be set to a scalar. Each field that does have a selection set must be another "entity" or a
list of "entities". The latter fields with selection sets are our relations between entities, like a
foreign key in relational databases.
Furthermore, the normalized cache can then read the `__typename` field on related entities. This is
called _Type Name Introspection_ and is how it finds out about the types of each entity.
From the above document we can assume the following relations:
- `Query.todo(id: 1)` → `Todo`
- `Todo.author` → `Author`
However, this isn't quite enough yet to store the relations from GraphQL results. The normalized
cache must also generate primary keys for each entity so that it can store them in table-like data
structures. This is for instance why [Relay
enforces](https://relay.dev/docs/guides/graphql-server-specification/#object-identification) that
each entity must have an `id` field. This allows it to assume that there's an obvious primary key
for each entity it may query. Instead, `urql`'s Graphcache and Apollo assume that there _may_ be an
`id` or `_id` field in a given selection set. If Graphcache can't find these two fields it'll issue
a warning, however a custom `keys` configuration may be used to generate custom keys for a given
type. With this logic the normalized cache will actually create the following "links" between its
relational data:
- `"Query"`, `.todo(id: 1)` → `"Todo:1"`
- `"Todo:1"`, `.author` → `"Author:1"`
As we can see, the `Query` root type itself has a constant key of `"Query"`. All relational data
originates here, since the GraphQL schema is a graph and, like a tree, all selections on a GraphQL
query document originate from it.
Internally, the normalized cache now stores field values on entities by their primary keys. The
above can also be said or written as:
- The `Query` entity's `todo` field with `{"id": 1}` arguments points to the `Todo:1` entity.
- The `Todo:1` entity's `author` field points to the `Author:1` entity.
In Graphcache, these "links" are stored in a nested structure per-entity. "Records" are kept
separate from this relational data.

## Storing Normalized Data
At its core, normalizing data means that we take individual fields and store them in a table. In our
case we store all values of fields in a dictionary of their primary key, generated from an ID or
other key and type name, and the field’s name and arguments, if it has any.
| Primary Key | Field | Value |
| ---------------------- | ----------------------------------------------- | ------------------------ |
| Type name and ID (Key) | Field name (not alias) and optionally arguments | Scalar value or relation |
To reiterate we have three pieces of information that are stored in tables:
- The entity's key can be derived from its type name via the `__typename` field and a keyable field.
By default _Graphcache_ will check the `id` and `_id` fields, however this is configurable.
- The field's name (like `todo`) and optional arguments. If the field has any arguments then we can
normalize it by JSON stringifying the arguments, making sure that the JSON key is stable by
sorting its keys.
- Lastly, we may store relations as either `null`, a primary key that refers to another entity, or a
list of such. For storing "records" we can store the scalars in a separate table.
In _Graphcache_ the data structure for these tables looks a little like the following, where each
entity has a record from fields to other entity keys:
```js
{
links: Map {
'Query': Record {
'todo({"id":1})': 'Todo:1'
},
'Todo:1': Record {
'author': 'Author:1'
},
'Author:1': Record { },
}
}
```
We can see how the normalized cache is now able to traverse a GraphQL query by starting on the
`Query` entity and retrieve relations for other fields.
To retrieve "records" which are all fields with scalar values and no selection sets, _Graphcache_
keeps a second table around with an identical structure. This table only contains scalar values,
which keeps our non-relational data away from our "links":
```js
{
records: Map {
'Query': Record {
'__typename': 'Query'
},
'Todo:1': Record {
'__typename': 'Todo',
'id': 1,
'title': 'implement graphcache'
},
'Author:1': Record {
'__typename': 'Author',
'id': 1,
'name': 'urql-team'
},
}
}
```
This is very similar to how we'd go about creating a state management store manually, except that
_Graphcache_ can use the GraphQL document to perform this normalization automatically.
What we gain from this normalization is that we have a data structure that we can both read from and
write to, to reproduce the API results for GraphQL query documents. Any mutation or subscription can
also be written to this data structure. Once _Graphcache_ finds a keyable entity in their results
it's written to its relational table which may update other queries in our application.
Similarly queries may share data between one another which means that they effectively share
entities using this approach and can update one another.
In other words, once we have a primary key like `"Todo:1"` we may find this primary key again in
other entities in other GraphQL results.
## Custom Keys and Non-Keyable Entities
In the above introduction we've learned that while _Graphcache_ doesn't enforce `id` fields on each
entity, it checks for the `id` and `_id` fields by default. There are many situations in which
entities may either not have a key field or have different keys.
As _Graphcache_ traverses JSON data and a GraphQL query document to write data to the cache you may
see a warning from it along the lines of ["Invalid key: [...] No key could be generated for the data
at this field."](./errors.md/#15-invalid-key) _Graphcache_ has many warnings like these that attempt
to detect undesirable behaviour and helps us to update our configuration or queries accordingly.
In the simplest cases, we may simply have forgotten to add the `id` field to the selection set of
our GraphQL query document. However, what if the field is instead called `uuid` and our query looks
accordingly different?
```graphql
{
item {
uuid
}
}
```
In the above selection set we have an `item` field that has a `uuid` field rather than an `id`
field. This means that _Graphcache_ won't automatically be able to generate a primary key for this
entity. Instead, we have to help it generate a key by passing it a custom `keys` config:
```js
cacheExchange({
keys: {
Item: data => data.uuid,
},
});
```
We may add a function as an entry to the `keys` configuration. The property here, `"Item"` must be
the typename of the entity for which we're generating a key. The function may return an arbitarily
generated key. So for our `item` field, which in our example schema gives us an `Item` entity, we
can create a `keys` configuration entry that creates a key from the `uuid` field rather than the
`id` field.
This also raises a question, **what does _Graphcache_ do with unkeyable data by default? And, what
if my data has no key?**
This special case is what we call "embedded data". Not all types in a GraphQL schema will have
keyable fields and some types may just abstract data without themselves being relational. They may
be "edges", entities that have a field pointing to other entities that simply connect two entities,
or data types like a `GeoJson` or `Image` type.
In these cases, where the normalized cache encounters unkeyable types, it will create an embedded
key by using the parent's primary key and combining it with the field key. This means that
"embedded entities" are only reachable from a specific field on their parent entities. They're
globally unique and aren't strictly speaking relational data.
```graphql
{
__typename
todo(id: 1) {
id
image {
url
width
height
}
}
}
```
In the above example we're querying an `Image` type on a `Todo`. This imaginary `Image` type has no
key because the image is embedded data and will only ever be associated to this `Todo`. In other
words, the API's schema doesn't consider it necessary to have a primary key field for this type.
Maybe it doesn't even have an ID in our backend's database. We _could_ assign this type an imaginary
key (maybe based on the `url`) but in fact if it's not shared data it wouldn't make much sense to
do so.
When _Graphcache_ attempts to store this entity it will issue the previously mentioned warning.
Internally, it'll then generate an embedded key for this entity based on the parent entity. If
the parent entity's key is `Todo:1` then the embedded key for our `Image` will become
`Todo:1.image`. This is also how this entity will be stored internally by _Graphcache_:
```js
{
records: Map {
'Todo:1.image': Record {
'__typename': 'Image',
'url': '...',
'width': 1024,
'height': 768
},
}
}
```
This doesn't however mute the warning that _Graphcache_ outputs, since it believes we may have made a
mistake. The warning itself gives us advice on how to mute it:
> If this is intentional, create a keys config for `Image` that always returns null.
Meaning, that we can add an entry to our `keys` config for our non-keyable type that explicitly
returns `null`, which tells _Graphcache_ that the entity has no key:
```js
cacheExchange({
keys: {
Image: () => null,
},
});
```
### Flexible Key Generation
In some cases, you may want to create a pattern for your key generation. For instance, you may want
to say "create a special key for every type ending in `'Node'`. In such a case we recommend creating
a small JS `Proxy` to take care of key generation for you and making the keys functional.
```js
cacheExchange({
keys: new Proxy(
{
Image: () => null,
},
{
get(target, prop, receiver) {
if (prop.endsWith('Node')) {
return data => data.uid;
}
const fallback = data => data.uuid;
return target[prop] || fallback;
},
}
),
});
```
In the above example, we dynamically change the key generator depending on the typename. When
a typename ends in `'Node'`, we return a key generator that uses the `uid` field. We still fall back
to an object of manual key generation functions however. Lastly though, when a type doesn't have
a predefined key generator, we change the default behavior from using `id` and `_id` fields to using
`uuid` fields.
## Non-Automatic Relations and Updates
While _Graphcache_ is able to store and update our entities in an in-memory relational data
structure, which keeps the same entities in singular unique locations, a GraphQL API may make a lot
of implicit changes to the relations of data as it runs or have trivial relations that our cache
doesn't need to see to resolve. Like with the `keys` config, we have two more configuration options
to combat this: `resolvers` and `updates`.
### Manually resolving entities
Some fields in our configuration can be resolved without checking the GraphQL API for relations. The
`resolvers` config allows us to create a list of client-side resolvers where we can read from the
cache directly as _Graphcache_ creates a local GraphQL result from its cached data.
```graphql
{
todo(id: 1) {
id
}
}
```
Previously we've looked at the above query to illustrate how data from a GraphQL API may be written
to _Graphcache_'s relational data structure to store the links and entities in a result against this
GraphQL query document. However, it may be possible for another query to have already written this
`Todo` entity to the cache. So, **how do we resolve a relation manually?**
In such a case, _Graphcache_ may have seen and stored the `Todo` entity but isn't aware of the
relation between `Query.todo({"id":1})` and the `Todo:1` entity. However, we can tell _Graphcache_
which entity it should look for when it accesses the `Query.todo` field by creating a resolver for
it:
```js
cacheExchange({
resolvers: {
Query: {
todo(parent, args, cache, info) {
return { __typename: 'Todo', id: args.id };
},
},
},
});
```
A resolver is a function that's similar to [GraphQL.js' resolvers on the
server-side](https://www.graphql-tools.com/docs/resolvers/). They receive the parent data, the
field's arguments, access to _Graphcache_'s cached data, and an `info` object. [The entire function
signature and more explanations can be found in the API docs.](../api/graphcache.md#resolvers-option)
Since it can access the field's arguments from the GraphQL query document, we can return a partial
`Todo` entity. As long as this
object is keyable, it will tell _Graphcache_ what the key of the returned entity is. In other words,
we've told it how to get to a `Todo` from the `Query.todo` field.
This mechanism is immensely more powerful than this example. We have other use-cases that
resolvers may be used for:
- Resolvers can be applied to fields with records, which means that it can be used to change or
transform scalar values. For instance, we can update a string or parse a `Date` right inside a
resolver.
- Resolvers can return deeply nested results, which will be layered on top of the in-memory
relational cached data of _Graphcache_, which means that it can emulate infinite pagination and
other complex behaviour.
- Resolvers can change when a cache miss or hit occurs. Returning `null` means that a field’s value
is literally `null`, which will not cause a cache miss, while returning `undefined` will mean
a field’s value is uncached.
- Resolvers can return either partial entities or keys, so we can chain `cache.resolve` calls to
read fields from the cache, even when a field is pointing at another entity, since we can return
keys to the other entity directly.
[Read more about resolvers on the following page about "Local Resolvers".](./local-resolvers.md)
### Manual cache updates
While `resolvers`, as shown above, operate while _Graphcache_ is reading from its in-memory cache,
`updates` are a configuration option that operate while _Graphcache_ is writing to its cached data.
Specifically, these functions can be used to add more updates onto what a `Mutation` or
`Subscription` may automatically update.
As stated before, a GraphQL schema's data may undergo a lot of implicit changes when we send it a
`Mutation` or `Subscription`. A new item that we create may for instance manipulate a completely
different item or even a list. Often mutations and subscriptions alter relations that their
selection sets wouldn't necessarily see. Since mutations and subscriptions operate on a different
root type, rather than the `Query` root type, we often need to update links in the rest of our data
when a mutation is executed.
```graphql
query TodosList {
todos {
id
title
}
}
mutation AddTodo($title: String!) {
addTodo(title: $title) {
id
title
}
}
```
In a simple example, like the one above, we have a list of todos in a query and create a new todo
using the `Mutation.addTodo` mutation field. When the mutation is executed and we get the result
back, _Graphcache_ already writes the `Todo` item to its normalized cache. However, we also want to
add the new `Todo` item to the list on `Query.todos`:
```js
import { gql } from '@urql/core';
cacheExchange({
updates: {
Mutation: {
addTodo(result, args, cache, info) {
const query = gql`
{
todos {
id
}
}
`;
cache.updateQuery({ query }, data => {
data.todos.push(result.addTodo);
return data;
});
},
},
},
});
```
In this code example we can first see that the signature of the `updates` entry is very similar to
the one of `resolvers`. However, we're seeing the `cache` in use for the first time. The `cache`
object (as [documented in the API docs](../api/graphcache.md#cache)) gives us
access to _Graphcache_'s mechanisms directly. Not only can we resolve data using it, we can directly
start sub-queries or sub-writes manually. These are full normalized cache runs inside other runs. In
this case we're calling `cache.updateQuery` on a list of `Todo` items while the `Mutation` that
added the `Todo` is already being written to the cache.
As we can see, we may perform manual changes inside of `updates` functions, which can be used to
affect other parts of the cache (like `Query.todos` here) beyond the automatic updates that a
normalized cache is expected to perform.
We get methods like `cache.updateQuery`, `cache.writeFragment`, and `cache.link` in our updater
functions, which aren't available to us in local resolvers, and can only be used in these `updates`
entries to change the data that the cache holds.
[Read more about writing cache updates on the "Cache Updates" page.](./cache-updates.md)
## Deterministic Cache Updates
Above, in [the "Storing Normalized Data" section](#storing-normalized-data), we've talked about how
Graphcache is able to store normalized data. However, apart from storing this data there are a
couple of caveats that many applications simply ignore, skip, or simplify when they implement a
store to cache their data in.
Amongst features like [Optimistic Updates](./cache-updates.md#optimistic-updates) and [Offline
Support](./offline.md), Graphcache supports several features that allow our API results to be more
unreliable. Essentially we don't expect API results to always come back in order or on time.
However, we expect Graphcache to prevent us from making "indeterministic cache updates", meaning
that we expect it to handle API results that come back in a random order and delayed gracefully.
In terms of the ["Manual Cache Updates"](#manual-cache-updates) that we've talked about above and
[Optimistic Updates](./cache-updates.md#optimistic-updates) the limitations are pretty simple at
first and if we use Graphcache as usual we may not even notice them:
- When we make an _optimistic_ change, we define what a mutation's result may look like once the API
responds in the future and apply this temporary result immediately. We store this temporary data
in a separate "layer". Once the real result comes back this layer can be deleted and the real API
result can be applied as usual.
- When multiple _optimistic updates_ are made at the same time, we never allow these layers to be
deleted separately. Instead Graphcache waits for all mutations to complete before deleting the
optimistic layers and applying the real API result. This means that a mutation update cannot
accidentally commit optimistic data to the cache permanently.
- While an _optimistic update_ has been applied, Graphcache stops refetching any queries that contain
this optimistic data so that it doesn't "flip back" to its non-optimistic state without the
optimistic update being applied. Otherwise we'd see a "flicker" in the UI.
These three principles are the basic mechanisms we can expect from Graphcache. The summary is:
**Graphcache groups optimistic mutations and pauses queries so that optimistic updates look as
expected,** which is an implementation detail we can mostly ignore when using it.
However, one implementation detail we cannot ignore is the last mechanism in Graphcache which is
called **"Commutativity"**. As we can tell, "optimistic updates" need to store their normalized
results on a separate layer. This means that the previous data structure we've seen in Graphcache is
actually more like a list, with many tables of links and entities.
Each layer may contain optimistic results and have an order of preference. However, this order also
applies to queries. Since queries are run in one order but their API results can come back to us in
a very different order, if we access enough pages in a random order things can sometimes look rather
weird. We may see that in an application on a slow network connection the results may vary depending
on when their results came back.

Instead, Graphcache actually uses layers for any API result it receives. In case, an API result
arrives out-of-order, it sorts them by precedence — or rather by when they've been requested.
Overall, we don't have to worry about this, but Graphcache has mechanisms that keep our updates
safe.
## Reading on
This concludes the introduction to Graphcache with a short overview of how it works, what it
supports, and some hidden mechanisms and internals. Next we may want to learn more about how to use
it and more of its features:
- [How do we write "Local Resolvers"?](./local-resolvers.md)
- [How to set up "Cache Updates" and "Optimistic Updates"?](./cache-updates.md)
- [What is Graphcache's "Schema Awareness" feature for?](./schema-awareness.md)
- [How do I enable "Offline Support"?](./offline.md)
================================================
FILE: docs/graphcache/offline.md
================================================
---
title: Offline Support
order: 7
---
# Offline Support
_Graphcache_ allows you to build an offline-first app with built-in offline and persistence support,
by adding a `storage` interface. In combination with its [Schema
Awareness](./schema-awareness.md) support and [Optimistic
Updates](./cache-updates.md#optimistic-updates) this can be used to build an application that
serves cached data entirely from memory when a user's device is offline and still display
optimistically executed mutations.
## Setup
Everything that's needed to set up offline-support is already packaged in the
`@urql/exchange-graphcache` package.
We initially recommend setting up the [Schema Awareness](./schema-awareness.md). This adds our
server-side schema information to the cache, which allows it to make decisions on what partial data
complies with the schema. This is useful since the offline cache may often be lacking some data but
may then be used to display the partial data we do have, as long as missing data is actually marked
as optional in the schema.
Furthermore, if we have any mutations that the user doesn't interact with after triggering them (for
instance, "liking a post"), we can set up [Optimistic
Updates](./cache-updates.md#optimistic-updates) for these mutations, which allows them to be
reflected in our UI before sending a request to the API.
To actually now set up offline support, we'll swap out the `cacheExchange` with the
`offlineExchange` that's also exported by `@urql/exchange-graphcache`.
```js
import { Client, fetchExchange } from 'urql';
import { offlineExchange } from '@urql/exchange-graphcache';
const cache = offlineExchange({
schema,
updates: {
/* ... */
},
optimistic: {
/* ... */
},
});
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cache, fetchExchange],
});
```
This activates offline support, however we'll also need to provide the `storage` option to the
`offlineExchange`. The `storage` is an adapter that contains methods for storing cache data in a
persisted storage interface on the user's device.
By default, we can use the default storage option that `@urql/exchange-graphcache` comes with. This
default storage uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) to
persist the cache's data. We can use this default storage by importing the `makeDefaultStorage`
function from `@urql/exchange-graphcache/default-storage`.
```js
import { Client, fetchExchange } from 'urql';
import { offlineExchange } from '@urql/exchange-graphcache';
import { makeDefaultStorage } from '@urql/exchange-graphcache/default-storage';
const storage = makeDefaultStorage({
idbName: 'graphcache-v3', // The name of the IndexedDB database
maxAge: 7, // The maximum age of the persisted data in days
});
const cache = offlineExchange({
schema,
storage,
updates: {
/* ... */
},
optimistic: {
/* ... */
},
});
const client = new Client({
url: 'http://localhost:3000/graphql',
exchanges: [cache, fetchExchange],
});
```
## React Native
For React Native, we can use the async storage package `@urql/storage-rn`.
Before installing the [library](https://github.com/urql-graphql/urql/tree/main/packages/storage-rn), ensure you have installed the necessary peer dependencies:
- NetInfo ([RN](https://github.com/react-native-netinfo/react-native-netinfo) | [Expo](https://docs.expo.dev/versions/latest/sdk/netinfo/)) and
- AsyncStorage ([RN](https://react-native-async-storage.github.io/async-storage/docs/install) | [Expo](https://docs.expo.dev/versions/v42.0.0/sdk/async-storage/)).
```sh
yarn add @urql/storage-rn
# or
npm install --save @urql/storage-rn
```
You can then create the custom storage and use it in the offline exchange:
```js
import { makeAsyncStorage } from '@urql/storage-rn';
const storage = makeAsyncStorage({
dataKey: 'graphcache-data', // The AsyncStorage key used for the data (defaults to graphcache-data)
metadataKey: 'graphcache-metadata', // The AsyncStorage key used for the metadata (defaults to graphcache-metadata)
maxAge: 7, // How long to persist the data in storage (defaults to 7 days)
});
```
## Offline Behavior
_Graphcache_ applies several mechanisms that improve the consistency of the cache and how it behaves
when it's used in highly cached-dependent scenarios, including when it's used with its offline
support. We've previously read about some of these guarantees on the ["Normalized Caching"
page.](./normalized-caching.md)
While the client is offline, _Graphcache_ will also apply some opinionated mechanisms to queries and
mutations.
When a query fails with a Network Error, which indicates that the client is
offline the `offlineExchange` won't deliver the error for this query to avoid it from being
surfaced to the user. This works particularly well in combination with ["Schema
Awareness"](./schema-awareness.md) which will deliver as much of a partial query result as possible.
In combination with the [`cache-and-network` request policy](../basics/document-caching.md#request-policies)
we can now ensure that we display as much data as possible when the user is offline while still
keeping the cache up-to-date when the user is online.
A similar mechanism is applied to optimistic mutations when the user is offline. Normal
non-optimistic mutations are executed as usual and may fail with a network error. Optimistic
mutations however will be queued up and may be retried when the app is restarted or when the user
comes back online.
If we wish to customize when an operation result from the API is deemed an operation that has failed
because the device is offline, we can pass a custom `isOfflineError` function to the
`offlineExchange`, like so:
```js
const cache = offlineExchange({
isOfflineError(error, _result) {
return !!error.networkError;
},
// ...
});
```
However, this is optional, and the default function checks for common offline error messages and
checks `navigator.onLine` for you.
## Custom Storages
In the [Setup section](#setup) we've learned how to use the default storage engine to store
persisted cache data in IndexedDB. You can also write custom storage engines, if the default one
doesn't align with your expectations or requirements.
One limitation of our default storage engine is for instance that data is stored time limited with a
maximum age, which prevents the database from becoming too full, but a custom storage engine may
have different strategies for dealing with this.
[The API docs list the entire interface for the `storage` option.](../api/graphcache.md#storage-option)
There we can see the methods we need to implement to implement a custom storage engine.
Following is an example of the simplest possible storage engine, which uses the browser's
[Local Storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
Initially we'll implement the basic persistence methods, `readData` and `writeData`.
```js
const makeLocalStorage = () => {
const cache = {};
return {
writeData(delta) {
return Promise.resolve().then(() => {
Object.assign(cache, delta);
localStorage.setItem('data', JSON.stringify(cache));
});
},
readData() {
return Promise.resolve().then(() => {
const local = localStorage.getItem('data') || null;
Object.assign(cache, JSON.parse(local));
return cache;
});
},
};
};
```
As we can see, the `writeData` method only sends us "deltas", partial objects that only describe
updated cache data rather than all cache data. The implementation of `writeMetadata` and
`readMetadata` will however be even simpler, since it always sends us complete data.
```js
const makeLocalStorage = () => {
return {
/* ... */
writeMetadata(data) {
localStorage.setItem('metadata', JSON.stringify(data));
},
readMetadata() {
return Promise.resolve().then(() => {
const metadataJson = localStorage.getItem('metadata') || null;
return JSON.parse(metadataJson);
});
},
};
};
```
Lastly, the `onOnline` method will likely always look the same, as long as your `storage` is
intended to work for browsers only:
```js
const makeLocalStorage = () => {
return {
/* ... */
onOnline(cb: () => void) {
window.addEventListener('online', () => {
cb();
});
},
};
};
```
================================================
FILE: docs/graphcache/schema-awareness.md
================================================
---
title: Schema Awareness
order: 5
---
# Schema Awareness
Previously, [on the "Normalized Caching" page](./normalized-caching.md) we've seen how Graphcache
stores normalized data in its store and how it traverses GraphQL documents to do so. What we've seen
is that just using the GraphQL document for traversal, and the `__typename` introspection field
Graphcache is able to build a normalized caching structure that keeps our application up-to-date
across API results, allows it to store data by entities and keys, and provides us configuration
options to write [manual cache updates](./cache-updates.md) and [local
resolvers](./local-resolvers.md).
While this is all possible without any information about a GraphQL API's schema, the `schema` option
on `cacheExchange` allows us to pass an introspected schema to Graphcache:
```js
const introspectedSchema = {
__schema: {
queryType: { name: 'Query' },
mutationType: { name: 'Mutation' },
subscriptionType: { name: 'Subscription' },
},
};
cacheExchange({ schema: introspectedSchema });
```
In GraphQL, [APIs allow for the entire schema to be
"introspected"](https://graphql.org/learn/introspection/), which are special GraphQL queries that
give us information on what the API supports. This information can either be retrieved from a
GraphQL API directly or from the GraphQL.js Schema and contains a list of all types, the types'
fields, scalars, and other information.
In Graphcache we can pass this schema information to enable several features that aren't enabled if
we don't pass any information to this option:
- Fragments will be matched deterministically: A fragment can be written to be on an interface type
or multiple fragments can be spread for separate union'ed types in a selection set. In many cases,
if Graphcache doesn't have any schema information then it won't know what possible types a field
can return and may sometimes make a guess and [issue a
warning](./errors.md#16-heuristic-fragment-matching). If we pass Graphcache a `schema` then it'll
be able to match fragments deterministically.
- A schema may have non-default names for its root types; `Query`, `Mutation`, and `Subscription`.
The names can be changed by passing `schema` information to `cacheExchange` which is important
if the root type appears elsewhere in the schema, e.g. if the `Query` can be accessed on a
`Mutation` field's result.
- We may write a lot of configuration for our `cacheExchange` but if we pass a `schema` then it'll
start checking whether any of the configuration options actually don't exist, maybe because we've
typo'd them. This is a small detail but can make a large difference in a longer configuration.
- Lastly; a schema contains information on **which fields are optional or required**. When
Graphcache has a schema it knows optional fields that may be left out, and it'll be able to generate
"partial results".
### Partial Results
As we navigate an app that uses Graphcache we may be in states where some of our data is already
cached while some aren't. Graphcache normalizes data and stores it in tables for links and records for
each entity, which means that sometimes it can maybe even execute a query against its cache that it
hasn't sent to the API before.
[On the "Local Resolvers" page](./local-resolvers.md#resolving-entities) we've seen how to write
resolvers that resolve entities without having to have seen a link from an API result before. If
Graphcache uses these resolvers and previously cached data we often run into situations where a
"partial result" could already be generated, which is what Graphcache does when it has `schema`
information.

Without a `schema` and information on which fields are optional, Graphcache will consider a "partial
result" as a cache miss. If we don't have all the information for a query then we can't execute
it against the locally cached data after all. However, an API's schema contains information on which
fields are required and optional, and if our apps are typed with this schema and
TypeScript, can't we then use and handle these partial results before a request is sent to the API?
This is the idea behind "Schema Awareness" and "Partial Results". When Graphcache has `schema`
information it may give us partial results [with the `stale` flag
set](../api/core.md#operationresult) while it fetches the full result from the API in the
background. This allows our apps to show some information while more is loading.
## Getting your schema
But how do you get an introspected `schema`? The process of introspecting a schema is running an
introspection query on the GraphQL API, which will give us our `IntrospectionQuery` result. So an
introspection is just another query we can run against our GraphQL APIs or schemas.
As long as `introspection` is turned on and permitted, we can download an introspection schema by
running a normal GraphQL query against the API and save the result in a JSON file.
```js
import { getIntrospectionQuery } from 'graphql';
import fetch from 'node-fetch'; // or your preferred request in Node.js
import * as fs from 'fs';
fetch('http://localhost:3000/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
variables: {},
query: getIntrospectionQuery({ descriptions: false }),
}),
})
.then(result => result.json())
.then(({ data }) => {
fs.writeFile('./schema.json', JSON.stringify(data), err => {
if (err) {
console.error('Writing failed:', err);
return;
}
console.log('Schema written!');
});
});
```
Alternatively, if you're already using [GraphQL Code Generator](https://graphql-code-generator.com/)
you can use [their `@graphql-codegen/introspection`
plugin](https://graphql-code-generator.com/docs/plugins/introspection) to do the same automatically
against a local schema. Furthermore it's also possible to
[`execute`](https://graphql.org/graphql-js/execution/#execute) the introspection query directly
against your `GraphQLSchema`.
## Optimizing a schema
An `IntrospectionQuery` JSON blob from a GraphQL API can without modification become quite large.
The shape of this data is `{ "__schema": ... }` and this _schema_ data will contain information on
all directives, types, input objects, scalars, deprecation, enums, and more. This can quickly add up and one of the
largest schemas, the GitHub GraphQL API's schema, has an introspection size of about 1.1MB, or about
50KB gzipped.
However, we can use the `@urql/introspection` package's `minifyIntrospectionQuery` helper to reduce
the size of this introspection data. This helper strips out information on directives, scalars,
input types, deprecation, enums, and redundant fields to only leave information that _Graphcache_
actually requires.
In the example of the GitHub GraphQL API this reduces the introspected data to around 20kB gzipped,
which is much more acceptable.
### Installation & Setup
First, install the `@urql/introspection` package:
```sh
yarn add @urql/introspection
# or
npm install --save @urql/introspection
```
You'll then need to integrate it into your introspection script or in another place where it can
optimise the introspection data. For this example, we'll just add it to the fetching script from
[above](#getting-your-schema).
```js
import { getIntrospectionQuery } from 'graphql';
import fetch from 'node-fetch'; // or your preferred request in Node.js
import * as fs from 'fs';
import { getIntrospectedSchema, minifyIntrospectionQuery } from '@urql/introspection';
fetch('http://localhost:3000/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
variables: {},
query: getIntrospectionQuery({ descriptions: false }),
}),
})
.then(result => result.json())
.then(({ data }) => {
const minified = minifyIntrospectionQuery(getIntrospectedSchema(data));
fs.writeFileSync('./schema.json', JSON.stringify(minified));
});
```
The `getIntrospectionSchema ` doesn't only accept `IntrospectionQuery` JSON data as inputs, but also
allows you to pass a JSON string, `GraphQLSchema`, or GraphQL Schema SDL strings. It's a convenience
helper and not needed in the above example.
## Integrating a schema
Once we have a schema that's already saved to a JSON file, we can load it and pass it to the
`cacheExchange`'s `schema` option:
```js
import schema from './schema.json';
const cache = cacheExchange({ schema });
```
It may be worth checking what your bundler or framework does when you import a JSON file. Typically
you can reduce the parsing time by making sure it's turned into a string and parsed using
`JSON.parse`
================================================
FILE: docs/showcase.md
================================================
---
title: Showcase
order: 7
---
# Showcase
`urql` wouldn't be the same without our growing and loving community of users,
maintainers and supporters. This page is specifically dedicated to all of you!
## Used by folks at
## Articles & Tutorials
- [Egghead Course](https://egghead.io/lessons/graphql-set-up-an-urql-graphql-provider-in-react?pl=introduction-to-urql-a-react-graphql-client-faaa2bf5)
by [Ian Jones](https://twitter.com/_jonesian).
- [How To GraphQL: React + urql](https://www.howtographql.com/react-urql/0-introduction/)
## Community packages
- [`reason-urql`](https://github.com/FormidableLabs/reason-urql): The official Reason bindings for
`urql`.
- [`urql-persisted-queries`](https://github.com/Daniel15/urql-persisted-queries): Support for
Apollo-style persisted queries.
- [`urql-computed-queries`](https://github.com/Drawbotics/urql-computed-exchange): An exchange to
compute fields on-the-fly using the `@computed` directive.
- [`graphql-code-generator`](https://graphql-code-generator.com/docs/plugins/typescript-urql): A plugin
that helps you make typesafe hooks/components with urql.
- [`urql-custom-scalars-exchange`](https://github.com/clentfort/urql-custom-scalars-exchange): An exchange
to automatically convert scalars.
- [`@grafbase/urql-exchange`](https://github.com/grafbase/playground/tree/main/packages/grafbase-urql-exchange): URQL-exchange for handling Server Sent Events (SSE) with Grafbase GraphQL Live Queries.
to automatically convert scalars.
- [`urql-rest-exchange`](https://github.com/iamsavani/urql-rest-exchange): A custom exchange for URQL that supports GraphQL queries/mutations via REST endpoints
- [`urql-exhaustive-additional-typenames-exchange`](https://github.com/route06/urql-exhaustive-additional-typenames-exchange): URQL-exchange that add all list fields of the operation to additionalTypenames to help document caching
================================================
FILE: examples/README.md
================================================
This example shows how to implement **infinite scroll** pagination with `urql`
in your React UI code.
It's slightly different than the [`with-pagination`](../with-pagination) example
and shows how to implement a full infinitely scrolling list with only your UI code,
while fulfilling the following requirements:
- Unlike with [`with-graphcache-pagination`](../with-graphcache-pagination),
the `urql` cache doesn't have to know about your infinite list, and this works
with any cache, even the document cache
- Unlike with [`with-pagination`](../with-pagination), your list can use cursors,
and each page can update, while keeping the variables for the next page dynamic.
- It uses no added state, no extra processing of lists, and you need no effects.
In other words, unless you need a flat array of items
(e.g. unless you’re using React Native’s `FlatList`), this is the simplest way
to implement an infinitely scrolling, paginated list.
This example is also reapplicable to other libraries, like Svelte or Vue.
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.jsx)
- This also contains a search input which is used as input for the GraphQL queries
- All pagination components are in [`src/SearchResults.jsx`](src/SearchResults.jsx)
- The `SearchRoot` component loads the first page of results and renders `SearchPage`
- The `SearchPage` displays cached results, and otherwise only starts a network request on
a button press
- The `Package` component is used for each result item
================================================
FILE: examples/with-infinite-pagination/index.html
================================================
with-pagination
================================================
FILE: examples/with-infinite-pagination/package.json
================================================
{
"name": "with-pagination",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite"
},
"dependencies": {
"@urql/core": "^6.0.1",
"graphql": "^16.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"urql": "^5.0.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-infinite-pagination/src/App.jsx
================================================
import React, { useState } from 'react';
import { Client, Provider, cacheExchange, fetchExchange } from 'urql';
import SearchRoot from './SearchResults';
const client = new Client({
// The GraphQL API we use here uses the NPM registry
// We'll use it to display search results for packages
url: 'https://trygql.formidable.dev/graphql/relay-npm',
exchanges: [cacheExchange, fetchExchange],
});
// We will be able to enter a search term, and this term
// will render search results
function PaginatedNpmSearch() {
const [search, setSearch] = useState('urql');
const setSearchValue = event => {
event.preventDefault();
setSearch(event.currentTarget.value);
};
return (
<>
Type to search for npm packages
{/* Try changing the search input, then changing it back... */}
{/* If you do this, all cached pages will display immediately! */}
{/* The component contains all querying logic */}
>
);
}
function App() {
return (
);
}
export default App;
================================================
FILE: examples/with-infinite-pagination/src/SearchResults.jsx
================================================
import React, { useCallback } from 'react';
import { gql, useQuery } from 'urql';
// We define a fragment, just to define the data
// that our item component will use in the results list
const packageFragment = gql`
fragment SearchPackage on Package {
id
name
latest: version(selector: "latest") {
version
}
}
`;
// The main query fetches the first page of results and gets our `PageInfo`
// This tells us whether more pages are present which we can query.
const rootQuery = gql`
query SearchRoot($searchTerm: String!, $resultsPerPage: Int!) {
search(query: $searchTerm, first: $resultsPerPage) {
edges {
cursor
node {
...SearchPackage
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
${packageFragment}
`;
// We split the next pages we load into a separate query. In this example code,
// both queries could be the same, but we keep them separate for educational
// purposes.
// In a real app, your "root query" would often fetch more data than the search page query.
const pageQuery = gql`
query SearchPage(
$searchTerm: String!
$resultsPerPage: Int!
$afterCursor: String!
) {
search(query: $searchTerm, first: $resultsPerPage, after: $afterCursor) {
edges {
cursor
node {
...SearchPackage
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
${packageFragment}
`;
// This is the component that we render in `./App.jsx`.
// It accepts our variables as props.
const SearchRoot = ({ searchTerm = 'urql', resultsPerPage = 10 }) => {
const [rootResult] = useQuery({
query: rootQuery,
variables: {
searchTerm,
resultsPerPage,
},
});
if (rootResult.fetching) {
return Loading...;
}
// Here, we render the results as a list into a fragment, and if `hasNextPage`
// is truthy, we immediately render for the next page.
const connection = rootResult.data?.search;
return (
<>
{connection?.edges?.length === 0 ? No Results : null}
{connection?.edges.map(edge => (
))}
{/* The component receives the same props, plus the `afterCursor` for its variables */}
{connection?.pageInfo.hasNextPage ? (
) : rootResult.fetching ? (
Loading...
) : null}
>
);
};
// The is rendered for each page of results, except for the root query.
// It renders *itself* recursively, for the next page of results.
const SearchPage = ({ searchTerm, resultsPerPage, afterCursor }) => {
// Each fetches its own page results!
const [pageResult, executeQuery] = useQuery({
query: pageQuery,
// Initially, we *only* want to display results if, they're cached
requestPolicy: 'cache-only',
// We don't want to run the query if we don't have a cursor (in this example, this will never happen)
pause: !afterCursor,
variables: {
searchTerm,
resultsPerPage,
afterCursor,
},
});
// We only load more results, by allowing the query to make a network request, if
// a button has pressed.
// In your app, you may want to do this automatically if the user can see the end of
// your list, e.g. via an IntersectionObserver.
const onLoadMore = useCallback(() => {
// This tells the query above to execute and instead of `cache-only`, which forbids
// network requests, we now allow them.
executeQuery({ requestPolicy: 'cache-first' });
}, [executeQuery]);
if (pageResult.fetching) {
return Loading...;
}
const connection = pageResult.data?.search;
return (
<>
{/* If our query has nodes, we render them here. The page renders its own results */}
{connection?.edges.map(edge => (
))}
{/* If we have a next page, we now render it recursively! */}
{/* As before, the next will not fetch immediately, but only query from cache */}
{connection?.pageInfo.hasNextPage ? (
) : pageResult.fetching ? (
Loading...
) : null}
{!connection && !pageResult.fetching ? (
Load more
) : null}
>
);
};
// This is the component that then renders each result item
const Package = ({ node }) => (
{node.name}@{node.latest.version}
);
export default SearchRoot;
================================================
FILE: examples/with-infinite-pagination/src/index.jsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render();
================================================
FILE: examples/with-infinite-pagination/vite.config.js
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/with-multipart/README.md
================================================
# With Multipart File Upload
This example shows `urql` in use with `@urql/exchange-multipart-fetch`'s `multipartFetchExchange`
to support file uploads in GraphQL. This largely follows the ["File Uploads" docs
page](https://formidable.com/open-source/urql/docs/advanced/persistence-and-uploads/)
and uses the [`trygql.formidable.dev/graphql/uploads-mock` schema](https://github.com/FormidableLabs/trygql).
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx)
- A basic file upload form in [`src/FileUpload.jsx`](src/FileUpload.jsx)
================================================
FILE: examples/with-multipart/index.html
================================================
with-multipart
================================================
FILE: examples/with-multipart/package.json
================================================
{
"name": "with-multipart",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite"
},
"dependencies": {
"@urql/core": "^6.0.1",
"graphql": "^16.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"urql": "^5.0.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-multipart/src/App.jsx
================================================
import React from 'react';
import { Client, Provider, fetchExchange } from 'urql';
import FileUpload from './FileUpload';
const client = new Client({
url: 'https://trygql.formidable.dev/graphql/uploads-mock',
exchanges: [fetchExchange],
});
function App() {
return (
);
}
export default App;
================================================
FILE: examples/with-multipart/src/FileUpload.jsx
================================================
import React, { useState } from 'react';
import { gql, useMutation } from 'urql';
const UPLOAD_FILE = gql`
mutation UploadFile($file: Upload!) {
uploadFile(file: $file) {
filename
}
}
`;
const FileUpload = () => {
const [selectedFile, setSelectedFile] = useState();
const [result, uploadFile] = useMutation(UPLOAD_FILE);
const { data, fetching, error } = result;
const handleFileUpload = () => {
uploadFile({ file: selectedFile });
};
const handleFileChange = event => {
setSelectedFile(event.target.files[0]);
};
return (
{fetching &&
Loading...
}
{error &&
Oh no... {error.message}
}
{data && data.uploadFile ? (
File uploaded to {data.uploadFile.filename}
) : (
Upload!
)}
);
};
export default FileUpload;
================================================
FILE: examples/with-multipart/src/index.jsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render();
================================================
FILE: examples/with-multipart/vite.config.js
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/with-next/README.md
================================================
# With Next.js
This example shows `next-urql` and `urql` in use with Next.js as explained [in the "Next.js" section
on the "Server-side Rendering" docs
page](https://formidable.com/open-source/urql/docs/advanced/server-side-rendering/#nextjs).
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
================================================
FILE: examples/with-next/app/layout.tsx
================================================
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
================================================
FILE: examples/with-next/app/non-rsc/layout.tsx
================================================
'use client';
import { useMemo } from 'react';
import {
UrqlProvider,
ssrExchange,
cacheExchange,
fetchExchange,
createClient,
} from '@urql/next';
export default function Layout({ children }: React.PropsWithChildren) {
const [client, ssr] = useMemo(() => {
const ssr = ssrExchange({
isClient: typeof window !== 'undefined',
});
const client = createClient({
url: 'https://graphql-pokeapi.graphcdn.app/',
exchanges: [cacheExchange, ssr, fetchExchange],
suspense: true,
});
return [client, ssr];
}, []);
return (
{children}
);
}
================================================
FILE: examples/with-next/app/non-rsc/page.tsx
================================================
'use client';
import Link from 'next/link';
import { Suspense } from 'react';
import { useQuery, gql } from '@urql/next';
export default function Page() {
return (
);
}
const PokemonsQuery = gql`
query {
pokemons(limit: 10) {
results {
id
name
}
}
}
`;
function Pokemons() {
const [result] = useQuery({ query: PokemonsQuery });
return (
This example shows how to implement pagination with `urql` in your React UI code.
It renders several pages as fragments with one component managing the variables
for the page queries. This example is also reapplicable to other libraries,
like Svelte or Vue.
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `urql` bindings and a React app with a client set up in [`src/App.js`](src/App.jsx)
- A managing component called `PaginatedNpmSearch` set up to render all pages in [`src/PaginatedNpmSearch.jss`](src/PaginatedNpmSearch.jsx)
- A page component called `SearchResultPage` running page queries in [`src/PaginatedNpmSearch.jsx`](src/PaginatedNpmSearch.jsx)
================================================
FILE: examples/with-pagination/index.html
================================================
with-pagination
================================================
FILE: examples/with-pagination/package.json
================================================
{
"name": "with-pagination",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite"
},
"dependencies": {
"@urql/core": "^6.0.1",
"graphql": "^16.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"urql": "^5.0.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-pagination/src/App.jsx
================================================
import React from 'react';
import { Client, Provider, cacheExchange, fetchExchange } from 'urql';
import PaginatedNpmSearch from './PaginatedNpmSearch';
const client = new Client({
url: 'https://trygql.formidable.dev/graphql/relay-npm',
exchanges: [cacheExchange, fetchExchange],
});
function App() {
return (
);
}
export default App;
================================================
FILE: examples/with-pagination/src/PaginatedNpmSearch.jsx
================================================
import React, { useState } from 'react';
import { gql, useQuery } from 'urql';
const limit = 5;
const query = 'graphql';
const NPM_SEARCH = gql`
query Search($query: String!, $first: Int!, $after: String) {
search(query: $query, first: $first, after: $after) {
nodes {
id
name
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
const SearchResultPage = ({ variables, onLoadMore, isLastPage }) => {
const [result] = useQuery({ query: NPM_SEARCH, variables });
const { data, fetching, error } = result;
const searchResults = data?.search;
return (
);
};
export default PaginatedNpmSearch;
================================================
FILE: examples/with-pagination/src/index.jsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render();
================================================
FILE: examples/with-pagination/vite.config.js
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/with-react/README.md
================================================
# With React
This example shows `urql` in use with React, as explained on the ["React/Preact" page of the "Basics"
documentation.](https://formidable.com/open-source/urql/docs/basics/react-preact/)
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx)
- A query for pokémon in [`src/PokemonList.jsx`](src/PokemonList.jsx)
================================================
FILE: examples/with-react/index.html
================================================
with-react
================================================
FILE: examples/with-react/package.json
================================================
{
"name": "with-react",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite"
},
"dependencies": {
"@urql/core": "^6.0.1",
"graphql": "^16.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"urql": "^5.0.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-react/src/App.jsx
================================================
import React from 'react';
import { Client, Provider, cacheExchange, fetchExchange } from 'urql';
import PokemonList from './PokemonList';
const client = new Client({
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
exchanges: [cacheExchange, fetchExchange],
});
function App() {
return (
);
}
export default App;
================================================
FILE: examples/with-react/src/PokemonList.jsx
================================================
import React from 'react';
import { gql, useQuery } from 'urql';
const POKEMONS_QUERY = gql`
query Pokemons {
pokemons(limit: 10) {
id
name
}
}
`;
const PokemonList = () => {
const [result] = useQuery({ query: POKEMONS_QUERY });
const { data, fetching, error } = result;
return (
{fetching &&
Loading...
}
{error &&
Oh no... {error.message}
}
{data && (
{data.pokemons.map(pokemon => (
{pokemon.name}
))}
)}
);
};
export default PokemonList;
================================================
FILE: examples/with-react/src/index.jsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render();
================================================
FILE: examples/with-react/vite.config.js
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/with-react-native/App.js
================================================
import React from 'react';
import { Client, Provider, cacheExchange, fetchExchange } from 'urql';
import PokemonList from './src/screens/PokemonList';
const client = new Client({
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
exchanges: [cacheExchange, fetchExchange],
});
const App = () => {
return (
);
};
export default App;
================================================
FILE: examples/with-react-native/README.md
================================================
# With React Native
This example shows `urql` in use with React Native.
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `urql` bindings and a React Native app with a client set up in [`App.js`](./App.js)
- A query for pokémon in [`src/screens/PokemonList.js`](src/screens/PokemonList.js)
================================================
FILE: examples/with-react-native/app.json
================================================
{
"name": "withReactNative",
"displayName": "withReactNative"
}
================================================
FILE: examples/with-react-native/index.js
================================================
/**
* @format
*/
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
AppRegistry.registerComponent(appName, () => App);
================================================
FILE: examples/with-react-native/package.json
================================================
{
"name": "with-react-native",
"version": "0.0.0",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start"
},
"dependencies": {
"@urql/core": "^6.0.1",
"graphql": "^16.6.0",
"react": "18.2.0",
"react-native": "0.71.4",
"urql": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/preset-env": "^7.20.2",
"@babel/runtime": "^7.12.5",
"metro-react-native-babel-preset": "^0.76.0"
}
}
================================================
FILE: examples/with-react-native/src/screens/PokemonList.js
================================================
import React from 'react';
import { SafeAreaView, StyleSheet, Text, FlatList, View } from 'react-native';
import { gql, useQuery } from 'urql';
const POKEMONS_QUERY = gql`
query Pokemons {
pokemons(limit: 10) {
id
name
}
}
`;
const Item = ({ name }) => (
{name}
);
const PokemonList = () => {
const [result] = useQuery({ query: POKEMONS_QUERY });
const { data, fetching, error } = result;
const renderItem = ({ item }) => ;
return (
{fetching && Loading...}
{error && Oh no... {error.message}}
item.id}
/>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
backgroundColor: '#dadada',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 20,
},
});
export default PokemonList;
================================================
FILE: examples/with-refresh-auth/README.md
================================================
# With Refresh Authentication
This example shows `urql` in use with `@urql/exchange-auth`'s `authExchange`
to support authentication token and refresh token logic. This largely follows the ["Authentication" docs
page](https://formidable.com/open-source/urql/docs/advanced/authentication/)
and uses the [`trygql.formidable.dev/graphql/web-collections` schema](https://github.com/FormidableLabs/trygql).
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `urql` bindings and a React app set up in [`src/App.jsx`](src/App.jsx)
- Some authentication glue code to store the tokens in [`src/authStore.js`](src/authStore.js)
- The `Client` and the `authExchange` from `@urql/exchange-auth` set up in [`src/client.js`](src/client.js)
- A basic login form in [`src/pages/LoginForm.jsx`](src/pages/LoginForm.jsx)
- And a basic login guard on [`src/App.jsx`](src/App.jsx)
(Note: This isn't using a query in this particular component, since this is just an example)
================================================
FILE: examples/with-refresh-auth/index.html
================================================
with-refresh-auth
================================================
FILE: examples/with-refresh-auth/package.json
================================================
{
"name": "with-refresh-auth",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite"
},
"dependencies": {
"@urql/core": "^6.0.1",
"@urql/exchange-auth": "^3.0.0",
"graphql": "^16.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"urql": "^5.0.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-refresh-auth/src/App.jsx
================================================
import React, { useState, useEffect } from 'react';
import { Provider } from 'urql';
import client from './client';
import { getToken, saveAuthData } from './authStore';
import Profile from './pages/Profile';
import LoginForm from './pages/LoginForm';
const Home = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const onLoginSuccess = auth => {
saveAuthData(auth);
setIsLoggedIn(true);
};
useEffect(() => {
if (getToken()) {
setIsLoggedIn(true);
}
}, []);
return isLoggedIn ? (
) : (
);
};
function App() {
return (
);
}
export default App;
================================================
FILE: examples/with-refresh-auth/src/authStore.js
================================================
const TOKEN_KEY = 'token';
const REFRESH_TOKEN_KEY = 'refresh_token';
export const saveAuthData = ({ token, refreshToken }) => {
localStorage.setItem(TOKEN_KEY, token);
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
};
export const getToken = () => {
return localStorage.getItem(TOKEN_KEY);
};
export const getRefreshToken = () => {
return localStorage.getItem(REFRESH_TOKEN_KEY);
};
export const clearStorage = () => {
localStorage.clear();
};
================================================
FILE: examples/with-refresh-auth/src/client.js
================================================
import { Client, fetchExchange, cacheExchange, gql } from 'urql';
import { authExchange } from '@urql/exchange-auth';
import {
getRefreshToken,
getToken,
saveAuthData,
clearStorage,
} from './authStore';
const REFRESH_TOKEN_MUTATION = gql`
mutation RefreshCredentials($refreshToken: String!) {
refreshCredentials(refreshToken: $refreshToken) {
refreshToken
token
}
}
`;
const auth = authExchange(async utilities => {
let token = getToken();
let refreshToken = getRefreshToken();
return {
addAuthToOperation(operation) {
return token
? utilities.appendHeaders(operation, {
Authorization: `Bearer ${token}`,
})
: operation;
},
didAuthError(error) {
return error.graphQLErrors.some(
e => e.extensions?.code === 'UNAUTHORIZED'
);
},
willAuthError(operation) {
// Sync tokens on every operation
token = getToken();
refreshToken = getRefreshToken();
if (!token) {
// Detect our login mutation and let this operation through:
return (
operation.kind !== 'mutation' ||
// Here we find any mutation definition with the "signin" field
!operation.query.definitions.some(definition => {
return (
definition.kind === 'OperationDefinition' &&
definition.selectionSet.selections.some(node => {
// The field name is just an example, since register may also be an exception
return node.kind === 'Field' && node.name.value === 'signin';
})
);
})
);
}
return false;
},
async refreshAuth() {
if (refreshToken) {
const result = await utilities.mutate(REFRESH_TOKEN_MUTATION, {
refreshToken,
});
if (result.data?.refreshCredentials) {
token = result.data.refreshCredentials.token;
refreshToken = result.data.refreshCredentials.refreshToken;
saveAuthData({ token, refreshToken });
return;
}
}
// This is where auth has gone wrong and we need to clean up and redirect to a login page
clearStorage();
window.location.reload();
},
};
});
const client = new Client({
url: 'https://trygql.formidable.dev/graphql/web-collections',
exchanges: [cacheExchange, auth, fetchExchange],
});
export default client;
================================================
FILE: examples/with-refresh-auth/src/index.jsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render();
================================================
FILE: examples/with-refresh-auth/src/pages/LoginForm.jsx
================================================
import React from 'react';
import { gql, useMutation } from 'urql';
const LOGIN_MUTATION = gql`
mutation Login($input: LoginInput!) {
signin(input: $input) {
refreshToken
token
}
}
`;
const REGISTER_MUTATION = gql`
mutation Register($input: LoginInput!) {
register(input: $input) {
refreshToken
token
}
}
`;
const LoginForm = ({ onLoginSuccess }) => {
const [loginResult, login] = useMutation(LOGIN_MUTATION);
const [registerResult, register] = useMutation(REGISTER_MUTATION);
const onSubmitLogin = event => {
event.preventDefault();
const data = new FormData(event.target);
const username = data.get('username');
const password = data.get('password');
login({ input: { username, password } }).then(result => {
if (!result.error && result.data && result.data.signin) {
onLoginSuccess(result.data.signin);
}
});
};
const onSubmitRegister = event => {
event.preventDefault();
const data = new FormData(event.target);
const username = data.get('username');
const password = data.get('password');
register({ input: { username, password } }).then(result => {
if (!result.error && result.data && result.data.register) {
onLoginSuccess(result.data.register);
}
});
};
const disabled = loginResult.fetching || registerResult.fetching;
return (
<>
>
);
};
export default LoginForm;
================================================
FILE: examples/with-refresh-auth/src/pages/Profile.jsx
================================================
import React from 'react';
import { gql, useQuery } from 'urql';
const PROFILE_QUERY = gql`
query Profile {
me {
id
username
createdAt
}
}
`;
const Profile = () => {
const [result] = useQuery({ query: PROFILE_QUERY });
const { data, fetching, error } = result;
return (
Integrating urql is as simple as:
1. Install packages
```sh
yarn add urql graphql
# or
npm install --save urql graphql
```
2. Add [retry exchange](https://formidable.com/open-source/urql/docs/advanced/retry-operations/)
```sh
yarn add @urql/exchange-retry
# or
npm install --save @urql/exchange-retry
```
3. Setting up the Client and adding the `retryExchange` [here](src/App.js)
4. Execute the Query [here](src/pages/Color.js)
# With Retry
This example shows `urql` in use with `@urql/exchange-retry`'s `retryExchange`
to implement retrying failed operations. This largely follows the ["Retrying Operations" docs
page](https://formidable.com/open-source/urql/docs/advanced/retry-operations/)
and uses the [`trygql.formidable.dev/graphql/intermittent-colors`
schema](https://github.com/FormidableLabs/trygql), which emits a special `NO_SOUP` error randomly.
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `urql` bindings and a React app with a client set up in [`src/App.jsx`](src/App.jsx)
- The `retryExchange` from `@urql/exchange-retry` in [`src/App.jsx`](src/App.jsx)
- A random colour query in [`src/Color.jsx`](src/pages/Color.jsx)
================================================
FILE: examples/with-retry/index.html
================================================
with-retry
================================================
FILE: examples/with-retry/package.json
================================================
{
"name": "with-retry",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite"
},
"dependencies": {
"@urql/core": "^6.0.1",
"@urql/exchange-retry": "^2.0.0",
"graphql": "^16.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"urql": "^5.0.1"
},
"devDependencies": {
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-retry/src/App.jsx
================================================
import React from 'react';
import { Client, fetchExchange, Provider } from 'urql';
import { retryExchange } from '@urql/exchange-retry';
import Color from './Color';
const client = new Client({
url: 'https://trygql.formidable.dev/graphql/intermittent-colors',
exchanges: [
retryExchange({
maxNumberAttempts: 10,
maxDelayMs: 500,
retryIf: error => {
// NOTE: With this deemo schema we have a specific random error to look out for:
return (
error.graphQLErrors.some(x => x.extensions?.code === 'NO_SOUP') ||
!!error.networkError
);
},
}),
fetchExchange,
],
});
function App() {
return (
);
}
export default App;
================================================
FILE: examples/with-retry/src/Color.jsx
================================================
import React from 'react';
import { gql, useQuery } from 'urql';
const RANDOM_COLOR_QUERY = gql`
query RandomColor {
randomColor {
name
hex
}
}
`;
const RandomColorDisplay = () => {
const [result] = useQuery({ query: RANDOM_COLOR_QUERY });
const { data, fetching, error } = result;
return (
{fetching &&
Loading...
}
{error &&
Oh no... {error.message}
}
{data && (
{data.randomColor.name}
)}
{result.operation && (
To get a result, the retry exchange retried:{' '}
{result.operation.context.retryCount || 0} times.
)}
);
};
export default RandomColorDisplay;
================================================
FILE: examples/with-retry/src/index.jsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render();
================================================
FILE: examples/with-retry/vite.config.js
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/with-solid/.eslintrc.js
================================================
module.exports = {
rules: {
'react/react-in-jsx-scope': 'off',
},
};
================================================
FILE: examples/with-solid/README.md
================================================
# URQL with Solid
This example demonstrates how to use URQL with Solid.js.
## Features
- Basic query with `createQuery`
- Client setup with `Provider`
- Suspense integration
## Getting Started
```bash
pnpm install
pnpm start
```
Then open [http://localhost:5173](http://localhost:5173) in your browser.
## What's Inside
- `src/App.jsx` - Sets up the URQL client and provider
- `src/PokemonList.jsx` - Demonstrates `createQuery` with Suspense
## Learn More
- [Solid Documentation](https://www.solidjs.com/)
- [URQL Solid Documentation](https://formidable.com/open-source/urql/docs/basics/solid/)
================================================
FILE: examples/with-solid/index.html
================================================
URQL with Solid
================================================
FILE: examples/with-solid/package.json
================================================
{
"name": "with-solid",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite",
"build": "vite build"
},
"dependencies": {
"@urql/core": "^6.0.1",
"@urql/solid": "^1.0.1",
"graphql": "^16.9.0",
"solid-js": "^1.9.10"
},
"devDependencies": {
"vite": "^7.3.1",
"vite-plugin-solid": "^2.11.10"
}
}
================================================
FILE: examples/with-solid/src/App.jsx
================================================
/** @jsxImportSource solid-js */
import {
createClient,
Provider,
cacheExchange,
fetchExchange,
} from '@urql/solid';
import PokemonList from './PokemonList';
const client = createClient({
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
exchanges: [cacheExchange, fetchExchange],
});
function App() {
return (
);
}
export default App;
================================================
FILE: examples/with-solid/src/PokemonList.jsx
================================================
/** @jsxImportSource solid-js */
import { Suspense, For } from 'solid-js';
import { gql } from '@urql/core';
import { createQuery } from '@urql/solid';
const POKEMONS_QUERY = gql`
query Pokemons {
pokemons(limit: 10) {
id
name
}
}
`;
const PokemonList = () => {
const [result] = createQuery({ query: POKEMONS_QUERY });
return (
Pokemon List
Loading...}>
{pokemon =>
{pokemon.name}
}
);
};
export default PokemonList;
================================================
FILE: examples/with-solid/src/index.jsx
================================================
/** @jsxImportSource solid-js */
import { render } from 'solid-js/web';
import App from './App';
render(() => , document.getElementById('root'));
================================================
FILE: examples/with-solid/vite.config.js
================================================
import { defineConfig } from 'vite';
import solid from 'vite-plugin-solid';
export default defineConfig({
plugins: [solid()],
});
================================================
FILE: examples/with-solid-start/.gitignore
================================================
/.output
/.vinxi
.DS_Store
================================================
FILE: examples/with-solid-start/README.md
================================================
# URQL with SolidStart
This example demonstrates how to use URQL with SolidStart.
## Features
- Basic query with `createQuery`
- Client setup with `Provider`
- SSR with automatic hydration
- Route-level preloading
- Suspense integration
## Getting Started
```bash
pnpm install
pnpm start
```
Then open [http://localhost:3000](http://localhost:3000) in your browser.
## What's Inside
- `src/app.tsx` - Sets up the URQL client and router
- `src/routes/index.tsx` - Demonstrates `createQuery` with preloading and Suspense
## Key Features Demonstrated
### Route Preloading
The example uses SolidStart's `preload` function to start fetching data before the route component renders:
```tsx
export const route = {
preload: () => {
const pokemons = createQuery({ query: POKEMONS_QUERY });
return pokemons(); // Start fetching
},
} satisfies RouteDefinition;
```
### Server-Side Rendering
Queries automatically execute on the server during SSR and hydrate on the client without refetching.
## Learn More
- [SolidStart Documentation](https://start.solidjs.com/)
- [URQL SolidStart Documentation](https://formidable.com/open-source/urql/docs/basics/solid-start/)
================================================
FILE: examples/with-solid-start/app.config.ts
================================================
import { defineConfig } from '@solidjs/start/config';
export default defineConfig({});
================================================
FILE: examples/with-solid-start/package.json
================================================
{
"name": "with-solid-start",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "vinxi dev",
"build": "vinxi build"
},
"dependencies": {
"@solidjs/router": "^0.15.4",
"@solidjs/start": "^1.2.1",
"@urql/core": "^6.0.1",
"@urql/solid-start": "^0.1.0",
"graphql": "^16.9.0",
"solid-js": "^1.9.10",
"vinxi": "^0.5.0"
},
"devDependencies": {
"vite": "^7.3.1",
"vite-plugin-solid": "^2.11.10"
}
}
================================================
FILE: examples/with-solid-start/src/app.tsx
================================================
import { Router, action, query } from '@solidjs/router';
import { FileRoutes } from '@solidjs/start/router';
import { Suspense } from 'solid-js';
import {
createClient,
Provider,
cacheExchange,
fetchExchange,
} from '@urql/solid-start';
const client = createClient({
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
exchanges: [cacheExchange, fetchExchange],
});
export default function App() {
return (
(
{props.children}
)}
>
);
}
================================================
FILE: examples/with-solid-start/src/entry-client.tsx
================================================
// @refresh reload
import { mount, StartClient } from '@solidjs/start/client';
mount(() => , document.getElementById('app')!);
================================================
FILE: examples/with-solid-start/src/entry-server.tsx
================================================
// @refresh reload
import { createHandler, StartServer } from '@solidjs/start/server';
export default createHandler(() => (
(
URQL with SolidStart
{assets}
{children}
{scripts}
)}
/>
));
================================================
FILE: examples/with-solid-start/src/routes/index.tsx
================================================
import { Suspense, For, Show, createSignal } from 'solid-js';
import { createAsync, useAction, useSubmission } from '@solidjs/router';
import { gql } from '@urql/core';
import { createQuery, createMutation } from '@urql/solid-start';
const POKEMONS_QUERY = gql`
query Pokemons {
pokemons(limit: 10) {
id
name
}
}
`;
const ADD_POKEMON_MUTATION = gql`
mutation AddPokemon($name: String!) {
addPokemon(name: $name) {
id
name
}
}
`;
export default function Home() {
const queryPokemons = createQuery(POKEMONS_QUERY, 'list-pokemons');
const result = createAsync(() => queryPokemons());
// Create the mutation action inside the component where it has access to context
const addPokemonAction = createMutation(ADD_POKEMON_MUTATION, 'add-pokemon');
const addPokemon = useAction(addPokemonAction);
const submission = useSubmission(addPokemonAction);
const [pokemonName, setPokemonName] = createSignal('');
const handleSubmit = async (e: Event) => {
e.preventDefault();
const name = pokemonName();
if (!name) return;
const result = await addPokemon({ name });
if (result.data) {
setPokemonName('');
// Note: In a real app, you'd want to refetch or update the cache
}
};
return (
This example shows `@urql/svelte` in use with Svelte, as explained on the ["Svelte" page of the "Basics"
documentation.](https://formidable.com/open-source/urql/docs/basics/svelte/)
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `@urql/svelte` bindings with a client set up in [`src/App.svelte`](src/App.svelte)
- A query for pokémon in [`src/PokemonList.svelte`](src/pages/PokemonList.svelte)
================================================
FILE: examples/with-svelte/index.html
================================================
with-svelte
================================================
FILE: examples/with-svelte/package.json
================================================
{
"name": "with-svelte",
"private": true,
"version": "0.0.0",
"scripts": {
"start": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"@urql/core": "^6.0.1",
"@urql/svelte": "^5.0.0",
"graphql": "^16.6.0",
"svelte": "^4.0.5"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-svelte/src/App.svelte
================================================
================================================
FILE: examples/with-svelte/src/PokemonList.svelte
================================================
{#if $pokemons.fetching}
Loading...
{:else if $pokemons.error}
Oh no... {$pokemons.error.message}
{:else}
{#each $pokemons.data.pokemons as pokemon}
{pokemon.name}
{/each}
{/if}
Next PageReexec
================================================
FILE: examples/with-svelte/src/main.js
================================================
import App from './App.svelte';
var app = new App({
target: document.body,
});
export default app;
================================================
FILE: examples/with-svelte/vite.config.mjs
================================================
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
});
================================================
FILE: examples/with-vue3/.gitignore
================================================
node_modules
.DS_Store
dist
*.local
================================================
FILE: examples/with-vue3/README.md
================================================
# With Vue 3
This example shows `@urql/vue` in use with Vue 3, as explained on the ["Vue" page of the "Basics"
documentation.](https://formidable.com/open-source/urql/docs/basics/vue/)
To run this example install dependencies and run the `start` script:
```sh
yarn install
yarn run start
# or
npm install
npm run start
```
This example contains:
- The `@urql/vue` bindings with a client set up in [`src/App.vue`](src/App.vue)
- A suspense loading boundary in the `App` component in [`src/App.vue`](src/App.vue)
- A query for pokémon in [`src/PokemonList.vue`](src/pages/PokemonList.vue)
================================================
FILE: examples/with-vue3/index.html
================================================
Vite App
================================================
FILE: examples/with-vue3/package.json
================================================
{
"name": "with-vue3",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"@urql/core": "^6.0.1",
"@urql/vue": "^2.0.0",
"graphql": "^16.6.0",
"vue": "^3.2.47"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
"vite": "^4.2.0"
}
}
================================================
FILE: examples/with-vue3/src/App.vue
================================================
================================================
FILE: examples/with-vue3/src/main.js
================================================
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
================================================
FILE: examples/with-vue3/vite.config.js
================================================
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
});
================================================
FILE: exchanges/auth/CHANGELOG.md
================================================
# Changelog
## 3.0.0
### Patch Changes
- Updated dependencies (See [#3789](https://github.com/urql-graphql/urql/pull/3789) and [#3807](https://github.com/urql-graphql/urql/pull/3807))
- @urql/core@6.0.0
## 2.2.1
### Patch Changes
- Omit minified files and sourcemaps' `sourcesContent` in published packages
Submitted by [@kitten](https://github.com/kitten) (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- Updated dependencies (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- @urql/core@5.1.1
## 2.2.0
### Minor Changes
- Mark `@urql/core` as a peer dependency as well as a regular dependency
Submitted by [@kitten](https://github.com/kitten) (See [#3579](https://github.com/urql-graphql/urql/pull/3579))
## 2.1.6
### Patch Changes
- `authExchange()` will now block and pass on errors if the initialization function passed to it fails, and will retry indefinitely. It’ll also output a warning for these cases, as the initialization function (i.e. `authExchange(async (utils) => { /*...*/ })`) is not expected to reject/throw
Submitted by [@kitten](https://github.com/kitten) (See [#3343](https://github.com/urql-graphql/urql/pull/3343))
## 2.1.5
### Patch Changes
- Handle `refreshAuth` rejections and pass the resulting error on to `OperationResult`s on the authentication queue
Submitted by [@kitten](https://github.com/kitten) (See [#3307](https://github.com/urql-graphql/urql/pull/3307))
## 2.1.4
### Patch Changes
- ⚠️ Fix regression that caused teardowns to be ignored by an `authExchange`’s retry queue
Submitted by [@kitten](https://github.com/kitten) (See [#3235](https://github.com/urql-graphql/urql/pull/3235))
## 2.1.3
### Patch Changes
- Update build process to generate correct source maps
Submitted by [@kitten](https://github.com/kitten) (See [#3201](https://github.com/urql-graphql/urql/pull/3201))
## 2.1.2
### Patch Changes
- Publish with npm provenance
Submitted by [@kitten](https://github.com/kitten) (See [#3180](https://github.com/urql-graphql/urql/pull/3180))
## 2.1.1
### Patch Changes
- ⚠️ Fix operations created by `utilities.mutate()` erroneously being retried and sent again like a regular operation
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3164](https://github.com/urql-graphql/urql/pull/3164))
## 2.1.0
### Minor Changes
- Update exchanges to drop redundant `share` calls, since `@urql/core`’s `composeExchanges` utility now automatically does so for us
Submitted by [@kitten](https://github.com/kitten) (See [#3082](https://github.com/urql-graphql/urql/pull/3082))
### Patch Changes
- ⚠️ Fix source maps included with recently published packages, which lost their `sourcesContent`, including additional source files, and had incorrect paths in some of them
Submitted by [@kitten](https://github.com/kitten) (See [#3053](https://github.com/urql-graphql/urql/pull/3053))
- Upgrade to `wonka@^6.3.0`
Submitted by [@kitten](https://github.com/kitten) (See [#3104](https://github.com/urql-graphql/urql/pull/3104))
- Avoid infinite loop when `didAuthError` keeps returning true
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3112](https://github.com/urql-graphql/urql/pull/3112))
- Updated dependencies (See [#3101](https://github.com/urql-graphql/urql/pull/3101), [#3033](https://github.com/urql-graphql/urql/pull/3033), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3053](https://github.com/urql-graphql/urql/pull/3053), [#3060](https://github.com/urql-graphql/urql/pull/3060), [#3081](https://github.com/urql-graphql/urql/pull/3081), [#3039](https://github.com/urql-graphql/urql/pull/3039), [#3104](https://github.com/urql-graphql/urql/pull/3104), [#3082](https://github.com/urql-graphql/urql/pull/3082), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3061](https://github.com/urql-graphql/urql/pull/3061), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3085](https://github.com/urql-graphql/urql/pull/3085), [#3079](https://github.com/urql-graphql/urql/pull/3079), [#3087](https://github.com/urql-graphql/urql/pull/3087), [#3059](https://github.com/urql-graphql/urql/pull/3059), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3057](https://github.com/urql-graphql/urql/pull/3057), [#3050](https://github.com/urql-graphql/urql/pull/3050), [#3062](https://github.com/urql-graphql/urql/pull/3062), [#3051](https://github.com/urql-graphql/urql/pull/3051), [#3043](https://github.com/urql-graphql/urql/pull/3043), [#3063](https://github.com/urql-graphql/urql/pull/3063), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3102](https://github.com/urql-graphql/urql/pull/3102), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3106](https://github.com/urql-graphql/urql/pull/3106), [#3058](https://github.com/urql-graphql/urql/pull/3058), and [#3062](https://github.com/urql-graphql/urql/pull/3062))
- @urql/core@4.0.0
## 2.0.0
### Major Changes
- Implement new `authExchange` API, which removes the need for an `authState` (i.e. an internal authentication state) and removes `getAuth`, replacing it with a separate `refreshAuth` flow.
The new API requires you to now pass an initializer function. This function receives a `utils`
object with `utils.mutate` and `utils.appendHeaders` utility methods.
It must return the configuration object, wrapped in a promise, and this configuration is similar to
what we had before, if you're migrating to this. Its `refreshAuth` method is now only called after
authentication errors occur and not on initialization. Instead, it's now recommended that you write
your initialization logic in-line.
```js
authExchange(async utils => {
let token = localStorage.getItem('token');
let refreshToken = localStorage.getItem('refreshToken');
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: `Bearer ${token}`,
});
},
didAuthError(error) {
return error.graphQLErrors.some(
e => e.extensions?.code === 'FORBIDDEN'
);
},
async refreshAuth() {
const result = await utils.mutate(REFRESH, { token });
if (result.data?.refreshLogin) {
token = result.data.refreshLogin.token;
refreshToken = result.data.refreshLogin.refreshToken;
localStorage.setItem('token', token);
localStorage.setItem('refreshToken', refreshToken);
}
},
};
});
```
Submitted by [@kitten](https://github.com/kitten) (See [#3012](https://github.com/urql-graphql/urql/pull/3012))
### Patch Changes
- ⚠️ Fix `willAuthError` not being called for operations that are waiting on the authentication state to update. This can actually lead to a common issue where operations that came in during the authentication initialization (on startup) will never have `willAuthError` called on them. This can cause an easy mistake where the initial authentication state is never checked to be valid
Submitted by [@kitten](https://github.com/kitten) (See [#3017](https://github.com/urql-graphql/urql/pull/3017))
- Updated dependencies (See [#3007](https://github.com/urql-graphql/urql/pull/3007), [#2962](https://github.com/urql-graphql/urql/pull/2962), [#3007](https://github.com/urql-graphql/urql/pull/3007), [#3015](https://github.com/urql-graphql/urql/pull/3015), and [#3022](https://github.com/urql-graphql/urql/pull/3022))
- @urql/core@3.2.0
## 1.0.0
### Major Changes
- **Goodbye IE11!** 👋 This major release removes support for IE11. All code that is shipped will be transpiled much less and will _not_ be ES5-compatible anymore, by [@kitten](https://github.com/kitten) (See [#2504](https://github.com/FormidableLabs/urql/pull/2504))
- Upgrade to [Wonka v6](https://github.com/0no-co/wonka) (`wonka@^6.0.0`), which has no breaking changes but is built to target ES2015 and comes with other minor improvements.
The library has fully been migrated to TypeScript which will hopefully help with making contributions easier!, by [@kitten](https://github.com/kitten) (See [#2504](https://github.com/FormidableLabs/urql/pull/2504))
### Minor Changes
- Remove the `babel-plugin-modular-graphql` helper, this because the graphql package hasn't converted to ESM yet which gives issues in node environments, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2551](https://github.com/FormidableLabs/urql/pull/2551))
### Patch Changes
- Updated dependencies (See [#2551](https://github.com/FormidableLabs/urql/pull/2551), [#2504](https://github.com/FormidableLabs/urql/pull/2504), [#2619](https://github.com/FormidableLabs/urql/pull/2619), [#2607](https://github.com/FormidableLabs/urql/pull/2607), and [#2504](https://github.com/FormidableLabs/urql/pull/2504))
- @urql/core@3.0.0
## 0.1.7
### Patch Changes
- Extend peer dependency range of `graphql` to include `^16.0.0`.
As always when upgrading across many packages of `urql`, especially including `@urql/core` we recommend you to deduplicate dependencies after upgrading, using `npm dedupe` or `npx yarn-deduplicate`, by [@kitten](https://github.com/kitten) (See [#2133](https://github.com/FormidableLabs/urql/pull/2133))
- Updated dependencies (See [#2133](https://github.com/FormidableLabs/urql/pull/2133))
- @urql/core@2.3.6
## 0.1.6
### Patch Changes
- ⚠️ Fix willAuthError causing duplicate operations, by [@yankovalera](https://github.com/yankovalera) (See [#1849](https://github.com/FormidableLabs/urql/pull/1849))
- Updated dependencies (See [#1851](https://github.com/FormidableLabs/urql/pull/1851), [#1850](https://github.com/FormidableLabs/urql/pull/1850), and [#1852](https://github.com/FormidableLabs/urql/pull/1852))
- @urql/core@2.2.0
## 0.1.5
### Patch Changes
- Expose `AuthContext` type, by [@arempe93](https://github.com/arempe93) (See [#1828](https://github.com/FormidableLabs/urql/pull/1828))
- Updated dependencies (See [#1829](https://github.com/FormidableLabs/urql/pull/1829))
- @urql/core@2.1.6
## 0.1.4
### Patch Changes
- Allow `mutate` to infer the result's type when a `TypedDocumentNode` is passed via the usual generics, like `client.mutation` for instance, by [@younesmln](https://github.com/younesmln) (See [#1796](https://github.com/FormidableLabs/urql/pull/1796))
## 0.1.3
### Patch Changes
- Remove closure-compiler from the build step (See [#1570](https://github.com/FormidableLabs/urql/pull/1570))
- Updated dependencies (See [#1570](https://github.com/FormidableLabs/urql/pull/1570), [#1509](https://github.com/FormidableLabs/urql/pull/1509), [#1600](https://github.com/FormidableLabs/urql/pull/1600), and [#1515](https://github.com/FormidableLabs/urql/pull/1515))
- @urql/core@2.1.0
## 0.1.2
### Patch Changes
- Deprecate the `Operation.operationName` property in favor of `Operation.kind`. This name was
previously confusing as `operationName` was effectively referring to two different things. You can
safely upgrade to this new version, however to mute all deprecation warnings you will have to
**upgrade** all `urql` packages you use. If you have custom exchanges that spread operations, please
use [the new `makeOperation` helper
function](https://formidable.com/open-source/urql/docs/api/core/#makeoperation) instead, by [@bkonkle](https://github.com/bkonkle) (See [#1045](https://github.com/FormidableLabs/urql/pull/1045))
- Updated dependencies (See [#1094](https://github.com/FormidableLabs/urql/pull/1094) and [#1045](https://github.com/FormidableLabs/urql/pull/1045))
- @urql/core@1.14.0
## 0.1.1
### Patch Changes
- ⚠️ Fix an operation that triggers `willAuthError` with a truthy return value being sent off twice, by [@kitten](https://github.com/kitten) (See [#1075](https://github.com/FormidableLabs/urql/pull/1075))
## v0.1.0
**Initial Release**
================================================
FILE: exchanges/auth/README.md
================================================
@urql/exchange-auth
An exchange for managing authentication in urql
`@urql/exchange-auth` is an exchange for the [`urql`](https://github.com/urql-graphql/urql) GraphQL client which helps handle auth headers and token refresh
## Quick Start Guide
First install `@urql/exchange-auth` alongside `urql`:
```sh
yarn add @urql/exchange-auth
# or
npm install --save @urql/exchange-auth
```
You'll then need to add the `authExchange`, that this package exposes to your `urql` Client
```js
import { createClient, cacheExchange, fetchExchange } from 'urql';
import { makeOperation } from '@urql/core';
import { authExchange } from '@urql/exchange-auth';
const client = createClient({
url: 'http://localhost:1234/graphql',
exchanges: [
cacheExchange,
authExchange(async utils => {
// called on initial launch,
// fetch the auth state from storage (local storage, async storage etc)
let token = localStorage.getItem('token');
let refreshToken = localStorage.getItem('refreshToken');
return {
addAuthToOperation(operation) {
if (token) {
return utils.appendHeaders(operation, {
Authorization: `Bearer ${token}`,
});
}
return operation;
},
willAuthError(_operation) {
// e.g. check for expiration, existence of auth etc
return !token;
},
didAuthError(error, _operation) {
// check if the error was an auth error
// this can be implemented in various ways, e.g. 401 or a special error code
return error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
},
async refreshAuth() {
// called when auth error has occurred
// we should refresh the token with a GraphQL mutation or a fetch call,
// depending on what the API supports
const result = await mutate(refreshMutation, {
token: authState?.refreshToken,
});
if (result.data?.refreshLogin) {
// save the new tokens in storage for next restart
token = result.data.refreshLogin.token;
refreshToken = result.data.refreshLogin.refreshToken;
localStorage.setItem('token', token);
localStorage.setItem('refreshToken', refreshToken);
} else {
// otherwise, if refresh fails, log clear storage and log out
localStorage.clear();
logout();
}
},
};
}),
fetchExchange,
],
});
```
## Handling Errors via the errorExchange
Handling the logout logic in `refreshAuth` is the easiest way to get started,
but it means the errors will always get swallowed by the `authExchange`.
If you want to handle errors globally, this can be done using the `mapExchange`:
```js
import { mapExchange } from 'urql';
// this needs to be placed ABOVE the authExchange in the exchanges array, otherwise the auth error
// will show up hear before the auth exchange has had the chance to handle it
mapExchange({
onError(error) {
// we only get an auth error here when the auth exchange had attempted to refresh auth and
// getting an auth error again for the second time
const isAuthError = error.graphQLErrors.some(
e => e.extensions?.code === 'FORBIDDEN',
);
if (isAuthError) {
// clear storage, log the user out etc
}
}
}),
```
================================================
FILE: exchanges/auth/jsr.json
================================================
{
"name": "@urql/exchange-auth",
"version": "3.0.0",
"exports": {
".": "./src/index.ts"
},
"exclude": [
"node_modules",
"cypress",
"**/*.test.*",
"**/*.spec.*",
"**/*.test.*.snap",
"**/*.spec.*.snap"
]
}
================================================
FILE: exchanges/auth/package.json
================================================
{
"name": "@urql/exchange-auth",
"version": "3.0.0",
"description": "An exchange for managing authentication and token refresh in urql",
"sideEffects": false,
"homepage": "https://formidable.com/open-source/urql/docs/",
"bugs": "https://github.com/urql-graphql/urql/issues",
"license": "MIT",
"author": "urql GraphQL Contributors",
"repository": {
"type": "git",
"url": "https://github.com/urql-graphql/urql.git",
"directory": "exchanges/auth"
},
"keywords": [
"urql",
"exchange",
"auth",
"authentication",
"graphql",
"exchanges"
],
"main": "dist/urql-exchange-auth",
"module": "dist/urql-exchange-auth.mjs",
"types": "dist/urql-exchange-auth.d.ts",
"source": "src/index.ts",
"exports": {
".": {
"types": "./dist/urql-exchange-auth.d.ts",
"import": "./dist/urql-exchange-auth.mjs",
"require": "./dist/urql-exchange-auth.js",
"source": "./src/index.ts"
},
"./package.json": "./package.json"
},
"files": [
"LICENSE",
"CHANGELOG.md",
"README.md",
"dist/"
],
"scripts": {
"test": "vitest",
"clean": "rimraf dist extras",
"check": "tsc --noEmit",
"lint": "eslint --ext=js,jsx,ts,tsx .",
"build": "rollup -c ../../scripts/rollup/config.mjs",
"prepare": "node ../../scripts/prepare/index.js",
"prepublishOnly": "run-s clean build"
},
"peerDependencies": {
"@urql/core": "^6.0.0"
},
"dependencies": {
"@urql/core": "workspace:^6.0.1",
"wonka": "^6.3.2"
},
"devDependencies": {
"@urql/core": "workspace:*",
"graphql": "^16.0.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
================================================
FILE: exchanges/auth/src/authExchange.test.ts
================================================
import {
Source,
pipe,
fromValue,
toPromise,
take,
makeSubject,
share,
publish,
scan,
tap,
map,
} from 'wonka';
import {
makeOperation,
CombinedError,
Client,
Operation,
OperationResult,
} from '@urql/core';
import { vi, expect, it } from 'vitest';
import { print } from 'graphql';
import {
queryResponse,
queryOperation,
} from '../../../packages/core/src/test-utils';
import { authExchange } from './authExchange';
const makeExchangeArgs = () => {
const operations: Operation[] = [];
const result = vi.fn(
(operation: Operation): OperationResult => ({ ...queryResponse, operation })
);
return {
operations,
result,
exchangeArgs: {
forward: (op$: Source) =>
pipe(
op$,
tap(op => operations.push(op)),
map(result),
share
),
client: new Client({
url: '/api',
exchanges: [],
}),
} as any,
};
};
it('adds the auth header correctly', async () => {
const { exchangeArgs } = makeExchangeArgs();
const res = await pipe(
fromValue(queryOperation),
authExchange(async utils => {
const token = 'my-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
didAuthError: () => false,
async refreshAuth() {
/*noop*/
},
};
})(exchangeArgs),
take(1),
toPromise
);
expect(res.operation.context.authAttempt).toBe(false);
expect(res.operation.context.fetchOptions).toEqual({
...(queryOperation.context.fetchOptions || {}),
headers: {
Authorization: 'my-token',
},
});
});
it('adds the auth header correctly when intialized asynchronously', async () => {
const { exchangeArgs } = makeExchangeArgs();
const res = await pipe(
fromValue(queryOperation),
authExchange(async utils => {
// delayed initial auth
await Promise.resolve();
const token = 'async-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
didAuthError: () => false,
async refreshAuth() {
/*noop*/
},
};
})(exchangeArgs),
take(1),
toPromise
);
expect(res.operation.context.authAttempt).toBe(false);
expect(res.operation.context.fetchOptions).toEqual({
...(queryOperation.context.fetchOptions || {}),
headers: {
Authorization: 'async-token',
},
});
});
it('supports calls to the mutate() method in refreshAuth()', async () => {
const { exchangeArgs } = makeExchangeArgs();
const willAuthError = vi
.fn()
.mockReturnValueOnce(true)
.mockReturnValue(false);
const [mutateRes, res] = await pipe(
fromValue(queryOperation),
authExchange(async utils => {
const token = 'async-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
willAuthError,
didAuthError: () => false,
async refreshAuth() {
const result = await utils.mutate('mutation { auth }', undefined);
expect(print(result.operation.query)).toBe('mutation {\n auth\n}');
},
};
})(exchangeArgs),
take(2),
scan((acc, res) => [...acc, res], [] as OperationResult[]),
toPromise
);
expect(mutateRes.operation.context.fetchOptions).toEqual({
headers: {
Authorization: 'async-token',
},
});
expect(res.operation.context.authAttempt).toBe(true);
expect(res.operation.context.fetchOptions).toEqual({
method: 'POST',
headers: {
Authorization: 'async-token',
},
});
});
it('adds the same token to subsequent operations', async () => {
const { exchangeArgs } = makeExchangeArgs();
const { source, next } = makeSubject();
const result = vi.fn();
const auth$ = pipe(
source,
authExchange(async utils => {
const token = 'my-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
didAuthError: () => false,
async refreshAuth() {
/*noop*/
},
};
})(exchangeArgs),
tap(result),
take(2),
toPromise
);
await new Promise(resolve => setTimeout(resolve));
next(queryOperation);
next(
makeOperation('query', queryOperation, {
...queryOperation.context,
foo: 'bar',
})
);
await auth$;
expect(result).toHaveBeenCalledTimes(2);
expect(result.mock.calls[0][0].operation.context.authAttempt).toBe(false);
expect(result.mock.calls[0][0].operation.context.fetchOptions).toEqual({
...(queryOperation.context.fetchOptions || {}),
headers: {
Authorization: 'my-token',
},
});
expect(result.mock.calls[1][0].operation.context.authAttempt).toBe(false);
expect(result.mock.calls[1][0].operation.context.fetchOptions).toEqual({
...(queryOperation.context.fetchOptions || {}),
headers: {
Authorization: 'my-token',
},
});
});
it('triggers authentication when an operation did error', async () => {
const { exchangeArgs, result, operations } = makeExchangeArgs();
const { source, next } = makeSubject();
const didAuthError = vi.fn().mockReturnValueOnce(true);
pipe(
source,
authExchange(async utils => {
let token = 'initial-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
didAuthError,
async refreshAuth() {
token = 'final-token';
},
};
})(exchangeArgs),
publish
);
await new Promise(resolve => setTimeout(resolve));
result.mockReturnValueOnce({
...queryResponse,
operation: queryOperation,
data: undefined,
error: new CombinedError({
graphQLErrors: [{ message: 'Oops' }],
}),
});
next(queryOperation);
expect(result).toHaveBeenCalledTimes(1);
expect(didAuthError).toHaveBeenCalledTimes(1);
await new Promise(resolve => setTimeout(resolve));
expect(result).toHaveBeenCalledTimes(2);
expect(operations.length).toBe(2);
expect(operations[0]).toHaveProperty(
'context.fetchOptions.headers.Authorization',
'initial-token'
);
expect(operations[1]).toHaveProperty(
'context.fetchOptions.headers.Authorization',
'final-token'
);
});
it('triggers authentication when an operation will error', async () => {
const { exchangeArgs, result, operations } = makeExchangeArgs();
const { source, next } = makeSubject();
const willAuthError = vi
.fn()
.mockReturnValueOnce(true)
.mockReturnValue(false);
pipe(
source,
authExchange(async utils => {
let token = 'initial-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
willAuthError,
didAuthError: () => false,
async refreshAuth() {
token = 'final-token';
},
};
})(exchangeArgs),
publish
);
await new Promise(resolve => setTimeout(resolve));
next(queryOperation);
expect(result).toHaveBeenCalledTimes(0);
expect(willAuthError).toHaveBeenCalledTimes(1);
await new Promise(resolve => setTimeout(resolve));
expect(result).toHaveBeenCalledTimes(1);
expect(operations.length).toBe(1);
expect(operations[0]).toHaveProperty(
'context.fetchOptions.headers.Authorization',
'final-token'
);
});
it('calls willAuthError on queued operations', async () => {
const { exchangeArgs, result, operations } = makeExchangeArgs();
const { source, next } = makeSubject();
let initialAuthResolve: ((_?: any) => void) | undefined;
const willAuthError = vi
.fn()
.mockReturnValueOnce(true)
.mockReturnValue(false);
pipe(
source,
authExchange(async utils => {
await new Promise(resolve => {
initialAuthResolve = resolve;
});
let token = 'token';
return {
willAuthError,
didAuthError: () => false,
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
async refreshAuth() {
token = 'final-token';
},
};
})(exchangeArgs),
publish
);
await Promise.resolve();
next({ ...queryOperation, key: 1 });
next({ ...queryOperation, key: 2 });
expect(result).toHaveBeenCalledTimes(0);
expect(willAuthError).toHaveBeenCalledTimes(0);
expect(initialAuthResolve).toBeDefined();
initialAuthResolve!();
await new Promise(resolve => setTimeout(resolve));
expect(willAuthError).toHaveBeenCalledTimes(2);
expect(result).toHaveBeenCalledTimes(2);
expect(operations.length).toBe(2);
expect(operations[0]).toHaveProperty(
'context.fetchOptions.headers.Authorization',
'final-token'
);
expect(operations[1]).toHaveProperty(
'context.fetchOptions.headers.Authorization',
'final-token'
);
});
it('does not infinitely retry authentication when an operation did error', async () => {
const { exchangeArgs, result, operations } = makeExchangeArgs();
const { source, next } = makeSubject();
const didAuthError = vi.fn().mockReturnValue(true);
pipe(
source,
authExchange(async utils => {
let token = 'initial-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
didAuthError,
async refreshAuth() {
token = 'final-token';
},
};
})(exchangeArgs),
publish
);
await new Promise(resolve => setTimeout(resolve));
result.mockImplementation(x => ({
...queryResponse,
operation: {
...queryResponse.operation,
...x,
},
data: undefined,
error: new CombinedError({
graphQLErrors: [{ message: 'Oops' }],
}),
}));
next(queryOperation);
expect(result).toHaveBeenCalledTimes(1);
expect(didAuthError).toHaveBeenCalledTimes(1);
await new Promise(resolve => setTimeout(resolve));
expect(result).toHaveBeenCalledTimes(2);
expect(operations.length).toBe(2);
expect(operations[0]).toHaveProperty(
'context.fetchOptions.headers.Authorization',
'initial-token'
);
expect(operations[1]).toHaveProperty(
'context.fetchOptions.headers.Authorization',
'final-token'
);
});
it('passes on failing refreshAuth() errors to results', async () => {
const { exchangeArgs, result } = makeExchangeArgs();
const didAuthError = vi.fn().mockReturnValue(true);
const willAuthError = vi.fn().mockReturnValue(true);
const res = await pipe(
fromValue(queryOperation),
authExchange(async utils => {
const token = 'initial-token';
return {
addAuthToOperation(operation) {
return utils.appendHeaders(operation, {
Authorization: token,
});
},
didAuthError,
willAuthError,
async refreshAuth() {
throw new Error('test');
},
};
})(exchangeArgs),
take(1),
toPromise
);
expect(result).toHaveBeenCalledTimes(0);
expect(didAuthError).toHaveBeenCalledTimes(0);
expect(willAuthError).toHaveBeenCalledTimes(1);
expect(res.error).toMatchInlineSnapshot('[CombinedError: [Network] test]');
});
it('passes on errors during initialization', async () => {
const { source, next } = makeSubject();
const { exchangeArgs, result } = makeExchangeArgs();
const init = vi.fn().mockRejectedValue(new Error('oops!'));
const output = vi.fn();
pipe(source, authExchange(init)(exchangeArgs), tap(output), publish);
expect(result).toHaveBeenCalledTimes(0);
expect(output).toHaveBeenCalledTimes(0);
next(queryOperation);
await new Promise(resolve => setTimeout(resolve));
expect(result).toHaveBeenCalledTimes(0);
expect(output).toHaveBeenCalledTimes(1);
expect(init).toHaveBeenCalledTimes(1);
expect(output.mock.calls[0][0].error).toMatchInlineSnapshot(
'[CombinedError: [Network] oops!]'
);
next(queryOperation);
await new Promise(resolve => setTimeout(resolve));
expect(result).toHaveBeenCalledTimes(0);
expect(output).toHaveBeenCalledTimes(2);
expect(init).toHaveBeenCalledTimes(2);
expect(output.mock.calls[1][0].error).toMatchInlineSnapshot(
'[CombinedError: [Network] oops!]'
);
});
================================================
FILE: exchanges/auth/src/authExchange.ts
================================================
import type { Source } from 'wonka';
import {
pipe,
map,
filter,
onStart,
take,
makeSubject,
toPromise,
merge,
} from 'wonka';
import type {
Operation,
OperationContext,
OperationResult,
CombinedError,
Exchange,
DocumentInput,
AnyVariables,
OperationInstance,
} from '@urql/core';
import { createRequest, makeOperation, makeErrorResult } from '@urql/core';
/** Utilities to use while refreshing authentication tokens. */
export interface AuthUtilities {
/** Sends a mutation to your GraphQL API, bypassing earlier exchanges and authentication.
*
* @param query - a GraphQL document containing the mutation operation that will be executed.
* @param variables - the variables used to execute the operation.
* @param context - {@link OperationContext} options that'll be used in future exchanges.
* @returns A `Promise` of an {@link OperationResult} for the GraphQL mutation.
*
* @remarks
* The `mutation()` utility method is useful when your authentication requires you to make a GraphQL mutation
* request to update your authentication tokens. In these cases, you likely wish to bypass prior exchanges and
* the authentication in the `authExchange` itself.
*
* This method bypasses the usual mutation flow of the `Client` and instead issues the mutation as directly
* as possible. This also means that it doesn’t carry your `Client`'s default {@link OperationContext}
* options, so you may have to pass them again, if needed.
*/
mutate(
query: DocumentInput,
variables: Variables,
context?: Partial
): Promise>;
/** Adds additional HTTP headers to an `Operation`.
*
* @param operation - An {@link Operation} to add headers to.
* @param headers - The HTTP headers to add to the `Operation`.
* @returns The passed {@link Operation} with the headers added to it.
*
* @remarks
* The `appendHeaders()` utility method is useful to add additional HTTP headers
* to an {@link Operation}. It’s a simple convenience function that takes
* `operation.context.fetchOptions` into account, since adding headers for
* authentication is common.
*/
appendHeaders(
operation: Operation,
headers: Record
): Operation;
}
/** Configuration for the `authExchange` returned by the initializer function you write. */
export interface AuthConfig {
/** Called for every operation to add authentication data to your operation.
*
* @param operation - An {@link Operation} that needs authentication tokens added.
* @returns a new {@link Operation} with added authentication tokens.
*
* @remarks
* The {@link authExchange} will call this function you provide and expects that you
* add your authentication tokens to your operation here, on the {@link Operation}
* that is returned.
*
* Hint: You likely want to modify your `fetchOptions.headers` here, for instance to
* add an `Authorization` header.
*/
addAuthToOperation(operation: Operation): Operation;
/** Called before an operation is forwaded onwards to make a request.
*
* @param operation - An {@link Operation} that needs authentication tokens added.
* @returns a boolean, if true, authentication must be refreshed.
*
* @remarks
* The {@link authExchange} will call this function before an {@link Operation} is
* forwarded onwards to your following exchanges.
*
* When this function returns `true`, the `authExchange` will call
* {@link AuthConfig.refreshAuth} before forwarding more operations
* to prompt you to update your authentication tokens.
*
* Hint: If you define this function, you can use it to check whether your authentication
* tokens have expired.
*/
willAuthError?(operation: Operation): boolean;
/** Called after receiving an operation result to check whether it has failed with an authentication error.
*
* @param error - A {@link CombinedError} that a result has come back with.
* @param operation - The {@link Operation} of that has failed.
* @returns a boolean, if true, authentication must be refreshed.
*
* @remarks
* The {@link authExchange} will call this function if it sees an {@link OperationResult}
* with a {@link CombinedError} on it, implying that it may have failed due to an authentication
* error.
*
* When this function returns `true`, the `authExchange` will call
* {@link AuthConfig.refreshAuth} before forwarding more operations
* to prompt you to update your authentication tokens.
* Afterwards, this operation will be retried once.
*
* Hint: You should define a function that detects your API’s authentication
* errors, e.g. using `result.extensions`.
*/
didAuthError(error: CombinedError, operation: Operation): boolean;
/** Called to refresh the authentication state.
*
* @remarks
* The {@link authExchange} will call this function if either {@link AuthConfig.willAuthError}
* or {@link AuthConfig.didAuthError} have returned `true` prior, which indicates that the
* authentication state you hold has expired or is out-of-date.
*
* When this function is called, you should refresh your authentication state.
* For instance, if you have a refresh token and an access token, you should rotate
* these tokens with your API by sending the refresh token.
*
* Hint: You can use the {@link fetch} API here, or use {@link AuthUtilities.mutate}
* if your API requires a GraphQL mutation to refresh your authentication state.
*/
refreshAuth(): Promise;
}
const addAuthAttemptToOperation = (
operation: Operation,
authAttempt: boolean
) =>
makeOperation(operation.kind, operation, {
...operation.context,
authAttempt,
});
/** Creates an `Exchange` handling control flow for authentication.
*
* @param init - An initializer function that returns an {@link AuthConfig} wrapped in a `Promise`.
* @returns the created authentication {@link Exchange}.
*
* @remarks
* The `authExchange` is used to create an exchange handling authentication and
* the control flow of refresh authentication.
*
* You must pass an initializer function, which receives {@link AuthUtilities} and
* must return an {@link AuthConfig} wrapped in a `Promise`.
* When this exchange is used in your `Client`, it will first call your initializer
* function, which gives you an opportunity to get your authentication state, e.g.
* from local storage.
*
* You may then choose to validate this authentication state and update it, and must
* then return an {@link AuthConfig}.
*
* This configuration defines how you add authentication state to {@link Operation | Operations},
* when your authentication state expires, when an {@link OperationResult} has errored
* with an authentication error, and how to refresh your authentication state.
*
* @example
* ```ts
* authExchange(async (utils) => {
* let token = localStorage.getItem('token');
* let refreshToken = localStorage.getItem('refreshToken');
* return {
* addAuthToOperation(operation) {
* return utils.appendHeaders(operation, {
* Authorization: `Bearer ${token}`,
* });
* },
* didAuthError(error) {
* return error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
* },
* async refreshAuth() {
* const result = await utils.mutate(REFRESH, { token });
* if (result.data?.refreshLogin) {
* token = result.data.refreshLogin.token;
* refreshToken = result.data.refreshLogin.refreshToken;
* localStorage.setItem('token', token);
* localStorage.setItem('refreshToken', refreshToken);
* }
* },
* };
* });
* ```
*/
export function authExchange(
init: (utilities: AuthUtilities) => Promise
): Exchange {
return ({ client, forward }) => {
const bypassQueue = new Set();
const retries = makeSubject();
const errors = makeSubject();
let retryQueue = new Map();
function flushQueue() {
authPromise = undefined;
const queue = retryQueue;
retryQueue = new Map();
queue.forEach(retries.next);
}
function errorQueue(error: Error) {
authPromise = undefined;
const queue = retryQueue;
retryQueue = new Map();
queue.forEach(operation => {
errors.next(makeErrorResult(operation, error));
});
}
let authPromise: Promise | void;
let config: AuthConfig | null = null;
return operations$ => {
function initAuth() {
authPromise = Promise.resolve()
.then(() =>
init({
mutate(
query: DocumentInput,
variables: Variables,
context?: Partial
): Promise> {
const baseOperation = client.createRequestOperation(
'mutation',
createRequest(query, variables),
context
);
return pipe(
result$,
onStart(() => {
const operation = addAuthToOperation(baseOperation);
bypassQueue.add(
operation.context._instance as OperationInstance
);
retries.next(operation);
}),
filter(
result =>
result.operation.key === baseOperation.key &&
baseOperation.context._instance ===
result.operation.context._instance
),
take(1),
toPromise
);
},
appendHeaders(
operation: Operation,
headers: Record
) {
const fetchOptions =
typeof operation.context.fetchOptions === 'function'
? operation.context.fetchOptions()
: operation.context.fetchOptions || {};
return makeOperation(operation.kind, operation, {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
...headers,
},
},
});
},
})
)
.then((_config: AuthConfig) => {
if (_config) config = _config;
flushQueue();
})
.catch((error: Error) => {
if (process.env.NODE_ENV !== 'production') {
console.warn(
'authExchange()’s initialization function has failed, which is unexpected.\n' +
'If your initialization function is expected to throw/reject, catch this error and handle it explicitly.\n' +
'Unless this error is handled it’ll be passed onto any `OperationResult` instantly and authExchange() will block further operations and retry.',
error
);
}
errorQueue(error);
});
}
initAuth();
function refreshAuth(operation: Operation) {
// add to retry queue to try again later
retryQueue.set(
operation.key,
addAuthAttemptToOperation(operation, true)
);
// check that another operation isn't already doing refresh
if (config && !authPromise) {
authPromise = config.refreshAuth().then(flushQueue).catch(errorQueue);
}
}
function willAuthError(operation: Operation) {
return (
!operation.context.authAttempt &&
config &&
config.willAuthError &&
config.willAuthError(operation)
);
}
function didAuthError(result: OperationResult) {
return (
config &&
config.didAuthError &&
config.didAuthError(result.error!, result.operation)
);
}
function addAuthToOperation(operation: Operation) {
return config ? config.addAuthToOperation(operation) : operation;
}
const opsWithAuth$ = pipe(
merge([retries.source, operations$]),
map(operation => {
if (operation.kind === 'teardown') {
retryQueue.delete(operation.key);
return operation;
} else if (
operation.context._instance &&
bypassQueue.has(operation.context._instance)
) {
return operation;
} else if (operation.context.authAttempt) {
return addAuthToOperation(operation);
} else if (authPromise || !config) {
if (!authPromise) initAuth();
if (!retryQueue.has(operation.key))
retryQueue.set(
operation.key,
addAuthAttemptToOperation(operation, false)
);
return null;
} else if (willAuthError(operation)) {
refreshAuth(operation);
return null;
}
return addAuthToOperation(
addAuthAttemptToOperation(operation, false)
);
}),
filter(Boolean)
) as Source;
const result$ = pipe(opsWithAuth$, forward);
return merge([
errors.source,
pipe(
result$,
filter(result => {
if (
!bypassQueue.has(result.operation.context._instance) &&
result.error &&
didAuthError(result) &&
!result.operation.context.authAttempt
) {
refreshAuth(result.operation);
return false;
}
if (bypassQueue.has(result.operation.context._instance)) {
bypassQueue.delete(result.operation.context._instance);
}
return true;
})
),
]);
};
};
}
================================================
FILE: exchanges/auth/src/index.ts
================================================
export { authExchange } from './authExchange';
export type { AuthUtilities, AuthConfig } from './authExchange';
================================================
FILE: exchanges/auth/tsconfig.json
================================================
{
"extends": "../../tsconfig.json",
"include": ["src"]
}
================================================
FILE: exchanges/auth/vitest.config.ts
================================================
import { mergeConfig } from 'vitest/config';
import baseConfig from '../../vitest.config';
export default mergeConfig(baseConfig, {});
================================================
FILE: exchanges/context/CHANGELOG.md
================================================
# Changelog
## 1.0.0
### Patch Changes
- Updated dependencies (See [#3789](https://github.com/urql-graphql/urql/pull/3789) and [#3807](https://github.com/urql-graphql/urql/pull/3807))
- @urql/core@6.0.0
## 0.3.1
### Patch Changes
- Omit minified files and sourcemaps' `sourcesContent` in published packages
Submitted by [@kitten](https://github.com/kitten) (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- Updated dependencies (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- @urql/core@5.1.1
## 0.3.0
### Minor Changes
- Mark `@urql/core` as a peer dependency as well as a regular dependency
Submitted by [@kitten](https://github.com/kitten) (See [#3579](https://github.com/urql-graphql/urql/pull/3579))
## 0.2.1
### Patch Changes
- Publish with npm provenance
Submitted by [@kitten](https://github.com/kitten) (See [#3180](https://github.com/urql-graphql/urql/pull/3180))
## 0.2.0
### Minor Changes
- Update exchanges to drop redundant `share` calls, since `@urql/core`’s `composeExchanges` utility now automatically does so for us
Submitted by [@kitten](https://github.com/kitten) (See [#3082](https://github.com/urql-graphql/urql/pull/3082))
### Patch Changes
- Upgrade to `wonka@^6.3.0`
Submitted by [@kitten](https://github.com/kitten) (See [#3104](https://github.com/urql-graphql/urql/pull/3104))
- Add TSDocs for all exchanges, documenting API internals
Submitted by [@kitten](https://github.com/kitten) (See [#3072](https://github.com/urql-graphql/urql/pull/3072))
- Updated dependencies (See [#3101](https://github.com/urql-graphql/urql/pull/3101), [#3033](https://github.com/urql-graphql/urql/pull/3033), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3053](https://github.com/urql-graphql/urql/pull/3053), [#3060](https://github.com/urql-graphql/urql/pull/3060), [#3081](https://github.com/urql-graphql/urql/pull/3081), [#3039](https://github.com/urql-graphql/urql/pull/3039), [#3104](https://github.com/urql-graphql/urql/pull/3104), [#3082](https://github.com/urql-graphql/urql/pull/3082), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3061](https://github.com/urql-graphql/urql/pull/3061), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3085](https://github.com/urql-graphql/urql/pull/3085), [#3079](https://github.com/urql-graphql/urql/pull/3079), [#3087](https://github.com/urql-graphql/urql/pull/3087), [#3059](https://github.com/urql-graphql/urql/pull/3059), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3057](https://github.com/urql-graphql/urql/pull/3057), [#3050](https://github.com/urql-graphql/urql/pull/3050), [#3062](https://github.com/urql-graphql/urql/pull/3062), [#3051](https://github.com/urql-graphql/urql/pull/3051), [#3043](https://github.com/urql-graphql/urql/pull/3043), [#3063](https://github.com/urql-graphql/urql/pull/3063), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3102](https://github.com/urql-graphql/urql/pull/3102), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3106](https://github.com/urql-graphql/urql/pull/3106), [#3058](https://github.com/urql-graphql/urql/pull/3058), and [#3062](https://github.com/urql-graphql/urql/pull/3062))
- @urql/core@4.0.0
## v0.1.0
**Initial Release**
================================================
FILE: exchanges/context/README.md
================================================
@urql/exchange-context
An exchange for setting operation context in urql
`@urql/exchange-context` is an exchange for the [`urql`](https://github.com/urql-graphql/urql) GraphQL client which can set the operation context both synchronously as well as asynchronously
## Quick Start Guide
First install `@urql/exchange-context` alongside `urql`:
```sh
yarn add @urql/exchange-context
# or
npm install --save @urql/exchange-context
```
You'll then need to add the `contextExchange`, that this package exposes, to your `urql` Client, the positioning of this exchange depends on whether you set an async setter or not. If you set an async context-setter it's best placed after all the synchronous exchanges (in front of the fetchExchange).
```js
import { createClient, cacheExchange, fetchExchange } from 'urql';
import { contextExchange } from '@urql/exchange-context';
const client = createClient({
url: 'http://localhost:1234/graphql',
exchanges: [
cacheExchange,
contextExchange({
getContext: async operation => {
const token = await getToken();
return { ...operation.context, headers: { authorization: token } };
},
}),
fetchExchange,
],
});
```
================================================
FILE: exchanges/context/jsr.json
================================================
{
"name": "@urql/exchange-context",
"version": "1.0.0",
"exports": {
".": "./src/index.ts"
},
"exclude": [
"node_modules",
"cypress",
"**/*.test.*",
"**/*.spec.*",
"**/*.test.*.snap",
"**/*.spec.*.snap"
]
}
================================================
FILE: exchanges/context/package.json
================================================
{
"name": "@urql/exchange-context",
"version": "1.0.0",
"description": "An exchange for setting (a)synchronous operation-context in urql",
"sideEffects": false,
"homepage": "https://formidable.com/open-source/urql/docs/",
"bugs": "https://github.com/urql-graphql/urql/issues",
"license": "MIT",
"author": "urql GraphQL Contributors",
"repository": {
"type": "git",
"url": "https://github.com/urql-graphql/urql.git",
"directory": "exchanges/context"
},
"keywords": [
"urql",
"exchange",
"context",
"graphql",
"exchanges"
],
"main": "dist/urql-exchange-context",
"module": "dist/urql-exchange-context.mjs",
"types": "dist/urql-exchange-context.d.ts",
"source": "src/index.ts",
"exports": {
".": {
"types": "./dist/urql-exchange-context.d.ts",
"import": "./dist/urql-exchange-context.mjs",
"require": "./dist/urql-exchange-context.js",
"source": "./src/index.ts"
},
"./package.json": "./package.json"
},
"files": [
"LICENSE",
"CHANGELOG.md",
"README.md",
"dist/"
],
"scripts": {
"test": "vitest",
"clean": "rimraf dist extras",
"check": "tsc --noEmit",
"lint": "eslint --ext=js,jsx,ts,tsx .",
"build": "rollup -c ../../scripts/rollup/config.mjs",
"prepare": "node ../../scripts/prepare/index.js",
"prepublishOnly": "run-s clean build"
},
"peerDependencies": {
"@urql/core": "^6.0.0"
},
"dependencies": {
"@urql/core": "workspace:^6.0.1",
"wonka": "^6.3.2"
},
"devDependencies": {
"@urql/core": "workspace:*",
"graphql": "^16.0.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
================================================
FILE: exchanges/context/src/context.test.ts
================================================
import { pipe, map, makeSubject, publish, tap } from 'wonka';
import { vi, expect, it, beforeEach } from 'vitest';
import {
gql,
createClient,
Operation,
OperationResult,
ExchangeIO,
} from '@urql/core';
import { queryResponse } from '../../../packages/core/src/test-utils';
import { contextExchange } from './context';
const queryOne = gql`
{
author {
id
name
}
}
`;
const queryOneData = {
__typename: 'Query',
author: {
__typename: 'Author',
id: '123',
name: 'Author',
},
};
const dispatchDebug = vi.fn();
let client, op, ops$, next;
beforeEach(() => {
client = createClient({
url: 'http://0.0.0.0',
exchanges: [],
});
op = client.createRequestOperation('query', {
key: 1,
query: queryOne,
});
({ source: ops$, next } = makeSubject());
});
it(`calls getContext`, () => {
const response = vi.fn((forwardOp: Operation): OperationResult => {
return {
...queryResponse,
operation: forwardOp,
data: queryOneData,
};
});
const result = vi.fn();
const forward: ExchangeIO = ops$ => {
return pipe(ops$, map(response));
};
const headers = { hello: 'world' };
pipe(
contextExchange({
getContext: op => ({ ...op.context, headers }),
})({
forward,
client,
dispatchDebug,
})(ops$),
tap(result),
publish
);
next(op);
expect(response).toHaveBeenCalledTimes(1);
expect(response.mock.calls[0][0].context.headers).toEqual(headers);
expect(result).toHaveBeenCalledTimes(1);
});
it(`calls getContext async`, async () => {
const response = vi.fn((forwardOp: Operation): OperationResult => {
return {
...queryResponse,
operation: forwardOp,
data: queryOneData,
};
});
const result = vi.fn();
const forward: ExchangeIO = ops$ => {
return pipe(ops$, map(response));
};
const headers = { hello: 'world' };
pipe(
contextExchange({
getContext: async op => {
await Promise.resolve();
return { ...op.context, headers };
},
})({
forward,
client,
dispatchDebug,
})(ops$),
tap(result),
publish
);
next(op);
await new Promise(res => {
setTimeout(() => {
expect(response).toHaveBeenCalledTimes(1);
expect(response.mock.calls[0][0].context.headers).toEqual(headers);
expect(result).toHaveBeenCalledTimes(1);
res(null);
}, 10);
});
});
================================================
FILE: exchanges/context/src/context.ts
================================================
import type { Exchange, Operation, OperationContext } from '@urql/core';
import { makeOperation } from '@urql/core';
import { fromPromise, fromValue, mergeMap, pipe } from 'wonka';
/** Input parameters for the {@link contextExchange}. */
export interface ContextExchangeArgs {
/** Returns a new {@link OperationContext}, optionally wrapped in a `Promise`.
*
* @remarks
* `getContext` is called for every {@link Operation} the `contextExchange`
* receives and must return a new {@link OperationContext} or a `Promise`
* of it.
*
* The new `OperationContext` will be used to update the `Operation`'s
* context before it's forwarded to the next exchange.
*/
getContext(
operation: Operation
): OperationContext | Promise;
}
/** Exchange factory modifying the {@link OperationContext} per incoming `Operation`.
*
* @param options - A {@link ContextExchangeArgs} configuration object.
* @returns the created context {@link Exchange}.
*
* @remarks
* The `contextExchange` allows the {@link OperationContext` to be easily
* modified per `Operation`. This may be useful to dynamically change the
* `Operation`’s parameters, even when we need to do so asynchronously.
*
* You must define a {@link ContextExchangeArgs.getContext} function,
* which may return a `Promise` or `OperationContext`.
*
* Hint: If the `getContext` function passed to this exchange returns a
* `Promise` it must be placed _after_ all synchronous exchanges, such as
* a `cacheExchange`.
*
* @example
* ```ts
* import { Client, cacheExchange, fetchExchange } from '@urql/core';
* import { contextExchange } from '@urql/exchange-context';
*
* const client = new Client({
* url: '',
* exchanges: [
* cacheExchange,
* contextExchange({
* async getContext(operation) {
* const url = await loadDynamicUrl();
* return {
* ...operation.context,
* url,
* };
* },
* }),
* fetchExchange,
* ],
* });
* ```
*/
export const contextExchange =
({ getContext }: ContextExchangeArgs): Exchange =>
({ forward }) => {
return ops$ => {
return pipe(
ops$,
mergeMap(operation => {
const result = getContext(operation);
const isPromise = 'then' in result;
if (isPromise) {
return fromPromise(
result.then((ctx: OperationContext) =>
makeOperation(operation.kind, operation, ctx)
)
);
} else {
return fromValue(
makeOperation(
operation.kind,
operation,
result as OperationContext
)
);
}
}),
forward
);
};
};
================================================
FILE: exchanges/context/src/index.ts
================================================
export { contextExchange } from './context';
export type { ContextExchangeArgs } from './context';
================================================
FILE: exchanges/context/tsconfig.json
================================================
{
"extends": "../../tsconfig.json",
"include": ["src"]
}
================================================
FILE: exchanges/context/vitest.config.ts
================================================
import { mergeConfig } from 'vitest/config';
import baseConfig from '../../vitest.config';
export default mergeConfig(baseConfig, {});
================================================
FILE: exchanges/execute/CHANGELOG.md
================================================
# Changelog
## 3.0.0
### Patch Changes
- Updated dependencies (See [#3789](https://github.com/urql-graphql/urql/pull/3789) and [#3807](https://github.com/urql-graphql/urql/pull/3807))
- @urql/core@6.0.0
## 2.3.1
### Patch Changes
- Omit minified files and sourcemaps' `sourcesContent` in published packages
Submitted by [@kitten](https://github.com/kitten) (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- Updated dependencies (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- @urql/core@5.1.1
## 2.3.0
### Minor Changes
- Mark `@urql/core` as a peer dependency as well as a regular dependency
Submitted by [@kitten](https://github.com/kitten) (See [#3579](https://github.com/urql-graphql/urql/pull/3579))
## 2.2.2
### Patch Changes
- Update build process to generate correct source maps
Submitted by [@kitten](https://github.com/kitten) (See [#3201](https://github.com/urql-graphql/urql/pull/3201))
## 2.2.1
### Patch Changes
- Publish with npm provenance
Submitted by [@kitten](https://github.com/kitten) (See [#3180](https://github.com/urql-graphql/urql/pull/3180))
## 2.2.0
### Minor Changes
- Update exchanges to drop redundant `share` calls, since `@urql/core`’s `composeExchanges` utility now automatically does so for us
Submitted by [@kitten](https://github.com/kitten) (See [#3082](https://github.com/urql-graphql/urql/pull/3082))
- Remove `getOperationName` export from `@urql/core`
Submitted by [@kitten](https://github.com/kitten) (See [#3062](https://github.com/urql-graphql/urql/pull/3062))
### Patch Changes
- Upgrade to `wonka@^6.3.0`
Submitted by [@kitten](https://github.com/kitten) (See [#3104](https://github.com/urql-graphql/urql/pull/3104))
- Add TSDocs for all exchanges, documenting API internals
Submitted by [@kitten](https://github.com/kitten) (See [#3072](https://github.com/urql-graphql/urql/pull/3072))
- Updated dependencies (See [#3101](https://github.com/urql-graphql/urql/pull/3101), [#3033](https://github.com/urql-graphql/urql/pull/3033), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3053](https://github.com/urql-graphql/urql/pull/3053), [#3060](https://github.com/urql-graphql/urql/pull/3060), [#3081](https://github.com/urql-graphql/urql/pull/3081), [#3039](https://github.com/urql-graphql/urql/pull/3039), [#3104](https://github.com/urql-graphql/urql/pull/3104), [#3082](https://github.com/urql-graphql/urql/pull/3082), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3061](https://github.com/urql-graphql/urql/pull/3061), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3085](https://github.com/urql-graphql/urql/pull/3085), [#3079](https://github.com/urql-graphql/urql/pull/3079), [#3087](https://github.com/urql-graphql/urql/pull/3087), [#3059](https://github.com/urql-graphql/urql/pull/3059), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3057](https://github.com/urql-graphql/urql/pull/3057), [#3050](https://github.com/urql-graphql/urql/pull/3050), [#3062](https://github.com/urql-graphql/urql/pull/3062), [#3051](https://github.com/urql-graphql/urql/pull/3051), [#3043](https://github.com/urql-graphql/urql/pull/3043), [#3063](https://github.com/urql-graphql/urql/pull/3063), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3102](https://github.com/urql-graphql/urql/pull/3102), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3106](https://github.com/urql-graphql/urql/pull/3106), [#3058](https://github.com/urql-graphql/urql/pull/3058), and [#3062](https://github.com/urql-graphql/urql/pull/3062))
- @urql/core@4.0.0
## 2.1.1
### Patch Changes
- ⚠️ Fix type-generation, with a change in TS/Rollup the type generation took the paths as src and resolved them into the types dir, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2870](https://github.com/urql-graphql/urql/pull/2870))
- Updated dependencies (See [#2872](https://github.com/urql-graphql/urql/pull/2872), [#2870](https://github.com/urql-graphql/urql/pull/2870), and [#2871](https://github.com/urql-graphql/urql/pull/2871))
- @urql/core@3.1.1
## 2.1.0
### Minor Changes
- The `context` option, which may be set to a context value or a function returning a context, can now return a `Promise` and will be correctly resolved and awaited, by [@YutaUra](https://github.com/YutaUra) (See [#2806](https://github.com/urql-graphql/urql/pull/2806))
### Patch Changes
- End iterator when teardown functions runs, previously it waited for one extra call to next, then ended the iterator, by [@danielkaxis](https://github.com/danielkaxis) (See [#2803](https://github.com/urql-graphql/urql/pull/2803))
- Updated dependencies (See [#2843](https://github.com/urql-graphql/urql/pull/2843), [#2847](https://github.com/urql-graphql/urql/pull/2847), [#2850](https://github.com/urql-graphql/urql/pull/2850), and [#2846](https://github.com/urql-graphql/urql/pull/2846))
- @urql/core@3.1.0
## 2.0.0
### Major Changes
- **Goodbye IE11!** 👋 This major release removes support for IE11. All code that is shipped will be transpiled much less and will _not_ be ES5-compatible anymore, by [@kitten](https://github.com/kitten) (See [#2504](https://github.com/FormidableLabs/urql/pull/2504))
- Upgrade to [Wonka v6](https://github.com/0no-co/wonka) (`wonka@^6.0.0`), which has no breaking changes but is built to target ES2015 and comes with other minor improvements.
The library has fully been migrated to TypeScript which will hopefully help with making contributions easier!, by [@kitten](https://github.com/kitten) (See [#2504](https://github.com/FormidableLabs/urql/pull/2504))
### Minor Changes
- Remove the `babel-plugin-modular-graphql` helper, this because the graphql package hasn't converted to ESM yet which gives issues in node environments, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2551](https://github.com/FormidableLabs/urql/pull/2551))
### Patch Changes
- ⚠️ fix return for context function argument, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2583](https://github.com/FormidableLabs/urql/pull/2583))
- Updated dependencies (See [#2551](https://github.com/FormidableLabs/urql/pull/2551), [#2504](https://github.com/FormidableLabs/urql/pull/2504), [#2619](https://github.com/FormidableLabs/urql/pull/2619), [#2607](https://github.com/FormidableLabs/urql/pull/2607), and [#2504](https://github.com/FormidableLabs/urql/pull/2504))
- @urql/core@3.0.0
## 1.2.3
### Patch Changes
- Support using default values with directives. Previously, using a variables with a default value within a directive would fail the validation if it is empty, by [@fathyb](https://github.com/fathyb) (See [#2435](https://github.com/FormidableLabs/urql/pull/2435))
## 1.2.2
### Patch Changes
- Upgrade modular imports for graphql package, which fixes an issue in `@urql/exchange-execute`, where `graphql@16` files wouldn't resolve the old `subscribe` import from the correct file, by [@kitten](https://github.com/kitten) (See [#2149](https://github.com/FormidableLabs/urql/pull/2149))
## 1.2.1
### Patch Changes
- Extend peer dependency range of `graphql` to include `^16.0.0`.
As always when upgrading across many packages of `urql`, especially including `@urql/core` we recommend you to deduplicate dependencies after upgrading, using `npm dedupe` or `npx yarn-deduplicate`, by [@kitten](https://github.com/kitten) (See [#2133](https://github.com/FormidableLabs/urql/pull/2133))
- Updated dependencies (See [#2133](https://github.com/FormidableLabs/urql/pull/2133))
- @urql/core@2.3.6
## 1.2.0
### Minor Changes
- Add subscription support, by [@Tigge](https://github.com/Tigge) (See [#2061](https://github.com/FormidableLabs/urql/pull/2061))
### Patch Changes
- Updated dependencies (See [#2074](https://github.com/FormidableLabs/urql/pull/2074))
- @urql/core@2.3.5
## 1.1.0
### Minor Changes
- Support async iterated results, including subscriptions via `AsyncIterator` support and `@defer` / `@stream` if the appropriate version of `graphql` is used, e.g. `15.4.0-experimental-stream-defer.1`, by [@kitten](https://github.com/kitten) (See [#1854](https://github.com/FormidableLabs/urql/pull/1854))
### Patch Changes
- Updated dependencies (See [#1854](https://github.com/FormidableLabs/urql/pull/1854))
- @urql/core@2.3.0
## 1.0.5
### Patch Changes
- Expose `ExecuteExchangeArgs` interface, by [@taneba](https://github.com/taneba) (See [#1837](https://github.com/FormidableLabs/urql/pull/1837))
- Updated dependencies (See [#1829](https://github.com/FormidableLabs/urql/pull/1829))
- @urql/core@2.1.6
## 1.0.4
### Patch Changes
- Remove closure-compiler from the build step (See [#1570](https://github.com/FormidableLabs/urql/pull/1570))
- Updated dependencies (See [#1570](https://github.com/FormidableLabs/urql/pull/1570), [#1509](https://github.com/FormidableLabs/urql/pull/1509), [#1600](https://github.com/FormidableLabs/urql/pull/1600), and [#1515](https://github.com/FormidableLabs/urql/pull/1515))
- @urql/core@2.1.0
## 1.0.3
### Patch Changes
- Export `getOperationName` from `@urql/core` and use it in `@urql/exchange-execute`, fixing several imports, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1135](https://github.com/FormidableLabs/urql/pull/1135))
- Updated dependencies (See [#1135](https://github.com/FormidableLabs/urql/pull/1135))
- @urql/core@1.15.1
## 1.0.2
### Patch Changes
- Add missing `.mjs` extension to all imports from `graphql` to fix Webpack 5 builds, which require extension-specific import paths for ESM bundles and packages. **This change allows you to safely upgrade to Webpack 5.**, by [@kitten](https://github.com/kitten) (See [#1094](https://github.com/FormidableLabs/urql/pull/1094))
- Deprecate the `Operation.operationName` property in favor of `Operation.kind`. This name was
previously confusing as `operationName` was effectively referring to two different things. You can
safely upgrade to this new version, however to mute all deprecation warnings you will have to
**upgrade** all `urql` packages you use. If you have custom exchanges that spread operations, please
use [the new `makeOperation` helper
function](https://formidable.com/open-source/urql/docs/api/core/#makeoperation) instead, by [@bkonkle](https://github.com/bkonkle) (See [#1045](https://github.com/FormidableLabs/urql/pull/1045))
- Updated dependencies (See [#1094](https://github.com/FormidableLabs/urql/pull/1094) and [#1045](https://github.com/FormidableLabs/urql/pull/1045))
- @urql/core@1.14.0
## 1.0.1
### Patch Changes
- Upgrade to a minimum version of wonka@^4.0.14 to work around issues with React Native's minification builds, which use uglify-es and could lead to broken bundles, by [@kitten](https://github.com/kitten) (See [#842](https://github.com/FormidableLabs/urql/pull/842))
- Updated dependencies (See [#838](https://github.com/FormidableLabs/urql/pull/838) and [#842](https://github.com/FormidableLabs/urql/pull/842))
- @urql/core@1.12.0
## v1.0.0
**Initial Release**
================================================
FILE: exchanges/execute/README.md
================================================
@urql/exchange-execute
An exchange for executing queries against a local schema in urql
`@urql/exchange-execute` is an exchange for the [`urql`](https://github.com/urql-graphql/urql) GraphQL client which executes queries against a local schema.
This is a replacement for the default _fetchExchange_ which sends queries over HTTP/S to be executed remotely.
## Quick Start Guide
First install `@urql/exchange-execute` alongside `urql`:
```sh
yarn add @urql/exchange-execute
# or
npm install --save @urql/exchange-execute
```
You'll then need to add the `executeExchange`, that this package exposes, to your `urql` Client,
by replacing the default fetch exchange with it:
```js
import { createClient, cacheExchange } from 'urql';
import { executeExchange } from '@urql/exchange-execute';
const client = createClient({
url: 'http://localhost:1234/graphql',
exchanges: [
cacheExchange,
// Replace the default fetchExchange with the new one.
executeExchange({
/* config */
}),
],
});
```
## Usage
The exchange takes the same arguments as the [_execute_ function](https://graphql.org/graphql-js/execution/#execute) provided by graphql-js.
Here's a brief example of how it might be used:
```js
import { buildSchema } from 'graphql';
// Create local schema
const schema = buildSchema(`
type Todo {
id: ID!
text: String!
}
type Query {
todos: [Todo]!
}
type Mutation {
addTodo(text: String!): Todo!
}
`);
// Create local state
let todos = [];
// Create root value with resolvers
const rootValue = {
todos: () => todos,
addTodo: (_, args) => {
const todo = { id: todos.length.toString(), ...args };
todos = [...todos, todo];
return todo;
}
}
// ...
// Pass schema and root value to executeExchange
executeExchange({
schema,
rootValue,
}),
// ...
```
================================================
FILE: exchanges/execute/jsr.json
================================================
{
"name": "@urql/exchange-execute",
"version": "3.0.0",
"exports": {
".": "./src/index.ts"
},
"exclude": [
"node_modules",
"cypress",
"**/*.test.*",
"**/*.spec.*",
"**/*.test.*.snap",
"**/*.spec.*.snap"
]
}
================================================
FILE: exchanges/execute/package.json
================================================
{
"name": "@urql/exchange-execute",
"version": "3.0.0",
"description": "An exchange for executing queries against a local schema in urql",
"sideEffects": false,
"homepage": "https://formidable.com/open-source/urql/docs/",
"bugs": "https://github.com/urql-graphql/urql/issues",
"license": "MIT",
"author": "urql GraphQL Contributors",
"repository": {
"type": "git",
"url": "https://github.com/urql-graphql/urql.git",
"directory": "exchanges/execute"
},
"keywords": [
"urql",
"exchange",
"execute",
"executable schema",
"graphql",
"exchanges"
],
"main": "dist/urql-exchange-execute",
"module": "dist/urql-exchange-execute.mjs",
"types": "dist/urql-exchange-execute.d.ts",
"source": "src/index.ts",
"exports": {
".": {
"types": "./dist/urql-exchange-execute.d.ts",
"import": "./dist/urql-exchange-execute.mjs",
"require": "./dist/urql-exchange-execute.js",
"source": "./src/index.ts"
},
"./package.json": "./package.json"
},
"files": [
"LICENSE",
"CHANGELOG.md",
"README.md",
"dist/"
],
"scripts": {
"test": "vitest",
"clean": "rimraf dist extras",
"check": "tsc --noEmit",
"lint": "eslint --ext=js,jsx,ts,tsx .",
"build": "rollup -c ../../scripts/rollup/config.mjs",
"prepare": "node ../../scripts/prepare/index.js",
"prepublishOnly": "run-s clean build"
},
"dependencies": {
"@urql/core": "workspace:^6.0.1",
"wonka": "^6.3.2"
},
"peerDependencies": {
"@urql/core": "^6.0.0",
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
},
"devDependencies": {
"@urql/core": "workspace:*",
"graphql": "^16.0.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
================================================
FILE: exchanges/execute/src/execute.test.ts
================================================
import { vi, expect, it, beforeEach, afterEach, describe, Mock } from 'vitest';
vi.mock('graphql', async () => {
const graphql = await vi.importActual('graphql');
return {
__esModule: true,
...(graphql as object),
print: vi.fn(() => '{ placeholder }'),
execute: vi.fn(() => ({ key: 'value' })),
subscribe: vi.fn(),
};
});
import { fetchExchange } from '@urql/core';
import { executeExchange } from './execute';
import { execute, print, subscribe } from 'graphql';
import {
pipe,
fromValue,
toPromise,
take,
makeSubject,
empty,
Source,
} from 'wonka';
import {
context,
queryOperation,
subscriptionOperation,
} from '../../../packages/core/src/test-utils';
import {
makeErrorResult,
makeOperation,
Client,
OperationResult,
} from '@urql/core';
const mocked = (x: any): any => x;
const schema = 'STUB_SCHEMA' as any;
const exchangeArgs = {
forward: a => a,
client: {},
} as any;
const expectedQueryOperationName = 'getUser';
const expectedSubscribeOperationName = 'subscribeToUser';
const fetchMock = (globalThis as any).fetch as Mock;
const mockHttpResponseData = { key: 'value' };
beforeEach(() => {
vi.clearAllMocks();
mocked(print).mockImplementation(a => a as any);
mocked(execute).mockResolvedValue({ data: mockHttpResponseData });
mocked(subscribe).mockImplementation(async function* x(this: any) {
yield { data: { key: 'value1' } };
yield { data: { key: 'value2' } };
yield { data: { key: 'value3' } };
});
});
afterEach(() => {
fetchMock.mockClear();
});
describe('on operation', () => {
it('calls execute with args', async () => {
const context = 'USER_ID=123';
await pipe(
fromValue(queryOperation),
executeExchange({ schema, context })(exchangeArgs),
take(1),
toPromise
);
expect(mocked(execute)).toBeCalledTimes(1);
expect(mocked(execute)).toBeCalledWith({
schema,
document: queryOperation.query,
rootValue: undefined,
contextValue: context,
variableValues: queryOperation.variables,
operationName: expectedQueryOperationName,
fieldResolver: undefined,
typeResolver: undefined,
subscribeFieldResolver: undefined,
});
});
it('calls subscribe with args', async () => {
const context = 'USER_ID=123';
await pipe(
fromValue(subscriptionOperation),
executeExchange({ schema, context })(exchangeArgs),
take(3),
toPromise
);
expect(mocked(subscribe)).toBeCalledTimes(1);
expect(mocked(subscribe)).toBeCalledWith({
schema,
document: subscriptionOperation.query,
rootValue: undefined,
contextValue: context,
variableValues: subscriptionOperation.variables,
operationName: expectedSubscribeOperationName,
fieldResolver: undefined,
typeResolver: undefined,
subscribeFieldResolver: undefined,
});
});
it('calls execute after executing context as a function', async () => {
const context = operation => {
expect(operation).toBe(queryOperation);
return 'CALCULATED_USER_ID=' + 8 * 10;
};
await pipe(
fromValue(queryOperation),
executeExchange({ schema, context })(exchangeArgs),
take(1),
toPromise
);
expect(mocked(execute)).toBeCalledTimes(1);
expect(mocked(execute)).toBeCalledWith({
schema,
document: queryOperation.query,
rootValue: undefined,
contextValue: 'CALCULATED_USER_ID=80',
variableValues: queryOperation.variables,
operationName: expectedQueryOperationName,
fieldResolver: undefined,
typeResolver: undefined,
subscribeFieldResolver: undefined,
});
});
it('calls execute after executing context as a function returning a Promise', async () => {
const context = async operation => {
expect(operation).toBe(queryOperation);
return 'CALCULATED_USER_ID=' + 8 * 10;
};
await pipe(
fromValue(queryOperation),
executeExchange({ schema, context })(exchangeArgs),
take(1),
toPromise
);
expect(mocked(execute)).toBeCalledTimes(1);
expect(mocked(execute)).toBeCalledWith({
schema,
document: queryOperation.query,
rootValue: undefined,
contextValue: 'CALCULATED_USER_ID=80',
variableValues: queryOperation.variables,
operationName: expectedQueryOperationName,
fieldResolver: undefined,
typeResolver: undefined,
subscribeFieldResolver: undefined,
});
});
it('should return data from subscribe', async () => {
const context = 'USER_ID=123';
const responseFromExecuteExchange = await pipe(
fromValue(subscriptionOperation),
executeExchange({ schema, context })(exchangeArgs),
take(3),
toPromise
);
expect(responseFromExecuteExchange.data).toEqual({ key: 'value3' });
});
it('should return the same data as the fetch exchange', async () => {
const context = 'USER_ID=123';
const responseFromExecuteExchange = await pipe(
fromValue(queryOperation),
executeExchange({ schema, context })(exchangeArgs),
take(1),
toPromise
);
fetchMock.mockResolvedValue({
status: 200,
headers: { get: () => 'application/json' },
text: vi
.fn()
.mockResolvedValue(JSON.stringify({ data: mockHttpResponseData })),
});
const responseFromFetchExchange = await pipe(
fromValue(queryOperation),
fetchExchange({
dispatchDebug: vi.fn(),
forward: () => empty as Source,
client: {} as Client,
}),
toPromise
);
expect(responseFromExecuteExchange.data).toEqual(
responseFromFetchExchange.data
);
expect(mocked(execute)).toBeCalledTimes(1);
expect(fetchMock).toBeCalledTimes(1);
});
it('should trim undefined values before calling execute()', async () => {
const contextValue = 'USER_ID=123';
const operation = makeOperation(
'query',
{
...queryOperation,
variables: { ...queryOperation.variables, withLastName: undefined },
},
context
);
await pipe(
fromValue(operation),
executeExchange({ schema, context: contextValue })(exchangeArgs),
take(1),
toPromise
);
expect(mocked(execute)).toBeCalledTimes(1);
expect(mocked(execute)).toBeCalledWith({
schema,
document: queryOperation.query,
rootValue: undefined,
contextValue: contextValue,
variableValues: queryOperation.variables,
operationName: expectedQueryOperationName,
fieldResolver: undefined,
typeResolver: undefined,
subscribeFieldResolver: undefined,
});
const variables = mocked(execute).mock.calls[0][0].variableValues;
for (const key in variables) {
expect(variables[key]).not.toBeUndefined();
}
});
});
describe('on success response', () => {
it('returns operation result', async () => {
const response = await pipe(
fromValue(queryOperation),
executeExchange({ schema })(exchangeArgs),
take(1),
toPromise
);
expect(response).toEqual({
operation: queryOperation,
data: mockHttpResponseData,
hasNext: false,
stale: false,
});
});
});
describe('on error response', () => {
const errors = ['error'] as any;
beforeEach(() => {
mocked(execute).mockResolvedValue({ errors });
});
it('returns operation result', async () => {
const response = await pipe(
fromValue(queryOperation),
executeExchange({ schema })(exchangeArgs),
take(1),
toPromise
);
expect(response).toHaveProperty('operation', queryOperation);
expect(response).toHaveProperty('error');
});
});
describe('on thrown error', () => {
const errors = ['error'] as any;
beforeEach(() => {
mocked(execute).mockRejectedValue({ errors });
});
it('returns operation result', async () => {
const response = await pipe(
fromValue(queryOperation),
executeExchange({ schema })(exchangeArgs),
take(1),
toPromise
);
const expected = makeErrorResult(queryOperation, errors);
expect(response.operation).toBe(expected.operation);
expect(response.data).toEqual(expected.data);
expect(response.error).toEqual(expected.error);
});
});
describe('on unsupported operation', () => {
const operation = makeOperation(
'teardown',
queryOperation,
queryOperation.context
);
it('returns operation result', async () => {
const { source, next } = makeSubject();
const response = pipe(
source,
executeExchange({ schema })(exchangeArgs),
take(1),
toPromise
);
next(operation);
expect(await response).toEqual(operation);
});
});
================================================
FILE: exchanges/execute/src/execute.ts
================================================
import type { Source } from 'wonka';
import { pipe, filter, takeUntil, mergeMap, merge, make } from 'wonka';
import type {
GraphQLSchema,
GraphQLFieldResolver,
GraphQLTypeResolver,
ExecutionArgs,
SubscriptionArgs,
} from 'graphql';
import { execute, subscribe, Kind } from 'graphql';
import type {
Exchange,
ExecutionResult,
Operation,
OperationResult,
} from '@urql/core';
import { makeResult, makeErrorResult, mergeResultPatch } from '@urql/core';
/** Input parameters for the {@link executeExchange}.
* @see {@link ExecutionArgs} which this interface mirrors. */
export interface ExecuteExchangeArgs {
/** GraphQL Schema definition that `Operation`s are execute against. */
schema: GraphQLSchema;
/** Context object or a factory function creating a `context` object.
*
* @remarks
* The `context` that is passed to the `schema` may either be passed
* or created from an incoming `Operation`, which also allows it to
* be recreated per `Operation`.
*/
context?: ((operation: Operation) => any) | any;
rootValue?: any;
fieldResolver?: GraphQLFieldResolver;
typeResolver?: GraphQLTypeResolver;
subscribeFieldResolver?: GraphQLFieldResolver;
}
type ExecuteParams = ExecutionArgs | SubscriptionArgs;
const asyncIterator =
typeof Symbol !== 'undefined' ? Symbol.asyncIterator : null;
const makeExecuteSource = (
operation: Operation,
_args: ExecuteParams
): Source => {
return make(observer => {
let iterator: AsyncIterator;
let ended = false;
Promise.resolve()
.then(async () => ({ ..._args, contextValue: await _args.contextValue }))
.then(args => {
if (ended) return;
if (operation.kind === 'subscription') {
return subscribe(args) as any;
}
return execute(args) as any;
})
.then((result: ExecutionResult | AsyncIterable) => {
if (ended || !result) {
return;
} else if (!asyncIterator || !result[asyncIterator]) {
observer.next(makeResult(operation, result as ExecutionResult));
return;
}
iterator = result[asyncIterator!]();
let prevResult: OperationResult | null = null;
function next({
done,
value,
}: {
done?: boolean;
value: ExecutionResult;
}) {
if (value) {
observer.next(
(prevResult = prevResult
? mergeResultPatch(prevResult, value)
: makeResult(operation, value))
);
}
if (!done && !ended) {
return iterator.next().then(next);
}
}
return iterator.next().then(next);
})
.then(() => {
observer.complete();
})
.catch(error => {
observer.next(makeErrorResult(operation, error));
observer.complete();
});
return () => {
if (iterator && iterator.return) iterator.return();
ended = true;
};
});
};
/** Exchange factory that executes operations against a GraphQL schema.
*
* @param options - A {@link ExecuteExchangeArgs} configuration object.
* @returns the created execute {@link Exchange}.
*
* @remarks
* The `executeExchange` executes GraphQL operations against the `schema`
* that it’s passed. As such, its options mirror the options that GraphQL.js’
* {@link execute} function accepts.
*/
export const executeExchange =
(options: ExecuteExchangeArgs): Exchange =>
({ forward }) => {
return operations$ => {
const executedOps$ = pipe(
operations$,
filter((operation: Operation) => {
return (
operation.kind === 'query' ||
operation.kind === 'mutation' ||
operation.kind === 'subscription'
);
}),
mergeMap((operation: Operation) => {
const { key } = operation;
const teardown$ = pipe(
operations$,
filter(op => op.kind === 'teardown' && op.key === key)
);
const contextValue =
typeof options.context === 'function'
? options.context(operation)
: options.context;
// Filter undefined values from variables before calling execute()
// to support default values within directives.
const variableValues = Object.create(null);
if (operation.variables) {
for (const key in operation.variables) {
if (operation.variables[key] !== undefined) {
variableValues[key] = operation.variables[key];
}
}
}
let operationName: string | undefined;
for (const node of operation.query.definitions) {
if (node.kind === Kind.OPERATION_DEFINITION) {
operationName = node.name ? node.name.value : undefined;
break;
}
}
return pipe(
makeExecuteSource(operation, {
schema: options.schema,
document: operation.query,
rootValue: options.rootValue,
contextValue,
variableValues,
operationName,
fieldResolver: options.fieldResolver,
typeResolver: options.typeResolver,
subscribeFieldResolver: options.subscribeFieldResolver,
}),
takeUntil(teardown$)
);
})
);
const forwardedOps$ = pipe(
operations$,
filter(operation => operation.kind === 'teardown'),
forward
);
return merge([executedOps$, forwardedOps$]);
};
};
================================================
FILE: exchanges/execute/src/index.ts
================================================
export { executeExchange } from './execute';
export type { ExecuteExchangeArgs } from './execute';
================================================
FILE: exchanges/execute/tsconfig.json
================================================
{
"extends": "../../tsconfig.json",
"include": ["src"]
}
================================================
FILE: exchanges/execute/vitest.config.ts
================================================
import { mergeConfig } from 'vitest/config';
import baseConfig from '../../vitest.config';
export default mergeConfig(baseConfig, {});
================================================
FILE: exchanges/graphcache/.gitignore
================================================
/extras
/default-storage
================================================
FILE: exchanges/graphcache/CHANGELOG.md
================================================
# @urql/exchange-graphcache
## 9.0.0
### Major Changes
- Don't serialize data to IDB. This invalidates all existing data, but greatly improves performance of read/write operations
Submitted by [@ThaUnknown](https://github.com/ThaUnknown) (See [#3824](https://github.com/urql-graphql/urql/pull/3824))
## 8.1.0
### Minor Changes
- Add possibleTypes config for deterministic fragment matching
Submitted by [@xuanduc987](https://github.com/xuanduc987) (See [#3805](https://github.com/urql-graphql/urql/pull/3805))
## 8.0.0
### Patch Changes
- Updated dependencies (See [#3789](https://github.com/urql-graphql/urql/pull/3789) and [#3807](https://github.com/urql-graphql/urql/pull/3807))
- @urql/core@6.0.0
## 7.2.4
### Patch Changes
- ⚠️ Fix compatibility with Typescript >5.5 (See: https://github.com/0no-co/graphql.web/pull/49)
Submitted by [@andreisergiu98](https://github.com/andreisergiu98) (See [#3730](https://github.com/urql-graphql/urql/pull/3730))
- Updated dependencies (See [#3773](https://github.com/urql-graphql/urql/pull/3773), [#3767](https://github.com/urql-graphql/urql/pull/3767), [#3730](https://github.com/urql-graphql/urql/pull/3730), and [#3770](https://github.com/urql-graphql/urql/pull/3770))
- @urql/core@5.1.2
## 7.2.3
### Patch Changes
- Omit minified files and sourcemaps' `sourcesContent` in published packages
Submitted by [@kitten](https://github.com/kitten) (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- Updated dependencies (See [#3755](https://github.com/urql-graphql/urql/pull/3755))
- @urql/core@5.1.1
## 7.2.2
### Patch Changes
- Remove addMetadata transform where we'd strip out metadata for production environments, this particularly affects OperationResult.context.metadata.cacheOutcome
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3744](https://github.com/urql-graphql/urql/pull/3744))
## 7.2.1
### Patch Changes
- Update selection iterator implementation for JSC memory reduction
Submitted by [@kitten](https://github.com/kitten) (See [#3693](https://github.com/urql-graphql/urql/pull/3693))
## 7.2.0
### Minor Changes
- Allow @\_required directive to be used in combination with configured schemas
Submitted by [@AndrewIngram](https://github.com/AndrewIngram) (See [#3685](https://github.com/urql-graphql/urql/pull/3685))
## 7.1.3
### Patch Changes
- ⚠️ fix bug that mutation would cause dependent operations and reexecuting operations to become the same set
Submitted by [@xuanduc987](https://github.com/xuanduc987) (See [#3665](https://github.com/urql-graphql/urql/pull/3665))
## 7.1.2
### Patch Changes
- Disregard write-only operation when fragment-matching with schema awareness
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3621](https://github.com/urql-graphql/urql/pull/3621))
## 7.1.1
### Patch Changes
- ⚠️ Fix where we would incorrectly match all fragment concrete types because they belong to the abstract type
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3603](https://github.com/urql-graphql/urql/pull/3603))
## 7.1.0
### Minor Changes
- Mark `@urql/core` as a peer dependency as well as a regular dependency
Submitted by [@kitten](https://github.com/kitten) (See [#3579](https://github.com/urql-graphql/urql/pull/3579))
## 7.0.2
### Patch Changes
- Only record dependencies that are changing data, this will reduce the amount of operations we re-invoke due to network-only/cache-and-network queries and mutations
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3564](https://github.com/urql-graphql/urql/pull/3564))
## 7.0.1
### Patch Changes
- When invoking the automatic creation updater ignore the entity we are currently on in the mutation
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3560](https://github.com/urql-graphql/urql/pull/3560))
## 7.0.0
### Major Changes
- Add a default updater for mutation fields who are lacking an updater and where the returned entity is not present in the cache
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3518](https://github.com/urql-graphql/urql/pull/3518))
- Remove deprecated `resolveFieldByKey`, use `cache.resolve` instead
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3520](https://github.com/urql-graphql/urql/pull/3520))
### Minor Changes
- Track abstract types being written so that we have a more reliable way of matching abstract fragments
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3548](https://github.com/urql-graphql/urql/pull/3548))
### Patch Changes
- ⚠️ Fix `invalidate` not applying when using a string to invalidate an entity
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3545](https://github.com/urql-graphql/urql/pull/3545))
- Upgrade `@0no-co/graphql.web` to `1.0.5`
Submitted by [@kitten](https://github.com/kitten) (See [#3553](https://github.com/urql-graphql/urql/pull/3553))
- Updated dependencies (See [#3520](https://github.com/urql-graphql/urql/pull/3520), [#3553](https://github.com/urql-graphql/urql/pull/3553), and [#3520](https://github.com/urql-graphql/urql/pull/3520))
- @urql/core@5.0.0
## 6.5.0
### Minor Changes
- Allow `@_optional` and `@_required` to be placed on fragment definitions and inline fragments
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3502](https://github.com/urql-graphql/urql/pull/3502))
- Track list of entity keys for a given type name. This enables enumerating and invalidating all entities of a given type within the normalized cache
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3501](https://github.com/urql-graphql/urql/pull/3501))
### Patch Changes
- Prevent `@defer` from being applied in child field selections. Previously, a child field (i.e. a nested field) under a `@defer`-ed fragment would also become optional, which was based on a prior version of the DeferStream spec which didn't require deferred fields to be delivered as a group
Submitted by [@kitten](https://github.com/kitten) (See [#3517](https://github.com/urql-graphql/urql/pull/3517))
- ⚠️ Fix `store.resolve()` returning the exact link array that’s used by the cache. This can lead to subtle bugs when a user mutates the result returned by `cache.resolve()`, since this directly mutates what’s in the cache at that layer
Submitted by [@kitten](https://github.com/kitten) (See [#3516](https://github.com/urql-graphql/urql/pull/3516))
- Updated dependencies (See [#3514](https://github.com/urql-graphql/urql/pull/3514), [#3505](https://github.com/urql-graphql/urql/pull/3505), [#3499](https://github.com/urql-graphql/urql/pull/3499), and [#3515](https://github.com/urql-graphql/urql/pull/3515))
- @urql/core@4.3.0
## 6.4.1
### Patch Changes
- Set `stale: true` on cache results, even if a reexecution has been blocked by the loop protection, if the operation is already pending and in-flight
Submitted by [@kitten](https://github.com/kitten) (See [#3493](https://github.com/urql-graphql/urql/pull/3493))
- ⚠️ Fix `@defer` state leaking into following operations
Submitted by [@kitten](https://github.com/kitten) (See [#3497](https://github.com/urql-graphql/urql/pull/3497))
## 6.4.0
### Minor Changes
- Allow the user to debug cache-misses by means of the new `logger` interface on the `cacheExchange`. A field miss will dispatch a `debug` log when it's not marked with `@_optional` or when it's non-nullable in the `schema`
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3446](https://github.com/urql-graphql/urql/pull/3446))
- Add `onCacheHydrated` as an option for the `StorageAdapter`
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3428](https://github.com/urql-graphql/urql/pull/3428))
- Add optional `logger` to the options, this allows you to filter out warnings or disable them all together
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3444](https://github.com/urql-graphql/urql/pull/3444))
## 6.3.3
### Patch Changes
- ⚠️ Fix a typo that caused an inverted condition, for checking owned data, to cause incorrect results when handling `null` values and encountering them first
Submitted by [@kitten](https://github.com/kitten) (See [#3371](https://github.com/urql-graphql/urql/pull/3371))
## 6.3.2
### Patch Changes
- ⚠️ Fix extra variables in mutation results regressing by a change made in [#3317](https://github.com/urql-graphql/urql/pull/3317). The original operation wasn't being preserved anymore
Submitted by [@kitten](https://github.com/kitten) (See [#3356](https://github.com/urql-graphql/urql/pull/3356))
## 6.3.1
### Patch Changes
- Reset `partial` result marker when reading from selections when a child value sees a cache miss. This only affects resolvers on child values enabling `info.partial` while a parent may abort early instead
Submitted by [@kitten](https://github.com/kitten) (See [#3340](https://github.com/urql-graphql/urql/pull/3340))
- ⚠️ Fix `@_optional` directive not setting `info.partial = true` on cache miss and fix usage of `info.parentKey` and `info.parentFieldKey` usage in default directives
Submitted by [@kitten](https://github.com/kitten) (See [#3338](https://github.com/urql-graphql/urql/pull/3338))
- Replace implementation for `@_optional` and `@_required` with built-in handling inside cache reads to allow `@_optional` to work for nested selection sets
Submitted by [@kitten](https://github.com/kitten) (See [#3341](https://github.com/urql-graphql/urql/pull/3341))
## 6.3.0
### Minor Changes
- Allow scalar values on the parent to be accessed from `parent[info.fieldName]` consistently. Prior to this change `parent[fieldAlias]` would get populated, which wouldn’t always result in a field that’s consistently accessible
Submitted by [@kitten](https://github.com/kitten) (See [#3336](https://github.com/urql-graphql/urql/pull/3336))
- Allow `cache.resolve` to return `undefined` when a value is not cached to make it easier to cause a cache miss in resolvers. **Reminder:** Returning `undefined` from a resolver means a field is uncached, while returning `null` means that a field’s value is `null` without causing a cache miss
Submitted by [@kitten](https://github.com/kitten) (See [#3333](https://github.com/urql-graphql/urql/pull/3333))
### Patch Changes
- Record a dependency when `__typename` field is read. This removes a prior, outdated exception to avoid confusion when using `cache.resolve(entity, '__typename')` which doesn't cause the cache to record a dependency
Submitted by [@kitten](https://github.com/kitten) (See [#3335](https://github.com/urql-graphql/urql/pull/3335))
- ⚠️ Fix cases where `ResolveInfo`’s `parentFieldKey` was incorrectly populated with a key that isn’t a field key (allowing for `cache.resolve(info.parentKey, info.parentFieldKey)` to be possible) but was instead set to `info.parentKey` combined with the field key
Submitted by [@kitten](https://github.com/kitten) (See [#3336](https://github.com/urql-graphql/urql/pull/3336))
## 6.2.0
### Minor Changes
- Implement **local directives**. It’s now possible to add client-only directives to queries by adding them to the `cacheExchange`’s new `directives` option.
Directives accept an object of their arguments and return a resolver. When a field is annotated with
a resolver, e.g. `@_optional` or `@_required`, their resolvers from the `directives` config are
executed. This means it’s now possible to use `@_relayPagination` for example, by passing adding
the `relayPagination` helper to the config.
Due to the change in [#3317](https://github.com/urql-graphql/urql/pull/3317), any directive in
queries that’s prefixed with an underscore (`_`) is only visible to Graphcache and not the API.
Submitted by undefined (See https://github.com/urql-graphql/urql/pull/3306)
### Patch Changes
- Use new `FormattedNode` / `formatDocument` functionality added to `@urql/core` to slightly speed up directive processing by using the client-side `_directives` dictionary that `formatDocument` adds
Submitted by [@kitten](https://github.com/kitten) (See [#3317](https://github.com/urql-graphql/urql/pull/3317))
- Allow `offlineExchange` to once again issue all request policies, instead of mapping them to `cache-first`. When replaying operations after rehydrating it will now prioritise network policies, and before rehydrating receiving a network result will prevent a network request from being issued again
Submitted by [@kitten](https://github.com/kitten) (See [#3308](https://github.com/urql-graphql/urql/pull/3308))
- Add `OperationContext.optimistic` flag as an internal indication on whether a mutation triggered an optimistic update in `@urql/exchange-graphcache`'s `cacheExchange`
Submitted by [@kitten](https://github.com/kitten) (See [#3308](https://github.com/urql-graphql/urql/pull/3308))
- Updated dependencies (See [#3317](https://github.com/urql-graphql/urql/pull/3317) and [#3308](https://github.com/urql-graphql/urql/pull/3308))
- @urql/core@4.1.0
## 6.1.4
### Patch Changes
- ⚠️ Fix untranspiled class property initializer syntax being leftover in build output. (Regression in #3053)
Submitted by [@kitten](https://github.com/kitten) (See [#3275](https://github.com/urql-graphql/urql/pull/3275))
## 6.1.3
### Patch Changes
- ⚠️ Fix `info.parentKey` not being correctly set for updaters or optimistic updaters
Submitted by [@kitten](https://github.com/kitten) (See [#3267](https://github.com/urql-graphql/urql/pull/3267))
## 6.1.2
### Patch Changes
- Make "Invalid undefined" warning heuristic smarter and allow for partial optimistic results. Previously, when a partial optimistic result would be passed, a warning would be issued, and in production, fields would be deleted from the cache. Instead, we now only issue a warning if these fields aren't cached already
Submitted by [@kitten](https://github.com/kitten) (See [#3264](https://github.com/urql-graphql/urql/pull/3264))
- Optimistic mutation results should never result in dependent operations being blocked
Submitted by [@kitten](https://github.com/kitten) (See [#3265](https://github.com/urql-graphql/urql/pull/3265))
## 6.1.1
### Patch Changes
- ⚠️ Fix torn down queries not being removed from `offlineExchange`’s failed queue on rehydration
Submitted by [@kitten](https://github.com/kitten) (See [#3236](https://github.com/urql-graphql/urql/pull/3236))
## 6.1.0
### Minor Changes
- Add `globalIDs` configuration option to omit typenames in cache keys
Submitted by [@kitten](https://github.com/kitten) (See [#3224](https://github.com/urql-graphql/urql/pull/3224))
### Patch Changes
- Update build process to generate correct source maps
Submitted by [@kitten](https://github.com/kitten) (See [#3201](https://github.com/urql-graphql/urql/pull/3201))
- Prevent `offlineExchange` from issuing duplicate operations
Submitted by [@kitten](https://github.com/kitten) (See [#3200](https://github.com/urql-graphql/urql/pull/3200))
- ⚠️ Fix reference equality not being preserved. This is a fix on top of [#3165](https://github.com/urql-graphql/urql/pull/3165), and was previously not addressed to avoid having to test for corner cases that are hard to cover. If you experience issues with this fix, please let us know
Submitted by [@kitten](https://github.com/kitten) (See [#3228](https://github.com/urql-graphql/urql/pull/3228))
- Retry operations against offline cache and stabilize timing of flushing failed operations queue after rehydrating the storage data
Submitted by [@kitten](https://github.com/kitten) (See [#3196](https://github.com/urql-graphql/urql/pull/3196))
## 6.0.4
### Patch Changes
- ⚠️ Fix missing cache updates, when a query that was previously torn down restarts and retrieves results from the cache. In this case a regression caused cache updates to not be correctly applied to the queried results, since the operation wouldn’t be recognised properly
Submitted by [@kitten](https://github.com/kitten) (See [#3193](https://github.com/urql-graphql/urql/pull/3193))
## 6.0.3
### Patch Changes
- Publish with npm provenance
Submitted by [@kitten](https://github.com/kitten) (See [#3180](https://github.com/urql-graphql/urql/pull/3180))
## 6.0.2
### Patch Changes
- Prevent reusal of incoming API data in Graphcache’s produced (“owned”) data. This prevents us from copying the `__typename` and other superfluous fields
Submitted by [@kitten](https://github.com/kitten) (See [#3165](https://github.com/urql-graphql/urql/pull/3165))
- ⚠️ Fix regression which caused `@defer` directives from becoming “sticky” and causing every subsequent cache read to be treated as if the field was deferred
Submitted by [@kitten](https://github.com/kitten) (See [#3167](https://github.com/urql-graphql/urql/pull/3167))
- Apply `hasNext: true` and fallthrough logic to cached queries that contain deferred, uncached fields. Deferred query results will now be fetched against the API correctly, even if prior requests have been incomplete
Submitted by [@kitten](https://github.com/kitten) (See [#3163](https://github.com/urql-graphql/urql/pull/3163))
- ⚠️ Fix `offlineExchange` duplicating offline mutations in failed queue
Submitted by [@kitten](https://github.com/kitten) (See [#3158](https://github.com/urql-graphql/urql/pull/3158))
## 6.0.1
### Patch Changes
- Remove inclusion and usage of optional chaining operator
Submitted by [@kitten](https://github.com/kitten) (See [#3116](https://github.com/urql-graphql/urql/pull/3116))
## 6.0.0
### Major Changes
- Remove dependence on `graphql` package and replace it with `@0no-co/graphql.web`, which reduces the default bundlesize impact of `urql` packages to a minimum. All types should remain compatible, even if you use `graphql` elsewhere in your app, and if other dependencies are using `graphql` you may alias it to `graphql-web-lite`
Submitted by [@kitten](https://github.com/kitten) (See [#3097](https://github.com/urql-graphql/urql/pull/3097))
- Update `OperationResult.hasNext` and `OperationResult.stale` to be required fields. If you have a custom exchange creating results, you'll have to add these fields or use the `makeResult`, `mergeResultPatch`, or `makeErrorResult` helpers
Submitted by [@kitten](https://github.com/kitten) (See [#3061](https://github.com/urql-graphql/urql/pull/3061))
### Minor Changes
- Update exchanges to drop redundant `share` calls, since `@urql/core`’s `composeExchanges` utility now automatically does so for us
Submitted by [@kitten](https://github.com/kitten) (See [#3082](https://github.com/urql-graphql/urql/pull/3082))
### Patch Changes
- ⚠️ Fix source maps included with recently published packages, which lost their `sourcesContent`, including additional source files, and had incorrect paths in some of them
Submitted by [@kitten](https://github.com/kitten) (See [#3053](https://github.com/urql-graphql/urql/pull/3053))
- Upgrade to `wonka@^6.3.0`
Submitted by [@kitten](https://github.com/kitten) (See [#3104](https://github.com/urql-graphql/urql/pull/3104))
- Restore variables correctly on mutations
Submitted by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#3046](https://github.com/urql-graphql/urql/pull/3046))
- Use `stringifyDocument` in `offlineExchange` rather than `print` and serialize `operation.extensions` as needed
Submitted by [@kitten](https://github.com/kitten) (See [#3094](https://github.com/urql-graphql/urql/pull/3094))
- Add missing `hasNext` and `stale` passthroughs on caching exchanges
Submitted by [@kitten](https://github.com/kitten) (See [#3059](https://github.com/urql-graphql/urql/pull/3059))
- Add TSDocs for all exchanges, documenting API internals
Submitted by [@kitten](https://github.com/kitten) (See [#3072](https://github.com/urql-graphql/urql/pull/3072))
- Updated dependencies (See [#3101](https://github.com/urql-graphql/urql/pull/3101), [#3033](https://github.com/urql-graphql/urql/pull/3033), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3053](https://github.com/urql-graphql/urql/pull/3053), [#3060](https://github.com/urql-graphql/urql/pull/3060), [#3081](https://github.com/urql-graphql/urql/pull/3081), [#3039](https://github.com/urql-graphql/urql/pull/3039), [#3104](https://github.com/urql-graphql/urql/pull/3104), [#3082](https://github.com/urql-graphql/urql/pull/3082), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3061](https://github.com/urql-graphql/urql/pull/3061), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3085](https://github.com/urql-graphql/urql/pull/3085), [#3079](https://github.com/urql-graphql/urql/pull/3079), [#3087](https://github.com/urql-graphql/urql/pull/3087), [#3059](https://github.com/urql-graphql/urql/pull/3059), [#3055](https://github.com/urql-graphql/urql/pull/3055), [#3057](https://github.com/urql-graphql/urql/pull/3057), [#3050](https://github.com/urql-graphql/urql/pull/3050), [#3062](https://github.com/urql-graphql/urql/pull/3062), [#3051](https://github.com/urql-graphql/urql/pull/3051), [#3043](https://github.com/urql-graphql/urql/pull/3043), [#3063](https://github.com/urql-graphql/urql/pull/3063), [#3054](https://github.com/urql-graphql/urql/pull/3054), [#3102](https://github.com/urql-graphql/urql/pull/3102), [#3097](https://github.com/urql-graphql/urql/pull/3097), [#3106](https://github.com/urql-graphql/urql/pull/3106), [#3058](https://github.com/urql-graphql/urql/pull/3058), and [#3062](https://github.com/urql-graphql/urql/pull/3062))
- @urql/core@4.0.0
## 5.2.0
### Minor Changes
- Add `isOfflineError` option to the `offlineExchange` to allow it to be customized to different conditions to determine whether an operation has failed because of a network error
Submitted by [@robertherber](https://github.com/robertherber) (See [#3020](https://github.com/urql-graphql/urql/pull/3020))
- Allow `updates` config to react to arbitrary type updates other than just `Mutation` and `Subscription` fields.
You’ll now be able to write updaters that react to any entity field being written to the cache,
which allows for more granular invalidations. **Note:** If you’ve previously used `updates.Mutation`
and `updated.Subscription` with a custom schema with custom root names, you‘ll get a warning since
you’ll have to update your `updates` config to reflect this. This was a prior implementation
mistake!
Submitted by [@kitten](https://github.com/kitten) (See [#2979](https://github.com/urql-graphql/urql/pull/2979))
### Patch Changes
- ⚠️ Fix regression which caused partial results, whose refetches were blocked by the looping protection, to not have a `stale: true` flag added to them. This is a regression from https://github.com/urql-graphql/urql/pull/2831 and only applies to `cacheExchange`s that had the `schema` option set
Submitted by [@kitten](https://github.com/kitten) (See [#2999](https://github.com/urql-graphql/urql/pull/2999))
- Add `invariant` to data layer that prevents cache writes during cache query operations. This prevents `cache.writeFragment`, `cache.updateQuery`, and `cache.link` from being called in `resolvers` for instance
Submitted by [@kitten](https://github.com/kitten) (See [#2978](https://github.com/urql-graphql/urql/pull/2978))
- Updated dependencies (See [#3007](https://github.com/urql-graphql/urql/pull/3007), [#2962](https://github.com/urql-graphql/urql/pull/2962), [#3007](https://github.com/urql-graphql/urql/pull/3007), [#3015](https://github.com/urql-graphql/urql/pull/3015), and [#3022](https://github.com/urql-graphql/urql/pull/3022))
- @urql/core@3.2.0
## 5.0.9
### Patch Changes
- ⚠️ Fix potential data loss in `offlineExchange` that's caused when `onOnline` triggers and flushes mutation queue before the mutation queue is used, by [@trcoffman](https://github.com/trcoffman) (See [#2945](https://github.com/urql-graphql/urql/pull/2945))
- Patch message for `(16) Heuristic Fragment Matching`, by [@inokawa](https://github.com/inokawa) (See [#2923](https://github.com/urql-graphql/urql/pull/2923))
- Patch message for (19) Can't generate a key for invalidate(...) error, by [@inokawa](https://github.com/inokawa) (See [#2918](https://github.com/urql-graphql/urql/pull/2918))
## 5.0.8
### Patch Changes
- ⚠️ Fix operation being blocked for looping due to it not cancelling the looping protection when a `teardown` is received. This bug could be triggered when a shared query operation triggers again and causes a cache miss (e.g. due to an error). The re-execution of the operation would then be blocked as Graphcache considered it a "reexecution loop" rather than a legitimate execution triggered by the UI. (See https://github.com/urql-graphql/urql/pull/2737 for more information), by [@kitten](https://github.com/kitten) (See [#2876](https://github.com/urql-graphql/urql/pull/2876))
## 5.0.7
### Patch Changes
- ⚠️ Fix type-generation, with a change in TS/Rollup the type generation took the paths as src and resolved them into the types dir, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2870](https://github.com/urql-graphql/urql/pull/2870))
- Updated dependencies (See [#2872](https://github.com/urql-graphql/urql/pull/2872), [#2870](https://github.com/urql-graphql/urql/pull/2870), and [#2871](https://github.com/urql-graphql/urql/pull/2871))
- @urql/core@3.1.1
## 5.0.6
### Patch Changes
- Solve issue where partial data could cause loops between related queries, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2831](https://github.com/urql-graphql/urql/pull/2831))
- Add skipping of garbage collection runs when the cache is waiting for optimistic, deferred or other results in layers. This means that we only take an opportunity to run garbage collection after results have settled and are hence decreasing the chance of hogging the event loop when a run isn't needed, by [@kitten](https://github.com/kitten) (See [#2862](https://github.com/urql-graphql/urql/pull/2862))
- ⚠️ Fix a deadlock condition in Graphcache's layers, which is caused by subscriptions (or other deferred layers) starting before one-off mutation layers. This causes the mutation to not be completed, which keeps its data preferred above the deferred layer. That in turn means that layers stop squashing, which causes new results to be missing indefinitely, when they overlap, by [@kitten](https://github.com/kitten) (See [#2861](https://github.com/urql-graphql/urql/pull/2861))
- Updated dependencies (See [#2843](https://github.com/urql-graphql/urql/pull/2843), [#2847](https://github.com/urql-graphql/urql/pull/2847), [#2850](https://github.com/urql-graphql/urql/pull/2850), and [#2846](https://github.com/urql-graphql/urql/pull/2846))
- @urql/core@3.1.0
## 5.0.5
### Patch Changes
- Set operations when updating the cache with a result, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2782](https://github.com/FormidableLabs/urql/pull/2782))
## 5.0.4
### Patch Changes
- Ensure we aren't eagerly removing layers that are caused by subscriptions, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2771](https://github.com/FormidableLabs/urql/pull/2771))
## 5.0.3
### Patch Changes
- ⚠️ Fix case where a mutation would also be counted in the loop-protection, this prevented partial queries from initiating refetches, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2761](https://github.com/FormidableLabs/urql/pull/2761))
- Updated dependencies (See [#2758](https://github.com/FormidableLabs/urql/pull/2758) and [#2762](https://github.com/FormidableLabs/urql/pull/2762))
- @urql/core@3.0.5
## 5.0.2
### Patch Changes
- Preserve the original `DocumentNode` AST when updating the cache, to prevent results after a network request from differing and breaking referential equality due to added `__typename` fields, by [@kitten](https://github.com/kitten) (See [#2736](https://github.com/FormidableLabs/urql/pull/2736))
- ⚠️ Fix optimistic mutations containing partial results (`undefined` fields), which previously actually caused a hidden cache miss, which may then affect a subsequent non-optimistic mutation result, by [@kitten](https://github.com/kitten) (See [#2740](https://github.com/FormidableLabs/urql/pull/2740))
- Prevent cache misses from causing infinite network requests from being issued, when two operations manipulate each other while experiencing cache misses or are partially uncacheable, by [@kitten](https://github.com/kitten) (See [#2737](https://github.com/FormidableLabs/urql/pull/2737))
- ⚠️ Fix operation identities preventing users from deeply cloning operation contexts. Instead, we now use a client-wide counter (rolling over as needed).
While this changes an internal data structure in `@urql/core` only, this change also affects the `offlineExchange` in `@urql/exchange-graphcache` due to it relying on the identity being previously an object rather than an integer, by [@kitten](https://github.com/kitten) (See [#2732](https://github.com/FormidableLabs/urql/pull/2732))
- ⚠️ Fix referential equality preservation in Graphcache failing after API results, due to a typo writing the API result rather than the updated cache result, by [@kitten](https://github.com/kitten) (See [#2741](https://github.com/FormidableLabs/urql/pull/2741))
- Updated dependencies (See [#2691](https://github.com/FormidableLabs/urql/pull/2691), [#2692](https://github.com/FormidableLabs/urql/pull/2692), and [#2732](https://github.com/FormidableLabs/urql/pull/2732))
- @urql/core@3.0.4
## 5.0.1
### Patch Changes
- Adjust timing of when an introspected schema will be processed into field maps, interface maps, and union type maps. By making this lazy we can avoid excessive work when these maps aren't actually ever used, by [@kitten](https://github.com/kitten) (See [#2640](https://github.com/FormidableLabs/urql/pull/2640))
## 5.0.0
### Major Changes
- **Goodbye IE11!** 👋 This major release removes support for IE11. All code that is shipped will be transpiled much less and will _not_ be ES5-compatible anymore, by [@kitten](https://github.com/kitten) (See [#2504](https://github.com/FormidableLabs/urql/pull/2504))
- Prevent cache-hydration from buffering operations, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2612](https://github.com/FormidableLabs/urql/pull/2612))
- Implement stricter variables types, which require variables to always be passed and match TypeScript types when the generic is set or inferred. This is a breaking change for TypeScript users potentially, unless all types are adhered to, by [@kitten](https://github.com/kitten) (See [#2607](https://github.com/FormidableLabs/urql/pull/2607))
- Upgrade to [Wonka v6](https://github.com/0no-co/wonka) (`wonka@^6.0.0`), which has no breaking changes but is built to target ES2015 and comes with other minor improvements.
The library has fully been migrated to TypeScript which will hopefully help with making contributions easier!, by [@kitten](https://github.com/kitten) (See [#2504](https://github.com/FormidableLabs/urql/pull/2504))
### Minor Changes
- Remove the `babel-plugin-modular-graphql` helper, this because the graphql package hasn't converted to ESM yet which gives issues in node environments, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2551](https://github.com/FormidableLabs/urql/pull/2551))
- Allow passing in `fragmentName` for `write` and `read` operations, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2609](https://github.com/FormidableLabs/urql/pull/2609))
### Patch Changes
- Graphcache's `optimistic` option now accepts optimistic mutation resolvers that return fields by
name rather than alias. Previously, depending on which mutation was run, the optimistic resolvers
would read your optimistic data by field alias (i.e. "alias" for `alias: id` rather than "id").
Instead, optimistic updates now correctly use field names and allow you to also pass resolvers as
values on your optimistic config, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2616](https://github.com/FormidableLabs/urql/pull/2616))
- Updated dependencies (See [#2551](https://github.com/FormidableLabs/urql/pull/2551), [#2504](https://github.com/FormidableLabs/urql/pull/2504), [#2619](https://github.com/FormidableLabs/urql/pull/2619), [#2607](https://github.com/FormidableLabs/urql/pull/2607), and [#2504](https://github.com/FormidableLabs/urql/pull/2504))
- @urql/core@3.0.0
## 4.4.3
### Patch Changes
- Correctly reorder optimistic layers when we see repeated keys coming in, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2489](https://github.com/FormidableLabs/urql/pull/2489))
## 4.4.2
### Patch Changes
- Keep track of mutations in the offline exchange so we can accurately recreate the original variables, there could be more variables for use in updater functions which we strip away in graphCache before sending to the API, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2472](https://github.com/FormidableLabs/urql/pull/2472))
## 4.4.1
### Patch Changes
- Switch `isFragmentHeuristicallyMatching()` to always return `true` for writes, so that we give every fragment a chance to be applied and to write to the cache, by [@kitten](https://github.com/kitten) (See [#2455](https://github.com/FormidableLabs/urql/pull/2455))
- ⚠️ Fix default storage persisting data after `clear()` was called on it, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#2458](https://github.com/FormidableLabs/urql/pull/2458))
- Updated dependencies (See [#2446](https://github.com/FormidableLabs/urql/pull/2446), [#2456](https://github.com/FormidableLabs/urql/pull/2456), and [#2457](https://github.com/FormidableLabs/urql/pull/2457))
- @urql/core@2.5.0
## 4.4.0
### Minor Changes
- Fix issues with continuously updating operations (i.e. subscriptions and `hasNext: true` queries) by shifting their layers in Graphcache behind those that are still awaiting a result. This causes continuous updates to not overwrite one-off query results while still keeping continuously updating operations at the highest possible layer, by [@kitten](https://github.com/kitten) (See [#2419](https://github.com/FormidableLabs/urql/pull/2419))
### Patch Changes
- ⚠️ Fix ignore empty relay edges when using the `relayPagination` resolver, by [@tgriesser](https://github.com/tgriesser) (See [#2431](https://github.com/FormidableLabs/urql/pull/2431))
- Prevent creating unnecessary layers, which should improve performance slightly, by [@kitten](https://github.com/kitten) (See [#2419](https://github.com/FormidableLabs/urql/pull/2419))
## 4.3.6
### Patch Changes
- Extend peer dependency range of `graphql` to include `^16.0.0`.
As always when upgrading across many packages of `urql`, especially including `@urql/core` we recommend you to deduplicate dependencies after upgrading, using `npm dedupe` or `npx yarn-deduplicate`, by [@kitten](https://github.com/kitten) (See [#2133](https://github.com/FormidableLabs/urql/pull/2133))
- Updated dependencies (See [#2133](https://github.com/FormidableLabs/urql/pull/2133))
- @urql/core@2.3.6
## 4.3.5
### Patch Changes
- ⚠️ Fix regression from [#1869](https://github.com/FormidableLabs/urql/pull/1869) that caused nullable lists to always cause a cache miss, if schema awareness is enabled, by [@kitten](https://github.com/kitten) (See [#1983](https://github.com/FormidableLabs/urql/pull/1983))
- Updated dependencies (See [#1985](https://github.com/FormidableLabs/urql/pull/1985))
- @urql/core@2.3.3
## 4.3.4
### Patch Changes
- Improve perf by using String.indexOf in getField, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1957](https://github.com/FormidableLabs/urql/pull/1957))
- Updated dependencies (See [#1944](https://github.com/FormidableLabs/urql/pull/1944))
- @urql/core@2.3.2
## 4.3.3
### Patch Changes
- Remove `hasNext: true` flag from stale responses. This was erroneously added in debugging, but leads to stale responses being marked with `hasNext`, which means the `dedupExchange` will keep waiting for further network responses, by [@kitten](https://github.com/kitten) (See [#1911](https://github.com/FormidableLabs/urql/pull/1911))
## 4.3.2
### Patch Changes
- Cleanup the previous `onOnline` event-listener when called again, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1896](https://github.com/FormidableLabs/urql/pull/1896))
## 4.3.1
### Patch Changes
- ⚠️ Fix previous results' `null` values spilling into the next result that Graphcache issues, which may prevent updates from being issued until the query is reexecuted. This was affecting any `null` links on data, and any queries that were issued before non-optimistic mutations, by [@kitten](https://github.com/kitten) (See [#1885](https://github.com/FormidableLabs/urql/pull/1885))
- Updated dependencies (See [#1870](https://github.com/FormidableLabs/urql/pull/1870) and [#1880](https://github.com/FormidableLabs/urql/pull/1880))
- @urql/core@2.3.1
## 4.3.0
### Minor Changes
- Improve referential equality of deeply queried objects from the normalised cache for queries. Each query operation will now reuse the last known result and only incrementally change references as necessary, scanning over the previous result to identify whether anything has changed.
This should help improve the performance of processing updates in UI frameworks (e.g. in React with `useMemo` or `React.memo`). (See [#1859](https://github.com/FormidableLabs/urql/pull/1859))
- Add **experimental** support for `@defer` and `@stream` responses for GraphQL. This implements the ["GraphQL Defer and Stream Directives"](https://github.com/graphql/graphql-spec/blob/4fd39e0/rfcs/DeferStream.md) and ["Incremental Delivery over HTTP"](https://github.com/graphql/graphql-over-http/blob/290b0e2/rfcs/IncrementalDelivery.md) specifications. If a GraphQL API supports `multipart/mixed` responses for deferred and streamed delivery of GraphQL results, `@urql/core` (and all its derived fetch implementations) will attempt to stream results. This is _only supported_ on browsers [supporting streamed fetch responses](https://developer.mozilla.org/en-US/docs/Web/API/Response/body), which excludes IE11.
The implementation of streamed multipart responses is derived from [`meros` by `@maraisr`](https://github.com/maraisr/meros), and is subject to change if the RFCs end up changing, by [@kitten](https://github.com/kitten) (See [#1854](https://github.com/FormidableLabs/urql/pull/1854))
### Patch Changes
- ⚠️ Fix missing values cascading into lists causing a `null` item without the query being marked as stale and fetched from the API. This would happen in schema awareness when a required field, which isn't cached, cascades into a nullable list, by [@kitten](https://github.com/kitten) (See [#1869](https://github.com/FormidableLabs/urql/pull/1869))
- Updated dependencies (See [#1854](https://github.com/FormidableLabs/urql/pull/1854))
- @urql/core@2.3.0
## 4.2.1
### Patch Changes
- ⚠️ Fix issue where operations that get dispatched synchronously after the cache restoration completes get forgotten, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1789](https://github.com/FormidableLabs/urql/pull/1789))
## 4.2.0
### Minor Changes
- Fixed typing of OptimisticMutationResolver, by [@taneba](https://github.com/taneba) (See [#1765](https://github.com/FormidableLabs/urql/pull/1765))
### Patch Changes
- Type the `relayPagination` and `simplePagination` helpers return value as `Resolver` as there's no way to match them consistently to either generated or non-generated resolver types anymore, by [@kitten](https://github.com/kitten) (See [#1778](https://github.com/FormidableLabs/urql/pull/1778))
- Updated dependencies (See [#1776](https://github.com/FormidableLabs/urql/pull/1776) and [#1755](https://github.com/FormidableLabs/urql/pull/1755))
- @urql/core@2.1.5
## 4.1.4
### Patch Changes
- Apply [`bivarianceHack`](https://stackoverflow.com/questions/52667959/what-is-the-purpose-of-bivariancehack-in-typescript-types) in the `graphcache` types to better support code-generated configs, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1687](https://github.com/FormidableLabs/urql/pull/1687))
- Updated dependencies (See [#1709](https://github.com/FormidableLabs/urql/pull/1709))
- @urql/core@2.1.4
## 4.1.3
### Patch Changes
- ⚠️ Fix: add the `ENTRIES_STORE_NAME` to the clear transaction, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1685](https://github.com/FormidableLabs/urql/pull/1685))
- Updated dependencies (See [#1695](https://github.com/FormidableLabs/urql/pull/1695))
- @urql/core@2.1.3
## 4.1.2
### Patch Changes
- Loosen type constraint on `ScalarObject` to account for custom scalar deserialization like `Date` for `DateTime`s, by [@kitten](https://github.com/kitten) (See [#1648](https://github.com/FormidableLabs/urql/pull/1648))
- Loosen the typing constraint on the cacheExchange generic, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1675](https://github.com/FormidableLabs/urql/pull/1675))
## 4.1.1
### Patch Changes
- ⚠️ Fix an edge-case for which an introspection query during runtime could fail when schema-awareness was enabled in Graphcache, since built-in types weren't recognised as existent, by [@kitten](https://github.com/kitten) (See [#1631](https://github.com/FormidableLabs/urql/pull/1631))
## 4.1.0
### Minor Changes
- Add `cache.link(...)` method to Graphcache. This method may be used in updaters to update links in the cache. It is hence the writing-equivalent of `cache.resolve()`, which previously didn't have any equivalent as such, which meant that only `cache.updateQuery` or `cache.writeFragment` could be used, even to update simple relations, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1551](https://github.com/FormidableLabs/urql/pull/1551))
- Add on a generic to `cacheExchange` and `offlineExchange` for future, experimental type-generation support, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1562](https://github.com/FormidableLabs/urql/pull/1562))
### Patch Changes
- ⚠️ Fix up internal types in Graphcache to improve their accuracy for catching more edge cases in its implementation. This only affects you if you previously imported any type related to `ScalarObject` from Graphcache which now is a more opaque type. We've also adjusted the `NullArray` types to be potentially nested, since lists in GraphQL can be nested arbitarily, which we were covering but didn't reflect in our types, by [@kitten](https://github.com/kitten) (See [#1591](https://github.com/FormidableLabs/urql/pull/1591))
- Remove closure-compiler from the build step (See [#1570](https://github.com/FormidableLabs/urql/pull/1570))
- ⚠️ Fix list items being returned as `null` even for non-nullable lists, when the entities are missing in the cache. This could happen when a resolver was added returning entities or their keys. This behaviour is now (correctly) only applied to partial results with schema awareness, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1566](https://github.com/FormidableLabs/urql/pull/1566))
- Allow for the schema subscription and mutationType to be null, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1530](https://github.com/FormidableLabs/urql/pull/1530))
- Updated dependencies (See [#1570](https://github.com/FormidableLabs/urql/pull/1570), [#1509](https://github.com/FormidableLabs/urql/pull/1509), [#1600](https://github.com/FormidableLabs/urql/pull/1600), and [#1515](https://github.com/FormidableLabs/urql/pull/1515))
- @urql/core@2.1.0
## 4.0.0
### Major Changes
- Add improved error awareness to Graphcache. When Graphcache now receives a `GraphQLError` (via a `CombinedError`) it checks whether the `GraphQLError`'s `path` matches up with `null` values in the `data`. Any `null` values that the write operation now sees in the data will be replaced with a "cache miss" value (i.e. `undefined`) when it has an associated error. This means that errored fields from your GraphQL API will be marked as uncached and won't be cached. Instead the client will now attempt a refetch of the data so that errors aren't preventing future refetches or with schema awareness it will attempt a refetch automatically. Additionally, the `updates` functions will now be able to check whether the current field has any errors associated with it with `info.error`, by [@kitten](https://github.com/kitten) (See [#1356](https://github.com/FormidableLabs/urql/pull/1356))
### Minor Changes
- Allow `schema` option to be passed with a partial introspection result that only contains `queryType`, `mutationType`, and `subscriptionType` with their respective names. This allows you to pass `{ __schema: { queryType: { name: 'Query' } } }` and the likes to Graphcache's `cacheExchange` to alter the default root names without enabling full schema awareness, by [@kitten](https://github.com/kitten) (See [#1379](https://github.com/FormidableLabs/urql/pull/1379))
### Patch Changes
- Updated dependencies (See [#1374](https://github.com/FormidableLabs/urql/pull/1374), [#1357](https://github.com/FormidableLabs/urql/pull/1357), and [#1375](https://github.com/FormidableLabs/urql/pull/1375))
- @urql/core@2.0.0
## 3.4.0
### Minor Changes
- Warn when using an interface or union field in the graphCache resolvers config, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1304](https://github.com/FormidableLabs/urql/pull/1304))
### Patch Changes
- ⚠️ Fix edge-case where query results would pick up invalidated fields from mutation results as they're written to the cache. This would cause invalid cache misses although the result was expected to just be passed through from the API result, by [@kitten](https://github.com/kitten) (See [#1300](https://github.com/FormidableLabs/urql/pull/1300))
- ⚠️ Fix a Relay Pagination edge case where overlapping ends of pages queried using the `last` argument would be in reverse order, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1311](https://github.com/FormidableLabs/urql/pull/1311))
## 3.3.4
### Patch Changes
- ⚠️ Fix, add null as a possible type for the variables argument in `cache.invalidate`, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#1269](https://github.com/FormidableLabs/urql/pull/1269))
## 3.3.3
### Patch Changes
- Update `cache.resolve(parent, ...)` case to enable _even more_ cases, for instance where `parent.__typename` isn't set yet. This was intended to be enabled in the previous patch but has been forgotten, by [@kitten](https://github.com/kitten) (See [#1219](https://github.com/FormidableLabs/urql/pull/1219))
- Deprecate `cache.resolveFieldByKey` in favour of `cache.resolve`, which functionally was already able to do the same, by [@kitten](https://github.com/kitten) (See [#1219](https://github.com/FormidableLabs/urql/pull/1219))
- Updated dependencies (See [#1225](https://github.com/FormidableLabs/urql/pull/1225))
- @urql/core@1.16.1
## 3.3.2
### Patch Changes
- Update `cache` methods, for instance `cache.resolve`, to consistently accept the `parent` argument from `resolvers` and `updates` and alias it to the parent's key (which is usually found on `info.parentKey`). This usage of `cache.resolve(parent, ...)` was intuitive and is now supported as expected, by [@kitten](https://github.com/kitten) (See [#1208](https://github.com/FormidableLabs/urql/pull/1208))
## 3.3.1
### Patch Changes
- ⚠️ Fix reusing original query data from APIs accidentally, which can lead to subtle mismatches in results when the API's incoming `query` results are being updated by the `cacheExchange`, to apply resolvers. Specifically this may lead to relations from being set back to `null` when the resolver returns a different list of links than the result, since some `null` relations may unintentionally exist but aren't related. If you're using `relayPagination` then this fix is critical, by [@kitten](https://github.com/kitten) (See [#1196](https://github.com/FormidableLabs/urql/pull/1196))
## 3.3.0
### Minor Changes
- Increase the consistency of when and how the `__typename` field is added to results. Instead of
adding it by default and automatically first, the `__typename` field will now be added along with
the usual selection set. The `write` operation now automatically issues a warning if `__typename`
isn't present where it's expected more often, which helps in debugging. Also the `__typename` field
may now not proactively be added to root results, e.g. `"Query"`, by [@kitten](https://github.com/kitten) (See [#1185](https://github.com/FormidableLabs/urql/pull/1185))
### Patch Changes
- Replace `graphql/utilities/buildClientSchema.mjs` with a custom-tailored, lighter implementation
built into `@urql/exchange-graphcache`. This will appear to increase its size by about `0.2kB gzip`
but will actually save around `8.5kB gzip` to `9.4kB gzip` in any production bundle by using less of
`graphql`'s code, by [@kitten](https://github.com/kitten) (See [#1189](https://github.com/FormidableLabs/urql/pull/1189))
- Updated dependencies (See [#1187](https://github.com/FormidableLabs/urql/pull/1187), [#1186](https://github.com/FormidableLabs/urql/pull/1186), and [#1186](https://github.com/FormidableLabs/urql/pull/1186))
- @urql/core@1.16.0
## 3.2.0
### Minor Changes
- Add a `mergeMode: 'before' | 'after'` option to the `simplePagination` helper to define whether pages are merged before or after preceding ones when pagination, similar to `relayPagination`'s option, by [@hoangvvo](https://github.com/hoangvvo) (See [#1174](https://github.com/FormidableLabs/urql/pull/1174))
### Patch Changes
- Updated dependencies (See [#1168](https://github.com/FormidableLabs/urql/pull/1168))
- @urql/core@1.15.2
## 3.1.11
### Patch Changes
- Add support for `TypedDocumentNode` to infer the type of the `OperationResult` and `Operation` for all methods, functions, and hooks that either directly or indirectly accept a `DocumentNode`. See [`graphql-typed-document-node` and the corresponding blog post for more information.](https://github.com/dotansimha/graphql-typed-document-node), by [@kitten](https://github.com/kitten) (See [#1113](https://github.com/FormidableLabs/urql/pull/1113))
- Updated dependencies (See [#1119](https://github.com/FormidableLabs/urql/pull/1119), [#1113](https://github.com/FormidableLabs/urql/pull/1113), [#1104](https://github.com/FormidableLabs/urql/pull/1104), and [#1123](https://github.com/FormidableLabs/urql/pull/1123))
- @urql/core@1.15.0
## 3.1.10
### Patch Changes
- ⚠️ Fix a stray `operationName` deprecation warning in `@urql/exchange-graphcache`'s exchange logic, which adds the `meta.cacheOutcome` field to the operation's context, by [@kitten](https://github.com/kitten) (See [#1103](https://github.com/FormidableLabs/urql/pull/1103))
## 3.1.9
### Patch Changes
- ⚠️ Fix the production build overwriting the development build. Specifically in the previous release we mistakenly replaced all development bundles with production bundles. This doesn't have any direct influence on how these packages work, but prevented development warnings from being logged or full errors from being thrown, by [@kitten](https://github.com/kitten) (See [#1097](https://github.com/FormidableLabs/urql/pull/1097))
- Updated dependencies (See [#1097](https://github.com/FormidableLabs/urql/pull/1097))
- @urql/core@1.14.1
## 3.1.8
### Patch Changes
- Add missing `.mjs` extension to all imports from `graphql` to fix Webpack 5 builds, which require extension-specific import paths for ESM bundles and packages. **This change allows you to safely upgrade to Webpack 5.**, by [@kitten](https://github.com/kitten) (See [#1094](https://github.com/FormidableLabs/urql/pull/1094))
- Deprecate the `Operation.operationName` property in favor of `Operation.kind`. This name was
previously confusing as `operationName` was effectively referring to two different things. You can
safely upgrade to this new version, however to mute all deprecation warnings you will have to
**upgrade** all `urql` packages you use. If you have custom exchanges that spread operations, please
use [the new `makeOperation` helper
function](https://formidable.com/open-source/urql/docs/api/core/#makeoperation) instead, by [@bkonkle](https://github.com/bkonkle) (See [#1045](https://github.com/FormidableLabs/urql/pull/1045))
- Updated dependencies (See [#1094](https://github.com/FormidableLabs/urql/pull/1094) and [#1045](https://github.com/FormidableLabs/urql/pull/1045))
- @urql/core@1.14.0
## 3.1.7
### Patch Changes
- Enforce atomic optimistic updates so that optimistic layers are cleared before they're reapplied. This is important for instance when an optimistic update is performed while offline and then reapplied while online, which would previously repeat the optimistic update on top of its past data changes, by [@kitten](https://github.com/kitten) (See [#1080](https://github.com/FormidableLabs/urql/pull/1080))
## 3.1.6
### Patch Changes
- ⚠️ Fix optimistic updates not being allowed to be cumulative and apply on top of each other. Previously in [#866](https://github.com/FormidableLabs/urql/pull/866) we explicitly deemed this as unsafe which isn't correct anymore given that concrete, non-optimistic updates are now never applied on top of optimistic layers, by [@kitten](https://github.com/kitten) (See [#1074](https://github.com/FormidableLabs/urql/pull/1074))
## 3.1.5
### Patch Changes
- Changes some internals of how selections are iterated over and remove some private exports. This will have no effect or fixes on how Graphcache functions, but may improve some minor performance characteristics of large queries, by [@kitten](https://github.com/kitten) (See [#1060](https://github.com/FormidableLabs/urql/pull/1060))
## 3.1.4
### Patch Changes
- ⚠️ Fix inline fragments being skipped when they were missing a full type condition as per the GraphQL spec (e.g `{ ... { field } }`), by [@kitten](https://github.com/kitten) (See [#1040](https://github.com/FormidableLabs/urql/pull/1040))
## 3.1.3
### Patch Changes
- ⚠️ Fix a case where the `offlineExchange` would not start processing operations after hydrating persisted data when no operations arrived in time by the time the persisted data was restored. This would be more evident in Preact and Svelte due to their internal short timings, by [@kitten](https://github.com/kitten) (See [#1019](https://github.com/FormidableLabs/urql/pull/1019))
## 3.1.2
### Patch Changes
- ⚠️ Fix small pieces of code where polyfill-less ES5 usage was compromised. This was unlikely to have affected anyone in production as `Array.prototype.find` (the only usage of an ES6 method) is commonly used and polyfilled, by [@kitten](https://github.com/kitten) (See [#991](https://github.com/FormidableLabs/urql/pull/991))
- ⚠️ Fix queries that have erroed with a `NetworkError` (`isOfflineError`) not flowing back completely through the `cacheExchange`.
These queries should also now be reexecuted when the client comes back online, by [@kitten](https://github.com/kitten) (See [#1011](https://github.com/FormidableLabs/urql/pull/1011))
- Updated dependencies (See [#1011](https://github.com/FormidableLabs/urql/pull/1011))
- @urql/core@1.13.1
## 3.1.1
### Patch Changes
- ⚠️ Fix updaters config not working when Mutation/Subscription root names were altered.
For instance, a Mutation named `mutation_root` could cause `store.updates` to be misread and cause a
runtime error, by [@kitten](https://github.com/kitten) (See [#984](https://github.com/FormidableLabs/urql/pull/984))
- ⚠️ Fix operation results being obstructed by the `offlineExchange` when the network request has failed due to being offline and no cache result has been issued. Instead the `offlineExchange` will now retry with `cache-only` policy, by [@kitten](https://github.com/kitten) (See [#985](https://github.com/FormidableLabs/urql/pull/985))
## 3.1.0
### Minor Changes
- Add support for `nodes` fields to the `relayPagination` helper, instead of only supporting the standard `edges`. (See [#897](https://github.com/FormidableLabs/urql/pull/897))
### Patch Changes
- Updated dependencies (See [#947](https://github.com/FormidableLabs/urql/pull/947), [#962](https://github.com/FormidableLabs/urql/pull/962), and [#957](https://github.com/FormidableLabs/urql/pull/957))
- @urql/core@1.13.0
## 3.0.2
### Patch Changes
- Add special-case for fetching an introspection result in our schema-checking, this avoids an error when urql-devtools fetches the backend graphql schema, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#893](https://github.com/FormidableLabs/urql/pull/893))
- Mute warning when using built-in GraphQL fields, like `__type`, by [@kitten](https://github.com/kitten) (See [#919](https://github.com/FormidableLabs/urql/pull/919))
- ⚠️ Fix return type for resolvers to allow data objects to be returned with `__typename` as expected, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#927](https://github.com/FormidableLabs/urql/pull/927))
- Updated dependencies (See [#911](https://github.com/FormidableLabs/urql/pull/911) and [#908](https://github.com/FormidableLabs/urql/pull/908))
- @urql/core@1.12.3
## 3.0.1
### Patch Changes
- Add warning for queries that traverse an Operation Root Type (Mutation / Subscription types occuring in a query result), by [@kitten](https://github.com/kitten) (See [#859](https://github.com/FormidableLabs/urql/pull/859))
- ⚠️ Fix storage implementation not preserving deleted values correctly or erroneously checking optimistically written entries for changes. This is fixed by adding a new default serializer to the `@urql/exchange-graphcache/default-storage` implementation, which will be incompatible with the old one, by [@kitten](https://github.com/kitten) (See [#866](https://github.com/FormidableLabs/urql/pull/866))
- Replace unnecessary `scheduleTask` polyfill with inline `Promise.resolve().then(fn)` calls, by [@kitten](https://github.com/kitten) (See [#861](https://github.com/FormidableLabs/urql/pull/861))
- Updated dependencies (See [#860](https://github.com/FormidableLabs/urql/pull/860) and [#861](https://github.com/FormidableLabs/urql/pull/861))
- @urql/core@1.12.1
## 3.0.0
This major release comes with a couple of fixes and new **experimental offline support**, which
we're very excited for! Please give it a try if your application is targeting Offline First!
To migrate to this new major version, check the major breaking changes below. Mainly you will have
to watch out for `cache.invalidateQuery` which has been removed. Instead you should now invalidate
individual entities and fields using `cache.invalidate`. [Learn more about this method on our
docs.](https://formidable.com/open-source/urql/docs/graphcache/custom-updates/#cacheinvalidate)
### Major Changes
- Remove the deprecated `populateExchange` export from `@urql/exchange-graphcache`.
If you're using the `populateExchange`, please install the separate `@urql/exchange-populate` package and import it from there, by [@kitten](https://github.com/kitten) (See [#840](https://github.com/FormidableLabs/urql/pull/840))
- The deprecated `cache.invalidateQuery()` method has been removed. Please migrate over to `cache.invalidate()` instead, which operates on individual fields instead of queries, by [@kitten](https://github.com/kitten) (See [#840](https://github.com/FormidableLabs/urql/pull/840))
### Minor Changes
- Implement experimental Offline Support in Graphcache.
[Read more about how to use the Offline Support in our docs.](https://formidable.com/open-source/urql/docs/graphcache/offline/), by [@kitten](https://github.com/kitten) (See [#793](https://github.com/FormidableLabs/urql/pull/793))
- Issue warnings when an unknown type or field has been included in Graphcache's `opts` configuration to help spot typos.
Checks `opts.keys`, `opts.updates`, `opts.resolvers` and `opts.optimistic`. (See [#820](https://github.com/FormidableLabs/urql/pull/820) and [#826](https://github.com/FormidableLabs/urql/pull/826))
### Patch Changes
- ⚠️ Fix resolvers being executed for data even when data is currently written. This behaviour could lead to interference with custom updaters that update fragments or queries, e.g. an updater that was receiving paginated data due to a pagination resolver. We've determined that generally it is undesirable to have any resolvers run during the cache update (writing) process, since it may lead to resolver data being accidentally written to the cache or for resolvers to interfere with custom user updates, by [@olistic](https://github.com/olistic) (See [#812](https://github.com/FormidableLabs/urql/pull/812))
- Upgrade to a minimum version of wonka@^4.0.14 to work around issues with React Native's minification builds, which use uglify-es and could lead to broken bundles, by [@kitten](https://github.com/kitten) (See [#842](https://github.com/FormidableLabs/urql/pull/842))
- Updated dependencies (See [#838](https://github.com/FormidableLabs/urql/pull/838) and [#842](https://github.com/FormidableLabs/urql/pull/842))
- @urql/core@1.12.0
## 2.4.2
### Patch Changes
- Add `source` debug name to all `dispatchDebug` calls during build time to identify events by which exchange dispatched them, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#780](https://github.com/FormidableLabs/urql/pull/780))
- ⚠️ Fix Introspection Queries (or internal types in general) triggering lots of warnings for unkeyed entities, by [@kitten](https://github.com/kitten) (See [#779](https://github.com/FormidableLabs/urql/pull/779))
- Updated dependencies (See [#780](https://github.com/FormidableLabs/urql/pull/780))
- @urql/core@1.11.7
## 2.4.1
### Patch Changes
- Add a `"./package.json"` entry to the `package.json`'s `"exports"` field for Node 14. This seems to be required by packages like `rollup-plugin-svelte` to function properly, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#771](https://github.com/FormidableLabs/urql/pull/771))
- ⚠️ Fix traversal issue, where when a prior selection set has set a nested result field to `null`, a subsequent traversal of this field attempts to access `prevData` on `null`, by [@kitten](https://github.com/kitten) (See [#772](https://github.com/FormidableLabs/urql/pull/772))
- Updated dependencies (See [#771](https://github.com/FormidableLabs/urql/pull/771) and [#771](https://github.com/FormidableLabs/urql/pull/771))
- @urql/exchange-populate@0.1.7
- @urql/core@1.11.6
## 2.4.0
This release heavily improves on the intuitiveness of how Optimistic Updates work. It ensures that
optimistic updates aren't accidentally discarded, by temporarily blocking some refetches when
necessary. It also prevents optimistic mutation updates from becoming permanent, which could
previously happen if an updater read optimistic data and rewrote it again. This isn't possible
anymore as mutation results are applied as a batch.
### Minor Changes
- Implement refetch blocking for queries that are affected by optimistic update. When a query would normally be refetched, either because it was partial or a cache-and-network operation, we now wait if it touched optimistic data for that optimistic mutation to complete. This prevents optimistic update data from unexpectedly disappearing, by [@kitten](https://github.com/kitten) (See [#750](https://github.com/FormidableLabs/urql/pull/750))
- Implement optimistic mutation result flushing. Mutation results for mutation that have had optimistic updates will now wait for all optimistic mutations to complete at the same time before being applied to the cache. This sometimes does delay cache updates to until after multiple mutations have completed, but it does prevent optimistic data from being accidentally committed permanently, which is more intuitive, by [@kitten](https://github.com/kitten) (See [#750](https://github.com/FormidableLabs/urql/pull/750))
### Patch Changes
- Adjust mutation results priority to always override query results as they arrive, similarly to subscriptions. This will prevent race conditions when mutations are slow to execute at the cost of some consistency, by [@kitten](https://github.com/kitten) (See [#745](https://github.com/FormidableLabs/urql/pull/745))
- Improve warning and error console output in development by cleaning up the GraphQL trace stack, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#751](https://github.com/FormidableLabs/urql/pull/751))
## 2.3.8
### Patch Changes
Sorry for the many updates; Please only upgrade to `>=2.3.8` and don't use the deprecated `2.3.7`
and `2.3.6` release.
- ⚠️ Fix nested package path for @urql/core/internal and @urql/exchange-graphcache/extras, by [@kitten](https://github.com/kitten) (See [#734](https://github.com/FormidableLabs/urql/pull/734))
- Updated dependencies (See [#734](https://github.com/FormidableLabs/urql/pull/734))
- @urql/core@1.11.4
## 2.3.7
### Patch Changes
- Make the extension of the main export unknown, which fixes a Webpack issue where the resolver won't pick `module` fields in `package.json` files once it's importing from another `.mjs` file, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#733](https://github.com/FormidableLabs/urql/pull/733))
- Updated dependencies (See [#733](https://github.com/FormidableLabs/urql/pull/733))
- @urql/core@1.11.2
## 2.3.5
### Patch Changes
- ⚠️ Fix data persistence for embedded fields, by [@kitten](https://github.com/kitten) (See [#727](https://github.com/FormidableLabs/urql/pull/727))
## 2.3.4
### Patch Changes
- Add debugging events to exchanges that add more detailed information on what is happening
internally, which will be displayed by devtools like the urql [Chrome / Firefox extension](https://github.com/FormidableLabs/urql-devtools), by [@andyrichardson](https://github.com/andyrichardson) (See [#608](https://github.com/FormidableLabs/urql/pull/608))
- ⚠️ Fix persistence using special tab character in serialized keys and add sanitization to persistence key serializer, by [@kitten](https://github.com/kitten) (See [#715](https://github.com/FormidableLabs/urql/pull/715))
- Updated dependencies (See [#608](https://github.com/FormidableLabs/urql/pull/608), [#718](https://github.com/FormidableLabs/urql/pull/718), and [#722](https://github.com/FormidableLabs/urql/pull/722))
- @urql/core@1.11.0
## 2.3.3
### Patch Changes
- ⚠️ Fix @urql/exchange-populate visitWithTypeInfo import by bumping babel-plugin-modular-graphql, by [@kitten](https://github.com/kitten) (See [#709](https://github.com/FormidableLabs/urql/pull/709))
- Updated dependencies (See [#709](https://github.com/FormidableLabs/urql/pull/709))
- @urql/exchange-populate@0.1.6
## 2.3.2
### Patch Changes
- Pick modules from graphql package, instead of importing from graphql/index.mjs, by [@kitten](https://github.com/kitten) (See [#700](https://github.com/FormidableLabs/urql/pull/700))
- Change invalidation to check for undefined links since null is a valid value in graphql, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#694](https://github.com/FormidableLabs/urql/pull/694))
- Updated dependencies (See [#700](https://github.com/FormidableLabs/urql/pull/700))
- @urql/exchange-populate@0.1.5
- @urql/core@1.10.9
## 2.3.1
### Patch Changes
- Add graphql@^15.0.0 to peer dependency range, by [@kitten](https://github.com/kitten) (See [#688](https://github.com/FormidableLabs/urql/pull/688))
- Forcefully bump @urql/core package in all bindings and in @urql/exchange-graphcache.
We're aware that in some cases users may not have upgraded to @urql/core, even though that's within
the typical patch range. Since the latest @urql/core version contains a patch that is required for
`cache-and-network` to work, we're pushing another patch that now forcefully bumps everyone to the
new version that includes this fix, by [@kitten](https://github.com/kitten) (See [#684](https://github.com/FormidableLabs/urql/pull/684))
- Reimplement persistence support to take commutative layers into account, by [@kitten](https://github.com/kitten) (See [#674](https://github.com/FormidableLabs/urql/pull/674))
- Updated dependencies (See [#688](https://github.com/FormidableLabs/urql/pull/688) and [#678](https://github.com/FormidableLabs/urql/pull/678))
- @urql/exchange-populate@0.1.4
- @urql/core@1.10.8
## 2.3.0
### Minor Changes
- Support optimistic values for mutations without a selectionset, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#657](https://github.com/FormidableLabs/urql/pull/657))
### Patch Changes
- Refactor to replace dictionary-based (`Object.create(null)`) results with regular objects, by [@kitten](https://github.com/kitten) (See [#651](https://github.com/FormidableLabs/urql/pull/651))
- ⚠️ Fix case where a mutation-rootfield would cause an empty call to the cache.updates[mutationRootField], by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#654](https://github.com/FormidableLabs/urql/pull/654))
- Updated dependencies (See [#658](https://github.com/FormidableLabs/urql/pull/658) and [#650](https://github.com/FormidableLabs/urql/pull/650))
- @urql/core@1.10.5
## 2.2.8
### Patch Changes
- ⚠️ Fix node resolution when using Webpack, which experiences a bug where it only resolves
`package.json:main` instead of `module` when an `.mjs` file imports a package, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#642](https://github.com/FormidableLabs/urql/pull/642))
- Updated dependencies (See [#642](https://github.com/FormidableLabs/urql/pull/642))
- @urql/exchange-populate@0.1.3
- @urql/core@1.10.4
## 2.2.7
### Patch Changes
- ⚠️ Fix critical ordering bug in commutative queries and mutations. Subscriptions and queries would ad-hoc be receiving an empty optimistic layer accidentally. This leads to subscription results potentially being cleared, queries from being erased on a second write, and layers from sticking around on every second write or indefinitely. This affects versions `> 2.2.2` so please upgrade!, by [@kitten](https://github.com/kitten) (See [#638](https://github.com/FormidableLabs/urql/pull/638))
- ⚠️ Fix multipart conversion, in the `extract-files` dependency (used by multipart-fetch) there is an explicit check for the constructor property of an object. This made the files unretrievable, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#639](https://github.com/FormidableLabs/urql/pull/639))
- ⚠️ Fix Node.js Module support for v13 (experimental-modules) and v14. If your bundler doesn't support
`.mjs` files and fails to resolve the new version, please double check your configuration for
Webpack, or similar tools, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#637](https://github.com/FormidableLabs/urql/pull/637))
- Updated dependencies (See [#637](https://github.com/FormidableLabs/urql/pull/637))
- @urql/exchange-populate@0.1.2
- @urql/core@1.10.3
## 2.2.6
### Patch Changes
- ⚠️ Fix cache.inspectFields causing an undefined error for uninitialised or cleared commutative layers, by [@kitten](https://github.com/kitten) (See [#626](https://github.com/FormidableLabs/urql/pull/626))
- Improve Store constructor to accept an options object instead of separate arguments, identical to the cacheExchange options. (This is a patch, not a minor, since we consider Store part of the private API), by [@kitten](https://github.com/kitten) (See [#622](https://github.com/FormidableLabs/urql/pull/622))
- Allow a single field to be invalidated using cache.invalidate using two additional arguments, similar to store.resolve; This is a very small addition, so it's marked as a patch, by [@kitten](https://github.com/kitten) (See [#627](https://github.com/FormidableLabs/urql/pull/627))
- Prevent variables from being filtered and queries from being altered before they're forwarded, which prevented additional untyped variables from being used inside updater functions, by [@kitten](https://github.com/kitten) (See [#629](https://github.com/FormidableLabs/urql/pull/629))
- Expose generated result data on writeOptimistic and passthrough data on write operations, by [@kitten](https://github.com/kitten) (See [#613](https://github.com/FormidableLabs/urql/pull/613))
- Updated dependencies (See [#621](https://github.com/FormidableLabs/urql/pull/621))
- @urql/core@1.10.2
## 2.2.5
### Patch Changes
- Refactor parts of Graphcache for a minor performance boost and bundlesize reductions, by [@kitten](https://github.com/kitten) (See [#611](https://github.com/FormidableLabs/urql/pull/611))
## 2.2.4
### Patch Changes
- ⚠️ Fix Rollup bundle output being written to .es.js instead of .esm.js, by [@kitten](https://github.com/kitten) (See [#609](https://github.com/FormidableLabs/urql/pull/609))
- Updated dependencies (See [#609](https://github.com/FormidableLabs/urql/pull/609))
- @urql/core@1.10.1
## 2.2.3
### Patch Changes
- Apply commutative layers to all operations, so now including mutations and subscriptions, to ensure that unordered data is written in the correct order, by [@kitten](https://github.com/kitten) (See [#593](https://github.com/FormidableLabs/urql/pull/593))
- Updated dependencies (See [#607](https://github.com/FormidableLabs/urql/pull/607) and [#601](https://github.com/FormidableLabs/urql/pull/601))
- @urql/core@1.10.0
## 2.2.2
### Patch Changes
- ⚠️ Fix commutative layer edge case when lowest-priority layer comes back earlier than others, by [@kitten](https://github.com/kitten) (See [#587](https://github.com/FormidableLabs/urql/pull/587))
- Externalise @urql/exchange-populate from bundle, by [@kitten](https://github.com/kitten) (See [#590](https://github.com/FormidableLabs/urql/pull/590))
- ⚠️ Fix teardown events leading to broken commutativity, by [@kitten](https://github.com/kitten) (See [#588](https://github.com/FormidableLabs/urql/pull/588))
## 2.2.1
### Patch Changes
- Remove the shared package, this will fix the types file generation for graphcache, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#579](https://github.com/FormidableLabs/urql/pull/579))
- Updated dependencies (See [#577](https://github.com/FormidableLabs/urql/pull/577))
- @urql/core@1.9.2
## 2.2.0
### Minor Changes
- Add `cache.invalidate` to invalidate an entity directly to remove it from the cache and all subsequent cache results, e.g. `cache.invalidate({ __typename: 'Todo', id: 1 })`, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#566](https://github.com/FormidableLabs/urql/pull/566))
### Patch Changes
- ⚠️ Fix `cache-only` operations being forwarded and triggering fetch requests, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#551](https://github.com/FormidableLabs/urql/pull/551))
- Apply Query results in-order and commutatively even when results arrive out-of-order, by [@kitten](https://github.com/kitten) (See [#565](https://github.com/FormidableLabs/urql/pull/565))
- Updated dependencies (See [#551](https://github.com/FormidableLabs/urql/pull/551), [#542](https://github.com/FormidableLabs/urql/pull/542), and [#544](https://github.com/FormidableLabs/urql/pull/544))
- @urql/core@1.9.1
## 2.1.1
### Patch Changes
- Update the `updater` function type of `cache.updateQuery` to have a return type of `DataFields` so that `__typename` does not need to be defined, by [@JoviDeCroock](https://github.com/JoviDeCroock) (See [#538](https://github.com/FormidableLabs/urql/pull/538))
- ⚠️ Fix updates not being triggered when optimistic updates diverge from the actual result. (See [#160](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/160))
- Refactor away SchemaPredicates helper to reduce bundlesize. (See [#161](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/161))
- Ensure that pagination helpers don't confuse pages that have less params with a
query that has more params. (See [#156](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/156))
- Updated dependencies (See [#533](https://github.com/FormidableLabs/urql/pull/533), [#519](https://github.com/FormidableLabs/urql/pull/519), [#515](https://github.com/FormidableLabs/urql/pull/515), [#512](https://github.com/FormidableLabs/urql/pull/512), and [#518](https://github.com/FormidableLabs/urql/pull/518))
- @urql/core@1.9.0
## 2.1.0
This release adds support for cache persistence which is bringing us one step closer to
full offline-support, which we hope to bring you soon.
It also allows `wonka@^4.0.0` as a dependency to be compatible with [`urql@1.8.0`](https://github.com/FormidableLabs/urql/blob/master/CHANGELOG.md#v180). It also fixes a couple of issues in our
new `populateExchange`.
- Refactor internal store code and simplify `Store` (see [#134](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/134))
- ✨ Implement store persistence support (see [#137](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/137))
- ✨ Apply GC to store persistence (see [#138](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/138))
- Remove unused case where scalars are written from an API when links are expected (see [#142](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/142))
- ⚠️ Add support for resolvers causing cache misses (see [#143](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/143))
- ⚠️ Fix nested types (e.g. `[Item!]!`) in `populateExchange` (see [#150](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/150))
- Fix duplicate fragments in `populateExchange` output (see [#151](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/151))
- Allow `wonka@^3.2.1||^4.0.0` to be used (see [#153](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/153))
## 2.0.0
> **Note:** The minimum required version of `urql` for this release is now `1.7.0`!
**Christmas came early!** This version improves performance again by about 25% over `1.2.2`. It also
now ships with two new features: The `populateExchange` and automatic garbage collection.
Including the `populateExchange` is optional. It records all fragments in any active queries, and
populates mutation selection sets when the `@populate` directive is used based on typenames. If your
schema includes `viewer` fields on mutations, which resolve back to your `Query` type, you can use
this to automatically update your app's data when a mutation is made. _(More documentation on this
is coming soon!)_
The garbage collection works by utilising an automatic reference counting algorithm rather than
a mark & sweep algorithm. We feel this is the best tradeoff to maintain good performance during
runtime while minimising the data that is unnecessarily retained in-memory. You don't have to do
_anything_! Graphcache will do its newly added magic in the background.
There are some breaking changes, if you're using `cache.resolveConnections` or `resolveValueOrLink`
then you now need to use `inspectFields` and `resolveFieldByKey` instead. You may also now make
use of `cache.keyOfField`. (More info on [#128](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/128))
- ✨ Implement `populateExchange` (see [#120](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/120))
- Improve type safety of `invariant` and `warning` (see [#121](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/121))
- Reduce size of `populateExchange` (see [#122](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/122))
- Move more code to KVMap (see [#125](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/125))
- Move deletion to setting `undefined` instead (see [#126](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/126))
- Fix multiple edge cases in the `relayPagination` helper, by [@rafeca](https://github.com/rafeca) (see [#127](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/127))
- ✨⚠️ Reimplement data structure and add garbage collection (see [#128](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/128))
- Use Closure Compiler (see [#131](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/131))
- Switch to using `urql/core` on `1.7.0` (see [#132](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/132))
## 1.2.2
This patch replaces `pessimism` (our former underlying data structure) with a smaller implementation
that just uses `Map`s, since we weren't relying on any immutability internally. This cuts down
on bundlesize and massively on GC-pressure, which provides a large speedup on low-end devices.
- Replace Pessimism with mutable store to prevent excessive GC work (see [#117](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/117))
## 1.2.1
- Fix viewer fields (which return `Query` types) not being written or read correctly (see [#116](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/116))
## 1.2.0
- ⚠️ Fix unions not being checked supported by schema predicates, by [@StevenLangbroek](https://github.com/StevenLangbroek) (see [#113](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/113))
- ✨ Add `simplePagination` helper for resolving simple, paginated lists (see [#115](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/115))
## 1.1.2
- Fix `relayPagination` helper causing cache-misses for empty lists, by [@rafeca](https://github.com/rafeca) (see [#111](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/111))
## 1.1.1
This is a minor release since it increases the peer dependency of `urql` to `>= 1.6.0`, due to the addition
of the `stale` flag to partial responses and `cache-and-network` responses. This flag is useful to check
whether more requests are being made in the background by `@urql/exchange-graphcache`.
Additionally, this release adds a small stack to every error and warning that indicates where an
error has occured. It lists out the query and all subsequent fragments it has been traversing
so that errors and warnings can be traced more easily.
- Add a query/fragment stack to all errors and warnings (see [#107](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/107))
- Add `stale: true` to all `cache-and-network` and partial responses (see [#108](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/108))
## 1.0.3
- Fix `relayPagination` helper merging pages with different field arguments (see [#104](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/104))
## 1.0.2
- Deduplicate connections in `Store.writeConnection` when possible (see [#103](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/103))
- Fix early bail-out in `relayPagination` helper (see [#103](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/103))
## 1.0.1
- Trims down the size by 100 bytes (see [#96](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/96))
- Include the `/extras` build in the published version (see [#97](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/97))
- Invariant and warnings will now have an error code associated with a more elaborate explanation (see [#99](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/99))
- Invariant errors will now be included in your production bundle (see [#100](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/100))
- Fixes the relayPagination helper to correctly return partial results (see [#101](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/101))
- Add special case to relayPagination for first and last during inwards merge (see [#102](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/102))
## 1.0.0
> **Note:** The minimum required version of `urql` for this release is now `1.5.1`!
**Hooray it's `v1.0.0` time!** This doesn't mean that we won't be changing little things anymore, but we're so far happy with our API and trust Graphcache to work correctly. We will further iterate on this version with some **planned features**, like "fragment invalidation", garbage collection, and more.
This version refactors the **cache resolvers** and adds some new special powers to them! You can now return almost anything from cache resolvers and trust that it'll do the right thing:
- You can return entity keys, which will resolve the cached entities
- You can return keyable entities, which will also be resolved from cache
- You may also return unkeyable entities, which will be partially resolved from cache, with your resolved values taking precedence
This can also be nested, so that unkeyable entities can eventually lead back to normal, cached entities!
This has enabled us to expose the `relayPagination()` helper! This is a resolver that you can just drop into the `cacheExchange`'s `resolvers` config. It automatically does Relay-style pagination, which is now possible due to our more powerful resolvers! You can import it from `@urql/exchange-graphcache/extras`.
- ✨ Add full cache resolver traversal (see [#91](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/91))
- ✨ Add a new `relayPagination` helper (see [#91](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/91))
- Add a `Cache` interface with all methods (that are safe for userland) having documentation (see [#91](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/91))
- ⚠ Fix non-default root keys (that aren't just `Query`) not being respected (see [#87](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/87))
## 1.0.0-rc.11
- Fix `updates` not being called for `optimistic` results (see [#83](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/83))
- Add optional `variables` argument to `readFragment` and `writeFragment` (see [#84](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/84))
- ⚠ Fix field arguments not normalising optional `null` values (see [#85](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/85))
## 1.0.0-rc.10
- ⚠ Fix removing cache entries by upgrading to Pessimism `1.1.4` (see [ae72d3](https://github.com/FormidableLabs/urql-exchange-graphcache/commit/ae72d3b1c8b3e5965e122d5509eb561f68579474))
## 1.0.0-rc.9
- ⚠ Fix optimistic updates by upgrading to Pessimism `1.1.3` (see [#81](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/81))
## 1.0.0-rc.8
- Fix warnings being shown for Relay `Connection` and `Edge` embedded types (see [#79](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/79))
- Implement `readFragment` method on `Store` (see [#73](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/73))
- Implement `readQuery` method on `Store` (see [#73](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/73))
- Improve `writeFragment` method on `Store` (see [#73](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/73))
## 1.0.0-rc.7
- ⚠ Fix reexecuted operations due to dependencies not using `cache-first` (see [0bd58f6](https://github.com/FormidableLabs/urql-exchange-graphcache/commit/0bd58f6))
## 1.0.0-rc.6
- ⚠ Fix concurrency issue where a single operation is reexecuted multiple times (see [#70](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/70))
- Skip writing `undefined` to the cache and log a warning in development (see [#71](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/71))
- Allow `query` to be passed as a string to `store.updateQuery` (see [#72](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/72))
## 1.0.0-rc.5
- ⚠ Fix user-provided `keys` config not being able to return `null` (see [#68](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/68))
## 1.0.0-rc.4
- ⚠ Fix development warnings throwing an error for root fields (see [#65](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/65))
## 1.0.0-rc.3
_Note: This is release contains a bug that `v1.0.0-rc.4` fixes_
- Fix warning condition for missing entity keys (see [98287ae](https://github.com/FormidableLabs/urql-exchange-graphcache/commit/98287ae))
## 1.0.0-rc.2
_Note: This is release contains a bug that `v1.0.0-rc.3` fixes_
- Add warnings for unknown fields based on the schema and deduplicate warnings (see [#63](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/63))
## 1.0.0-rc.1
This is the first release that adds _schema awareness_. Passing a schema to Graphcache allows it to make deterministic
assumptions about the cached results it generates from its data. It can deterministically match fragments to interfaces,
instead of resorting to a heuristic, and it can provide _partial results_ for queries. With a `schema` passed to Graphcache,
as long as only nullable fields are uncached and missing, it will still provide an initial cached result.
- ✨ Add schema awareness using the `schema` option (see [#58](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/58))
- ✨ Allow for partial results to cascade missing values upwards (see [#59](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/59))
- Fix `store.keyOfEntity` not using root names from the schema (see [#62](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/62))
## 1.0.0-rc.0
This is where this CHANGELOG starts.
For a log on what happened in `beta` and `alpha` releases, please read the commit history.
================================================
FILE: exchanges/graphcache/README.md
================================================
@urql/exchange-graphcache
An exchange for normalized caching support in urql
`@urql/exchange-graphcache` is a normalized cache exchange for the [`urql`](https://github.com/urql-graphql/urql) GraphQL client.
This is a drop-in replacement for the default `cacheExchange` that, instead of document
caching, caches normalized data by keys and connections between data.
You can also pass your introspected GraphQL schema to the `cacheExchange`, which enables
it to deliver partial results and match fragments deterministically!
`urql` is already quite a comprehensive GraphQL client. However in several cases it may be
desirable to have data update across the entirety of an app when a response updates some
known pieces of data.
[Learn more about Graphcache and normalized caching on our docs!](https://formidable.com/open-source/urql/docs/graphcache/)
## Quick Start Guide
First install `@urql/exchange-graphcache` alongside `urql`:
```sh
yarn add @urql/exchange-graphcache
# or
npm install --save @urql/exchange-graphcache
```
You'll then need to add the `cacheExchange`, that this package exposes, to your `urql` Client,
by replacing the default cache exchange with it:
```js
import { createClient, fetchExchange } from 'urql';
import { cacheExchange } from '@urql/exchange-graphcache';
const client = createClient({
url: 'http://localhost:1234/graphql',
exchanges: [
// Replace the default cacheExchange with the new one
cacheExchange({
/* optional config */
}),
fetchExchange,
],
});
```
================================================
FILE: exchanges/graphcache/benchmarks/10000Reads.html
================================================
10000 Reads
================================================
FILE: exchanges/graphcache/benchmarks/10000ReadsComplex.html
================================================
10000 Reads Complex
================================================
FILE: exchanges/graphcache/benchmarks/10000Writes.html
================================================
10000 Writes
================================================
FILE: exchanges/graphcache/benchmarks/10000WritesComplex.html
================================================
10000 Writes Complex
================================================
FILE: exchanges/graphcache/benchmarks/1000Reads.html
================================================
1000 Reads
================================================
FILE: exchanges/graphcache/benchmarks/1000ReadsComplex.html
================================================
1000 Reads Complex
================================================
FILE: exchanges/graphcache/benchmarks/1000Writes.html
================================================
1000 Writes
================================================
FILE: exchanges/graphcache/benchmarks/1000WritesComplex.html
================================================
1000 Writes Complex
================================================
FILE: exchanges/graphcache/benchmarks/100Reads.html
================================================
100 Reads
================================================
FILE: exchanges/graphcache/benchmarks/100ReadsComplex.html
================================================
100 Reads Complex
================================================
FILE: exchanges/graphcache/benchmarks/100Writes.html
================================================
100 Writes
================================================
FILE: exchanges/graphcache/benchmarks/100WritesComplex.html
================================================
100 Writes Complex
================================================
FILE: exchanges/graphcache/benchmarks/50000Reads.html
================================================
50000 Reads
================================================
FILE: exchanges/graphcache/benchmarks/50000Writes.html
================================================
50000 Writes
================================================
FILE: exchanges/graphcache/benchmarks/5000Reads.html
================================================
5000 Writes
================================================
FILE: exchanges/graphcache/benchmarks/5000Writes.html
================================================
5000 Writes
================================================
FILE: exchanges/graphcache/benchmarks/500Reads.html
================================================
500 Writes
================================================
FILE: exchanges/graphcache/benchmarks/500Writes.html
================================================
500 Writes
================================================
FILE: exchanges/graphcache/benchmarks/addTodo.html
================================================
Add Todo
================================================
FILE: exchanges/graphcache/benchmarks/benchmarks.js
================================================
import urqlClient from './urqlClient.js';
import {
ALL_TODOS_QUERY,
ALL_WRITERS_QUERY,
ALL_BOOKS_QUERY,
ALL_STORES_QUERY,
ALL_EMPLOYEES_QUERY,
ALL_AUTHORS_QUERY,
ADD_TODO_MUTATION,
UPDATE_TODO_MUTATION,
ADD_TODOS_MUTATION,
ADD_WRITERS_MUTATION,
ADD_BOOKS_MUTATION,
ADD_STORES_MUTATION,
ADD_EMPLOYEES_MUTATION,
ADD_AUTHORS_MUTATION,
} from './operations.js';
// create functions that execute operations/queries/mutaitons to be benchmarked
export const getAllTodos = async () => {
const queryResult = await urqlClient.query(ALL_TODOS_QUERY).toPromise();
return queryResult.data.todos;
};
export const getAllWriters = async () => {
const queryResult = await urqlClient.query(ALL_WRITERS_QUERY).toPromise();
return queryResult.data.writers;
};
export const getAllBooks = async () => {
const queryResult = await urqlClient.query(ALL_BOOKS_QUERY).toPromise();
return queryResult.data.books;
};
export const getAllStores = async () => {
const queryResult = await urqlClient.query(ALL_STORES_QUERY).toPromise();
return queryResult.data.stores;
};
export const getAllEmployees = async () => {
const queryResult = await urqlClient.query(ALL_EMPLOYEES_QUERY).toPromise();
return queryResult.data.employees;
};
export const getAllAuthors = async () => {
const queryResult = await urqlClient.query(ALL_AUTHORS_QUERY).toPromise();
return queryResult.data.authors;
};
export const addTodo = async () => {
const newTodo = { text: 'New todo', complete: true };
const mutationResult = await urqlClient
.mutation(ADD_TODO_MUTATION, newTodo)
.toPromise();
return mutationResult.data.addTodo;
};
export const updateTodo = async ({ id, complete }) => {
const updatedTodo = { id, complete };
const mutationResult = await urqlClient
.mutation(UPDATE_TODO_MUTATION, updatedTodo)
.toPromise();
return mutationResult.data.updateTodo;
};
export const addTodos = async todosToBeAdded => {
const newTodos = { newTodos: { todos: todosToBeAdded } };
const mutationResult = await urqlClient
.mutation(ADD_TODOS_MUTATION, newTodos)
.toPromise();
return mutationResult.data.addTodos;
};
export const addWriters = async writersToBeAdded => {
const newWriters = { newWriters: { writers: writersToBeAdded } };
const mutationResult = await urqlClient
.mutation(ADD_WRITERS_MUTATION, newWriters)
.toPromise();
return mutationResult.data.addWriters;
};
export const addBooks = async booksToBeAdded => {
const newBooks = { newBooks: { books: booksToBeAdded } };
const mutationResult = await urqlClient
.mutation(ADD_BOOKS_MUTATION, newBooks)
.toPromise();
return mutationResult.data.addBooks;
};
export const addStores = async storesToBeAdded => {
const newStores = { newStores: { stores: storesToBeAdded } };
const mutationResult = await urqlClient
.mutation(ADD_STORES_MUTATION, newStores)
.toPromise();
return mutationResult.data.addStores;
};
export const addEmployees = async employeesToBeAdded => {
const newEmployees = { newEmployees: { employees: employeesToBeAdded } };
const mutationResult = await urqlClient
.mutation(ADD_EMPLOYEES_MUTATION, newEmployees)
.toPromise();
return mutationResult.data.addEmployees;
};
export const addAuthors = async authorsToBeAdded => {
const newAuthors = { newAuthors: { authors: authorsToBeAdded } };
const mutationResult = await urqlClient
.mutation(ADD_AUTHORS_MUTATION, newAuthors)
.toPromise();
return mutationResult.data.addAuthors;
};
================================================
FILE: exchanges/graphcache/benchmarks/entities.js
================================================
// functions to produce objects representing entities => todos, writers, books, stores, employees
export const makeTodo = i => ({
id: `${i}`,
text: `Todo ${i}`,
complete: false,
});
export const makeWriter = i => ({
id: `${i}`,
name: `Writer ${i}`,
amountOfBooks: Math.random() * 100,
recognized: Boolean(i % 2),
number: i,
interests: 'Dragonball-Z',
});
export const makeBook = i => ({
id: `${i}`,
title: `Book ${i}`,
published: Boolean(i % 2),
genre: 'Fantasy',
rating: (i / Math.random()) * 100,
});
export const makeStore = i => ({
id: `${i}`,
name: `Store ${i}`,
country: 'USA',
});
export const makeEmployee = i => ({
id: `${i}`,
name: `Employee ${i}`,
origin: 'USA',
});
export const makeAuthor = i => ({
id: `${i}`,
name: `Author ${i}`,
recognized: Boolean(i % 2),
book: {
id: `${i}`,
title: `Book ${i}`,
published: Boolean(i % 2),
genre: `Non-Fiction`,
rating: (i / Math.random()) * 100,
review: {
id: `${i}`,
score: i,
name: `Review ${i}`,
reviewer: {
id: `${i}`,
name: `Person ${i}`,
verified: Boolean(i % 2),
},
},
},
});
================================================
FILE: exchanges/graphcache/benchmarks/makeEntries.js
================================================
// create a function that will take in a number of times to be run and a function that will produce an entry/entity
export const makeEntries = (amount, makeEntry) => {
// create array of entries to be outputted
const entries = [];
// iterate from 0 up to the amount inputted
for (let i = 0; i < amount; i += 1) {
// each iteration, create an entry and pass it the current index & push it into output array
const entry = makeEntry(i);
entries.push(entry);
}
// return array of entries
return entries;
};
================================================
FILE: exchanges/graphcache/benchmarks/operations.js
================================================
// create operations, i.e., queries & mutations, to be performed
export const ALL_TODOS_QUERY = `
query ALL_TODOS_QUERY {
todos {
id,
text,
complete
}
}
`;
export const ALL_WRITERS_QUERY = `
query ALL_WRITERS_QUERY {
writers {
id,
name,
amountOfBooks,
recognized,
number,
interests
}
}
`;
export const ALL_BOOKS_QUERY = `
query ALL_BOOKS_QUERY {
books {
id,
title,
published,
genre,
rating
}
}
`;
export const ALL_STORES_QUERY = `
query ALL_STORES_QUERY {
stores {
id,
name,
country
}
}
`;
export const ALL_EMPLOYEES_QUERY = `
query ALL_EMPLOYEES_QUERY {
employees {
id,
name,
origin
}
}
`;
export const ALL_AUTHORS_QUERY = `
query ALL_AUTHORS_QUERY {
authors {
id,
name,
recognized,
book
}
}
`;
export const ADD_TODO_MUTATION = `
mutation ADD_TODO_MUTATION($text: String!, $complete: Boolean!){
addTodo(text: $text, complete: $complete){
id,
text,
complete
}
}
`;
export const UPDATE_TODO_MUTATION = `
mutation UPDATE_TODO_MUTATION($id: ID!, $complete: Boolean!){
updateTodo(id: $id, complete: $complete){
id,
text,
complete
}
}
`;
export const ADD_TODOS_MUTATION = `
mutation ADD_TODOS_MUTATION($newTodos: NewTodosInput!){
addTodos(newTodos: $newTodos){
id,
text,
complete
}
}
`;
export const ADD_WRITERS_MUTATION = `
mutation ADD_WRITERS_MUTATION($newWriters: NewWritersInput!){
addWriters(newWriters: $newWriters){
id,
name,
amountOfBooks,
recognized,
number,
interests
}
}
`;
export const ADD_BOOKS_MUTATION = `
mutation ADD_BOOKS_MUTATION($newBooks: NewBooksInput!){
addBooks(newBooks: $newBooks){
id,
title,
published,
genre,
rating
}
}
`;
export const ADD_STORES_MUTATION = `
mutation ADD_STORES_MUTATION($newStores: NewStoresInput!){
addStores(newStores: $newStores){
id,
name,
country
}
}
`;
export const ADD_EMPLOYEES_MUTATION = `
mutation ADD_EMPLOYEES_MUTATION($newEmployees: NewEmployeesInput!){
addEmployees(newEmployees: $newEmployees){
id,
name,
origin
}
}
`;
export const ADD_AUTHORS_MUTATION = `
mutation ADD_AUTHORS_MUTATION($newAuthors: NewAuthorsInput!){
addAuthors(newAuthors: $newAuthors){
id
name
recognized
book
}
}
`;
================================================
FILE: exchanges/graphcache/benchmarks/package.json
================================================
{
"name": "@urql/exchange-graphcache-tachometer-benchmark",
"version": "1.0.0",
"description": "Comprehensive Tachometer benchmarks for urql/graphcache",
"main": "index.js",
"scripts": {
"bench": "npm-run-all bench:*",
"bench:addTodo": "tachometer addTodo.html",
"bench:update": "tachometer updateTodo.html",
"bench:write100": "tachometer 100Writes.html",
"bench:write100c": "tachometer 100WritesComplex.html",
"bench:write500": "tachometer 500Writes.html",
"bench:write1000": "tachometer 1000Writes.html",
"bench:write1000c": "tachometer 1000WritesComplex.html",
"bench:write5000": "tachometer 5000Writes.html",
"bench:write10000": "tachometer 10000Writes.html",
"bench:write10000c": "tachometer 10000WritesComplex.html",
"bench:write50000": "tachometer 50000Writes.html",
"bench:read100": "tachometer 100Reads.html",
"bench:read100c": "tachometer 100ReadsComplex.html",
"bench:read500": "tachometer 500Reads.html",
"bench:read1000": "tachometer 1000Reads.html",
"bench:read1000c": "tachometer 1000ReadsComplex.html",
"bench:read5000": "tachometer 5000Reads.html",
"bench:read10000": "tachometer 10000Reads.html",
"bench:read10000c": "tachometer 10000ReadsComplex.html",
"bench:read50000": "tachometer 50000Reads.html"
},
"license": "MIT",
"dependencies": {
"@urql/exchange-execute": "file:../../execute",
"@urql/exchange-graphcache": "file:../",
"graphql": "^16.9.0",
"npm-run-all": "^4.1.5",
"tachometer": "^0.7.1",
"urql": "file:../../../packages/react-urql"
}
}
================================================
FILE: exchanges/graphcache/benchmarks/readMe.md
================================================
## About
This is a set of benchmarks assessing the performance of various graphCache operations. The operations are of varying sizes, complexitites, and are accomplished via a singular `urql` client instance. Client has a stubbed out GQL API (fetchExchange) to perform GQL operations against.
## Usage
#### 1. Install dependencies in repo root.
To get started, make sure to install necessary dependencies in the root directory of your clone.
```bash
# In root directory
yarn or npm i
```
#### 2. Run benchmark(s).
The commands to run benchmarks follows a certain syntax:
npm run `ActionQuantityComplexity` => i.e., npm run read500c
read === Action
5000 === Quantity
c === Complex
Action & Quantity are required, but c is optional, as not all operations involve a more complex data structure.
There are two exceptions that don't follow the beformentioned conventions for the commands to run benchmarks. They are `addTodo` & `updateTodo`.
They are simply run as follows:
```
npm run addTodo
npm run updateTodo
```
#### 3. Benchmark Expections
Upon executing a command, `Tachometer` will automatically execute the benchmarks via your default browser. Done 50 times prior to returning benchmark result in the console where the command was launched.
================================================
FILE: exchanges/graphcache/benchmarks/updateTodo.html
================================================
Update Todo
================================================
FILE: exchanges/graphcache/benchmarks/urqlClient.js
================================================
import { createClient } from '@urql/core';
import { cacheExchange } from '@urql/exchange-graphcache';
import { executeExchange } from '@urql/exchange-execute';
import { buildSchema } from 'graphql';
import { ALL_TODOS_QUERY } from './operations';
export const cache = cacheExchange({
updates: {
Mutation: {
addTodo: (result, args, cache) => {
cache.updateQuery({ query: ALL_TODOS_QUERY }, data => {
data.todos.push(result.addTodo);
return data;
});
return result;
},
},
},
});
// local schema to be used with Execute Exchange
const schema = buildSchema(`
type Todo {
id: ID!
text: String!
complete: Boolean!
}
type Writer {
id: ID!
name: String
amountOfBooks: Float!
recognized: Boolean!
number: Int!
interests: String!
}
type Book {
id: ID!
title: String!
published: Boolean!
genre: String!
rating: Float!
review: Review
}
type Store {
id: ID!
name: String!
country: String!
}
type Employee {
id: ID!
name: String!
origin: String!
}
type Author {
id: ID!
name: String!
recognized: Boolean!
book: Book!
}
type Review {
id: ID!
score: Int!
name: String!
reviewer: Person!
}
type Person {
id: ID!
name: String!
verfied: Boolean!
}
input NewTodo {
id: ID!
text: String!
complete: Boolean!
}
input NewTodosInput {
todos: [NewTodo]!
}
input NewWriter {
id: ID!
name: String
amountOfBooks: Float!
recognized: Boolean!
number: Int!
interests: String!
}
input NewWritersInput {
writers: [NewWriter]!
}
input NewBook {
id: ID!
title: String!
published: Boolean!
genre: String!
rating: Float!
review: NewReview
}
input NewBooksInput {
books: [NewBook]!
}
input NewStore {
id: ID!
name: String!
country: String!
}
input NewStoresInput {
stores: [NewStore]!
}
input NewEmployee {
id: ID!
name: String!
origin: String!
}
input NewEmployeesInput {
employees: [NewEmployee]!
}
input NewAuthor {
id: ID!
name: String!
recognized: Boolean!
book: NewBook!
}
input NewAuthorsInput {
authors: [NewAuthor]!
}
input NewReview {
id: ID!
score: Int!
name: String!
reviewer: NewPerson!
}
input NewPerson {
id: ID!
name: String!
verified: Boolean!
}
type Query {
todos: [Todo]!
writers: [Writer]!
books: [Book]!
stores: [Store]!
employees: [Employee]!
authors: [Author]!
}
type Mutation {
addTodo( text: String!, complete: Boolean! ): Todo!
updateTodo( id: ID!, complete: Boolean! ): Todo!
addTodos( newTodos: NewTodosInput! ): [Todo]!
addWriters( newWriters: NewWritersInput! ): [Writer]!
addBooks( newBooks: NewBooksInput! ): [Book]!
addStores( newStores: NewStoresInput! ): [Store]!
addEmployees( newEmployees: NewEmployeesInput! ): [Employee]!
addAuthors( newAuthors: NewAuthorsInput! ): [Author]!
}
`);
// local state to be used with Execute Exchange
const todos = [];
const writers = [];
const books = [];
const stores = [];
const employees = [];
const authors = [];
// root value with resolvers to be used with Execute Exchange
const rootValue = {
todos: () => {
return todos;
},
writers: () => {
return writers;
},
books: () => {
return books;
},
stores: () => {
return stores;
},
employees: () => {
return employees;
},
authors: () => {
return authors;
},
addTodo: args => {
const todo = { id: todos.length.toString(), ...args };
todos.push(todo);
return todo;
},
updateTodo: ({ id, complete }) => {
const [todoToBeUpdated] = todos.filter(todo => todo.id === id);
todoToBeUpdated.complete = complete;
return todoToBeUpdated;
},
addTodos: ({ newTodos }) => {
const todosToBeAdded = newTodos.todos;
todos.push(...todosToBeAdded);
return todos;
},
addWriters: ({ newWriters }) => {
const writersToBeAdded = newWriters.writers;
writers.push(...writersToBeAdded);
return writers;
},
addBooks: ({ newBooks }) => {
const booksToBeAdded = newBooks.books;
books.push(...booksToBeAdded);
return books;
},
addStores: ({ newStores }) => {
const storesToBeAdded = newStores.stores;
stores.push(...storesToBeAdded);
return stores;
},
addEmployees: ({ newEmployees }) => {
const employeesToBeAdded = newEmployees.employees;
employees.push(...employeesToBeAdded);
return employees;
},
addAuthors: ({ newAuthors }) => {
const authorsToBeAdded = newAuthors.authors;
authors.push(...authorsToBeAdded);
return authors;
},
};
const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [
cache,
// cacheExchange({}),
executeExchange({ schema, rootValue }),
],
});
export default client;
================================================
FILE: exchanges/graphcache/cypress/fixtures/example.json
================================================
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
================================================
FILE: exchanges/graphcache/cypress/plugins/index.js
================================================
// eslint-disable-next-line
const { startDevServer } = require('@cypress/vite-dev-server');
// eslint-disable-next-line
const path = require('path');
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, _config) => {
on('dev-server:start', options =>
startDevServer({
options,
})
);
};
================================================
FILE: exchanges/graphcache/cypress/support/component-index.html
================================================
Components App
================================================
FILE: exchanges/graphcache/cypress/support/component.js
================================================
// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
import { mount } from 'cypress/react';
Cypress.Commands.add('mount', mount);
// Example use:
// cy.mount()
================================================
FILE: exchanges/graphcache/cypress.config.js
================================================
// eslint-disable-next-line
const { defineConfig } = require('cypress');
// eslint-disable-next-line
const tsconfigPaths = require('vite-tsconfig-paths').default;
module.exports = defineConfig({
video: false,
e2e: {
setupNodeEvents(_on, _config) {
/*noop*/
},
supportFile: false,
},
component: {
specPattern: './**/e2e-tests/*spec.tsx',
devServer: {
framework: 'react',
bundler: 'vite',
viteConfig: {
plugins: [tsconfigPaths()],
server: {
fs: {
allow: ['../..'],
},
},
},
},
},
});
================================================
FILE: exchanges/graphcache/e2e-tests/query.spec.tsx
================================================
///
import * as React from 'react';
import { mount } from '@cypress/react';
import { Provider, createClient, useQuery, debugExchange } from 'urql';
import { executeExchange } from '@urql/exchange-execute';
import { buildSchema, introspectionFromSchema } from 'graphql';
import { cacheExchange } from '../src';
const schema = buildSchema(`
type Query {
movie: Movie
}
type Movie {
id: String
title: String
metadata: Metadata
}
type Metadata {
uri: String
}
`);
const rootValue = {
movie: async () => {
await new Promise(resolve => setTimeout(resolve, 50));
return {
id: 'foo',
title: 'title',
metadata: () => {
throw new Error('Test');
},
};
},
};
describe('Graphcache Queries', () => {
it('should not loop with no schema present', () => {
const client = createClient({
url: 'https://trygql.formidable.dev/graphql/basic-pokedex',
exchanges: [
cacheExchange({}),
debugExchange,
executeExchange({ schema, rootValue }),
],
});
const FirstComponent = () => {
const [{ fetching, error }] = useQuery({
query: `{
movie {
id
title
metadata {
uri
}
}
}`,
});
return (
{fetching === true ? (
'loading'
) : (
First Component
{`Error: ${error?.message}`}
)}
);
};
const SecondComponent = () => {
const [{ error, fetching }] = useQuery({
query: `{
movie {
id
metadata {
uri
}
}
}`,
});
if (fetching) {
return