Skip to content

Commit 807b43c

Browse files
committed
Alternative implementation
1 parent 5045792 commit 807b43c

File tree

6 files changed

+111
-36
lines changed

6 files changed

+111
-36
lines changed

README.md

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,45 @@ await octokit.graphql.paginate(
157157
);
158158
```
159159

160-
### Options
160+
### Early stop when iterating
161161

162-
You can provide a third argument to `paginate` or `iterator` to modify the behavior of the pagination.
162+
You can provide a third argument, a function, to `paginate` or `iterator` to stop the pagination earlier. The function will be called with two arguments, the first is the content of the most recent page, the second is a `done` function to stop the iteration.
163163

164-
`maxPages` will stop the iteration at the specified number of pages, useful when you don't need all the items in the response but still want to take advantage of the automatic merging.
164+
For example, you can stop the iteration after a certain number of pages:
165165

166+
```js
167+
const maxPages = 2;
168+
let pages = 0;
169+
170+
await octokit.graphql.paginate(
171+
`query paginate ($cursor: String) {
172+
repository(owner: "octokit", name: "rest.js") {
173+
issues(first: 10, after: $cursor) {
174+
nodes {
175+
title
176+
}
177+
pageInfo {
178+
hasNextPage
179+
endCursor
180+
}
181+
}
182+
}
183+
}`,
184+
{},
185+
(_, done) => {
186+
pages += 1;
187+
if (pages >= maxPages) {
188+
done();
189+
}
190+
},
191+
);
166192
```
167-
const { repository } = await octokit.graphql.paginate(
168-
`query paginate($cursor: String) {
193+
194+
Or, to stop after you find a certain item:
195+
196+
```js
197+
await octokit.graphql.paginate(
198+
`query paginate ($cursor: String) {
169199
repository(owner: "octokit", name: "rest.js") {
170200
issues(first: 10, after: $cursor) {
171201
nodes {
@@ -178,8 +208,12 @@ const { repository } = await octokit.graphql.paginate(
178208
}
179209
}
180210
}`,
181-
{ },
182-
{ maxPages: 10 },
211+
{},
212+
(response, done) => {
213+
if (response?.repository?.issues?.nodes?.[0].title === "Issue 2") {
214+
done();
215+
}
216+
},
183217
);
184218
```
185219

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Octokit } from "@octokit/core";
22
import { createIterator } from "./iterator";
33
import { createPaginate } from "./paginate";
44
export type { PageInfoForward, PageInfoBackward } from "./page-info";
5-
export type { Options } from "./options";
65

76
export function paginateGraphql(octokit: Octokit) {
87
octokit.graphql;

src/iterator.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,31 @@ import { extractPageInfos } from "./extract-page-info";
22
import { Octokit } from "@octokit/core";
33
import { getCursorFrom, hasAnotherPage } from "./page-info";
44
import { MissingCursorChange } from "./errors";
5-
import type { Options } from "./options";
65

76
const createIterator = (octokit: Octokit) => {
87
return <ResponseType = any>(
98
query: string,
109
initialParameters: Record<string, any> = {},
11-
options: Options = {},
10+
stopFunction?: (response: ResponseType, done: () => void) => void,
1211
) => {
1312
let nextPageExists = true;
13+
let stopEarly = false;
1414
let parameters = { ...initialParameters };
15-
const { maxPages } = options;
16-
let page = 0;
1715

1816
return {
1917
[Symbol.asyncIterator]: () => ({
2018
async next() {
21-
if (!nextPageExists) return { done: true, value: {} as ResponseType };
22-
if (maxPages && page >= maxPages) {
19+
if (!nextPageExists || stopEarly) {
2320
return { done: true, value: {} as ResponseType };
2421
}
2522

26-
page += 1;
27-
2823
const response = await octokit.graphql<ResponseType>(
2924
query,
3025
parameters,
3126
);
3227

28+
stopFunction?.(response, () => (stopEarly = true));
29+
3330
const pageInfoContext = extractPageInfos(response);
3431
const nextCursorValue = getCursorFrom(pageInfoContext.pageInfo);
3532
nextPageExists = hasAnotherPage(pageInfoContext.pageInfo);

src/options.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/paginate.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { Octokit } from "@octokit/core";
22
import { mergeResponses } from "./merge-responses";
33
import { createIterator } from "./iterator";
4-
import type { Options } from "./options";
54

65
const createPaginate = (octokit: Octokit) => {
76
const iterator = createIterator(octokit);
87
return async <ResponseType extends object = any>(
98
query: string,
109
initialParameters: Record<string, any> = {},
11-
options: Options = {},
10+
stopFunction?: (response: ResponseType, done: () => void) => void,
1211
): Promise<ResponseType> => {
1312
let mergedResponse: ResponseType = {} as ResponseType;
1413
for await (const response of iterator<ResponseType>(
1514
query,
1615
initialParameters,
17-
options,
16+
stopFunction,
1817
)) {
1918
mergedResponse = mergeResponses<ResponseType>(mergedResponse, response);
2019
}

test/paginate.test.ts

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -282,30 +282,79 @@ describe("pagination", () => {
282282
]);
283283
});
284284

285-
it(".paginate.iterator() allows users to pass `maxPages` parameter and stops at the right place.", async (): Promise<void> => {
285+
it(".paginate.iterator() allows users to pass `stopFunction` and stops at the right place.", async (): Promise<void> => {
286286
const responses = createResponsePages({ amount: 3 });
287287

288288
const { octokit, getCallCount, getPassedVariablesForCall } = MockOctokit({
289289
responses,
290290
});
291291

292+
const maxPages = 2;
293+
let pages = 0;
294+
292295
const actualResponse = await octokit.graphql.paginate<TestResponseType>(
293-
`
294-
query paginate ($cursor: String) {
295-
repository(owner: "octokit", name: "rest.js") {
296-
issues(first: 10, after: $cursor) {
297-
nodes {
298-
title
299-
}
300-
pageInfo {
301-
hasNextPage
302-
endCursor
303-
}
296+
`query paginate ($cursor: String) {
297+
repository(owner: "octokit", name: "rest.js") {
298+
issues(first: 10, after: $cursor) {
299+
nodes {
300+
title
301+
}
302+
pageInfo {
303+
hasNextPage
304+
endCursor
304305
}
305306
}
306-
}`,
307+
}
308+
}`,
307309
{},
308-
{ maxPages: 2 },
310+
(_, done) => {
311+
pages += 1;
312+
if (pages >= maxPages) {
313+
done();
314+
}
315+
},
316+
);
317+
318+
expect(actualResponse).toEqual({
319+
repository: {
320+
issues: {
321+
nodes: [{ title: "Issue 1" }, { title: "Issue 2" }],
322+
pageInfo: { hasNextPage: true, endCursor: "endCursor2" },
323+
},
324+
},
325+
});
326+
expect(getCallCount()).toBe(2);
327+
expect(getPassedVariablesForCall(1)).toBeUndefined();
328+
expect(getPassedVariablesForCall(2)).toEqual({ cursor: "endCursor1" });
329+
});
330+
331+
it(".paginate.iterator() allows users to pass `stopFunction` and stops at the right place.", async (): Promise<void> => {
332+
const responses = createResponsePages({ amount: 3 });
333+
334+
const { octokit, getCallCount, getPassedVariablesForCall } = MockOctokit({
335+
responses,
336+
});
337+
338+
const actualResponse = await octokit.graphql.paginate<TestResponseType>(
339+
`query paginate ($cursor: String) {
340+
repository(owner: "octokit", name: "rest.js") {
341+
issues(first: 10, after: $cursor) {
342+
nodes {
343+
title
344+
}
345+
pageInfo {
346+
hasNextPage
347+
endCursor
348+
}
349+
}
350+
}
351+
}`,
352+
{},
353+
(response, done) => {
354+
if (response?.repository?.issues?.nodes?.[0].title === "Issue 2") {
355+
done();
356+
}
357+
},
309358
);
310359

311360
expect(actualResponse).toEqual({

0 commit comments

Comments
 (0)