self-reload-json
Advanced tools
Comparing version 0.2.0 to 0.3.0
261
index.js
@@ -1,129 +0,200 @@ | ||
(function() { | ||
var fs = require('fs'), | ||
path = require('path'), | ||
util = require('util'), | ||
EventEmitter = require('events').EventEmitter, | ||
_ = require('underscore'); | ||
(() => { | ||
'use strict'; | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const util = require('util'); | ||
const EventEmitter = require('events').EventEmitter; | ||
var omitKeys = _.allKeys(new EventEmitter()); | ||
omitKeys.push('stop', 'resume', 'save', 'forceUpdate'); | ||
const privateProps = Symbol('private'); | ||
const fileChanged = Symbol('onFileChange'); | ||
var SelfReloadJSON = function SelfReloadJSON(options) { | ||
EventEmitter.call(this); | ||
function deleteAll(obj, keys) { | ||
for(const key of keys) | ||
if(key in obj) | ||
delete obj[key]; | ||
return obj; | ||
} | ||
switch(typeof options) { | ||
case 'string': options = { fileName: options }; break; | ||
case 'object': case 'undefined': break; | ||
default: throw new Error('Invalid options type.'); | ||
} | ||
function getAllProperties(obj, output) { | ||
output = output || []; | ||
if(!obj) return output; | ||
Array.prototype.push.apply(output, Object.getOwnPropertyNames(obj)); | ||
return getAllProperties(Object.getPrototypeOf(obj), output); | ||
} | ||
var updateFile, onFileChange, stop, resume, save; | ||
var content, updateFileLock, fileName, watcher; | ||
class SelfReloadJSON extends EventEmitter { | ||
constructor(options) { | ||
super(); | ||
content = this; | ||
switch(typeof options) { | ||
case 'string': options = { fileName: options }; break; | ||
case 'object': case 'undefined': break; | ||
default: throw new Error('Invalid options type.'); | ||
} | ||
options = _.defaults(options || {}, { | ||
fileName: '', | ||
encoding: 'utf8', | ||
additive: false, | ||
method: 'native', | ||
interval: 5000, | ||
reviver: null, | ||
replacer: null | ||
}); | ||
// Recursive fetch all property names even in prototype | ||
// which will be omitted from fetched JSON object. | ||
const localOmitKeys = getAllProperties(this); | ||
content.stop = stop = function stop() { | ||
if(watcher) { | ||
if(typeof watcher === 'string') | ||
fs.unwatchFile(watcher, onFileChange); | ||
else | ||
watcher.close(); | ||
watcher = null; | ||
// Convert all internal values to non-enumerable, | ||
// prevents those values exposed by save function. | ||
for(let key in this) { | ||
const value = this[key]; | ||
delete this[key]; | ||
Object.defineProperty(this, key, { | ||
value, | ||
enumerable: false, | ||
configurable: true, | ||
writable: true | ||
}); | ||
} | ||
}; | ||
content.resume = resume = function resume() { | ||
stop(); | ||
this[privateProps] = { | ||
keys: [], | ||
fileName: '', | ||
watcher: null, | ||
content: null, | ||
fileChanged: this[fileChanged].bind(this), | ||
omitKeys: localOmitKeys, | ||
options: Object.assign({ | ||
fileName: '', | ||
encoding: 'utf8', | ||
additive: false, | ||
method: 'native', | ||
interval: 5000, | ||
reviver: null, | ||
replacer: null | ||
}, options || {}) | ||
}; | ||
this.resume(); | ||
} | ||
stop() { | ||
const internals = this[privateProps]; | ||
if(!internals.watcher) return; | ||
if(typeof internals.watcher === 'string') | ||
fs.unwatchFile(internals.watcher, internals.fileChanged); | ||
else | ||
internals.watcher.close(); | ||
internals.watcher = null; | ||
} | ||
resume() { | ||
this.stop(); | ||
const internals = this[privateProps]; | ||
const options = internals.options; | ||
if(internals.retryTimer) { | ||
clearImmediate(internals.retryTimer); | ||
delete internals.retryTimer; | ||
} | ||
options.fileName = path.resolve(options.fileName); | ||
fileName = path.basename(options.fileName); | ||
internals.fileName = path.basename(options.fileName); | ||
switch(options.method) { | ||
case 'native': | ||
watcher = fs.watch(options.fileName, { | ||
encoding: options.encoding | ||
}, onFileChange); | ||
internals.watcher = fs.watch( | ||
options.fileName, | ||
{ encoding: options.encoding }, | ||
internals.fileChanged | ||
); | ||
break; | ||
case 'polling': | ||
watcher = options.fileName; | ||
fs.watchFile(options.fileName, { | ||
interval: options.interval | ||
}, onFileChange); | ||
internals.watcher = options.fileName; | ||
fs.watchFile( | ||
options.fileName, | ||
{ interval: options.interval }, | ||
internals.fileChanged | ||
); | ||
break; | ||
} | ||
updateFile(); | ||
}; | ||
this.forceUpdate(); | ||
} | ||
content.save = save = function save(opts) { | ||
opts = _.defaults(opts || {}, { | ||
encoding: options.encoding, | ||
replacer: options.replacer, | ||
space: null | ||
}); | ||
updateFileLock = true; | ||
[fileChanged](a, b) { | ||
try { | ||
fs.writeFileSync( | ||
options.fileName, | ||
JSON.stringify(_.omit(content, function(v, k) { | ||
return _.contains(omitKeys, k); | ||
}), opts.replacer, opts.space), | ||
_.omit(opts, 'replacer', 'space') | ||
); | ||
} finally { | ||
updateFileLock = false; | ||
} | ||
}; | ||
onFileChange = function onFileChange(a, b) { | ||
try { | ||
if(a instanceof fs.Stats) { | ||
if(a.mtime === b.mtime) return; | ||
updateFile(); | ||
} else { | ||
if(b !== fileName) return; | ||
updateFile(); | ||
if(b !== this[privateProps].fileName) return; | ||
} | ||
this.forceUpdate(); | ||
} catch(err) { | ||
console.log(err.stack ? err.stack : err); | ||
console.log(err.stack || err); | ||
} | ||
}; | ||
} | ||
content.forceUpdate = updateFile = function updateFile() { | ||
if(updateFileLock) return; | ||
updateFileLock = true; | ||
save(options) { | ||
const internals = this[privateProps]; | ||
options = Object.assign( | ||
{ space: null }, | ||
internals.options, | ||
options || {} | ||
); | ||
internals.updateFileLock = true; | ||
try { | ||
var rawFile = fs.readFileSync(options.fileName, { | ||
encoding: options.encoding | ||
}); | ||
var newContent = _.omit(JSON.parse(rawFile, options.reviver), function(v, k) { | ||
return _.contains(omitKeys, k); | ||
}); | ||
if(!options.additive) { | ||
var removeList = _.chain(content).keys().difference(omitKeys).value(); | ||
for(var i = 0, l = removeList.length; i < l; i++) | ||
delete content[removeList[i]]; | ||
const json = JSON.stringify(this, options.replacer, options.space); | ||
fs.writeFileSync(internals.options.fileName, json, options); | ||
internals.raw = json; | ||
} finally { | ||
internals.updateFileLock = false; | ||
} | ||
} | ||
forceUpdate() { | ||
const internals = this[privateProps]; | ||
const options = internals.options; | ||
if(internals.updateFileLock) return; | ||
internals.updateFileLock = true; | ||
if(internals.retryTimer) { | ||
clearImmediate(internals.retryTimer); | ||
delete internals.retryTimer; | ||
} | ||
try { | ||
const rawContent = fs.readFileSync(options.fileName, { encoding: options.encoding }); | ||
if(internals.raw === rawContent) return; | ||
internals.raw = rawContent; | ||
let newContent = JSON.parse(rawContent, options.reviver); | ||
if(typeof newContent !== 'object') | ||
newContent = { value: newContent }; | ||
// Ignore all values which defined internally | ||
const safeContent = deleteAll(Object.assign({}, newContent), internals.omitKeys); | ||
if(options.additive) { | ||
Object.assign(this, safeContent); | ||
Object.assign(internals.newContent, newContent); | ||
} else { | ||
deleteAll(this, internals.keys); | ||
Object.assign(this, safeContent); | ||
internals.newContent = newContent; | ||
} | ||
_.extendOwn(content, newContent); | ||
content.emit('updated'); | ||
internals.keys = Object.keys(internals.newContent); | ||
} catch(err) { | ||
console.log(err.stack ? err.stack : err); | ||
content.emit('error', err); | ||
switch(err && err.code) { | ||
case 'EBUSY': | ||
case 'EAGAIN': | ||
internals.retryTimer = setImmediate(this.forceUpdate.bind(this)); | ||
return; | ||
} | ||
console.error(err.stack || err); | ||
this.emit('error', err); | ||
return; | ||
} finally { | ||
updateFileLock = false; | ||
internals.updateFileLock = false; | ||
} | ||
}; | ||
resume(); | ||
}; | ||
this.emit('updated', internals.newContent); | ||
} | ||
} | ||
util.inherits(SelfReloadJSON, EventEmitter); | ||
module.exports = SelfReloadJSON; | ||
})(); |
{ | ||
"name": "self-reload-json", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Self reloading JSON handler", | ||
@@ -23,5 +23,3 @@ "main": "index.js", | ||
"homepage": "https://github.com/JLChnToZ/selfreloadjson#readme", | ||
"dependencies": { | ||
"underscore": "^1.8.3" | ||
} | ||
"dependencies": {} | ||
} |
@@ -12,3 +12,5 @@ Self Reload JSON | ||
With NPM: | ||
`npm install self-reload-json` | ||
```sh | ||
$ npm install self-reload-json | ||
``` | ||
@@ -28,2 +30,4 @@ Then in script file: | ||
The `SelfReloadJSON` class itself inherits [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter). | ||
### Methods | ||
@@ -60,4 +64,4 @@ #### `new SelfReloadJSON(options | fileName)` | ||
#### `on('updated', function() { ... })` | ||
This event will be emitted after the JSON content refreshed. | ||
#### `on('updated', function(json) { ... })` | ||
This event will be emitted after the JSON content refreshed. The `json` parameter is the raw parsed JSON object (not the SelfReloadJSON instance itself). | ||
@@ -69,3 +73,3 @@ #### `on('error', function(err) { ... })` | ||
----- | ||
If a property name conflicts with the function names described above, they will be ignored. | ||
If a property name conflicts with the function names described above, they will be ignored. To get those values you should use `updated` event listener instead. | ||
@@ -72,0 +76,0 @@ license |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
12511
0
173
75
1
- Removedunderscore@^1.8.3
- Removedunderscore@1.13.7(transitive)