From def4f3309c01d4ec095eda999808d76fee652224 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:47:46 +0000 Subject: [PATCH 1/4] fix: ensure devtools compatibility with @apollo/client 3.13.9 - Add fetchQueryObservable shim for Apollo Client 3.8+ where it was replaced by fetchConcastWithInfo (used by apollo-inspector) - Add null-safety for mutationStore in tracker and recent-activity publishers - Widen @apollo/client dev dependency to >=3.6.0 Agent-Logs-Url: https://github.com/microsoft/graphitation/sessions/c31ae7e8-02d9-44de-a2af-1739a775a8fc Co-authored-by: pavelglac <42679661+pavelglac@users.noreply.github.com> --- packages/rempl-apollo-devtools/package.json | 2 +- .../publisher/helpers/apollo-client-compat.ts | 51 +++++++++++++++++++ .../apollo-operations-tracker-publisher.ts | 11 ++-- .../apollo-recent-activity-publisher.ts | 4 +- .../publishers/apollo-tracker-publisher.ts | 4 +- 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts diff --git a/packages/rempl-apollo-devtools/package.json b/packages/rempl-apollo-devtools/package.json index 2a1e44a45..0a739f4e4 100644 --- a/packages/rempl-apollo-devtools/package.json +++ b/packages/rempl-apollo-devtools/package.json @@ -18,7 +18,7 @@ "just": "monorepo-scripts" }, "devDependencies": { - "@apollo/client": "~3.6.0", + "@apollo/client": ">=3.6.0", "@emotion/react": "^11.8.1", "@fluentui/react-components": "^9.18.9", "@fluentui/react-data-grid-react-window": "^9.0.0-beta.21", diff --git a/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts new file mode 100644 index 000000000..ccc61d3c7 --- /dev/null +++ b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts @@ -0,0 +1,51 @@ +import { ApolloClient, NormalizedCacheObject } from "@apollo/client"; + +/** + * Ensures an Apollo Client instance is compatible with apollo-inspector + * by shimming internal APIs that were renamed/removed in newer versions. + * + * Apollo Client 3.8+ replaced `queryManager.fetchQueryObservable` with + * `queryManager.fetchConcastWithInfo`. apollo-inspector hooks into + * `fetchQueryObservable` to track query operations; without it, queries + * are silently missing from recordings. + */ +export function ensureApolloClientCompat( + client: ApolloClient, +): void { + const qm = (client as any).queryManager; + if (!qm) return; + + // Shim fetchQueryObservable for Apollo Client 3.8+ + // where it was replaced by fetchConcastWithInfo (private). + if (!qm.fetchQueryObservable && qm.fetchConcastWithInfo) { + const getQueryInfo = + typeof qm.getOrCreateQuery === "function" + ? qm.getOrCreateQuery.bind(qm) + : typeof qm.getQuery === "function" + ? qm.getQuery.bind(qm) + : undefined; + + if (getQueryInfo) { + qm.fetchQueryObservable = function fetchQueryObservableShim( + queryId: string, + options: any, + networkStatus?: any, + ) { + const queryInfo = getQueryInfo(queryId); + const result = qm.fetchConcastWithInfo( + queryInfo, + options, + networkStatus, + ); + return result.concast; + }; + } + } + + // Ensure mutationStore exists — in Apollo Client 3.13+, + // mutationStore is only initialised when onBroadcast is provided. + // Some devtools code iterates mutationStore, so it must be non-null. + if (!qm.mutationStore) { + qm.mutationStore = Object.create(null); + } +} diff --git a/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-operations-tracker-publisher.ts b/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-operations-tracker-publisher.ts index 511a49791..3321118b4 100644 --- a/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-operations-tracker-publisher.ts +++ b/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-operations-tracker-publisher.ts @@ -8,6 +8,7 @@ import { import { ClientObject, WrapperCallbackParams } from "../../types"; import { Subscription } from "rxjs"; import { ICopyData } from "apollo-inspector-ui"; +import { ensureApolloClientCompat } from "../helpers/apollo-client-compat"; export class ApolloOperationsTrackerPublisher { private remplWrapper: RemplWrapper; @@ -34,11 +35,15 @@ export class ApolloOperationsTrackerPublisher { this.apolloPublisher.provide("startOperationsTracker", (options: any) => { this.trackingSubscription?.unsubscribe(); const apolloClients: IApolloClientObject[] = this.apolloClients.map( - (ac) => - ({ + (ac) => { + ensureApolloClientCompat( + ac.client as ApolloClient, + ); + return { client: ac.client as ApolloClient, clientId: ac.clientId, - } as unknown as IApolloClientObject), + } as unknown as IApolloClientObject; + }, ); const inspector = new ApolloInspector(apolloClients); diff --git a/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-recent-activity-publisher.ts b/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-recent-activity-publisher.ts index 8badf57e3..792e7b394 100644 --- a/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-recent-activity-publisher.ts +++ b/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-recent-activity-publisher.ts @@ -159,8 +159,8 @@ export class ApolloRecentActivityPublisher { if (client.queryManager.mutationStore?.getStore) { return client.queryManager.mutationStore.getStore(); } else { - // Apollo Client 3.3+ - return client.queryManager.mutationStore; + // Apollo Client 3.3+ (mutationStore may be undefined in 3.13+) + return client.queryManager.mutationStore || {}; } } diff --git a/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-tracker-publisher.ts b/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-tracker-publisher.ts index 08767a9ba..2d278f50e 100644 --- a/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-tracker-publisher.ts +++ b/packages/rempl-apollo-devtools/src/publisher/publishers/apollo-tracker-publisher.ts @@ -128,8 +128,8 @@ export class ApolloTrackerPublisher { if (client.queryManager.mutationStore?.getStore) { return client.queryManager.mutationStore.getStore(); } else { - // Apollo Client 3.3+ - return client.queryManager.mutationStore; + // Apollo Client 3.3+ (mutationStore may be undefined in 3.13+) + return client.queryManager.mutationStore || {}; } } From ee19555e59b89cd6d931389f99c1d64b81eb617b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:49:51 +0000 Subject: [PATCH 2/4] fix: address code review feedback in compat shim Agent-Logs-Url: https://github.com/microsoft/graphitation/sessions/c31ae7e8-02d9-44de-a2af-1739a775a8fc Co-authored-by: pavelglac <42679661+pavelglac@users.noreply.github.com> --- .../src/publisher/helpers/apollo-client-compat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts index ccc61d3c7..a93f917eb 100644 --- a/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts +++ b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts @@ -43,9 +43,9 @@ export function ensureApolloClientCompat( } // Ensure mutationStore exists — in Apollo Client 3.13+, - // mutationStore is only initialised when onBroadcast is provided. + // mutationStore is only initialized when onBroadcast is provided. // Some devtools code iterates mutationStore, so it must be non-null. if (!qm.mutationStore) { - qm.mutationStore = Object.create(null); + qm.mutationStore = {}; } } From 55934f2f83697b45127500ba68d0805f8af9562a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:15:25 +0000 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20properly=20bridge=20fetchConcastWith?= =?UTF-8?q?Info=20=E2=86=92=20fetchQueryObservable,=20add=20getQuery=20ali?= =?UTF-8?q?as,=20apply=20shim=20early?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three critical fixes for Apollo Client 3.8+/3.13.9 compatibility: 1. Wrap fetchConcastWithInfo to route through fetchQueryObservable when apollo-inspector's hook is installed. Previously the shim only added fetchQueryObservable but Apollo Client never called it - now internal calls to fetchConcastWithInfo trigger the hook. 2. Add getQuery as an alias for getOrCreateQuery (renamed in 3.8+). apollo-inspector calls queryManager.getQuery() in its verbose hook. 3. Apply compat shim in rempl-wrapper.runAllHooks() so ALL publishers benefit (tracker, recent-activity, operations-tracker) instead of only when operations tracking starts. Also reverts @apollo/client devDep from >=3.6.0 back to ~3.6.0 since the wider range accidentally pulled in v4.x at build time. Agent-Logs-Url: https://github.com/microsoft/graphitation/sessions/f7c76f3f-a3f4-4e3d-a426-bfd6a4ce2472 Co-authored-by: pavelglac <42679661+pavelglac@users.noreply.github.com> --- packages/rempl-apollo-devtools/package.json | 2 +- .../publisher/helpers/apollo-client-compat.ts | 77 ++++++++++++++++--- .../src/publisher/rempl-wrapper.ts | 7 ++ 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/packages/rempl-apollo-devtools/package.json b/packages/rempl-apollo-devtools/package.json index 0a739f4e4..2a1e44a45 100644 --- a/packages/rempl-apollo-devtools/package.json +++ b/packages/rempl-apollo-devtools/package.json @@ -18,7 +18,7 @@ "just": "monorepo-scripts" }, "devDependencies": { - "@apollo/client": ">=3.6.0", + "@apollo/client": "~3.6.0", "@emotion/react": "^11.8.1", "@fluentui/react-components": "^9.18.9", "@fluentui/react-data-grid-react-window": "^9.0.0-beta.21", diff --git a/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts index a93f917eb..826f8c296 100644 --- a/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts +++ b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts @@ -5,9 +5,16 @@ import { ApolloClient, NormalizedCacheObject } from "@apollo/client"; * by shimming internal APIs that were renamed/removed in newer versions. * * Apollo Client 3.8+ replaced `queryManager.fetchQueryObservable` with - * `queryManager.fetchConcastWithInfo`. apollo-inspector hooks into - * `fetchQueryObservable` to track query operations; without it, queries - * are silently missing from recordings. + * the private `queryManager.fetchConcastWithInfo` and renamed `getQuery` + * to `getOrCreateQuery`. apollo-inspector monkey-patches + * `fetchQueryObservable` to track operations, so we must: + * + * 1. Provide a `fetchQueryObservable` shim backed by `fetchConcastWithInfo`. + * 2. Wrap `fetchConcastWithInfo` so that every call routes through + * `fetchQueryObservable` — this ensures apollo-inspector's hook is + * triggered by Apollo Client's own internal calls. + * 3. Alias `getQuery` → `getOrCreateQuery` (used by apollo-inspector + * inside its `fetchQueryObservable` hook). */ export function ensureApolloClientCompat( client: ApolloClient, @@ -15,8 +22,15 @@ export function ensureApolloClientCompat( const qm = (client as any).queryManager; if (!qm) return; - // Shim fetchQueryObservable for Apollo Client 3.8+ - // where it was replaced by fetchConcastWithInfo (private). + // 1. Alias getQuery → getOrCreateQuery (renamed in Apollo Client 3.8+). + // apollo-inspector calls queryManager.getQuery(queryId) in its hooks. + if (!qm.getQuery && typeof qm.getOrCreateQuery === "function") { + qm.getQuery = qm.getOrCreateQuery; + } + + // 2. Shim fetchQueryObservable and wrap fetchConcastWithInfo. + // apollo-inspector hooks fetchQueryObservable, but Apollo Client 3.8+ + // only calls fetchConcastWithInfo internally — so we must bridge them. if (!qm.fetchQueryObservable && qm.fetchConcastWithInfo) { const getQueryInfo = typeof qm.getOrCreateQuery === "function" @@ -26,25 +40,68 @@ export function ensureApolloClientCompat( : undefined; if (getQueryInfo) { - qm.fetchQueryObservable = function fetchQueryObservableShim( + const originalFetchConcastWithInfo = qm.fetchConcastWithInfo; + + // Closure variable to pass fromLink from the shim back to the + // fetchConcastWithInfo wrapper (safe: single-threaded execution). + let lastFromLink = true; + + // The shim that apollo-inspector will hook into. + // It delegates to the *original* fetchConcastWithInfo. + const shimFetchQueryObservable = function fetchQueryObservableShim( + this: any, queryId: string, options: any, networkStatus?: any, ) { const queryInfo = getQueryInfo(queryId); - const result = qm.fetchConcastWithInfo( + const result = originalFetchConcastWithInfo.call( + this, queryInfo, options, networkStatus, ); + lastFromLink = result.fromLink; return result.concast; }; + qm.fetchQueryObservable = shimFetchQueryObservable; + + // Wrap fetchConcastWithInfo so that Apollo Client's internal calls + // are routed through fetchQueryObservable (which may be replaced by + // apollo-inspector's hook at tracking time). + qm.fetchConcastWithInfo = function fetchConcastWithInfoWrapper( + this: any, + queryInfo: any, + options: any, + networkStatus?: any, + query?: any, + ) { + // If apollo-inspector has replaced fetchQueryObservable with its + // own hook, route through it so operation tracking triggers. + if (this.fetchQueryObservable !== shimFetchQueryObservable) { + const concast = this.fetchQueryObservable( + queryInfo.queryId, + options, + networkStatus, + ); + return { concast, fromLink: lastFromLink }; + } + + // No hook installed — call original directly (avoids overhead). + return originalFetchConcastWithInfo.call( + this, + queryInfo, + options, + networkStatus, + query, + ); + }; } } - // Ensure mutationStore exists — in Apollo Client 3.13+, - // mutationStore is only initialized when onBroadcast is provided. - // Some devtools code iterates mutationStore, so it must be non-null. + // 3. Ensure mutationStore exists — in Apollo Client 3.13+, + // mutationStore is only initialized when onBroadcast is provided. + // Devtools code iterates mutationStore, so it must be non-null. if (!qm.mutationStore) { qm.mutationStore = {}; } diff --git a/packages/rempl-apollo-devtools/src/publisher/rempl-wrapper.ts b/packages/rempl-apollo-devtools/src/publisher/rempl-wrapper.ts index 842d1481c..fb895c5f1 100644 --- a/packages/rempl-apollo-devtools/src/publisher/rempl-wrapper.ts +++ b/packages/rempl-apollo-devtools/src/publisher/rempl-wrapper.ts @@ -1,6 +1,7 @@ import { createPublisher, getHost } from "rempl"; import hotkeys from "hotkeys-js"; import { ClientObject, WrapperCallbackParams, Publisher } from "../types"; +import { ensureApolloClientCompat } from "./helpers/apollo-client-compat"; declare let __APOLLO_DEVTOOLS_SUBSCRIBER__: string; type RemplStatusHook = { @@ -94,6 +95,12 @@ export class RemplWrapper { return; } + // Apply compatibility shims for newer Apollo Client versions (3.8+). + // This must run before any publisher accesses client internals. + for (const clientObj of browserWindow.__APOLLO_CLIENTS__) { + ensureApolloClientCompat(clientObj.client); + } + for (const { id, callback, timeout } of this.remplStatusHooks) { if (this.intervalExists(id)) { return; From 54fad144771c840de0e25c19d458ade3664d372f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:16:40 +0000 Subject: [PATCH 4/4] fix: add idempotency guard to compat shim Agent-Logs-Url: https://github.com/microsoft/graphitation/sessions/f7c76f3f-a3f4-4e3d-a426-bfd6a4ce2472 Co-authored-by: pavelglac <42679661+pavelglac@users.noreply.github.com> --- .../src/publisher/helpers/apollo-client-compat.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts index 826f8c296..afbd3a7a7 100644 --- a/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts +++ b/packages/rempl-apollo-devtools/src/publisher/helpers/apollo-client-compat.ts @@ -22,6 +22,10 @@ export function ensureApolloClientCompat( const qm = (client as any).queryManager; if (!qm) return; + // Guard against repeated calls on the same client. + if (qm.__apolloCompatApplied) return; + qm.__apolloCompatApplied = true; + // 1. Alias getQuery → getOrCreateQuery (renamed in Apollo Client 3.8+). // apollo-inspector calls queryManager.getQuery(queryId) in its hooks. if (!qm.getQuery && typeof qm.getOrCreateQuery === "function") {