-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds ability to cache requests
Closes #28
- Loading branch information
Showing
11 changed files
with
291 additions
and
4 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
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,82 @@ | ||
import { DefaultHttpCache as Cache } from '../http-cache'; | ||
|
||
describe('DefaultHttpCache', () => { | ||
const createCacheWithInfinityTtl = () => { | ||
const cache = new Cache(); | ||
cache.ttl = Infinity; | ||
return cache; | ||
}; | ||
|
||
it('should return the same singleton instance on every call', () => { | ||
expect(Cache.shared).toBeInstanceOf(Cache); | ||
expect(Cache.shared).toBe(Cache.shared); | ||
expect(new Cache()).not.toBe(Cache.shared); | ||
}); | ||
it('should serialize http payload to a string', () => { | ||
const cache = createCacheWithInfinityTtl(); | ||
const payload = { | ||
method: 'GET', | ||
headers: { hello: 'world' }, | ||
body: { hi: 'there' }, | ||
url: 'https://example.com', | ||
}; | ||
const key = cache.serializeCacheKey(payload); | ||
expect(typeof key).toBe('string'); | ||
expect(cache.serializeCacheKey(payload)).toBe(key); | ||
expect( | ||
cache.serializeCacheKey({ | ||
...payload, | ||
url: 'https://example2.com', | ||
}), | ||
).not.toBe(key); | ||
expect(cache.serializeCacheKey({ ...payload })).toBe(key); | ||
}); | ||
it('should update ttl only if one is valid', () => { | ||
const cache = new Cache(); | ||
cache.ttl = 0; | ||
expect(cache.ttl).toBe(0); | ||
cache.ttl = Infinity; | ||
expect(cache.ttl).toBe(Infinity); | ||
cache.ttl = 10; | ||
expect(cache.ttl).toBe(10); | ||
cache.ttl = -1; | ||
expect(cache.ttl).toBe(10); | ||
cache.ttl = true as any; | ||
expect(cache.ttl).toBe(10); | ||
}); | ||
it('should insert new cache entries', () => { | ||
const cache = createCacheWithInfinityTtl(); | ||
expect(cache.set('key', 1).get('key')).toBe(1); | ||
expect(cache.set('key2', { hello: 'world' }).get('key2')).toStrictEqual({ hello: 'world' }); | ||
}); | ||
it('should delete cache entries', () => { | ||
const cache = createCacheWithInfinityTtl(); | ||
cache.set('key2', 2); | ||
expect(cache.set('key', 1).delete('key').get('key')).toBeNull(); | ||
expect(cache.get('key2')).toBe(2); | ||
}); | ||
it('should clear cache', () => { | ||
const cache = createCacheWithInfinityTtl(); | ||
cache.set('key', 1).set('key2', 2); | ||
expect(cache.size).toBe(2); | ||
cache.reset(); | ||
expect(cache.size).toBe(0); | ||
}); | ||
it('should delete cache entries after their expiration', (done) => { | ||
const cache = createCacheWithInfinityTtl(); | ||
cache.set('key', 1); | ||
cache.ttl = 0; | ||
cache.set('key2', 2); | ||
cache.ttl = 25; | ||
cache.set('key3', 3); | ||
cache.ttl = 100; | ||
cache.set('key4', 4); | ||
setTimeout(() => { | ||
expect(cache.get('key')).toBe(1); | ||
expect(cache.get('key2')).toBeNull(); | ||
expect(cache.get('key3')).toBeNull(); | ||
expect(cache.get('key4')).toBe(4); | ||
done(); | ||
}, 50); | ||
}); | ||
}); |
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,59 @@ | ||
import type { SerializeCacheKeyPayload } from './types'; | ||
|
||
export abstract class HttpCache { | ||
/** | ||
* Получить данные из кеша | ||
* @param key - Уникальный ключ кеша | ||
* @example | ||
* ```ts | ||
* cache.get('key'); | ||
* ``` | ||
*/ | ||
public abstract get<T = any>(key: string): T | null; | ||
|
||
/** | ||
* Добавить данные в кеш | ||
* @param key - Уникальный ключ кеша | ||
* @param data - Данные для добавления | ||
* @example | ||
* ```ts | ||
* cache.set('key', { ok: true }); | ||
* ``` | ||
*/ | ||
public abstract set(key: string, data: any, ...rest: any): any; | ||
|
||
/** | ||
* Удалить закешированные данные по ключу | ||
* @param key - Уникальный ключ кеша | ||
* @xample | ||
* ```ts | ||
* cache.delete('key'); | ||
* ``` | ||
*/ | ||
public abstract delete(key: string): any; | ||
|
||
/** | ||
* Полностью очистить кеш | ||
*/ | ||
public abstract reset(): any; | ||
|
||
/** | ||
* Сгенерировать уникальный ключ кеша из параметров http-запроса | ||
* @example | ||
* ```ts | ||
* cache.serializeCacheKey({ | ||
* url: 'https://example.com', | ||
* body: { key: "value" }, | ||
* method: "POST" | ||
* }) | ||
* ``` | ||
*/ | ||
public serializeCacheKey(payload: SerializeCacheKeyPayload): string { | ||
try { | ||
return JSON.stringify(payload); | ||
} catch (_e) { | ||
// на случай попытки сериализации объекта с циклическими зависимостями внутри | ||
return payload.url + String(Math.random()); | ||
} | ||
} | ||
} |
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,84 @@ | ||
import { HttpCache } from './abstract'; | ||
import type { HttpCacheEntry } from './types'; | ||
|
||
const minute = 60000; | ||
|
||
export class DefaultHttpCache extends HttpCache { | ||
private static sharedInstance: DefaultHttpCache; | ||
|
||
private _map = new Map<string, HttpCacheEntry>(); | ||
|
||
private _ttl = 10 * minute; | ||
|
||
/** | ||
* Синглтон | ||
* @example | ||
* ```ts | ||
* cache.shared.get('key'); | ||
* ``` | ||
*/ | ||
public static get shared(): DefaultHttpCache { | ||
if (!DefaultHttpCache.sharedInstance) { | ||
DefaultHttpCache.sharedInstance = new DefaultHttpCache(); | ||
} | ||
return DefaultHttpCache.sharedInstance; | ||
} | ||
|
||
/** | ||
* Время жизни кеша в миллисекундах | ||
* @example | ||
* ```ts | ||
* cache.ttl = 60000; | ||
* cache.ttl = Infinity; | ||
* cache.tll = 0; | ||
* | ||
* // негативные значения игнорируются | ||
* cache.ttl = -1; | ||
* cache.ttl = Number.NEGATIVE_INFINITY; | ||
* ``` | ||
*/ | ||
public get ttl(): number { | ||
return this._ttl; | ||
} | ||
|
||
public set ttl(ttl: number) { | ||
if (typeof ttl === 'number' && ttl >= 0) { | ||
this._ttl = ttl; | ||
} | ||
} | ||
|
||
/** | ||
* Количество элементов в кеше | ||
*/ | ||
public get size(): number { | ||
return this._map.size; | ||
} | ||
|
||
public get<T = any>(key: string) { | ||
const data = this._map.get(key); | ||
if (!data) return null; | ||
if (data.expires <= Date.now()) { | ||
this.delete(key); | ||
return null; | ||
} | ||
return data.data as T; | ||
} | ||
|
||
public set(key: string, data: any): this { | ||
this._map.set(key, { | ||
data, | ||
expires: Date.now() + this.ttl, | ||
}); | ||
return this; | ||
} | ||
|
||
public delete(key: string): this { | ||
this._map.delete(key); | ||
return this; | ||
} | ||
|
||
public reset(): this { | ||
this._map.clear(); | ||
return this; | ||
} | ||
} |
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,2 @@ | ||
export { HttpCache } from './abstract'; | ||
export { DefaultHttpCache } from './default-cache'; |
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,11 @@ | ||
export interface HttpCacheEntry { | ||
data: any; | ||
expires: number; | ||
} | ||
|
||
export interface SerializeCacheKeyPayload { | ||
headers?: Record<string, string>; | ||
method?: string; | ||
url: string; | ||
body?: Record<string, any>; | ||
} |
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 |
---|---|---|
|
@@ -44,3 +44,4 @@ export { | |
DaDataFioSuggestion, | ||
DaDataGender, | ||
}; | ||
export { HttpCache } from './http-cache'; |
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