155 lines
4.7 KiB
TypeScript
155 lines
4.7 KiB
TypeScript
/* eslint-disable no-constant-condition */
|
|
import { promises as fs, accessSync, constants, writeFileSync } from 'fs';
|
|
import { promisify } from 'util';
|
|
import { gzip, gzipSync, unzip } from 'zlib';
|
|
|
|
type JSONData = [{key: string, value: any}?];
|
|
|
|
|
|
/**
|
|
* Persistant local JSON-based storage.
|
|
* - auto-locking system to prevent corrupted data
|
|
* - uses gzip compression to keep DB storage space utilization low
|
|
* @author Matthew <matthew@staff.libraryofcode.org>
|
|
*/
|
|
export default class LocalStorage {
|
|
protected readonly storagePath: string;
|
|
|
|
protected locked: boolean = false;
|
|
|
|
constructor(dbName: string, dir = `${__dirname}/../../localstorage`) {
|
|
this.storagePath = `${dir}/${dbName}.json.gz`;
|
|
this.init();
|
|
}
|
|
|
|
private init() {
|
|
try {
|
|
accessSync(this.storagePath, constants.F_OK);
|
|
} catch {
|
|
const setup = [];
|
|
const data = gzipSync(JSON.stringify(setup));
|
|
writeFileSync(this.storagePath, data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compresses data using gzip.
|
|
* @param data The data to be compressed.
|
|
* ```ts
|
|
* await LocalStorage.compress('hello!');
|
|
* ```
|
|
*/
|
|
static async compress(data: string): Promise<Buffer> {
|
|
const func = promisify(gzip);
|
|
const comp = <Buffer> await func(data);
|
|
return comp;
|
|
}
|
|
|
|
/**
|
|
* Decompresses data using gzip.
|
|
* @param data The data to be decompressed.
|
|
* ```ts
|
|
* const compressed = await LocalStorage.compress('data');
|
|
* const decompressed = await LocalStorage.decompress(compressed);
|
|
* console.log(decompressed); // logs 'data';
|
|
* ```
|
|
*/
|
|
static async decompress(data: Buffer): Promise<string> {
|
|
const func = promisify(unzip);
|
|
const uncomp = <Buffer> await func(data);
|
|
return uncomp.toString();
|
|
}
|
|
|
|
/**
|
|
* Retrieves one data from the store.
|
|
* If the store has multiple entries for the same key, this function will only return the first entry.
|
|
* ```ts
|
|
* await LocalStorage.get<type>('data-key');
|
|
* ```
|
|
* @param key The key for the data entry.
|
|
*/
|
|
public async get<T>(key: string): Promise<T> {
|
|
while (true) {
|
|
if (!this.locked) break;
|
|
}
|
|
this.locked = true;
|
|
|
|
const file = await fs.readFile(this.storagePath);
|
|
const uncomp = await LocalStorage.decompress(file);
|
|
this.locked = false;
|
|
const json: JSONData = JSON.parse(uncomp);
|
|
const result = json.filter((data) => data.key === key);
|
|
if (!result[0]) return null;
|
|
return result[0].value;
|
|
}
|
|
|
|
/**
|
|
* Retrieves multiple data keys/values from the store.
|
|
* This function will return all of the values matching the key you provided exactly. Use `LocalStorage.get();` if possible.
|
|
* ```ts
|
|
* await LocalStorage.get<type>('data-key');
|
|
* @param key The key for the data entry.
|
|
*/
|
|
public async getMany<T>(key: string): Promise<{key: string, value: T}[]> {
|
|
while (true) {
|
|
if (!this.locked) break;
|
|
}
|
|
this.locked = true;
|
|
|
|
const file = await fs.readFile(this.storagePath);
|
|
const uncomp = await LocalStorage.decompress(file);
|
|
this.locked = false;
|
|
const json: JSONData = JSON.parse(uncomp);
|
|
const result = json.filter((data) => data.key === key);
|
|
if (result.length < 1) return null;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sets a key/value pair and creates a new data entry.
|
|
* @param key The key for the data entry.
|
|
* @param value The value for the data entry, can be anything that is valid JSON.
|
|
* @param options.override [DEPRECATED] By default, this function will error if the key you're trying to set already exists. Set this option to true to override that setting.
|
|
* ```ts
|
|
* await LocalStorage.set('data-key', 'test');
|
|
* ```
|
|
*/
|
|
public async set(key: string, value: any): Promise<void> {
|
|
while (true) {
|
|
if (!this.locked) break;
|
|
}
|
|
this.locked = true;
|
|
|
|
const file = await fs.readFile(this.storagePath);
|
|
const uncomp = await LocalStorage.decompress(file);
|
|
const json: JSONData = JSON.parse(uncomp);
|
|
json.push({ key, value });
|
|
const comp = await LocalStorage.compress(JSON.stringify(json));
|
|
await fs.writeFile(this.storagePath, comp);
|
|
this.locked = false;
|
|
}
|
|
|
|
/**
|
|
* Deletes the data for the specified key.
|
|
* **Warning:** This function will delete ALL matching entries.
|
|
* ```ts
|
|
* await LocalStorage.del('data-key');
|
|
* ```
|
|
* @param key The key for the data entry.
|
|
*/
|
|
public async del(key: string): Promise<void> {
|
|
while (true) {
|
|
if (!this.locked) break;
|
|
}
|
|
this.locked = true;
|
|
|
|
const file = await fs.readFile(this.storagePath);
|
|
const uncomp = await LocalStorage.decompress(file);
|
|
const json: JSONData = JSON.parse(uncomp);
|
|
const filtered = json.filter((data) => data.key !== key);
|
|
const comp = await LocalStorage.compress(JSON.stringify(filtered));
|
|
await fs.writeFile(this.storagePath, comp);
|
|
this.locked = false;
|
|
}
|
|
}
|