From d36f7152e34552e533d5fbc1c10fde35017a6b09 Mon Sep 17 00:00:00 2001 From: Maksim Shylau Date: Wed, 24 Apr 2024 10:53:19 +0200 Subject: [PATCH 1/2] Add `includeUnusedVariables` option support remove console.log statements --- createUploadLink.mjs | 11 +++ createUploadLink.test.mjs | 142 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/createUploadLink.mjs b/createUploadLink.mjs index f0c3d3a..db29b81 100644 --- a/createUploadLink.mjs +++ b/createUploadLink.mjs @@ -11,6 +11,7 @@ import { } from "@apollo/client/link/http/selectHttpOptionsAndBody.js"; import { selectURI } from "@apollo/client/link/http/selectURI.js"; import { serializeFetchParameter } from "@apollo/client/link/http/serializeFetchParameter.js"; +import { filterOperationVariables } from "@apollo/client/link/utils/filterOperationVariables.js"; import { Observable } from "@apollo/client/utilities/observables/Observable.js"; import extractFiles from "extract-files/extractFiles.mjs"; @@ -63,6 +64,8 @@ import isExtractableFile from "./isExtractableFile.mjs"; * {@linkcode fetchOptions}. * @param {boolean} [options.includeExtensions] Toggles sending `extensions` * fields to the GraphQL server. Defaults to `false`. + * @param {boolean} [options.includeUnusedVariables] * If set to true, the default behavior of stripping unused variables + * from the request will be disabled. Defaults to `false`. * @returns A [terminating Apollo Link](https://www.apollographql.com/docs/react/api/link/introduction/#the-terminating-link). * @example * A basic Apollo Client setup: @@ -89,6 +92,7 @@ export default function createUploadLink({ credentials, headers, includeExtensions, + includeUnusedVariables = false, } = {}) { const linkConfig = { http: { includeExtensions }, @@ -136,6 +140,13 @@ export default function createUploadLink({ contextConfig, ); + if (body.variables && !includeUnusedVariables) { + body.variables = filterOperationVariables( + body.variables, + operation.query, + ); + } + const { clone, files } = extractFiles(body, customIsExtractableFile, ""); let uri = selectURI(operation, fetchUri); diff --git a/createUploadLink.test.mjs b/createUploadLink.test.mjs index e5f6890..982ae04 100644 --- a/createUploadLink.test.mjs +++ b/createUploadLink.test.mjs @@ -316,6 +316,147 @@ describe("Function `createUploadLink`.", { concurrency: true }, () => { deepStrictEqual(nextData, payload); }); + it("Option `includeUnusedVariables`, set to false", async () => { + /** @type {unknown} */ + let fetchInput; + + /** @type {RequestInit | undefined} */ + let fetchOptions; + + /** @type {unknown} */ + let nextData; + + const query = "query ($a: Boolean) {\n a(a: $a)\n}"; + const payload = { data: { a: true, b: true } }; + + await timeLimitPromise( + /** @type {Promise} */ ( + new Promise((resolve, reject) => { + execute( + createUploadLink({ + includeUnusedVariables: false, + async fetch(input, options) { + fetchInput = input; + fetchOptions = options; + + return new Response( + JSON.stringify(payload), + graphqlResponseOptions, + ); + }, + }), + { + query: gql(query), + variables: { + a: true, + b: true, + }, + }, + ).subscribe({ + next(data) { + nextData = data; + }, + error() { + reject(createUnexpectedCallError()); + }, + complete() { + resolve(); + }, + }); + }) + ), + ); + + strictEqual(fetchInput, defaultUri); + ok(typeof fetchOptions === "object"); + + const { signal: fetchOptionsSignal, ...fetchOptionsRest } = fetchOptions; + + ok(fetchOptionsSignal instanceof AbortSignal); + deepEqual(fetchOptionsRest, { + method: "POST", + headers: { accept: "*/*", "content-type": "application/json" }, + body: JSON.stringify({ + variables: { + a: true, + }, + query, + }), + }); + deepStrictEqual(nextData, payload); + }); + + it("Option `includeUnusedVariables`, set to true", async () => { + /** @type {unknown} */ + let fetchInput; + + /** @type {RequestInit | undefined} */ + let fetchOptions; + + /** @type {unknown} */ + let nextData; + + const query = "query ($a: Boolean) {\n a(a: $a)\n}"; + const payload = { data: { a: true, b: true } }; + + await timeLimitPromise( + /** @type {Promise} */ ( + new Promise((resolve, reject) => { + execute( + createUploadLink({ + includeUnusedVariables: true, + async fetch(input, options) { + fetchInput = input; + fetchOptions = options; + + return new Response( + JSON.stringify(payload), + graphqlResponseOptions, + ); + }, + }), + { + query: gql(query), + variables: { + a: true, + b: true, + }, + }, + ).subscribe({ + next(data) { + nextData = data; + }, + error() { + reject(createUnexpectedCallError()); + }, + complete() { + resolve(); + }, + }); + }) + ), + ); + + strictEqual(fetchInput, defaultUri); + ok(typeof fetchOptions === "object"); + + const { signal: fetchOptionsSignal, ...fetchOptionsRest } = fetchOptions; + + ok(fetchOptionsSignal instanceof AbortSignal); + deepEqual(fetchOptionsRest, { + method: "POST", + headers: { accept: "*/*", "content-type": "application/json" }, + body: JSON.stringify({ + variables: { + a: true, + b: true, + }, + query, + }), + }); + deepStrictEqual(nextData, payload); + }); + it("Option `print`.", async () => { /** @type {unknown} */ let fetchInput; @@ -608,6 +749,7 @@ describe("Function `createUploadLink`.", { concurrency: true }, () => { execute( createUploadLink({ useGETForQueries: true, + includeUnusedVariables: true, async fetch() { fetched = true; From 71e55aefd579d1bce68cfbcb22ecc3f779f077d8 Mon Sep 17 00:00:00 2001 From: Jayden Seric Date: Mon, 25 Aug 2025 17:05:44 +1000 Subject: [PATCH 2/2] Tidy and add a changelog entry. --- changelog.md | 4 ++++ createUploadLink.mjs | 7 +++---- createUploadLink.test.mjs | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 5004e83..728e46c 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,10 @@ - Use the TypeScript v5.5+ JSDoc tag `@import` to import types in modules. - Updated dev dependencies, some of which require newer Node.js versions than previously supported. +### Minor + +- Added a new function `createUploadLink` option `includeUnusedVariables` defaulting to `false` to toggle including unused GraphQL variables in the request (similar to the Apollo `BaseHttpLink` option [`includeUnusedVariables`](https://www.apollographql.com/docs/react/api/link/apollo-link-base-http#options-includeunusedvariables)), via [#348](https://github.com/jaydenseric/apollo-upload-client/pull/348). + ### Patch - Updated the package scripts: diff --git a/createUploadLink.mjs b/createUploadLink.mjs index 7e66a25..f72b0fd 100644 --- a/createUploadLink.mjs +++ b/createUploadLink.mjs @@ -70,8 +70,8 @@ import isExtractableFile from "./isExtractableFile.mjs"; * {@linkcode fetchOptions}. * @param {boolean} [options.includeExtensions] Toggles sending `extensions` * fields to the GraphQL server. Defaults to `false`. - * @param {boolean} [options.includeUnusedVariables] * If set to true, the default behavior of stripping unused variables - * from the request will be disabled. Defaults to `false`. + * @param {boolean} [options.includeUnusedVariables] Toggles including unused + * GraphQL variables in the request. Defaults to `false`. * @returns A [terminating Apollo Link](https://www.apollographql.com/docs/react/api/link/introduction/#the-terminating-link). * @example * A basic Apollo Client setup: @@ -146,12 +146,11 @@ export default function createUploadLink({ contextConfig, ); - if (body.variables && !includeUnusedVariables) { + if (body.variables && !includeUnusedVariables) body.variables = filterOperationVariables( body.variables, operation.query, ); - } const { clone, files } = extractFiles(body, customIsExtractableFile, ""); diff --git a/createUploadLink.test.mjs b/createUploadLink.test.mjs index 7c1a45a..df415a7 100644 --- a/createUploadLink.test.mjs +++ b/createUploadLink.test.mjs @@ -314,7 +314,7 @@ describe("Function `createUploadLink`.", { concurrency: true }, () => { deepStrictEqual(nextData, payload); }); - it("Option `includeUnusedVariables`, set to false", async () => { + it("Option `includeUnusedVariables`, set to false.", async () => { /** @type {unknown} */ let fetchInput; @@ -325,7 +325,7 @@ describe("Function `createUploadLink`.", { concurrency: true }, () => { let nextData; const query = "query ($a: Boolean) {\n a(a: $a)\n}"; - const payload = { data: { a: true, b: true } }; + const payload = { data: { a: true } }; await timeLimitPromise( /** @type {Promise} */ ( @@ -384,7 +384,7 @@ describe("Function `createUploadLink`.", { concurrency: true }, () => { deepStrictEqual(nextData, payload); }); - it("Option `includeUnusedVariables`, set to true", async () => { + it("Option `includeUnusedVariables`, set to true.", async () => { /** @type {unknown} */ let fetchInput; @@ -395,7 +395,7 @@ describe("Function `createUploadLink`.", { concurrency: true }, () => { let nextData; const query = "query ($a: Boolean) {\n a(a: $a)\n}"; - const payload = { data: { a: true, b: true } }; + const payload = { data: { a: true } }; await timeLimitPromise( /** @type {Promise} */ (