UNPKG

keyv-file

Version:

File storage adapter for Keyv, using msgpack to serialize data fast and small.

234 lines (233 loc) 7.33 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Field = exports.KeyvFile = exports.defaultOpts = void 0; exports.makeField = makeField; const tslib_1 = require("tslib"); const os = tslib_1.__importStar(require("os")); const fs = tslib_1.__importStar(require("fs-extra")); const events_1 = tslib_1.__importDefault(require("events")); const serialize_1 = require("@keyv/serialize"); exports.defaultOpts = { deserialize: serialize_1.defaultDeserialize, dialect: 'redis', expiredCheckDelay: 24 * 3600 * 1000, // ms filename: `${os.tmpdir()}/keyv-file/default.json`, serialize: serialize_1.defaultSerialize, writeDelay: 100, // ms checkFileLock: false, }; function isNumber(val) { return typeof val === 'number'; } class KeyvFile extends events_1.default { constructor(options) { super(); this.ttlSupport = true; this.opts = Object.assign({}, exports.defaultOpts, options); if (this.opts.checkFileLock) { this.acquireFileLock(); } try { const data = this.opts.deserialize(fs.readFileSync(this.opts.filename, 'utf8')); if (!Array.isArray(data.cache)) { const _cache = data.cache; data.cache = []; for (const key in _cache) { if (_cache.hasOwnProperty(key)) { data.cache.push([key, _cache[key]]); } } } this._cache = new Map(data.cache); this._lastExpire = data.lastExpire; } catch (e) { this._cache = new Map(); this._lastExpire = Date.now(); } } get _lockFile() { return this.opts.filename + '.lock'; } acquireFileLock() { try { let fd = fs.openSync(this._lockFile, "wx"); fs.closeSync(fd); process.on('SIGINT', () => { fs.unlinkSync(this._lockFile); process.exit(0); }); process.on('exit', () => { this.releaseFileLock(); }); } catch (error) { console.error(`[keyv-file] There is another process using this file`); throw error; } } releaseFileLock() { fs.unlinkSync(this._lockFile); } get(key) { return tslib_1.__awaiter(this, void 0, void 0, function* () { try { const data = this._cache.get(key); if (!data) { return undefined; } else if (this.isExpired(data)) { yield this.delete(key); return undefined; } else { return data.value; } } catch (error) { // do nothing; } }); } getMany(keys) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const results = yield Promise.all(keys.map((key) => tslib_1.__awaiter(this, void 0, void 0, function* () { const value = yield this.get(key); return value; }))); return results; }); } set(key, value, ttl) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (ttl === 0) { ttl = undefined; } this._cache.set(key, { expire: isNumber(ttl) ? Date.now() + ttl : undefined, value: value, }); return this.save(); }); } delete(key) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const ret = this._cache.delete(key); yield this.save(); return ret; }); } deleteMany(keys) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const deletePromises = keys.map((key) => this.delete(key)); const results = yield Promise.all(deletePromises); return results.every((result) => result); }); } clear() { return tslib_1.__awaiter(this, void 0, void 0, function* () { this._cache = new Map(); this._lastExpire = Date.now(); return this.save(); }); } has(key) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const value = yield this.get(key); return value !== undefined; }); } isExpired(data) { return isNumber(data.expire) && data.expire <= Date.now(); } clearExpire() { const now = Date.now(); if (now - this._lastExpire <= this.opts.expiredCheckDelay) { return; } for (const key of this._cache.keys()) { const data = this._cache.get(key); if (this.isExpired(data)) { this._cache.delete(key); } } this._lastExpire = now; } saveToDisk() { const cache = []; for (const [key, val] of this._cache) { cache.push([key, val]); } const data = this.opts.serialize({ cache, lastExpire: this._lastExpire, }); return new Promise((resolve, reject) => { fs.outputFile(this.opts.filename, data, (err) => { if (err) { reject(err); } else { resolve(); } }); }); } save() { this.clearExpire(); if (this._savePromise) { return this._savePromise; } this._savePromise = new Promise((resolve, reject) => { setTimeout(() => { this.saveToDisk() .then(resolve, reject) .finally(() => { this._savePromise = void 0; }); }, this.opts.writeDelay); }); return this._savePromise; } disconnect() { return Promise.resolve(); } iterator(namespace) { return tslib_1.__asyncGenerator(this, arguments, function* iterator_1() { for (const [key, data] of this._cache.entries()) { if (key === undefined) { continue; } // Filter by namespace if provided if (!namespace || key.includes(namespace)) { yield yield tslib_1.__await([key, data.value]); } } }); } } exports.KeyvFile = KeyvFile; exports.default = KeyvFile; class Field { constructor(kv, key, defaults) { this.kv = kv; this.key = key; this.defaults = defaults; } get() { return tslib_1.__awaiter(this, arguments, void 0, function* (def = this.defaults) { var _a; return (_a = (yield this.kv.get(this.key))) !== null && _a !== void 0 ? _a : def; }); } set(val, ttl) { return this.kv.set(this.key, val, ttl); } delete() { return this.kv.delete(this.key); } } exports.Field = Field; function makeField(kv, key, defaults) { return new Field(kv, key, defaults); }