Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,80 @@
## 1.13.0

- New `EntityPagination.onEvent`: an optional hook notified of what is being
fetched, for progress reporting and logging.

```dart
var p = userRepository.paginateByQuery(' state == ? ',
parameters: ['NY'], limit: 20, onEvent: (event) {
switch (event) {
case EntityPaginationPageLoading(:var page):
print('fetching page $page...');
case EntityPaginationPageLoaded(:var page, :var entriesLength):
print('page $page: $entriesLength entries');
case EntityPaginationPageError(:var page, :var error):
print('page $page failed: $error');
case EntityPaginationPageSkipped(:var page, :var reason):
print('page $page not fetched: ${reason.name}');
case EntityPaginationEnd(:var totalLength):
print('done: $totalLength entries');
case EntityPaginationReset(:var discardedPages):
print('discarded ${discardedPages.length} pages');
}
});
```

- Delivered **synchronously**, at the point where it happens and in order, so
it is also correct for a synchronous `EntityPageLoader` (a `Stream` would
only deliver in a later microtask, after a sync read already finished).
To consume it as a stream, forward 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.
- An exception thrown by the listener is reported to the current `Zone` and
does not break the fetch.
- Nothing is allocated (not even the fetch timer) while `onEvent` is `null`.

- New `EntityPaginationEvent<O>`, a `sealed` hierarchy so a `switch` over it is
exhaustive, with `EntityPaginationListener<O>` as the callback type:
- `EntityPaginationPageLoading`: a fetch is about to start. Emitted once per
*actual* fetch.
- `EntityPaginationPageLoaded`: a fetch finished, with the `entries`, the
`entriesLength`, the `elapsedTime` of the `pageLoader` and `isFinalPage`.
- `EntityPaginationPageError`: a fetch failed, with the `error`, the
`stackTrace` and the `elapsedTime`. The error is rethrown to the caller
right after the event.
- `EntityPaginationPageSkipped`: a page was served *without* a fetch, with an
`EntityPaginationSkipReason`: `alreadyLoaded`, `inFlight` (a concurrent
request shares the fetch) or `knownEmpty` (past the resolved end). Not an
error — it is what makes a repeated, concurrent or past-the-end read free.
- `EntityPaginationEnd`: the end was resolved, with the `finalPage` and the
`totalLength`. Emitted once, immediately after the
`EntityPaginationPageLoaded` that resolved it — which is not necessarily
the final page itself, since an empty page can pin the end at its
predecessor.
- `EntityPaginationReset`: `reset()` or `refresh()` discarded the loaded
pages, with the `discardedPages`, the `discardedEntitiesLength` and
`isRefresh` — `true` while it is the reset of a `refresh()`, which
re-fetches those pages right after, so a consumer can tell an in-progress
refresh from a pagination that was simply emptied. Emitted *after* the
state is cleared, so the pagination already reads as empty and the
discarded state is on the event.

Every event but `EntityPaginationReset` is about a page, and is an
`EntityPaginationPageEvent` (also `sealed`) carrying the `page`.

Note that concurrent page loads interleave: `getRange` and `refresh` start
every page at once, so all the fetches are announced before any completes.

- `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` (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).

## 1.12.0

- New `EntityPagination<O>`: a lazily loaded, paginated view over a select,
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,37 @@ until it reaches the end, so `totalLength` is `null` until the final page is
identified (`isFinalPageResolved`). Until then you still know
`maxLoadedIndex`, `maxKnownPage` and which pages are loaded.

The optional `onEvent` hook reports what is being fetched, for progress
reporting and logging:

```dart
var page = accountRepository.paginateByQuery(
' address.state == ? ',
parameters: ['NY'],
limit: 20,
onEvent: (event) {
switch (event) {
case EntityPaginationPageLoading(:var page):
print('fetching page $page...');
case EntityPaginationPageLoaded(:var page, :var entriesLength):
print('page $page: $entriesLength entries');
case EntityPaginationPageError(:var page, :var error):
print('page $page failed: $error');
case EntityPaginationPageSkipped(:var page, :var reason):
print('page $page not fetched: ${reason.name}');
case EntityPaginationEnd(:var totalLength):
print('done: $totalLength entries');
case EntityPaginationReset(:var discardedPages):
print('discarded ${discardedPages.length} pages');
}
},
);
```

