Skip to content

Commit

Permalink
cache
Browse files Browse the repository at this point in the history
replace factory functions to class
rename CacheManager to CacheProvider
remove keysStorageIn MemoryStorage (now is using keys from map)
  • Loading branch information
JohnDoePlusPlus committed Nov 14, 2019
1 parent 1299d6c commit 7c0c11d
Show file tree
Hide file tree
Showing 14 changed files with 163 additions and 152 deletions.
12 changes: 4 additions & 8 deletions lib/cache/CacheOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
23 changes: 0 additions & 23 deletions lib/cache/cacheManager/ClassCacheManager.ts

This file was deleted.

26 changes: 0 additions & 26 deletions lib/cache/cacheManager/InstanceCacheManager.ts

This file was deleted.

27 changes: 0 additions & 27 deletions lib/cache/cacheManager/factory.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ClassType } from '../../interfaces/class';
import { Cache } from '../caches/Cache';

export interface CacheManager<K = any> {
export interface CacheProvider<K = any> {
get(instance: ClassType): Cache<K>;
}
19 changes: 19 additions & 0 deletions lib/cache/cacheProvider/ClassCacheProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Cache } from '../caches/Cache';
import { CacheFactory } from '../caches/factory';
import { CacheProvider } from './CacheProvider';

export class ClassCacheProvider<K = any> implements CacheProvider<K> {

private cache: Cache<K> = null;

constructor(private readonly cacheFactory: CacheFactory) { }

public get(): Cache<K> {
if (!this.cache) {
this.cache = this.cacheFactory.create();
}

return this.cache;
}

}
22 changes: 22 additions & 0 deletions lib/cache/cacheProvider/InstanceCacheProvider.ts
Original file line number Diff line number Diff line change
@@ -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<K = any> implements CacheProvider<K> {

private instanceCaches = new WeakMap<ClassType, Cache<K>>();

constructor(private readonly cacheFactory: CacheFactory) { }

public get(instance: ClassType): Cache<K> {
const hasCache = !this.instanceCaches.has(instance);
if (hasCache) {
const cache = this.cacheFactory.create();
this.instanceCaches.set(instance, cache);
}

return this.instanceCaches.get(instance);
}

}
35 changes: 35 additions & 0 deletions lib/cache/cacheProvider/factory.ts
Original file line number Diff line number Diff line change
@@ -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<CacheProvider> {

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);
}

}
25 changes: 17 additions & 8 deletions lib/cache/caches/factory.ts
Original file line number Diff line number Diff line change
@@ -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<K = any>(timeout: number, options: CacheOptions): Cache<K> {
const storage = storageFactory(options);
const expiration = expirationFactory(timeout, options);
const hash = new HashService();
export class CacheFactory<K = any> implements Factory<Cache<K>> {

constructor(
private readonly hash: HashService,
private readonly expirationFactory: ExpirationFactory,
private readonly storageFactory: StorageFactory,
) { }

public create(): Cache<K> {
const storage = this.storageFactory.create();
const expiration = this.expirationFactory.create();

return new Cache(storage, expiration, this.hash);
}

return new Cache<K>(storage, expiration, hash);
}
35 changes: 24 additions & 11 deletions lib/cache/expirations/factory.ts
Original file line number Diff line number Diff line change
@@ -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<Expiration> {

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);
}
30 changes: 17 additions & 13 deletions lib/cache/index.ts
Original file line number Diff line number Diff line change
@@ -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 };

Expand All @@ -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;
Expand Down
28 changes: 5 additions & 23 deletions lib/cache/storages/MemoryStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,10 @@ export class MemoryStorage implements Storage {

private readonly storage = new Map<string, any>();

private readonly hasLimit: boolean;
private readonly keysStorage: Set<string>;

constructor(private readonly limit?: number) {
this.hasLimit = typeof this.limit === 'number';
if (this.hasLimit) {
this.keysStorage = new Set<string>();
}
}
constructor(private readonly limit?: number) { }

public async set<V>(key: string, value: V): Promise<this> {
this.checkSize(key);
this.checkSize();

this.storage.set(key, value);

Expand All @@ -33,23 +25,13 @@ export class MemoryStorage implements Storage {
public async delete(key: string): Promise<this> {
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);
}

}
27 changes: 17 additions & 10 deletions lib/cache/storages/factory.ts
Original file line number Diff line number Diff line change
@@ -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<Storage> {

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);
}
Loading

0 comments on commit 7c0c11d

Please sign in to comment.