diff --git a/src/cache.service.spec.ts b/src/cache.service.spec.ts index f3825ed..55dfa8d 100644 --- a/src/cache.service.spec.ts +++ b/src/cache.service.spec.ts @@ -102,7 +102,7 @@ describe('CacheService', () => { const response = await fetch(dataURL); const blob = await response.blob(); - await service.saveBlobItem(blobKey, blob, groupKey, ttl); + await service.saveItem(blobKey, blob, groupKey, ttl); done(); } catch (e) { expect(e).toBeUndefined(); @@ -115,7 +115,7 @@ describe('CacheService', () => { const response = await fetch(dataURL); const blob = await response.blob(); - let value = await service.getBlobItem(blobKey); + const value = await service.getItem(blobKey); expect(value).toEqual(blob); done(); } catch (e) { @@ -173,7 +173,7 @@ describe('CacheService', () => { it('should throw error because cache blob expired (async)', done => { setTimeout(async () => { try { - await service.getBlobItem(blobKey); + await service.getItem(blobKey); expect(false).toBeTruthy(); done(); @@ -576,7 +576,7 @@ describe('Observable blob caching errors', () => { }); it('should return data from observable (async)', (done: any) => { - service.loadFromBlobObservable(key, observableError).subscribe( + service.loadFromObservable(key, observableError).subscribe( res => { expect(true).toBeFalsy(); done(); @@ -602,9 +602,9 @@ describe('Observable Blob Caching', () => { const delay = ms => new Promise(res => setTimeout(res, ms)); - let mockBlob: Blob = new Blob(['Hello, world!'], {type: 'text/plain'}); + const mockBlob: Blob = new Blob(['Hello, world!'], {type: 'text/plain'}); - let observable = of(mockBlob); + const observable = of(mockBlob); let service: CacheService; @@ -629,7 +629,7 @@ describe('Observable Blob Caching', () => { }); it('should return blob data from observable (async)', (done: any) => { - service.loadFromBlobObservable(key, observable).subscribe( + service.loadFromObservable(key, observable).subscribe( async res => { expect(res).toBeDefined(); expect(observable.subscribe).toHaveBeenCalled(); @@ -645,7 +645,7 @@ describe('Observable Blob Caching', () => { }); it('should return cached observable blob data (async)', done => { - service.loadFromBlobObservable(key, observable).subscribe(res => { + service.loadFromObservable(key, observable).subscribe(res => { expect(observable.subscribe).not.toHaveBeenCalled(); expect(res).toEqual(mockBlob); done(); diff --git a/src/cache.service.ts b/src/cache.service.ts index e2f94ef..8a2a2a0 100644 --- a/src/cache.service.ts +++ b/src/cache.service.ts @@ -37,6 +37,23 @@ const isHttpResponse = (data: any): boolean => { return data && (data instanceof HttpResponse || orCondition); }; +const isJsOrResponseType = (data: any): boolean => { + const jsType = + data.type === 'undefined' || + data.type === 'object' || + data.type === 'boolean' || + data.type === 'number' || + data.type === 'bigint' || + data.type === 'string' || + data.type === 'symbol' || + data.type === 'function'; + + const responseType = + data.type === 'response'; + + return responseType || jsType; +} + @Injectable() export class CacheService { private ttl: number = 60 * 60; // one hour @@ -151,6 +168,10 @@ export class CacheService { throw new Error(MESSAGES[1]); } + if (Blob.name === data.constructor.name) { + return this.saveBlobItem(key, data, groupKey, ttl); + } + const expires = new Date().getTime() + ttl * 1000, type = isHttpResponse(data) ? 'response' : typeof data, value = JSON.stringify(data); @@ -171,7 +192,7 @@ export class CacheService { * @param {number} [ttl] - TTL in seconds * @return {Promise} - saved data */ - async saveBlobItem( + private async saveBlobItem( key: string, blob: any, groupKey: string = 'none', @@ -306,25 +327,6 @@ export class CacheService { return CacheService.decodeRawData(data); } - /** - * @description Get blob item from cache with expire check and correct type assign - * @param {string} key - Unique key - * @return {Promise} - promise that resolves with blob data from cache - */ - async getBlobItem(key: string): Promise { - if (!this.cacheEnabled) { - throw new Error(MESSAGES[1]); - } - - let data = await this.getRawItem(key); - - if (data.expires < new Date().getTime() && (this.invalidateOffline || this.isOnline())) { - throw new Error(MESSAGES[2] + key); - } - - return CacheService.decodeRawBlobData(data); - } - async getOrSetItem( key: string, factory: CacheValueFactory, @@ -348,35 +350,28 @@ export class CacheService { * @param {any} data - Data * @return {any} - decoded data */ - static decodeRawData(data: StorageCacheItem): any { + static async decodeRawData(data: StorageCacheItem): Promise { let dataJson = JSON.parse(data.value); - if (isHttpResponse(dataJson)) { - let response: any = { - body: dataJson._body || dataJson.body, - status: dataJson.status, - headers: dataJson.headers, - statusText: dataJson.statusText, - url: dataJson.url - }; - - return new HttpResponse(response); - } - - return dataJson; - } - - /** - * @description Decode raw blob data from DB - * @param {any} data - Data - * @return {Promise} - promise that resolves with a Blob. - */ - static async decodeRawBlobData(data: StorageCacheItem): Promise { - const dataURL = JSON.parse(data.value); + if (isJsOrResponseType(data)) { + if (isHttpResponse(dataJson)) { + let response: any = { + body: dataJson._body || dataJson.body, + status: dataJson.status, + headers: dataJson.headers, + statusText: dataJson.statusText, + url: dataJson.url + }; + + return new HttpResponse(response); + } - // Technique derived from: https://stackoverflow.com/a/36183085 - const response = await fetch(dataURL); + return dataJson; + } else { + // Technique derived from: https://stackoverflow.com/a/36183085 + const response = await fetch(dataJson); - return response.blob(); + return response.blob(); + } } /** @@ -470,8 +465,8 @@ export class CacheService { }) .catch(e => { this.getRawItem(key) - .then(res => { - let result = CacheService.decodeRawData(res); + .then(async(res) => { + let result = await CacheService.decodeRawData(res); if (metaKey) { result[metaKey] = result[metaKey] || {}; result[metaKey].fromCache = true; @@ -485,42 +480,6 @@ export class CacheService { return observableSubject.asObservable(); } - /** - * @description Load blob item from cache if it's in cache or load from origin observable - * @param {string} key - Unique key - * @param {any} observable - Observable with blob data - * @param {string} [groupKey] - group key - * @param {number} [ttl] - TTL in seconds - * @return {Observable} - blob data from cache or origin observable - */ - loadFromBlobObservable( - key: string, - observable: Observable, - groupKey?: string, - ttl?: number - ): Observable { - if (!this.cacheEnabled) return observable; - - observable = observable.pipe(share()); - - return defer(() => { - return from(this.getBlobItem(key)).pipe( - catchError(e => { - observable.subscribe( - blob => { - return this.saveBlobItem(key, blob, groupKey, ttl); - }, - error => { - return throwError(error); - } - ); - - return observable; - }) - ); - }); - } - /** * Perform complete cache clear * @return {Promise}