Add cache interface, factory, and basic in-mem implementation

This commit is contained in:
Garrett Mills 2021-03-09 10:16:27 -06:00
parent 42d9cc101f
commit 088fdfb1ef
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
5 changed files with 138 additions and 0 deletions

View File

@ -59,6 +59,10 @@ export * from './service/HTTPServer'
export * from './service/Routing' export * from './service/Routing'
export * from './service/Middlewares' export * from './service/Middlewares'
export * from './support/cache/Cache'
export * from './support/cache/MemoryCache'
export * from './support/cache/CacheFactory'
export * from './views/ViewEngine' export * from './views/ViewEngine'
export * from './views/ViewEngineFactory' export * from './views/ViewEngineFactory'
export * from './views/PugViewEngine' export * from './views/PugViewEngine'

View File

@ -14,6 +14,7 @@ import {Logging} from '../service/Logging';
import {RunLevelErrorHandler} from "./RunLevelErrorHandler"; import {RunLevelErrorHandler} from "./RunLevelErrorHandler";
import {Unit, UnitStatus} from "./Unit"; import {Unit, UnitStatus} from "./Unit";
import * as dotenv from 'dotenv'; import * as dotenv from 'dotenv';
import {CacheFactory} from "../support/cache/CacheFactory";
export function env(key: string, defaultValue?: any): any { export function env(key: string, defaultValue?: any): any {
return Application.getApplication().env(key, defaultValue) return Application.getApplication().env(key, defaultValue)
@ -103,6 +104,8 @@ export class Application extends Container {
this.bootstrapEnvironment() this.bootstrapEnvironment()
this.setupLogging() this.setupLogging()
this.registerFactory(new CacheFactory()) // FIXME move this somewhere else?
this.make<Logging>(Logging).debug(`Application root: ${this.baseDir}`) this.make<Logging>(Logging).debug(`Application root: ${this.baseDir}`)
} }

31
src/support/cache/Cache.ts vendored Normal file
View File

@ -0,0 +1,31 @@
/**
* Abstract interface class for an application cache object.
*/
export abstract class Cache {
/**
* Fetch a value from the cache by its key.
* @param {string} key
* @return Promise<any|undefined>
*/
public abstract fetch(key: string): string | undefined | Promise<string | undefined>;
/**
* Store the given value in the cache by key.
* @param {string} key
* @param {string} value
*/
public abstract put(key: string, value: string): void | Promise<void>;
/**
* Check if the cache has the given key.
* @param {string} key
* @return Promise<boolean>
*/
public abstract has(key: string): boolean | Promise<boolean>;
/**
* Drop the given key from the cache.
* @param {string} key
*/
public abstract drop(key: string): void | Promise<void>;
}

72
src/support/cache/CacheFactory.ts vendored Normal file
View File

@ -0,0 +1,72 @@
import {
AbstractFactory,
Container,
DependencyRequirement,
PropertyDependency,
isInstantiable,
DEPENDENCY_KEYS_METADATA_KEY,
DEPENDENCY_KEYS_PROPERTY_METADATA_KEY
} from "@extollo/di"
import {Collection, ErrorWithContext} from "@extollo/util"
import {Logging} from "../../service/Logging";
import {Config} from "../../service/Config";
import {Cache} from "./Cache"
import {MemoryCache} from "./MemoryCache";
export class CacheFactory extends AbstractFactory {
protected readonly logging: Logging
protected readonly config: Config
private static loggedMemoryCacheWarningOnce = false
constructor() {
super({})
this.logging = Container.getContainer().make<Logging>(Logging)
this.config = Container.getContainer().make<Config>(Config)
}
produce(dependencies: any[], parameters: any[]): Cache {
return new (this.getCacheClass())
}
match(something: any) {
return something === Cache
}
getDependencyKeys(): Collection<DependencyRequirement> {
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getCacheClass())
if ( meta ) return meta
return new Collection<DependencyRequirement>()
}
getInjectedProperties(): Collection<PropertyDependency> {
const meta = new Collection<PropertyDependency>()
let currentToken = this.getCacheClass()
do {
const loadedMeta = Reflect.getMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, currentToken)
if ( loadedMeta ) meta.concat(loadedMeta)
currentToken = Object.getPrototypeOf(currentToken)
} while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype)
return meta
}
protected getCacheClass() {
const CacheClass = this.config.get('server.cache.driver', MemoryCache)
if ( CacheClass === MemoryCache && !CacheFactory.loggedMemoryCacheWarningOnce ) {
this.logging.warn(`You are using the default memory-based cache driver. It is recommended you configure a persistent cache driver instead.`)
CacheFactory.loggedMemoryCacheWarningOnce = true
}
if ( !isInstantiable(CacheClass) || !(CacheClass.prototype instanceof Cache) ) {
const e = new ErrorWithContext('Provided session class does not extend from @extollo/lib.Cache');
e.context = {
config_key: 'server.cache.driver',
class: CacheClass.toString(),
}
}
return CacheClass
}
}

28
src/support/cache/MemoryCache.ts vendored Normal file
View File

@ -0,0 +1,28 @@
import {Collection} from "@extollo/util"
import {Cache} from "./Cache"
export class MemoryCache extends Cache {
private static cacheItems: Collection<{key: string, value: string}> = new Collection<{key: string; value: string}>()
public fetch(key: string): string | Promise<string | undefined> | undefined {
return MemoryCache.cacheItems
.firstWhere('key', '=', key)?.value
}
public put(key: string, value: string): void | Promise<void> {
const existing = MemoryCache.cacheItems.firstWhere('key', '=', key)
if ( existing ) {
existing.value = value
} else {
MemoryCache.cacheItems.push({ key, value })
}
}
public has(key: string): boolean | Promise<boolean> {
return !!MemoryCache.cacheItems.firstWhere('key', '=', key)
}
public drop(key: string): void | Promise<void> {
MemoryCache.cacheItems = MemoryCache.cacheItems.where('key', '!=', key)
}
}