getAll<T> method

Future<Map<String, T>> getAll<T>(
  1. List<String> keys, {
  2. Map<String, Future<T> Function()>? refreshCallbacks,
  3. CachePolicy? policy,
})

Retrieves multiple values associated with the given keys.

Returns a map where the keys are the original keys and the values are the retrieved values. If a key is not found in the cache or its value is expired, it will not be included in the returned map.

If refreshCallbacks is provided and an item is stale according to its policy, the callback for that key will be used to refresh the data based on the refresh strategy.

Throws a CacheException if there is an error retrieving the data.

Implementation

Future<Map<String, T>> getAll<T>(
  List<String> keys, {
  Map<String, Future<T> Function()>? refreshCallbacks,
  CachePolicy? policy,
}) async {
  try {
    if (keys.isEmpty) {
      return {};
    }

    // Check for empty keys
    for (final key in keys) {
      if (key.isEmpty) {
        throw ArgumentError('Keys cannot be empty');
      }
    }

    final result = <String, T>{};
    final cacheItems = await _cacheAdapter.getAll(keys);
    final keysToDelete = <String>[];
    final effectivePolicy = policy ?? CachePolicy.defaultPolicy;

    // Process cache hits
    for (final entry in cacheItems.entries) {
      final key = entry.key;
      final cacheItem = entry.value;

      if (cacheItem.isExpired) {
        // Record cache miss in analytics
        _analytics.recordMiss(key);
        keysToDelete.add(key);

        // If a refresh callback is provided for this key, use it to get fresh data
        if (refreshCallbacks != null && refreshCallbacks.containsKey(key)) {
          final freshValue = await refreshCallbacks[key]!();
          await put(key, freshValue, policy: policy);
          result[key] = freshValue;
        }

        continue;
      }

      // Check if the item is stale and needs refreshing
      if (refreshCallbacks != null &&
          refreshCallbacks.containsKey(key) &&
          effectivePolicy.staleTime != null &&
          cacheItem.isStale(effectivePolicy.staleTime!)) {
        // Handle different refresh strategies
        switch (effectivePolicy.refreshStrategy) {
          case RefreshStrategy.backgroundRefresh:
            // Update in the background without blocking
            _refreshInBackground(
                key, refreshCallbacks[key]!, effectivePolicy);
            break;
          case RefreshStrategy.immediateRefresh:
            // Refresh immediately and return the fresh value
            final freshValue = await refreshCallbacks[key]!();
            await put(key, freshValue, policy: effectivePolicy);
            result[key] = freshValue;
            continue; // Skip the rest of the loop for this item
          case RefreshStrategy.never:
            // Do nothing, just use the cached value
            break;
        }
      }

      // Decompress the value if it's compressed
      T? resultValue;
      if (cacheItem.isCompressed &&
          _compression != null &&
          cacheItem.value is String) {
        try {
          final decompressedValue =
              _compression.decompressString(cacheItem.value as String);
          resultValue = decompressedValue as T?;
          _log.fine('Decompressed value for key $key');
        } catch (e) {
          _log.warning('Failed to decompress value for key $key: $e');
          resultValue = cacheItem.value as T?;
        }
      } else {
        resultValue = cacheItem.value as T?;
      }

      // Update sliding expiry if needed
      if (cacheItem.slidingExpiry != null) {
        final updatedCacheItem = cacheItem.updateExpiry();
        await _cacheAdapter.put(key, updatedCacheItem);
        // Record cache hit in analytics
        _analytics.recordHit(key);
        result[key] = resultValue as T;
      } else {
        // Update access metadata
        final updatedCacheItem = cacheItem.updateExpiry();
        if (updatedCacheItem != cacheItem) {
          await _cacheAdapter.put(key, updatedCacheItem);
        }

        // Record cache hit in analytics
        _analytics.recordHit(key);
        result[key] = resultValue as T;
      }
    }

    // Process cache misses with refresh callbacks
    if (refreshCallbacks != null) {
      final missingKeys = keys.where((key) =>
          !result.containsKey(key) &&
          !keysToDelete.contains(key) &&
          refreshCallbacks.containsKey(key));

      for (final key in missingKeys) {
        // Record cache miss in analytics
        _analytics.recordMiss(key);
        final freshValue = await refreshCallbacks[key]!();
        await put(key, freshValue, policy: policy);
        result[key] = freshValue;
      }
    }

    // Delete expired items in batch
    if (keysToDelete.isNotEmpty) {
      await _cacheAdapter.deleteAll(keysToDelete);
    }

    return result;
  } on HiveError catch (e) {
    _log.severe('Failed to get data from cache (HiveError): $e');
    throw CacheException('Failed to get data from cache: ${e.message}');
  } catch (e) {
    _log.severe('Failed to get data from cache (Unknown Error): $e');
    throw CacheException('Failed to get data from cache: $e');
  }
}