UNPKG

data-context

Version:

Watch data changes in the browser and node.js

1,589 lines (1,051 loc) 56.7 kB
/** Copyright (c) 2024, Manuel Lõhmus (MIT License). */ "use strict"; (function () { exportModule("data-context", function factory() { //var isDebug = true; var ignoreMetadata = false; /** * Create a new proxy object with the same structure as the original object. * With the ability to listen to changes in the object * and the ability to restore the original object. * @param {any} value Value. * @param {string} propertyName Property name. Default is null. * @param {any} parent Parent. Default is null. * @returns {Proxy} Returns a proxy object. */ function createDataContext(value, propertyName = null, parent = null) { if (createDataContext === this?.constructor) { throw new Error('This function must be used without the `new` keyword.'); } var type = _typeof(value); if (value?._isDataContext || type !== "object" && type !== "array") { return value; } var proxy = null; Object.defineProperties(value, { _isDataContext: { value: true, writable: false, configurable: false, enumerable: false }, _isModified: { value: false, writable: true, configurable: false, enumerable: false }, _modified: { value: [], writable: false, configurable: false, enumerable: false }, _propertyName: { configurable: false, enumerable: false, get: function () { return propertyName; }, set: function (v) { propertyName = v; } }, _parent: { configurable: false, enumerable: false, get: function () { if (parent === null) { return null; } if (_isMyParent()) { return parent; } return null; }, set: function (v) { parent = v; } }, toString: { writable: true, configurable: false, enumerable: false, value: function toString() { if (type === "object") { return "{" + Object.keys(this).map(function (k) { return _string(k) + ":" + _string(value[k]); }).join(",") + "}"; } if (type === "array") { return "[" + this.map(function (v) { return _string(v); }).join(",") + "]"; } return "undefined"; function _string(val) { if (val?._isDataContext) { return val.toString(); } var t = typeof val; if (t === "string") { return '"' + val + '"'; } if (t === "boolean") { return Boolean.prototype.toString.call(val); } if (t === "number") { return Number.prototype.toString.call(val); } if (val === null) { return "null"; } if (Array.isArray(val)) { return "[" + val.map(function (v) { return _string(v); }).join(",") + "]"; } if (t === "object") { return "{" + Object.keys(val).map(function (k) { return _string(k) + ":" + _string(val[k]); }).join(",") + "}"; } return "undefined"; } } }, overwritingData: { writable: true, configurable: false, enumerable: false, value: function _overwritingData(text, reviver) { parse.call(this, text, reviver); } }, stringifyChanges: { writable: true, configurable: false, enumerable: false, value: function _stringifyChanges(replacer, space, modifiedData = true, setUnmodified = true) { return stringify(this, replacer, space, { modifiedData, setUnmodified }); } }, isChanged: { configurable: false, enumerable: false, get: function () { return Boolean(this._isModified || this._modified.length); } }, resetChanges: { writable: true, configurable: false, enumerable: false, value: function _resetChanges() { for (var k of this._modified) { if (this[k] && "undefined" !== typeof this[k].resetChanges) { this[k].resetChanges(); } } if ("undefined" !== typeof this._isModified) { this._isModified = false; } if ("undefined" !== typeof this._modified) { this._modified.length = 0; } } }, // EventEmitter _events: { value: {}, writable: true, configurable: false, enumerable: false }, once: { writable: false, configurable: false, enumerable: false, /** * @param {string} eventName * @param {(...params:any)=>void} listener * @returns {DataContext} */ value: function once(eventName, listener) { return this.on(eventName, listener, false); } }, on: { writable: false, configurable: false, enumerable: false, /** * @param {string} eventName * @param {(...params:any)=>void} listener * @param {boolean|()=>boolean|Node} isActive if false then adds a one-time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked. * @returns {DataContext} */ value: function on(eventName, listener, isActive = true) { if (!this._events[eventName]) { this._events[eventName] = []; } this._events[eventName].push(listener); isActive && (listener.isActive = isActive); return this; } }, emitToParent: { writable: false, configurable: false, enumerable: false, /** * @param {string} eventName * @param {...any} params * @returns {boolean} Returns true if the event had listeners, false otherwise. */ value: function emit(eventName, ...params) { var ret = this.emit(eventName, ...params); if (parent && typeof parent.emitToParent === "function") { //_modified if (params[0] && params[0].propertyPath) { params[0].propertyPath.unshift(propertyName); } if (eventName === '-' || eventName === '-change') { ret = parent.emitToParent(eventName, ...params) || ret; } else { ret = parent.emitToParent(propertyName, ...params) || ret; } } return ret; } }, emit: { writable: false, configurable: false, enumerable: false, value: function emit(eventName, ...params) { var ret = false; var arr = this._events[eventName] || []; var index = 0; while (index < arr.length) { var listener = arr[index]; if (arr[index] === listener && (typeof listener.isActive === "function" && !listener.isActive() || listener.isActive?.isConnected === false || listener.isActive === undefined || !listener.isActive === true)) { arr.splice(index, 1); } if (typeof listener.isActive === "function" && listener.isActive() || listener.isActive?.isConnected === true || listener.isActive === undefined || listener.isActive === true) { ret = true; if (!listener.call("undefined" != typeof window && listener.isActive instanceof window.Node && listener.isActive, ...params)) { arr.splice(index, 1); } } if (arr[index] === listener) { index++; } } if (this._events[eventName] && !this._events[eventName].length) { delete this._events[eventName]; } return ret; } } }); proxy = new Proxy(value, handler); Object.keys(value).forEach(function (key) { if (value[key] && !value[key]._isDataContext) { value[key] = createDataContext(value[key]); } if (value[key] && value[key]._isDataContext) { value[key]._propertyName = key; value[key]._parent = proxy; } }); return proxy; function _isMyParent() { return !parent || parent && parent[propertyName] === proxy; } } /** * Stringify type. * @param {any} v Value. * @returns {string} Returns the type of the value. */ function _typeof(v) { return v === null ? "null" : Array.isArray(v) ? "array" : typeof v; } /** * Is global object in the browser and Node.js. * @param {any} g Check object. * @returns {boolean} Returns true if the object is global. */ function isGlobal(g) { return g === (typeof globalThis !== 'undefined' ? globalThis : global || self); } //#region *** Handler *** var handler = { deleteProperty, set }; function deleteProperty(target, property) { if (target.propertyIsEnumerable(property)) { var oldValue = target[property], newValue = undefined; var ret = Reflect.deleteProperty(target, property); if (oldValue === undefined) { //console.log("? del: oldValue is undefined", property, { target, propertyPath: [property], oldValue, newValue }); target.emitToParent("-", { eventName: "delete", target, propertyPath: [property], oldValue, newValue }); target.emitToParent(property, { eventName: "delete", target, propertyPath: [property], oldValue, newValue }); } //else if (oldValue._parent === null) { // //console.log("'-delete' del: oldValue._parent is null", property, { target, propertyPath: [property], oldValue, newValue }); // target.emitToParent("-delete", { target, propertyPath: [property], oldValue, newValue }); // target.emitToParent(property, { eventName: "delete", target, propertyPath: [property], oldValue, newValue }); //} else { //console.log("'-delete' del: oldValue is ", property, event); target.emitToParent("-", { eventName: "delete", target, propertyPath: [property], oldValue, newValue }); target.emitToParent(property, { eventName: "delete", target, propertyPath: [property], oldValue, newValue }); } if (Array.isArray(target)) { target._isModified = true; } else if (!target._modified.includes(property)) { target._modified.push(property); } setModified(target._parent, target._propertyName); if (Array.isArray(target)) { var index = target._modified.indexOf(property); if (index > -1) { target._modified.splice(index, 1); } } target.emitToParent(property, { eventName: "delete", target, propertyPath: [property], oldValue, newValue }); clearTimeout(target.emitToParent.timeout); target.emitToParent.timeout = setTimeout(function () { delete target.emitToParent.timeout; target.emitToParent("-change", { eventName: "-change", target, propertyPath: [property], oldValue, newValue }); }); return ret; } return Reflect.deleteProperty(target, property); } function set(target, property, newValue, proxy) { if (target.propertyIsEnumerable(property) || target[property] === undefined) { var oldValue = target[property], newValue = newValue && newValue._isDataContext ? newValue : createDataContext(newValue, property); if (oldValue !== newValue) { var eventName = ""; var _modifiedLength = target?._modified.length; var ret = Reflect.set(target, property, newValue, proxy); var isDC = newValue && newValue._isDataContext; var isNew = isDC && newValue._parent !== proxy && oldValue === undefined; isDC && (newValue._isModified = true); isDC && (newValue._parent = proxy); if (isDC && newValue._propertyName !== property) { //console.log("'-reposition' set: newValue propertyName is change ", newValue + "", ">", property, event); eventName = "reposition"; newValue._propertyName = property; target.emitToParent("-", { eventName, target, propertyPath: [property], oldValue, newValue }); } else if (isDC && isNew) { //console.log("'-new' set: oldValue is undefined", property, event); eventName = "new"; target.emitToParent("-", { eventName, target, propertyPath: [property], oldValue, newValue }); } else { //console.log("'-set' set: newValue parent is change", property, event); eventName = "set"; target.emitToParent("-", { eventName, target, propertyPath: [property], oldValue, newValue }); } setModified(target, property); target.emitToParent(property, { eventName, target, propertyPath: [property], oldValue, newValue }); clearTimeout(target.emitToParent.timeout); target.emitToParent.timeout = setTimeout(function () { delete target.emitToParent.timeout; if (target?._isModified || target?._modified.length !== _modifiedLength) { target.emitToParent("-change", { eventName: "-change", target, propertyPath: [property], oldValue, newValue }); } }); return ret; } } return Reflect.set(target, property, newValue, proxy); } function setModified(target, property) { if (target?._isDataContext) { if (!target._modified.includes(property)) { target._modified.push(property); } setModified(target._parent, target._propertyName); } } //#endregion //#region *** parse / stringify *** /** * Parse a string to an object. * @param {string|number} text Input value. * @param {any} reviver Reviver. * @returns {any} Returns an object. */ function parse(text, reviver) { if (text !== null && typeof text !== "string" && text && typeof text !== "number") { return; } var it = new _it(text); var _this = isGlobal(this) || this === createDataContext ? undefined : this; var isOverwriting = Boolean(_this); if (typeof reviver === "object") { _this = reviver; if (_this?._isDataContext) { reviver = createDataContext; } } if (reviver === createDataContext) { reviver = function _set(k, v) { if (v?._isDataContext) { return v; } return createDataContext(v, k, this); }; } try { var meta = _whitespace(); var value = _value(_this); } catch (e) { throw "[ ERROR ] " + e + " In parsing position: " + it.position + " '" + it.current + "' => " + it.text.substring(it.position < 10 ? 0 : it.position - 10, it.position + 10) .replace(/\r/g, "\\r") .replace(/\n/g, "\\n"); } _setMetadata(value, meta); if (reviver && reviver.name === "_set") { value = reviver.call( undefined, undefined, value ); return value; } if (reviver) { value = reviver.call( { "": value }, "", value ); } return value; function _it(text) { var currentPosition = 0; this.text = text + ''; this.position = 1; this.current = this.text.charAt(0); this.following = this.text.charAt(1); this.is = _is; this.next = _next; this.isInfiniteLoop = _isInfiniteLoop; this.setPosition = _setPosition; function _isInfiniteLoop() { if (currentPosition !== this.position) { currentPosition = this.position; return false; } throw "Incorrect entry."; } function _next() { this.position++; this.current = this.following; this.following = this.text.charAt(this.position); } function _is(str) { if (this.text.substring(this.position - 1, this.position - 1 + str.length) === str) { this.position = this.position + str.length; this.current = this.text.charAt(this.position - 1); this.following = this.text.charAt(this.position); return true; } return false; } function _setPosition(pos) { this.position = pos; this.current = this.text.charAt(pos - 1); this.following = this.text.charAt(pos); } } function _get(val, def) { if (it.current === '\r' && it.following === '\r' || _typeof(val) !== _typeof(def)) { return def; } return val; } function _whitespace() { var meta = [], metadata; __whitespace(); while (metadata = _metadata()) { if (metadata.trim()) { meta.push(metadata); } __whitespace(); } __whitespace(); return meta; function __whitespace() { while (it.current === '\n' || it.current === '\r' || it.current === '\t' || it.current === '\u0020') { it.next(); } } } function _metadata() { if (it.current === '/' && (it.following === '*' || it.following === '/')) { var metadata = '', isMetadata = it.current === '/' && it.following === '*'; it.next(); it.next(); while (isMetadata && !(it.current === '*' && it.following === '/') || !isMetadata && !(it.current === '\r' || it.current === '\n')) { metadata += it.current; it.next(); } if (it.current === '\r' || it.current === '\n') { return ' '; } it.next(); it.next(); return metadata; } } function _setMetadata(obj, metadata, key = '') { if (ignoreMetadata) { return; } if (metadata && metadata.length && obj && typeof obj === 'object') { key = key + ""; Object.defineProperty( obj, '-metadata' + (key ? '-' + key : ''), { value: metadata, writable: true, configurable: true, enumerable: false } ); } } function _value(val) { var _val = _object(val); if (_val !== undefined) { return _val; } _val = _array(val); if (_val !== undefined) { return _val; } _val = _string(); if (_val !== undefined) { return _val; } _val = _true(); if (_val !== undefined) { return _val; } _val = _false(); if (_val !== undefined) { return _val; } _val = _null(); if (_val !== undefined) { return _val; } _val = _number(val); return _val; } function _string() { _whitespace(); if (it.current === '"') { var str = ''; it.next(); if (!it.current) { return; } while (it.current && it.current !== '"') { str += it.current; it.next(); } if (it.current === '"') { it.next(); } return str; } } function _number() { _whitespace(); var str = _negative(); if (it.current === "0") { str += it.current; it.next(); } else { str += _digit(); } if (it.current === ".") { str += it.current; it.next(); str += _digit(); } if (it.current && "eE".includes(it.current)) { str += it.current; it.next(); if (it.current && "-+".includes(it.current)) { str += it.current; it.next(); } str += _digit(); } if (!str) { return undefined; } str = JSON.parse(str); return str; } function _negative() { if (it.current === '-') { it.next(); return '-'; } return ''; } function _digit() { var str = ''; while (it.current && "0123456789".includes(it.current)) { str += it.current; it.next(); } return str; } function _object(val) { var meta = _whitespace(); if (it.current === '{') { it.next(); var obj = _get(val, {}); _setMetadata(obj, meta); while (it.current !== '}' && !it.isInfiniteLoop()) { meta = _whitespace(); if (it.current === '}') { if (isOverwriting && _typeof(_this) === 'object') { Object.keys(_this).forEach(function (k) { delete _this[k]; }); } break; } var k = _key(); _whitespace(); var v = _value(obj[k]); _whitespace(); if (it.current !== ',' && it.current !== '}') { throw "Incorrect object separator."; } if (it.current === ',') { it.next(); } if (typeof k === 'string' && v !== undefined) { if (reviver) { Reflect.set( obj, k, reviver.call(obj, k, v) ); } else { obj[k] = v; } if (typeof obj[k] === 'object') { _setMetadata(obj[k], meta); } else { _setMetadata(obj, meta, k); } } else { delete obj[k]; } } if (it.current === '}') { it.next(); } return obj; } } function _key() { _whitespace(); if (it.current === ":") { throw "Invalid object key."; } var key = _string(); _whitespace(); if (typeof key !== "string" || it.current !== ":") { throw "Invalid object key."; } else { it.next(); } return key; } function _array(val) { var meta = _whitespace(); if (it.current === '[') { it.next(); var arr = _get(val, []); _setMetadata(arr, meta); var i = 0; while (it.current !== ']' && !it.isInfiniteLoop()) { meta = _whitespace(); if (it.current === ']') { if (isOverwriting && _typeof(_this) === 'array') { _this.length = 0; } break; } // for update var index = _index(); if (isOverwriting && index === undefined) { if (typeof isDebug === 'boolean' && isDebug) { throw "Overwriting data -> array index must be."; } console.warn("Overwriting data -> array index must be."); index = i; } _whitespace(); var v = _value(arr[typeof index === "number" && index || i]); _whitespace(); if (it.current !== ',' && it.current !== ']') { throw "Incorrect array separator."; } if (it.current === ',') { it.next(); } if (reviver) { v = reviver.call(arr, String(typeof index === "number" && index || i), v); } if (v === undefined && typeof index === "number" && index > -1) { arr.splice(index, 1); } else if (typeof index === "number" && arr[index] !== undefined) { arr[index] = v; } else { arr.push(v); } if (typeof arr[typeof index === "number" && index || i] === 'object') { _setMetadata(arr[typeof index === "number" && index || i], meta); } else { _setMetadata(arr, meta, typeof index === "number" && index || i); } i++; } if (it.current === ']') { it.next(); } return arr; } } function _index() { _whitespace(); if (it.current === ":") { throw "Invalid array index."; } var pos = it.position; var nr = _number(); _whitespace(); if (typeof nr !== "number" && it.current === ":") { throw "Invalid array index."; } if (typeof nr === "number" && it.current === ":") { it.next(); return nr; } it.setPosition(pos); return; } function _true() { _whitespace(); if (it.is('true')) { return true; } } function _false() { _whitespace(); if (it.is('false')) { return false; } } function _null() { _whitespace(); if (it.is('null')) { return null; } } } /** * Stringify an object. * @param {any} value Input value. * @param {any} replacer Replacer. Optional. * @param {any} space Space. Optional. * @param {any} options Options. Optional. * @returns {string} Returns a string. * * @typedef {Object} Options * @property {boolean} modifiedData Select modified data. Optional. * @property {boolean} setUnmodified Set unmodified. Optional. * @property {WriteStream} writeStream Write stream. Optional. * @property {function} callback Callback. Optional. */ function stringify(value, replacer, space, { modifiedData = false, setUnmodified = false, writeStream = null, callback = null } = {}) { var strJSON = ''; var isStream = _isStream(writeStream); var isModified = false; replacer = _replacer(replacer); space = _space(space); if (typeof replacer === "function") { value = replacer.call( { "": value }, "", value ); } _value(value, 0, function () { if (isStream) { writeStream.end(); } if (typeof callback === "function") { callback(); } }); return strJSON || undefined; function _isStream(obj) { return Boolean(obj && typeof obj === "object" && typeof obj.writable === "boolean"); } function _replacer(replacer) { if (Array.isArray(replacer)) { replacer = fn(replacer); function fn(arr) { var isInitial = true; return function (k, v) { if (isInitial) { isInitial = false; return v; } return arr?.includes(k) ? v : undefined; }; } } return function (k, v) { if (typeof replacer === "function") { v = replacer.call(this, k, v); } if (typeof v?.toJSON === "function" && v.toJSON.name !== "_toJSON") { v = v.toJSON(k); } return v; }; } function _space(space) { if (typeof space === "number") { var l = space < 1 ? 1 : space > 10 ? 10 : space; space = ""; for (var i = 0; i < l; i++) { space += " "; } } if (typeof space === "string") { space = space.substring(0, 10); } else { space = undefined; } return space; ; } function _lineSpace(text, level) { if (!space || !level) { return text; } return _space_(level) + text; function _space_(level) { if (!space || !level) { return ""; } return get[level] || get(); function get() { get[level] = ""; for (var i = 0; i < level; i++) { get[level] += space; } return get[level]; } } } function _newline() { if (!space) { return ""; } return "\n"; } function _metadata(value, level, key = "", cb) { if (ignoreMetadata || !value) { return cb(); } var arr = []; var i = -1; key = key + ""; key = "-metadata" + (key ? "-" + key : ""); if (value[key] && !Array.isArray(value[key])) { value[key] = [value[key]]; // remove from Ienumerable -metadata if (value.propertyIsEnumerable(key)) { Object.defineProperty(value, key, { enumerable: false }); if (value._modified.includes(key)) { const index = value._modified.indexOf(key); if (index > -1) { value._modified.splice(index, 1); } } } } write(); return; function write() { i++; if (value[key] && i < value[key].length) { if (space) { // write line return _write( _lineSpace( '/*' + value[key][i] + '*/' + _newline() , level ), write ); } // write line return _write('/*' + value[key][i] + '*/', write); } return cb(); } } function _value(value, level, cb) { var type = _typeof(value); _meta(function () { if (modifiedData) { //root if (!value?._parent) { _getValue(cb); } //selec all else if (isModified) { _setUnmodified(); _getValue(cb); } else if (value?._isModified === true) { var isMod = isModified; isModified = true; _getValue(cb); isModified = isMod; _setUnmodified(); } //selec modified else { _setUnmodified(); _getValue(cb); } } else { _getValue(cb); } }); return; function _meta(cb) { if (level === 0 || modifiedData && value?._parent && !isModified && value?._isModified === true) { return _metadata(value, level, '', cb); } return cb(); } function _setUnmodified() { if (modifiedData && setUnmodified && value?._isDataContext) { //set _isModified false if (value._isModified === true) { value._isModified = false; } //removing propertyName if (Array.isArray(value._parent?._modified)) { var index = value._parent._modified.indexOf(value._propertyName); if (index > -1) { value._parent._modified.splice(index, 1); } } } } function _getValue(cb) { if (_object(cb)) { return; } if (_string(cb)) { return; } if (_bool(cb)) { return; } if (_null(cb)) { return; } if (_number(cb)) { return; } return cb(); } function _string(cb) { if (type === "string") { _write('"' + value + '"' + '', cb); return true; } } function _number(cb) { if (type === "number") { _write(JSON.stringify(value), cb); return true; } } function _object(cb) { var startChar, isKeyVal, endChar; if (type === "object") { startChar = '{'; isKeyVal = true; endChar = "}"; } else if (type === "array") { startChar = '['; endChar = "]"; } if (startChar && endChar) { // signal of updating the entire object if (modifiedData && value._isModified) { if (value._isModified) { startChar += '\r\r'; } else { return cb(); } } var keys = modifiedData && !value._isModified && !isModified ? Object.values(value._modified).sort() : Object.keys(value); // write emty object if (!keys.length) { _write(startChar + endChar, cb); return true; } // write startChar if (space) { startChar += startChar.includes("\r\r") ? "" : _newline(); } _write(startChar, function () { _writeValues(function () { if (space) { return _write(_lineSpace(endChar, level), cb); } return _write(endChar, cb); }); }); return true; } return; function _writeValues(cb) { var isMod = isModified; isModified = value._isModified; _writeKeyValMeta(keys.shift(), _next); return; function _next() { if (keys.length) { _writeKeyValMeta(keys.shift(), _next); } else { isModified = isMod; if (modifiedData) { value._isModified = false; } _write(_newline(), cb); } } } function _writeKeyValMeta(k, cb) { var val = value[k]; if (typeof replacer === "function") { val = replacer.call( value, k, val ); } if (val?._isDataContext) { _metadata(val, level + 1, "", _writeKeyVal); } else { _metadata(value, level + 1, k, _writeKeyVal); } return; function _writeKeyVal() { // minified array and modified data if (!space && !isKeyVal && modifiedData) { _writeKey(k + ':', true); } // minified array else if (!space && !isKeyVal) { _writeKey(''); } // minified object else if (!space && isKeyVal) { _writeKey('"' + k + '":'); } // array and modified data else if (!isKeyVal && modifiedData) { _writeKey(_lineSpace(k + ': ', level + 1), true); } // array else if (!isKeyVal) { _writeKey(_lineSpace('', level + 1)); } // object else { _writeKey(_lineSpace('"' + k + '": ', level + 1)); } return; } function _writeKey(strKey, isStartTrim) { _write(strKey, function () { // write value _value(val, level + 1, function () { // write separator _write(keys.length ? ',' + _newline() : '', setUnmodified); return; function setUnmodified() { //set unmodified private val if (modifiedData && setUnmodified) { //removing propertyName var index = value._modified.indexOf(k); if (index > -1) { value._modified.splice(index, 1); } } return cb(); } }); }); return; } } } function _bool(cb) { if (type === "boolean") { _write(value + '', cb); return true; } } function _null(cb) { if (type === "null") { _write("null", cb); return true; } } } function _write(str, cb) { if (!isStream) { strJSON += str; cb(); } else if (str) { // write -> OK if (writeStream.write(str)) { cb(); } else { writeStream.once('drain', cb); } } } } //#endregion // is node.js if (typeof exports === 'object' && typeof module !== 'undefined') { var path = require('path'), fs = require('fs'), isSaveChanges = true; /** * * @param {string} filePath * @param {(event:Event)=>void} onDataChange * @param {(event:Event)=>void} onFileChange * @param {any} data Default {} * @returns Proxy * * @typedef {object} Event Event object * @property {string} strChanges * @property {string} strJson * @property {Proxy} datacontext */ function watchJsonFile(filePath, onDataChange, onFileChange, data) { filePath = resolvePath(filePath); var isInitData = Boolean(data) && !fs.existsSync(filePath), isFileWriteInProgress = false, fileWatchTimeout; if (!data) { data = createDataContext({}); } if (!data._isDataContext) { data = createDataContext(data); } fs.watchFile(filePath, (curr, prev) => { readFile(); });