From 7c0c11d50925bb4449c122294a6210075ec9e8f0 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 14 Nov 2019 10:52:47 +0200 Subject: [PATCH] cache replace factory functions to class rename CacheManager to CacheProvider remove keysStorageIn MemoryStorage (now is using keys from map) --- lib/cache/CacheOptions.ts | 12 +++---- lib/cache/cacheManager/ClassCacheManager.ts | 23 ------------ .../cacheManager/InstanceCacheManager.ts | 26 -------------- lib/cache/cacheManager/factory.ts | 27 -------------- .../CacheProvider.ts} | 2 +- lib/cache/cacheProvider/ClassCacheProvider.ts | 19 ++++++++++ .../cacheProvider/InstanceCacheProvider.ts | 22 ++++++++++++ lib/cache/cacheProvider/factory.ts | 35 +++++++++++++++++++ lib/cache/caches/factory.ts | 25 ++++++++----- lib/cache/expirations/factory.ts | 35 +++++++++++++------ lib/cache/index.ts | 30 +++++++++------- lib/cache/storages/MemoryStorage.ts | 28 +++------------ lib/cache/storages/factory.ts | 27 ++++++++------ test/{ => cache}/cache.spec.ts | 4 +-- 14 files changed, 163 insertions(+), 152 deletions(-) delete mode 100644 lib/cache/cacheManager/ClassCacheManager.ts delete mode 100644 lib/cache/cacheManager/InstanceCacheManager.ts delete mode 100644 lib/cache/cacheManager/factory.ts rename lib/cache/{cacheManager/CacheManager.ts => cacheProvider/CacheProvider.ts} (76%) create mode 100644 lib/cache/cacheProvider/ClassCacheProvider.ts create mode 100644 lib/cache/cacheProvider/InstanceCacheProvider.ts create mode 100644 lib/cache/cacheProvider/factory.ts rename test/{ => cache}/cache.spec.ts (99%) diff --git a/lib/cache/CacheOptions.ts b/lib/cache/CacheOptions.ts index ad3f96b..43a33dc 100644 --- a/lib/cache/CacheOptions.ts +++ b/lib/cache/CacheOptions.ts @@ -26,13 +26,9 @@ export type CacheOptions = { size?: number, }; -export const DEFAULT_EXPIRATION = 'absolute'; -export const DEFAULT_SCOPE = 'class'; -export const DEFAULT_STORAGE = 'memory'; -export const DEFAULT_SIZE = null; export const DEFAULT_OPTIONS: CacheOptions = { - expiration: DEFAULT_EXPIRATION, - scope: DEFAULT_SCOPE, - storage: DEFAULT_STORAGE, - size: DEFAULT_SIZE, + expiration: 'absolute', + scope: 'class', + storage: 'memory', + size: null, }; diff --git a/lib/cache/cacheManager/ClassCacheManager.ts b/lib/cache/cacheManager/ClassCacheManager.ts deleted file mode 100644 index 0a444e9..0000000 --- a/lib/cache/cacheManager/ClassCacheManager.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Cache } from '../caches/Cache'; -import { CacheManager } from './CacheManager'; -import { cacheFactory } from '../caches/factory'; -import { CacheOptions } from '../CacheOptions'; - -export class ClassCacheManager implements CacheManager { - - private cacheInstance: Cache = null; - - constructor( - private readonly timeout: number, - private readonly options: Readonly, - ) { } - - public get(): Cache { - if (!this.cacheInstance) { - this.cacheInstance = cacheFactory(this.timeout, this.options); - } - - return this.cacheInstance; - } - -} diff --git a/lib/cache/cacheManager/InstanceCacheManager.ts b/lib/cache/cacheManager/InstanceCacheManager.ts deleted file mode 100644 index fad18cf..0000000 --- a/lib/cache/cacheManager/InstanceCacheManager.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Cache } from '../caches/Cache'; -import { CacheManager } from './CacheManager'; -import { cacheFactory } from '../caches/factory'; -import { CacheOptions } from '../CacheOptions'; -import { ClassType } from '../../interfaces/class'; - -export class InstanceCacheManager implements CacheManager { - - private cacheByInstances = new WeakMap>(); - - constructor( - private readonly timeout: number, - private readonly options: Readonly, - ) { } - - public get(instance: ClassType): Cache { - const shouldCreateCache = !this.cacheByInstances.has(instance); - if (shouldCreateCache) { - const cache = cacheFactory(this.timeout, this.options); - this.cacheByInstances.set(instance, cache); - } - - return this.cacheByInstances.get(instance); - } - -} diff --git a/lib/cache/cacheManager/factory.ts b/lib/cache/cacheManager/factory.ts deleted file mode 100644 index 020096b..0000000 --- a/lib/cache/cacheManager/factory.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CacheOptions } from '../CacheOptions'; -import { CacheManager } from './CacheManager'; -import { ClassCacheManager } from './ClassCacheManager'; -import { InstanceCacheManager } from './InstanceCacheManager'; - -const cacheManagerFactories: ReadonlyMap< - 'class' | 'instance', - (timeout: number, options: CacheOptions) => CacheManager -> = new Map<'class' | 'instance', (timeout: number, options: CacheOptions) => CacheManager>() - .set('class', (timeout, options) => new ClassCacheManager(timeout, options)) - .set('instance', (timeout, options) => new InstanceCacheManager(timeout, options)); - -export function cacheManagerFactory( - timeout: number, - options: CacheOptions, -): CacheManager { - - const { scope } = options; - - const factory = cacheManagerFactories.get(scope); - - if (!factory) { - throw new Error(`@cache invalid scope option: ${scope}.`); - } - - return factory(timeout, options); -} diff --git a/lib/cache/cacheManager/CacheManager.ts b/lib/cache/cacheProvider/CacheProvider.ts similarity index 76% rename from lib/cache/cacheManager/CacheManager.ts rename to lib/cache/cacheProvider/CacheProvider.ts index 64f2d7e..dbd5c6b 100644 --- a/lib/cache/cacheManager/CacheManager.ts +++ b/lib/cache/cacheProvider/CacheProvider.ts @@ -1,6 +1,6 @@ import { ClassType } from '../../interfaces/class'; import { Cache } from '../caches/Cache'; -export interface CacheManager { +export interface CacheProvider { get(instance: ClassType): Cache; } diff --git a/lib/cache/cacheProvider/ClassCacheProvider.ts b/lib/cache/cacheProvider/ClassCacheProvider.ts new file mode 100644 index 0000000..0d36066 --- /dev/null +++ b/lib/cache/cacheProvider/ClassCacheProvider.ts @@ -0,0 +1,19 @@ +import { Cache } from '../caches/Cache'; +import { CacheFactory } from '../caches/factory'; +import { CacheProvider } from './CacheProvider'; + +export class ClassCacheProvider implements CacheProvider { + + private cache: Cache = null; + + constructor(private readonly cacheFactory: CacheFactory) { } + + public get(): Cache { + if (!this.cache) { + this.cache = this.cacheFactory.create(); + } + + return this.cache; + } + +} diff --git a/lib/cache/cacheProvider/InstanceCacheProvider.ts b/lib/cache/cacheProvider/InstanceCacheProvider.ts new file mode 100644 index 0000000..711e0cd --- /dev/null +++ b/lib/cache/cacheProvider/InstanceCacheProvider.ts @@ -0,0 +1,22 @@ +import { ClassType } from '../../interfaces/class'; +import { Cache } from '../caches/Cache'; +import { CacheFactory } from '../caches/factory'; +import { CacheProvider } from './CacheProvider'; + +export class InstanceCacheProvider implements CacheProvider { + + private instanceCaches = new WeakMap>(); + + constructor(private readonly cacheFactory: CacheFactory) { } + + public get(instance: ClassType): Cache { + const hasCache = !this.instanceCaches.has(instance); + if (hasCache) { + const cache = this.cacheFactory.create(); + this.instanceCaches.set(instance, cache); + } + + return this.instanceCaches.get(instance); + } + +} diff --git a/lib/cache/cacheProvider/factory.ts b/lib/cache/cacheProvider/factory.ts new file mode 100644 index 0000000..e2f1e16 --- /dev/null +++ b/lib/cache/cacheProvider/factory.ts @@ -0,0 +1,35 @@ +import { Factory } from '../../interfaces/factory'; +import { CacheFactory } from '../caches/factory'; +import { CacheProvider } from './CacheProvider'; +import { ClassCacheProvider } from './ClassCacheProvider'; +import { InstanceCacheProvider } from './InstanceCacheProvider'; + +export class CacheProviderFactory implements Factory { + + constructor( + private readonly scope: 'class' | 'instance', + private readonly cacheFactory: CacheFactory, + ) { } + + public create() { + switch (this.scope) { + case 'class': + return this.classCacheProvider(); + + case 'instance': + return this.instanceCacheProvider(); + + default: + throw new Error(`@cache invalid scope option: ${this.scope}.`); + } + } + + private classCacheProvider(): ClassCacheProvider { + return new ClassCacheProvider(this.cacheFactory); + } + + private instanceCacheProvider(): InstanceCacheProvider { + return new InstanceCacheProvider(this.cacheFactory); + } + +} diff --git a/lib/cache/caches/factory.ts b/lib/cache/caches/factory.ts index c1a3cbf..856678f 100644 --- a/lib/cache/caches/factory.ts +++ b/lib/cache/caches/factory.ts @@ -1,13 +1,22 @@ +import { Factory } from '../../interfaces/factory'; import { HashService } from '../../utils/hash'; -import { CacheOptions } from '../CacheOptions'; -import { expirationFactory } from '../expirations/factory'; -import { storageFactory } from '../storages/factory'; +import { ExpirationFactory } from '../expirations/factory'; +import { StorageFactory } from '../storages/factory'; import { Cache } from './Cache'; -export function cacheFactory(timeout: number, options: CacheOptions): Cache { - const storage = storageFactory(options); - const expiration = expirationFactory(timeout, options); - const hash = new HashService(); +export class CacheFactory implements Factory> { + + constructor( + private readonly hash: HashService, + private readonly expirationFactory: ExpirationFactory, + private readonly storageFactory: StorageFactory, + ) { } + + public create(): Cache { + const storage = this.storageFactory.create(); + const expiration = this.expirationFactory.create(); + + return new Cache(storage, expiration, this.hash); + } - return new Cache(storage, expiration, hash); } diff --git a/lib/cache/expirations/factory.ts b/lib/cache/expirations/factory.ts index 3775b2e..f6ec58a 100644 --- a/lib/cache/expirations/factory.ts +++ b/lib/cache/expirations/factory.ts @@ -1,21 +1,34 @@ -import { CacheOptions } from '..'; +import { Factory } from '../../interfaces/factory'; import { AbsoluteExpiration } from './AbsoluteExpiration'; import { Expiration } from './Expiration'; import { SlidingExpiration } from './SlidingExpiration'; -const expirationFactories: ReadonlyMap<'absolute' | 'sliding', (timeout: number) => Expiration> = - new Map<'absolute' | 'sliding', (timeout: number) => Expiration>() - .set('absolute', timeout => new AbsoluteExpiration(timeout)) - .set('sliding', timeout => new SlidingExpiration(timeout)); +export class ExpirationFactory implements Factory { -export function expirationFactory(timeout: number, options: CacheOptions): Expiration { - const { expiration } = options; + constructor( + private readonly timeout: number, + private readonly expiration: 'absolute' | 'sliding', + ) { } - const factory = expirationFactories.get(expiration); + public create(): Expiration { + switch (this.expiration) { + case 'absolute': + return this.absoluteExpirtation(); - if (!factory) { - throw new Error(`@cache Expiration type is not supported: ${expiration}.`); + case 'sliding': + return this.slidingExpiration(); + + default: + throw new Error(`@cache Expiration type is not supported: ${this.expiration}.`); + } + } + + private absoluteExpirtation(): AbsoluteExpiration { + return new AbsoluteExpiration(this.timeout); + } + + private slidingExpiration(): SlidingExpiration { + return new SlidingExpiration(this.timeout); } - return factory(timeout); } diff --git a/lib/cache/index.ts b/lib/cache/index.ts index f38118f..4c37cdd 100644 --- a/lib/cache/index.ts +++ b/lib/cache/index.ts @@ -1,6 +1,9 @@ import { CacheOptions, DEFAULT_OPTIONS } from './CacheOptions'; -import { cacheFactory } from './caches/factory'; -import { cacheManagerFactory } from './cacheManager/factory'; +import { ExpirationFactory } from './expirations/factory'; +import { StorageFactory } from './storages/factory'; +import { CacheFactory } from './caches/factory'; +import { HashService } from '../utils/hash'; +import { CacheProviderFactory } from './cacheProvider/factory'; export { CacheOptions }; @@ -20,26 +23,27 @@ export function cache( ): MethodDecorator { const { timeout, options } = parseParameters(timeoutOrOptions, optionsOrVoid); - const cacheManager = cacheManagerFactory(timeout, options); + + const hashService = new HashService(); + const expirationFactory = new ExpirationFactory(timeout, options.expiration); + const storageFactory = new StorageFactory(options.size, options.storage); + const cacheFactory = new CacheFactory(hashService, expirationFactory, storageFactory); + const cacheProvider = new CacheProviderFactory(options.scope, cacheFactory).create(); return function (_: any, __: any, descriptor: PropertyDescriptor) { const method = descriptor.value; descriptor.value = async function (...args: any[]) { - const cacheService = cacheManager.get(this); - const wasCached = await cacheService.has(args); + const cacheService = cacheProvider.get(this); + const isCached = await cacheService.has(args); - if (wasCached) { + if (isCached) { return cacheService.get(args); } - try { - const value = await method(...args); - cacheService.set(args, value); - return value; - } catch (error) { - return Promise.reject(error); - } + const value = await method(...args); + cacheService.set(args, value); + return value; }; return descriptor; diff --git a/lib/cache/storages/MemoryStorage.ts b/lib/cache/storages/MemoryStorage.ts index a007baf..ecf9cc0 100644 --- a/lib/cache/storages/MemoryStorage.ts +++ b/lib/cache/storages/MemoryStorage.ts @@ -4,18 +4,10 @@ export class MemoryStorage implements Storage { private readonly storage = new Map(); - private readonly hasLimit: boolean; - private readonly keysStorage: Set; - - constructor(private readonly limit?: number) { - this.hasLimit = typeof this.limit === 'number'; - if (this.hasLimit) { - this.keysStorage = new Set(); - } - } + constructor(private readonly limit?: number) { } public async set(key: string, value: V): Promise { - this.checkSize(key); + this.checkSize(); this.storage.set(key, value); @@ -33,23 +25,13 @@ export class MemoryStorage implements Storage { public async delete(key: string): Promise { this.storage.delete(key); - if (this.hasLimit) { - this.keysStorage.delete(key); - } - return this; } - private checkSize(key: string): void { - if (!this.hasLimit) { - return; - } - - if (this.storage.size >= this.limit) { - this.delete(this.keysStorage.keys().next().value); + private checkSize(): void { + if (typeof this.limit === 'number' && this.storage.size >= this.limit) { + this.delete(this.storage.keys().next().value); } - - this.keysStorage.add(key); } } diff --git a/lib/cache/storages/factory.ts b/lib/cache/storages/factory.ts index 56321ae..e5c8bf7 100644 --- a/lib/cache/storages/factory.ts +++ b/lib/cache/storages/factory.ts @@ -1,19 +1,26 @@ -import { CacheOptions } from '..'; +import { Factory } from '../../interfaces/factory'; import { MemoryStorage } from './MemoryStorage'; import { Storage } from './Storage'; -const storeFactories: ReadonlyMap<'memory', (limit: number) => Storage> = - new Map<'memory', (limit: number) => Storage>() - .set('memory', limit => new MemoryStorage(limit)); +export class StorageFactory implements Factory { -export function storageFactory(options: CacheOptions): Storage { - const { size, storage } = options; + constructor( + private readonly limit: number, + private readonly storage: 'memory', + ) { } - const factory = storeFactories.get(storage); + public create(): Storage { + switch (this.storage) { + case 'memory': + return this.memoryStorage(); - if (!factory) { - throw new Error(`@cache Storage type is not supported: ${storage}.`); + default: + throw new Error(`@cache Storage type is not supported: ${this.storage}.`); + } + } + + private memoryStorage(): MemoryStorage { + return new MemoryStorage(this.limit); } - return factory(size); } diff --git a/test/cache.spec.ts b/test/cache/cache.spec.ts similarity index 99% rename from test/cache.spec.ts rename to test/cache/cache.spec.ts index 7c5e972..e3d2966 100644 --- a/test/cache.spec.ts +++ b/test/cache/cache.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { cache, CacheOptions } from '../lib'; -import { delay, executionTime } from './utils'; +import { cache, CacheOptions } from '../../lib'; +import { delay, executionTime } from '../utils'; describe('@cache', () => {