key-file-storage
Version:
Simple key-value storage directly on file system, maps each key to a separate file.
240 lines (239 loc) • 8.89 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
var fs_extra_1 = require("fs-extra");
var path_1 = require("path");
var isValidPath = require('is-valid-path');
var recurFs = require('recur-fs');
function keyFileBasic(storagePath, cache) {
storagePath = storagePath || __dirname; // Current working folder by default.
storagePath = String(storagePath);
if (!isValidPath(storagePath)) {
throw new Error('Invalid stroage path.');
}
return {
// Synchronous
setSync: setSync,
getSync: getSync,
deleteSync: deleteSync,
clearSync: clearSync,
hasSync: hasSync,
querySync: querySync,
// Asynchronous
setAsync: setAsync,
getAsync: getAsync,
deleteAsync: deleteAsync,
clearAsync: clearAsync,
hasAsync: hasAsync,
queryAsync: queryAsync,
};
function setSync(key, value) {
if (value === undefined)
return deleteSync(key);
key = validizeKey(key);
var file = path_1.join(storagePath, key);
fs_extra_1.outputJsonSync(file, value, { spaces: 2 });
return (cache[key] = value);
}
function getSync(key) {
key = validizeKey(key);
if (key in cache)
return cache[key];
var file = path_1.join(storagePath, key);
try {
var status = fs_extra_1.statSync(file);
if (!status || !status.isFile())
return (cache[key] = null);
}
catch (err) {
return (cache[key] = null);
}
return (cache[key] = fs_extra_1.readJsonSync(file));
}
function deleteSync(key) {
key = validizeKey(key);
if (key === '*')
return clearSync();
var file = path_1.join(storagePath, key);
fs_extra_1.removeSync(file);
return delete cache[key];
}
function clearSync() {
fs_extra_1.removeSync(storagePath);
return delete cache['*'];
}
function hasSync(key) {
key = validizeKey(key);
if (key in cache)
return true;
var file = path_1.join(storagePath, key);
try {
var status = fs_extra_1.statSync(file);
if (!status || !status.isFile())
return false;
}
catch (err) {
return false;
}
return true;
}
function querySync(collection) {
collection = validizeKey(collection);
if (collection in cache)
return cache[collection];
try {
var collectionPath = path_1.join(storagePath, collection);
var files = recurFs.readdir.sync(collectionPath, function (resource, status) {
return status.isFile();
});
files = files.map(function (file) { return validizeKey(path_1.relative(storagePath, file)); });
return (cache[collection] = files || []);
}
catch (err) {
return [];
}
}
function setAsync(key, value) {
if (value === undefined)
return deleteAsync(key);
key = validizeKey(key);
var file = path_1.join(storagePath, key);
return new Promise(function (resolve, reject) {
fs_extra_1.outputJson(file, value, { spaces: 2 }, function (err) {
if (err)
return reject(err);
resolve((cache[key] = value));
});
});
}
function getAsync(key) {
key = validizeKey(key);
if (key in cache)
return Promise.resolve(cache[key]);
var file = path_1.join(storagePath, key);
return new Promise(function (resolve, reject) {
fs_extra_1.stat(file, function (err, status) {
if (err || !status || !status.isFile())
return resolve((cache[key] = null));
fs_extra_1.readJson(file, function (err, value) {
if (err)
return reject(err);
resolve((cache[key] = value));
});
});
});
}
function deleteAsync(key) {
key = validizeKey(key);
if (key === '*')
return clearAsync();
var file = path_1.join(storagePath, key);
return new Promise(function (resolve, reject) {
fs_extra_1.remove(file, function (err) {
if (err)
return reject(err);
resolve(delete cache[key]);
});
});
}
function clearAsync() {
return new Promise(function (resolve, reject) {
fs_extra_1.remove(storagePath, function (err) {
if (err)
return reject(err);
resolve(delete cache['*']);
});
});
}
function hasAsync(key) {
key = validizeKey(key);
if (key in cache)
return Promise.resolve(true);
var file = path_1.join(storagePath, key);
return new Promise(function (resolve, reject) {
fs_extra_1.stat(file, function (err, status) {
resolve(!!(!err && status && status.isFile()));
});
});
}
function queryAsync(collection) {
collection = validizeKey(collection);
if (collection in cache)
return Promise.resolve(cache[collection]);
return new Promise(function (resolve, reject) {
//// This implementation does not work with empty folders:
// recurFs.readdir(collection, function(resource, status, next) {
// next(status.isFile());
// }, function(err, resources) {
// if (err) {
// reject(err);
// }
// else {
// resolve(resources.map(file => path.relative(storagePath, file)));
// }
// });
var fileList = [], jobNumber = 1, terminated = false;
var collectionPath = path_1.join(storagePath, collection);
fs_extra_1.stat(collectionPath, function (err, status) {
if (err) {
if (err.code === 'ENOENT')
resolve((cache[collection] = []));
else
reject(err);
}
else {
processFolder(collection);
}
});
function processFolder(folder) {
if (terminated)
return;
var folderPath = path_1.join(storagePath, folder);
fs_extra_1.readdir(folderPath, function (err, files) {
if (terminated)
return;
jobNumber--;
if (err) {
terminated = true;
reject(err);
}
jobNumber += files.length;
if (!jobNumber) {
resolve((cache[collection] = fileList));
}
files.forEach(function (file) {
if (terminated)
return;
var filePath = path_1.join(folderPath, file);
fs_extra_1.stat(filePath, function (err, status) {
if (terminated)
return;
jobNumber--;
if (err) {
terminated = true;
reject(err);
}
if (status.isFile()) {
fileList.push(validizeKey(path_1.relative(storagePath, filePath)));
}
else if (status.isDirectory()) {
jobNumber++;
processFolder(filePath);
}
if (!jobNumber) {
resolve((cache[collection] = fileList));
}
});
});
});
}
});
}
///////////////////////////////////////////////////
function validizeKey(key) {
key = String(key).replace(/\\/g, '/');
if (key.includes('/..') || key.includes('../') || key === '..')
throw new Error('Invalid key name.');
return key;
}
}
exports.default = keyFileBasic;