2019-10-02 20:12:55 +00:00
|
|
|
import uuid1 from "uuid/v1";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Standard interface for the simplest key-value store
|
|
|
|
*/
|
|
|
|
export interface KeyValueStorage {
|
|
|
|
get(key: string): Promise<string | null>
|
|
|
|
set(key: string, value: string | null): Promise<void>
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Basic memory implementation
|
|
|
|
*/
|
|
|
|
export class MemoryStorage implements KeyValueStorage {
|
|
|
|
private store: { [key: string]: string } = {}
|
|
|
|
|
|
|
|
async get(key: string): Promise<string | null> {
|
|
|
|
const result = this.store[key];
|
|
|
|
return (typeof result == "undefined") ? null : result;
|
|
|
|
}
|
|
|
|
|
|
|
|
async set(key: string, value: string | null): Promise<void> {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-11-07 21:33:23 +00:00
|
|
|
* Wrap a storage, scoping the keys using a suffix
|
|
|
|
*/
|
|
|
|
export class ScopedStorage implements KeyValueStorage {
|
|
|
|
private readonly target: KeyValueStorage
|
|
|
|
|
|
|
|
constructor(target: StorageDelegate, private readonly suffix: string) {
|
|
|
|
this.target = fromDelegate(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
async get(key: string): Promise<string | null> {
|
|
|
|
return await this.target.get(`${key}#${this.suffix}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
async set(key: string, value: string | null): Promise<void> {
|
|
|
|
return await this.target.set(`${key}#${this.suffix}`, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use a target unscoped storage, scoping the keys in a virtual random namespace
|
2019-10-02 20:12:55 +00:00
|
|
|
*
|
|
|
|
* The namespace is persisted in a reference storage (used unscoped)
|
|
|
|
*/
|
2019-11-07 21:33:23 +00:00
|
|
|
export class RefScopedStorage implements KeyValueStorage {
|
2019-10-02 20:12:55 +00:00
|
|
|
private readonly reference: KeyValueStorage
|
|
|
|
private readonly target: KeyValueStorage
|
2019-11-07 21:33:23 +00:00
|
|
|
private inner?: KeyValueStorage
|
2019-10-02 20:12:55 +00:00
|
|
|
|
|
|
|
constructor(reference: StorageDelegate, target: StorageDelegate) {
|
|
|
|
this.reference = fromDelegate(reference);
|
|
|
|
this.target = fromDelegate(target);
|
|
|
|
}
|
|
|
|
|
2019-11-07 21:33:23 +00:00
|
|
|
private async init(): Promise<KeyValueStorage> {
|
|
|
|
const refkey = "tk-storage-scope-ref";
|
2019-10-02 20:12:55 +00:00
|
|
|
const suffix = await this.reference.get(refkey);
|
|
|
|
if (suffix) {
|
2019-11-07 21:33:23 +00:00
|
|
|
return new ScopedStorage(this.target, suffix);
|
2019-10-02 20:12:55 +00:00
|
|
|
} else {
|
2019-11-07 21:33:23 +00:00
|
|
|
const suffix = uuid1();
|
2019-10-02 20:12:55 +00:00
|
|
|
await this.reference.set(refkey, suffix);
|
2019-11-07 21:33:23 +00:00
|
|
|
return new ScopedStorage(this.target, suffix);
|
2019-10-02 20:12:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async get(key: string): Promise<string | null> {
|
2019-11-07 21:33:23 +00:00
|
|
|
if (!this.inner) {
|
|
|
|
this.inner = await this.init();
|
2019-10-02 20:12:55 +00:00
|
|
|
}
|
|
|
|
|
2019-11-07 21:33:23 +00:00
|
|
|
return await this.inner.get(key);
|
2019-10-02 20:12:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async set(key: string, value: string | null): Promise<void> {
|
2019-11-07 21:33:23 +00:00
|
|
|
if (!this.inner) {
|
|
|
|
this.inner = await this.init();
|
2019-10-02 20:12:55 +00:00
|
|
|
}
|
|
|
|
|
2019-11-07 21:33:23 +00:00
|
|
|
return await this.inner.set(key, value);
|
2019-10-02 20:12:55 +00:00
|
|
|
}
|
|
|
|
}
|