From 22b7e4c4ea80f2ffbb3f62b4956cb2decb970f69 Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Wed, 22 Oct 2025 10:36:46 +0200 Subject: [PATCH 1/8] feat: make apq a explicitly opt in feature * fixed an issue were POST calls where implicitly converted into GET calls when a documentId was provided, even though apq was not enabled. * removed a deprecated isPersistedQuery flag --- .changeset/pre.json | 8 ++++++++ .changeset/rich-ants-shop.md | 5 +++++ package.json | 2 +- src/client.test.ts | 2 +- src/client.ts | 8 +------- 5 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 .changeset/pre.json create mode 100644 .changeset/rich-ants-shop.md diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000..748c6ce --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,8 @@ +{ + "mode": "pre", + "tag": "beta", + "initialVersions": { + "@labdigital/graphql-fetcher": "2.0.0" + }, + "changesets": [] +} diff --git a/.changeset/rich-ants-shop.md b/.changeset/rich-ants-shop.md new file mode 100644 index 0000000..205905d --- /dev/null +++ b/.changeset/rich-ants-shop.md @@ -0,0 +1,5 @@ +--- +"@labdigital/graphql-fetcher": major +--- + +Remove deprecated is "isPersistedQuery" and make apq explicitly opt in diff --git a/package.json b/package.json index 9a1d227..4032b22 100644 --- a/package.json +++ b/package.json @@ -70,4 +70,4 @@ "react-dom": ">= 18.2.0" }, "packageManager": "pnpm@9.15.3" -} +} \ No newline at end of file diff --git a/src/client.test.ts b/src/client.test.ts index a1983d4..f21d23c 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -37,7 +37,7 @@ describe("gqlClientFetch", () => { const fetcher = initClientFetcher("https://localhost/graphql"); const persistedFetcher = initClientFetcher("https://localhost/graphql", { - persistedQueries: true, + apq: true, }); it("should perform a query", async () => { diff --git a/src/client.ts b/src/client.ts index a8b0a11..e8ecb37 100644 --- a/src/client.ts +++ b/src/client.ts @@ -25,9 +25,6 @@ type Options = { */ apq?: boolean; - /** Deprecated: use `apq: ` */ - persistedQueries?: boolean; - /** * Sets the default timeout duration in ms after which a request will throw a timeout error * @@ -71,7 +68,6 @@ export const initClientFetcher = endpoint: string, { apq = false, - persistedQueries = false, defaultTimeout = 30000, defaultHeaders = {}, includeQuery = false, @@ -110,10 +106,8 @@ export const initClientFetcher = const queryType = getQueryType(query); - apq = apq || persistedQueries; - // For queries we can use GET requests if persisted queries are enabled - if (queryType === "query" && (apq || isPersistedQuery(request))) { + if (queryType === "query" && apq) { const url = createRequestURL(endpoint, request); response = await parseResponse>(() => fetch(url.toString(), { From 0fa98ebb4ad34c498f38cc793109e9c0b468951d Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Wed, 22 Oct 2025 15:02:57 +0200 Subject: [PATCH 2/8] chore: version bump --- .changeset/pre.json | 4 +++- CHANGELOG.md | 6 ++++++ package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 748c6ce..e53cfb9 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -4,5 +4,7 @@ "initialVersions": { "@labdigital/graphql-fetcher": "2.0.0" }, - "changesets": [] + "changesets": [ + "rich-ants-shop" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bda83a..e34256b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @labdigital/react-query-opal +## 3.0.0-beta.0 + +### Major Changes + +- c570e03: Remove deprecated is "isPersistedQuery" and make apq explicitly opt in + ## 2.0.0 ### Minor Changes diff --git a/package.json b/package.json index 4032b22..c7b54f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labdigital/graphql-fetcher", - "version": "2.0.0", + "version": "3.0.0-beta.0", "description": "Custom fetcher for react-query to use with @labdigital/node-federated-token", "type": "module", "main": "./dist/index.cjs", From 3755bc98d9e30c57026189baa85bf683534b9315 Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Wed, 22 Oct 2025 16:56:47 +0200 Subject: [PATCH 3/8] Revert "chore: version bump" This reverts commit 0c672362c50507fecbb05eca980ad99ce99a25f2. --- .changeset/pre.json | 4 +--- CHANGELOG.md | 6 ------ package.json | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index e53cfb9..748c6ce 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -4,7 +4,5 @@ "initialVersions": { "@labdigital/graphql-fetcher": "2.0.0" }, - "changesets": [ - "rich-ants-shop" - ] + "changesets": [] } diff --git a/CHANGELOG.md b/CHANGELOG.md index e34256b..8bda83a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,5 @@ # @labdigital/react-query-opal -## 3.0.0-beta.0 - -### Major Changes - -- c570e03: Remove deprecated is "isPersistedQuery" and make apq explicitly opt in - ## 2.0.0 ### Minor Changes diff --git a/package.json b/package.json index c7b54f4..4032b22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labdigital/graphql-fetcher", - "version": "3.0.0-beta.0", + "version": "2.0.0", "description": "Custom fetcher for react-query to use with @labdigital/node-federated-token", "type": "module", "main": "./dist/index.cjs", From 8149bf792770199ae598e0c2e84155f00425662a Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Wed, 22 Oct 2025 16:59:10 +0200 Subject: [PATCH 4/8] Reapply "chore: version bump" This reverts commit c279db512aa2cbf2f2631fbc0fff90e526dd1b64. --- .changeset/pre.json | 4 +++- CHANGELOG.md | 6 ++++++ package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 748c6ce..e53cfb9 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -4,5 +4,7 @@ "initialVersions": { "@labdigital/graphql-fetcher": "2.0.0" }, - "changesets": [] + "changesets": [ + "rich-ants-shop" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bda83a..e34256b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @labdigital/react-query-opal +## 3.0.0-beta.0 + +### Major Changes + +- c570e03: Remove deprecated is "isPersistedQuery" and make apq explicitly opt in + ## 2.0.0 ### Minor Changes diff --git a/package.json b/package.json index 4032b22..c7b54f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labdigital/graphql-fetcher", - "version": "2.0.0", + "version": "3.0.0-beta.0", "description": "Custom fetcher for react-query to use with @labdigital/node-federated-token", "type": "module", "main": "./dist/index.cjs", From b4df3f0d1cec4e79e2db21c2b883bd30c00a94ef Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Wed, 22 Oct 2025 17:07:20 +0200 Subject: [PATCH 5/8] Revert "Reapply "chore: version bump"" This reverts commit f83a91fdd542c604d565c5303efcbb4dbd0fe872. --- .changeset/pre.json | 4 +--- CHANGELOG.md | 6 ------ package.json | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index e53cfb9..748c6ce 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -4,7 +4,5 @@ "initialVersions": { "@labdigital/graphql-fetcher": "2.0.0" }, - "changesets": [ - "rich-ants-shop" - ] + "changesets": [] } diff --git a/CHANGELOG.md b/CHANGELOG.md index e34256b..8bda83a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,5 @@ # @labdigital/react-query-opal -## 3.0.0-beta.0 - -### Major Changes - -- c570e03: Remove deprecated is "isPersistedQuery" and make apq explicitly opt in - ## 2.0.0 ### Minor Changes diff --git a/package.json b/package.json index c7b54f4..4032b22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labdigital/graphql-fetcher", - "version": "3.0.0-beta.0", + "version": "2.0.0", "description": "Custom fetcher for react-query to use with @labdigital/node-federated-token", "type": "module", "main": "./dist/index.cjs", From 0b99c22d541495ddc2254c9006d8f562b20d1fb6 Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Wed, 22 Oct 2025 17:08:22 +0200 Subject: [PATCH 6/8] chore: verion files --- .changeset/pre.json | 4 +++- CHANGELOG.md | 6 ++++++ package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 748c6ce..e53cfb9 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -4,5 +4,7 @@ "initialVersions": { "@labdigital/graphql-fetcher": "2.0.0" }, - "changesets": [] + "changesets": [ + "rich-ants-shop" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bda83a..73184a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @labdigital/react-query-opal +## 3.0.0-beta.0 + +### Major Changes + +- ff7f7e4: Remove deprecated is "isPersistedQuery" and make apq explicitly opt in + ## 2.0.0 ### Minor Changes diff --git a/package.json b/package.json index 4032b22..c7b54f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labdigital/graphql-fetcher", - "version": "2.0.0", + "version": "3.0.0-beta.0", "description": "Custom fetcher for react-query to use with @labdigital/node-federated-token", "type": "module", "main": "./dist/index.cjs", From 6d280ddca9ecee94c54c9c2429946b2d530ca225 Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Mon, 27 Oct 2025 13:56:36 +0100 Subject: [PATCH 7/8] feat: explicitly require apq on server fetcher --- src/server.test.ts | 56 +++++++++++++++-- src/server.ts | 150 +++++++++++++++++++++++++++++---------------- 2 files changed, 148 insertions(+), 58 deletions(-) diff --git a/src/server.test.ts b/src/server.test.ts index a739085..94ad16a 100644 --- a/src/server.test.ts +++ b/src/server.test.ts @@ -25,7 +25,9 @@ const errorResponse = JSON.stringify({ describe("gqlServerFetch", () => { it("should fetch a persisted query", async () => { - const gqlServerFetch = initServerFetcher("https://localhost/graphql"); + const gqlServerFetch = initServerFetcher("https://localhost/graphql", { + apq: true, + }); const mockedFetch = fetchMock.mockResponse(successResponse); const gqlResponse = await gqlServerFetch( query, @@ -57,7 +59,9 @@ describe("gqlServerFetch", () => { }); it("should persist the query if it wasn't persisted yet", async () => { - const gqlServerFetch = initServerFetcher("https://localhost/graphql"); + const gqlServerFetch = initServerFetcher("https://localhost/graphql", { + apq: true, + }); // Mock server saying: 'PersistedQueryNotFound' const mockedFetch = fetchMock .mockResponseOnce(errorResponse) @@ -134,7 +138,9 @@ describe("gqlServerFetch", () => { }); it("should fetch a persisted query without revalidate", async () => { - const gqlServerFetch = initServerFetcher("https://localhost/graphql"); + const gqlServerFetch = initServerFetcher("https://localhost/graphql", { + apq: true, + }); const mockedFetch = fetchMock.mockResponse(successResponse); const gqlResponse = await gqlServerFetch( query, @@ -166,7 +172,9 @@ describe("gqlServerFetch", () => { }); it("should fetch a with custom headers", async () => { - const gqlServerFetch = initServerFetcher("https://localhost/graphql"); + const gqlServerFetch = initServerFetcher("https://localhost/graphql", { + apq: true, + }); const mockedFetch = fetchMock.mockResponse(successResponse); const gqlResponse = await gqlServerFetch( query, @@ -257,6 +265,7 @@ describe("gqlServerFetch", () => { const gqlServerFetch = initServerFetcher("https://localhost/graphql", { defaultTimeout: 1, + apq: true, }); fetchMock.mockResponse(successResponse); @@ -338,3 +347,42 @@ describe("gqlServerFetch", () => { expect(fetchMock).toHaveBeenCalledTimes(1); }); }); + +it("should skip persisted queries if operation apq is disabled", async () => { + const gqlServerFetch = initServerFetcher("https://localhost/graphql", { + apq: false, + }); + const mockedFetch = fetchMock.mockResponseOnce(successResponse); + + const gqlResponse = await gqlServerFetch( + query, + { myVar: "baz" }, + { + next: { revalidate: 900 }, + }, + ); + + expect(gqlResponse).toEqual(response); + expect(mockedFetch).toHaveBeenCalledTimes(1); + expect(mockedFetch).toHaveBeenNthCalledWith( + 1, + "https://localhost/graphql?op=myQuery", + { + method: "POST", + body: JSON.stringify({ + query: query.toString(), + variables: { myVar: "baz" }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash: await createSha256(query.toString()), + }, + }, + }), + headers: new Headers({ + "Content-Type": "application/json", + }), + next: { revalidate: 900 }, + }, + ); +}); diff --git a/src/server.ts b/src/server.ts index b5ce68e..6aa2493 100644 --- a/src/server.ts +++ b/src/server.ts @@ -31,6 +31,12 @@ type RequestOptions = { }; type Options = { + /** + * Enable use of automated persisted queries, this will always add a extra + * roundtrip to the server if queries aren't cacheable + * @default false + */ + apq?: boolean; /** * Disables all forms of caching for the fetcher, use only in development * @@ -81,6 +87,7 @@ export const initServerFetcher = defaultTimeout = undefined, defaultHeaders = {}, includeQuery = false, + apq = false, createDocumentId = getDocumentId, }: Options = {}, ) => @@ -137,63 +144,36 @@ export const initServerFetcher = }); } - // Skip automatic persisted queries if operation is a mutation const queryType = getQueryType(query); - if (queryType === "mutation") { - return tracer.startActiveSpan(request.operationName, async (span) => { - try { - const response = await gqlPost( - url, - request, - { cache, next }, - requestOptions, - ); - - span.end(); - return response as GqlResponse; - } catch (err: unknown) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: err instanceof Error ? err.message : String(err), - }); - throw err; - } - }); + if (!apq) { + return post( + request, + url, + cache, + next, + requestOptions, + ); } - // Otherwise, try to get the cached query - return tracer.startActiveSpan(request.operationName, async (span) => { - try { - let response = await gqlPersistedQuery( - url, - request, - { cache, next }, - requestOptions, - ); - - // If this is not a persisted query, but we tried to use automatic - // persisted queries (APQ) then we retry with a POST - if (!isPersistedQuery(request) && hasPersistedQueryError(response)) { - // If the cached query doesn't exist, fall back to POST request and - // let the server cache it. - response = await gqlPost( - url, - request, - { cache, next }, - requestOptions, - ); - } + // if apq is enabled, only queries are converted into get calls + // https://www.apollographql.com/docs/apollo-server/performance/apq#using-get-requests-with-apq-on-a-cdn + if (queryType === "mutation") { + return post( + request, + url, + cache, + next, + requestOptions, + ); + } - span.end(); - return response as GqlResponse; - } catch (err: any) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: err?.message ?? String(err), - }); - throw err; - } - }); + return get( + request, + url, + cache, + next, + requestOptions, + ); }; const gqlPost = async ( @@ -204,7 +184,6 @@ const gqlPost = async ( ) => { const endpoint = new URL(url); endpoint.searchParams.append("op", request.operationName); - const response = await fetch(endpoint.toString(), { headers: options.headers, method: "POST", @@ -253,3 +232,66 @@ const parseResponse = async ( return await response.json(); }; +function get( + request: GraphQLRequest, + url: string, + cache: RequestCache | undefined, + next: NextFetchRequestConfig, + requestOptions: RequestOptions, +): GqlResponse | PromiseLike> { + return tracer.startActiveSpan(request.operationName, async (span) => { + try { + let response = await gqlPersistedQuery( + url, + request, + { cache, next }, + requestOptions, + ); + + // If this is not a persisted query, but we tried to use automatic + // persisted queries (APQ) then we retry with a POST + if (!isPersistedQuery(request) && hasPersistedQueryError(response)) { + // If the cached query doesn't exist, fall back to POST request and + // let the server cache it. + response = await gqlPost(url, request, { cache, next }, requestOptions); + } + + span.end(); + return response as GqlResponse; + } catch (err: any) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err?.message ?? String(err), + }); + throw err; + } + }); +} + +function post( + request: GraphQLRequest, + url: string, + cache: RequestCache | undefined, + next: NextFetchRequestConfig, + requestOptions: RequestOptions, +): GqlResponse | PromiseLike> { + return tracer.startActiveSpan(request.operationName, async (span) => { + try { + const response = await gqlPost( + url, + request, + { cache, next }, + requestOptions, + ); + + span.end(); + return response as GqlResponse; + } catch (err: unknown) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err instanceof Error ? err.message : String(err), + }); + throw err; + } + }); +} From d3cb9db317696f3d254186591530a52309e06e1a Mon Sep 17 00:00:00 2001 From: Paul Vaneveld Date: Mon, 27 Oct 2025 14:21:04 +0100 Subject: [PATCH 8/8] chore: version bump --- .changeset/pre.json | 3 ++- .changeset/sharp-radios-relate.md | 5 +++++ CHANGELOG.md | 6 ++++++ package.json | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 .changeset/sharp-radios-relate.md diff --git a/.changeset/pre.json b/.changeset/pre.json index e53cfb9..83827a3 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -5,6 +5,7 @@ "@labdigital/graphql-fetcher": "2.0.0" }, "changesets": [ - "rich-ants-shop" + "rich-ants-shop", + "sharp-radios-relate" ] } diff --git a/.changeset/sharp-radios-relate.md b/.changeset/sharp-radios-relate.md new file mode 100644 index 0000000..2a1553a --- /dev/null +++ b/.changeset/sharp-radios-relate.md @@ -0,0 +1,5 @@ +--- +"@labdigital/graphql-fetcher": minor +--- + +Make apq opt in for the server side client instead of opt in diff --git a/CHANGELOG.md b/CHANGELOG.md index 73184a2..7074d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @labdigital/react-query-opal +## 3.0.0-beta.1 + +### Minor Changes + +- Make apq opt in for the server side client instead of opt in + ## 3.0.0-beta.0 ### Major Changes diff --git a/package.json b/package.json index c7b54f4..10d8b87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labdigital/graphql-fetcher", - "version": "3.0.0-beta.0", + "version": "3.0.0-beta.1", "description": "Custom fetcher for react-query to use with @labdigital/node-federated-token", "type": "module", "main": "./dist/index.cjs",