Skip to content

Commit 7b80592

Browse files
authored
fix: the graph pagination (#1218)
## Summary by Sourcery Improve TheGraph client pagination by enhancing directive stripping, streamlining default argument handling, and unifying query invocation to support both document strings and request options. Bug Fixes: - Ensure default pagination limits (first/skip) are consistently applied when using the @fetchall directive. Enhancements: - Enable parsing and pagination for string-based RequestDocument inputs by integrating graphql parse in stripFetchAllDirective. - Eliminate explicit hasFirst/hasSkip flags and simplify firstValue/skipValue determination with default fallback logic. - Overload the query method to accept either a document plus variables or a RequestOptions object, using an isRequestOptions type guard. - Preserve the original graphql-request client.request method and wrap it with the paginated client.query to integrate pagination correctly.
1 parent 0340edf commit 7b80592

File tree

2 files changed

+30
-28
lines changed

2 files changed

+30
-28
lines changed

sdk/thegraph/src/thegraph.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,11 @@ export function createTheGraphClient<const Setup extends AbstractSetupSchema>(
105105
...clientOptions,
106106
headers: appendHeaders(clientOptions?.headers, { "x-auth-token": validatedOptions.accessToken }),
107107
});
108-
const paginatedClient = createTheGraphClientWithPagination(client);
109-
client.request = paginatedClient.query as typeof client.request;
108+
const originalRequest = client.request.bind(client);
109+
const paginatedClient = createTheGraphClientWithPagination({
110+
request: originalRequest,
111+
});
112+
client.request = paginatedClient.query;
110113
return {
111114
client,
112115
graphql,

sdk/thegraph/src/utils/pagination.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { sortBy } from "es-toolkit";
22
import { get, isArray, isEmpty, set } from "es-toolkit/compat";
33
import type { TadaDocumentNode } from "gql.tada";
4-
import { type ArgumentNode, type DocumentNode, Kind, type SelectionNode, visit } from "graphql";
5-
import type { GraphQLClient, Variables } from "graphql-request";
4+
import { type ArgumentNode, type DocumentNode, Kind, parse, type SelectionNode, visit } from "graphql";
5+
import type { GraphQLClient, RequestDocument, RequestOptions, Variables } from "graphql-request";
66

77
// Constants for TheGraph limits
88
const THE_GRAPH_LIMIT = 500;
@@ -14,7 +14,6 @@ interface ListField {
1414
path: string[];
1515
fieldName: string;
1616
alias?: string;
17-
hasFirstArg: boolean;
1817
firstValue?: number;
1918
skipValue?: number;
2019
otherArgs: ArgumentNode[];
@@ -35,13 +34,13 @@ interface ListField {
3534
* - Removes the directive from the AST (The Graph doesn't recognize it)
3635
* - Returns both the cleaned document and a list of fields to auto-paginate
3736
*/
38-
function stripFetchAllDirective(document: DocumentNode): {
37+
function stripFetchAllDirective(document: DocumentNode | RequestDocument): {
3938
document: DocumentNode;
4039
fetchAllFields: Set<string>;
4140
} {
4241
const fetchAllFields = new Set<string>();
43-
44-
const strippedDocument = visit(document, {
42+
const documentNode = typeof document === "string" ? parse(document) : document;
43+
const strippedDocument = visit(documentNode, {
4544
Field(node) {
4645
// Check if this field has the @fetchAll directive
4746
if (node.directives && node.directives.length > 0) {
@@ -132,41 +131,27 @@ function extractFetchAllFields(
132131
}
133132

134133
// Check if this field has pagination arguments (first or skip)
135-
let hasFirstArg = false;
136-
let hasSkipArg = false;
137134
let firstValue: number | undefined;
138135
let skipValue: number | undefined;
139-
let firstValueIsDefault = false;
140136
const otherArgs: ArgumentNode[] = [];
141137

142138
if (node.arguments) {
143139
for (const arg of node.arguments) {
144140
if (arg.name.value === FIRST_ARG) {
145-
hasFirstArg = true;
146141
if (arg.value.kind === Kind.INT) {
147142
firstValue = Number.parseInt(arg.value.value);
148143
} else if (arg.value.kind === Kind.VARIABLE && variables) {
149144
const varName = arg.value.name.value;
150145
const varValue = (variables as Record<string, unknown>)[varName];
151146
firstValue = typeof varValue === "number" ? varValue : undefined;
152-
// If variable is defined in query but not passed in input, check if it's a standard pagination variable
153-
if (firstValue === undefined && varName === arg.value.name.value) {
154-
firstValue = THE_GRAPH_LIMIT; // Default to THE_GRAPH_LIMIT
155-
firstValueIsDefault = true; // Mark that this was defaulted
156-
}
157147
}
158148
} else if (arg.name.value === SKIP_ARG) {
159-
hasSkipArg = true;
160149
if (arg.value.kind === Kind.INT) {
161150
skipValue = Number.parseInt(arg.value.value);
162151
} else if (arg.value.kind === Kind.VARIABLE && variables) {
163152
const varName = arg.value.name.value;
164153
const varValue = (variables as Record<string, unknown>)[varName];
165154
skipValue = typeof varValue === "number" ? varValue : undefined;
166-
// If variable is defined in query but not passed in input, check if it's a standard pagination variable
167-
if (skipValue === undefined && varName === arg.value.name.value) {
168-
skipValue = 0; // Default to 0
169-
}
170155
}
171156
} else {
172157
otherArgs.push(arg);
@@ -190,13 +175,12 @@ function extractFetchAllFields(
190175
path: [...pathStack],
191176
fieldName: node.name.value,
192177
alias: node.alias?.value,
193-
hasFirstArg,
194-
firstValue: hasFetchAllDirective && !hasFirstArg ? THE_GRAPH_LIMIT : firstValue,
195-
skipValue: hasFetchAllDirective && !hasSkipArg ? 0 : skipValue,
178+
firstValue: hasFetchAllDirective && (firstValue ?? THE_GRAPH_LIMIT),
179+
skipValue: hasFetchAllDirective && (skipValue ?? 0),
196180
otherArgs,
197181
selections: node.selectionSet?.selections,
198182
hasFetchAllDirective,
199-
firstValueIsDefault,
183+
firstValueIsDefault: hasFetchAllDirective ? firstValue === undefined : false,
200184
});
201185
}
202186
},
@@ -398,9 +382,20 @@ export function createTheGraphClientWithPagination(theGraphClient: Pick<GraphQLC
398382

399383
return {
400384
async query<TResult, TVariables extends Variables>(
401-
document: TadaDocumentNode<TResult, TVariables>,
402-
variables: Omit<TVariables, "skip" | "first">,
385+
documentOrOptions: TadaDocumentNode<TResult, TVariables> | RequestDocument | RequestOptions<TVariables, TResult>,
386+
variablesRaw?: Omit<TVariables, "skip" | "first">,
403387
): Promise<TResult> {
388+
let document: TadaDocumentNode<TResult, TVariables> | RequestDocument;
389+
let variables: Omit<TVariables, "skip" | "first">;
390+
391+
if (isRequestOptions(documentOrOptions)) {
392+
document = documentOrOptions.document;
393+
variables = documentOrOptions.variables as TVariables;
394+
} else {
395+
document = documentOrOptions;
396+
variables = variablesRaw ?? ({} as TVariables);
397+
}
398+
404399
// First, detect and strip @fetchAll directives
405400
const { document: processedDocument, fetchAllFields } = stripFetchAllDirective(document);
406401

@@ -450,3 +445,7 @@ export function createTheGraphClientWithPagination(theGraphClient: Pick<GraphQLC
450445
},
451446
} as const;
452447
}
448+
449+
function isRequestOptions(args: unknown): args is RequestOptions<Variables, unknown> {
450+
return typeof args === "object" && args !== null && "document" in args && "variables" in args;
451+
}

0 commit comments

Comments
 (0)