read method
Reads a single entity by its ID from the primary local adapter. Reads a single entity by its ID from the primary local adapter.
Soft-delete tombstones (isDeleted: true) are treated as absent unless
includeDeleted is true — a deleted entity should be gone from the
app's perspective while the tombstone still syncs underneath.
The withRelated parameter allows eager loading of related entities.
Implementation
Future<T?> read(String id, {String? userId, List<String> withRelated = const [], bool includeDeleted = false}) async {
_ensureInitialized();
// Create cache key for entity existence
final cacheKey = '${T.toString()}:$id:${userId ?? ''}';
// The existence cache holds only positive entries (see below), and a
// positive hit still requires the fetch — so no read shortcut exists here.
var entity = await localAdapter.read(id, userId: userId);
if (!includeDeleted && (entity?.isDeleted ?? false)) entity = null;
// Only cache POSITIVE existence. Caching a negative (absent) result caused
// stale reads in offline-first/realtime scenarios: once an entity was cached
// as "does not exist", data that later arrived via sync or a realtime push
// was never observed, breaking delete/conflict resolution and reactive
// reads. A positive entry is safe because reads always re-fetch the row.
if (entity != null) {
_cacheCoordinator.entityExistenceCache[cacheKey] = true;
_logger.debug('Cached entity existence for key: $cacheKey (exists: true)');
}
if (entity == null) return null;
if (withRelated.isNotEmpty) {
await _fetchAndStitchRelations([entity], withRelated, DataSource.local, userId);
}
return _applyPostFetchTransforms(entity);
}