Created
August 19, 2020 06:49
-
-
Save fred-o/e8f58f9b02223d740c312beec48b1d2e to your computer and use it in GitHub Desktop.
valuestore.ts
This file contains 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 { Semaphore } from 'await-semaphore' | |
import { EventEmitter } from 'events' | |
import Redis from 'ioredis' | |
import StrictEventEmitter from 'strict-event-emitter-types' | |
interface Events<T> { | |
change: (data: T) => void, | |
delete: void | |
} | |
interface Value<T> extends StrictEventEmitter<EventEmitter, Events<T>> { | |
get(): Promise<T> | |
mutate(mutator: (val: T) => Promise<T>): Promise<T> | |
delete(): Promise<void> | |
} | |
export class ValueClass<T> extends EventEmitter { | |
name: string | |
store: Redis.Redis | |
semaphore: Semaphore | |
constructor(name: string, sub: Redis.Redis, store: Redis.Redis, semaphore: Semaphore) { | |
super() | |
this.name = name | |
this.store = store | |
this.semaphore = semaphore | |
sub.subscribe('valuestore:change', 'valuestore:delete') | |
sub.on('message', async (channel, data) => { | |
if (data === this.name) { | |
if (channel === 'valuestore:change') { this.emit('change', await this.get()) } | |
if (channel === 'valuestore:delete') { this.emit('delete') } | |
} | |
}) | |
} | |
async get(): Promise<T> { | |
return JSON.parse(await this.store.get(this.name)) | |
} | |
async mutate(mutator: (val: T) => Promise<T>): Promise<T> { | |
return this.semaphore.use(async () => { | |
await this.store.watch(this.name) | |
let val = await mutator(await this.get()) | |
await this.store.multi() | |
.set(this.name, JSON.stringify(val)) | |
.publish(`valuestore:change`, this.name) | |
.exec() | |
return val | |
}) | |
} | |
async delete(): Promise<void> { | |
return this.semaphore.use(async () => { | |
await this.store.watch(this.name) | |
await this.store.multi() | |
.del(this.name) | |
.publish(`valuestore:delete`, this.name) | |
.exec() | |
}) | |
} | |
} | |
export class ValueStore { | |
sub: Redis.Redis | |
store: Redis.Redis | |
keys: { [name: string]: ValueClass<any> } = {} | |
semaphore = new Semaphore(1) | |
constructor(opts?: { redis: Redis.RedisOptions }) { | |
this.sub = new Redis(opts?.redis) | |
this.store = new Redis(opts?.redis) | |
} | |
key<T>(name: string): Value<T> { | |
if (!this.keys[name]) this.keys[name] = new ValueClass<T>(name, this.sub, this.store, this.semaphore) | |
return this.keys[name] | |
} | |
close() { | |
this.sub.quit() | |
this.store.quit() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment