@furystack/filesystem-store
Version:
Simple File System store implementation for FuryStack
126 lines • 4.08 kB
JavaScript
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