From d1d5ba37e7d7214d2e21b4ec0bd85895f20a596d Mon Sep 17 00:00:00 2001 From: Harsh Kumar <71206223+Harsh4114@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:42:24 +0530 Subject: [PATCH 1/8] Add Bloc-like offline sync architecture layer --- README.md | 83 +++++++++++++ lib/offline_sync_engine.dart | 13 ++ .../contracts/cloud_data_source.dart | 7 ++ .../contracts/in_memory_sync_log_store.dart | 43 +++++++ .../contracts/local_data_source.dart | 7 ++ .../bloc_like/contracts/sync_log_store.dart | 10 ++ .../bloc_like/controller/sync_controller.dart | 53 +++++++++ .../bloc_like/engine/offline_sync_engine.dart | 26 ++++ lib/src/bloc_like/events/sync_event.dart | 26 ++++ lib/src/bloc_like/models/sync_log.dart | 35 ++++++ .../bloc_like/repository/sync_repository.dart | 111 ++++++++++++++++++ lib/src/bloc_like/states/sync_state.dart | 20 ++++ test/bloc_like_architecture_test.dart | 86 ++++++++++++++ 13 files changed, 520 insertions(+) create mode 100644 lib/src/bloc_like/contracts/cloud_data_source.dart create mode 100644 lib/src/bloc_like/contracts/in_memory_sync_log_store.dart create mode 100644 lib/src/bloc_like/contracts/local_data_source.dart create mode 100644 lib/src/bloc_like/contracts/sync_log_store.dart create mode 100644 lib/src/bloc_like/controller/sync_controller.dart create mode 100644 lib/src/bloc_like/engine/offline_sync_engine.dart create mode 100644 lib/src/bloc_like/events/sync_event.dart create mode 100644 lib/src/bloc_like/models/sync_log.dart create mode 100644 lib/src/bloc_like/repository/sync_repository.dart create mode 100644 lib/src/bloc_like/states/sync_state.dart create mode 100644 test/bloc_like_architecture_test.dart diff --git a/README.md b/README.md index 66a9eaf..6515a78 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,89 @@ It keeps data synchronized across devices with deterministic conflict resolution └────────────────────────┘ └────────────────────────┘ ```` + +## Bloc-Like Architecture (New) + +The package now supports a Bloc-like mental model for teams that prefer event/state flows with minimal boilerplate. + +| Bloc Concept | Package Equivalent | +| --- | --- | +| Event | `SyncEvent` | +| State | `SyncState` | +| Bloc | `SyncController` | +| Repository | `SyncRepository` | +| Data source | `LocalDataSource` + `CloudDataSource` | +| Queue store | `SyncLogStore` | + +### Contracts + +```dart +abstract class LocalDataSource { + Future insert(T data); + Future update(T data); + Future delete(String id); + Future getById(String id); + Future> getAll(); +} + +abstract class CloudDataSource { + Future create(T data); + Future update(T data); + Future delete(String id); + Future fetch(String id); + Future> fetchAll(); +} +``` + +### Sync queue model + +```dart +enum SyncOperationType { create, update, delete } +enum SyncStatus { pending, syncing, synced, failed } +``` + +Each local write appends a `SyncLog`, then `StartSync` replays pending logs using latest-first ordering. + +### Events and states + +```dart +AddData, UpdateData, DeleteData, StartSync, RetryFailed +SyncInitial, SyncInProgress, SyncSuccess, SyncFailure +``` + +### Recommended wiring + +```dart +final repository = SyncRepository( + local: localDataSource, + cloud: cloudDataSource, + logStore: InMemorySyncLogStore(), + idResolver: (user) => user.id, +); + +final controller = SyncController(repository: repository); +final engine = OfflineSyncEngine(controller: controller); + +await engine.add(user); +await engine.sync(); +``` + +### Architecture diagram + +``` +UI + ↓ +OfflineSyncEngine + ↓ +SyncController + ↓ +SyncRepository + ↓ +LocalDataSource CloudDataSource + ↓ + SyncLogStore +``` + ## Installation ```bash diff --git a/lib/offline_sync_engine.dart b/lib/offline_sync_engine.dart index 59b8e97..cb37834 100644 --- a/lib/offline_sync_engine.dart +++ b/lib/offline_sync_engine.dart @@ -18,3 +18,16 @@ export 'src/sync/sync_manager.dart'; // Built-in Implementations (ready to use) export 'src/implementations/in_memory_database.dart'; export 'src/implementations/in_memory_cloud.dart'; + + +// Bloc-like architecture (vNext) +export 'src/bloc_like/contracts/local_data_source.dart'; +export 'src/bloc_like/contracts/cloud_data_source.dart'; +export 'src/bloc_like/contracts/sync_log_store.dart'; +export 'src/bloc_like/contracts/in_memory_sync_log_store.dart'; +export 'src/bloc_like/models/sync_log.dart'; +export 'src/bloc_like/events/sync_event.dart'; +export 'src/bloc_like/states/sync_state.dart'; +export 'src/bloc_like/repository/sync_repository.dart'; +export 'src/bloc_like/controller/sync_controller.dart'; +export 'src/bloc_like/engine/offline_sync_engine.dart'; diff --git a/lib/src/bloc_like/contracts/cloud_data_source.dart b/lib/src/bloc_like/contracts/cloud_data_source.dart new file mode 100644 index 0000000..2aff9cd --- /dev/null +++ b/lib/src/bloc_like/contracts/cloud_data_source.dart @@ -0,0 +1,7 @@ +abstract class CloudDataSource { + Future create(T data); + Future update(T data); + Future delete(String id); + Future fetch(String id); + Future> fetchAll(); +} diff --git a/lib/src/bloc_like/contracts/in_memory_sync_log_store.dart b/lib/src/bloc_like/contracts/in_memory_sync_log_store.dart new file mode 100644 index 0000000..5fcfdd9 --- /dev/null +++ b/lib/src/bloc_like/contracts/in_memory_sync_log_store.dart @@ -0,0 +1,43 @@ +import '../models/sync_log.dart'; +import 'sync_log_store.dart'; + +class InMemorySyncLogStore implements SyncLogStore { + final List _logs = []; + + @override + Future add(SyncLog log) async { + _logs.add(log); + } + + @override + Future> getFailedLogs() async { + return _logs.where((log) => log.status == SyncStatus.failed).toList(); + } + + @override + Future> getPendingLogs() async { + return _logs.where((log) => log.status == SyncStatus.pending).toList(); + } + + @override + Future markFailed(String logId) async { + _update(logId, SyncStatus.failed); + } + + @override + Future markSynced(String logId) async { + _update(logId, SyncStatus.synced); + } + + @override + Future markSyncing(String logId) async { + _update(logId, SyncStatus.syncing); + } + + void _update(String logId, SyncStatus status) { + final index = _logs.indexWhere((log) => log.id == logId); + if (index == -1) return; + + _logs[index] = _logs[index].copyWith(status: status); + } +} diff --git a/lib/src/bloc_like/contracts/local_data_source.dart b/lib/src/bloc_like/contracts/local_data_source.dart new file mode 100644 index 0000000..f50a083 --- /dev/null +++ b/lib/src/bloc_like/contracts/local_data_source.dart @@ -0,0 +1,7 @@ +abstract class LocalDataSource { + Future insert(T data); + Future update(T data); + Future delete(String id); + Future getById(String id); + Future> getAll(); +} diff --git a/lib/src/bloc_like/contracts/sync_log_store.dart b/lib/src/bloc_like/contracts/sync_log_store.dart new file mode 100644 index 0000000..89ab77b --- /dev/null +++ b/lib/src/bloc_like/contracts/sync_log_store.dart @@ -0,0 +1,10 @@ +import '../models/sync_log.dart'; + +abstract class SyncLogStore { + Future add(SyncLog log); + Future> getPendingLogs(); + Future> getFailedLogs(); + Future markSyncing(String logId); + Future markSynced(String logId); + Future markFailed(String logId); +} diff --git a/lib/src/bloc_like/controller/sync_controller.dart b/lib/src/bloc_like/controller/sync_controller.dart new file mode 100644 index 0000000..2eb44c0 --- /dev/null +++ b/lib/src/bloc_like/controller/sync_controller.dart @@ -0,0 +1,53 @@ +import 'dart:async'; + +import '../events/sync_event.dart'; +import '../repository/sync_repository.dart'; +import '../states/sync_state.dart'; + +class SyncController { + final SyncRepository repository; + final StreamController _states = + StreamController.broadcast(); + + SyncState _current = const SyncInitial(); + + SyncController({required this.repository}) { + _states.add(_current); + } + + Stream get states => _states.stream; + + SyncState get currentState => _current; + + Future handle(SyncEvent event) async { + _emit(const SyncInProgress()); + + try { + if (event is AddData) { + await repository.add(event.data); + } else if (event is UpdateData) { + await repository.update(event.data); + } else if (event is DeleteData) { + await repository.delete(event.id); + } else if (event is StartSync) { + await repository.syncPending(); + } else if (event is RetryFailed) { + await repository.retryFailed(); + } + + _emit(const SyncSuccess()); + } catch (e) { + _emit(SyncFailure(e.toString())); + rethrow; + } + } + + void _emit(SyncState state) { + _current = state; + _states.add(state); + } + + Future dispose() async { + await _states.close(); + } +} diff --git a/lib/src/bloc_like/engine/offline_sync_engine.dart b/lib/src/bloc_like/engine/offline_sync_engine.dart new file mode 100644 index 0000000..1b4200e --- /dev/null +++ b/lib/src/bloc_like/engine/offline_sync_engine.dart @@ -0,0 +1,26 @@ +import '../controller/sync_controller.dart'; +import '../events/sync_event.dart'; +import '../states/sync_state.dart'; + +class OfflineSyncEngine { + final SyncController _controller; + + OfflineSyncEngine({required SyncController controller}) + : _controller = controller; + + Stream get states => _controller.states; + + SyncState get currentState => _controller.currentState; + + Future add(T data) => _controller.handle(AddData(data)); + + Future update(T data) => _controller.handle(UpdateData(data)); + + Future delete(String id) => _controller.handle(DeleteData(id)); + + Future sync() => _controller.handle(const StartSync()); + + Future retryFailed() => _controller.handle(const RetryFailed()); + + Future dispose() => _controller.dispose(); +} diff --git a/lib/src/bloc_like/events/sync_event.dart b/lib/src/bloc_like/events/sync_event.dart new file mode 100644 index 0000000..7ae9238 --- /dev/null +++ b/lib/src/bloc_like/events/sync_event.dart @@ -0,0 +1,26 @@ +abstract class SyncEvent { + const SyncEvent(); +} + +class AddData extends SyncEvent { + final T data; + const AddData(this.data); +} + +class UpdateData extends SyncEvent { + final T data; + const UpdateData(this.data); +} + +class DeleteData extends SyncEvent { + final String id; + const DeleteData(this.id); +} + +class StartSync extends SyncEvent { + const StartSync(); +} + +class RetryFailed extends SyncEvent { + const RetryFailed(); +} diff --git a/lib/src/bloc_like/models/sync_log.dart b/lib/src/bloc_like/models/sync_log.dart new file mode 100644 index 0000000..913d105 --- /dev/null +++ b/lib/src/bloc_like/models/sync_log.dart @@ -0,0 +1,35 @@ +enum SyncOperationType { create, update, delete } + +enum SyncStatus { pending, syncing, synced, failed } + +class SyncLog { + final String id; + final String entityId; + final SyncOperationType operation; + final DateTime timestamp; + final SyncStatus status; + + const SyncLog({ + required this.id, + required this.entityId, + required this.operation, + required this.timestamp, + required this.status, + }); + + SyncLog copyWith({ + String? id, + String? entityId, + SyncOperationType? operation, + DateTime? timestamp, + SyncStatus? status, + }) { + return SyncLog( + id: id ?? this.id, + entityId: entityId ?? this.entityId, + operation: operation ?? this.operation, + timestamp: timestamp ?? this.timestamp, + status: status ?? this.status, + ); + } +} diff --git a/lib/src/bloc_like/repository/sync_repository.dart b/lib/src/bloc_like/repository/sync_repository.dart new file mode 100644 index 0000000..985e692 --- /dev/null +++ b/lib/src/bloc_like/repository/sync_repository.dart @@ -0,0 +1,111 @@ +import '../contracts/cloud_data_source.dart'; +import '../contracts/local_data_source.dart'; +import '../contracts/sync_log_store.dart'; +import '../models/sync_log.dart'; + +typedef EntityIdResolver = String Function(T value); + +class SyncRepository { + final LocalDataSource local; + final CloudDataSource cloud; + final SyncLogStore logStore; + final EntityIdResolver idResolver; + + SyncRepository({ + required this.local, + required this.cloud, + required this.logStore, + required this.idResolver, + }); + + Future add(T data) async { + final id = idResolver(data); + await local.insert(data); + await logStore.add( + SyncLog( + id: _logId(), + entityId: id, + operation: SyncOperationType.create, + timestamp: DateTime.now(), + status: SyncStatus.pending, + ), + ); + } + + Future update(T data) async { + final id = idResolver(data); + await local.update(data); + await logStore.add( + SyncLog( + id: _logId(), + entityId: id, + operation: SyncOperationType.update, + timestamp: DateTime.now(), + status: SyncStatus.pending, + ), + ); + } + + Future delete(String id) async { + await local.delete(id); + await logStore.add( + SyncLog( + id: _logId(), + entityId: id, + operation: SyncOperationType.delete, + timestamp: DateTime.now(), + status: SyncStatus.pending, + ), + ); + } + + Future syncPending() async { + final logs = await logStore.getPendingLogs(); + await _syncLogs(logs); + } + + Future retryFailed() async { + final logs = await logStore.getFailedLogs(); + await _syncLogs(logs); + } + + Future _syncLogs(List logs) async { + final sorted = [...logs] + ..sort((a, b) => b.timestamp.compareTo(a.timestamp)); + + for (final log in sorted) { + try { + await logStore.markSyncing(log.id); + await _execute(log); + await logStore.markSynced(log.id); + } catch (_) { + await logStore.markFailed(log.id); + } + } + } + + Future _execute(SyncLog log) async { + switch (log.operation) { + case SyncOperationType.create: + final data = await local.getById(log.entityId); + if (data != null) { + await cloud.create(data); + } + return; + case SyncOperationType.update: + final data = await local.getById(log.entityId); + if (data != null) { + await cloud.update(data); + } + return; + case SyncOperationType.delete: + await cloud.delete(log.entityId); + return; + } + } + + String _logId() { + final micros = DateTime.now().microsecondsSinceEpoch; + return 'sync_log_$micros'; + } +} diff --git a/lib/src/bloc_like/states/sync_state.dart b/lib/src/bloc_like/states/sync_state.dart new file mode 100644 index 0000000..3f85e63 --- /dev/null +++ b/lib/src/bloc_like/states/sync_state.dart @@ -0,0 +1,20 @@ +abstract class SyncState { + const SyncState(); +} + +class SyncInitial extends SyncState { + const SyncInitial(); +} + +class SyncInProgress extends SyncState { + const SyncInProgress(); +} + +class SyncSuccess extends SyncState { + const SyncSuccess(); +} + +class SyncFailure extends SyncState { + final String message; + const SyncFailure(this.message); +} diff --git a/test/bloc_like_architecture_test.dart b/test/bloc_like_architecture_test.dart new file mode 100644 index 0000000..8861b25 --- /dev/null +++ b/test/bloc_like_architecture_test.dart @@ -0,0 +1,86 @@ +import 'package:offline_sync_engine/offline_sync_engine.dart'; +import 'package:test/test.dart'; + +class _User { + final String id; + final String name; + + const _User(this.id, this.name); +} + +class _InMemoryLocalUsers implements LocalDataSource<_User> { + final Map _users = {}; + + @override + Future delete(String id) async { + _users.remove(id); + } + + @override + Future> getAll() async => _users.values.toList(); + + @override + Future<_User?> getById(String id) async => _users[id]; + + @override + Future insert(_User data) async { + _users[data.id] = data; + } + + @override + Future update(_User data) async { + _users[data.id] = data; + } +} + +class _InMemoryCloudUsers implements CloudDataSource<_User> { + final Map _users = {}; + + @override + Future create(_User data) async { + _users[data.id] = data; + } + + @override + Future delete(String id) async { + _users.remove(id); + } + + @override + Future<_User?> fetch(String id) async => _users[id]; + + @override + Future> fetchAll() async => _users.values.toList(); + + @override + Future update(_User data) async { + _users[data.id] = data; + } +} + +void main() { + test('offline sync engine handles add + sync with bloc-like controller', () async { + final local = _InMemoryLocalUsers(); + final cloud = _InMemoryCloudUsers(); + + final repository = SyncRepository<_User>( + local: local, + cloud: cloud, + logStore: InMemorySyncLogStore(), + idResolver: (user) => user.id, + ); + + final controller = SyncController<_User>(repository: repository); + final engine = OfflineSyncEngine<_User>(controller: controller); + + await engine.add(const _User('1', 'Asha')); + await engine.sync(); + + final remote = await cloud.fetch('1'); + + expect(remote?.name, 'Asha'); + expect(engine.currentState, isA()); + + await engine.dispose(); + }); +} From d032f9e55f6cb44cba4205d3b035851cb9c80f3c Mon Sep 17 00:00:00 2001 From: Harsh Kumar <71206223+Harsh4114@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:50:50 +0530 Subject: [PATCH 2/8] Update lib/src/bloc_like/repository/sync_repository.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/src/bloc_like/repository/sync_repository.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/src/bloc_like/repository/sync_repository.dart b/lib/src/bloc_like/repository/sync_repository.dart index 985e692..21064a9 100644 --- a/lib/src/bloc_like/repository/sync_repository.dart +++ b/lib/src/bloc_like/repository/sync_repository.dart @@ -47,11 +47,16 @@ class SyncRepository { } Future delete(String id) async { - await local.delete(id); + final trimmedId = id.trim(); + if (trimmedId.isEmpty) { + throw ArgumentError.value(id, 'id', 'Identifier must not be empty or whitespace'); + } + + await local.delete(trimmedId); await logStore.add( SyncLog( id: _logId(), - entityId: id, + entityId: trimmedId, operation: SyncOperationType.delete, timestamp: DateTime.now(), status: SyncStatus.pending, From 2f41bef8e4df4bcf79eac68a010a22a1ae073aa0 Mon Sep 17 00:00:00 2001 From: Harsh Kumar <71206223+Harsh4114@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:51:27 +0530 Subject: [PATCH 3/8] Update lib/src/bloc_like/repository/sync_repository.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/src/bloc_like/repository/sync_repository.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/bloc_like/repository/sync_repository.dart b/lib/src/bloc_like/repository/sync_repository.dart index 21064a9..f8e13da 100644 --- a/lib/src/bloc_like/repository/sync_repository.dart +++ b/lib/src/bloc_like/repository/sync_repository.dart @@ -93,9 +93,12 @@ class SyncRepository { switch (log.operation) { case SyncOperationType.create: final data = await local.getById(log.entityId); - if (data != null) { - await cloud.create(data); + if (data == null) { + throw StateError( + 'Local data not found for create operation on entityId ${log.entityId}', + ); } + await cloud.create(data); return; case SyncOperationType.update: final data = await local.getById(log.entityId); From 5977aca1b6c8a9d41f07d5da25675036c9fa6d2e Mon Sep 17 00:00:00 2001 From: Harsh Kumar <71206223+Harsh4114@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:51:41 +0530 Subject: [PATCH 4/8] Update lib/src/bloc_like/repository/sync_repository.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/src/bloc_like/repository/sync_repository.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/bloc_like/repository/sync_repository.dart b/lib/src/bloc_like/repository/sync_repository.dart index f8e13da..fc7963e 100644 --- a/lib/src/bloc_like/repository/sync_repository.dart +++ b/lib/src/bloc_like/repository/sync_repository.dart @@ -102,9 +102,11 @@ class SyncRepository { return; case SyncOperationType.update: final data = await local.getById(log.entityId); - if (data != null) { - await cloud.update(data); + if (data == null) { + // Treat missing local data as an error so the log is marked as failed. + throw StateError('Cannot update: local entity not found for id ${log.entityId}'); } + await cloud.update(data); return; case SyncOperationType.delete: await cloud.delete(log.entityId); From 40c160ba9ac6152f8411126cf84e6b4591786e31 Mon Sep 17 00:00:00 2001 From: Harsh Kumar <71206223+Harsh4114@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:51:59 +0530 Subject: [PATCH 5/8] Update lib/src/bloc_like/repository/sync_repository.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../bloc_like/repository/sync_repository.dart | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/lib/src/bloc_like/repository/sync_repository.dart b/lib/src/bloc_like/repository/sync_repository.dart index fc7963e..f9fe877 100644 --- a/lib/src/bloc_like/repository/sync_repository.dart +++ b/lib/src/bloc_like/repository/sync_repository.dart @@ -26,42 +26,68 @@ class SyncRepository { id: _logId(), entityId: id, operation: SyncOperationType.create, - timestamp: DateTime.now(), - status: SyncStatus.pending, - ), - ); + try { + await logStore.add( + SyncLog( + id: _logId(), + entityId: id, + operation: SyncOperationType.create, + timestamp: DateTime.now(), + status: SyncStatus.pending, + ), + ); + } catch (_) { + // Roll back local insert if logging the sync intent fails. + await local.delete(id); + rethrow; + } } Future update(T data) async { final id = idResolver(data); + // Capture previous state to allow rollback if logging fails. + final previous = await local.getById(id); await local.update(data); - await logStore.add( - SyncLog( - id: _logId(), - entityId: id, - operation: SyncOperationType.update, - timestamp: DateTime.now(), - status: SyncStatus.pending, - ), - ); + try { + await logStore.add( + SyncLog( + id: _logId(), + entityId: id, + operation: SyncOperationType.update, + timestamp: DateTime.now(), + status: SyncStatus.pending, + ), + ); + } catch (_) { + // Attempt to restore previous state if available. + if (previous != null) { + await local.update(previous); + } + rethrow; + } } Future delete(String id) async { - final trimmedId = id.trim(); - if (trimmedId.isEmpty) { - throw ArgumentError.value(id, 'id', 'Identifier must not be empty or whitespace'); + // Capture the entity before deletion to allow rollback if logging fails. + final existing = await local.getById(id); + await local.delete(id); + try { + await logStore.add( + SyncLog( + id: _logId(), + entityId: id, + operation: SyncOperationType.delete, + timestamp: DateTime.now(), + status: SyncStatus.pending, + ), + ); + } catch (_) { + // Attempt to restore the deleted entity if it previously existed. + if (existing != null) { + await local.insert(existing); + } + rethrow; } - - await local.delete(trimmedId); - await logStore.add( - SyncLog( - id: _logId(), - entityId: trimmedId, - operation: SyncOperationType.delete, - timestamp: DateTime.now(), - status: SyncStatus.pending, - ), - ); } Future syncPending() async { From 3a7aaf387a43eb6004d6e78f14d8d4da71cae48e Mon Sep 17 00:00:00 2001 From: Harsh Kumar <71206223+Harsh4114@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:52:49 +0530 Subject: [PATCH 6/8] Update lib/src/bloc_like/repository/sync_repository.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/src/bloc_like/repository/sync_repository.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/bloc_like/repository/sync_repository.dart b/lib/src/bloc_like/repository/sync_repository.dart index f9fe877..b611497 100644 --- a/lib/src/bloc_like/repository/sync_repository.dart +++ b/lib/src/bloc_like/repository/sync_repository.dart @@ -109,8 +109,9 @@ class SyncRepository { await logStore.markSyncing(log.id); await _execute(log); await logStore.markSynced(log.id); - } catch (_) { + } catch (e, s) { await logStore.markFailed(log.id); + rethrow; } } } From 43bbd528842e26194de41080885d0e644fc09a30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:23:32 +0000 Subject: [PATCH 7/8] Initial plan From 645e566a0f2b62c8cecc56f7eac26d2d84b38ef3 Mon Sep 17 00:00:00 2001 From: Harsh Kumar <71206223+Harsh4114@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:53:32 +0530 Subject: [PATCH 8/8] Update lib/src/bloc_like/controller/sync_controller.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/src/bloc_like/controller/sync_controller.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/bloc_like/controller/sync_controller.dart b/lib/src/bloc_like/controller/sync_controller.dart index 2eb44c0..87d97c2 100644 --- a/lib/src/bloc_like/controller/sync_controller.dart +++ b/lib/src/bloc_like/controller/sync_controller.dart @@ -33,6 +33,9 @@ class SyncController { await repository.syncPending(); } else if (event is RetryFailed) { await repository.retryFailed(); + } else { + throw UnsupportedError( + 'Unsupported SyncEvent type: ${event.runtimeType}'); } _emit(const SyncSuccess());