import uuid1 from "uuid/v1"; /** * Standard interface for the simplest key-value store */ export interface KeyValueStorage { get(key: string): Promise set(key: string, value: string | null): Promise } /** * Basic memory implementation */ export class MemoryStorage implements KeyValueStorage { private store: { [key: string]: string } = {} async get(key: string): Promise { const result = this.store[key]; return (typeof result == "undefined") ? null : result; } async set(key: string, value: string | null): Promise { if (value === null) { delete this.store[key]; } else { this.store[key] = value; } } } type StorageDelegate = KeyValueStorage | (() => KeyValueStorage) function fromDelegate(delegate: StorageDelegate): KeyValueStorage { return (typeof delegate === "function") ? delegate() : delegate; } /** * Use a target unscoped storage, scoping the keys in a virtual namespace * * The namespace is persisted in a reference storage (used unscoped) */ export class ScopedStorage implements KeyValueStorage { private readonly reference: KeyValueStorage private readonly target: KeyValueStorage private suffix?: string constructor(reference: StorageDelegate, target: StorageDelegate) { this.reference = fromDelegate(reference); this.target = fromDelegate(target); } private async init(): Promise { const refkey = "tk-storage-scope-suffix" const suffix = await this.reference.get(refkey); if (suffix) { this.suffix = suffix; } else { const suffix = "#" + uuid1(); await this.reference.set(refkey, suffix); this.suffix = suffix; } } async get(key: string): Promise { if (!this.suffix) { await this.init(); } return await this.target.get(key + this.suffix); } async set(key: string, value: string | null): Promise { if (!this.suffix) { await this.init(); } return await this.target.set(key + this.suffix, value); } }