From ba89e5abe3fd93f935f3f95ccc6a6baaa5e67664 Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Mon, 28 Oct 2024 13:56:42 -0400 Subject: [PATCH] eng(supabase): expose private supabase methods as protected; allow type generics in SQLite domain (#469) --- docs/home.md | 2 +- packages/brick_offline_first/CHANGELOG.md | 2 + .../lib/src/mixins/get_first_mixin.dart | 42 +++++++++++++++++++ packages/brick_sqlite/CHANGELOG.md | 5 +++ .../lib/memory_cache_provider.dart | 16 +++---- .../brick_sqlite/lib/src/sqlite_provider.dart | 16 +++---- packages/brick_sqlite/pubspec.yaml | 2 +- packages/brick_supabase/CHANGELOG.md | 8 ++++ .../lib/src/supabase_provider.dart | 16 ++++--- packages/brick_supabase/pubspec.yaml | 2 +- 10 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart diff --git a/docs/home.md b/docs/home.md index b3173f32..36cf9163 100644 --- a/docs/home.md +++ b/docs/home.md @@ -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) diff --git a/packages/brick_offline_first/CHANGELOG.md b/packages/brick_offline_first/CHANGELOG.md index 35fffe70..a2d1578c 100644 --- a/packages/brick_offline_first/CHANGELOG.md +++ b/packages/brick_offline_first/CHANGELOG.md @@ -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 diff --git a/packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart b/packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart new file mode 100644 index 00000000..5933b45f --- /dev/null +++ b/packages/brick_offline_first/lib/src/mixins/get_first_mixin.dart @@ -0,0 +1,42 @@ +import 'package:brick_offline_first/brick_offline_first.dart'; + +/// A convenience mixin for single-instance get operations. +mixin GetFirstMixin on OfflineFirstRepository { + /// 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 getFirst({ + OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, + Query? query, + bool seedOnly = false, + }) async { + final result = await super.get( + 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 getFirstOrNull({ + OfflineFirstGetPolicy policy = OfflineFirstGetPolicy.awaitRemoteWhenNoneExist, + Query? query, + bool seedOnly = false, + }) async { + final result = await super.get( + policy: policy, + query: query?.copyWith(providerArgs: {'limit': 1}), + seedOnly: seedOnly, + ); + + if (result.isEmpty) return null; + return result.first; + } +} diff --git a/packages/brick_sqlite/CHANGELOG.md b/packages/brick_sqlite/CHANGELOG.md index f060b91d..98c45907 100644 --- a/packages/brick_sqlite/CHANGELOG.md +++ b/packages/brick_sqlite/CHANGELOG.md @@ -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 diff --git a/packages/brick_sqlite/lib/memory_cache_provider.dart b/packages/brick_sqlite/lib/memory_cache_provider.dart index 4251f598..f5da74e6 100644 --- a/packages/brick_sqlite/lib/memory_cache_provider.dart +++ b/packages/brick_sqlite/lib/memory_cache_provider.dart @@ -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 { +class MemoryCacheProvider extends Provider { @protected final Logger logger = Logger('MemoryCacheProvider'); @@ -23,13 +23,13 @@ class MemoryCacheProvider extends Provider { final modelDictionary = _MemoryCacheModelDictionary(); /// A complete hash table of the - Map> managedObjects = {}; + Map> 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 [], ]); @@ -40,13 +40,13 @@ class MemoryCacheProvider extends Provider { /// 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([Query? query]) { + bool canFind([Query? query]) { final byPrimaryKey = Where.firstByField(InsertTable.PRIMARY_KEY_FIELD, query?.where); return manages(TModel) && byPrimaryKey?.value != null; } @override - bool delete(instance, {query, repository}) { + bool delete(instance, {query, repository}) { if (!manages(TModel)) return false; logger.finest('#delete: $TModel, $instance, $query'); @@ -56,7 +56,7 @@ class MemoryCacheProvider extends Provider { } @override - List? get({query, repository}) { + List? get({query, repository}) { if (!manages(TModel)) return null; managedObjects[TModel] ??= {}; @@ -79,7 +79,7 @@ class MemoryCacheProvider extends Provider { /// 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 hydrate(List models) { + List hydrate(List models) { if (!manages(TModel)) return models; managedObjects[TModel] ??= {}; @@ -98,7 +98,7 @@ class MemoryCacheProvider extends Provider { } @override - TModel? upsert(instance, {query, repository}) { + TModel? upsert(instance, {query, repository}) { if (!manages(TModel)) return null; logger.finest('#upsert: $TModel, $instance, $query'); hydrate([instance]); diff --git a/packages/brick_sqlite/lib/src/sqlite_provider.dart b/packages/brick_sqlite/lib/src/sqlite_provider.dart index c9f83acd..ceb96be2 100644 --- a/packages/brick_sqlite/lib/src/sqlite_provider.dart +++ b/packages/brick_sqlite/lib/src/sqlite_provider.dart @@ -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 { +class SqliteProvider implements Provider { /// Access the [SQLite](https://github.com/tekartik/sqflite/tree/master/sqflite_common_ffi), /// instance agnostically across platforms. @protected @@ -51,7 +51,7 @@ class SqliteProvider implements Provider { /// Remove record from SQLite. [query] is ignored. @override - Future delete(instance, {query, repository}) async { + Future delete(instance, {query, repository}) async { final adapter = modelDictionary.adapterFor[TModel]!; final db = await getDb(); final existingPrimaryKey = await adapter.primaryKeyByUniqueColumns(instance, db); @@ -73,9 +73,9 @@ class SqliteProvider implements Provider { /// /// If [query.where] is `null`, existence for **any** record is executed. @override - Future exists({ + Future exists({ Query? query, - ModelRepository? repository, + ModelRepository? repository, }) async { final sqlQuery = QuerySqlTransformer( modelDictionary: modelDictionary, @@ -119,7 +119,7 @@ class SqliteProvider implements Provider { /// 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> get({ + Future> get({ query, repository, }) async { @@ -205,10 +205,10 @@ class SqliteProvider implements Provider { /// Fetch results for model with a custom SQL statement. /// It is recommended to use [get] whenever possible. **Advanced use only**. - Future> rawGet( + Future> rawGet( String sql, List arguments, { - ModelRepository? repository, + ModelRepository? repository, }) async { final adapter = modelDictionary.adapterFor[TModel]!; @@ -272,7 +272,7 @@ class SqliteProvider implements Provider { /// Insert record into SQLite. Returns the primary key of the record inserted @override - Future upsert(instance, {query, repository}) async { + Future upsert(instance, {query, repository}) async { final adapter = modelDictionary.adapterFor[TModel]!; final db = await getDb(); diff --git a/packages/brick_sqlite/pubspec.yaml b/packages/brick_sqlite/pubspec.yaml index 297d8032..03358ded 100644 --- a/packages/brick_sqlite/pubspec.yaml +++ b/packages/brick_sqlite/pubspec.yaml @@ -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" diff --git a/packages/brick_supabase/CHANGELOG.md b/packages/brick_supabase/CHANGELOG.md index 7f49afa9..d77faa22 100644 --- a/packages/brick_supabase/CHANGELOG.md +++ b/packages/brick_supabase/CHANGELOG.md @@ -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` diff --git a/packages/brick_supabase/lib/src/supabase_provider.dart b/packages/brick_supabase/lib/src/supabase_provider.dart index 347859be..8786c40b 100644 --- a/packages/brick_supabase/lib/src/supabase_provider.dart +++ b/packages/brick_supabase/lib/src/supabase_provider.dart @@ -87,7 +87,7 @@ class SupabaseProvider implements Provider { 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, @@ -95,7 +95,10 @@ class SupabaseProvider implements Provider { ) as TModel; } - Future _upsertByType( + /// Used by [recursiveAssociationUpsert], this performs the upsert to Supabase + /// and selects the model's fields in the response. + @protected + Future upsertByType( Map serializedInstance, { required Type type, Query? query, @@ -127,7 +130,10 @@ class SupabaseProvider implements Provider { return adapter.fromSupabase(resp, repository: repository, provider: this); } - Future _recursiveAssociationUpsert( + /// Discover all SupabaseModel-like associations of a serialized instance and + /// upsert them recursively before the requested instance is upserted. + @protected + Future recursiveAssociationUpsert( Map serializedInstance, { required Type type, Query? query, @@ -148,7 +154,7 @@ class SupabaseProvider implements Provider { continue; } - await _recursiveAssociationUpsert( + await recursiveAssociationUpsert( Map.from(serializedInstance[association.columnName]), type: association.associationType!, query: query, @@ -157,7 +163,7 @@ class SupabaseProvider implements Provider { serializedInstance.remove(association.columnName); } - return await _upsertByType( + return await upsertByType( serializedInstance, type: type, query: query, diff --git a/packages/brick_supabase/pubspec.yaml b/packages/brick_supabase/pubspec.yaml index 8c2366e0..ddd1f5b6 100644 --- a/packages/brick_supabase/pubspec.yaml +++ b/packages/brick_supabase/pubspec.yaml @@ -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"