keyv-file
Version:
File storage adapter for Keyv, using msgpack to serialize data fast and small.
234 lines (233 loc) • 7.33 kB
JavaScript
'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);
}