Pagination
@spotify-effect/core provides two pagination strategies: offset-based (for most endpoints) and cursor-based (for recently played tracks and follow lists).
Offset Pagination
Most Spotify endpoints use offset-based pagination via the Paging object.
Using paginateAll
Fetch all items from a paginated endpoint:
import { Effect } from "effect";import { Library, paginateAll } from "@spotify-effect/core";
const program = Effect.gen(function* () { const library = yield* Library;
const allTracks = yield* paginateAll( (offset, limit) => library.getSavedTracks({ offset, limit }), 50, // page size );
console.log(`Found ${allTracks.length} saved tracks`); return allTracks;});Using paginateStream
For streaming large result sets without loading everything into memory:
import { Effect, Stream } from "effect";import { Library, paginateStream } from "@spotify-effect/core";
const program = Effect.gen(function* () { const library = yield* Library;
const trackStream = paginateStream( (offset, limit) => library.getSavedTracks({ offset, limit }), 50, );
// Process tracks one by one yield* Stream.runForEach(trackStream, (track) => Effect.sync(() => console.log(track.name)));});Cursor Pagination
Cursor-based pagination is used for:
- Recently played tracks
- Followed artists/users
Using cursorPaginateAll
import { Effect } from "effect";import { Player, cursorPaginateAll } from "@spotify-effect/core";
const program = Effect.gen(function* () { const player = yield* Player;
const history = yield* cursorPaginateAll( (options) => player.getRecentlyPlayedTracks(options), 50, );
console.log(`Found ${history.length} recently played tracks`); return history;});Using cursorPaginateStream
import { Effect, Stream } from "effect";import { Player, cursorPaginateStream } from "@spotify-effect/core";
const program = Effect.gen(function* () { const player = yield* Player;
const historyStream = cursorPaginateStream( (options) => player.getRecentlyPlayedTracks(options), 50, );
yield* Stream.runForEach(historyStream, (item) => Effect.sync(() => console.log(item.track.name)), );});Practical Examples
Fetch all playlists with pagination
import { Effect } from "effect";import { Playlists, paginateAll } from "@spotify-effect/core";
const getAllPlaylists = Effect.gen(function* () { const playlists = yield* Playlists;
return yield* paginateAll((offset, limit) => playlists.getMyPlaylists({ offset, limit }), 50);});Search with type filters
import { Effect } from "effect";import { Search } from "@spotify-effect/core";
const program = Effect.gen(function* () { const search = yield* Search;
// Search across multiple types const results = yield* search.search("Daft Punk", ["album", "artist", "track"], { limit: 10, });
console.log("Albums:", results.albums?.items.length); console.log("Artists:", results.artists?.items.length); console.log("Tracks:", results.tracks?.items.length);});Get all albums from an artist
import { Effect } from "effect";import { Artists, Albums, paginateAll } from "@spotify-effect/core";
const getAllArtistAlbums = (artistId: string) => Effect.gen(function* () { const artists = yield* Artists; const albums = yield* Albums;
const artistAlbums = yield* paginateAll( (offset, limit) => artists.getArtistAlbums(artistId, { offset, limit, include_groups: ["album", "single", "compilation"], }), 50, );
// Fetch full album details for each (batched) const albumIds = artistAlbums.map((a) => a.id); const fullAlbums = yield* albums.getAlbums(albumIds);
return fullAlbums; });Process large datasets with streams
import { Effect, Stream } from "effect";import { Library, Library as LibraryService, paginateStream } from "@spotify-effect/core";
const processSavedTracks = () => Effect.gen(function* () { const library = yield* Library;
const trackStream = paginateStream( (offset, limit) => library.getSavedTracks({ offset, limit }), 50, );
// Count tracks by artist const artistCounts = yield* Stream.runFold( trackStream, new Map<string, number>(), (counts, track) => { const artist = track.artists[0]?.name ?? "Unknown"; counts.set(artist, (counts.get(artist) ?? 0) + 1); return counts; }, );
// Find top artists const topArtists = [...artistCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
console.log("Top 10 artists by saved tracks:"); for (const [artist, count] of topArtists) { console.log(`${artist}: ${count} tracks`); } });Combining with Error Handling
import { Effect, Exit } from "effect";import { Library, paginateAll } from "@spotify-effect/core";import { SpotifyRateLimitError, isRetryableError } from "@spotify-effect/core";
const getAllSavedTracks = (maxRetries = 3) => Effect.gen(function* () { const library = yield* Library;
return yield* Effect.retry( paginateAll((offset, limit) => library.getSavedTracks({ offset, limit }), 50), { times: maxRetries, schedule: Schedule.exponential("1 second"), while: isRetryableError, }, ); });Performance Tips
- Use appropriate page sizes — 50 is a good default balance between request count and memory usage
- Use streams for large datasets — avoid loading millions of items into memory
- Batch API calls when possible — use
getAlbumsinstead of callinggetAlbumin a loop - Cache results — consider caching paginated results that don’t change frequently
Next steps
- Error Handling — handle pagination errors
- Library — save and manage user library
- Playlists — work with playlists