1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
| import * as fs from 'fs-extra'; import * as path from 'path'; import * as crypto from 'crypto';
class Store<T extends Record<string, any> = Record<string, unknown>> { private _dirName: string; private _fileName: string; private _useDot: boolean = true;
private _fileEnc?: { iv: Buffer; key: Buffer; algo: crypto.CipherGCMTypes; };
private _json: T = {} as T;
constructor(opts: { dirName: string; fileName: string; useDot?: boolean; fileEncKey?: string; }) { this._dirName = opts.dirName; this._fileName = opts.fileName; if (typeof opts.useDot === 'boolean') { this._useDot = opts.useDot; }
if (opts.fileEncKey) { const iv = crypto.randomBytes(16); const key = crypto.pbkdf2Sync(opts.fileEncKey, iv, 10000, 32, 'sha512'); this._fileEnc = { algo: 'aes-256-gcm', key, iv }; }
const filepath = path.join(this._dirName, this._fileName); if (fs.existsSync(filepath)) { try { if (opts.fileEncKey) { const _buf = fs.readFileSync(filepath); const _iv = _buf.slice(0, 16); const _key = crypto.pbkdf2Sync(opts.fileEncKey, _iv, 10000, 32, 'sha512'); const _authTag = _buf.slice(-16); const decipher = crypto.createDecipheriv(this._fileEnc!.algo, _key, _iv); decipher.setAuthTag(_authTag);
this._json = JSON.parse(Buffer.concat([decipher.update(_buf.slice(16, -16)), decipher.final()]).toString('utf-8')); } else { this._json = JSON.parse(fs.readFileSync(filepath, { encoding: 'utf8' })); } } catch (_) { } } else { if (!fs.existsSync(this._dirName)) { fs.ensureDirSync(this._dirName); } } } data(format: 'string' | 'format-string'): string; data(format?: 'object'): T; data(format?: 'string' | 'format-string' | 'object') { switch (format) { case 'string': return JSON.stringify(this._json); case 'format-string': return JSON.stringify(this._json, undefined, 4); case 'object': default: return this._json; } }
get<K extends keyof T>(key: K) { return this._json[key] || {} as T[K]; }
set<K extends keyof T>(key: K, value: T[K]) { if (this._useDot) { const sp = key.toString().split('.'); if (sp.length === 1) { this._json[key] = value; } else if (sp.length > 1) { let obj: any = this._json;
for (let k of sp.slice(0, -1)) { if (!(obj[k] && typeof obj[k] === 'object')) { obj[k] = {}; }
obj = obj[k]; }
obj[sp[sp.length - 1]] = value; } } else { this._json[key] = value; }
if (this._fileEnc) { const cipher = crypto.createCipheriv(this._fileEnc.algo, this._fileEnc.key, this._fileEnc.iv); const writeBuffer = Buffer.concat([this._fileEnc.iv, cipher.update(Buffer.from(this.data('string'))), cipher.final(), cipher.getAuthTag()]); fs.writeFileSync(path.join(this._dirName, this._fileName), writeBuffer); } else { fs.writeFileSync(path.join(this._dirName, this._fileName), JSON.stringify(this._json, undefined, 4)); } }
clear(delFile = false) { this._json = {} as T; if (delFile) { const filepath = path.join(this._dirName, this._fileName); if (fs.existsSync(filepath)) { fs.unlinkSync(filepath); } } } }
const s = new Store({ dirName: __dirname, fileName: 'store.dat', fileEncKey: '123456' });
const count = 20; let sum = 0; for (let i = 0; i < count; i++) { const t = Date.now(); for (let i = 0; i < 1000; i++) { s.set('a.b.c', 'ccc'); } sum += Date.now() - t; }
console.log(`${sum / count}ms`);
|