Skip to content

Commit

Permalink
fix: CursorPageIterator received items in revese order, add more te…
Browse files Browse the repository at this point in the history
…sts for it
  • Loading branch information
MellKam committed Feb 20, 2024
1 parent 025f58d commit 54b7f0d
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 8 deletions.
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,18 +205,21 @@ console.log(me);
## Pagination

To simplify the process of paginating through the results, we provide a
`PageIterator` class.
`PageIterator` and `CursorPageIterator` classes.

```ts
import { getPlaylistTracks, SpotifyClient } from "@soundify/web-api";
import { PageIterator } from "@soundify/web-api/pagination";

const client = new SpotifyClient("YOUR_ACCESS_TOKEN", {
waitForRateLimit: true,
});
const client = new SpotifyClient("YOUR_ACCESS_TOKEN");

const playlistIter = new PageIterator(
(opts) => getPlaylistTracks(client, "37i9dQZEVXbMDoHDwVN2tF", opts),
(offset) => getPlaylistTracks(client, "37i9dQZEVXbMDoHDwVN2tF", {
// you can find the max limit for specific endpoint
// in spotify docs or in the jsdoc comments of this property
limit: 50,
offset,
}),
);

// iterate over all tracks in the playlist
Expand All @@ -227,6 +230,41 @@ for await (const track of playlistIter) {
// or collect all tracks into an array
const allTracks = await playlistIter.collect();
console.log(allTracks.length);

// Want to get the last 100 items? No problem
const lastHundredTracks = new PageIterator(
(offset) => getPlaylistTracks(
client,
"37i9dQZEVXbMDoHDwVN2tF",
{ limit: 50, offset }
),
{ initialOffset: -100 }, // this will work just as `Array.slice(-100)`
).collect();
```

```ts
import { getFollowedArtists, SpotifyClient } from "@soundify/web-api";
import { CursorPageIterator } from "@soundify/web-api/pagination";

const client = new SpotifyClient("YOUR_ACCESS_TOKEN");

// loop over all followed artists
for await (const artist of new CursorPageIterator(
opts => getFollowedArtists(client, { limit: 50, after: opts.after })
)) {
console.log(artist.name);
}

// or collect all followed artists into an array
const artists = await new CursorPageIterator(
opts => getFollowedArtists(client, { limit: 50, after: opts.after })
).collect();

// get all followed artists starting from Radiohead
const artists = await new CursorPageIterator(
opts => getFollowedArtists(client, { limit: 50, after: opts.after }),
{ initialAfter: "4Z8W4fKeB5YxbusRsdQVPb" } // let's start from Radiohead
).collect();
```

## Other customizations
Expand Down
42 changes: 42 additions & 0 deletions pagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,45 @@ Deno.test("CursorPageIterator: asyncIterator", async () => {
value: null,
});
});

Deno.test("CursorPageIterator: asyncIterator backwards", async () => {
const initialBefore = 30;
const cursorPageIterator = new CursorPageIterator((opts) => {
const limit = 20;

const beforeArtistIndex = mockArtists.findIndex((artist) =>
artist.id === opts.before
);
if (beforeArtistIndex === -1) {
throw new Error("Invalid cursor");
}

const lastIndex = beforeArtistIndex - limit - 1;
const hasPreviousPage = !(lastIndex < 0);

return Promise.resolve({
items: mockArtists.slice(
hasPreviousPage ? lastIndex : 0,
beforeArtistIndex,
),
next: hasPreviousPage ? "http://example.com" : null,
cursors: {
before: hasPreviousPage ? mockArtists.at(lastIndex)?.id : undefined,
},
});
}, { direction: "backward", initialBefore: initialBefore.toString() });

const iter = cursorPageIterator[Symbol.asyncIterator]();

for (let i = initialBefore - 1; i >= 0; i--) {
const result = await iter.next();
assertEquals(result, {
done: false,
value: mockArtists[i],
});
}
assertEquals(await iter.next(), {
done: true,
value: null,
});
});
12 changes: 9 additions & 3 deletions pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export type CursorPageIteratorOptions<TDirection extends Direction> =
* ```ts
* // get the first 100 followed artists
* const artists = await new CursorPageIterator(
* opts => getFollowedArtists(client, { limit: 50, after: opts.after})
* opts => getFollowedArtists(client, { limit: 50, after: opts.after })
* ).collect(100);
* ```
*/
Expand Down Expand Up @@ -164,8 +164,14 @@ export class CursorPageIterator<
},
);

for (let i = 0; i < page.items.length; i++) {
yield page.items[i];
if (direction === "forward") {
for (let i = 0; i < page.items.length; i++) {
yield page.items[i];
}
} else {
for (let i = page.items.length - 1; i >= 0; i--) {
yield page.items[i];
}
}

if (!page.next) {
Expand Down

0 comments on commit 54b7f0d

Please sign in to comment.