-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* adding new module, bvFetch * adding bvFetch module * changes to the err callback implementation * updating module structure * adding function description * adding error unit test case * updating test cases and readme * updating README * updating bv-ui-core README * changing callback name * updating readme * 2.9.0 * updating test case * updating test case
- Loading branch information
1 parent
1970c92
commit d9b40ba
Showing
6 changed files
with
334 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# BvFetch | ||
|
||
The BvFetch module provides methods to cache duplicate API calls and interact with the cacheStorage | ||
|
||
|
||
## The following methods are provided: | ||
|
||
## BvFetch Parameters | ||
`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. | ||
|
||
`cacheName (String):` Optional. Specifies the name of the cache to be used. If not provided, the default cache name 'bvCache' will be used. | ||
|
||
## bvFetchFunc Method Parameters | ||
`url (String):` The URL of the API endpoint to fetch data from. | ||
|
||
`options (Object):` Optional request options such as headers, method, etc., as supported by the Fetch API. | ||
|
||
## bvFetchFunc Return Value | ||
`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. | ||
|
||
## flushCache Method Parameters | ||
This method takes no parameters. | ||
|
||
## flushCache Return Value | ||
`Promise<void>:` A promise indicating the completion of cache flush operation. | ||
|
||
|
||
## Usage with of `BvFetch`: | ||
|
||
```js | ||
var BvFetch = require('bv-ui-core/lib/bvFetch') | ||
|
||
// Initialize BV Fetch instance | ||
const bvFetch = new BVFetch({ | ||
canBeCached: canBeCached, // optional | ||
cacheName: "bvCache" // optional, default is "bvCache" | ||
}); | ||
|
||
// Make API calls using bvFetchFunc method | ||
bvFetch.bvFetchFunc('https://api.example.com/data') | ||
.then(response => { | ||
// Handle response | ||
}) | ||
.catch(error => { | ||
// Handle error | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
|
||
/** | ||
* @fileOverview | ||
* Provides api response caching utilties | ||
*/ | ||
|
||
const { fetch } = require('../polyfills/fetch') | ||
|
||
module.exports = function BvFetch ({ shouldCache, cacheName }) { | ||
this.shouldCache = shouldCache; | ||
this.cacheName = cacheName || 'bvCache'; | ||
this.fetchPromises = new Map(); | ||
|
||
/** | ||
* Generates a unique cache key for the given URL and options. | ||
* @param {string} url - The URL of the API endpoint. | ||
* @param {Object} options - Optional request options. | ||
* @returns {string} The generated cache key. | ||
*/ | ||
|
||
this.generateCacheKey = (url, options) => { | ||
const optionsString = (Object.keys(options).length > 0) ? JSON.stringify(options) : ''; | ||
const key = url + optionsString; | ||
return key; | ||
}; | ||
|
||
/** | ||
* Fetches data from the API endpoint, caches responses, and handles caching logic. | ||
* @param {string} url - The URL of the API endpoint. | ||
* @param {Object} options - Optional request options. | ||
* @returns {Promise<Response>} A promise resolving to the API response. | ||
*/ | ||
|
||
this.bvFetchFunc = (url, options = {}) => { | ||
// get the key | ||
const cacheKey = this.generateCacheKey(url, options); | ||
|
||
// check if its available in the cache | ||
return caches.open(this.cacheName) | ||
.then(currentCache => currentCache.match(cacheKey)) | ||
.then(cachedResponse => { | ||
if (cachedResponse) { | ||
const cachedTime = cachedResponse.headers.get('X-Cached-Time'); | ||
const ttl = cachedResponse.headers.get('Cache-Control').match(/max-age=(\d+)/)[1]; | ||
const currentTimestamp = Date.now(); | ||
const cacheAge = (currentTimestamp - cachedTime) / 1000; | ||
|
||
if (cacheAge < ttl) { | ||
// Cached response found | ||
return cachedResponse.clone(); | ||
} | ||
} | ||
|
||
// check if there is an ongoing promise | ||
if (this.fetchPromises.has(cacheKey)) { | ||
return this.fetchPromises.get(cacheKey); | ||
} | ||
|
||
// Make a new call | ||
const newPromise = fetch(url, options); | ||
|
||
// Push the newPromise to the fetchPromises Map | ||
this.fetchPromises.set(cacheKey, newPromise); | ||
|
||
return newPromise | ||
.then(response => { | ||
const clonedResponse = response.clone(); | ||
const errJson = clonedResponse.clone() | ||
let canBeCached = true; | ||
return errJson.json().then(json => { | ||
if (typeof this.shouldCache === 'function') { | ||
canBeCached = this.shouldCache(json); | ||
} | ||
return response | ||
}).then(res => { | ||
if (canBeCached) { | ||
const newHeaders = new Headers(); | ||
clonedResponse.headers.forEach((value, key) => { | ||
newHeaders.append(key, value); | ||
}); | ||
newHeaders.append('X-Cached-Time', Date.now()); | ||
|
||
const newResponse = new Response(clonedResponse._bodyBlob, { | ||
status: clonedResponse.status, | ||
statusText: clonedResponse.statusText, | ||
headers: newHeaders | ||
}); | ||
//Delete promise from promise map once its resolved | ||
this.fetchPromises.delete(cacheKey); | ||
|
||
return caches.open(this.cacheName) | ||
.then(currentCache => | ||
currentCache.put(cacheKey, newResponse) | ||
) | ||
.then(() => res); | ||
} | ||
else { | ||
//Delete promise from promise map if error exists | ||
this.fetchPromises.delete(cacheKey); | ||
|
||
return res | ||
} | ||
|
||
}); | ||
}) | ||
}) | ||
.catch(err => { | ||
// Remove the promise that was pushed earlier | ||
this.fetchPromises.delete(cacheKey); | ||
throw err; | ||
}); | ||
}; | ||
|
||
/** | ||
* Clears all cache entries stored in the cache storage. | ||
* @returns {Promise<void>} A promise indicating cache flush completion. | ||
*/ | ||
|
||
this.flushCache = () => { | ||
return caches.open(this.cacheName).then(cache => { | ||
return cache.keys().then(keys => { | ||
const deletionPromises = keys.map(key => cache.delete(key)); | ||
return Promise.all(deletionPromises); | ||
}); | ||
}); | ||
}; | ||
|
||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
//Imports | ||
|
||
var BvFetch = require('../../../lib/bvFetch'); | ||
|
||
describe('BvFetch', function () { | ||
let bvFetchInstance; | ||
let cacheStub; | ||
let cacheStorage; | ||
|
||
beforeEach(function () { | ||
bvFetchInstance = new BvFetch({ | ||
shouldCache: null, | ||
cacheName: 'testCache' | ||
}); | ||
|
||
// Define cacheStorage as a Map | ||
cacheStorage = new Map(); | ||
|
||
// Stubbing caches.open | ||
cacheStub = sinon.stub(caches, 'open').resolves({ | ||
match: key => { | ||
const cachedResponse = cacheStorage.get(key); | ||
return Promise.resolve(cachedResponse); | ||
}, | ||
put: (key, response) => { | ||
cacheStorage.set(key, response); | ||
return Promise.resolve(); | ||
} | ||
}); | ||
|
||
}); | ||
|
||
afterEach(function () { | ||
bvFetchInstance = null; | ||
// Restore the original method after each test | ||
caches.open.restore(); | ||
}); | ||
|
||
it('should generate correct cache key', function () { | ||
const url = 'https://jsonplaceholder.typicode.com/todos'; | ||
const options = {}; | ||
const expectedKey = 'https://jsonplaceholder.typicode.com/todos'; | ||
const generatedKey = bvFetchInstance.generateCacheKey(url, options); | ||
expect(generatedKey).to.equal(expectedKey); | ||
}); | ||
|
||
|
||
it('should fetch from cache when the response is cached', function (done) { | ||
const url = 'https://jsonplaceholder.typicode.com/todos'; | ||
const options = {}; | ||
|
||
// Mocking cache response | ||
const mockResponse = new Response('Mock Data', { | ||
status: 200, | ||
statusText: 'OK', | ||
headers: { | ||
'Cache-Control': 'max-age=3600', | ||
'X-Cached-Time': Date.now() | ||
} | ||
}); | ||
|
||
const cacheKey = bvFetchInstance.generateCacheKey(url, options); | ||
|
||
// Overriding the stub for this specific test case | ||
caches.open.resolves({ | ||
match: (key) => { | ||
expect(key).to.equal(cacheKey); | ||
Promise.resolve(mockResponse) | ||
}, | ||
put: (key, response) => { | ||
cacheStorage.set(key, response); | ||
return Promise.resolve(); | ||
} | ||
}); | ||
|
||
bvFetchInstance.bvFetchFunc(url, options) | ||
.then(response => { | ||
// Check if response is fetched from cache | ||
expect(response).to.not.be.null; | ||
|
||
// Check if response is cached | ||
const cachedResponse = cacheStorage.get(cacheKey); | ||
expect(cachedResponse).to.not.be.null; | ||
|
||
// Check if caches.open was called | ||
expect(cacheStub.called).to.be.true; | ||
|
||
done(); | ||
}) | ||
.catch(error => { | ||
done(error); // Call done with error if any | ||
}) | ||
}); | ||
|
||
|
||
it('should fetch from network when response is not cached', function (done) { | ||
const url = 'https://jsonplaceholder.typicode.com/todos'; | ||
const options = {}; | ||
|
||
const cacheKey = bvFetchInstance.generateCacheKey(url, options); | ||
|
||
caches.open.resolves({ | ||
match: (key) => { | ||
expect(key).to.equal(cacheKey); | ||
Promise.resolve(null) | ||
}, | ||
put: (key, response) => { | ||
cacheStorage.set(key, response); | ||
return Promise.resolve(); | ||
} | ||
}); | ||
|
||
|
||
bvFetchInstance.bvFetchFunc(url, options) | ||
.then(response => { | ||
// Check if response is fetched from network | ||
expect(response).to.not.be.null; | ||
console.log(response.body) | ||
|
||
// Check if caches.match was called | ||
expect(cacheStub.called).to.be.true; | ||
|
||
done(); | ||
}) | ||
.catch(done); | ||
}); | ||
|
||
it('should not cache response when there is an error', function (done) { | ||
const url = 'https://jsonplaceholder.typicode.com/todos'; | ||
const options = {}; | ||
|
||
// Define shouldCache directly in bvFetchInstance | ||
bvFetchInstance.shouldCache = (res) => { | ||
return false | ||
}; | ||
|
||
bvFetchInstance.bvFetchFunc(url, options) | ||
.then(response => { | ||
// Check if response is fetched from network | ||
expect(response).to.not.be.null; | ||
console.log(response.body) | ||
|
||
// Check if caches.match was called | ||
expect(cacheStub.calledOnce).to.be.true; | ||
|
||
// Check if response is not cached | ||
const cachedResponse = cacheStorage.get(url); | ||
expect(cachedResponse).to.be.undefined; | ||
|
||
done(); | ||
}) | ||
.catch(done); | ||
}); | ||
|
||
|
||
}); |