break Selectable methods into smaller interfaces based on return type.

This makes it so users can expose more refined/foolproof apis to their application:

```dart
extension TaskMethods on Task {
  /// won't make mistakes like using getSingle when using
  SingleOrNullSelectable<CalendarEvent> get event =>
      db.calendarEvents.forTask(this);

  /// autocomplete ignores getSingle, etc
  MultiSelectable<Metric> get metrics =>
      db.metrics.forTask(this);
}
```
This commit is contained in:
micimize 2021-04-02 21:18:55 -05:00
parent 15c6140925
commit 2db108529f
No known key found for this signature in database
GPG Key ID: 88F30E5F5F5D64A1
1 changed files with 69 additions and 35 deletions

View File

@ -68,19 +68,22 @@ abstract class Query<T extends Table, D extends DataClass> extends Component {
}
}
/// Abstract class for queries which can return one-time values or a stream
/// of values.
abstract class Selectable<T> {
/// [Selectable] methods for returning multiple results.
abstract class MultiSelectable<T> {
/// Executes this statement and returns the result.
Future<List<T>> get();
/// Creates an auto-updating stream of the result that emits new items
/// whenever any table used in this statement changes.
Stream<List<T>> watch();
}
/// Executes this statement, like [get], but only returns one value. If the
/// query returns no or too many rows, the returned future will complete with
/// an error.
/// [Selectable] methods for returning or streaming single,
/// non-nullable results.
abstract class SingleSelectable<T> {
/// Executes this statement, like [Selectable.get], but only returns one
/// value. the query returns no or too many rows, the returned future will
/// complete with an error.
///
/// {@template moor_single_query_expl}
/// Be aware that this operation won't put a limit clause on this statement,
@ -99,20 +102,69 @@ abstract class Selectable<T> {
/// clause will only allow one row.
/// {@endtemplate}
///
/// See also: [getSingleOrNull], which returns `null` instead of throwing if
/// the query completes with no rows.
/// See also: [Selectable.getSingleOrNull], which returns `null` instead of
/// throwing if the query completes with no rows.
Future<T> getSingle();
/// Creates an auto-updating stream of this statement, similar to
/// [Selectable.watch]. However, it is assumed that the query will only emit
/// one result, so instead of returning a `Stream<List<T>>`, this returns a
/// `Stream<T>`. If, at any point, the query emits no or more than one rows,
/// an error will be added to the stream instead.
///
/// {@macro moor_single_query_expl}
Stream<T> watchSingle();
}
/// [Selectable] methods for returning or streaming single,
/// nullable results.
abstract class SingleOrNullSelectable<T> {
/// Executes this statement, like [Selectable.get], but only returns one
/// value. If the result too many values, this method will throw. If no
/// row is returned, `null` will be returned instead.
///
/// {@macro moor_single_query_expl}
///
/// See also: [Selectable.getSingle], which can be used if the query will
/// always evaluate to exactly one row.
Future<T?> getSingleOrNull();
/// Creates an auto-updating stream of this statement, similar to
/// [Selectable.watch]. However, it is assumed that the query will only
/// emit one result, so instead of returning a `Stream<List<T>>`, this
/// returns a `Stream<T?>`. If the query emits more than one row at
/// some point, an error will be emitted to the stream instead.
/// If the query emits zero rows at some point, `null` will be added
/// to the stream instead.
///
/// {@macro moor_single_query_expl}
Stream<T?> watchSingleOrNull();
}
/// Abstract class for queries which can return one-time values or a stream
/// of values.
abstract class Selectable<T>
implements
MultiSelectable<T>,
SingleSelectable<T>,
SingleOrNullSelectable<T> {
@override
Future<List<T>> get();
@override
Stream<List<T>> watch();
@override
Future<T> getSingle() async {
return (await get()).single;
}
/// Executes this statement, like [get], but only returns one value. If the
/// result too many values, this method will throw. If no row is returned,
/// `null` will be returned instead.
///
/// {@macro moor_single_query_expl}
///
/// See also: [getSingle], which can be used if the query will never evaluate
/// to exactly one row.
@override
Stream<T> watchSingle() {
return watch().transform(singleElements());
}
@override
Future<T?> getSingleOrNull() async {
final list = await get();
final iterator = list.iterator;
@ -128,25 +180,7 @@ abstract class Selectable<T> {
return element;
}
/// Creates an auto-updating stream of this statement, similar to [watch].
/// However, it is assumed that the query will only emit one result, so
/// instead of returning a `Stream<List<T>>`, this returns a `Stream<T>`. If,
/// at any point, the query emits no or more than one rows, an error will be
/// added to the stream instead.
///
/// {@macro moor_single_query_expl}
Stream<T> watchSingle() {
return watch().transform(singleElements());
}
/// Creates an auto-updating stream of this statement, similar to [watch].
/// However, it is assumed that the query will only emit one result, so
/// instead of returning a `Stream<List<T>>`, this returns a `Stream<T?>`. If
/// the query emits more than one row at some point, an error will be emitted
/// to the stream instead. If the query emits zero rows at some point, `null`
/// will be added to the stream instead.
///
/// {@macro moor_single_query_expl}
@override
Stream<T?> watchSingleOrNull() {
return watch().transform(singleElementsOrNull());
}