UNPKG

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
"use strict"; 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;