/* 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 */ 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 { const func = promisify(gzip); const comp = 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 { const func = promisify(unzip); const uncomp = 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('data-key'); * ``` * @param key The key for the data entry. */ public async get(key: string): Promise { 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('data-key'); * @param key The key for the data entry. */ public async getMany(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 { 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 { 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; } }