bv-ui-core 2.9.6 → 2.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bvFetch/README.md +20 -11
- package/lib/bvFetch/index.js +107 -26
- package/package.json +1 -1
- package/test/unit/bvFetch/index.spec.js +18 -6
package/lib/bvFetch/README.md
CHANGED
@@ -9,6 +9,8 @@ The BvFetch module provides methods to cache duplicate API calls and interact wi
|
|
9
9
|
`shouldCache (Function):` A function that takes the API response JSON as input and returns a boolean indicating whether to cache the response or not. This allows you to implement custom logic based on the response content. If caching is desired, the function should return true; otherwise, false.
|
10
10
|
`cacheName (String):` Optional. Specifies the name of the cache to be used. If not provided, the default cache name 'bvCache' will be used.
|
11
11
|
`cacheLimit (Integer)`: Optional. Specifies the cache size limit for the cache storage. Its value should be in MB. Default value is 10 MB.
|
12
|
+
`excludeHeaders (Array[String])`: Includes the list of headers that are excluded from the cacheKey
|
13
|
+
|
12
14
|
|
13
15
|
## bvFetchFunc Method Parameters
|
14
16
|
`url (String):` The URL of the API endpoint to fetch data from.
|
@@ -17,17 +19,22 @@ The BvFetch module provides methods to cache duplicate API calls and interact wi
|
|
17
19
|
## bvFetchFunc Return Value
|
18
20
|
`Promise<Response>:` A promise that resolves to the API response. If the response is cached, it returns the cached response. Otherwise, it fetches data from the API endpoint, caches the response according to the caching logic, and returns the fetched response.
|
19
21
|
|
20
|
-
## generateCacheKey Method
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
## generateCacheKey Method
|
23
|
+
|
24
|
+
Generates a cache key as a Request object for a given URL and options, filtering out any headers specified in `excludeHeaders`.
|
25
|
+
|
26
|
+
**Parameters:**
|
27
|
+
- `url (String)`: The URL of the API endpoint.
|
28
|
+
- `options (Object)`: *(Optional)* Request options (e.g., headers, method, etc.).
|
29
|
+
|
30
|
+
**Returns:**
|
31
|
+
`Request`: The generated Request object to be used as a cache key.
|
25
32
|
|
26
|
-
##
|
27
|
-
Retrieves cached
|
28
|
-
##
|
33
|
+
## retrievecachedRequests Method
|
34
|
+
Retrieves cached Requests from the cache storage associated with the provided cache name.
|
35
|
+
## retrievecachedRequests Parameters
|
29
36
|
This method takes no parameters.
|
30
|
-
##
|
37
|
+
## retrievecachedRequests Return Value
|
31
38
|
`void:` This method does not return anything.
|
32
39
|
|
33
40
|
## fetchDataAndCache Method
|
@@ -77,8 +84,10 @@ var BvFetch = require('bv-ui-core/lib/bvFetch')
|
|
77
84
|
|
78
85
|
// Initialize BV Fetch instance
|
79
86
|
const bvFetch = new BVFetch({
|
80
|
-
|
81
|
-
|
87
|
+
shouldCache: function(responseJson) => { /* return true or false */ }, // optional, callback function to check if the response is cachable
|
88
|
+
cacheName: "bvCache" // optional, default is "bvCache"
|
89
|
+
cacheLimit: 10, // optional but default is 10
|
90
|
+
excludeHeaders: [], // optional, list of headers to be excluded from cache
|
82
91
|
});
|
83
92
|
|
84
93
|
// Make API calls using bvFetchFunc method
|
package/lib/bvFetch/index.js
CHANGED
@@ -5,46 +5,122 @@
|
|
5
5
|
|
6
6
|
const { fetch } = require('../polyfills/fetch')
|
7
7
|
|
8
|
-
module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
8
|
+
module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit, excludeHeaders = [] }) {
|
9
9
|
this.shouldCache = shouldCache;
|
10
10
|
this.cacheName = cacheName || 'bvCache';
|
11
11
|
this.cacheLimit = cacheLimit * 1024 * 1024 || 10 * 1024 * 1024;
|
12
12
|
this.fetchPromises = new Map();
|
13
|
-
this.
|
13
|
+
this.cachedRequests = new Set();
|
14
|
+
this.excludeHeaders = excludeHeaders;
|
14
15
|
|
15
16
|
/**
|
16
|
-
*
|
17
|
-
*
|
18
|
-
* @param {
|
19
|
-
* @
|
17
|
+
* Checks if a request is present in a set of cached URLs.
|
18
|
+
*
|
19
|
+
* @param {Set} cachedRequests - A set of cached request objects.
|
20
|
+
* @param {Object} cacheKey - The request object to check for in the cachedRequests set.
|
21
|
+
* @param {string} cacheKey.url - The URL of the request.
|
22
|
+
* @param {Headers} cacheKey.headers - The headers of the request.
|
23
|
+
* @returns {boolean} - Returns true if the request is found in the cachedRequests set, otherwise false.
|
20
24
|
*/
|
25
|
+
function isRequestInSet (cachedRequests, cacheKey) {
|
26
|
+
try {
|
27
|
+
// Convert the Set to an array and check if any request matches
|
28
|
+
return [...cachedRequests].some((cachedRequest) => {
|
29
|
+
// Compare URLs
|
30
|
+
if (cachedRequest.url !== cacheKey.url) {
|
31
|
+
return false;
|
32
|
+
}
|
33
|
+
|
34
|
+
// Compare headers
|
35
|
+
const cachedHeaders = [...cachedRequest.headers.entries()];
|
36
|
+
const keyHeaders = [...cacheKey.headers.entries()];
|
37
|
+
|
38
|
+
if (cachedHeaders.length !== keyHeaders.length) {
|
39
|
+
return false; // Different number of headers
|
40
|
+
}
|
41
|
+
// Check if all headers match
|
42
|
+
return cachedHeaders.every(([key, value]) => {
|
43
|
+
return cacheKey.headers.get(key) === value;
|
44
|
+
});
|
45
|
+
});
|
46
|
+
}
|
47
|
+
catch (error) {
|
48
|
+
console.warn('Error checking in if request is in cache: ', error);
|
49
|
+
return false;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
21
53
|
|
54
|
+
/**
|
55
|
+
* Creates a new Request object with the given URL and options.
|
56
|
+
*
|
57
|
+
* @param {string} url - The URL to which the request is sent.
|
58
|
+
* @param {Object} options - The options to apply to the request.
|
59
|
+
* @returns {Request} The created Request object.
|
60
|
+
*/
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Generates a cache key as a Request object for the given URL and options.
|
64
|
+
* Filters out headers specified in `excludeHeaders` from the options before creating the Request.
|
65
|
+
*
|
66
|
+
* @param {string} url - The URL of the API endpoint.
|
67
|
+
* @param {Object} options - Optional request options (e.g., headers, method, etc.).
|
68
|
+
* @returns {Request} The generated Request object to be used as a cache key.
|
69
|
+
*/
|
22
70
|
this.generateCacheKey = (url, options) => {
|
23
|
-
const
|
24
|
-
|
71
|
+
const filteredOptions = Object.assign({}, options);
|
72
|
+
this.excludeHeaders.forEach(header => {
|
73
|
+
if (filteredOptions.headers && filteredOptions.headers[header]) {
|
74
|
+
delete filteredOptions.headers[header];
|
75
|
+
}
|
76
|
+
}
|
77
|
+
);
|
78
|
+
const key = new Request(url, filteredOptions);
|
25
79
|
return key;
|
26
80
|
};
|
27
81
|
|
82
|
+
/**
|
83
|
+
* Serializes a request object into a JSON string for use as a cache key.
|
84
|
+
* The serialization includes the URL, method, and sorted headers of the request.
|
85
|
+
* @param {Request} request - The request object to serialize.
|
86
|
+
* @returns {string} A JSON string representing the serialized request.
|
87
|
+
*/
|
88
|
+
this.serializeRequestKey = (request) => {
|
89
|
+
const url = request.url;
|
90
|
+
const method = request.method || 'GET';
|
91
|
+
// Convert headers to a sorted array of [key, value] pairs for consistency
|
92
|
+
const headersArr = Array.from(request.headers.entries()).sort(([a], [b]) => a.localeCompare(b));
|
93
|
+
return JSON.stringify([url, method, { headers: headersArr }]);
|
94
|
+
}
|
95
|
+
|
28
96
|
/**
|
29
|
-
* Retrieves cached
|
97
|
+
* Retrieves cached Requests from the cache storage associated with the provided cache name.
|
30
98
|
* @returns {void}
|
31
99
|
*/
|
32
100
|
|
33
|
-
this.
|
101
|
+
this.retrievecachedRequests = () => {
|
34
102
|
// Open the Cache Storage
|
35
103
|
caches.open(this.cacheName).then(cache => {
|
36
104
|
// Get all cache keys
|
37
105
|
cache.keys().then(keys => {
|
38
106
|
keys.forEach(request => {
|
39
|
-
|
107
|
+
const headers = {};
|
108
|
+
request.headers.forEach((value, key) => {
|
109
|
+
if (!this.excludeHeaders.includes(key)) {
|
110
|
+
headers[key] = value;
|
111
|
+
}
|
112
|
+
});
|
113
|
+
|
114
|
+
// Generate the cache key with headers and URL
|
115
|
+
const cacheKey = this.generateCacheKey(request.url, { headers });
|
116
|
+
this.cachedRequests.add(cacheKey); // Add to the set
|
40
117
|
});
|
41
118
|
});
|
42
119
|
});
|
43
|
-
|
44
120
|
}
|
45
121
|
|
46
|
-
//
|
47
|
-
this.
|
122
|
+
//callretrievecachedRequests function to set the cache URL set with the cached URLS
|
123
|
+
this.retrievecachedRequests();
|
48
124
|
|
49
125
|
/**
|
50
126
|
* Fetches data from the specified URL, caches the response, and returns the response.
|
@@ -75,6 +151,11 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
75
151
|
this.cacheData = (response, cacheKey) => {
|
76
152
|
const errJson = response.clone();
|
77
153
|
let canBeCached = true;
|
154
|
+
if (!response.ok) {
|
155
|
+
// If the response is not ok, we don't cache it
|
156
|
+
canBeCached = false;
|
157
|
+
return;
|
158
|
+
}
|
78
159
|
// Check for error in response obj
|
79
160
|
errJson.json().then(json => {
|
80
161
|
if (typeof this.shouldCache === 'function') {
|
@@ -106,8 +187,8 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
106
187
|
// Cache the response
|
107
188
|
caches.open(this.cacheName).then(cache => {
|
108
189
|
cache.put(cacheKey, newResponse);
|
109
|
-
//add key to
|
110
|
-
this.
|
190
|
+
//add key to cachedRequests set
|
191
|
+
this.cachedRequests.add(cacheKey);
|
111
192
|
});
|
112
193
|
});
|
113
194
|
}
|
@@ -122,8 +203,8 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
122
203
|
* @throws {Error} Throws an error if there's any problem fetching from cache.
|
123
204
|
*/
|
124
205
|
this.fetchFromCache = (cacheKey) => {
|
125
|
-
// Check if the URL is in the set of cached
|
126
|
-
if (!this.
|
206
|
+
// Check if the URL is in the set of cached requests set
|
207
|
+
if (!isRequestInSet(this.cachedRequests, cacheKey)) {
|
127
208
|
return Promise.resolve(null);
|
128
209
|
}
|
129
210
|
|
@@ -133,7 +214,7 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
133
214
|
return cache.match(cacheKey)
|
134
215
|
.then((cachedResponse) => {
|
135
216
|
if (!cachedResponse) {
|
136
|
-
this.
|
217
|
+
this.cachedRequests.delete(cacheKey)
|
137
218
|
return Promise.resolve(null);
|
138
219
|
}
|
139
220
|
const cachedTime = cachedResponse.headers.get('X-Bazaarvoice-Cached-Time');
|
@@ -162,11 +243,11 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
162
243
|
|
163
244
|
const cacheKey = this.generateCacheKey(url, options);
|
164
245
|
// If an ongoing fetch promise exists for the URL, return it
|
165
|
-
if (this.fetchPromises.has(cacheKey)) {
|
166
|
-
return this.fetchPromises.get(cacheKey).then(res => res.clone());
|
246
|
+
if (this.fetchPromises.has(this.serializeRequestKey(cacheKey))) {
|
247
|
+
return this.fetchPromises.get(this.serializeRequestKey(cacheKey)).then(res => res.clone());
|
167
248
|
}
|
168
249
|
|
169
|
-
|
250
|
+
// Check if response is available in cache
|
170
251
|
const newPromise = this.fetchFromCache(cacheKey)
|
171
252
|
.then((cachedResponse) => {
|
172
253
|
// If response found in cache, return it
|
@@ -178,14 +259,14 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
178
259
|
});
|
179
260
|
|
180
261
|
// Store the ongoing fetch promise
|
181
|
-
this.fetchPromises.set(cacheKey, newPromise);
|
262
|
+
this.fetchPromises.set(this.serializeRequestKey(cacheKey), newPromise);
|
182
263
|
|
183
264
|
//initiate cache cleanUp
|
184
265
|
this.debounceCleanupExpiredCache();
|
185
266
|
|
186
267
|
// When fetch completes or fails, remove the promise from the store
|
187
268
|
newPromise.finally(() => {
|
188
|
-
this.fetchPromises.delete(cacheKey);
|
269
|
+
this.fetchPromises.delete(this.serializeRequestKey(cacheKey));
|
189
270
|
});
|
190
271
|
|
191
272
|
return newPromise.then(res => res.clone());
|
@@ -218,7 +299,7 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
218
299
|
const cacheAge = (currentTimestamp - cachedTime) / 1000;
|
219
300
|
if (cacheAge >= ttl) {
|
220
301
|
cache.delete(key);
|
221
|
-
this.
|
302
|
+
this.cachedRequests.delete(key);
|
222
303
|
}
|
223
304
|
});
|
224
305
|
});
|
@@ -266,7 +347,7 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
|
|
266
347
|
cacheEntries.forEach(entry => {
|
267
348
|
if (currentSize > this.cacheLimit) {
|
268
349
|
cache.delete(entry.key);
|
269
|
-
this.
|
350
|
+
this.cachedRequests.delete(entry.key);
|
270
351
|
currentSize -= entry.size;
|
271
352
|
}
|
272
353
|
});
|
package/package.json
CHANGED
@@ -38,16 +38,27 @@ describe('BvFetch', function () {
|
|
38
38
|
|
39
39
|
it('should generate correct cache key', function () {
|
40
40
|
const url = 'https://jsonplaceholder.typicode.com/todos';
|
41
|
-
const options = {
|
42
|
-
|
41
|
+
const options = {
|
42
|
+
method: 'GET',
|
43
|
+
headers: {
|
44
|
+
'X-Header-1': 'Value 1',
|
45
|
+
}
|
46
|
+
};
|
47
|
+
const expectedKey = new Request(url, options)
|
43
48
|
const generatedKey = bvFetchInstance.generateCacheKey(url, options);
|
44
|
-
expect(generatedKey).to.equal(expectedKey);
|
49
|
+
expect(generatedKey.url).to.equal(expectedKey.url);
|
50
|
+
expect(generatedKey.headers).to.deep.equal(expectedKey.headers);
|
45
51
|
});
|
46
52
|
|
47
53
|
|
48
54
|
it('should fetch from cache when the response is cached', function (done) {
|
49
55
|
const url = 'https://jsonplaceholder.typicode.com/todos';
|
50
|
-
const options = {
|
56
|
+
const options = {
|
57
|
+
method: 'GET',
|
58
|
+
headers: {
|
59
|
+
'X-Header-1': 'Value 1',
|
60
|
+
}
|
61
|
+
};
|
51
62
|
|
52
63
|
// Mocking cache response
|
53
64
|
const mockResponse = new Response('Mock Data', {
|
@@ -64,7 +75,8 @@ describe('BvFetch', function () {
|
|
64
75
|
// Overriding the stub for this specific test case
|
65
76
|
caches.open.resolves({
|
66
77
|
match: (key) => {
|
67
|
-
expect(key).to.equal(cacheKey);
|
78
|
+
expect(key.url).to.equal(cacheKey.url);
|
79
|
+
expect(key.headers).to.deep.equal(cacheKey.headers);
|
68
80
|
return Promise.resolve(mockResponse)
|
69
81
|
},
|
70
82
|
put: (key, response) => {
|
@@ -74,7 +86,7 @@ describe('BvFetch', function () {
|
|
74
86
|
});
|
75
87
|
|
76
88
|
// Simulate that the response is cached
|
77
|
-
bvFetchInstance.
|
89
|
+
bvFetchInstance.cachedRequests.add(cacheKey);
|
78
90
|
|
79
91
|
// Call the function under test
|
80
92
|
bvFetchInstance.bvFetchFunc(url, options)
|