///|
/// One decoded page and its optional cursor for the following page.
pub(all) struct Page[T] {
items : Array[T]
next : String?
}
///|
/// A stateful async cursor paginator.
pub struct Paginator[T] {
priv fetch : async (String?) -> Page[T] raise SdkError
priv mut next : String?
priv mut started : Bool
priv mut done : Bool
}
///|
/// Creates a paginator whose first fetch receives `None`.
pub fn[T] Paginator::new(
fetch : async (String?) -> Page[T] raise SdkError,
) -> Paginator[T] {
{ fetch, next: None, started: false, done: false, }
}
///|
/// Fetches the next page. A failed fetch leaves the cursor unchanged.
pub async fn[T] Paginator::next_page(
self : Paginator[T],
) -> Array[T]? raise SdkError {
if self.done {
return None
}
let cursor = if self.started { self.next } else { None }
let page = (self.fetch)(cursor)
self.started = true
self.next = page.next
self.done = page.next is None
Some(page.items)
}
///|
/// Visits every remaining item in page order.
pub async fn[T] Paginator::each(
self : Paginator[T],
f : async (T) -> Unit raise SdkError,
) -> Unit raise SdkError {
for ;; {
guard self.next_page() is Some(items) else { return }
for item in items {
f(item)
}
}
}
///|
/// Collects at most `max` remaining items, discarding the unused page suffix.
pub async fn[T] Paginator::collect(
self : Paginator[T],
max~ : Int,
) -> Array[T] raise SdkError {
let result = []
if max <= 0 {
return result
}
for ; result.length() < max; {
guard self.next_page() is Some(items) else { break }
for item in items {
if result.length() >= max {
break
}
result.push(item)
}
}
result
}