diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 100ef65..bd43c0f 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -58,3 +58,5 @@ jobs: run: dart pub global run coverage:format_coverage --lcov --in=coverage.json --out=lcov.info --report-on=lib - uses: codecov/codecov-action@v3.1.1 + with: + fail_ci_if_error: false \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..7ddfc9e 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,5 +1,11 @@ + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index a207b4f..4782460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.1.0-dev.0 - TODO + +- Refactor `executeUpdate`: + - Convert to named parameters. + - Moved to `TransactionallyStorage`. + - Update docs. + - `Transformer` can return a `Future`. + ## 2.0.0 - Jun 1, 2022 - Update dependencies diff --git a/lib/rx_storage.dart b/lib/rx_storage.dart index a7650ba..8694f68 100644 --- a/lib/rx_storage.dart +++ b/lib/rx_storage.dart @@ -6,6 +6,7 @@ library rx_storage; export 'src/impl/real_storage.dart'; export 'src/interface/rx_storage.dart'; export 'src/interface/storage.dart'; +export 'src/interface/transactionally_storage.dart'; export 'src/logger/default_logger.dart'; export 'src/logger/empty_logger.dart'; export 'src/logger/event.dart'; diff --git a/lib/src/impl/real_storage.dart b/lib/src/impl/real_storage.dart index 1d8c917..42084f1 100644 --- a/lib/src/impl/real_storage.dart +++ b/lib/src/impl/real_storage.dart @@ -8,6 +8,7 @@ import '../async/async_memoizer.dart'; import '../async/async_queue.dart'; import '../interface/rx_storage.dart'; import '../interface/storage.dart'; +import '../interface/transactionally_storage.dart'; import '../logger/event.dart'; import '../logger/logger.dart'; import '../model/error.dart'; @@ -332,13 +333,13 @@ class RealRxStorage executeUpdate( - Key key, - Decoder decoder, - Transformer transformer, - Encoder encoder, [ + Future executeUpdate({ + required Key key, + required Decoder decoder, + required Transformer transformer, + required Encoder encoder, Options? options, - ]) { + }) { assert(_debugAssertNotDisposed()); return _enqueueWritingTask( @@ -346,8 +347,11 @@ class RealRxStorage(key, decoder, options); + // Modify - final transformed = transformer(value); + final futureOr = transformer(value); + final transformed = futureOr is Future ? await futureOr : futureOr; + // Write await _writeWithoutSynchronization(key, transformed, encoder, options); }, diff --git a/lib/src/interface/rx_storage.dart b/lib/src/interface/rx_storage.dart index 0bb154d..1050baa 100644 --- a/lib/src/interface/rx_storage.dart +++ b/lib/src/interface/rx_storage.dart @@ -1,17 +1,13 @@ import 'dart:async'; -import 'package:meta/meta.dart'; - import '../impl/real_storage.dart'; import '../logger/logger.dart'; import 'storage.dart'; - -/// Transform a value to another value with same type. -typedef Transformer = T Function(T); +import 'transactionally_storage.dart'; /// Get [Stream]s by key from persistent storage. abstract class RxStorage - implements Storage { + implements TransactionallyStorage { /// Constructs a [RxStorage] by wrapping a [Storage]. factory RxStorage( FutureOr> storageOrFuture, [ @@ -24,21 +20,6 @@ abstract class RxStorage onDispose, ); - /// `Read–modify–write`. - /// - /// Read value by [key], then decode with [decoder], - /// then transform by [transformer], - /// then encode with [encoder] - /// and finally save decoded value to persistent storage. - @experimental - Future executeUpdate( - Key key, - Decoder decoder, - Transformer transformer, - Encoder encoder, [ - Options? options, - ]); - /// Return [Stream] that will emit value read from persistent storage. /// It will automatic emit value when value associated with key was changed. Stream observe(Key key, Decoder decoder, diff --git a/lib/src/interface/transactionally_storage.dart b/lib/src/interface/transactionally_storage.dart new file mode 100644 index 0000000..cc7c9a9 --- /dev/null +++ b/lib/src/interface/transactionally_storage.dart @@ -0,0 +1,35 @@ +import 'package:meta/meta.dart'; +import 'dart:async'; + +import 'storage.dart'; + +/// Transform a value to another value of the same type. +typedef Transformer = FutureOr Function(T); + +/// A persistent store for simple data. +/// Data is persisted to disk asynchronously and transactionally. +abstract class TransactionallyStorage + implements Storage { + /// `Read–modify–write`. + /// + /// Updates the data transactionally in an atomic read-modify-write operation. + /// All operations are serialized, and the [transformer] can perform asynchronous computations + /// such as RPCs, database queries, API calls, etc. + /// + /// The future completes when the data has been persisted durably to disk. + /// If the transform or write to disk fails, the transaction is aborted and the error is rethrown. + /// + /// When calling this, logic will be executed in the following order: + /// - Read raw value by [key], then decode it with [decoder]. + /// - Transform the decoded value with [transformer]. + /// - Encode the transformed value with [encoder]. + /// - Finally, save encoded value to persistent storage. + @experimental + Future executeUpdate({ + required Key key, + required Decoder decoder, + required Transformer transformer, + required Encoder encoder, + Options? options, + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index 161dabe..90cd037 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: rx_storage description: Reactive storage for Dart/Flutter. RxDart Storage for Dart/Flutter. -version: 2.0.0 +version: 2.1.0-dev.0 homepage: https://github.com/Flutter-Dart-Open-Source/rx_storage.git repository: https://github.com/Flutter-Dart-Open-Source/rx_storage.git issue_tracker: https://github.com/Flutter-Dart-Open-Source/rx_storage/issues diff --git a/test/storage/streams_test.dart b/test/storage/streams_test.dart index 71e833e..49f7917 100644 --- a/test/storage/streams_test.dart +++ b/test/storage/streams_test.dart @@ -33,9 +33,9 @@ void main() { ); }); - tearDown(() { + tearDown(() async { try { - rxStorage.dispose(); + await rxStorage.dispose(); } catch (_) {} }); @@ -379,10 +379,10 @@ void main() { ); await rxStorage.executeUpdate( - 'String', - (s) => s as String?, // read - (s) => 'Transformed $s', // modify, - (s) => s, // write + key: 'String', + decoder: (s) => s as String?, // read + transformer: (s) => 'Transformed $s', // modify, + encoder: (s) => s, // write ); expect( @@ -403,11 +403,13 @@ void main() { ); await rxStorage.executeUpdate( - 'User', - jsonStringToUser, // read - (user) => user?.withName('Transformed ${user.name}'), + key: 'User', + // read + decoder: jsonStringToUser, // modify - userToJsonString, // write + transformer: (user) => user?.withName('Transformed ${user.name}'), + // write + encoder: userToJsonString, ); expect( @@ -422,20 +424,22 @@ void main() { expect( rxStorage.executeUpdate( - 'String', - (s) => s as String?, // read - (s) => 'Transformed $s', // modify, - (s) => s, // write + key: 'String', + decoder: (s) => s as String?, // read + transformer: (s) => 'Transformed $s', // modify, + encoder: (s) => s, // write ), throwsException, ); expect( rxStorage.executeUpdate( - 'User', - jsonStringToUser, // read - (user) => user?.withName('Transformed ${user.name}'), + key: 'User', + // read + decoder: jsonStringToUser, // modify - userToJsonString, // write + transformer: (user) => user?.withName('Transformed ${user.name}'), + // write + encoder: userToJsonString, ), throwsException, );