Repository: urql-graphql/urql
Pull request #3192 (2023-04-27 22:58:00)

fix(persisted): persisted queries can use GETs within URL limits
<!--
  Thanks for opening a pull request! We appreciate your dedication and help!
  Before submitting your pull request, please make sure to read our CONTRIBUTING guide.

  The best contribution is always a PR, but please make sure to open an issue or discuss
  your changes first, if you’re looking to submit a larger PR.

  If this PR is already related to an issue, please reference it like so:
  Resolves #123
-->

## Summary

<!-- What's the motivation of this change? What does it solve? -->

This PR aims to resolve https://github.com/urql-graphql/urql/issues/3185.

Currently, `@urql/exchange-persisted`'s `options.preferGetForPersistedQueries` doesn't allow for `persistedOperation.context.preferGetMethod` to be set to `true` or `'within-url-limit'`, and instead sets `persistedOperation.context.preferGetMethod` to `'force'` when `options.preferGetForPersistedQueries` is `true`. This leads to broken GET requests when queries become massive.

Another issue was that although the TSDocs suggest that [the default value](https://github.com/urql-graphql/urql/blob/cb628ca4e9207b1928a5e137fddbd14c33afb6e4/exchanges/persisted/src/persistedExchange.ts#L42) of `options.preferGetForPersistedQueries` is `true`, this wasn't reflected in the logic.

## Set of changes

<!--
  Roughly list the changes you've made and which packages are affected.
  Leave some notes on what may be noteworthy files you've changed.
  And lastly, please let us know if you think this is a breaking change.
-->

In `@urql/exchange-persisted`:
- The type of `options.preferGetForPersistedQueries` has changed from `boolean | undefined` to match the value of the [Client](https://github.com/urql-graphql/urql/blob/main/docs/api/core.md#client)'s `preferGetMethod` option, which includes `'force'` and `'within-url-limit'`.
- Sets a default value of `options.preferGetForPersistedQueries` (to `true`) to match the TSDocs.
- When `options.preferGetForPersistedQueries` is `true`, we no longer set `operation.context.preferGetMethod` to `'force'`. Instead, `operation.context.preferGetMethod` is set to `true`.
  - This extends to all values of `options.preferGetForPersistedQueries`. The value set there will be the value set in `operation.context.preferGetMethod`.
- Removes a [check on the value of `preferGetForPersistedQueries `](https://github.com/urql-graphql/urql/blob/cb628ca4e9207b1928a5e137fddbd14c33afb6e4/exchanges/persisted/src/persistedExchange.ts#L164) before setting `persistedOperation.context.preferGetMethod`. This allows `persistedOperation.context.preferGetMethod` to be set to `false` if `options.preferGetForPersistedQueries` is set to `false`.
- Adds unit tests to validate the changes described here.


Patch:
diff --git a/.changeset/fair-eels-march.md b/.changeset/fair-eels-march.md
new file mode 100644
index 00000000..7cebb0d1
--- /dev/null
+++ b/.changeset/fair-eels-march.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-persisted': major
+---
+
+Update the `preferGetForPersistedQueries` option to include the `'force'` and `'within-url-limit'` values from the Client's `preferGetMethod` option. The default value of `true` no longer sets `OperationContext`'s `preferGetMethod` setting to `'force'`. Instead, the value of `preferGetForPersistedQueries` carries through to the `OperationContext`'s `preferGetMethod` setting for persisted queries.
diff --git a/.changeset/sixty-masks-itch.md b/.changeset/sixty-masks-itch.md
new file mode 100644
index 00000000..4c14c354
--- /dev/null
+++ b/.changeset/sixty-masks-itch.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-graphcache': patch
+---
+
+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
diff --git a/README.md b/README.md
index d07ef531..fcac4f4f 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,23 @@ 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)
 
-## 📃 [Documentation](https://formidable.com/open-source/urql/docs/)
+## 📦 [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:
@@ -54,6 +70,10 @@ when you first get 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._
 
 <img width="100%" src="docs/assets/urql-spoiler.png" />
diff --git a/docs/advanced/persistence-and-uploads.md b/docs/advanced/persistence-and-uploads.md
index e03263e4..f076d07e 100644
--- a/docs/advanced/persistence-and-uploads.md
+++ b/docs/advanced/persistence-and-uploads.md
@@ -61,10 +61,10 @@ const client = new Client({
 });
 ```
 
-As we can see, typically it's recommended to set `preferGetForPersistedQueries` to `true` to force
-all persisted queries to use GET requests instead of POST so that CDNs can do their job.
-It does so by setting the `preferGetMethod` option to `'force'` when it's
-updating operations.
+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
diff --git a/docs/basics/vue.md b/docs/basics/vue.md
index 8d0b0be5..c1f88252 100644
--- a/docs/basics/vue.md
+++ b/docs/basics/vue.md
@@ -97,12 +97,13 @@ importing its default export and using it [as a plugin](https://v3.vuejs.org/gui
 ```js
 import { createApp } from 'vue';
 import Root from './App.vue';
-import urql from '@urql/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');
diff --git a/exchanges/graphcache/src/cacheExchange.ts b/exchanges/graphcache/src/cacheExchange.ts
index 9f08f84e..9d461e69 100644
--- a/exchanges/graphcache/src/cacheExchange.ts
+++ b/exchanges/graphcache/src/cacheExchange.ts
@@ -219,6 +219,7 @@ export const cacheExchange =
         : 'miss';
 
       results.set(operation.key, result.data);
+      operations.set(operation.key, operation);
       updateDependencies(operation, result.dependencies);
 
       return {
diff --git a/exchanges/persisted/src/persistedExchange.ts b/exchanges/persisted/src/persistedExchange.ts
index cd4d99e8..013a81b5 100644
--- a/exchanges/persisted/src/persistedExchange.ts
+++ b/exchanges/persisted/src/persistedExchange.ts
@@ -17,6 +17,7 @@ import {
   CombinedError,
   Exchange,
   Operation,
+  OperationContext,
 } from '@urql/core';
 
 import { hash } from './sha256';
@@ -29,19 +30,23 @@ const isPersistedUnsupported = (error: CombinedError): boolean =>
 
 /** Input parameters for the {@link persistedExchange}. */
 export interface PersistedExchangeOptions {
-  /** Enforces GET method requests to be made for Persisted Queries.
+  /** Controls whether GET method requests will be made for Persisted Queries.
    *
    * @remarks
-   * When enabled, the `persistedExchange` will set
+   * When set to `true` or `'within-url-limit'`, the `persistedExchange`
+   * will use GET requests on persisted queries when the request URL
+   * doesn't exceed the 2048 character limit.
+   *
+   * When set to `force`, the `persistedExchange` will set
    * `OperationContext.preferGetMethod` to `'force'` on persisted queries,
    * which will force requests to be made using a GET request.
    *
-   * This is frequently used to make GraphQL requests more cacheable
-   * on CDNs.
+   * GET requests are frequently used to make GraphQL requests more
+   * cacheable on CDNs.
    *
-   * @defaultValue `true` - enabled
+   * @defaultValue `undefined` - disabled
    */
-  preferGetForPersistedQueries?: boolean;
+  preferGetForPersistedQueries?: OperationContext['preferGetMethod'];
   /** Enforces non-automatic persisted queries by ignoring APQ errors.
    *
    * @remarks
@@ -118,7 +123,7 @@ export const persistedExchange =
   ({ forward }) => {
     if (!options) options = {};
 
-    const preferGetForPersistedQueries = !!options.preferGetForPersistedQueries;
+    const preferGetForPersistedQueries = options.preferGetForPersistedQueries;
     const enforcePersistedQueries = !!options.enforcePersistedQueries;
     const hashFn = options.generateHash || hash;
     const enableForMutation = !!options.enableForMutation;
@@ -163,7 +168,8 @@ export const persistedExchange =
               persistedOperation.kind === 'query' &&
               preferGetForPersistedQueries
             ) {
-              persistedOperation.context.preferGetMethod = 'force';
+              persistedOperation.context.preferGetMethod =
+                preferGetForPersistedQueries;
             }
           }
 


Test patch:
diff --git a/exchanges/persisted/src/persistedExchange.test.ts b/exchanges/persisted/src/persistedExchange.test.ts
index 97dd21e5..8e013801 100644
--- a/exchanges/persisted/src/persistedExchange.test.ts
+++ b/exchanges/persisted/src/persistedExchange.test.ts
@@ -118,3 +118,21 @@ it('retries query persisted query resulted in unsupported', async () => {
 
   expect(operations[2].extensions).toEqual(undefined);
 });
+
+it.each([true, 'force', 'within-url-limit'] as const)(
+  'sets `context.preferGetMethod` to %s when `options.preferGetForPersistedQueries` is %s',
+  async preferGetMethodValue => {
+    const { exchangeArgs } = makeExchangeArgs();
+
+    const res = await pipe(
+      fromValue(queryOperation),
+      persistedExchange({ preferGetForPersistedQueries: preferGetMethodValue })(
+        exchangeArgs
+      ),
+      take(1),
+      toPromise
+    );
+
+    expect(res.operation.context.preferGetMethod).toBe(preferGetMethodValue);
+  }
+);