Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions sdk/thegraph/src/utils/pagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,29 @@ describe("createTheGraphClientWithPagination", () => {
});
});

it("should return token if only @fetchAll field is requested", async () => {
const result = await client.query(
theGraphGraphql(`
query($name: String) {
token(name: $name) {
holders @fetchAll {
name
}
}
}`),
{
name: "Token 100",
},
);

expect(result).toEqual({
token: {
holders: TEST_HOLDERS,
},
});
expect(requestMock).toHaveBeenCalledTimes(3);
});

it("should allow multiple queries in a single request", async () => {
const result = await client.query(
theGraphGraphql(`
Expand Down
32 changes: 26 additions & 6 deletions sdk/thegraph/src/utils/pagination.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { sortBy } from "es-toolkit";
import { get, isArray, isEmpty, set } from "es-toolkit/compat";
import type { TadaDocumentNode } from "gql.tada";
import { type ArgumentNode, type DocumentNode, Kind, parse, visit } from "graphql";
import { type ArgumentNode, type DocumentNode, type FieldNode, Kind, parse, visit } from "graphql";
import type { GraphQLClient, RequestDocument, RequestOptions, Variables } from "graphql-request";

// Constants for TheGraph limits
Expand Down Expand Up @@ -244,7 +244,6 @@ function createSingleFieldQuery(

// Create query without list fields
function createNonListQuery(document: DocumentNode, listFields: ListFieldWithFetchAllDirective[]): DocumentNode | null {
let hasFields = false;
const pathStack: string[] = [];

const filtered = visit(document, {
Expand All @@ -263,17 +262,35 @@ function createNonListQuery(document: DocumentNode, listFields: ListFieldWithFet
pathStack.pop();
return null;
}

hasFields = true;
return undefined;
},
leave: () => {
leave: (node: FieldNode) => {
pathStack.pop();
if (node.selectionSet && node.selectionSet.selections.length === 0) {
return null;
}
return undefined;
},
},
});

return hasFields ? filtered : null;
return filtered;
}

function countExecutableFields(document: DocumentNode): number {
let fieldCount = 0;

visit(document, {
Field: (node) => {
if (!node.name.value.startsWith("__")) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

countExecutableFields excludes meta fields like __typename, which can cause the non-list request to be skipped and omit requested data when __typename is the only remaining field. Include __typename in the count so the follow-up query executes.

Prompt for AI agents
Address the following comment on sdk/thegraph/src/utils/pagination.ts at line 285:

<comment>countExecutableFields excludes meta fields like __typename, which can cause the non-list request to be skipped and omit requested data when __typename is the only remaining field. Include __typename in the count so the follow-up query executes.</comment>

<file context>
@@ -263,17 +262,35 @@ function createNonListQuery(document: DocumentNode, listFields: ListFieldWithFet
+
+  visit(document, {
+    Field: (node) =&gt; {
+      if (!node.name.value.startsWith(&quot;__&quot;)) {
+        if (!node.selectionSet || node.selectionSet.selections.length &gt; 0) {
+          fieldCount += 1;
</file context>
Fix with Cubic

if (!node.selectionSet || node.selectionSet.selections.length > 0) {
fieldCount += 1;
}
}
},
});
Comment on lines +284 to +291
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don't skip __typename-only follow-up queries

countExecutableFields ignores fields whose names start with "__", so a query like token { __typename holders @fetchAll { ... } } now short-circuits before the non-list request runs. The merged result never includes __typename, even though the caller explicitly asked for it. Previously we still executed the non-list fetch and returned the correct payload. Please count meta fields as executable (at least __typename) so we don't drop requested data.

Apply this diff to ensure __typename keeps the non-list query alive:

-    Field: (node) => {
-      if (!node.name.value.startsWith("__")) {
-        if (!node.selectionSet || node.selectionSet.selections.length > 0) {
-          fieldCount += 1;
-        }
-      }
-    },
+    Field: (node) => {
+      if (!node.selectionSet || node.selectionSet.selections.length > 0) {
+        fieldCount += 1;
+      }
+    },
🤖 Prompt for AI Agents
In sdk/thegraph/src/utils/pagination.ts around lines 284-291, the current logic
skips all fields whose names start with "__" causing queries that only request
__typename (e.g. token { __typename holders @fetchAll { ... } }) to
short-circuit and drop requested data; update the conditional so that meta
fields are still counted when executable, specifically treat "__typename" as
executable: do not exclude it from the fieldCount check (i.e., only skip
double-underscore fields except for "__typename"), and keep the existing
selectionSet/selections length check so __typename-only follow-up queries keep
the non-list query alive.


return fieldCount;
}

// Filter variables to only include used ones
Expand Down Expand Up @@ -416,6 +433,9 @@ export function createTheGraphClientWithPagination(theGraphClient: Pick<GraphQLC
const nonListQuery = createNonListQuery(processedDocument, listFields);

if (nonListQuery) {
if (countExecutableFields(nonListQuery) === 0) {
return result as TResult;
}
const nonListResult = await theGraphClient.request(
nonListQuery,
filterVariables(variables, nonListQuery) ?? {},
Expand Down
Loading