Skip to main content

ttl_cache_service 1.0.0 copy "ttl_cache_service: ^1.0.0" to clipboard
ttl_cache_service: ^1.0.0 copied to clipboard

retracted

High-performance in-memory TTL cache with latency-aware eviction (least fetch time evicted first), stampede protection, and func.getTtlCache() extensions.

example/example.dart

import 'dart:async';
import 'package:ttl_cache_service/ttl_cache_service.dart';

// Simulated remote service
class DoctorApi {
  int fetchCount = 0;

  Future<Map<String, dynamic>> getDoctor(String id) async {
    fetchCount++;
    await Future.delayed(const Duration(milliseconds: 50));
    return {'id': id, 'name': 'Dr. Ahmed Mahmoud', 'specialty': 'Cardiology'};
  }

  Future<List<String>> getAppointments(String docId, String date) async {
    fetchCount++;
    await Future.delayed(const Duration(milliseconds: 100));
    return ['10:00 AM', '11:30 AM', '02:00 PM'];
  }
}

void main() async {
  print('=== 1. Testing func.cache() with Auto Key Generation ===');
  final api = DoctorApi();

  // Call 1: Cache Miss -> executes network call
  final doc1 = await api.getDoctor.cache('doc_1');
  print('Call 1 result: $doc1 (Network fetch count: ${api.fetchCount})');

  // Call 2: Cache Hit -> returns instantly (0ms), NO network call
  final doc2 = await api.getDoctor.cache('doc_1');
  print('Call 2 result: $doc2 (Network fetch count: ${api.fetchCount})');
  assert(api.fetchCount == 1, 'Network call must not execute on cache hit!');

  // Multi-arg call with auto key generation ('getAppointments:doc_1:2026-09-12')
  final slots1 = await api.getAppointments.cache('doc_1', '2026-09-12');
  print('Slots Call 1: $slots1 (Network fetch count: ${api.fetchCount})');

  final slots2 = await api.getAppointments.cache('doc_1', '2026-09-12');
  print('Slots Call 2: $slots2 (Network fetch count: ${api.fetchCount})');
  assert(api.fetchCount == 2, 'Slots hit must not increment fetch count!');

  print('\n=== 2. Testing func.invalidateCache() (Zero Manual Keys) ===');
  // Invalidate exact doctor entry without specifying manual key:
  final removedCount = api.getDoctor.invalidateCache('doc_1');
  print('Invalidated doc_1: $removedCount entry removed');
  assert(removedCount == 1, 'doc_1 entry must be invalidated!');

  // Calling getDoctor now must re-fetch from network (fetch count becomes 3)
  await api.getDoctor.cache('doc_1');
  print('After invalidation, fetch count: ${api.fetchCount} (re-fetched as expected)');
  assert(api.fetchCount == 3, 'Network fetch must re-run after invalidation!');

  // Seed multiple dates for doc_1 and doc_2
  await api.getAppointments.cache('doc_1', '2026-09-13');
  await api.getAppointments.cache('doc_2', '2026-09-12');

  // Partial invalidation: invalidate all dates for doc_1, keeping doc_2 intact
  final partialRemoved = api.getAppointments.invalidateCache('doc_1');
  print('Partial invalidation for doc_1 removed: $partialRemoved entries');
  assert(partialRemoved >= 2, 'Must remove all entries for doc_1');

  // Complete function invalidation: invalidate all appointments for all doctors
  final allRemoved = api.getAppointments.invalidateAllCache();
  print('Complete invalidation of getAppointments removed: $allRemoved entries');

  print('\n=== 3. Testing Stampede / Thundering-Herd Protection ===');
  var stampedeExecutions = 0;
  Future<String> expensiveQuery() async {
    stampedeExecutions++;
    await Future.delayed(const Duration(milliseconds: 100));
    return 'Computation Result';
  }

  // Launch 10 simultaneous parallel callers for the exact same key
  final results = await Future.wait(
    List.generate(10, (_) => expensiveQuery.cache()),
  );
  print('Results count: ${results.length}, Executions: $stampedeExecutions');
  assert(stampedeExecutions == 1, 'Stampede protection must coalesce requests into 1 execution!');

  print('\n=== 4. Testing Latency-Aware Eviction (Evict Least Fetch Time) ===');
  final testCache = TtlCache<String, String>(
    maxEntries: 2,
    evictionPolicy: TtlEvictionPolicy.leastFetchTime,
  );

  // Key A: Very expensive query (takes 1000ms)
  testCache.set('expensive_query', 'Val A', fetchDuration: const Duration(milliseconds: 1000));

  // Key B: Very cheap query (takes 2ms)
  testCache.set('cheap_query', 'Val B', fetchDuration: const Duration(milliseconds: 2));

  // Cache is now full (2 / 2). Inserting Key C (50ms) triggers capacity eviction:
  testCache.set('medium_query', 'Val C', fetchDuration: const Duration(milliseconds: 50));

  print('expensive_query cached? ${testCache.has("expensive_query")} (Preserved because it took 1000ms)');
  print('cheap_query cached? ${testCache.has("cheap_query")} (Evicted because it was cheapest: 2ms)');
  print('medium_query cached? ${testCache.has("medium_query")} (Newly inserted)');
  assert(testCache.has('expensive_query'), 'Expensive query must be preserved!');
  assert(!testCache.has('cheap_query'), 'Cheapest query must be evicted first!');

  print('\n=== 5. Testing Prefix Invalidation on TtlCache ===');
  final clinicCache = TtlCache<String, String>();
  clinicCache.set('clinic:1:profile', 'Clinic 1');
  clinicCache.set('clinic:1:settings', 'Settings 1');
  clinicCache.set('clinic:2:profile', 'Clinic 2');

  print('Before prefix invalidation size: ${clinicCache.size}');
  clinicCache.invalidatePrefix('clinic:1:');
  print('After invalidating clinic:1: prefix, size: ${clinicCache.size}');
  assert(!clinicCache.has('clinic:1:profile'));
  assert(clinicCache.has('clinic:2:profile'));

  print('\n=== 6. Cache Statistics & Wall-Clock Latency Saved ===');
  print('Global default cache stats: ${TtlCache.defaultInstance.stats}');
  print('Test cache stats: ${testCache.stats}');
  print('\nAll checks passed successfully! 🎉');
}
1
likes
0
points
179
downloads

Publisher

unverified uploader

Weekly Downloads

High-performance in-memory TTL cache with latency-aware eviction (least fetch time evicted first), stampede protection, and func.getTtlCache() extensions.

Repository (GitHub)
View/report issues

Topics

#cache #ttl #lru #performance #in-memory

License

unknown (license)

More

Packages that depend on ttl_cache_service