UNPKG

8.22 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _walkthroughtree = require('./walkthroughtree');
8
9Object.keys(_walkthroughtree).forEach(function (key) {
10 if (key === "default" || key === "__esModule") return;
11 Object.defineProperty(exports, key, {
12 enumerable: true,
13 get: function () {
14 return _walkthroughtree[key];
15 }
16 });
17});
18
19var _fs = require('fs');
20
21var _fs2 = _interopRequireDefault(_fs);
22
23var _path = require('path');
24
25var _path2 = _interopRequireDefault(_path);
26
27var _events = require('events');
28
29function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
30
31const getOwnPropertyHashes = typeof Reflect === 'object' && Reflect.ownKeys || (Object.getOwnPropertySymbols ? function getOwnPropertyHashes(obj) {
32 return Array.prototype.concat(Object.getOwnPropertyNames(obj), Object.getOwnPropertySymbols(obj));
33} : Object.getOwnPropertyNames);
34
35const privateProps = typeof Symbol === 'function' ? Symbol('internals') : '__internals';
36
37function isObject(o) {
38 if (!o) return false;
39 var type = typeof o;
40 return type === 'object' || type === 'function';
41}
42
43function getAllProperties(obj, output = []) {
44 if (!obj) return output;
45 Array.prototype.push.apply(output, getOwnPropertyHashes(obj));
46 return getAllProperties(Object.getPrototypeOf(obj), output);
47}
48
49function fileChanged(a, b, deferred) {
50 const internals = this[privateProps],
51 { options } = internals;
52 if (options.delay) {
53 if (internals.fileChangeTimeout) {
54 clearTimeout(internals.fileChangeTimeout);
55 delete internals.fileChangeTimeout;
56 }
57 if (!deferred && !internals.updateFileLock) {
58 internals.fileChangeTimeout = setTimeout(internals.fileChanged, options.delay, a, b, true);
59 return;
60 }
61 }
62 if (internals.updateFileLock) return;
63 try {
64 if (a instanceof _fs2.default.Stats ? a.mtime === b.mtime : b !== internals.fileName) return;
65 this.forceUpdate();
66 } catch (err) {
67 console.log(err.stack || err);
68 }
69}
70
71function deepPatchLayer(srcObject, srcDescriptor, patchDescriptor, key, depth, options) {
72 // Pass the patched value if source / patch is not patchable, or already patched.
73 if (!depth || !srcDescriptor) return Object.defineProperty(srcObject, key, patchDescriptor);
74
75 const srcValue = srcDescriptor.value;
76 const patchValue = patchDescriptor.value;
77 if (srcValue === patchValue || !isObject(srcValue) || !isObject(patchValue)) return Object.defineProperty(srcObject, key, patchDescriptor);
78
79 const { ignoreProperties, keepNonExists } = options;
80
81 // Fetch all keys from patcher object.
82 const patchKeys = getOwnPropertyHashes(patchValue);
83 for (const key of patchKeys) {
84 if (ignoreProperties && ignoreProperties.includes(key)) continue;
85
86 const srcDescriptor = Object.getOwnPropertyDescriptor(srcValue, key);
87 if (srcDescriptor && !srcDescriptor.configurable) continue;
88
89 const patchDescriptor = Object.getOwnPropertyDescriptor(patchValue, key);
90 if (!srcDescriptor || !srcDescriptor.get && !patchDescriptor.get) this.next(srcValue, srcDescriptor, patchDescriptor, key, depth - 1, options);
91 }
92
93 // Take away properties that does not available in patcher (Pending to be deleted)
94 if (!keepNonExists) for (const key of getOwnPropertyHashes(srcValue)) {
95 if (patchKeys.includes(key) || ignoreProperties && ignoreProperties.includes(key)) continue;
96
97 const srcDescriptor = Object.getOwnPropertyDescriptor(srcValue, key);
98 if (!srcDescriptor.configurable) continue;
99
100 delete srcValue[key];
101 }
102
103 return srcObject;
104}
105
106class SelfReloadJSON extends _events.EventEmitter {
107 constructor(options) {
108 super();
109
110 switch (typeof options) {
111 case 'string':
112 options = { fileName: options };break;
113 case 'object':case 'undefined':
114 break;
115 default:
116 throw new Error('Invalid options type.');
117 }
118
119 // Prevent removal of internal handlers
120 this[privateProps] = null;
121
122 this[privateProps] = {
123 keys: [],
124 fileName: '',
125 watcher: null,
126 content: null,
127 fileChanged: (a, b, deferred) => fileChanged.call(this, a, b, deferred),
128 forceUpdate: () => this.forceUpdate(),
129 omitKeys: getAllProperties(this),
130 options: Object.assign({
131 fileName: '',
132 encoding: 'utf8',
133 additive: false,
134 method: 'native',
135 interval: 5000,
136 reviver: null,
137 replacer: null,
138 delay: 0,
139 depth: -1
140 }, options)
141 };
142
143 // Convert all internal values to non-enumerable,
144 // prevents those values exposed by save function.
145 for (let key in this) {
146 const value = this[key];
147 delete this[key];
148 Object.defineProperty(this, key, {
149 value,
150 enumerable: false,
151 configurable: true,
152 writable: true
153 });
154 }
155
156 this.resume();
157 }
158
159 stop() {
160 const internals = this[privateProps];
161 if (!internals.watcher) return;
162 if (typeof internals.watcher === 'string') _fs2.default.unwatchFile(internals.watcher, internals.fileChanged);else internals.watcher.close();
163 internals.watcher = null;
164 }
165
166 resume() {
167 this.stop();
168 const internals = this[privateProps],
169 { options } = internals;
170
171 options.fileName = _path2.default.resolve(options.fileName);
172 internals.fileName = _path2.default.basename(options.fileName);
173
174 switch (options.method) {
175 case 'native':
176 internals.watcher = _fs2.default.watch(options.fileName, { encoding: options.encoding }, internals.fileChanged);
177 break;
178
179 case 'polling':
180 internals.watcher = options.fileName;
181 _fs2.default.watchFile(options.fileName, { interval: options.interval }, internals.fileChanged);
182 break;
183 }
184 this.forceUpdate();
185 }
186
187 save(options) {
188 const internals = this[privateProps];
189 options = Object.assign({ space: null }, internals.options, options);
190 internals.updateFileLock = true;
191 try {
192 const json = JSON.stringify(this, options.replacer, options.space);
193 _fs2.default.writeFileSync(internals.options.fileName, json, options);
194 internals.raw = json;
195 } finally {
196 internals.updateFileLock = false;
197 }
198 }
199
200 forceUpdate() {
201 const internals = this[privateProps],
202 { options } = internals;
203 if (internals.updateFileLock) return;
204 internals.updateFileLock = true;
205
206 if (internals.retryTimer) {
207 clearTimeout(internals.retryTimer);
208 delete internals.retryTimer;
209 }
210
211 if (internals.fileChangeTimeout) {
212 clearTimeout(internals.fileChangeTimeout);
213 delete internals.fileChangeTimeout;
214 }
215
216 try {
217 const rawContent = _fs2.default.readFileSync(options.fileName, { encoding: options.encoding });
218 if (internals.raw === rawContent) return;
219 internals.raw = rawContent;
220
221 let newContent = JSON.parse(rawContent, options.reviver);
222 if (typeof newContent !== 'object') newContent = { value: newContent };
223
224 SelfReloadJSON.deepPatch(this, newContent, {
225 keepNonExists: options.additive,
226 ignoreProperties: internals.omitKeys,
227 depth: options.depth
228 });
229
230 internals.newContent = SelfReloadJSON.deepPatch(internals.newContent || {}, newContent, {
231 keepNonExists: options.additive,
232 depth: options.depth
233 });
234 } catch (err) {
235 switch (err && err.code) {
236 case 'EBUSY':
237 case 'EAGAIN':
238 internals.retryTimer = setTimeout(internals.forceUpdate, options.delay);
239 return;
240 }
241
242 this.emit('error', err);
243 return;
244 } finally {
245 internals.updateFileLock = false;
246 }
247
248 this.emit('updated', internals.newContent);
249 }
250
251 static deepPatch(source, patch, options = {}) {
252 if (!isObject(source) || !isObject(patch)) return patch;
253 const dummy = { value: source };
254 return (0, _walkthroughtree.walkThroughTree)(deepPatchLayer, {
255 firstResult: true
256 }, dummy, dummy, {
257 value: patch,
258 configurable: true,
259 enumerable: true,
260 writable: true
261 }, 'value', 'depth' in options ? options.depth : -1, options).value;
262 }
263}
264
265exports.default = SelfReloadJSON;
266module.exports = exports['default'];
\No newline at end of file