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.
242 lines
6.1 KiB
242 lines
6.1 KiB
'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;
|
|
|