From 24f202c91e0ac724383c6a095a9278ea1ac608d9 Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Sat, 24 Aug 2024 15:05:43 -0700 Subject: [PATCH 1/2] feat(supabase): add rest's offline queue to supabase --- .../CHANGELOG.md | 2 + .../example/README.md | 7 - .../lib/offline_queue.dart | 4 + .../lib/brick/adapters/horse_adapter.g.dart | 144 ++++ .../adapters/kitchen_sink_adapter.g.dart | 689 ++++++++++++++++++ .../lib/brick/adapters/mounty_adapter.g.dart | 134 ++++ .../example/lib/brick/brick.g.dart | 53 ++ .../brick/db/20240824214217.migration.dart | 256 +++++++ .../example/lib/brick/db/schema.g.dart | 153 ++++ .../example/lib/main.dart | 56 ++ .../example/lib/models/hat.dart | 17 + .../example/lib/models/horse.model.dart | 14 + .../lib/models/kitchen_sink.model.dart | 129 ++++ .../example/lib/models/mounty.model.dart | 17 + .../example/pubspec.yaml | 25 + ...ffline_first_with_supabase_repository.dart | 74 +- .../pubspec.yaml | 3 +- .../build.yaml | 10 +- 18 files changed, 1773 insertions(+), 14 deletions(-) delete mode 100644 packages/brick_offline_first_with_rest/example/README.md create mode 100644 packages/brick_offline_first_with_rest/lib/offline_queue.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/brick/adapters/horse_adapter.g.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/brick/adapters/kitchen_sink_adapter.g.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/brick/adapters/mounty_adapter.g.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/brick/brick.g.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/brick/db/20240824214217.migration.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/brick/db/schema.g.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/main.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/models/hat.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/models/horse.model.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/models/kitchen_sink.model.dart create mode 100644 packages/brick_offline_first_with_supabase/example/lib/models/mounty.model.dart create mode 100644 packages/brick_offline_first_with_supabase/example/pubspec.yaml diff --git a/packages/brick_offline_first_with_rest/CHANGELOG.md b/packages/brick_offline_first_with_rest/CHANGELOG.md index 77e0e02d..2e959ba0 100644 --- a/packages/brick_offline_first_with_rest/CHANGELOG.md +++ b/packages/brick_offline_first_with_rest/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +* Expose offline queue functionality in `offline_queue.dart` + ## 3.0.2 * Apply standardized lints diff --git a/packages/brick_offline_first_with_rest/example/README.md b/packages/brick_offline_first_with_rest/example/README.md deleted file mode 100644 index 4cd2e286..00000000 --- a/packages/brick_offline_first_with_rest/example/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Brick Offline First with Rest Example - -## FAQ - -### Why are generated files not ignored in this project? - -While a [normal installation should ignore](https://github.com/GetDutchie/brick#recommended-but-optional) `*.g.dart` files, this project has them committed. This is for illustrative purposes to accessibly showcase Brick's output. diff --git a/packages/brick_offline_first_with_rest/lib/offline_queue.dart b/packages/brick_offline_first_with_rest/lib/offline_queue.dart new file mode 100644 index 00000000..c1f53be8 --- /dev/null +++ b/packages/brick_offline_first_with_rest/lib/offline_queue.dart @@ -0,0 +1,4 @@ +export 'package:brick_offline_first_with_rest/src/offline_queue/rest_offline_queue_client.dart'; +export 'package:brick_offline_first_with_rest/src/offline_queue/rest_offline_request_queue.dart'; +export 'package:brick_offline_first_with_rest/src/offline_queue/rest_request_sqlite_cache.dart'; +export 'package:brick_offline_first_with_rest/src/offline_queue/rest_request_sqlite_cache_manager.dart'; diff --git a/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/horse_adapter.g.dart b/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/horse_adapter.g.dart new file mode 100644 index 00000000..a14bd318 --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/horse_adapter.g.dart @@ -0,0 +1,144 @@ +// GENERATED CODE DO NOT EDIT +part of '../brick.g.dart'; + +Future _$HorseFromSupabase(Map data, + {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return Horse( + name: data['name'] as String?, + mounties: await Future.wait(data['mounties'] + ?.map((d) => + MountyAdapter().fromSupabase(d, provider: provider, repository: repository)) + .toList() + .cast>() ?? + [])); +} + +Future> _$HorseToSupabase(Horse instance, + {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return { + 'name': instance.name, + 'mounties': await Future.wait>(instance.mounties + ?.map((s) => MountyAdapter().toSupabase(s, provider: provider, repository: repository)) + .toList() ?? + []) + }; +} + +Future _$HorseFromSqlite(Map data, + {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return Horse( + name: data['name'] == null ? null : data['name'] as String?, + mounties: (await provider.rawQuery( + 'SELECT DISTINCT `f_Mounty_brick_id` FROM `_brick_Horse_mounties` WHERE l_Horse_brick_id = ?', + [data['_brick_id'] as int]).then((results) { + final ids = results.map((r) => r['f_Mounty_brick_id']); + return Future.wait(ids.map((primaryKey) => repository! + .getAssociation( + Query.where('primaryKey', primaryKey, limit1: true), + ) + .then((r) => r!.first))); + })) + .toList() + .cast()) + ..primaryKey = data['_brick_id'] as int; +} + +Future> _$HorseToSqlite(Horse instance, + {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return {'name': instance.name}; +} + +/// Construct a [Horse] +class HorseAdapter extends OfflineFirstWithSupabaseAdapter { + HorseAdapter(); + + @override + final defaultToNull = true; + @override + final Map fieldsToSqliteColumns = { + 'name': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'name', + ), + 'mounties': const RuntimeSupabaseColumnDefinition( + association: true, + associationForeignKey: 'null', + associationType: Mounty, + columnName: 'mounties', + ) + }; + @override + final ignoreDuplicates = false; + @override + final uniqueFields = {}; + @override + final Map fieldsToSqliteColumns = { + 'primaryKey': const RuntimeSqliteColumnDefinition( + association: false, + columnName: '_brick_id', + iterable: false, + type: int, + ), + 'name': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'name', + iterable: false, + type: String, + ), + 'mounties': const RuntimeSqliteColumnDefinition( + association: true, + columnName: 'mounties', + iterable: true, + type: Mounty, + ) + }; + @override + Future primaryKeyByUniqueColumns(Horse instance, DatabaseExecutor executor) async => + instance.primaryKey; + @override + final String tableName = 'Horse'; + @override + Future afterSave(instance, {required provider, repository}) async { + if (instance.primaryKey != null) { + final mountiesOldColumns = await provider.rawQuery( + 'SELECT `f_Mounty_brick_id` FROM `_brick_Horse_mounties` WHERE `l_Horse_brick_id` = ?', + [instance.primaryKey]); + final mountiesOldIds = mountiesOldColumns.map((a) => a['f_Mounty_brick_id']); + final mountiesNewIds = instance.mounties?.map((s) => s.primaryKey).whereType() ?? []; + final mountiesIdsToDelete = mountiesOldIds.where((id) => !mountiesNewIds.contains(id)); + + await Future.wait(mountiesIdsToDelete.map((id) async { + return await provider.rawExecute( + 'DELETE FROM `_brick_Horse_mounties` WHERE `l_Horse_brick_id` = ? AND `f_Mounty_brick_id` = ?', + [instance.primaryKey, id]).catchError((e) => null); + })); + + await Future.wait(instance.mounties?.map((s) async { + final id = s.primaryKey ?? await provider.upsert(s, repository: repository); + return await provider.rawInsert( + 'INSERT OR IGNORE INTO `_brick_Horse_mounties` (`l_Horse_brick_id`, `f_Mounty_brick_id`) VALUES (?, ?)', + [instance.primaryKey, id]); + }) ?? + []); + } + } + + @override + Future fromSupabase(Map input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$HorseFromSupabase(input, provider: provider, repository: repository); + @override + Future> toSupabase(Horse input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$HorseToSupabase(input, provider: provider, repository: repository); + @override + Future fromSqlite(Map input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$HorseFromSqlite(input, provider: provider, repository: repository); + @override + Future> toSqlite(Horse input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$HorseToSqlite(input, provider: provider, repository: repository); +} diff --git a/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/kitchen_sink_adapter.g.dart b/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/kitchen_sink_adapter.g.dart new file mode 100644 index 00000000..e2855510 --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/kitchen_sink_adapter.g.dart @@ -0,0 +1,689 @@ +// GENERATED CODE DO NOT EDIT +part of '../brick.g.dart'; + +Future _$KitchenSinkFromSupabase(Map data, + {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return KitchenSink( + anyString: data['any_string'] as String?, + anyInt: data['any_int'] as int?, + anyDouble: data['any_double'] as double?, + anyNum: data['any_num'] as num?, + anyDateTime: + data['any_date_time'] == null ? null : DateTime.tryParse(data['any_date_time'] as String), + anyBool: data['any_bool'] as bool?, + anyMap: data['any_map'], + enumFromIndex: + data['enum_from_index'] is int ? AnyEnum.values[data['enum_from_index'] as int] : null, + anyList: data['any_list']?.toList().cast(), + anySet: data['any_set']?.toSet().cast(), + offlineFirstModel: data['offline_first_model'] == null + ? null + : await MountyAdapter().fromSupabase(data['offline_first_model'], + provider: provider, repository: repository), + listOfflineFirstModel: await Future.wait(data['list_offline_first_model'] + ?.map((d) => + MountyAdapter().fromSupabase(d, provider: provider, repository: repository)) + .toList() + .cast>() ?? + []), + setOfflineFirstModel: + (await Future.wait(data['set_offline_first_model']?.map((d) => MountyAdapter().fromSupabase(d, provider: provider, repository: repository)).toSet().cast>() ?? [])) + .toSet(), + restAnnotationName: data['restAnnotationOtherName'] as String?, + restAnnotationDefaultValue: + data['rest_annotation_default_value'] as String? ?? 'a default value', + restAnnotationNullable: data['rest_annotation_nullable'] == null + ? null + : data['rest_annotation_nullable'] as String?, + restAnnotationIgnoreTo: data['rest_annotation_ignore_to'] as String?, + restAnnotationFromGenerator: data['rest_annotation_from_generator'].toString(), + restAnnotationToGenerator: data['rest_annotation_to_generator'] as String?, + enumFromString: AnyEnum.values.byName(data['enum_from_string']), + sqliteAnnotationNullable: data['sqlite_annotation_nullable'] as String?, + sqliteAnnotationDefaultValue: data['sqlite_annotation_default_value'] as String?, + sqliteAnnotationFromGenerator: data['sqlite_annotation_from_generator'] as String?, + sqliteAnnotationToGenerator: data['sqlite_annotation_to_generator'] as String?, + sqliteAnnotationIgnore: data['sqlite_annotation_ignore'] as String?, + sqliteAnnotationUnique: data['sqlite_annotation_unique'] as String?, + sqliteAnnotationName: data['sqlite_annotation_name'] as String?, + offlineFirstWhere: await repository + ?.getAssociation( + Query(where: [Where.exact('email', data['mounty_email'])], providerArgs: {'limit': 1})) + .then((r) => r?.isNotEmpty ?? false ? r!.first : null)); +} + +Future> _$KitchenSinkToSupabase(KitchenSink instance, + {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return { + 'any_string': instance.anyString, + 'any_int': instance.anyInt, + 'any_double': instance.anyDouble, + 'any_num': instance.anyNum, + 'any_date_time': instance.anyDateTime?.toIso8601String(), + 'any_bool': instance.anyBool, + 'any_map': instance.anyMap, + 'enum_from_index': + instance.enumFromIndex != null ? AnyEnum.values.indexOf(instance.enumFromIndex!) : null, + 'any_list': instance.anyList, + 'any_set': instance.anySet, + 'offline_first_model': instance.offlineFirstModel != null + ? await MountyAdapter() + .toSupabase(instance.offlineFirstModel!, provider: provider, repository: repository) + : null, + 'list_offline_first_model': await Future.wait>(instance + .listOfflineFirstModel + ?.map((s) => MountyAdapter().toSupabase(s, provider: provider, repository: repository)) + .toList() ?? + []), + 'set_offline_first_model': await Future.wait>(instance.setOfflineFirstModel + ?.map((s) => MountyAdapter().toSupabase(s, provider: provider, repository: repository)) + .toList() ?? + []), + 'restAnnotationOtherName': instance.restAnnotationName, + 'rest_annotation_default_value': instance.restAnnotationDefaultValue, + 'rest_annotation_nullable': instance.restAnnotationNullable, + 'rest_annotation_ignore_from': instance.restAnnotationIgnoreFrom, + 'rest_annotation_from_generator': instance.restAnnotationFromGenerator, + 'rest_annotation_to_generator': instance.restAnnotationToGenerator.toString(), + 'enum_from_string': instance.enumFromString?.name, + 'sqlite_annotation_nullable': instance.sqliteAnnotationNullable, + 'sqlite_annotation_default_value': instance.sqliteAnnotationDefaultValue, + 'sqlite_annotation_from_generator': instance.sqliteAnnotationFromGenerator, + 'sqlite_annotation_to_generator': instance.sqliteAnnotationToGenerator, + 'sqlite_annotation_ignore': instance.sqliteAnnotationIgnore, + 'sqlite_annotation_unique': instance.sqliteAnnotationUnique, + 'sqlite_annotation_name': instance.sqliteAnnotationName, + 'offline_first_where': instance.offlineFirstWhere?.email + }; +} + +Future _$KitchenSinkFromSqlite(Map data, + {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return KitchenSink( + anyString: data['any_string'] == null ? null : data['any_string'] as String?, + anyInt: data['any_int'] == null ? null : data['any_int'] as int?, + anyDouble: data['any_double'] == null ? null : data['any_double'] as double?, + anyNum: data['any_num'] == null ? null : data['any_num'] as num?, + anyDateTime: data['any_date_time'] == null + ? null + : data['any_date_time'] == null + ? null + : DateTime.tryParse(data['any_date_time'] as String), + anyBool: data['any_bool'] == null ? null : data['any_bool'] == 1, + anyMap: data['any_map'] == null ? null : jsonDecode(data['any_map']), + enumFromIndex: data['enum_from_index'] == null + ? null + : (data['enum_from_index'] > -1 ? AnyEnum.values[data['enum_from_index'] as int] : null), + anyList: data['any_list'] == null ? null : jsonDecode(data['any_list']).toList().cast(), + anySet: data['any_set'] == null ? null : jsonDecode(data['any_set']).toSet().cast(), + offlineFirstModel: data['offline_first_model_Mounty_brick_id'] == null + ? null + : (data['offline_first_model_Mounty_brick_id'] > -1 + ? (await repository?.getAssociation( + Query.where('primaryKey', data['offline_first_model_Mounty_brick_id'] as int, + limit1: true), + )) + ?.first + : null), + listOfflineFirstModel: (await provider.rawQuery( + 'SELECT DISTINCT `f_Mounty_brick_id` FROM `_brick_KitchenSink_list_offline_first_model` WHERE l_KitchenSink_brick_id = ?', + [ + data['_brick_id'] as int + ]).then((results) { + final ids = results.map((r) => r['f_Mounty_brick_id']); + return Future.wait(ids.map((primaryKey) => repository! + .getAssociation( + Query.where('primaryKey', primaryKey, limit1: true), + ) + .then((r) => r!.first))); + })) + .toList() + .cast(), + setOfflineFirstModel: (await provider.rawQuery( + 'SELECT DISTINCT `f_Mounty_brick_id` FROM `_brick_KitchenSink_set_offline_first_model` WHERE l_KitchenSink_brick_id = ?', + [ + data['_brick_id'] as int + ]).then((results) { + final ids = results.map((r) => r['f_Mounty_brick_id']); + return Future.wait(ids.map((primaryKey) => repository! + .getAssociation( + Query.where('primaryKey', primaryKey, limit1: true), + ) + .then((r) => r!.first))); + })) + .toSet(), + restAnnotationName: + data['rest_annotation_name'] == null ? null : data['rest_annotation_name'] as String?, + restAnnotationDefaultValue: data['rest_annotation_default_value'] == null + ? null + : data['rest_annotation_default_value'] as String?, + restAnnotationNullable: data['rest_annotation_nullable'] == null ? null : data['rest_annotation_nullable'] as String?, + restAnnotationIgnore: data['rest_annotation_ignore'] == null ? null : data['rest_annotation_ignore'] as String?, + restAnnotationIgnoreTo: data['rest_annotation_ignore_to'] == null ? null : data['rest_annotation_ignore_to'] as String?, + restAnnotationIgnoreFrom: data['rest_annotation_ignore_from'] == null ? null : data['rest_annotation_ignore_from'] as String?, + restAnnotationFromGenerator: data['rest_annotation_from_generator'] == null ? null : data['rest_annotation_from_generator'] as String?, + restAnnotationToGenerator: data['rest_annotation_to_generator'] == null ? null : data['rest_annotation_to_generator'] as String?, + enumFromString: data['enum_from_string'] == null ? null : (data['enum_from_string'] > -1 ? AnyEnum.values[data['enum_from_string'] as int] : null), + sqliteAnnotationNullable: data['sqlite_annotation_nullable'] == null ? null : data['sqlite_annotation_nullable'] as String?, + sqliteAnnotationDefaultValue: data['sqlite_annotation_default_value'] == null ? null : data['sqlite_annotation_default_value'] as String? ?? 'default value', + sqliteAnnotationFromGenerator: data['sqlite_annotation_from_generator'] == null ? null : data['sqlite_annotation_from_generator'].toString(), + sqliteAnnotationToGenerator: data['sqlite_annotation_to_generator'] == null ? null : data['sqlite_annotation_to_generator'] as String?, + sqliteAnnotationUnique: data['sqlite_annotation_unique'] == null ? null : data['sqlite_annotation_unique'] as String?, + sqliteAnnotationName: data['custom column name'] == null ? null : data['custom column name'] as String?, + offlineFirstWhere: data['offline_first_where_Mounty_brick_id'] == null + ? null + : (data['offline_first_where_Mounty_brick_id'] > -1 + ? (await repository?.getAssociation( + Query.where('primaryKey', data['offline_first_where_Mounty_brick_id'] as int, + limit1: true), + )) + ?.first + : null)) + ..primaryKey = data['_brick_id'] as int; +} + +Future> _$KitchenSinkToSqlite(KitchenSink instance, + {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return { + 'any_string': instance.anyString, + 'any_int': instance.anyInt, + 'any_double': instance.anyDouble, + 'any_num': instance.anyNum, + 'any_date_time': instance.anyDateTime?.toIso8601String(), + 'any_bool': instance.anyBool == null ? null : (instance.anyBool! ? 1 : 0), + 'any_map': jsonEncode(instance.anyMap ?? {}), + 'enum_from_index': + instance.enumFromIndex != null ? AnyEnum.values.indexOf(instance.enumFromIndex!) : null, + 'any_list': instance.anyList == null ? null : jsonEncode(instance.anyList), + 'any_set': instance.anySet == null ? null : jsonEncode(instance.anySet.toList()), + 'offline_first_model_Mounty_brick_id': instance.offlineFirstModel != null + ? instance.offlineFirstModel!.primaryKey ?? + await provider.upsert(instance.offlineFirstModel!, repository: repository) + : null, + 'rest_annotation_name': instance.restAnnotationName, + 'rest_annotation_default_value': instance.restAnnotationDefaultValue, + 'rest_annotation_nullable': instance.restAnnotationNullable, + 'rest_annotation_ignore': instance.restAnnotationIgnore, + 'rest_annotation_ignore_to': instance.restAnnotationIgnoreTo, + 'rest_annotation_ignore_from': instance.restAnnotationIgnoreFrom, + 'rest_annotation_from_generator': instance.restAnnotationFromGenerator, + 'rest_annotation_to_generator': instance.restAnnotationToGenerator, + 'enum_from_string': + instance.enumFromString != null ? AnyEnum.values.indexOf(instance.enumFromString!) : null, + 'sqlite_annotation_nullable': instance.sqliteAnnotationNullable, + 'sqlite_annotation_default_value': instance.sqliteAnnotationDefaultValue, + 'sqlite_annotation_from_generator': instance.sqliteAnnotationFromGenerator, + 'sqlite_annotation_to_generator': instance.sqliteAnnotationToGenerator.toString(), + 'sqlite_annotation_unique': instance.sqliteAnnotationUnique, + 'custom column name': instance.sqliteAnnotationName, + 'offline_first_where_Mounty_brick_id': instance.offlineFirstWhere != null + ? instance.offlineFirstWhere!.primaryKey ?? + await provider.upsert(instance.offlineFirstWhere!, repository: repository) + : null + }; +} + +/// Construct a [KitchenSink] +class KitchenSinkAdapter extends OfflineFirstWithSupabaseAdapter { + KitchenSinkAdapter(); + + @override + final fieldsToOfflineFirstRuntimeDefinition = { + 'offlineFirstWhere': const RuntimeOfflineFirstDefinition( + where: {'email': "data['mounty_email']"}, + ) + }; + @override + final defaultToNull = true; + @override + final Map fieldsToSqliteColumns = { + 'anyString': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'any_string', + ), + 'anyInt': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: int, + columnName: 'any_int', + ), + 'anyDouble': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: double, + columnName: 'any_double', + ), + 'anyNum': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: num, + columnName: 'any_num', + ), + 'anyDateTime': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: DateTime, + columnName: 'any_date_time', + ), + 'anyBool': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: bool, + columnName: 'any_bool', + ), + 'anyMap': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: Map, + columnName: 'any_map', + ), + 'enumFromIndex': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: AnyEnum, + columnName: 'enum_from_index', + ), + 'anyList': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: int, + columnName: 'any_list', + ), + 'anySet': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: int, + columnName: 'any_set', + ), + 'offlineFirstModel': const RuntimeSupabaseColumnDefinition( + association: true, + associationForeignKey: 'null', + associationType: Mounty, + columnName: 'offline_first_model', + ), + 'listOfflineFirstModel': const RuntimeSupabaseColumnDefinition( + association: true, + associationForeignKey: 'null', + associationType: Mounty, + columnName: 'list_offline_first_model', + ), + 'setOfflineFirstModel': const RuntimeSupabaseColumnDefinition( + association: true, + associationForeignKey: 'null', + associationType: Mounty, + columnName: 'set_offline_first_model', + ), + 'restAnnotationName': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'restAnnotationOtherName', + ), + 'restAnnotationDefaultValue': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'rest_annotation_default_value', + ), + 'restAnnotationNullable': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'rest_annotation_nullable', + ), + 'restAnnotationIgnoreTo': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'rest_annotation_ignore_to', + ), + 'restAnnotationIgnoreFrom': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'rest_annotation_ignore_from', + ), + 'restAnnotationFromGenerator': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'rest_annotation_from_generator', + ), + 'restAnnotationToGenerator': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'rest_annotation_to_generator', + ), + 'enumFromString': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: AnyEnum, + columnName: 'enum_from_string', + ), + 'sqliteAnnotationNullable': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'sqlite_annotation_nullable', + ), + 'sqliteAnnotationDefaultValue': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'sqlite_annotation_default_value', + ), + 'sqliteAnnotationFromGenerator': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'sqlite_annotation_from_generator', + ), + 'sqliteAnnotationToGenerator': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'sqlite_annotation_to_generator', + ), + 'sqliteAnnotationIgnore': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'sqlite_annotation_ignore', + ), + 'sqliteAnnotationUnique': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'sqlite_annotation_unique', + ), + 'sqliteAnnotationName': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'sqlite_annotation_name', + ), + 'offlineFirstWhere': const RuntimeSupabaseColumnDefinition( + association: true, + associationForeignKey: 'null', + associationType: Mounty, + columnName: 'offline_first_where', + ) + }; + @override + final ignoreDuplicates = false; + @override + final uniqueFields = {}; + @override + final Map fieldsToSqliteColumns = { + 'primaryKey': const RuntimeSqliteColumnDefinition( + association: false, + columnName: '_brick_id', + iterable: false, + type: int, + ), + 'anyString': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_string', + iterable: false, + type: String, + ), + 'anyInt': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_int', + iterable: false, + type: int, + ), + 'anyDouble': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_double', + iterable: false, + type: double, + ), + 'anyNum': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_num', + iterable: false, + type: num, + ), + 'anyDateTime': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_date_time', + iterable: false, + type: DateTime, + ), + 'anyBool': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_bool', + iterable: false, + type: bool, + ), + 'anyMap': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_map', + iterable: false, + type: Map, + ), + 'enumFromIndex': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'enum_from_index', + iterable: false, + type: AnyEnum, + ), + 'anyList': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_list', + iterable: true, + type: int, + ), + 'anySet': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'any_set', + iterable: true, + type: int, + ), + 'offlineFirstModel': const RuntimeSqliteColumnDefinition( + association: true, + columnName: 'offline_first_model_Mounty_brick_id', + iterable: false, + type: Mounty, + ), + 'listOfflineFirstModel': const RuntimeSqliteColumnDefinition( + association: true, + columnName: 'list_offline_first_model', + iterable: true, + type: Mounty, + ), + 'setOfflineFirstModel': const RuntimeSqliteColumnDefinition( + association: true, + columnName: 'set_offline_first_model', + iterable: true, + type: Mounty, + ), + 'restAnnotationName': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_name', + iterable: false, + type: String, + ), + 'restAnnotationDefaultValue': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_default_value', + iterable: false, + type: String, + ), + 'restAnnotationNullable': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_nullable', + iterable: false, + type: String, + ), + 'restAnnotationIgnore': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_ignore', + iterable: false, + type: String, + ), + 'restAnnotationIgnoreTo': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_ignore_to', + iterable: false, + type: String, + ), + 'restAnnotationIgnoreFrom': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_ignore_from', + iterable: false, + type: String, + ), + 'restAnnotationFromGenerator': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_from_generator', + iterable: false, + type: String, + ), + 'restAnnotationToGenerator': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'rest_annotation_to_generator', + iterable: false, + type: String, + ), + 'enumFromString': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'enum_from_string', + iterable: false, + type: AnyEnum, + ), + 'sqliteAnnotationNullable': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'sqlite_annotation_nullable', + iterable: false, + type: String, + ), + 'sqliteAnnotationDefaultValue': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'sqlite_annotation_default_value', + iterable: false, + type: String, + ), + 'sqliteAnnotationFromGenerator': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'sqlite_annotation_from_generator', + iterable: false, + type: String, + ), + 'sqliteAnnotationToGenerator': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'sqlite_annotation_to_generator', + iterable: false, + type: String, + ), + 'sqliteAnnotationUnique': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'sqlite_annotation_unique', + iterable: false, + type: String, + ), + 'sqliteAnnotationName': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'custom column name', + iterable: false, + type: String, + ), + 'offlineFirstWhere': const RuntimeSqliteColumnDefinition( + association: true, + columnName: 'offline_first_where_Mounty_brick_id', + iterable: false, + type: Mounty, + ) + }; + @override + Future primaryKeyByUniqueColumns(KitchenSink instance, DatabaseExecutor executor) async { + final results = await executor.rawQuery(''' + SELECT * FROM `KitchenSink` WHERE sqlite_annotation_unique = ? LIMIT 1''', + [instance.sqliteAnnotationUnique]); + + // SQFlite returns [{}] when no results are found + if (results.isEmpty || (results.length == 1 && results.first.isEmpty)) { + return null; + } + + return results.first['_brick_id'] as int; + } + + @override + final String tableName = 'KitchenSink'; + @override + Future afterSave(instance, {required provider, repository}) async { + if (instance.primaryKey != null) { + final listOfflineFirstModelOldColumns = await provider.rawQuery( + 'SELECT `f_Mounty_brick_id` FROM `_brick_KitchenSink_list_offline_first_model` WHERE `l_KitchenSink_brick_id` = ?', + [instance.primaryKey]); + final listOfflineFirstModelOldIds = + listOfflineFirstModelOldColumns.map((a) => a['f_Mounty_brick_id']); + final listOfflineFirstModelNewIds = + instance.listOfflineFirstModel?.map((s) => s.primaryKey).whereType() ?? []; + final listOfflineFirstModelIdsToDelete = + listOfflineFirstModelOldIds.where((id) => !listOfflineFirstModelNewIds.contains(id)); + + await Future.wait(listOfflineFirstModelIdsToDelete.map((id) async { + return await provider.rawExecute( + 'DELETE FROM `_brick_KitchenSink_list_offline_first_model` WHERE `l_KitchenSink_brick_id` = ? AND `f_Mounty_brick_id` = ?', + [instance.primaryKey, id]).catchError((e) => null); + })); + + await Future.wait(instance.listOfflineFirstModel?.map((s) async { + final id = s.primaryKey ?? await provider.upsert(s, repository: repository); + return await provider.rawInsert( + 'INSERT OR IGNORE INTO `_brick_KitchenSink_list_offline_first_model` (`l_KitchenSink_brick_id`, `f_Mounty_brick_id`) VALUES (?, ?)', + [instance.primaryKey, id]); + }) ?? + []); + } + + if (instance.primaryKey != null) { + final setOfflineFirstModelOldColumns = await provider.rawQuery( + 'SELECT `f_Mounty_brick_id` FROM `_brick_KitchenSink_set_offline_first_model` WHERE `l_KitchenSink_brick_id` = ?', + [instance.primaryKey]); + final setOfflineFirstModelOldIds = + setOfflineFirstModelOldColumns.map((a) => a['f_Mounty_brick_id']); + final setOfflineFirstModelNewIds = + instance.setOfflineFirstModel?.map((s) => s.primaryKey).whereType() ?? []; + final setOfflineFirstModelIdsToDelete = + setOfflineFirstModelOldIds.where((id) => !setOfflineFirstModelNewIds.contains(id)); + + await Future.wait(setOfflineFirstModelIdsToDelete.map((id) async { + return await provider.rawExecute( + 'DELETE FROM `_brick_KitchenSink_set_offline_first_model` WHERE `l_KitchenSink_brick_id` = ? AND `f_Mounty_brick_id` = ?', + [instance.primaryKey, id]).catchError((e) => null); + })); + + await Future.wait(instance.setOfflineFirstModel?.map((s) async { + final id = s.primaryKey ?? await provider.upsert(s, repository: repository); + return await provider.rawInsert( + 'INSERT OR IGNORE INTO `_brick_KitchenSink_set_offline_first_model` (`l_KitchenSink_brick_id`, `f_Mounty_brick_id`) VALUES (?, ?)', + [instance.primaryKey, id]); + }) ?? + []); + } + } + + @override + Future fromSupabase(Map input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$KitchenSinkFromSupabase(input, provider: provider, repository: repository); + @override + Future> toSupabase(KitchenSink input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$KitchenSinkToSupabase(input, provider: provider, repository: repository); + @override + Future fromSqlite(Map input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$KitchenSinkFromSqlite(input, provider: provider, repository: repository); + @override + Future> toSqlite(KitchenSink input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$KitchenSinkToSqlite(input, provider: provider, repository: repository); +} diff --git a/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/mounty_adapter.g.dart b/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/mounty_adapter.g.dart new file mode 100644 index 00000000..eb46aeaa --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/brick/adapters/mounty_adapter.g.dart @@ -0,0 +1,134 @@ +// GENERATED CODE DO NOT EDIT +part of '../brick.g.dart'; + +Future _$MountyFromSupabase(Map data, + {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return Mounty( + name: data['name'] as String?, + email: data['email'] as String?, + hat: data['hat'] == null + ? null + : await HatAdapter() + .fromSupabase(data['hat'], provider: provider, repository: repository)); +} + +Future> _$MountyToSupabase(Mounty instance, + {required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return { + 'name': instance.name, + 'email': instance.email, + 'hat': instance.hat != null + ? await HatAdapter().toSupabase(instance.hat!, provider: provider, repository: repository) + : null + }; +} + +Future _$MountyFromSqlite(Map data, + {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return Mounty( + name: data['name'] == null ? null : data['name'] as String?, + email: data['email'] == null ? null : data['email'] as String?, + hat: data['hat_Hat_brick_id'] == null + ? null + : (data['hat_Hat_brick_id'] > -1 + ? (await repository?.getAssociation( + Query.where('primaryKey', data['hat_Hat_brick_id'] as int, limit1: true), + )) + ?.first + : null)) + ..primaryKey = data['_brick_id'] as int; +} + +Future> _$MountyToSqlite(Mounty instance, + {required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async { + return { + 'name': instance.name, + 'email': instance.email, + 'hat_Hat_brick_id': instance.hat != null + ? instance.hat!.primaryKey ?? + await provider.upsert(instance.hat!, repository: repository) + : null + }; +} + +/// Construct a [Mounty] +class MountyAdapter extends OfflineFirstWithSupabaseAdapter { + MountyAdapter(); + + @override + final defaultToNull = true; + @override + final Map fieldsToSqliteColumns = { + 'name': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'name', + ), + 'email': const RuntimeSupabaseColumnDefinition( + association: false, + associationForeignKey: 'null', + associationType: String, + columnName: 'email', + ), + 'hat': const RuntimeSupabaseColumnDefinition( + association: true, + associationForeignKey: 'null', + associationType: Hat, + columnName: 'hat', + ) + }; + @override + final ignoreDuplicates = false; + @override + final uniqueFields = {}; + @override + final Map fieldsToSqliteColumns = { + 'primaryKey': const RuntimeSqliteColumnDefinition( + association: false, + columnName: '_brick_id', + iterable: false, + type: int, + ), + 'name': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'name', + iterable: false, + type: String, + ), + 'email': const RuntimeSqliteColumnDefinition( + association: false, + columnName: 'email', + iterable: false, + type: String, + ), + 'hat': const RuntimeSqliteColumnDefinition( + association: true, + columnName: 'hat_Hat_brick_id', + iterable: false, + type: Hat, + ) + }; + @override + Future primaryKeyByUniqueColumns(Mounty instance, DatabaseExecutor executor) async => + instance.primaryKey; + @override + final String tableName = 'Mounty'; + + @override + Future fromSupabase(Map input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$MountyFromSupabase(input, provider: provider, repository: repository); + @override + Future> toSupabase(Mounty input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$MountyToSupabase(input, provider: provider, repository: repository); + @override + Future fromSqlite(Map input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$MountyFromSqlite(input, provider: provider, repository: repository); + @override + Future> toSqlite(Mounty input, + {required provider, covariant OfflineFirstWithSupabaseRepository? repository}) async => + await _$MountyToSqlite(input, provider: provider, repository: repository); +} diff --git a/packages/brick_offline_first_with_supabase/example/lib/brick/brick.g.dart b/packages/brick_offline_first_with_supabase/example/lib/brick/brick.g.dart new file mode 100644 index 00000000..ed548203 --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/brick/brick.g.dart @@ -0,0 +1,53 @@ +// ignore: unused_import, unused_shown_name, unnecessary_import +import 'package:brick_core/query.dart'; +// ignore: unused_import, unused_shown_name, unnecessary_import +import 'package:brick_sqlite/db.dart'; +// ignore: unused_import, unused_shown_name, unnecessary_import +import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart'; +// ignore: unused_import, unused_shown_name, unnecessary_import +import 'package:brick_offline_first_with_supabase_example/models/mounty.model.dart'; +// ignore: unused_import, unused_shown_name, unnecessary_import +import 'package:brick_sqlite/brick_sqlite.dart'; +// ignore: unused_import, unused_shown_name, unnecessary_import +import 'package:brick_supabase/brick_supabase.dart'; +// ignore: unused_import, unused_shown_name, unnecessary_import +import 'package:brick_offline_first_with_supabase_example/models/hat.dart'; // GENERATED CODE DO NOT EDIT +// ignore: unused_import +import 'dart:convert'; +import 'package:brick_sqlite/brick_sqlite.dart' + show + SqliteModel, + SqliteAdapter, + SqliteModelDictionary, + RuntimeSqliteColumnDefinition, + SqliteProvider; +import 'package:brick_supabase/brick_supabase.dart' + show SupabaseProvider, SupabaseModel, SupabaseAdapter, SupabaseModelDictionary; +// ignore: unused_import, unused_shown_name +import 'package:brick_offline_first/brick_offline_first.dart' show RuntimeOfflineFirstDefinition; +// ignore: unused_import, unused_shown_name +import 'package:sqflite_common/sqlite_api.dart' show DatabaseExecutor; + +import '../models/kitchen_sink.model.dart'; +import '../models/mounty.model.dart'; +import '../models/horse.model.dart'; + +part 'adapters/kitchen_sink_adapter.g.dart'; +part 'adapters/mounty_adapter.g.dart'; +part 'adapters/horse_adapter.g.dart'; + +/// Supabase mappings should only be used when initializing a [SupabaseProvider] +final Map> supabaseMappings = { + KitchenSink: KitchenSinkAdapter(), + Mounty: MountyAdapter(), + Horse: HorseAdapter() +}; +final supabaseModelDictionary = SupabaseModelDictionary(supabaseMappings); + +/// Sqlite mappings should only be used when initializing a [SqliteProvider] +final Map> sqliteMappings = { + KitchenSink: KitchenSinkAdapter(), + Mounty: MountyAdapter(), + Horse: HorseAdapter() +}; +final sqliteModelDictionary = SqliteModelDictionary(sqliteMappings); diff --git a/packages/brick_offline_first_with_supabase/example/lib/brick/db/20240824214217.migration.dart b/packages/brick_offline_first_with_supabase/example/lib/brick/db/20240824214217.migration.dart new file mode 100644 index 00000000..2e9305fc --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/brick/db/20240824214217.migration.dart @@ -0,0 +1,256 @@ +// GENERATED CODE EDIT WITH CAUTION +// THIS FILE **WILL NOT** BE REGENERATED +// This file should be version controlled and can be manually edited. +part of 'schema.g.dart'; + +// While migrations are intelligently created, the difference between some commands, such as +// DropTable vs. RenameTable, cannot be determined. For this reason, please review migrations after +// they are created to ensure the correct inference was made. + +// The migration version must **always** mirror the file name + +const List _migration_20240824214217_up = [ + InsertTable('_brick_KitchenSink_list_offline_first_model'), + InsertTable('_brick_KitchenSink_set_offline_first_model'), + InsertTable('_brick_KitchenSink_list_offline_first_serdes'), + InsertTable('_brick_KitchenSink_set_offline_first_serdes'), + InsertTable('KitchenSink'), + InsertTable('Mounty'), + InsertTable('_brick_Horse_mounties'), + InsertTable('Horse'), + InsertForeignKey( + '_brick_KitchenSink_list_offline_first_model', + 'KitchenSink', + foreignKeyColumn: 'l_KitchenSink_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_KitchenSink_list_offline_first_model', + 'Mounty', + foreignKeyColumn: 'f_Mounty_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_KitchenSink_set_offline_first_model', + 'KitchenSink', + foreignKeyColumn: 'l_KitchenSink_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_KitchenSink_set_offline_first_model', + 'Mounty', + foreignKeyColumn: 'f_Mounty_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_KitchenSink_list_offline_first_serdes', + 'KitchenSink', + foreignKeyColumn: 'l_KitchenSink_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_KitchenSink_list_offline_first_serdes', + 'Hat', + foreignKeyColumn: 'f_Hat_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_KitchenSink_set_offline_first_serdes', + 'KitchenSink', + foreignKeyColumn: 'l_KitchenSink_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_KitchenSink_set_offline_first_serdes', + 'Hat', + foreignKeyColumn: 'f_Hat_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertColumn('any_string', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('any_int', Column.integer, onTable: 'KitchenSink'), + InsertColumn('any_double', Column.Double, onTable: 'KitchenSink'), + InsertColumn('any_num', Column.num, onTable: 'KitchenSink'), + InsertColumn('any_date_time', Column.datetime, onTable: 'KitchenSink'), + InsertColumn('any_bool', Column.boolean, onTable: 'KitchenSink'), + InsertColumn('any_map', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('enum_from_index', Column.integer, onTable: 'KitchenSink'), + InsertColumn('any_list', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('any_set', Column.varchar, onTable: 'KitchenSink'), + InsertForeignKey( + 'KitchenSink', + 'Mounty', + foreignKeyColumn: 'offline_first_model_Mounty_brick_id', + onDeleteCascade: false, + onDeleteSetDefault: false, + ), + InsertForeignKey( + 'KitchenSink', + 'Hat', + foreignKeyColumn: 'offline_first_serdes_Hat_brick_id', + onDeleteCascade: false, + onDeleteSetDefault: false, + ), + InsertColumn('rest_annotation_name', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('rest_annotation_default_value', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('rest_annotation_nullable', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('rest_annotation_ignore', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('rest_annotation_ignore_to', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('rest_annotation_ignore_from', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('rest_annotation_from_generator', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('rest_annotation_to_generator', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('enum_from_string', Column.integer, onTable: 'KitchenSink'), + InsertColumn('sqlite_annotation_nullable', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('sqlite_annotation_default_value', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('sqlite_annotation_from_generator', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('sqlite_annotation_to_generator', Column.varchar, onTable: 'KitchenSink'), + InsertColumn('sqlite_annotation_unique', Column.varchar, onTable: 'KitchenSink', unique: true), + InsertColumn('custom column name', Column.varchar, onTable: 'KitchenSink'), + InsertForeignKey( + 'KitchenSink', + 'Mounty', + foreignKeyColumn: 'offline_first_where_Mounty_brick_id', + onDeleteCascade: false, + onDeleteSetDefault: false, + ), + InsertColumn('name', Column.varchar, onTable: 'Mounty'), + InsertColumn('email', Column.varchar, onTable: 'Mounty'), + InsertForeignKey( + 'Mounty', + 'Hat', + foreignKeyColumn: 'hat_Hat_brick_id', + onDeleteCascade: false, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_Horse_mounties', + 'Horse', + foreignKeyColumn: 'l_Horse_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertForeignKey( + '_brick_Horse_mounties', + 'Mounty', + foreignKeyColumn: 'f_Mounty_brick_id', + onDeleteCascade: true, + onDeleteSetDefault: false, + ), + InsertColumn('name', Column.varchar, onTable: 'Horse'), + CreateIndex( + columns: ['l_KitchenSink_brick_id', 'f_Mounty_brick_id'], + onTable: '_brick_KitchenSink_list_offline_first_model', + unique: true, + ), + CreateIndex( + columns: ['l_KitchenSink_brick_id', 'f_Mounty_brick_id'], + onTable: '_brick_KitchenSink_set_offline_first_model', + unique: true, + ), + CreateIndex( + columns: ['l_KitchenSink_brick_id', 'f_Hat_brick_id'], + onTable: '_brick_KitchenSink_list_offline_first_serdes', + unique: true, + ), + CreateIndex( + columns: ['l_KitchenSink_brick_id', 'f_Hat_brick_id'], + onTable: '_brick_KitchenSink_set_offline_first_serdes', + unique: true, + ), + CreateIndex( + columns: ['l_Horse_brick_id', 'f_Mounty_brick_id'], + onTable: '_brick_Horse_mounties', + unique: true, + ), +]; + +const List _migration_20240824214217_down = [ + DropTable('_brick_KitchenSink_list_offline_first_model'), + DropTable('_brick_KitchenSink_set_offline_first_model'), + DropTable('_brick_KitchenSink_list_offline_first_serdes'), + DropTable('_brick_KitchenSink_set_offline_first_serdes'), + DropTable('KitchenSink'), + DropTable('Mounty'), + DropTable('_brick_Horse_mounties'), + DropTable('Horse'), + DropColumn('l_KitchenSink_brick_id', onTable: '_brick_KitchenSink_list_offline_first_model'), + DropColumn('f_Mounty_brick_id', onTable: '_brick_KitchenSink_list_offline_first_model'), + DropColumn('l_KitchenSink_brick_id', onTable: '_brick_KitchenSink_set_offline_first_model'), + DropColumn('f_Mounty_brick_id', onTable: '_brick_KitchenSink_set_offline_first_model'), + DropColumn('l_KitchenSink_brick_id', onTable: '_brick_KitchenSink_list_offline_first_serdes'), + DropColumn('f_Hat_brick_id', onTable: '_brick_KitchenSink_list_offline_first_serdes'), + DropColumn('l_KitchenSink_brick_id', onTable: '_brick_KitchenSink_set_offline_first_serdes'), + DropColumn('f_Hat_brick_id', onTable: '_brick_KitchenSink_set_offline_first_serdes'), + DropColumn('any_string', onTable: 'KitchenSink'), + DropColumn('any_int', onTable: 'KitchenSink'), + DropColumn('any_double', onTable: 'KitchenSink'), + DropColumn('any_num', onTable: 'KitchenSink'), + DropColumn('any_date_time', onTable: 'KitchenSink'), + DropColumn('any_bool', onTable: 'KitchenSink'), + DropColumn('any_map', onTable: 'KitchenSink'), + DropColumn('enum_from_index', onTable: 'KitchenSink'), + DropColumn('any_list', onTable: 'KitchenSink'), + DropColumn('any_set', onTable: 'KitchenSink'), + DropColumn('offline_first_model_Mounty_brick_id', onTable: 'KitchenSink'), + DropColumn('offline_first_serdes_Hat_brick_id', onTable: 'KitchenSink'), + DropColumn('rest_annotation_name', onTable: 'KitchenSink'), + DropColumn('rest_annotation_default_value', onTable: 'KitchenSink'), + DropColumn('rest_annotation_nullable', onTable: 'KitchenSink'), + DropColumn('rest_annotation_ignore', onTable: 'KitchenSink'), + DropColumn('rest_annotation_ignore_to', onTable: 'KitchenSink'), + DropColumn('rest_annotation_ignore_from', onTable: 'KitchenSink'), + DropColumn('rest_annotation_from_generator', onTable: 'KitchenSink'), + DropColumn('rest_annotation_to_generator', onTable: 'KitchenSink'), + DropColumn('enum_from_string', onTable: 'KitchenSink'), + DropColumn('sqlite_annotation_nullable', onTable: 'KitchenSink'), + DropColumn('sqlite_annotation_default_value', onTable: 'KitchenSink'), + DropColumn('sqlite_annotation_from_generator', onTable: 'KitchenSink'), + DropColumn('sqlite_annotation_to_generator', onTable: 'KitchenSink'), + DropColumn('sqlite_annotation_unique', onTable: 'KitchenSink'), + DropColumn('custom column name', onTable: 'KitchenSink'), + DropColumn('offline_first_where_Mounty_brick_id', onTable: 'KitchenSink'), + DropColumn('name', onTable: 'Mounty'), + DropColumn('email', onTable: 'Mounty'), + DropColumn('hat_Hat_brick_id', onTable: 'Mounty'), + DropColumn('l_Horse_brick_id', onTable: '_brick_Horse_mounties'), + DropColumn('f_Mounty_brick_id', onTable: '_brick_Horse_mounties'), + DropColumn('name', onTable: 'Horse'), + DropIndex( + 'index__brick_KitchenSink_list_offline_first_model_on_l_KitchenSink_brick_id_f_Mounty_brick_id', + ), + DropIndex( + 'index__brick_KitchenSink_set_offline_first_model_on_l_KitchenSink_brick_id_f_Mounty_brick_id', + ), + DropIndex( + 'index__brick_KitchenSink_list_offline_first_serdes_on_l_KitchenSink_brick_id_f_Hat_brick_id', + ), + DropIndex( + 'index__brick_KitchenSink_set_offline_first_serdes_on_l_KitchenSink_brick_id_f_Hat_brick_id', + ), + DropIndex('index__brick_Horse_mounties_on_l_Horse_brick_id_f_Mounty_brick_id'), +]; + +// +// DO NOT EDIT BELOW THIS LINE +// + +@Migratable( + version: '20240824214217', + up: _migration_20240824214217_up, + down: _migration_20240824214217_down, +) +class Migration20240824214217 extends Migration { + const Migration20240824214217() + : super( + version: 20240824214217, + up: _migration_20240824214217_up, + down: _migration_20240824214217_down, + ); +} diff --git a/packages/brick_offline_first_with_supabase/example/lib/brick/db/schema.g.dart b/packages/brick_offline_first_with_supabase/example/lib/brick/db/schema.g.dart new file mode 100644 index 00000000..160fe22f --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/brick/db/schema.g.dart @@ -0,0 +1,153 @@ +// GENERATED CODE DO NOT EDIT +// This file should be version controlled +import 'package:brick_sqlite/db.dart'; +part '20240824214217.migration.dart'; + +/// All intelligently-generated migrations from all `@Migratable` classes on disk +final migrations = { + const Migration20240824214217(), +}; + +/// A consumable database structure including the latest generated migration. +final schema = Schema(20240824214217, generatorVersion: 1, tables: { + SchemaTable('_brick_KitchenSink_list_offline_first_model', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('l_KitchenSink_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'KitchenSink', + onDeleteCascade: true, + onDeleteSetDefault: false), + SchemaColumn('f_Mounty_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Mounty', + onDeleteCascade: true, + onDeleteSetDefault: false) + }, indices: { + SchemaIndex(columns: ['l_KitchenSink_brick_id', 'f_Mounty_brick_id'], unique: true) + }), + SchemaTable('_brick_KitchenSink_set_offline_first_model', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('l_KitchenSink_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'KitchenSink', + onDeleteCascade: true, + onDeleteSetDefault: false), + SchemaColumn('f_Mounty_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Mounty', + onDeleteCascade: true, + onDeleteSetDefault: false) + }, indices: { + SchemaIndex(columns: ['l_KitchenSink_brick_id', 'f_Mounty_brick_id'], unique: true) + }), + SchemaTable('_brick_KitchenSink_list_offline_first_serdes', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('l_KitchenSink_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'KitchenSink', + onDeleteCascade: true, + onDeleteSetDefault: false), + SchemaColumn('f_Hat_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Hat', + onDeleteCascade: true, + onDeleteSetDefault: false) + }, indices: { + SchemaIndex(columns: ['l_KitchenSink_brick_id', 'f_Hat_brick_id'], unique: true) + }), + SchemaTable('_brick_KitchenSink_set_offline_first_serdes', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('l_KitchenSink_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'KitchenSink', + onDeleteCascade: true, + onDeleteSetDefault: false), + SchemaColumn('f_Hat_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Hat', + onDeleteCascade: true, + onDeleteSetDefault: false) + }, indices: { + SchemaIndex(columns: ['l_KitchenSink_brick_id', 'f_Hat_brick_id'], unique: true) + }), + SchemaTable('KitchenSink', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('any_string', Column.varchar), + SchemaColumn('any_int', Column.integer), + SchemaColumn('any_double', Column.Double), + SchemaColumn('any_num', Column.num), + SchemaColumn('any_date_time', Column.datetime), + SchemaColumn('any_bool', Column.boolean), + SchemaColumn('any_map', Column.varchar), + SchemaColumn('enum_from_index', Column.integer), + SchemaColumn('any_list', Column.varchar), + SchemaColumn('any_set', Column.varchar), + SchemaColumn('offline_first_model_Mounty_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Mounty', + onDeleteCascade: false, + onDeleteSetDefault: false), + SchemaColumn('offline_first_serdes_Hat_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Hat', + onDeleteCascade: false, + onDeleteSetDefault: false), + SchemaColumn('rest_annotation_name', Column.varchar), + SchemaColumn('rest_annotation_default_value', Column.varchar), + SchemaColumn('rest_annotation_nullable', Column.varchar), + SchemaColumn('rest_annotation_ignore', Column.varchar), + SchemaColumn('rest_annotation_ignore_to', Column.varchar), + SchemaColumn('rest_annotation_ignore_from', Column.varchar), + SchemaColumn('rest_annotation_from_generator', Column.varchar), + SchemaColumn('rest_annotation_to_generator', Column.varchar), + SchemaColumn('enum_from_string', Column.integer), + SchemaColumn('sqlite_annotation_nullable', Column.varchar), + SchemaColumn('sqlite_annotation_default_value', Column.varchar), + SchemaColumn('sqlite_annotation_from_generator', Column.varchar), + SchemaColumn('sqlite_annotation_to_generator', Column.varchar), + SchemaColumn('sqlite_annotation_unique', Column.varchar, unique: true), + SchemaColumn('custom column name', Column.varchar), + SchemaColumn('offline_first_where_Mounty_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Mounty', + onDeleteCascade: false, + onDeleteSetDefault: false) + }, indices: {}), + SchemaTable('Mounty', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('name', Column.varchar), + SchemaColumn('email', Column.varchar), + SchemaColumn('hat_Hat_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Hat', + onDeleteCascade: false, + onDeleteSetDefault: false) + }, indices: {}), + SchemaTable('_brick_Horse_mounties', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('l_Horse_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Horse', + onDeleteCascade: true, + onDeleteSetDefault: false), + SchemaColumn('f_Mounty_brick_id', Column.integer, + isForeignKey: true, + foreignTableName: 'Mounty', + onDeleteCascade: true, + onDeleteSetDefault: false) + }, indices: { + SchemaIndex(columns: ['l_Horse_brick_id', 'f_Mounty_brick_id'], unique: true) + }), + SchemaTable('Horse', columns: { + SchemaColumn('_brick_id', Column.integer, + autoincrement: true, nullable: false, isPrimaryKey: true), + SchemaColumn('name', Column.varchar) + }, indices: {}) +}); diff --git a/packages/brick_offline_first_with_supabase/example/lib/main.dart b/packages/brick_offline_first_with_supabase/example/lib/main.dart new file mode 100644 index 00000000..97a2d975 --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/main.dart @@ -0,0 +1,56 @@ +// This file is named main for discovery on pub.dev, however, in a real-world +// application it would be lib/brick/repository.dart. + +import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart'; +import 'package:brick_sqlite/brick_sqlite.dart'; +import 'package:brick_sqlite/db.dart'; +import 'package:brick_sqlite/memory_cache_provider.dart'; +import 'package:brick_supabase/brick_supabase.dart'; +import 'package:sqflite/sqflite.dart' show databaseFactory; +import 'package:supabase/supabase.dart'; + +// Only relative imports are recognized in this nested package in VSCode. +// You should always use package imports in real-world code. +// ignore: always_use_package_imports +import 'brick/brick.g.dart'; + +class MyRepository extends OfflineFirstWithSupabaseRepository { + static late MyRepository? _singleton; + + MyRepository._({ + required super.supabaseProvider, + required super.sqliteProvider, + required super.migrations, + required super.offlineRequestQueue, + super.memoryCacheProvider, + }); + + factory MyRepository() => _singleton!; + + static void configure({ + required String supabaseUrl, + required String apiKey, + required Set migrations, + }) { + final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue( + databaseFactory: databaseFactory, + ); + + final provider = SupabaseProvider( + SupabaseClient(supabaseUrl, apiKey, httpClient: client), + modelDictionary: supabaseModelDictionary, + ); + + _singleton = MyRepository._( + supabaseProvider: provider, + sqliteProvider: SqliteProvider( + 'my_repository.sqlite', + databaseFactory: databaseFactory, + modelDictionary: sqliteModelDictionary, + ), + migrations: migrations, + offlineRequestQueue: queue, + memoryCacheProvider: MemoryCacheProvider(), + ); + } +} diff --git a/packages/brick_offline_first_with_supabase/example/lib/models/hat.dart b/packages/brick_offline_first_with_supabase/example/lib/models/hat.dart new file mode 100644 index 00000000..24255e2a --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/models/hat.dart @@ -0,0 +1,17 @@ +import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart'; + +enum Style { party, dance } + +class Hat extends OfflineFirstWithSupabaseModel { + final String? name; + + final String? flavour; + + final Style style; + + Hat({ + this.name, + this.flavour, + required this.style, + }); +} diff --git a/packages/brick_offline_first_with_supabase/example/lib/models/horse.model.dart b/packages/brick_offline_first_with_supabase/example/lib/models/horse.model.dart new file mode 100644 index 00000000..a809b754 --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/models/horse.model.dart @@ -0,0 +1,14 @@ +import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart'; +import 'package:brick_offline_first_with_supabase_example/models/mounty.model.dart'; + +@ConnectOfflineFirstWithSupabase() +class Horse extends OfflineFirstWithSupabaseModel { + final String? name; + + final List? mounties; + + Horse({ + this.name, + this.mounties, + }); +} diff --git a/packages/brick_offline_first_with_supabase/example/lib/models/kitchen_sink.model.dart b/packages/brick_offline_first_with_supabase/example/lib/models/kitchen_sink.model.dart new file mode 100644 index 00000000..c743f647 --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/models/kitchen_sink.model.dart @@ -0,0 +1,129 @@ +import 'package:brick_offline_first/brick_offline_first.dart'; +import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart'; +import 'package:brick_offline_first_with_supabase_example/models/mounty.model.dart'; +import 'package:brick_sqlite/brick_sqlite.dart'; +import 'package:brick_supabase/brick_supabase.dart'; + +@ConnectOfflineFirstWithSupabase() +class KitchenSink extends OfflineFirstWithSupabaseModel { + final String? anyString; + + final int? anyInt; + + final double? anyDouble; + + final num? anyNum; + + final DateTime? anyDateTime; + + final bool? anyBool; + + final Map? anyMap; + + final AnyEnum? enumFromIndex; + + final List? anyList; + + final Set? anySet; + + final Mounty? offlineFirstModel; + + final List? listOfflineFirstModel; + + final Set? setOfflineFirstModel; + + final Hat? offlineFirstSerdes; + + final List? listOfflineFirstSerdes; + + final Set? setOfflineFirstSerdes; + + @Supabase(name: 'restAnnotationOtherName') + final String? restAnnotationName; + + @Supabase(defaultValue: "'a default value'") + final String? restAnnotationDefaultValue; + + @Supabase(nullable: true) + final String? restAnnotationNullable; + + @Supabase(ignore: true) + final String? restAnnotationIgnore; + + @Supabase(ignoreTo: true) + final String? restAnnotationIgnoreTo; + + @Supabase(ignoreFrom: true) + final String? restAnnotationIgnoreFrom; + + @Supabase(fromGenerator: '%DATA_PROPERTY%.toString()') + final String? restAnnotationFromGenerator; + + @Supabase(toGenerator: '%INSTANCE_PROPERTY%.toString()') + final String? restAnnotationToGenerator; + + @Supabase(enumAsString: true) + final AnyEnum? enumFromString; + + @Sqlite(nullable: true) + final String? sqliteAnnotationNullable; + + @Sqlite(defaultValue: "'default value'") + final String? sqliteAnnotationDefaultValue; + + @Sqlite(fromGenerator: '%DATA_PROPERTY%.toString()') + final String? sqliteAnnotationFromGenerator; + + @Sqlite(toGenerator: '%INSTANCE_PROPERTY%.toString()') + final String? sqliteAnnotationToGenerator; + + @Sqlite(ignore: true) + final String? sqliteAnnotationIgnore; + + @Sqlite(unique: true) + final String? sqliteAnnotationUnique; + + @Sqlite(name: 'custom column name') + final String? sqliteAnnotationName; + + @OfflineFirst(where: {'email': "data['mounty_email']"}) + final Mounty? offlineFirstWhere; + + KitchenSink({ + this.anyString, + this.anyInt, + this.anyDouble, + this.anyNum, + this.anyDateTime, + this.anyBool, + this.anyMap, + this.enumFromIndex, + this.anyList, + this.anySet, + this.offlineFirstModel, + this.listOfflineFirstModel, + this.setOfflineFirstModel, + this.offlineFirstSerdes, + this.listOfflineFirstSerdes, + this.setOfflineFirstSerdes, + this.restAnnotationName, + this.restAnnotationDefaultValue, + this.restAnnotationNullable, + this.restAnnotationIgnore, + this.restAnnotationIgnoreTo, + this.restAnnotationIgnoreFrom, + this.restAnnotationFromGenerator, + this.restAnnotationToGenerator, + this.enumFromString, + this.sqliteAnnotationNullable, + this.sqliteAnnotationDefaultValue, + this.sqliteAnnotationFromGenerator, + this.sqliteAnnotationToGenerator, + this.sqliteAnnotationIgnore, + this.sqliteAnnotationUnique, + this.sqliteAnnotationName, + this.offlineFirstWhere, + }); +} + +enum AnyEnum { first, second } diff --git a/packages/brick_offline_first_with_supabase/example/lib/models/mounty.model.dart b/packages/brick_offline_first_with_supabase/example/lib/models/mounty.model.dart new file mode 100644 index 00000000..686f55d3 --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/lib/models/mounty.model.dart @@ -0,0 +1,17 @@ +import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart'; +import 'package:brick_offline_first_with_supabase_example/models/hat.dart'; + +@ConnectOfflineFirstWithSupabase() +class Mounty extends OfflineFirstWithSupabaseModel { + final String? name; + + final String? email; + + final Hat? hat; + + Mounty({ + this.name, + this.email, + this.hat, + }); +} diff --git a/packages/brick_offline_first_with_supabase/example/pubspec.yaml b/packages/brick_offline_first_with_supabase/example/pubspec.yaml new file mode 100644 index 00000000..aeecd3ae --- /dev/null +++ b/packages/brick_offline_first_with_supabase/example/pubspec.yaml @@ -0,0 +1,25 @@ +name: brick_offline_first_with_supabase_example + +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + sqflite: any + +dependency_overrides: + brick_offline_first_with_supabase: + path: ../ + brick_offline_first_with_rest: + path: ../../brick_offline_first_with_rest + brick_supabase: + path: ../../brick_supabase + brick_supabase_generators: + path: ../../brick_supabase_generators + +dev_dependencies: + build_runner: any + build_resolvers: any + brick_offline_first_with_supabase_build: + path: ../../brick_offline_first_with_supabase_build diff --git a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart index 96791a95..dfbf5971 100644 --- a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart +++ b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart @@ -1,6 +1,10 @@ import 'package:brick_offline_first/brick_offline_first.dart'; +import 'package:brick_offline_first_with_rest/offline_queue.dart'; import 'package:brick_offline_first_with_supabase/src/offline_first_with_supabase_model.dart'; -import 'package:brick_supabase/brick_supabase.dart' show SupabaseProvider; +import 'package:brick_supabase/brick_supabase.dart'; +import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; +import 'package:sqflite_common/sqlite_api.dart' show DatabaseFactory; /// Ensures the [remoteProvider] is a [SupabaseProvider]. /// @@ -8,6 +12,32 @@ import 'package:brick_supabase/brick_supabase.dart' show SupabaseProvider; /// <_RepositoryModel extends OfflineFirstWithSupabaseModel>, however, this causes a type bound /// error on runtime. The argument should be reintroduced with a future version of the /// compiler/analyzer. +/// +/// Care should be given to attach an offline queue to the provider using the static convenience +/// method [clientQueue]. +/// +/// ```dart +/// import 'package:sqflite/sqflite.dart' show databaseFactory; +/// import 'package:my_package/brick/brick.g.dart'; +/// +/// final (client, queue) = OfflineFirstWithSupabaseRepository.clientQueue( +/// databaseFactory: databaseFactory +/// ); +/// final provider = SupabaseProvider( +/// SupabaseClient(supabaseUrl, apiKey, httpClient: client), +/// modelDictionary: supabaseModelDictionary, +/// ); +/// +/// class MyRepository extends OfflineFirstWithSupabaseRepository { +/// MyRepository() : super( +/// supabaseProvider: provider, +/// sqliteProvider: SqliteProvider(databaseFactory), +/// memoryCacheProvider: MemoryCacheProvider(), +/// migrations: migrations, +/// offlineRequestQueue: queue, +/// ); +/// } +/// ``` abstract class OfflineFirstWithSupabaseRepository extends OfflineFirstRepository { /// The type declaration is important here for the rare circumstances that @@ -16,6 +46,10 @@ abstract class OfflineFirstWithSupabaseRepository // ignore: overridden_fields final SupabaseProvider remoteProvider; + /// In most cases, this queue can be generated using [clientQueue]. + @protected + final RestOfflineRequestQueue offlineRequestQueue; + OfflineFirstWithSupabaseRepository({ super.autoHydrate, super.loggerName, @@ -23,8 +57,46 @@ abstract class OfflineFirstWithSupabaseRepository required super.migrations, required SupabaseProvider supabaseProvider, required super.sqliteProvider, + required this.offlineRequestQueue, }) : remoteProvider = supabaseProvider, super( remoteProvider: supabaseProvider, ); + + @override + @mustCallSuper + Future initialize() async { + await super.initialize(); + offlineRequestQueue.start(); + } + + @override + @mustCallSuper + Future migrate() async { + await super.migrate(); + + // Migrate cached jobs schema + await offlineRequestQueue.client.requestManager.migrate(); + } + + /// This is a convenience method to create the basic offline client and queue. + /// The client is used to add offline capabilities to [SupabaseProvider]; + /// the queue is used to add offline to the repository. + static (RestOfflineQueueClient, RestOfflineRequestQueue) clientQueue({ + required DatabaseFactory databaseFactory, + http.Client? innerClient, + Duration? processingInterval, + bool? serialProcessing, + }) { + final client = RestOfflineQueueClient( + innerClient ?? http.Client(), + RestRequestSqliteCacheManager( + 'brick_offline_queue.sqlite', + databaseFactory: databaseFactory, + processingInterval: processingInterval, + serialProcessing: serialProcessing, + ), + ); + return (client, RestOfflineRequestQueue(client: client)); + } } diff --git a/packages/brick_offline_first_with_supabase/pubspec.yaml b/packages/brick_offline_first_with_supabase/pubspec.yaml index dcddf6ad..8ce77e37 100644 --- a/packages/brick_offline_first_with_supabase/pubspec.yaml +++ b/packages/brick_offline_first_with_supabase/pubspec.yaml @@ -8,13 +8,14 @@ repository: https://github.com/GetDutchie/brick version: 0.0.1 environment: - sdk: ">=2.18.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: brick_core: ^1.1.1 brick_offline_first: ">=3.0.0 <4.0.0" brick_supabase: ">=0.0.1 <2.0.0" brick_sqlite: ">=3.0.0 <4.0.0" + brick_offline_first_with_rest: ">=3.0.2 <4.0.0" logging: ">=1.0.0 <2.0.0" meta: ">=1.3.0 <2.0.0" sqflite_common: ">=2.0.0 <3.0.0" diff --git a/packages/brick_offline_first_with_supabase_build/build.yaml b/packages/brick_offline_first_with_supabase_build/build.yaml index c46a0959..3add6d2b 100644 --- a/packages/brick_offline_first_with_supabase_build/build.yaml +++ b/packages/brick_offline_first_with_supabase_build/build.yaml @@ -1,7 +1,7 @@ # Read about `build.yaml` at https://pub.dartlang.org/packages/build_config builders: brick_aggregate_builder: - import: "package:brick_offline_first_with_supabase_build/builder.dart" + import: "package:brick_offline_first_with_supabase_build/brick_offline_first_with_supabase_build.dart" builder_factories: ["offlineFirstAggregateBuilder"] build_extensions: { "$lib$": ["models_and_migrations.brick_aggregate.dart"] } @@ -18,7 +18,7 @@ builders: - brick/db/*.dart brick_adapters_builder: - import: "package:brick_offline_first_with_supabase_build/builder.dart" + import: "package:brick_offline_first_with_supabase_build/brick_offline_first_with_supabase_build.dart" builder_factories: ["offlineFirstAdaptersBuilder"] build_extensions: { ".model.dart": [".adapter_build_offline_first.dart"] } build_to: cache @@ -38,7 +38,7 @@ builders: - brick/db/*.dart brick_schema_builder: - import: "package:brick_offline_first_with_supabase_build/builder.dart" + import: "package:brick_offline_first_with_supabase_build/brick_offline_first_with_supabase_build.dart" builder_factories: ["offlineFirstSchemaBuilder"] build_extensions: { @@ -56,7 +56,7 @@ builders: ] brick_new_migration_builder: - import: "package:brick_offline_first_with_supabase_build/builder.dart" + import: "package:brick_offline_first_with_supabase_build/brick_offline_first_with_supabase_build.dart" builder_factories: ["offlineFirstNewMigrationBuilder"] build_extensions: { ".brick_aggregate.dart": [".brick_aggregate.migration_builder.dart"] } @@ -68,7 +68,7 @@ builders: ["source_gen|combining_builder", ":brick_aggregate_builder"] brick_model_dictionary_builder: - import: "package:brick_offline_first_with_supabase_build/builder.dart" + import: "package:brick_offline_first_with_supabase_build/brick_offline_first_with_supabase_build.dart" builder_factories: ["offlineFirstModelDictionaryBuilder"] build_extensions: { From 5dd75621bebc65b402eb8e503e76d7b294965314 Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Sat, 24 Aug 2024 15:53:17 -0700 Subject: [PATCH 2/2] remove sqflite dep --- .../brick_offline_first_with_supabase/example/pubspec.yaml | 3 --- .../offline_first_generator/test_specify_field_name.dart | 6 ++++-- .../test/offline_first_generator_test.dart | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/brick_offline_first_with_supabase/example/pubspec.yaml b/packages/brick_offline_first_with_supabase/example/pubspec.yaml index aeecd3ae..c5e085a2 100644 --- a/packages/brick_offline_first_with_supabase/example/pubspec.yaml +++ b/packages/brick_offline_first_with_supabase/example/pubspec.yaml @@ -5,9 +5,6 @@ publish_to: none environment: sdk: ">=3.0.0 <4.0.0" -dependencies: - sqflite: any - dependency_overrides: brick_offline_first_with_supabase: path: ../ diff --git a/packages/brick_offline_first_with_supabase_build/test/offline_first_generator/test_specify_field_name.dart b/packages/brick_offline_first_with_supabase_build/test/offline_first_generator/test_specify_field_name.dart index 5513cc18..def2f5a9 100644 --- a/packages/brick_offline_first_with_supabase_build/test/offline_first_generator/test_specify_field_name.dart +++ b/packages/brick_offline_first_with_supabase_build/test/offline_first_generator/test_specify_field_name.dart @@ -3,13 +3,15 @@ import 'package:brick_sqlite/brick_sqlite.dart'; import 'package:brick_supabase/brick_supabase.dart'; final output = r''' -Future _$SpecifyFieldNameFromSupabase(Map data, +Future _$SpecifyFieldNameFromSupabase( + Map data, {required SupabaseProvider provider, OfflineFirstRepository? repository}) async { return SpecifyFieldName(email: data['email_address'] as String?); } -Future> _$SpecifyFieldNameToSupabase(SpecifyFieldName instance, +Future> _$SpecifyFieldNameToSupabase( + SpecifyFieldName instance, {required SupabaseProvider provider, OfflineFirstRepository? repository}) async { return {'email_address': instance.email}; diff --git a/packages/brick_offline_first_with_supabase_build/test/offline_first_generator_test.dart b/packages/brick_offline_first_with_supabase_build/test/offline_first_generator_test.dart index 0ca22e59..abce7222 100644 --- a/packages/brick_offline_first_with_supabase_build/test/offline_first_generator_test.dart +++ b/packages/brick_offline_first_with_supabase_build/test/offline_first_generator_test.dart @@ -26,6 +26,7 @@ Future generateExpectation( }) async { final reader = await generateReader(filename); final generated = await (generator ?? _generator).generate(reader, MockBuildStep()); + print(generated); expect(generated.trim(), output.trim()); }