smart_repository 0.2.0
smart_repository: ^0.2.0 copied to clipboard
Policy-driven coordination for remote and local repository data.
smart_repository #
Policy-driven coordination for remote and local repository data. The package has no runtime dependencies and knows nothing about HTTP clients, databases, or state-management frameworks.
Quick start #
import 'package:smart_repository/smart_repository.dart';
final repository = SmartRepository<User>(
remote: api.getUser,
local: storage.getUser,
saveLocal: storage.saveUser,
deleteLocal: storage.deleteUser,
localTimestamp: storage.getUserStoredAt,
config: const SmartRepositoryConfig(
defaultPolicy: RepositoryPolicy.networkFirst,
maxAge: Duration(minutes: 15),
),
);
final result = await repository.get();
switch (result) {
case RepositorySuccess(:final data, :final source, :final isStale):
print('$data from $source (stale: $isStale)');
case RepositoryFailure(:final error):
print(error);
}
Callbacks are normalized internally. Larger projects may instead implement
ReadDataSource<T>, WriteDataSource<T>, and DeleteDataSource and pass the
corresponding *Source arguments.
Policies #
| Policy | Contract |
|---|---|
networkOnly |
Read remote; optionally persist; fail on remote error. |
cacheOnly |
Read local only; a miss returns RepositoryCacheMissException. |
cacheFirst |
Return usable cache; stale/missing cache triggers remote; stale cache can be fallback. |
networkFirst |
Read remote; allowed remote failures fall back to stale local data. |
staleWhileRevalidate |
Return cache immediately; stale/unknown cache refreshes in background. |
cacheAndNetwork |
Return any cache immediately and always refresh in background. |
Unknown freshness is usable for cacheFirst, but refreshable for
staleWhileRevalidate. Invalidation marks cached data stale.
Streams #
watch() replays current state to every new subscriber. It emits repository
events only; it is not a state-management framework.
For stale-while-revalidate with stale cache, order is:
RepositoryLoading
RepositoryData(local)
RepositoryRefreshing(local)
RepositoryData(remote)
The operation result and stream state are separate models. To avoid a Dart
name collision, one-shot errors use RepositoryFailure; stream errors use
RepositoryFailureState.
Errors and fallback #
Errors remain generic. mapError can translate transport-specific exceptions,
then fallbackWhen decides whether local fallback is safe. This prevents cases
such as silently serving a cached user after an authorization failure.
final repository = SmartRepository<User>(
remote: api.getUser,
local: storage.getUser,
mapError: (error, stackTrace) => parseApiError(error),
fallbackWhen: (error) => error is OfflineFailure,
);
Concurrent remote reads are deduplicated by default. refresh(force: true)
bypasses an in-flight request. Call dispose() when repository lifecycle ends.
Keyed repositories #
SmartRepositoryFamily<K, T> lazily owns one independent repository per key.
Requests for the same key deduplicate; different keys run independently.
final users = SmartRepositoryFamily<int, User>(
remote: (id) => api.getUser(id),
local: (id) => storage.getUser(id),
saveLocal: (id, user) => storage.saveUser(id, user),
deleteLocal: storage.deleteUser,
);
final result = await users.get(42, policy: RepositoryPolicy.cacheFirst);
final states = users.watch(42);
await users.remove(42); // Dispose and forget only key 42.
await users.dispose(); // Dispose every active key.
Family retains created keys until remove(key) or dispose() is called.
Persistent data is untouched by remove; use clear(key) to delete it.
Observability #
Extend RepositoryObserver<T> to connect logging, metrics, tracing, Sentry, or
analytics without package dependencies. Hooks cover reads, local hits/misses,
remote requests, fallback, persistence, invalidation, and clearing.
final repository = SmartRepository<User>(
remote: api.getUser,
observers: [RepositoryMetrics()],
);
Observers used by a family receive repositoryKey. Observer exceptions are
isolated and never affect repository results.
Scope #
Version 0.2 focuses on single-value, basic-list, and keyed reads. Pagination, mutations, and cancellation remain future additions.
Flutter example #
Full cross-platform demo lives in example/. No backend needed:
cd example
flutter pub get
flutter run