Events are delivered synchronously and in order, so the sequence is meaningful
even for a synchronous page loader. To consume them as a `Stream` instead,
forward them: `onEvent: myEventStream.add`.

The config file used above:

File: `api-local.yaml`
Expand Down
2 changes: 1 addition & 1 deletion lib/src/bones_api_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ typedef APILogger =
/// Bones API Library class.
class BonesAPI {
// ignore: constant_identifier_names
static const String VERSION = '1.12.0';
static const String VERSION = '1.13.0';

static bool _boot = false;

Expand Down
14 changes: 14 additions & 0 deletions lib/src/bones_api_entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3642,6 +3642,8 @@ abstract class EntitySource<O extends Object> extends EntityAccessor<O> {
/// over a stable order, so unlike the `select*` methods this is on by
/// default instead of being implied by an offset.
/// - [orderDirection]: the [OrderDirection] of the ordering.
/// - [onEvent]: an optional hook notified of what is being fetched.
/// See [EntityPaginationEvent].
/// {@endtemplate}
EntityPagination<O> paginateByQuery(
String query, {
Expand All @@ -3652,9 +3654,11 @@ abstract class EntitySource<O extends Object> extends EntityAccessor<O> {
bool? orderByID,
OrderDirection? orderDirection,
Transaction? transaction,
EntityPaginationListener<O>? onEvent,
}) => EntityPagination<O>(
limit: limit,
query: query,
onEvent: onEvent,
pageLoader:
(page, limit) => selectByQuery(
query,
Expand All @@ -3679,9 +3683,11 @@ abstract class EntitySource<O extends Object> extends EntityAccessor<O> {
bool? orderByID,
OrderDirection? orderDirection,
Transaction? transaction,
EntityPaginationListener<O>? onEvent,
}) => EntityPagination<O>(
limit: limit,
query: '$matcher',
onEvent: onEvent,
pageLoader:
(page, limit) => select(
matcher,
Expand All @@ -3702,9 +3708,11 @@ abstract class EntitySource<O extends Object> extends EntityAccessor<O> {
bool? orderByID,
OrderDirection? orderDirection,
Transaction? transaction,
EntityPaginationListener<O>? onEvent,
}) => EntityPagination<O>(
limit: limit,
query: 'ALL',
onEvent: onEvent,
pageLoader:
(page, limit) => selectAll(
transaction: transaction,
Expand Down Expand Up @@ -5822,8 +5830,10 @@ abstract class EntityRepository<O extends Object> extends EntityAccessor<O>
OrderDirection? orderDirection,
Transaction? transaction,
EntityResolutionRules? resolutionRules,
EntityPaginationListener<O>? onEvent,
}) => EntityPagination<O>(
limit: limit,
onEvent: onEvent,
query: query,
pageLoader:
(page, limit) => selectByQuery(
Expand Down Expand Up @@ -5854,8 +5864,10 @@ abstract class EntityRepository<O extends Object> extends EntityAccessor<O>
OrderDirection? orderDirection,
Transaction? transaction,
EntityResolutionRules? resolutionRules,
EntityPaginationListener<O>? onEvent,
}) => EntityPagination<O>(
limit: limit,
onEvent: onEvent,
query: '$matcher',
pageLoader:
(page, limit) => select(
Expand All @@ -5882,8 +5894,10 @@ abstract class EntityRepository<O extends Object> extends EntityAccessor<O>
OrderDirection? orderDirection,
Transaction? transaction,
EntityResolutionRules? resolutionRules,
EntityPaginationListener<O>? onEvent,
}) => EntityPagination<O>(
limit: limit,
onEvent: onEvent,
query: 'ALL',
pageLoader:
(page, limit) => selectAll(
Expand Down
Loading