Skip to content

feat: EntityPagination.onEvent — an optional hook of what is being fetched - #147

Merged
gmpassos merged 1 commit into
masterfrom
entity-pagination-events
Aug 2, 2026
Merged

feat: EntityPagination.onEvent — an optional hook of what is being fetched#147
gmpassos merged 1 commit into
masterfrom
entity-pagination-events

Conversation

@gmpassos

Copy link
Copy Markdown
Contributor

Adds an optional hook to EntityPagination, notified of what is being fetched — for progress reporting and logging over a paginated read.

var p = accountRepository.paginateByQuery(' address.state == ? ',
parameters: ['NY'], limit:20, onEvent: (event) {
switch (event) {
caseEntityPaginationPageLoading(:var page):print('fetching page $page...');
caseEntityPaginationPageLoaded(:var page, :var entriesLength):print('page $page: $entriesLength entries');
caseEntityPaginationPageError(:var page, :var error):print('page $page failed: $error');
caseEntityPaginationPageSkipped(:var page, :var reason):print('page $page not fetched: ${reason.name}');
caseEntityPaginationEnd(:var totalLength):print('done: $totalLength entries');
caseEntityPaginationReset(:var discardedPages):print('discarded ${discardedPages.length} pages');
}
});

The events

EntityPaginationEvent<O> is sealed, so a switch over it is exhaustive.
Everything but the reset is an EntityPaginationPageEvent, carrying the page.

EventPayload
EntityPaginationPageLoading— emitted once per actual fetch
EntityPaginationPageLoadedentries, entriesLength, elapsedTime, isFinalPage
EntityPaginationPageErrorerror, stackTrace, elapsedTime
EntityPaginationPageSkippedreason: alreadyLoaded / inFlight / knownEmpty
EntityPaginationEndfinalPage, totalLength
EntityPaginationResetdiscardedPages, discardedEntitiesLength, isRefresh

Design notes

  • Synchronous delivery.EntityPagination supports a synchronous EntityPageLoader (loadPage has a sync fast path), so a Stream would deliver every event in a later microtask — after a sync read already finished. A callback is the right primitive here; a stream is one line on top of it: onEvent: myEventStream.add.
  • onEvent is not final, so it can also be attached to an already built EntityPagination. Only events emitted afterwards are seen.
  • A listener that throws is reported to the current Zone and does not break the fetch.
  • Nothing is allocated while onEvent is null — not even the fetch timer.
  • _setPage now reports whether it resolved the final page, so EntityPaginationEnd is emitted exactly once and after the EntityPaginationPageLoaded that caused it (rather than from inside _setPage, i.e. before it).
  • EntityPaginationReset is emitted after the state is cleared, so the pagination already reads as empty and the discarded state travels on the event. isRefresh distinguishes refresh() (which re-fetches those pages right after) from a pagination that was simply emptied.
  • Concurrent page loads interleave: getRange and refresh start every page at once, so all the fetches are announced before any completes. Asserted in the tests.

paginateByQuery, paginate and paginateAll gained the optional onEvent parameter on EntitySource, EntityRepository and APIRepository, so the hook is reachable without building an EntityPagination by hand.

Tests

15 new cases in bones_api_entity_pagination_test.dart (37 → 52): the event sequence of a full read, of an exact multiple of the page size, of an empty result and of a failure; the 3 skip reasons; the synchronous delivery; a listener attached after construction; a throwing listener; and the reset/refresh events.

Full suite green: 686 passing + ensure_build, no failures. Generated code is unaffected (build_runner reports all 5 outputs same).

Version bumped 1.12.0 → 1.13.0 (pubspec.yaml, BonesAPI.VERSION, CHANGELOG), with the hook documented in the README's pagination section.

🤖 Generated with Claude Code

…tched
- New `EntityPaginationEvent<O>`, a `sealed` hierarchy so a `switch` over it is
exhaustive, with `EntityPaginationListener<O>` as the callback type:
`EntityPaginationPageLoading`, `EntityPaginationPageLoaded` (entries,
elapsedTime, isFinalPage), `EntityPaginationPageError` (error, stackTrace),
`EntityPaginationPageSkipped` (`alreadyLoaded` / `inFlight` / `knownEmpty`),
`EntityPaginationEnd` (finalPage, totalLength) and `EntityPaginationReset`
(discardedPages, discardedEntitiesLength, isRefresh).
Everything but the reset is an `EntityPaginationPageEvent`, carrying the page.
- Delivered synchronously and in order, so the sequence is also correct for a
synchronous `EntityPageLoader` (a `Stream` would only deliver in a later
microtask, after a sync read already finished). Forward it to get a stream:
`onEvent: myEventStream.add`.
- `onEvent` is not `final`, so it can be attached after construction. A
listener that throws is reported to the current `Zone` and does not break the
fetch. Nothing is allocated (not even the fetch timer) while it is `null`.
- `paginateByQuery`, `paginate` and `paginateAll` gained the optional `onEvent`
parameter, on `EntitySource`, `EntityRepository` and `APIRepository`.
- `_setPage` now reports whether it resolved the final page, so
`EntityPaginationEnd` is emitted exactly once and *after* the
`EntityPaginationPageLoaded` that caused it.
- Version 1.12.0 -> 1.13.0. 15 new tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecovBot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.77%. Comparing base (8a81d56) to head (4c108a0).

Files with missing linesPatch %Lines
lib/src/bones_api_entity_pagination.dart77.77%16 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## master #147 +/- ##
==========================================
+ Coverage 67.68% 67.77% +0.08% 
==========================================
Files 64 64 Lines 21544 21607 +63 ==========================================
+ Hits 14583 14644 +61 - Misses 6961 6963 +2 
FlagCoverage Δ
unittests67.77% <77.77%> (+0.08%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gmpassos
gmpassos merged commit 4caf237 into masterAug 2, 2026
5 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@gmpassos