cloudservices/src/class/LocalStorage.ts

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;
}
}