flash-store
Version:
FlashStore is a Key-Value persistent storage with easy to use ES6 Map-like API(both Async and Sync support), powered by LevelDB and TypeScript.
164 lines (127 loc) • 3.54 kB
text/typescript
import cuid from 'cuid'
import {
StateSwitch,
} from 'state-switch'
import {
log,
} from './config'
import {
FlashStore,
} from './flash-store'
export class FlashStoreSync<K = any, V = any> implements Map<K, V> {
static VERSION = FlashStore.VERSION
private cacheMap : Map<K, V>
private flashStore : FlashStore<K, V>
private asyncBusyState: StateSwitch
private asyncBusyDict: {
[id: string]: Promise<void>,
}
constructor (
public workdir?: string,
) {
log.verbose('CacheStore', 'constructor(%s)', workdir)
workdir = workdir || '.flash-store-sync'
this.asyncBusyDict = {}
this.asyncBusyState = new StateSwitch(
'Busy:' + workdir.split('/').pop(), // get the latest folder name
log,
)
this.cacheMap = new Map<K, V>()
this.flashStore = new FlashStore<K, V>(workdir)
this.asyncBusyAdd(this.loadStoreToCache())
}
private async loadStoreToCache (): Promise<void> {
this.cacheMap.clear()
for await (const [key, val] of this.flashStore) {
this.cacheMap.set(key, val)
}
}
private asyncBusyAdd (task: Promise<any>): void {
this.asyncBusyState.on(true)
const id = cuid()
this.asyncBusyDict[id] = task.finally(() => {
delete this.asyncBusyDict[id]
if (Object.keys(this.asyncBusyDict).length <= 0) {
this.asyncBusyState.off(true)
}
})
}
public version (): string {
return this.flashStore.version()
}
/**
*
* Async methods:
*
*/
public async close (): Promise<void> {
await this.ready()
await this.flashStore.close()
}
public async destroy (): Promise<void> {
this.clear()
// add destroy task at the end of the event loop
this.asyncBusyState.ready('off')
.then(
() => this.flashStore.destroy(),
).catch(console.error)
}
public async ready (): Promise<void> {
await this.asyncBusyState.ready('off')
}
/*******************************************************
*
*
* The following methods is all for ES6 Map Interface
*
*
*******************************************************/
public get size (): number {
return this.cacheMap.size
}
get [Symbol.toStringTag] () {
return 'FlashStoreSync' as any as 'Map'
}
public [Symbol.iterator] () {
return this.cacheMap[Symbol.iterator]()
}
public entries () {
return this.cacheMap.entries()
}
public keys () {
return this.cacheMap.keys()
}
public values () {
return this.cacheMap.values()
}
public clear (): void {
this.asyncBusyAdd(this.flashStore.clear())
return this.cacheMap.clear()
}
public delete (key: K): boolean {
this.asyncBusyAdd(this.flashStore.delete(key))
return this.cacheMap.delete(key)
}
/**
* Do not mutate the key/value in the forEach loop!
*/
public forEach (callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
/**
* 1. no need to call flashStore
* 2. callbackfn should not mutate the data, or the data will be lost sync between cache & store
*/
return this.cacheMap.forEach(callbackfn, thisArg)
}
public get (key: K): V | undefined {
return this.cacheMap.get(key)
}
public has (key: K): boolean {
return this.cacheMap.has(key)
}
public set (key: K, value: V): this {
this.asyncBusyAdd(this.flashStore.set(key, value))
this.cacheMap.set(key, value)
return this
}
}
export default FlashStoreSync