From 47f9cbad4d6b634298cca0eba561f64715801daf Mon Sep 17 00:00:00 2001
From: Harpreet Sangar <happy_san@protonmail.com>
Date: Thu, 19 Aug 2021 14:21:22 +0530
Subject: [PATCH 01/10] Update tests

---
 lib/src/services/base_api_call.dart   |  5 +-
 lib/src/services/request_cache.dart   | 10 ++-
 lib/src/services/typedefs.dart        |  6 ++
 pubspec.yaml                          |  1 +
 test/configuration_test.dart          | 31 ++++++++-
 test/services/request_cache_test.dart | 94 +++++++++++++++------------
 6 files changed, 98 insertions(+), 49 deletions(-)
 create mode 100644 lib/src/services/typedefs.dart

diff --git a/lib/src/services/base_api_call.dart b/lib/src/services/base_api_call.dart
index f44af64..ffcf6b8 100644
--- a/lib/src/services/base_api_call.dart
+++ b/lib/src/services/base_api_call.dart
@@ -2,6 +2,7 @@ import 'dart:async';
 
 import 'package:http/http.dart' as http;
 
+import 'typedefs.dart';
 import 'node_pool.dart';
 import '../configuration.dart';
 import '../models/node.dart';
@@ -46,12 +47,12 @@ abstract class BaseApiCall<R extends Object> {
   Map<String, String> get defaultQueryParameters =>
       Map.from(_defaultQueryParameters);
 
-  /// Retries the [request] untill a node responds or [Configuration.numRetries]
+  /// Retries the [request] untill a node functionresponds or [Configuration.numRetries]
   /// run out.
   ///
   /// Also sets the health status of nodes after each request so it can be put
   /// in/out of [NodePool]'s circulation.
-  Future<R> send(Future<http.Response> Function(Node) request) async {
+  Future<R> send(Request request) async {
     http.Response response;
     Node node;
     for (var triesLeft = config.numRetries;;) {
diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart
index 65f835f..fb70388 100644
--- a/lib/src/services/request_cache.dart
+++ b/lib/src/services/request_cache.dart
@@ -1,20 +1,18 @@
 import 'dart:collection';
 
-import 'package:http/http.dart' as http;
-
-import '../models/node.dart';
+import 'typedefs.dart';
 
 /// Cache store which uses a [HashMap] internally to serve requests.
 class RequestCache {
   final _cachedResponses = HashMap<int, _Cache>();
 
+  // TODO(harisarang): rename this function to getResponse
   /// Caches the response of the [request], identified by [key]. The cached
   /// response is valid till [cacheTTL].
   Future<Map<String, dynamic>> cache(
     int key,
-    Future<Map<String, dynamic>> Function(Future<http.Response> Function(Node))
-        send,
-    Future<http.Response> Function(Node) request,
+    Send<Map<String, dynamic>> send, // Only being used by ApiCall for now.
+    Request request,
     Duration cacheTTL,
   ) async {
     if (_cachedResponses.containsKey(key)) {
diff --git a/lib/src/services/typedefs.dart b/lib/src/services/typedefs.dart
new file mode 100644
index 0000000..50b9095
--- /dev/null
+++ b/lib/src/services/typedefs.dart
@@ -0,0 +1,6 @@
+import 'package:http/http.dart' as http;
+
+import '../models/node.dart';
+
+typedef Request = Future<http.Response> Function(Node);
+typedef Send<R> = Future<R> Function(Request);
diff --git a/pubspec.yaml b/pubspec.yaml
index 5aafc7a..4585efc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,6 +11,7 @@ dependencies:
   http: ^0.13.3
   crypto: ^3.0.1
   equatable: ^2.0.2
+  dcache: ^0.4.0
 
 dev_dependencies:
   test: ^1.17.7
diff --git a/test/configuration_test.dart b/test/configuration_test.dart
index 9d04788..f3ce36a 100644
--- a/test/configuration_test.dart
+++ b/test/configuration_test.dart
@@ -27,6 +27,7 @@ void main() {
     retryInterval: Duration(seconds: 3),
     sendApiKeyAsQueryParam: true,
     cachedSearchResultsTTL: Duration(seconds: 30),
+    cacheCapacity: 101,
   );
 
   group('Configuration', () {
@@ -61,9 +62,12 @@ void main() {
     test('has a sendApiKeyAsQueryParam field', () {
       expect(config.sendApiKeyAsQueryParam, isTrue);
     });
-    test('has a cacheSearchResults field', () {
+    test('has a cacheSearchResultsTTL field', () {
       expect(config.cachedSearchResultsTTL, equals(Duration(seconds: 30)));
     });
+    test('has a cacheCapacity field', () {
+      expect(config.cacheCapacity, equals(101));
+    });
   });
 
   group('Configuration initialization', () {
@@ -180,6 +184,31 @@ void main() {
       );
       expect(config.retryInterval, equals(Duration(milliseconds: 100)));
     });
+    test('with missing cacheCapacity, sets cacheCapacity to 100', () {
+      final config = Configuration(
+        apiKey: 'abc123',
+        connectionTimeout: Duration(seconds: 10),
+        healthcheckInterval: Duration(seconds: 5),
+        nearestNode: Node(
+          protocol: 'http',
+          host: 'localhost',
+          path: '/path/to/service',
+        ),
+        nodes: {
+          Node(
+            protocol: 'https',
+            host: 'localhost',
+            path: '/path/to/service',
+          ),
+        },
+        numRetries: 5,
+        retryInterval: Duration(seconds: 3),
+        sendApiKeyAsQueryParam: true,
+        cachedSearchResultsTTL: Duration(seconds: 30),
+      );
+
+      expect(config.cacheCapacity, equals(100));
+    });
     test(
         'with missing sendApiKeyAsQueryParam, sets sendApiKeyAsQueryParam to false',
         () {
diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart
index 8a50ea5..4367338 100644
--- a/test/services/request_cache_test.dart
+++ b/test/services/request_cache_test.dart
@@ -6,89 +6,103 @@ import 'package:http/http.dart' as http;
 
 import 'package:typesense/src/services/request_cache.dart';
 import 'package:typesense/src/models/node.dart';
+import 'package:typesense/src/services/typedefs.dart';
 
 import '../test_utils.dart';
 
 class MockResponse extends Mock implements http.Response {}
 
 void main() {
-  group('RequestCache', () {
-    RequestCache requestCache;
-    MockResponse mockResponse;
-    int requestNumber;
-    Future<Map<String, dynamic>> Function(Future<http.Response> Function(Node))
-        send;
-    Future<http.Response> Function(Node) request;
-    final cacheTTL = Duration(seconds: 1);
-    setUp(() {
-      requestCache = RequestCache();
-      mockResponse = MockResponse();
-      requestNumber = 1;
+  RequestCache requestCache;
+  MockResponse mockResponse;
+  int requestNumber;
+  Send<Map<String, dynamic>> send;
+  Request request;
+
+  setUp(() {
+    requestCache = RequestCache(5, Duration(seconds: 1));
+    mockResponse = MockResponse();
+    requestNumber = 1;
 
-      when(mockResponse.body).thenAnswer((invocation) {
-        switch (requestNumber++) {
-          case 1:
-            return json.encode({'value': 'initial'});
+    when(mockResponse.body).thenAnswer((invocation) {
+      switch (requestNumber++) {
+        case 1:
+          return json.encode({'value': 'initial'});
 
-          case 2:
-            return json.encode({'value': 'updated'});
+        case 2:
+          return json.encode({'value': 'updated'});
+
+        default:
+          return json.encode({});
+      }
+    });
 
-          default:
-            return json.encode({});
-        }
-      });
+    send = (request) async {
+      final response = await request(
+          Node(protocol: protocol, host: host, path: pathToService));
+      return json.decode(response.body);
+    };
+    request = (node) => Future.value(mockResponse);
+  });
+  group('RequestCache', () {
+    final requestCache = RequestCache(5, Duration(seconds: 1));
 
-      send = (request) async {
-        final response = await request(
-            Node(protocol: protocol, host: host, path: pathToService));
-        return json.decode(response.body);
-      };
-      request = (node) => Future.value(mockResponse);
+    test('has a size field', () {
+      expect(requestCache.size, equals(5));
     });
+    test('has a timeToUse field', () {
+      expect(requestCache.timeToUse, equals(Duration(seconds: 1)));
+    });
+    test('has a getResponse method', () async {
+      expect(
+          await requestCache.getResponse(
+            '/value'.hashCode,
+            send,
+            request,
+          ),
+          equals({'value': 'initial'}));
+    });
+  });
 
-    test('caches the response', () async {
+  group('RequestCache.getResponse', () {
+    test('returns cached response', () async {
       expect(
-          await requestCache.cache(
+          await requestCache.getResponse(
             '/value'.hashCode,
             send,
             request,
-            cacheTTL,
           ),
           equals({'value': 'initial'}));
       expect(
-          await requestCache.cache(
+          await requestCache.getResponse(
             '/value'.hashCode,
             send,
             request,
-            cacheTTL,
           ),
           equals({'value': 'initial'}));
     });
-    test('refreshes the cache after TTL duration', () async {
+    test('refreshes the cache after timeToUse duration', () async {
       expect(
-          await requestCache.cache(
+          await requestCache.getResponse(
             '/value'.hashCode,
             send,
             request,
-            cacheTTL,
           ),
           equals({'value': 'initial'}));
       expect(
-          await requestCache.cache(
+          await requestCache.getResponse(
             '/value'.hashCode,
             send,
             request,
-            cacheTTL,
           ),
           equals({'value': 'initial'}));
 
       await Future.delayed(Duration(seconds: 1, milliseconds: 100));
       expect(
-          await requestCache.cache(
+          await requestCache.getResponse(
             '/value'.hashCode,
             send,
             request,
-            cacheTTL,
           ),
           equals({'value': 'updated'}));
     });

From 886f995bfda7571b36f51c60b58a798f77271098 Mon Sep 17 00:00:00 2001
From: Harpreet Sangar <happy_san@protonmail.com>
Date: Thu, 19 Aug 2021 15:57:00 +0530
Subject: [PATCH 02/10] revert typo

---
 lib/src/services/base_api_call.dart | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/src/services/base_api_call.dart b/lib/src/services/base_api_call.dart
index ffcf6b8..6944df2 100644
--- a/lib/src/services/base_api_call.dart
+++ b/lib/src/services/base_api_call.dart
@@ -47,7 +47,7 @@ abstract class BaseApiCall<R extends Object> {
   Map<String, String> get defaultQueryParameters =>
       Map.from(_defaultQueryParameters);
 
-  /// Retries the [request] untill a node functionresponds or [Configuration.numRetries]
+  /// Retries the [request] untill a node responds or [Configuration.numRetries]
   /// run out.
   ///
   /// Also sets the health status of nodes after each request so it can be put

From a85a83d0a6d405d2ef0ddc842064d8eceb9ce3bd Mon Sep 17 00:00:00 2001
From: Harpreet Sangar <happy_san@protonmail.com>
Date: Fri, 20 Aug 2021 14:17:49 +0530
Subject: [PATCH 03/10] Add test for LRU eviction. [skip ci]

---
 test/services/request_cache_test.dart | 88 +++++++++++++++++++++------
 1 file changed, 71 insertions(+), 17 deletions(-)

diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart
index 4367338..844886d 100644
--- a/test/services/request_cache_test.dart
+++ b/test/services/request_cache_test.dart
@@ -12,18 +12,28 @@ import '../test_utils.dart';
 
 class MockResponse extends Mock implements http.Response {}
 
+Future<Map<String, dynamic>> send(request) async {
+  final response = await request(
+    Node(
+      protocol: protocol,
+      host: host,
+      path: pathToService,
+    ),
+  );
+  return json.decode(response.body);
+}
+
 void main() {
   RequestCache requestCache;
   MockResponse mockResponse;
   int requestNumber;
-  Send<Map<String, dynamic>> send;
   Request request;
 
   setUp(() {
-    requestCache = RequestCache(5, Duration(seconds: 1));
+    requestCache = RequestCache(5, Duration(seconds: 1), send);
+
     mockResponse = MockResponse();
     requestNumber = 1;
-
     when(mockResponse.body).thenAnswer((invocation) {
       switch (requestNumber++) {
         case 1:
@@ -36,17 +46,9 @@ void main() {
           return json.encode({});
       }
     });
-
-    send = (request) async {
-      final response = await request(
-          Node(protocol: protocol, host: host, path: pathToService));
-      return json.decode(response.body);
-    };
     request = (node) => Future.value(mockResponse);
   });
   group('RequestCache', () {
-    final requestCache = RequestCache(5, Duration(seconds: 1));
-
     test('has a size field', () {
       expect(requestCache.size, equals(5));
     });
@@ -57,11 +59,13 @@ void main() {
       expect(
           await requestCache.getResponse(
             '/value'.hashCode,
-            send,
             request,
           ),
           equals({'value': 'initial'}));
     });
+    test('has a send method', () async {
+      expect(await requestCache.send(request), equals({'value': 'initial'}));
+    });
   });
 
   group('RequestCache.getResponse', () {
@@ -69,14 +73,12 @@ void main() {
       expect(
           await requestCache.getResponse(
             '/value'.hashCode,
-            send,
             request,
           ),
           equals({'value': 'initial'}));
       expect(
           await requestCache.getResponse(
             '/value'.hashCode,
-            send,
             request,
           ),
           equals({'value': 'initial'}));
@@ -85,14 +87,12 @@ void main() {
       expect(
           await requestCache.getResponse(
             '/value'.hashCode,
-            send,
             request,
           ),
           equals({'value': 'initial'}));
       expect(
           await requestCache.getResponse(
             '/value'.hashCode,
-            send,
             request,
           ),
           equals({'value': 'initial'}));
@@ -101,10 +101,64 @@ void main() {
       expect(
           await requestCache.getResponse(
             '/value'.hashCode,
-            send,
             request,
           ),
           equals({'value': 'updated'}));
     });
+    test('evicts the least recently used response', () async {
+      requestCache = RequestCache(5, Duration(seconds: 10), send);
+
+      final mockResponses = List.generate(6, (_) => MockResponse()),
+          callCounters = List.filled(6, 0);
+      var i = 0;
+
+      for (final mockResponse in mockResponses) {
+        when(mockResponse.body).thenAnswer((invocation) {
+          return json.encode({'$i': '${++callCounters[i]}'});
+        });
+      }
+
+      // Cache size is 5, filling up the cache with different responses.
+      for (; i < 5; i++) {
+        expect(
+            await requestCache.getResponse(
+              i,
+              (node) => Future.value(mockResponses[i]),
+            ),
+            equals({'$i': '1'}));
+      }
+
+      // The responses should still be 1 since they're cached.
+      i = 0;
+      for (; i < 5; i++) {
+        expect(
+            await requestCache.getResponse(
+              i,
+              (node) => Future.value(mockResponses[i]),
+            ),
+            equals({'$i': '1'}));
+      }
+
+      // Least recently used response at this moment should be index 0 and hence
+      // should be evicted by the following call.
+      expect(
+          await requestCache.getResponse(
+            5,
+            (node) => Future.value(mockResponses[5]),
+          ),
+          equals({'5': '1'}));
+
+      // The responses should now be 2 since each response gets evicted before
+      // being called again.
+      i = 0;
+      for (; i < 5; i++) {
+        expect(
+            await requestCache.getResponse(
+              i,
+              (node) => Future.value(mockResponses[i]),
+            ),
+            equals({'$i': '2'}));
+      }
+    });
   });
 }

From 4eaef8ac50bb7f2a1c4e9662161a87d9ce9a36c4 Mon Sep 17 00:00:00 2001
From: Harisaran <hari@luxecraft.org>
Date: Sun, 22 Aug 2021 15:32:20 +0530
Subject: [PATCH 04/10] add: lru cache

---
 lib/src/services/request_cache.dart | 43 ++++++++++++-----------------
 1 file changed, 17 insertions(+), 26 deletions(-)

diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart
index fb70388..ece9083 100644
--- a/lib/src/services/request_cache.dart
+++ b/lib/src/services/request_cache.dart
@@ -1,42 +1,33 @@
 import 'dart:collection';
+import 'package:dcache/dcache.dart';
+import 'package:http/http.dart';
 
-import 'typedefs.dart';
+import 'typedefs.dart' as defs;
 
 /// Cache store which uses a [HashMap] internally to serve requests.
 class RequestCache {
-  final _cachedResponses = HashMap<int, _Cache>();
+  Cache _cachedResponses;
+  final Duration timeToUse;
+  final int size;
+  final defs.Send<Map<String, dynamic>> send;
+
+  RequestCache(this.size, this.timeToUse, this.send) {
+    _cachedResponses = LruCache<dynamic, Response>(storage: InMemoryStorage(size));
+  }
 
   // TODO(harisarang): rename this function to getResponse
   /// Caches the response of the [request], identified by [key]. The cached
   /// response is valid till [cacheTTL].
-  Future<Map<String, dynamic>> cache(
+  Future<Map<String, dynamic>> getResponse(
     int key,
-    Send<Map<String, dynamic>> send, // Only being used by ApiCall for now.
-    Request request,
-    Duration cacheTTL,
+    defs.Request request,
   ) async {
     if (_cachedResponses.containsKey(key)) {
-      if (_isCacheValid(_cachedResponses[key], cacheTTL)) {
-        // Cache entry is still valid, return it
-        return Future.value(_cachedResponses[key].data);
-      } else {
-        // Cache entry has expired, so delete it explicitly
-        _cachedResponses.remove(key);
-      }
+      return send(_cachedResponses.get(key));
     }
-
-    final response = await send(request);
-    _cachedResponses[key] = _Cache(response, DateTime.now());
+    
+    var response = await send(request);
+    _cachedResponses.set(key, response);
     return response;
   }
-
-  bool _isCacheValid(_Cache cache, Duration cacheTTL) =>
-      DateTime.now().difference(cache.creationTime) < cacheTTL;
-}
-
-class _Cache {
-  final DateTime creationTime;
-  final Map<String, dynamic> data;
-
-  const _Cache(this.data, this.creationTime);
 }

From ec4aecd75120b60cf72b1acf6d5fe285f184fe00 Mon Sep 17 00:00:00 2001
From: Harisaran <hari@luxecraft.org>
Date: Sun, 22 Aug 2021 16:03:02 +0530
Subject: [PATCH 05/10] update: type `dynamic` to `String`

---
 lib/src/services/request_cache.dart | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart
index ece9083..4603b00 100644
--- a/lib/src/services/request_cache.dart
+++ b/lib/src/services/request_cache.dart
@@ -12,14 +12,14 @@ class RequestCache {
   final defs.Send<Map<String, dynamic>> send;
 
   RequestCache(this.size, this.timeToUse, this.send) {
-    _cachedResponses = LruCache<dynamic, Response>(storage: InMemoryStorage(size));
+    _cachedResponses = LruCache<String, Response>(storage: InMemoryStorage(size));
   }
 
   // TODO(harisarang): rename this function to getResponse
   /// Caches the response of the [request], identified by [key]. The cached
   /// response is valid till [cacheTTL].
   Future<Map<String, dynamic>> getResponse(
-    int key,
+    String key,
     defs.Request request,
   ) async {
     if (_cachedResponses.containsKey(key)) {

From ce4176ccf88573e5f3698dd802c0b2b6aa4ac06d Mon Sep 17 00:00:00 2001
From: Harisaran <hari@luxecraft.org>
Date: Sun, 22 Aug 2021 16:35:14 +0530
Subject: [PATCH 06/10] update:  `Cache` types

---
 lib/src/services/request_cache.dart   | 14 +++++++-------
 test/services/request_cache_test.dart | 20 ++++++++++----------
 2 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart
index 4603b00..8bca308 100644
--- a/lib/src/services/request_cache.dart
+++ b/lib/src/services/request_cache.dart
@@ -1,18 +1,18 @@
 import 'dart:collection';
 import 'package:dcache/dcache.dart';
-import 'package:http/http.dart';
+import 'package:http/http.dart' as http;
 
-import 'typedefs.dart' as defs;
+import 'typedefs.dart';
 
 /// Cache store which uses a [HashMap] internally to serve requests.
 class RequestCache {
-  Cache _cachedResponses;
+  Cache<String, Map<String, dynamic>> _cachedResponses;
   final Duration timeToUse;
   final int size;
-  final defs.Send<Map<String, dynamic>> send;
+  final Send<Map<String, dynamic>> send;
 
   RequestCache(this.size, this.timeToUse, this.send) {
-    _cachedResponses = LruCache<String, Response>(storage: InMemoryStorage(size));
+    _cachedResponses = LruCache<String, Map<String, dynamic>>(storage: InMemoryStorage(size));
   }
 
   // TODO(harisarang): rename this function to getResponse
@@ -20,10 +20,10 @@ class RequestCache {
   /// response is valid till [cacheTTL].
   Future<Map<String, dynamic>> getResponse(
     String key,
-    defs.Request request,
+    Request request,
   ) async {
     if (_cachedResponses.containsKey(key)) {
-      return send(_cachedResponses.get(key));
+      return Future<Map<String, dynamic>>.value(_cachedResponses.get(key));
     }
     
     var response = await send(request);
diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart
index 844886d..6c1a92a 100644
--- a/test/services/request_cache_test.dart
+++ b/test/services/request_cache_test.dart
@@ -58,7 +58,7 @@ void main() {
     test('has a getResponse method', () async {
       expect(
           await requestCache.getResponse(
-            '/value'.hashCode,
+            '/value',
             request,
           ),
           equals({'value': 'initial'}));
@@ -72,13 +72,13 @@ void main() {
     test('returns cached response', () async {
       expect(
           await requestCache.getResponse(
-            '/value'.hashCode,
+            '/value',
             request,
           ),
           equals({'value': 'initial'}));
       expect(
           await requestCache.getResponse(
-            '/value'.hashCode,
+            '/value',
             request,
           ),
           equals({'value': 'initial'}));
@@ -86,13 +86,13 @@ void main() {
     test('refreshes the cache after timeToUse duration', () async {
       expect(
           await requestCache.getResponse(
-            '/value'.hashCode,
+            '/value',
             request,
           ),
           equals({'value': 'initial'}));
       expect(
           await requestCache.getResponse(
-            '/value'.hashCode,
+            '/value',
             request,
           ),
           equals({'value': 'initial'}));
@@ -100,7 +100,7 @@ void main() {
       await Future.delayed(Duration(seconds: 1, milliseconds: 100));
       expect(
           await requestCache.getResponse(
-            '/value'.hashCode,
+            '/value',
             request,
           ),
           equals({'value': 'updated'}));
@@ -122,7 +122,7 @@ void main() {
       for (; i < 5; i++) {
         expect(
             await requestCache.getResponse(
-              i,
+              i.toString(),
               (node) => Future.value(mockResponses[i]),
             ),
             equals({'$i': '1'}));
@@ -133,7 +133,7 @@ void main() {
       for (; i < 5; i++) {
         expect(
             await requestCache.getResponse(
-              i,
+              i.toString(),
               (node) => Future.value(mockResponses[i]),
             ),
             equals({'$i': '1'}));
@@ -143,7 +143,7 @@ void main() {
       // should be evicted by the following call.
       expect(
           await requestCache.getResponse(
-            5,
+            5.toString(),
             (node) => Future.value(mockResponses[5]),
           ),
           equals({'5': '1'}));
@@ -154,7 +154,7 @@ void main() {
       for (; i < 5; i++) {
         expect(
             await requestCache.getResponse(
-              i,
+              i.toString(),
               (node) => Future.value(mockResponses[i]),
             ),
             equals({'$i': '2'}));

From 37f7563237cb972a64f32816f7a46ce31e541d09 Mon Sep 17 00:00:00 2001
From: Harpreet Sangar <happy_san@protonmail.com>
Date: Sun, 22 Aug 2021 18:12:58 +0530
Subject: [PATCH 07/10] Update ApiCall to match getResponse

---
 lib/src/services/api_call.dart      | 11 ++++-------
 lib/src/services/request_cache.dart |  7 +++----
 2 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/lib/src/services/api_call.dart b/lib/src/services/api_call.dart
index 057d32f..bf5d6b9 100644
--- a/lib/src/services/api_call.dart
+++ b/lib/src/services/api_call.dart
@@ -30,17 +30,16 @@ class ApiCall extends BaseApiCall<Map<String, dynamic>> {
     bool shouldCacheResult = false,
   }) =>
       shouldCacheResult && config.cachedSearchResultsTTL != Duration.zero
-          ? _requestCache.cache(
+          ? _requestCache.getResponse(
               // SplayTreeMap ensures order of the parameters is maintained so
               // cache key won't differ because of different ordering of
               // parameters.
-              '$endpoint${SplayTreeMap.from(queryParams)}'.hashCode,
+              '$endpoint${SplayTreeMap.from(queryParams)}',
               send,
               (node) => node.client.get(
                 requestUri(node, endpoint, queryParams),
                 headers: defaultHeaders,
               ),
-              config.cachedSearchResultsTTL,
             )
           : send((node) => node.client.get(
                 requestUri(node, endpoint, queryParams),
@@ -80,19 +79,17 @@ class ApiCall extends BaseApiCall<Map<String, dynamic>> {
     bool shouldCacheResult = false,
   }) =>
       shouldCacheResult && config.cachedSearchResultsTTL != Duration.zero
-          ? _requestCache.cache(
+          ? _requestCache.getResponse(
               // SplayTreeMap ensures order of the parameters is maintained so
               // cache key won't differ because of different ordering of
               // parameters.
-              '$endpoint${SplayTreeMap.from(queryParams)}${SplayTreeMap.from(additionalHeaders)}${json.encode(bodyParameters)}'
-                  .hashCode,
+              '$endpoint${SplayTreeMap.from(queryParams)}${SplayTreeMap.from(additionalHeaders)}${json.encode(bodyParameters)}',
               send,
               (node) => node.client.post(
                 requestUri(node, endpoint, queryParams),
                 headers: {...defaultHeaders, ...additionalHeaders},
                 body: json.encode(bodyParameters),
               ),
-              config.cachedSearchResultsTTL,
             )
           : send((node) => node.client.post(
                 requestUri(node, endpoint, queryParams),
diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart
index 8bca308..f55dbe7 100644
--- a/lib/src/services/request_cache.dart
+++ b/lib/src/services/request_cache.dart
@@ -1,6 +1,5 @@
 import 'dart:collection';
 import 'package:dcache/dcache.dart';
-import 'package:http/http.dart' as http;
 
 import 'typedefs.dart';
 
@@ -12,10 +11,10 @@ class RequestCache {
   final Send<Map<String, dynamic>> send;
 
   RequestCache(this.size, this.timeToUse, this.send) {
-    _cachedResponses = LruCache<String, Map<String, dynamic>>(storage: InMemoryStorage(size));
+    _cachedResponses =
+        LruCache<String, Map<String, dynamic>>(storage: InMemoryStorage(size));
   }
 
-  // TODO(harisarang): rename this function to getResponse
   /// Caches the response of the [request], identified by [key]. The cached
   /// response is valid till [cacheTTL].
   Future<Map<String, dynamic>> getResponse(
@@ -25,7 +24,7 @@ class RequestCache {
     if (_cachedResponses.containsKey(key)) {
       return Future<Map<String, dynamic>>.value(_cachedResponses.get(key));
     }
-    
+
     var response = await send(request);
     _cachedResponses.set(key, response);
     return response;

From 7995247ae640d1ef2e5ac27de35c6047280b94f0 Mon Sep 17 00:00:00 2001
From: Harisaran <hari@luxecraft.org>
Date: Sun, 22 Aug 2021 19:06:52 +0530
Subject: [PATCH 08/10] refactor: `send` inside `getResponse()`

---
 lib/src/services/request_cache.dart   |  5 ++---
 test/services/request_cache_test.dart | 17 ++++++++++++-----
 2 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart
index 8bca308..aac9be0 100644
--- a/lib/src/services/request_cache.dart
+++ b/lib/src/services/request_cache.dart
@@ -1,6 +1,5 @@
 import 'dart:collection';
 import 'package:dcache/dcache.dart';
-import 'package:http/http.dart' as http;
 
 import 'typedefs.dart';
 
@@ -9,9 +8,8 @@ class RequestCache {
   Cache<String, Map<String, dynamic>> _cachedResponses;
   final Duration timeToUse;
   final int size;
-  final Send<Map<String, dynamic>> send;
 
-  RequestCache(this.size, this.timeToUse, this.send) {
+  RequestCache(this.size, this.timeToUse) {
     _cachedResponses = LruCache<String, Map<String, dynamic>>(storage: InMemoryStorage(size));
   }
 
@@ -21,6 +19,7 @@ class RequestCache {
   Future<Map<String, dynamic>> getResponse(
     String key,
     Request request,
+    Send<Map<String, dynamic>> send
   ) async {
     if (_cachedResponses.containsKey(key)) {
       return Future<Map<String, dynamic>>.value(_cachedResponses.get(key));
diff --git a/test/services/request_cache_test.dart b/test/services/request_cache_test.dart
index 6c1a92a..bbbaadb 100644
--- a/test/services/request_cache_test.dart
+++ b/test/services/request_cache_test.dart
@@ -30,7 +30,7 @@ void main() {
   Request request;
 
   setUp(() {
-    requestCache = RequestCache(5, Duration(seconds: 1), send);
+    requestCache = RequestCache(5, Duration(seconds: 1));
 
     mockResponse = MockResponse();
     requestNumber = 1;
@@ -60,12 +60,10 @@ void main() {
           await requestCache.getResponse(
             '/value',
             request,
+            send
           ),
           equals({'value': 'initial'}));
     });
-    test('has a send method', () async {
-      expect(await requestCache.send(request), equals({'value': 'initial'}));
-    });
   });
 
   group('RequestCache.getResponse', () {
@@ -74,12 +72,14 @@ void main() {
           await requestCache.getResponse(
             '/value',
             request,
+            send
           ),
           equals({'value': 'initial'}));
       expect(
           await requestCache.getResponse(
             '/value',
             request,
+            send
           ),
           equals({'value': 'initial'}));
     });
@@ -88,12 +88,14 @@ void main() {
           await requestCache.getResponse(
             '/value',
             request,
+            send
           ),
           equals({'value': 'initial'}));
       expect(
           await requestCache.getResponse(
             '/value',
             request,
+            send
           ),
           equals({'value': 'initial'}));
 
@@ -102,11 +104,12 @@ void main() {
           await requestCache.getResponse(
             '/value',
             request,
+            send
           ),
           equals({'value': 'updated'}));
     });
     test('evicts the least recently used response', () async {
-      requestCache = RequestCache(5, Duration(seconds: 10), send);
+      requestCache = RequestCache(5, Duration(seconds: 10));
 
       final mockResponses = List.generate(6, (_) => MockResponse()),
           callCounters = List.filled(6, 0);
@@ -124,6 +127,7 @@ void main() {
             await requestCache.getResponse(
               i.toString(),
               (node) => Future.value(mockResponses[i]),
+              send
             ),
             equals({'$i': '1'}));
       }
@@ -135,6 +139,7 @@ void main() {
             await requestCache.getResponse(
               i.toString(),
               (node) => Future.value(mockResponses[i]),
+              send
             ),
             equals({'$i': '1'}));
       }
@@ -145,6 +150,7 @@ void main() {
           await requestCache.getResponse(
             5.toString(),
             (node) => Future.value(mockResponses[5]),
+            send
           ),
           equals({'5': '1'}));
 
@@ -156,6 +162,7 @@ void main() {
             await requestCache.getResponse(
               i.toString(),
               (node) => Future.value(mockResponses[i]),
+              send
             ),
             equals({'$i': '2'}));
       }

From ea8bd063e870cf3133eebcce1489820505a0abff Mon Sep 17 00:00:00 2001
From: Harisaran <hari@luxecraft.org>
Date: Sun, 22 Aug 2021 19:09:39 +0530
Subject: [PATCH 09/10] update: apicall

---
 lib/src/services/api_call.dart | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/src/services/api_call.dart b/lib/src/services/api_call.dart
index bf5d6b9..7c4e56d 100644
--- a/lib/src/services/api_call.dart
+++ b/lib/src/services/api_call.dart
@@ -35,11 +35,11 @@ class ApiCall extends BaseApiCall<Map<String, dynamic>> {
               // cache key won't differ because of different ordering of
               // parameters.
               '$endpoint${SplayTreeMap.from(queryParams)}',
-              send,
               (node) => node.client.get(
                 requestUri(node, endpoint, queryParams),
                 headers: defaultHeaders,
               ),
+              send,
             )
           : send((node) => node.client.get(
                 requestUri(node, endpoint, queryParams),
@@ -84,12 +84,12 @@ class ApiCall extends BaseApiCall<Map<String, dynamic>> {
               // cache key won't differ because of different ordering of
               // parameters.
               '$endpoint${SplayTreeMap.from(queryParams)}${SplayTreeMap.from(additionalHeaders)}${json.encode(bodyParameters)}',
-              send,
               (node) => node.client.post(
                 requestUri(node, endpoint, queryParams),
                 headers: {...defaultHeaders, ...additionalHeaders},
                 body: json.encode(bodyParameters),
               ),
+              send,
             )
           : send((node) => node.client.post(
                 requestUri(node, endpoint, queryParams),

From c7717256b84fc8b185fb3c812de29d67b406ba09 Mon Sep 17 00:00:00 2001
From: Harisaran <hari@luxecraft.org>
Date: Sun, 22 Aug 2021 19:42:28 +0530
Subject: [PATCH 10/10] add: `_cacheTimestamp`

[ci skip]
---
 lib/src/services/request_cache.dart | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lib/src/services/request_cache.dart b/lib/src/services/request_cache.dart
index 950f259..e8e8b6f 100644
--- a/lib/src/services/request_cache.dart
+++ b/lib/src/services/request_cache.dart
@@ -6,6 +6,7 @@ import 'typedefs.dart';
 /// Cache store which uses a [HashMap] internally to serve requests.
 class RequestCache {
   Cache<String, Map<String, dynamic>> _cachedResponses;
+  final _cachedTimestamp = HashMap<String, DateTime>();
   final Duration timeToUse;
   final int size;
 
@@ -20,12 +21,16 @@ class RequestCache {
     Request request,
     Send<Map<String, dynamic>> send
   ) async {
-    if (_cachedResponses.containsKey(key)) {
+    if (_cachedResponses.containsKey(key) && _isCacheValid(key)) {
       return Future<Map<String, dynamic>>.value(_cachedResponses.get(key));
     }
 
     var response = await send(request);
     _cachedResponses.set(key, response);
+    _cachedTimestamp[key] = DateTime.now();
     return response;
   }
+
+  bool _isCacheValid(String key) =>
+      DateTime.now().difference(_cachedTimestamp[key]) < timeToUse;
 }