You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
6.1 KiB
243 lines
6.1 KiB
3 years ago
|
'use strict';
|
||
|
const assert = require('assert');
|
||
|
const _ = require('lodash');
|
||
|
|
||
|
/**
|
||
|
* Proxy handler for Storage
|
||
|
*/
|
||
|
const proxyHandler = {
|
||
|
get(storage, property) {
|
||
|
return storage.get(property);
|
||
|
},
|
||
|
set(storage, property, value) {
|
||
|
storage.set(property, value);
|
||
|
return true;
|
||
|
},
|
||
|
ownKeys(storage) {
|
||
|
return Reflect.ownKeys(storage._store);
|
||
|
},
|
||
|
has(target, prop) {
|
||
|
return target.get(prop) !== undefined;
|
||
|
},
|
||
|
getOwnPropertyDescriptor(target, key) {
|
||
|
return {
|
||
|
get: () => this.get(target, key),
|
||
|
enumerable: true,
|
||
|
configurable: true
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Storage instances handle a json file where Generator authors can store data.
|
||
|
*
|
||
|
* The `Generator` class instantiate the storage named `config` by default.
|
||
|
*
|
||
|
* @constructor
|
||
|
* @param {String} [name] The name of the new storage (this is a namespace)
|
||
|
* @param {mem-fs-editor} fs A mem-fs editor instance
|
||
|
* @param {String} configPath The filepath used as a storage.
|
||
|
* @param {Object} [options] Storage options.
|
||
|
* @param {Boolean} [options.lodashPath=false] Set true to treat name as a lodash path.
|
||
|
* @param {Boolean} [options.disableCache=false] Set true to disable json object cache.
|
||
|
* @param {Boolean} [options.disableCacheByFile=false] Set true to cleanup cache for every fs change.
|
||
|
*
|
||
|
* @example
|
||
|
* class extend Generator {
|
||
|
* writing: function() {
|
||
|
* this.config.set('coffeescript', false);
|
||
|
* }
|
||
|
* }
|
||
|
*/
|
||
|
class Storage {
|
||
|
constructor(name, fs, configPath, options = {}) {
|
||
|
if (name !== undefined && typeof name !== 'string') {
|
||
|
configPath = fs;
|
||
|
fs = name;
|
||
|
name = undefined;
|
||
|
}
|
||
|
|
||
|
if (typeof options === 'boolean') {
|
||
|
options = { lodashPath: options };
|
||
|
}
|
||
|
|
||
|
_.defaults(options, {
|
||
|
lodash: false,
|
||
|
disableCache: false,
|
||
|
disableCacheByFile: false
|
||
|
});
|
||
|
|
||
|
assert(configPath, 'A config filepath is required to create a storage');
|
||
|
|
||
|
this.path = configPath;
|
||
|
this.name = name;
|
||
|
this.fs = fs;
|
||
|
this.indent = 2;
|
||
|
this.lodashPath = options.lodashPath;
|
||
|
this.disableCache = options.disableCache;
|
||
|
this.disableCacheByFile = options.disableCacheByFile;
|
||
|
|
||
|
this.existed = Object.keys(this._store).length > 0;
|
||
|
|
||
|
this.fs.store.on('change', filename => {
|
||
|
// At mem-fs 1.1.3 filename is not passed to the event.
|
||
|
if (this.disableCacheByFile || (filename && filename !== this.path)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
delete this._cachedStore;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the current store as JSON object
|
||
|
* @return {Object} the store content
|
||
|
* @private
|
||
|
*/
|
||
|
get _store() {
|
||
|
const store = this._cachedStore || this.fs.readJSON(this.path, {});
|
||
|
if (!this.disableCache) {
|
||
|
this._cachedStore = store;
|
||
|
}
|
||
|
|
||
|
if (!this.name) {
|
||
|
return store || {};
|
||
|
}
|
||
|
|
||
|
return (this.lodashPath ? _.get(store, this.name) : store[this.name]) || {};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Persist a configuration to disk
|
||
|
* @param {Object} val - current configuration values
|
||
|
* @private
|
||
|
*/
|
||
|
_persist(val) {
|
||
|
let fullStore;
|
||
|
if (this.name) {
|
||
|
fullStore = this.fs.readJSON(this.path, {});
|
||
|
if (this.lodashPath) {
|
||
|
_.set(fullStore, this.name, val);
|
||
|
} else {
|
||
|
fullStore[this.name] = val;
|
||
|
}
|
||
|
} else {
|
||
|
fullStore = val;
|
||
|
}
|
||
|
|
||
|
this.fs.writeJSON(this.path, fullStore, null, this.indent);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Save a new object of values
|
||
|
*/
|
||
|
save() {
|
||
|
this._persist(this._store);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a stored value
|
||
|
* @param {String} key The key under which the value is stored.
|
||
|
* @return {*} The stored value. Any JSON valid type could be returned
|
||
|
*/
|
||
|
get(key) {
|
||
|
return this._store[key];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a stored value from a lodash path
|
||
|
* @param {String} path The path under which the value is stored.
|
||
|
* @return {*} The stored value. Any JSON valid type could be returned
|
||
|
*/
|
||
|
getPath(path) {
|
||
|
return _.get(this._store, path);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get all the stored values
|
||
|
* @return {Object} key-value object
|
||
|
*/
|
||
|
getAll() {
|
||
|
return _.cloneDeep(this._store);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Assign a key to a value and schedule a save.
|
||
|
* @param {String} key The key under which the value is stored
|
||
|
* @param {*} val Any valid JSON type value (String, Number, Array, Object).
|
||
|
* @return {*} val Whatever was passed in as val.
|
||
|
*/
|
||
|
set(key, val) {
|
||
|
assert(!_.isFunction(val), "Storage value can't be a function");
|
||
|
|
||
|
const store = this._store;
|
||
|
|
||
|
if (_.isObject(key)) {
|
||
|
val = _.assignIn(store, key);
|
||
|
} else {
|
||
|
store[key] = val;
|
||
|
}
|
||
|
|
||
|
this._persist(store);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Assign a lodash path to a value and schedule a save.
|
||
|
* @param {String} path The key under which the value is stored
|
||
|
* @param {*} val Any valid JSON type value (String, Number, Array, Object).
|
||
|
* @return {*} val Whatever was passed in as val.
|
||
|
*/
|
||
|
setPath(path, val) {
|
||
|
assert(!_.isFunction(val), "Storage value can't be a function");
|
||
|
|
||
|
const store = this._store;
|
||
|
_.set(store, path, val);
|
||
|
this._persist(store);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete a key from the store and schedule a save.
|
||
|
* @param {String} key The key under which the value is stored.
|
||
|
*/
|
||
|
delete(key) {
|
||
|
const store = this._store;
|
||
|
delete store[key];
|
||
|
this._persist(store);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setup the store with defaults value and schedule a save.
|
||
|
* If keys already exist, the initial value is kept.
|
||
|
* @param {Object} defaults Key-value object to store.
|
||
|
* @return {*} val Returns the merged options.
|
||
|
*/
|
||
|
defaults(defaults) {
|
||
|
assert(_.isObject(defaults), 'Storage `defaults` method only accept objects');
|
||
|
const val = _.defaults(this.getAll(), defaults);
|
||
|
this.set(val);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create a child storage.
|
||
|
* @param {String} path - relative path of the key to create a new storage.
|
||
|
* @return {Storage} Returns a new Storage.
|
||
|
*/
|
||
|
createStorage(path) {
|
||
|
const childName = this.name ? `${this.name}.${path}` : path;
|
||
|
return new Storage(childName, this.fs, this.path, true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a proxy object.
|
||
|
* @return {Object} proxy.
|
||
|
*/
|
||
|
createProxy() {
|
||
|
return new Proxy(this, proxyHandler);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = Storage;
|