diff --git a/packages/cache-utils/src/constants/metaTypes.ts b/packages/cache-utils/src/constants/metaTypes.ts index a232928..8bb385d 100644 --- a/packages/cache-utils/src/constants/metaTypes.ts +++ b/packages/cache-utils/src/constants/metaTypes.ts @@ -1 +1,2 @@ export const CACHE = 'cache'; +export const PROTOCOL_HTTP = 'PROTOCOL_HTTP'; diff --git a/packages/plugin-cache-memory/src/memory.spec.ts b/packages/plugin-cache-memory/src/memory.spec.ts index ed38072..fab1aaa 100644 --- a/packages/plugin-cache-memory/src/memory.spec.ts +++ b/packages/plugin-cache-memory/src/memory.spec.ts @@ -23,6 +23,7 @@ const next = jest.fn(); const context = new Context({ request: { url: 'test' } }); context.updateExternalMeta = jest.fn(context.updateExternalMeta.bind(context)); +context.updateInternalMeta = jest.fn(context.updateInternalMeta.bind(context)); describe('plugins/cache/memory', () => { beforeEach(() => { @@ -45,10 +46,10 @@ describe('plugins/cache/memory', () => { }); it('init, value from cache', () => { - const response = { a: 1 }; + const responseData = { response: {a: 1}, status: 200 }; mockLru.has.mockImplementation(() => true); - mockLru.get.mockImplementation(() => response); + mockLru.get.mockImplementation(() => responseData); plugin.init(context, next, null); expect(mockLru.get).toHaveBeenCalledWith('test'); @@ -56,19 +57,24 @@ describe('plugins/cache/memory', () => { memoryCache: true, memoryCacheOutdated: false, }); + expect(context.updateInternalMeta).toHaveBeenCalledWith(metaTypes.PROTOCOL_HTTP, { + response: { + status: responseData.status + } + }); expect(next).toHaveBeenCalledWith({ - response, + response: responseData.response, status: Status.COMPLETE, }); }); it('init, value from cache outdated, but allowStale is false', () => { const plugin = memoryCache({ allowStale: false }); - const response = { a: 1 }; + const responseData = { response: {a: 1}, status: 200 }; const makeRequest: any = jest.fn(() => Promise.resolve()); mockLru.has.mockImplementation(() => false); - mockLru.peek.mockImplementation(() => response); + mockLru.peek.mockImplementation(() => responseData); plugin.init(context, next, makeRequest); jest.runAllTimers(); @@ -80,36 +86,36 @@ describe('plugins/cache/memory', () => { it('init, value in cache is outdated and allowStale is true', () => { const plugin = memoryCache({ allowStale: true, staleTtl: 523 }); - const response = { a: 1 }; + const responseData = { response: {a: 1}, status: 200 }; const makeRequest: any = jest.fn(() => Promise.resolve()); mockLru.has.mockImplementation(() => false); - mockLru.peek.mockImplementation(() => response); + mockLru.peek.mockImplementation(() => responseData); plugin.init(context, next, makeRequest); jest.runAllTimers(); expect(mockLru.get).not.toHaveBeenCalledWith('test'); - expect(mockLru.set).toHaveBeenCalledWith('test', response, { ttl: 523 }); + expect(mockLru.set).toHaveBeenCalledWith('test', responseData, { ttl: 523 }); expect(makeRequest).toHaveBeenCalledWith({ url: 'test', memoryCacheForce: true, memoryCacheBackground: true }); expect(context.updateExternalMeta).toHaveBeenCalledWith(metaTypes.CACHE, { memoryCache: true, memoryCacheOutdated: true, }); expect(next).toHaveBeenCalledWith({ - response, + response: responseData.response, status: Status.COMPLETE, }); }); it('init, value in cache is outdated and request memoryCacheAllowStale is true', () => { const plugin = memoryCache({ allowStale: false }); - const response = { a: 1 }; + const responseData = { response: {a: 1}, status: 200 }; const makeRequest: any = jest.fn(() => Promise.resolve()); context.setState({ request: { url: 'test', memoryCacheAllowStale: true } }); mockLru.has.mockImplementation(() => false); - mockLru.peek.mockImplementation(() => response); + mockLru.peek.mockImplementation(() => responseData); plugin.init(context, next, makeRequest); jest.runAllTimers(); @@ -126,18 +132,18 @@ describe('plugins/cache/memory', () => { memoryCacheOutdated: true, }); expect(next).toHaveBeenCalledWith({ - response, + response: responseData.response, status: Status.COMPLETE, }); }); it('on complete saves to cache', () => { - const response = { a: 1 }; + const responseData = { response: {a: 1}, status: 200 }; - context.setState({ response, request: { url: 'test', memoryCacheTtl: 123 } }); + context.setState({ response: responseData.response, request: { url: 'test', memoryCacheTtl: 123 } }); plugin.complete(context, next, null); expect(next).toHaveBeenCalled(); - expect(mockLru.set).toHaveBeenCalledWith('test', response, { ttl: 123 }); + expect(mockLru.set).toHaveBeenCalledWith('test', responseData, { ttl: 123 }); }); }); diff --git a/packages/plugin-cache-memory/src/memory.ts b/packages/plugin-cache-memory/src/memory.ts index 0a72082..faec5a2 100644 --- a/packages/plugin-cache-memory/src/memory.ts +++ b/packages/plugin-cache-memory/src/memory.ts @@ -25,10 +25,15 @@ export interface MemoryPluginOptions { allowStale?: boolean; staleTtl?: number; staleBackgroundRequestTimeout?: number; - memoryConstructor?: (options: Options) => LRUCache; + memoryConstructor?: (options: Options) => LRUCache; getCacheKey?: (arg) => string; } +interface StoredValue { + response: Response + status?: number +} + /** * Caches requests response into memory. * Uses library `@tinkoff/lru-cache-nano` as memory storage. @@ -63,7 +68,7 @@ export default ({ memoryConstructor = (options: Options) => new (require('@tinkoff/lru-cache-nano'))(options), getCacheKey = undefined, }: MemoryPluginOptions = {}): Plugin => { - const lruCache: LRUCache = memoryConstructor({ + const lruCache: LRUCache = memoryConstructor({ ...lruOptions, allowStale: true, // should be true for the opportunity to control it for individual requests }); @@ -80,9 +85,19 @@ export default ({ memoryCacheOutdated: false, }); + const cashedValue = lruCache.get(cacheKey); + // when plugin break flow, plugin-http won't be called and meta will be empty, + // so we need to enrich the meta with status data, as it is done in a normal flow + if (cashedValue.status) { + context.updateInternalMeta(metaTypes.PROTOCOL_HTTP, { + response: { + status: cashedValue.status + } + }); + } return next({ status: Status.COMPLETE, - response: lruCache.get(cacheKey), + response: cashedValue.response, }); } @@ -96,6 +111,13 @@ export default ({ memoryCache: true, memoryCacheOutdated: true, }); + if (outdated.status) { + context.updateInternalMeta(metaTypes.PROTOCOL_HTTP, { + response: { + status: outdated.status + } + }); + } lruCache.set(cacheKey, outdated, { ttl: staleTtl }); // remember outdated value, to prevent losing it setTimeout( @@ -123,7 +145,7 @@ export default ({ return next({ status: Status.COMPLETE, - response: outdated, + response: outdated.response, }); } @@ -133,7 +155,16 @@ export default ({ const cacheKey = getCacheKeyUtil(context, getCacheKey); const ttl: number = prop('memoryCacheTtl', context.getRequest()); - lruCache.set(cacheKey, context.getResponse(), { ttl }); + const value: StoredValue = { + response: context.getResponse(), + }; + + const httpMeta = context.getInternalMeta(metaTypes.PROTOCOL_HTTP); + + if (httpMeta?.response?.status) { + value.status = httpMeta.response.status + } + lruCache.set(cacheKey, value, { ttl }); context.updateExternalMeta(metaTypes.CACHE, { memoryCacheBackground: prop('memoryCacheBackground', context.getRequest()),