paginate<T> function

Stream<T> paginate<T>(
  1. Future<Page<T>> fetch(
    1. String? cursor
    ), {
  2. int maxPages = 10000,
})

Iterate every item across all pages of a cursor / next_cursor endpoint as a Stream. A loop guard caps pages so a misbehaving cursor can't spin.

await for (final c in paginate((cursor) async {
  final r = await contactsApi.v1ContactsGet(cursor: cursor);
  return Page(r.data!.toList(), r.nextCursor);
})) { syncLocally(c); }

Implementation

Stream<T> paginate<T>(
  Future<Page<T>> Function(String? cursor) fetch, {
  int maxPages = 10000,
}) async* {
  String? cursor;
  var pages = 0;
  final seen = <String>{};
  while (true) {
    if (pages >= maxPages) {
      throw StateError('paginate exceeded maxPages ($maxPages) — possible cursor loop');
    }
    final page = await fetch(cursor);
    pages++;
    for (final item in page.items) {
      yield item;
    }
    final next = page.nextCursor;
    if (next == null || next.isEmpty || next == cursor || !seen.add(next)) return;
    cursor = next;
  }
}