Repository: urql-graphql/urql (ts)

Issue:
"Files" in the operations object is not null in version 4.X
### Describe the bug

First of all, thank you for a real nice piece of software. More versatile than other GraphQL tools I have tried in the past. But now to my problem at hand.

Since upgrading to the latest version of `@urql/core`, i removed `@urql/exchange-multipart-fetch` as a dependency but now my file uploads fails.  
Instead of `null`, files are now objects with a `__key` field inside the JSON operations object, e.g. `{"__key":"q6w2iwja1ta"}`. 

My API supports the unofficial [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec), where `files` inside the operation object should be `null`. This causes the API to throw an internal server error.

Is this intentional? If so, what is the recommended approach for the API to support this? Or can I somehow force urql to set the files to `null`?

A reproducible example can be found using the **with-multipart** example in your repo.

Im running Google Chrome 112.0.5615.137 on Mac OS Ventura 13.3.1

### Reproduction

https://github.com/urql-graphql/urql/tree/main/examples/with-multipart

### Urql version

urql v4.0.4

### Validations

- [X] 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
- [X] Read the [docs](https://formidable.com/open-source/urql/docs/).
- [X] Follow our [Code of Conduct](https://github.com/urql-graphql/urql/blob/main/CODE_OF_CONDUCT.md)

Pull request:
fix(core): Replace File/Blob objects with null in serialized variables
Resolves #3168

## Summary

Seems like some APIs/frameworks actually care about this 🤷 Currently, this will affect operation keys, however, since mutations have unique instances, we can probably get away with making the keys for variables identical for queries.

## Set of changes

- Replace `File` and `Blob` with `null` in stringified variables


Patch:
diff --git a/.changeset/afraid-seas-arrive.md b/.changeset/afraid-seas-arrive.md
new file mode 100644
index 00000000..da15c55d
--- /dev/null
+++ b/.changeset/afraid-seas-arrive.md
@@ -0,0 +1,5 @@
+---
+'@urql/core': patch
+---
+
+Replace `File` and `Blob` objects with `null` in variables if multipart request will be started.
diff --git a/packages/core/src/utils/variables.ts b/packages/core/src/utils/variables.ts
index ad586879..5bbd6257 100644
--- a/packages/core/src/utils/variables.ts
+++ b/packages/core/src/utils/variables.ts
@@ -18,6 +18,11 @@ const stringify = (x: any): string => {
     }
     out += ']';
     return out;
+  } else if (
+    (FileConstructor !== NoopConstructor && x instanceof FileConstructor) ||
+    (BlobConstructor !== NoopConstructor && x instanceof BlobConstructor)
+  ) {
+    return 'null';
   }
 
   const keys = Object.keys(x).sort();


Test patch:
diff --git a/packages/core/src/internal/fetchOptions.test.ts b/packages/core/src/internal/fetchOptions.test.ts
index bade73cf..9114018b 100644
--- a/packages/core/src/internal/fetchOptions.test.ts
+++ b/packages/core/src/internal/fetchOptions.test.ts
@@ -149,7 +149,7 @@ describe('makeFetchOptions', () => {
       ...body,
       variables: {
         ...body.variables,
-        file: { __key: expect.any(String) },
+        file: null,
       },
     });
 
diff --git a/packages/core/src/utils/variables.test.ts b/packages/core/src/utils/variables.test.ts
index dc414a93..3b78fca8 100644
--- a/packages/core/src/utils/variables.test.ts
+++ b/packages/core/src/utils/variables.test.ts
@@ -40,13 +40,8 @@ describe('stringifyVariables', () => {
 
   it('stringifies files correctly', () => {
     const file = new File([0] as any, 'test.js');
-    Object.defineProperty(file, 'lastModified', { value: 123 });
     const str = stringifyVariables(file);
-    expect(str).toBe(stringifyVariables(file));
-
-    const otherFile = new File([0] as any, 'otherFile.js');
-    Object.defineProperty(otherFile, 'lastModified', { value: 234 });
-    expect(str).not.toBe(stringifyVariables(otherFile));
+    expect(str).toBe('null');
   });
 });