Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a third options argument and allow users to specify a maxPages setting #163

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,69 @@ await octokit.graphql.paginate(
);
```

### Early stop when iterating

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.

For example, you can stop the iteration after a certain number of pages:

```js
const maxPages = 2;
let pages = 0;

await octokit.graphql.paginate(
`query paginate ($cursor: String) {
repository(owner: "octokit", name: "rest.js") {
issues(first: 10, after: $cursor) {
nodes {
title
}
pageInfo {
hasNextPage
endCursor
}
}
}
}`,
{},
(_, done) => {
pages += 1;
if (pages >= maxPages) {
done();
}
},
);
```

Or, to stop after you find a certain item:

```js
await octokit.graphql.paginate(
`query paginate ($cursor: String) {
repository(owner: "octokit", name: "rest.js") {
issues(first: 10, after: $cursor) {
nodes {
title
}
pageInfo {
hasNextPage
endCursor
}
}
}
}`,
{},
(response, done) => {
if (response?.repository?.issues?.nodes?.[0].title === "Issue 2") {
done();
}
},
);
```

### Pagination Direction

You can control the pagination direction by the properties deinfed in the `pageInfo` resource.
You can control the pagination direction by the properties defined in the `pageInfo` resource.

For a forward pagination, use:

Expand Down
8 changes: 7 additions & 1 deletion src/iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ const createIterator = (octokit: Octokit) => {
return <ResponseType = any>(
query: string,
initialParameters: Record<string, any> = {},
stopFunction?: (response: ResponseType, done: () => void) => void,
) => {
let nextPageExists = true;
let stopEarly = false;
let parameters = { ...initialParameters };

return {
[Symbol.asyncIterator]: () => ({
async next() {
if (!nextPageExists) return { done: true, value: {} as ResponseType };
if (!nextPageExists || stopEarly) {
return { done: true, value: {} as ResponseType };
}

const response = await octokit.graphql<ResponseType>(
query,
parameters,
);

stopFunction?.(response, () => (stopEarly = true));

const pageInfoContext = extractPageInfos(response);
const nextCursorValue = getCursorFrom(pageInfoContext.pageInfo);
nextPageExists = hasAnotherPage(pageInfoContext.pageInfo);
Expand Down
2 changes: 2 additions & 0 deletions src/paginate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ const createPaginate = (octokit: Octokit) => {
return async <ResponseType extends object = any>(
query: string,
initialParameters: Record<string, any> = {},
stopFunction?: (response: ResponseType, done: () => void) => void,
): Promise<ResponseType> => {
let mergedResponse: ResponseType = {} as ResponseType;
for await (const response of iterator<ResponseType>(
query,
initialParameters,
stopFunction,
)) {
mergedResponse = mergeResponses<ResponseType>(mergedResponse, response);
}
Expand Down
88 changes: 88 additions & 0 deletions test/paginate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,94 @@ describe("pagination", () => {
]);
});

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

const { octokit, getCallCount, getPassedVariablesForCall } = MockOctokit({
responses,
});

const maxPages = 2;
let pages = 0;

const actualResponse = await octokit.graphql.paginate<TestResponseType>(
`query paginate ($cursor: String) {
repository(owner: "octokit", name: "rest.js") {
issues(first: 10, after: $cursor) {
nodes {
title
}
pageInfo {
hasNextPage
endCursor
}
}
}
}`,
{},
(_, done) => {
pages += 1;
if (pages >= maxPages) {
done();
}
},
);

expect(actualResponse).toEqual({
repository: {
issues: {
nodes: [{ title: "Issue 1" }, { title: "Issue 2" }],
pageInfo: { hasNextPage: true, endCursor: "endCursor2" },
},
},
});
expect(getCallCount()).toBe(2);
expect(getPassedVariablesForCall(1)).toBeUndefined();
expect(getPassedVariablesForCall(2)).toEqual({ cursor: "endCursor1" });
});

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

const { octokit, getCallCount, getPassedVariablesForCall } = MockOctokit({
responses,
});

const actualResponse = await octokit.graphql.paginate<TestResponseType>(
`query paginate ($cursor: String) {
repository(owner: "octokit", name: "rest.js") {
issues(first: 10, after: $cursor) {
nodes {
title
}
pageInfo {
hasNextPage
endCursor
}
}
}
}`,
{},
(response, done) => {
if (response?.repository?.issues?.nodes?.[0].title === "Issue 2") {
done();
}
},
);

expect(actualResponse).toEqual({
repository: {
issues: {
nodes: [{ title: "Issue 1" }, { title: "Issue 2" }],
pageInfo: { hasNextPage: true, endCursor: "endCursor2" },
},
},
});
expect(getCallCount()).toBe(2);
expect(getPassedVariablesForCall(1)).toBeUndefined();
expect(getPassedVariablesForCall(2)).toEqual({ cursor: "endCursor1" });
});

it("paginate() throws error with path and variable name if cursors do not change between calls.", async (): Promise<void> => {
const [responsePage1, responsePage2] = createResponsePages({ amount: 2 });
responsePage2.repository.issues.pageInfo = {
Expand Down