UNPKG

@furystack/filesystem-store

Version:

Simple File System store implementation for FuryStack

126 lines 4.08 kB
import { InMemoryStore } from '@furystack/core'; import { EventHub } from '@furystack/utils'; import { promises, watch } from 'fs'; import { Lock } from 'semaphore-async-await'; /** * Store implementation that stores info in a simple JSON file */ export class FileSystemStore extends EventHub { options; watcher; model; primaryKey; inMemoryStore; get cache() { return this.inMemoryStore.cache; } async remove(...keys) { await this.fileLock.execute(async () => { await this.inMemoryStore.remove(...keys); }); this.hasChanges = true; } tick; hasChanges = false; async get(key, select) { return await this.fileLock.execute(async () => { return await this.inMemoryStore.get(key, select); }); } async add(...entries) { const result = await this.fileLock.execute(async () => { return await this.inMemoryStore.add(...entries); }); this.hasChanges = true; return result; } async find(filter) { return await this.fileLock.execute(async () => { return this.inMemoryStore.find(filter); }); } async count(filter) { return await this.fileLock.execute(async () => { return this.inMemoryStore.count(filter); }); } fileLock = new Lock(); async saveChanges() { if (!this.hasChanges) { return; } try { await this.fileLock.acquire(); const values = []; for (const key of this.cache.keys()) { values.push(this.cache.get(key)); } await this.writeFile(this.options.fileName, JSON.stringify(values)); this.hasChanges = false; } finally { this.fileLock.release(); } } async [Symbol.asyncDispose]() { await this.saveChanges(); this.watcher?.close(); clearInterval(this.tick); super[Symbol.dispose](); } async reloadData() { try { await this.fileLock.acquire(); const data = await this.readFile(this.options.fileName); const json = data ? JSON.parse(data.toString()) : []; this.cache.clear(); for (const entity of json) { this.cache.set(entity[this.primaryKey], entity); } } catch (err) { // ignore if file not exists yet if (err instanceof Error && err.code !== 'ENOENT') { throw err; } } finally { this.fileLock.release(); } } async update(id, data) { await this.fileLock.execute(async () => { return this.inMemoryStore.update(id, data); }); this.hasChanges = true; } readFile = promises.readFile; writeFile = promises.writeFile; constructor(options) { super(); this.options = options; this.primaryKey = options.primaryKey; this.model = options.model; this.inMemoryStore = new InMemoryStore({ model: this.model, primaryKey: this.primaryKey }); this.tick = setInterval(() => void this.saveChanges(), this.options.tickMs || 3000); this.inMemoryStore.subscribe('onEntityAdded', ({ entity }) => { this.emit('onEntityAdded', { entity }); }); this.inMemoryStore.subscribe('onEntityUpdated', ({ id, change }) => { this.emit('onEntityUpdated', { id, change }); }); this.inMemoryStore.subscribe('onEntityRemoved', ({ key }) => { this.emit('onEntityRemoved', { key }); }); try { void this.reloadData(); this.watcher = watch(this.options.fileName, { encoding: 'buffer' }, () => { void this.reloadData(); }); } catch (error) { // Error registering file watcher for store. External updates won't be updated. } } } //# sourceMappingURL=filesystem-store.js.map