Skip to content

Commit

Permalink
eng(supabase): expose private supabase methods as protected; allow ty…
Browse files Browse the repository at this point in the history
…pe generics in SQLite domain (#469)
  • Loading branch information
tshedor authored Oct 28, 2024
1 parent 191bcf6 commit ba89e5a
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 24 deletions.
2 changes: 1 addition & 1 deletion docs/home.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
- Video: [Brick Architecture](https://www.youtube.com/watch?v=2noLcro9iIw). An explanation of Brick parlance with a [supplemental analogy](https://medium.com/flutter-community/brick-your-app-five-compelling-reasons-and-a-pizza-analogy-to-make-your-data-accessible-8d802e1e526e).
- Video: [Brick Basics](https://www.youtube.com/watch?v=jm5i7e_BQq0). An overview of essential Brick mechanics.
- Example: [Simple Associations using the OfflineFirstWithGraphql domain](https://github.com/GetDutchie/brick/blob/main/example_graphql)
- Example: [Simple Associations using the OfflineFirstWithRest domain](https://github.com/GetDutchie/brick/blob/main/example)
- Example: [Simple Associations using the OfflineFirstWithRest domain](https://github.com/GetDutchie/brick/blob/main/example_rest)
- Example: [Simple Associations using the OfflineFirstWithSupabase domain](https://github.com/GetDutchie/brick/blob/main/example_supabase)
- Tutorial: [Setting up a simple app with Brick](http://www.flutterbyexample.com/#/posts/2_adding_a_repository)

Expand Down
2 changes: 2 additions & 0 deletions packages/brick_offline_first/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

- Add `GetFirstMixin` for convenient retrieval of the first results of `OfflineFirstRepository#get`

## 3.2.1

- Add `OfflineFirstSerdes#toSupabase` abstract method
Expand Down
42 changes: 42 additions & 0 deletions packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:brick_offline_first/brick_offline_first.dart';

/// A convenience mixin for single-instance get operations.
mixin GetFirstMixin<CModel extends OfflineFirstModel> on OfflineFirstRepository<CModel> {
/// Retrieves the first instance of [TModel] with certainty that it exists.
/// If no instances exist, a [StateError] is thrown from within Dart's core
/// `Iterable#first` method. It is recommended to use [getFirstOrNull] instead.
///
/// Automatically applies `'limit': 1` to the query's `providerArgs`
Future<TModel> getFirst<TModel extends CModel>({
OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist,
Query? query,
bool seedOnly = false,
}) async {
final result = await super.get<TModel>(
policy: policy,
query: query?.copyWith(providerArgs: {'limit': 1}),
seedOnly: seedOnly,
);

return result.first;
}

/// A safer version of [getFirst] that attempts to get the first instance of [TModel]
/// according to the [query], but returns `null` if no instances exist.
///
/// Automatically applies `'limit': 1` to the query's `providerArgs`
Future<TModel?> getFirstOrNull<TModel extends CModel>({
OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist,
Query? query,
bool seedOnly = false,
}) async {
final result = await super.get<TModel>(
policy: policy,
query: query?.copyWith(providerArgs: {'limit': 1}),
seedOnly: seedOnly,
);

if (result.isEmpty) return null;
return result.first;
}
}
5 changes: 5 additions & 0 deletions packages/brick_sqlite/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Unreleased

## 3.1.1

- Expose a generic type for `MemoryCacheProvider` models
- Expose a generic type for `SqliteProvider` models

## 3.1.0

- Apply standardized lints
Expand Down
16 changes: 8 additions & 8 deletions packages/brick_sqlite/lib/memory_cache_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:meta/meta.dart';
///
/// MemoryCacheProvider does not have a type argument due to a build_runner
/// exception: https://github.com/dart-lang/sdk/issues/38309
class MemoryCacheProvider extends Provider<SqliteModel> {
class MemoryCacheProvider<CModel extends SqliteModel> extends Provider<CModel> {
@protected
final Logger logger = Logger('MemoryCacheProvider');

Expand All @@ -23,13 +23,13 @@ class MemoryCacheProvider extends Provider<SqliteModel> {
final modelDictionary = _MemoryCacheModelDictionary();

/// A complete hash table of the
Map<Type, Map<int, SqliteModel>> managedObjects = {};
Map<Type, Map<int, CModel>> managedObjects = {};

/// Is the [type] cached by this provider?
bool manages(Type type) => managedModelTypes.contains(type);

/// It is strongly recommended to use this provider with smaller, frequently-accessed
/// and shared [SqliteModel]s.
/// and shared [TModel]s.
MemoryCacheProvider([
this.managedModelTypes = const <Type>[],
]);
Expand All @@ -40,13 +40,13 @@ class MemoryCacheProvider extends Provider<SqliteModel> {
/// basic lookups such as a single field (primary key).
/// However, if the provider is extended to support complex [Where] statements in [get],
/// this method should also be extended.
bool canFind<TModel extends SqliteModel>([Query? query]) {
bool canFind<TModel extends CModel>([Query? query]) {
final byPrimaryKey = Where.firstByField(InsertTable.PRIMARY_KEY_FIELD, query?.where);
return manages(TModel) && byPrimaryKey?.value != null;
}

@override
bool delete<TModel extends SqliteModel>(instance, {query, repository}) {
bool delete<TModel extends CModel>(instance, {query, repository}) {
if (!manages(TModel)) return false;
logger.finest('#delete: $TModel, $instance, $query');

Expand All @@ -56,7 +56,7 @@ class MemoryCacheProvider extends Provider<SqliteModel> {
}

@override
List<TModel>? get<TModel extends SqliteModel>({query, repository}) {
List<TModel>? get<TModel extends CModel>({query, repository}) {
if (!manages(TModel)) return null;
managedObjects[TModel] ??= {};

Expand All @@ -79,7 +79,7 @@ class MemoryCacheProvider extends Provider<SqliteModel> {
/// For convenience, the return value is the argument [models],
/// **not** the complete set of managed [TModel]s.
/// If the managed models are desired instead, use [get].
List<TModel> hydrate<TModel extends SqliteModel>(List<TModel> models) {
List<TModel> hydrate<TModel extends CModel>(List<TModel> models) {
if (!manages(TModel)) return models;
managedObjects[TModel] ??= {};

Expand All @@ -98,7 +98,7 @@ class MemoryCacheProvider extends Provider<SqliteModel> {
}

@override
TModel? upsert<TModel extends SqliteModel>(instance, {query, repository}) {
TModel? upsert<TModel extends CModel>(instance, {query, repository}) {
if (!manages(TModel)) return null;
logger.finest('#upsert: $TModel, $instance, $query');
hydrate<TModel>([instance]);
Expand Down
16 changes: 8 additions & 8 deletions packages/brick_sqlite/lib/src/sqlite_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'package:sqflite_common/utils/utils.dart' as sqlite_utils;
import 'package:synchronized/synchronized.dart';

/// Retrieves from a Sqlite database
class SqliteProvider implements Provider<SqliteModel> {
class SqliteProvider<CModel extends SqliteModel> implements Provider<CModel> {
/// Access the [SQLite](https://github.com/tekartik/sqflite/tree/master/sqflite_common_ffi),
/// instance agnostically across platforms.
@protected
Expand Down Expand Up @@ -51,7 +51,7 @@ class SqliteProvider implements Provider<SqliteModel> {

/// Remove record from SQLite. [query] is ignored.
@override
Future<int> delete<TModel extends SqliteModel>(instance, {query, repository}) async {
Future<int> delete<TModel extends CModel>(instance, {query, repository}) async {
final adapter = modelDictionary.adapterFor[TModel]!;
final db = await getDb();
final existingPrimaryKey = await adapter.primaryKeyByUniqueColumns(instance, db);
Expand All @@ -73,9 +73,9 @@ class SqliteProvider implements Provider<SqliteModel> {
///
/// If [query.where] is `null`, existence for **any** record is executed.
@override
Future<bool> exists<TModel extends SqliteModel>({
Future<bool> exists<TModel extends CModel>({
Query? query,
ModelRepository<SqliteModel>? repository,
ModelRepository<CModel>? repository,
}) async {
final sqlQuery = QuerySqlTransformer<TModel>(
modelDictionary: modelDictionary,
Expand Down Expand Up @@ -119,7 +119,7 @@ class SqliteProvider implements Provider<SqliteModel> {
/// equal `'providerArgs': { 'orderBy': 'created_at ASC, name ASC' }` with column names defined.
/// As Brick manages column names, this is not recommended and should be written only when necessary.
@override
Future<List<TModel>> get<TModel extends SqliteModel>({
Future<List<TModel>> get<TModel extends CModel>({
query,
repository,
}) async {
Expand Down Expand Up @@ -205,10 +205,10 @@ class SqliteProvider implements Provider<SqliteModel> {

/// Fetch results for model with a custom SQL statement.
/// It is recommended to use [get] whenever possible. **Advanced use only**.
Future<List<TModel>> rawGet<TModel extends SqliteModel>(
Future<List<TModel>> rawGet<TModel extends CModel>(
String sql,
List arguments, {
ModelRepository<SqliteModel>? repository,
ModelRepository<CModel>? repository,
}) async {
final adapter = modelDictionary.adapterFor[TModel]!;

Expand Down Expand Up @@ -272,7 +272,7 @@ class SqliteProvider implements Provider<SqliteModel> {

/// Insert record into SQLite. Returns the primary key of the record inserted
@override
Future<int?> upsert<TModel extends SqliteModel>(instance, {query, repository}) async {
Future<int?> upsert<TModel extends CModel>(instance, {query, repository}) async {
final adapter = modelDictionary.adapterFor[TModel]!;
final db = await getDb();

Expand Down
2 changes: 1 addition & 1 deletion packages/brick_sqlite/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_sqlite
issue_tracker: https://github.com/GetDutchie/brick/issues
repository: https://github.com/GetDutchie/brick

version: 3.1.0
version: 3.1.1+1

environment:
sdk: ">=2.18.0 <4.0.0"
Expand Down
8 changes: 8 additions & 0 deletions packages/brick_supabase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## Unreleased

## 1.0.5

- Add `onConflict` to `SupabaseProvider#upsert` requests (#467) @sonbs21

## 1.0.4

- Expose private methods `SupabaseProvider#upsertByType` and `SupabaseProvider#recursiveAssociationUpsert` as protected methods

## 1.0.3

- Remove `select` from `#delete`
Expand Down
16 changes: 11 additions & 5 deletions packages/brick_supabase/lib/src/supabase_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,18 @@ class SupabaseProvider implements Provider<SupabaseModel> {
final adapter = modelDictionary.adapterFor[TModel]!;
final output = await adapter.toSupabase(instance, provider: this, repository: repository);

return await _recursiveAssociationUpsert(
return await recursiveAssociationUpsert(
output,
type: TModel,
query: query,
repository: repository,
) as TModel;
}

Future<SupabaseModel> _upsertByType(
/// Used by [recursiveAssociationUpsert], this performs the upsert to Supabase
/// and selects the model's fields in the response.
@protected
Future<SupabaseModel> upsertByType(
Map<String, dynamic> serializedInstance, {
required Type type,
Query? query,
Expand Down Expand Up @@ -127,7 +130,10 @@ class SupabaseProvider implements Provider<SupabaseModel> {
return adapter.fromSupabase(resp, repository: repository, provider: this);
}

Future<SupabaseModel> _recursiveAssociationUpsert(
/// Discover all SupabaseModel-like associations of a serialized instance and
/// upsert them recursively before the requested instance is upserted.
@protected
Future<SupabaseModel> recursiveAssociationUpsert(
Map<String, dynamic> serializedInstance, {
required Type type,
Query? query,
Expand All @@ -148,7 +154,7 @@ class SupabaseProvider implements Provider<SupabaseModel> {
continue;
}

await _recursiveAssociationUpsert(
await recursiveAssociationUpsert(
Map<String, dynamic>.from(serializedInstance[association.columnName]),
type: association.associationType!,
query: query,
Expand All @@ -157,7 +163,7 @@ class SupabaseProvider implements Provider<SupabaseModel> {
serializedInstance.remove(association.columnName);
}

return await _upsertByType(
return await upsertByType(
serializedInstance,
type: type,
query: query,
Expand Down
2 changes: 1 addition & 1 deletion packages/brick_supabase/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ homepage: https://github.com/GetDutchie/brick/tree/main/packages/brick_supabase
issue_tracker: https://github.com/GetDutchie/brick/issues
repository: https://github.com/GetDutchie/brick

version: 1.0.3
version: 1.0.5

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down

0 comments on commit ba89e5a

Please sign in to comment.