Skip to content

Instantly share code, notes, and snippets.

@fred-o
Created August 19, 2020 06:49
Show Gist options
  • Save fred-o/e8f58f9b02223d740c312beec48b1d2e to your computer and use it in GitHub Desktop.
Save fred-o/e8f58f9b02223d740c312beec48b1d2e to your computer and use it in GitHub Desktop.
valuestore.ts
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