Skip to content

Instantly share code, notes, and snippets.

@RomkeVdMeulen
Last active January 10, 2017 16:17
Show Gist options
  • Save RomkeVdMeulen/d0ede5acab30a11ed418f3c7f64a95db to your computer and use it in GitHub Desktop.
Save RomkeVdMeulen/d0ede5acab30a11ed418f3c7f64a95db to your computer and use it in GitHub Desktop.
Aurelia cache service
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" });
});
});
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);
}
}
@swalters
Copy link

swalters commented Jan 9, 2017

CacheImpl.heefItem should be CacheImpl.hasItem

Thanks for Sharing!!!

@RomkeVdMeulen
Copy link
Author

Ah, missed that in the translation. Fixed!

@RomkeVdMeulen
Copy link
Author

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