Last active
January 10, 2017 16:17
-
-
Save RomkeVdMeulen/d0ede5acab30a11ed418f3c7f64a95db to your computer and use it in GitHub Desktop.
Aurelia cache service
This file contains hidden or 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
import {Container} from "aurelia-framework"; | |
import {CacheService, CACHE_STORE} from "./cache"; | |
describe("The Cache Service", () => { | |
let cacheManager: CacheService; | |
beforeEach(() => { | |
cacheManager = new Container().get(CacheService); | |
}); | |
it("gives the same Cache instance each time for the same key", () => { | |
let cache = cacheManager.getCache("test-instance"), | |
cache2 = cacheManager.getCache("test-instance"); | |
expect(cache).toBe(cache2); | |
}); | |
it("throws an error if you configure the same cache twice", () => { | |
cacheManager.configure("test-instance", { store: CACHE_STORE.SESSION }); | |
try { | |
cacheManager.configure("test-instance", { store: CACHE_STORE.PERSISTENT }); | |
fail(); | |
} catch (e) { | |
} | |
}); | |
}); | |
describe("The Cache object", () => { | |
let store: Storage, cacheService: CacheService; | |
beforeEach(() => { | |
cacheService = new Container().get(CacheService); | |
store = { | |
length: 0, | |
clear: jasmine.createSpy("clear"), | |
key: jasmine.createSpy("key"), | |
removeItem: jasmine.createSpy("removeItem"), | |
getItem: jasmine.createSpy("getItem").and.callFake((key: string) => { | |
return key in store ? store[key] : null; | |
}), | |
setItem: jasmine.createSpy("setItem").and.callFake((key: string, data: string) => { | |
store[key] = data; | |
}) | |
}; | |
cacheService.setDefaultConfig({ store: store }); | |
}); | |
it("writes data to storage", () => { | |
let cache = cacheService.getCache("test-storage"); | |
cache.setItem("string-lorem", "lorem"); | |
expect(store.setItem).toHaveBeenCalledWith( | |
jasmine.stringMatching("test-storage"), | |
jasmine.anything() | |
); | |
}); | |
it("returns entries from storage with the same type they were stored in", () => { | |
let cache = cacheService.getCache("test-types"); | |
cache.setItem("boolean-true", true); | |
cache.setItem("boolean-false", false); | |
cache.setItem("number-1", 1); | |
cache.setItem("null", null); | |
cache.setItem("string-lorem", "lorem"); | |
cache.setItem("array", ["lorem", "ipsum", "dolor"]); | |
cache.setItem("object", { lorem: "ipsum" }); | |
/* force cache reload */ | |
(<any>cacheService).caches = {}; | |
let cache2 = cacheService.getCache("test-types"); | |
expect(cache2.getItem("boolean-true")).toBe(true); | |
expect(cache2.getItem("boolean-false")).toBe(false); | |
expect(cache2.getItem("number-1")).toBe(1); | |
expect(cache2.getItem("null")).toBe(null); | |
expect(cache2.getItem("string-lorem")).toBe("lorem"); | |
expect(cache2.getItem("array")).toEqual(["lorem", "ipsum", "dolor"]); | |
expect(cache2.getItem("object")).toEqual({ lorem: "ipsum" }); | |
}); | |
}); |
This file contains hidden or 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
import {singleton} from "aurelia-framework"; | |
const CACHE_PREFIX = "aurelia:cache:"; | |
export interface CacheStore { | |
/** | |
* Retrieve item from store. Returns null if item not present. | |
*/ | |
getItem(key: string): any; | |
setItem(key: string, value: any): void; | |
removeItem(key: string): void; | |
} | |
class InMemoryCacheStore implements CacheStore { | |
private state: { [key: string]: any } = {}; | |
setItem(key: string, value: any) { | |
this.state[key] = value; | |
} | |
getItem(key: string): any { | |
return key in this.state ? this.state[key] : null; | |
} | |
removeItem(key: string) { | |
delete this.state[key]; | |
} | |
} | |
class StringValueStoreAdapter implements CacheStore { | |
constructor(private store: Storage) { | |
} | |
getItem(key: string): any { | |
let value = this.store.getItem(key); | |
return value === null ? null : JSON.parse(value); | |
} | |
setItem(key: string, value: any): void { | |
this.store.setItem(key, JSON.stringify(value)); | |
} | |
removeItem(key: string): void { | |
this.store.removeItem(key); | |
} | |
} | |
export const CACHE_STORE = { | |
IN_MEMORY: <CacheStore>new InMemoryCacheStore(), | |
SESSION: <CacheStore>new StringValueStoreAdapter(window.sessionStorage), | |
PERSISTENT: <CacheStore>new StringValueStoreAdapter(window.localStorage) | |
}; | |
export interface CacheConfig { | |
store?: CacheStore; | |
} | |
export interface Cache { | |
getItem(entryKey: string): any; | |
hasItem(entryKey: string): boolean; | |
setItem(entryKey: string, value: any): void; | |
removeItem(entryKey: string): void; | |
clear(): void; | |
} | |
@singleton() | |
export class CacheService { | |
private defaultConfig: CacheConfig = { | |
store: CACHE_STORE.SESSION | |
}; | |
private cacheConfig: { [key: string]: CacheConfig } = {}; | |
private caches: { [key: string]: Cache } = {}; | |
setDefaultConfig(config: CacheConfig) { | |
Object.assign(this.defaultConfig, config); | |
return this; | |
} | |
configure(name: string, config: CacheConfig) { | |
const cacheKey = CACHE_PREFIX + name; | |
if (cacheKey in this.cacheConfig) { | |
throw "Cache with key '" + cacheKey + "' already configured"; | |
} | |
this.cacheConfig[cacheKey] = Object.assign({}, this.defaultConfig, config); | |
return this; | |
} | |
getCache(name: string): Cache { | |
const cacheKey = CACHE_PREFIX + name; | |
if (!(cacheKey in this.caches)) { | |
if (!(cacheKey in this.cacheConfig)) { | |
this.configure(name, this.defaultConfig); | |
} | |
this.caches[cacheKey] = new CacheImpl(cacheKey, this.cacheConfig[cacheKey]); | |
} | |
return this.caches[cacheKey]; | |
} | |
} | |
class CacheImpl implements Cache { | |
constructor(private cacheKey: string, private config: CacheConfig) { | |
} | |
getItem(entryKey: string): any { | |
let values = this.getCacheContents(); | |
return (values !== null && entryKey in values) ? values[entryKey] : null; | |
} | |
hasItem(entryKey: string) { | |
return this.getItem(entryKey) !== null; | |
} | |
setItem(entryKey: string, value: any) { | |
let values = this.getCacheContents(); | |
if (values === null) { | |
values = {}; | |
} | |
values[entryKey] = value; | |
this.setCacheInhoud(values); | |
} | |
removeItem(entryKey: string) { | |
let values = this.getCacheContents(); | |
if (values === null || !(entryKey in values)) { | |
return; | |
} | |
delete values[entryKey]; | |
this.setCacheInhoud(values); | |
} | |
clear() { | |
this.setCacheContents(null); | |
} | |
private getCacheContents(): { [entryKey: string]: any } { | |
return this.config.store.getItem(this.cacheKey); | |
} | |
private setCacheContents(contents: { [entryKey: string]: any }): void { | |
this.config.store.setItem(this.cacheKey, contents); | |
} | |
} |
Ah, missed that in the translation. Fixed!
Note that StringValueStoreAdapter may not be the most efficient solution. Any input on this would be welcome.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
CacheImpl.heefItem should be CacheImpl.hasItem
Thanks for Sharing!!!