diff --git a/packages/core-kernel/src/contracts/core-kernel/cache.ts b/packages/core-kernel/src/contracts/core-kernel/cache.ts index 84864f00e5..a6631f099f 100644 --- a/packages/core-kernel/src/contracts/core-kernel/cache.ts +++ b/packages/core-kernel/src/contracts/core-kernel/cache.ts @@ -1,51 +1,165 @@ -export interface ICacheStore { +import { IApplication } from "./application"; + +export interface ICacheStore { + /** + * Create a new instance of the cache store. + * + * @param {IApplication} app + * @returns {ICacheStore} + * @memberof ICacheStore + */ + make(app: IApplication): Promise>; + + /** + * Get all of the items in the cache. + * + * @returns {Array<[K, T]>} + * @memberof ICacheStore + */ + all(): Promise>; + + /** + * Get the keys of the cache items. + * + * @returns {K[]} + * @memberof ICacheStore + */ + keys(): Promise; + + /** + * Get the values of the cache items. + * + * @returns {T[]} + * @memberof ICacheStore + */ + values(): Promise; + /** * Retrieve an item from the cache by key. + * + * @param {K} key + * @returns {(T | undefined)} + * @memberof ICacheStore */ - get(key: string): Promise; + get(key: K): Promise; /** * Retrieve multiple items from the cache by key. + * + * @param {K[]} keys + * @returns {(Array)} + * @memberof ICacheStore */ - many(keys: string[]): Promise>; + getMany(keys: K[]): Promise>; /** - * Store an item in the cache for a given number of milliseconds. + * Store an item in the cache for a given number of seconds. + * + * @param {K} key + * @param {T} value + * @param {number} seconds + * @returns {boolean} + * @memberof ICacheStore */ - put(key: string, value: any, ttl?: number): Promise; + put(key: K, value: T, seconds: number): Promise; /** - * Store multiple items in the cache for a given number of milliseconds. + * Store multiple items in the cache for a given number of seconds. + * + * @param {Array<[K, T]>} values + * @param {number} seconds + * @returns {boolean[]} + * @memberof ICacheStore */ - putMany(values: string[], ttl?: number): Promise; + putMany(values: Array<[K, T]>, seconds: number): Promise; /** - * Increment the value of an item in the cache. + * Determine if an item exists in the cache. + * + * @param {K} key + * @returns {boolean} + * @memberof ICacheStore */ - increment(key: string, value: number): Promise; + has(key: K): Promise; /** - * Decrement the value of an item in the cache. + * Determine multiple items exist in the cache. + * + * @param {K[]} keys + * @returns {boolean[]} + * @memberof ICacheStore */ - decrement(key: string, value: number): Promise; + hasMany(keys: K[]): Promise; /** - * Check if an item exists in the cache by key. + * Determine if an item doesn't exist in the cache. + * + * @param {K} key + * @returns {boolean} + * @memberof ICacheStore */ - has(key: string): Promise; + missing(key: K): Promise; + + /** + * Determine multiple items don't exist in the cache. + * + * @param {K[]} keys + * @returns {boolean[]} + * @memberof ICacheStore + */ + missingMany(keys: K[]): Promise; /** * Store an item in the cache indefinitely. + * + * @param {K} key + * @param {T} value + * @returns {boolean} + * @memberof ICacheStore + */ + forever(key: K, value: T): Promise; + + /** + * Store multiple items in the cache indefinitely. + * + * @param {Array<[K, T]>} values + * @param {T} value + * @returns {boolean[]} + * @memberof ICacheStore */ - forever(key: string, value: string): Promise; + foreverMany(values: Array<[K, T]>, value: T): Promise; /** * Remove an item from the cache. + * + * @param {K} key + * @returns {boolean} + * @memberof ICacheStore */ - forget(key: string): Promise; + forget(key: K): Promise; + + /** + * Remove multiple items from the cache. + * + * @param {K[]} keys + * @returns {boolean[]} + * @memberof ICacheStore + */ + forgetMany(keys: K[]): Promise; /** * Remove all items from the cache. + * + * @returns {boolean} + * @memberof ICacheStore + */ + flush(): Promise; + + /** + * Get the cache key prefix. + * + * @returns {string} + * @memberof ICacheStore */ - flush(): Promise; + getPrefix(): Promise; } diff --git a/packages/core-kernel/src/errors.ts b/packages/core-kernel/src/errors.ts index f495898fe7..3c99f7e22e 100644 --- a/packages/core-kernel/src/errors.ts +++ b/packages/core-kernel/src/errors.ts @@ -225,3 +225,19 @@ export class FailedDependencySatisfaction extends KernelError { super(`Expected "${dep}" to satisfy "${expected}" but received "${given}".`); } } + +/** + * @export + * @class NotImplementedError + * @extends {KernelError} + */ +export class NotImplementedError extends KernelError { + /** + * @param {string} klass + * @param {string} method + * @memberof NotImplementedError + */ + constructor(klass: string, method: string) { + super(`Method [${method}] is not implemented in [${klass}].`); + } +} diff --git a/packages/core-kernel/src/services/cache/adapters/memory.ts b/packages/core-kernel/src/services/cache/adapters/memory.ts new file mode 100644 index 0000000000..41f2be8496 --- /dev/null +++ b/packages/core-kernel/src/services/cache/adapters/memory.ts @@ -0,0 +1,225 @@ +import { IApplication } from "../../../contracts/core-kernel"; +import { ICacheStore } from "../../../contracts/core-kernel/cache"; +import { NotImplementedError } from "../../../errors"; + +/** + * @export + * @class MemoryStore + * @implements {ICacheStore} + */ +export class MemoryStore implements ICacheStore { + /** + * @private + * @type {Map} + * @memberof MemoryStore + */ + private readonly store: Map = new Map(); + + /** + * Create a new instance of the cache store. + * + * @param {IApplication} app + * @returns {ICacheStore} + * @memberof ICacheStore + */ + public async make(app: IApplication): Promise> { + return this; + } + + /** + * Get all of the items in the cache. + * + * @returns {Array<[K, T]>} + * @memberof MemoryStore + */ + public async all(): Promise> { + return Array.from(this.store.entries()); + } + + /** + * Get the keys of the cache items. + * + * @returns {K[]} + * @memberof MemoryStore + */ + public async keys(): Promise { + return Array.from(this.store.keys()); + } + + /** + * Get the values of the cache items. + * + * @returns {T[]} + * @memberof MemoryStore + */ + public async values(): Promise { + return Array.from(this.store.values()); + } + + /** + * Retrieve an item from the cache by key. + * + * @param {K} key + * @returns {(T | undefined)} + * @memberof MemoryStore + */ + public async get(key: K): Promise { + return this.store.get(key); + } + + /** + * Retrieve multiple items from the cache by key. + * + * @param {K[]} keys + * @returns {(Array)} + * @memberof MemoryStore + */ + public async getMany(keys: K[]): Promise> { + return keys.map((key: K) => this.store.get(key)); + } + + /** + * Store an item in the cache for a given number of seconds. + * + * @param {K} key + * @param {T} value + * @param {number} seconds + * @returns {boolean} + * @memberof MemoryStore + */ + public async put(key: K, value: T, seconds: number): Promise { + this.store.set(key, value); + + return this.has(key); + } + + /** + * Store multiple items in the cache for a given number of seconds. + * + * @param {Array<[K, T]>} values + * @param {number} seconds + * @returns {boolean[]} + * @memberof MemoryStore + */ + public async putMany(values: Array<[K, T]>, seconds: number): Promise { + return values.map((value: [K, T]) => { + this.store.set(value[0], value[1]); + + return this.has(value[0]); + }); + } + + /** + * Determine if an item exists in the cache. + * + * @param {K} key + * @returns {boolean} + * @memberof MemoryStore + */ + public async has(key: K): Promise { + return this.store.has(key); + } + + /** + * Determine multiple items exist in the cache. + * + * @param {K[]} keys + * @returns {boolean[]} + * @memberof MemoryStore + */ + public async hasMany(keys: K[]): Promise { + return keys.map((key: K) => this.has(key)); + } + + /** + * Determine if an item doesn't exist in the cache. + * + * @param {K} key + * @returns {boolean} + * @memberof MemoryStore + */ + public async missing(key: K): Promise { + return !this.has(key); + } + + /** + * Determine multiple items don't exist in the cache. + * + * @param {K[]} keys + * @returns {boolean[]} + * @memberof MemoryStore + */ + public async missingMany(keys: K[]): Promise { + return keys.map((key: K) => this.missing(key)); + } + + /** + * Store an item in the cache indefinitely. + * + * @param {K} key + * @param {T} value + * @returns {boolean} + * @memberof MemoryStore + */ + public async forever(key: K, value: T): Promise { + throw new NotImplementedError(this.constructor.name, "forever"); + } + + /** + * Store multiple items in the cache indefinitely. + * + * @param {Array<[K, T]>} values + * @param {T} value + * @returns {boolean[]} + * @memberof MemoryStore + */ + public async foreverMany(values: Array<[K, T]>, value: T): Promise { + throw new NotImplementedError(this.constructor.name, "foreverMany"); + } + + /** + * Remove an item from the cache. + * + * @param {K} key + * @returns {boolean} + * @memberof MemoryStore + */ + public async forget(key: K): Promise { + this.store.delete(key); + + return this.missing(key); + } + + /** + * Remove multiple items from the cache. + * + * @param {K[]} keys + * @returns {boolean[]} + * @memberof MemoryStore + */ + public async forgetMany(keys: K[]): Promise { + return keys.map((key: K) => this.missing(key)); + } + + /** + * Remove all items from the cache. + * + * @returns {boolean} + * @memberof MemoryStore + */ + public async flush(): Promise { + this.store.clear(); + + return this.store.size === 0; + } + + /** + * Get the cache key prefix. + * + * @returns {string} + * @memberof MemoryStore + */ + public async getPrefix(): Promise { + throw new NotImplementedError(this.constructor.name, "getPrefix"); + } +} diff --git a/packages/core-kernel/src/services/cache/factory.ts b/packages/core-kernel/src/services/cache/factory.ts index c039681cf9..382cfbac28 100644 --- a/packages/core-kernel/src/services/cache/factory.ts +++ b/packages/core-kernel/src/services/cache/factory.ts @@ -1,7 +1,22 @@ +import { Kernel } from "../../contracts"; + /** * @export * @class CacheFactory */ export class CacheFactory { - // + /** + * @param {Kernel.IApplication} app + * @memberof CacheFactory + */ + public constructor(private readonly app: Kernel.IApplication) {} + + /** + * @param {Kernel.ICacheStore} store + * @returns {Kernel.ICacheStore} + * @memberof CacheFactory + */ + public async make(store: Kernel.ICacheStore): Promise> { + return store.make(this.app); + } } diff --git a/packages/core-kernel/src/services/cache/index.ts b/packages/core-kernel/src/services/cache/index.ts new file mode 100644 index 0000000000..38f4b1c4d2 --- /dev/null +++ b/packages/core-kernel/src/services/cache/index.ts @@ -0,0 +1,3 @@ +export * from "./factory"; +export * from "./manager"; +export * from "./adapters/memory"; diff --git a/packages/core-kernel/src/services/cache/manager.ts b/packages/core-kernel/src/services/cache/manager.ts index 9cb55f4570..a5c856a684 100644 --- a/packages/core-kernel/src/services/cache/manager.ts +++ b/packages/core-kernel/src/services/cache/manager.ts @@ -1,7 +1,67 @@ +import { IApplication, ICacheStore } from "../../contracts/core-kernel"; +import { CacheFactory } from "./factory"; + /** * @export * @class CacheManager */ export class CacheManager { - // + /** + * @private + * @type {CacheFactory} + * @memberof CacheManager + */ + private readonly factory: CacheFactory; + + /** + * @private + * @type {Map} + * @memberof CacheManager + */ + private readonly drivers: Map = new Map(); + + /** + * @param {IApplication} app + * @memberof CacheManager + */ + public constructor(app: IApplication) { + this.factory = new CacheFactory(app); + } + + /** + * @param {string} [name="default"] + * @returns {ICacheStore} + * @memberof CacheManager + */ + public driver(name: string = "default"): ICacheStore { + return this.drivers.get(name); + } + + /** + * @param {ICacheStore} driver + * @param {string} [name="default"] + * @returns {ICacheStore} + * @memberof CacheManager + */ + public createDriver(driver: ICacheStore, name: string = "default"): ICacheStore { + this.drivers.set(name, this.factory.make(driver)); + + return this.driver(); + } + + /** + * @returns {Map} + * @memberof CacheManager + */ + public getDrivers(): Map { + return this.drivers; + } + + /** + * @returns {CacheFactory} + * @memberof CacheManager + */ + public getFactory(): CacheFactory { + return this.factory; + } } diff --git a/packages/core-kernel/src/services/cache/store.ts b/packages/core-kernel/src/services/cache/store.ts deleted file mode 100644 index 3115a38424..0000000000 --- a/packages/core-kernel/src/services/cache/store.ts +++ /dev/null @@ -1,133 +0,0 @@ -import Keyv from "keyv"; -import { ICacheStore } from "../../contracts/core-kernel/cache"; - -/** - * @export - * @class CacheStore - * @implements {ICacheStore} - */ -export class CacheStore implements ICacheStore { - /** - * @private - * @type {Keyv} - * @memberof CacheStore - */ - private store: Keyv; - - /** - * @param {Record} [opts] - * @memberof CacheStore - */ - public constructor(opts?: Record) { - this.store = new Keyv(opts); - } - - /** - * @param {string} key - * @returns {Promise} - * @memberof CacheStore - */ - public async get(key: string): Promise { - return this.store.get(key); - } - - /** - * @param {string[]} keys - * @returns {Promise>} - * @memberof CacheStore - */ - public async many(keys: string[]): Promise> { - const values: Record = {}; - - for (const key of Object.keys(keys)) { - values[key] = await this.get(key); - } - - return values; - } - - /** - * @param {string} key - * @param {*} value - * @param {number} [ttl] - * @returns {Promise} - * @memberof CacheStore - */ - public async put(key: string, value: any, ttl?: number): Promise { - await this.store.set(key, value, ttl); - } - - /** - * @param {string[]} values - * @param {number} [ttl] - * @returns {Promise} - * @memberof CacheStore - */ - public async putMany(values: string[], ttl?: number): Promise { - for (const [key, value] of Object.entries(values)) { - await this.put(key, value, ttl); - } - } - - /** - * @param {string} key - * @param {number} [value=1] - * @returns {Promise} - * @memberof CacheStore - */ - public async increment(key: string, value: number = 1): Promise { - const currentValue: number = await this.get(key); - - await this.put(key, currentValue * 1); - } - - /** - * @param {string} key - * @param {number} [value=1] - * @returns {Promise} - * @memberof CacheStore - */ - public async decrement(key: string, value: number = 1): Promise { - const currentValue: number = await this.get(key); - - await this.put(key, currentValue * -1); - } - - /** - * @param {string} key - * @returns {Promise} - * @memberof CacheStore - */ - public async has(key: string): Promise { - const value: any = await this.get(key); - - return value !== undefined; - } - - /** - * @param {string} key - * @param {string} value - * @returns {Promise} - * @memberof CacheStore - */ - public async forever(key: string, value: string): Promise { - await this.store.set(key, value, 5 * 315576e5); - } - - /** - * @param {string} key - * @returns {Promise} - * @memberof CacheStore - */ - public async forget(key: string): Promise { - await this.store.delete(key); - } - - /** - * @returns {Promise} - * @memberof CacheStore - */ - public async flush(): Promise { - await this.store.clear(); - } -}