1 | /*
|
---|
2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
3 | Author Tobias Koppers @sokra
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | const nextTick = require("process").nextTick;
|
---|
9 |
|
---|
10 | /** @typedef {import("./Resolver").FileSystem} FileSystem */
|
---|
11 | /** @typedef {import("./Resolver").PathLike} PathLike */
|
---|
12 | /** @typedef {import("./Resolver").PathOrFileDescriptor} PathOrFileDescriptor */
|
---|
13 | /** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
|
---|
14 | /** @typedef {FileSystem & SyncFileSystem} BaseFileSystem */
|
---|
15 |
|
---|
16 | /**
|
---|
17 | * @template T
|
---|
18 | * @typedef {import("./Resolver").FileSystemCallback<T>} FileSystemCallback<T>
|
---|
19 | */
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * @param {string} path path
|
---|
23 | * @returns {string} dirname
|
---|
24 | */
|
---|
25 | const dirname = path => {
|
---|
26 | let idx = path.length - 1;
|
---|
27 | while (idx >= 0) {
|
---|
28 | const c = path.charCodeAt(idx);
|
---|
29 | // slash or backslash
|
---|
30 | if (c === 47 || c === 92) break;
|
---|
31 | idx--;
|
---|
32 | }
|
---|
33 | if (idx < 0) return "";
|
---|
34 | return path.slice(0, idx);
|
---|
35 | };
|
---|
36 |
|
---|
37 | /**
|
---|
38 | * @template T
|
---|
39 | * @param {FileSystemCallback<T>[]} callbacks callbacks
|
---|
40 | * @param {Error | null} err error
|
---|
41 | * @param {T} result result
|
---|
42 | */
|
---|
43 | const runCallbacks = (callbacks, err, result) => {
|
---|
44 | if (callbacks.length === 1) {
|
---|
45 | callbacks[0](err, result);
|
---|
46 | callbacks.length = 0;
|
---|
47 | return;
|
---|
48 | }
|
---|
49 | let error;
|
---|
50 | for (const callback of callbacks) {
|
---|
51 | try {
|
---|
52 | callback(err, result);
|
---|
53 | } catch (e) {
|
---|
54 | if (!error) error = e;
|
---|
55 | }
|
---|
56 | }
|
---|
57 | callbacks.length = 0;
|
---|
58 | if (error) throw error;
|
---|
59 | };
|
---|
60 |
|
---|
61 | class OperationMergerBackend {
|
---|
62 | /**
|
---|
63 | * @param {Function | undefined} provider async method in filesystem
|
---|
64 | * @param {Function | undefined} syncProvider sync method in filesystem
|
---|
65 | * @param {BaseFileSystem} providerContext call context for the provider methods
|
---|
66 | */
|
---|
67 | constructor(provider, syncProvider, providerContext) {
|
---|
68 | this._provider = provider;
|
---|
69 | this._syncProvider = syncProvider;
|
---|
70 | this._providerContext = providerContext;
|
---|
71 | this._activeAsyncOperations = new Map();
|
---|
72 |
|
---|
73 | this.provide = this._provider
|
---|
74 | ? /**
|
---|
75 | * @param {PathLike | PathOrFileDescriptor} path path
|
---|
76 | * @param {object | FileSystemCallback<any> | undefined} options options
|
---|
77 | * @param {FileSystemCallback<any>=} callback callback
|
---|
78 | * @returns {any} result
|
---|
79 | */
|
---|
80 | (path, options, callback) => {
|
---|
81 | if (typeof options === "function") {
|
---|
82 | callback = /** @type {FileSystemCallback<any>} */ (options);
|
---|
83 | options = undefined;
|
---|
84 | }
|
---|
85 | if (
|
---|
86 | typeof path !== "string" &&
|
---|
87 | !Buffer.isBuffer(path) &&
|
---|
88 | !(path instanceof URL) &&
|
---|
89 | typeof path !== "number"
|
---|
90 | ) {
|
---|
91 | /** @type {Function} */
|
---|
92 | (callback)(
|
---|
93 | new TypeError("path must be a string, Buffer, URL or number")
|
---|
94 | );
|
---|
95 | return;
|
---|
96 | }
|
---|
97 | if (options) {
|
---|
98 | return /** @type {Function} */ (this._provider).call(
|
---|
99 | this._providerContext,
|
---|
100 | path,
|
---|
101 | options,
|
---|
102 | callback
|
---|
103 | );
|
---|
104 | }
|
---|
105 | let callbacks = this._activeAsyncOperations.get(path);
|
---|
106 | if (callbacks) {
|
---|
107 | callbacks.push(callback);
|
---|
108 | return;
|
---|
109 | }
|
---|
110 | this._activeAsyncOperations.set(path, (callbacks = [callback]));
|
---|
111 | /** @type {Function} */
|
---|
112 | (provider)(
|
---|
113 | path,
|
---|
114 | /**
|
---|
115 | * @param {Error} err error
|
---|
116 | * @param {any} result result
|
---|
117 | */
|
---|
118 | (err, result) => {
|
---|
119 | this._activeAsyncOperations.delete(path);
|
---|
120 | runCallbacks(callbacks, err, result);
|
---|
121 | }
|
---|
122 | );
|
---|
123 | }
|
---|
124 | : null;
|
---|
125 | this.provideSync = this._syncProvider
|
---|
126 | ? /**
|
---|
127 | * @param {PathLike | PathOrFileDescriptor} path path
|
---|
128 | * @param {object=} options options
|
---|
129 | * @returns {any} result
|
---|
130 | */
|
---|
131 | (path, options) => {
|
---|
132 | return /** @type {Function} */ (this._syncProvider).call(
|
---|
133 | this._providerContext,
|
---|
134 | path,
|
---|
135 | options
|
---|
136 | );
|
---|
137 | }
|
---|
138 | : null;
|
---|
139 | }
|
---|
140 |
|
---|
141 | purge() {}
|
---|
142 | purgeParent() {}
|
---|
143 | }
|
---|
144 |
|
---|
145 | /*
|
---|
146 |
|
---|
147 | IDLE:
|
---|
148 | insert data: goto SYNC
|
---|
149 |
|
---|
150 | SYNC:
|
---|
151 | before provide: run ticks
|
---|
152 | event loop tick: goto ASYNC_ACTIVE
|
---|
153 |
|
---|
154 | ASYNC:
|
---|
155 | timeout: run tick, goto ASYNC_PASSIVE
|
---|
156 |
|
---|
157 | ASYNC_PASSIVE:
|
---|
158 | before provide: run ticks
|
---|
159 |
|
---|
160 | IDLE --[insert data]--> SYNC --[event loop tick]--> ASYNC_ACTIVE --[interval tick]-> ASYNC_PASSIVE
|
---|
161 | ^ |
|
---|
162 | +---------[insert data]-------+
|
---|
163 | */
|
---|
164 |
|
---|
165 | const STORAGE_MODE_IDLE = 0;
|
---|
166 | const STORAGE_MODE_SYNC = 1;
|
---|
167 | const STORAGE_MODE_ASYNC = 2;
|
---|
168 |
|
---|
169 | /**
|
---|
170 | * @callback Provide
|
---|
171 | * @param {PathLike | PathOrFileDescriptor} path path
|
---|
172 | * @param {any} options options
|
---|
173 | * @param {FileSystemCallback<any>} callback callback
|
---|
174 | * @returns {void}
|
---|
175 | */
|
---|
176 |
|
---|
177 | class CacheBackend {
|
---|
178 | /**
|
---|
179 | * @param {number} duration max cache duration of items
|
---|
180 | * @param {function | undefined} provider async method
|
---|
181 | * @param {function | undefined} syncProvider sync method
|
---|
182 | * @param {BaseFileSystem} providerContext call context for the provider methods
|
---|
183 | */
|
---|
184 | constructor(duration, provider, syncProvider, providerContext) {
|
---|
185 | this._duration = duration;
|
---|
186 | this._provider = provider;
|
---|
187 | this._syncProvider = syncProvider;
|
---|
188 | this._providerContext = providerContext;
|
---|
189 | /** @type {Map<string, FileSystemCallback<any>[]>} */
|
---|
190 | this._activeAsyncOperations = new Map();
|
---|
191 | /** @type {Map<string, { err: Error | null, result?: any, level: Set<string> }>} */
|
---|
192 | this._data = new Map();
|
---|
193 | /** @type {Set<string>[]} */
|
---|
194 | this._levels = [];
|
---|
195 | for (let i = 0; i < 10; i++) this._levels.push(new Set());
|
---|
196 | for (let i = 5000; i < duration; i += 500) this._levels.push(new Set());
|
---|
197 | this._currentLevel = 0;
|
---|
198 | this._tickInterval = Math.floor(duration / this._levels.length);
|
---|
199 | /** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC} */
|
---|
200 | this._mode = STORAGE_MODE_IDLE;
|
---|
201 |
|
---|
202 | /** @type {NodeJS.Timeout | undefined} */
|
---|
203 | this._timeout = undefined;
|
---|
204 | /** @type {number | undefined} */
|
---|
205 | this._nextDecay = undefined;
|
---|
206 |
|
---|
207 | // @ts-ignore
|
---|
208 | this.provide = provider ? this.provide.bind(this) : null;
|
---|
209 | // @ts-ignore
|
---|
210 | this.provideSync = syncProvider ? this.provideSync.bind(this) : null;
|
---|
211 | }
|
---|
212 |
|
---|
213 | /**
|
---|
214 | * @param {PathLike | PathOrFileDescriptor} path path
|
---|
215 | * @param {any} options options
|
---|
216 | * @param {FileSystemCallback<any>} callback callback
|
---|
217 | * @returns {void}
|
---|
218 | */
|
---|
219 | provide(path, options, callback) {
|
---|
220 | if (typeof options === "function") {
|
---|
221 | callback = options;
|
---|
222 | options = undefined;
|
---|
223 | }
|
---|
224 | if (
|
---|
225 | typeof path !== "string" &&
|
---|
226 | !Buffer.isBuffer(path) &&
|
---|
227 | !(path instanceof URL) &&
|
---|
228 | typeof path !== "number"
|
---|
229 | ) {
|
---|
230 | callback(new TypeError("path must be a string, Buffer, URL or number"));
|
---|
231 | return;
|
---|
232 | }
|
---|
233 | const strPath = typeof path !== "string" ? path.toString() : path;
|
---|
234 | if (options) {
|
---|
235 | return /** @type {Function} */ (this._provider).call(
|
---|
236 | this._providerContext,
|
---|
237 | path,
|
---|
238 | options,
|
---|
239 | callback
|
---|
240 | );
|
---|
241 | }
|
---|
242 |
|
---|
243 | // When in sync mode we can move to async mode
|
---|
244 | if (this._mode === STORAGE_MODE_SYNC) {
|
---|
245 | this._enterAsyncMode();
|
---|
246 | }
|
---|
247 |
|
---|
248 | // Check in cache
|
---|
249 | let cacheEntry = this._data.get(strPath);
|
---|
250 | if (cacheEntry !== undefined) {
|
---|
251 | if (cacheEntry.err) return nextTick(callback, cacheEntry.err);
|
---|
252 | return nextTick(callback, null, cacheEntry.result);
|
---|
253 | }
|
---|
254 |
|
---|
255 | // Check if there is already the same operation running
|
---|
256 | let callbacks = this._activeAsyncOperations.get(strPath);
|
---|
257 | if (callbacks !== undefined) {
|
---|
258 | callbacks.push(callback);
|
---|
259 | return;
|
---|
260 | }
|
---|
261 | this._activeAsyncOperations.set(strPath, (callbacks = [callback]));
|
---|
262 |
|
---|
263 | // Run the operation
|
---|
264 | /** @type {Function} */
|
---|
265 | (this._provider).call(
|
---|
266 | this._providerContext,
|
---|
267 | path,
|
---|
268 | /**
|
---|
269 | * @param {Error | null} err error
|
---|
270 | * @param {any} [result] result
|
---|
271 | */
|
---|
272 | (err, result) => {
|
---|
273 | this._activeAsyncOperations.delete(strPath);
|
---|
274 | this._storeResult(strPath, err, result);
|
---|
275 |
|
---|
276 | // Enter async mode if not yet done
|
---|
277 | this._enterAsyncMode();
|
---|
278 |
|
---|
279 | runCallbacks(
|
---|
280 | /** @type {FileSystemCallback<any>[]} */ (callbacks),
|
---|
281 | err,
|
---|
282 | result
|
---|
283 | );
|
---|
284 | }
|
---|
285 | );
|
---|
286 | }
|
---|
287 |
|
---|
288 | /**
|
---|
289 | * @param {PathLike | PathOrFileDescriptor} path path
|
---|
290 | * @param {any} options options
|
---|
291 | * @returns {any} result
|
---|
292 | */
|
---|
293 | provideSync(path, options) {
|
---|
294 | if (
|
---|
295 | typeof path !== "string" &&
|
---|
296 | !Buffer.isBuffer(path) &&
|
---|
297 | !(path instanceof URL) &&
|
---|
298 | typeof path !== "number"
|
---|
299 | ) {
|
---|
300 | throw new TypeError("path must be a string");
|
---|
301 | }
|
---|
302 | const strPath = typeof path !== "string" ? path.toString() : path;
|
---|
303 | if (options) {
|
---|
304 | return /** @type {Function} */ (this._syncProvider).call(
|
---|
305 | this._providerContext,
|
---|
306 | path,
|
---|
307 | options
|
---|
308 | );
|
---|
309 | }
|
---|
310 |
|
---|
311 | // In sync mode we may have to decay some cache items
|
---|
312 | if (this._mode === STORAGE_MODE_SYNC) {
|
---|
313 | this._runDecays();
|
---|
314 | }
|
---|
315 |
|
---|
316 | // Check in cache
|
---|
317 | let cacheEntry = this._data.get(strPath);
|
---|
318 | if (cacheEntry !== undefined) {
|
---|
319 | if (cacheEntry.err) throw cacheEntry.err;
|
---|
320 | return cacheEntry.result;
|
---|
321 | }
|
---|
322 |
|
---|
323 | // Get all active async operations
|
---|
324 | // This sync operation will also complete them
|
---|
325 | const callbacks = this._activeAsyncOperations.get(strPath);
|
---|
326 | this._activeAsyncOperations.delete(strPath);
|
---|
327 |
|
---|
328 | // Run the operation
|
---|
329 | // When in idle mode, we will enter sync mode
|
---|
330 | let result;
|
---|
331 | try {
|
---|
332 | result = /** @type {Function} */ (this._syncProvider).call(
|
---|
333 | this._providerContext,
|
---|
334 | path
|
---|
335 | );
|
---|
336 | } catch (err) {
|
---|
337 | this._storeResult(strPath, /** @type {Error} */ (err), undefined);
|
---|
338 | this._enterSyncModeWhenIdle();
|
---|
339 | if (callbacks) {
|
---|
340 | runCallbacks(callbacks, /** @type {Error} */ (err), undefined);
|
---|
341 | }
|
---|
342 | throw err;
|
---|
343 | }
|
---|
344 | this._storeResult(strPath, null, result);
|
---|
345 | this._enterSyncModeWhenIdle();
|
---|
346 | if (callbacks) {
|
---|
347 | runCallbacks(callbacks, null, result);
|
---|
348 | }
|
---|
349 | return result;
|
---|
350 | }
|
---|
351 |
|
---|
352 | /**
|
---|
353 | * @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
|
---|
354 | */
|
---|
355 | purge(what) {
|
---|
356 | if (!what) {
|
---|
357 | if (this._mode !== STORAGE_MODE_IDLE) {
|
---|
358 | this._data.clear();
|
---|
359 | for (const level of this._levels) {
|
---|
360 | level.clear();
|
---|
361 | }
|
---|
362 | this._enterIdleMode();
|
---|
363 | }
|
---|
364 | } else if (
|
---|
365 | typeof what === "string" ||
|
---|
366 | Buffer.isBuffer(what) ||
|
---|
367 | what instanceof URL ||
|
---|
368 | typeof what === "number"
|
---|
369 | ) {
|
---|
370 | const strWhat = typeof what !== "string" ? what.toString() : what;
|
---|
371 | for (let [key, data] of this._data) {
|
---|
372 | if (key.startsWith(strWhat)) {
|
---|
373 | this._data.delete(key);
|
---|
374 | data.level.delete(key);
|
---|
375 | }
|
---|
376 | }
|
---|
377 | if (this._data.size === 0) {
|
---|
378 | this._enterIdleMode();
|
---|
379 | }
|
---|
380 | } else {
|
---|
381 | for (let [key, data] of this._data) {
|
---|
382 | for (const item of what) {
|
---|
383 | const strItem = typeof item !== "string" ? item.toString() : item;
|
---|
384 | if (key.startsWith(strItem)) {
|
---|
385 | this._data.delete(key);
|
---|
386 | data.level.delete(key);
|
---|
387 | break;
|
---|
388 | }
|
---|
389 | }
|
---|
390 | }
|
---|
391 | if (this._data.size === 0) {
|
---|
392 | this._enterIdleMode();
|
---|
393 | }
|
---|
394 | }
|
---|
395 | }
|
---|
396 |
|
---|
397 | /**
|
---|
398 | * @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
|
---|
399 | */
|
---|
400 | purgeParent(what) {
|
---|
401 | if (!what) {
|
---|
402 | this.purge();
|
---|
403 | } else if (
|
---|
404 | typeof what === "string" ||
|
---|
405 | Buffer.isBuffer(what) ||
|
---|
406 | what instanceof URL ||
|
---|
407 | typeof what === "number"
|
---|
408 | ) {
|
---|
409 | const strWhat = typeof what !== "string" ? what.toString() : what;
|
---|
410 | this.purge(dirname(strWhat));
|
---|
411 | } else {
|
---|
412 | const set = new Set();
|
---|
413 | for (const item of what) {
|
---|
414 | const strItem = typeof item !== "string" ? item.toString() : item;
|
---|
415 | set.add(dirname(strItem));
|
---|
416 | }
|
---|
417 | this.purge(set);
|
---|
418 | }
|
---|
419 | }
|
---|
420 |
|
---|
421 | /**
|
---|
422 | * @param {string} path path
|
---|
423 | * @param {Error | null} err error
|
---|
424 | * @param {any} result result
|
---|
425 | */
|
---|
426 | _storeResult(path, err, result) {
|
---|
427 | if (this._data.has(path)) return;
|
---|
428 | const level = this._levels[this._currentLevel];
|
---|
429 | this._data.set(path, { err, result, level });
|
---|
430 | level.add(path);
|
---|
431 | }
|
---|
432 |
|
---|
433 | _decayLevel() {
|
---|
434 | const nextLevel = (this._currentLevel + 1) % this._levels.length;
|
---|
435 | const decay = this._levels[nextLevel];
|
---|
436 | this._currentLevel = nextLevel;
|
---|
437 | for (let item of decay) {
|
---|
438 | this._data.delete(item);
|
---|
439 | }
|
---|
440 | decay.clear();
|
---|
441 | if (this._data.size === 0) {
|
---|
442 | this._enterIdleMode();
|
---|
443 | } else {
|
---|
444 | /** @type {number} */
|
---|
445 | (this._nextDecay) += this._tickInterval;
|
---|
446 | }
|
---|
447 | }
|
---|
448 |
|
---|
449 | _runDecays() {
|
---|
450 | while (
|
---|
451 | /** @type {number} */ (this._nextDecay) <= Date.now() &&
|
---|
452 | this._mode !== STORAGE_MODE_IDLE
|
---|
453 | ) {
|
---|
454 | this._decayLevel();
|
---|
455 | }
|
---|
456 | }
|
---|
457 |
|
---|
458 | _enterAsyncMode() {
|
---|
459 | let timeout = 0;
|
---|
460 | switch (this._mode) {
|
---|
461 | case STORAGE_MODE_ASYNC:
|
---|
462 | return;
|
---|
463 | case STORAGE_MODE_IDLE:
|
---|
464 | this._nextDecay = Date.now() + this._tickInterval;
|
---|
465 | timeout = this._tickInterval;
|
---|
466 | break;
|
---|
467 | case STORAGE_MODE_SYNC:
|
---|
468 | this._runDecays();
|
---|
469 | // _runDecays may change the mode
|
---|
470 | if (
|
---|
471 | /** @type {STORAGE_MODE_IDLE | STORAGE_MODE_SYNC | STORAGE_MODE_ASYNC}*/
|
---|
472 | (this._mode) === STORAGE_MODE_IDLE
|
---|
473 | )
|
---|
474 | return;
|
---|
475 | timeout = Math.max(
|
---|
476 | 0,
|
---|
477 | /** @type {number} */ (this._nextDecay) - Date.now()
|
---|
478 | );
|
---|
479 | break;
|
---|
480 | }
|
---|
481 | this._mode = STORAGE_MODE_ASYNC;
|
---|
482 | const ref = setTimeout(() => {
|
---|
483 | this._mode = STORAGE_MODE_SYNC;
|
---|
484 | this._runDecays();
|
---|
485 | }, timeout);
|
---|
486 | if (ref.unref) ref.unref();
|
---|
487 | this._timeout = ref;
|
---|
488 | }
|
---|
489 |
|
---|
490 | _enterSyncModeWhenIdle() {
|
---|
491 | if (this._mode === STORAGE_MODE_IDLE) {
|
---|
492 | this._mode = STORAGE_MODE_SYNC;
|
---|
493 | this._nextDecay = Date.now() + this._tickInterval;
|
---|
494 | }
|
---|
495 | }
|
---|
496 |
|
---|
497 | _enterIdleMode() {
|
---|
498 | this._mode = STORAGE_MODE_IDLE;
|
---|
499 | this._nextDecay = undefined;
|
---|
500 | if (this._timeout) clearTimeout(this._timeout);
|
---|
501 | }
|
---|
502 | }
|
---|
503 |
|
---|
504 | /**
|
---|
505 | * @template {function} Provider
|
---|
506 | * @template {function} AsyncProvider
|
---|
507 | * @template FileSystem
|
---|
508 | * @param {number} duration duration in ms files are cached
|
---|
509 | * @param {Provider | undefined} provider provider
|
---|
510 | * @param {AsyncProvider | undefined} syncProvider sync provider
|
---|
511 | * @param {BaseFileSystem} providerContext provider context
|
---|
512 | * @returns {OperationMergerBackend | CacheBackend} backend
|
---|
513 | */
|
---|
514 | const createBackend = (duration, provider, syncProvider, providerContext) => {
|
---|
515 | if (duration > 0) {
|
---|
516 | return new CacheBackend(duration, provider, syncProvider, providerContext);
|
---|
517 | }
|
---|
518 | return new OperationMergerBackend(provider, syncProvider, providerContext);
|
---|
519 | };
|
---|
520 |
|
---|
521 | module.exports = class CachedInputFileSystem {
|
---|
522 | /**
|
---|
523 | * @param {BaseFileSystem} fileSystem file system
|
---|
524 | * @param {number} duration duration in ms files are cached
|
---|
525 | */
|
---|
526 | constructor(fileSystem, duration) {
|
---|
527 | this.fileSystem = fileSystem;
|
---|
528 |
|
---|
529 | this._lstatBackend = createBackend(
|
---|
530 | duration,
|
---|
531 | this.fileSystem.lstat,
|
---|
532 | this.fileSystem.lstatSync,
|
---|
533 | this.fileSystem
|
---|
534 | );
|
---|
535 | const lstat = this._lstatBackend.provide;
|
---|
536 | this.lstat = /** @type {FileSystem["lstat"]} */ (lstat);
|
---|
537 | const lstatSync = this._lstatBackend.provideSync;
|
---|
538 | this.lstatSync = /** @type {SyncFileSystem["lstatSync"]} */ (lstatSync);
|
---|
539 |
|
---|
540 | this._statBackend = createBackend(
|
---|
541 | duration,
|
---|
542 | this.fileSystem.stat,
|
---|
543 | this.fileSystem.statSync,
|
---|
544 | this.fileSystem
|
---|
545 | );
|
---|
546 | const stat = this._statBackend.provide;
|
---|
547 | this.stat = /** @type {FileSystem["stat"]} */ (stat);
|
---|
548 | const statSync = this._statBackend.provideSync;
|
---|
549 | this.statSync = /** @type {SyncFileSystem["statSync"]} */ (statSync);
|
---|
550 |
|
---|
551 | this._readdirBackend = createBackend(
|
---|
552 | duration,
|
---|
553 | this.fileSystem.readdir,
|
---|
554 | this.fileSystem.readdirSync,
|
---|
555 | this.fileSystem
|
---|
556 | );
|
---|
557 | const readdir = this._readdirBackend.provide;
|
---|
558 | this.readdir = /** @type {FileSystem["readdir"]} */ (readdir);
|
---|
559 | const readdirSync = this._readdirBackend.provideSync;
|
---|
560 | this.readdirSync = /** @type {SyncFileSystem["readdirSync"]} */ (
|
---|
561 | readdirSync
|
---|
562 | );
|
---|
563 |
|
---|
564 | this._readFileBackend = createBackend(
|
---|
565 | duration,
|
---|
566 | this.fileSystem.readFile,
|
---|
567 | this.fileSystem.readFileSync,
|
---|
568 | this.fileSystem
|
---|
569 | );
|
---|
570 | const readFile = this._readFileBackend.provide;
|
---|
571 | this.readFile = /** @type {FileSystem["readFile"]} */ (readFile);
|
---|
572 | const readFileSync = this._readFileBackend.provideSync;
|
---|
573 | this.readFileSync = /** @type {SyncFileSystem["readFileSync"]} */ (
|
---|
574 | readFileSync
|
---|
575 | );
|
---|
576 |
|
---|
577 | this._readJsonBackend = createBackend(
|
---|
578 | duration,
|
---|
579 | // prettier-ignore
|
---|
580 | this.fileSystem.readJson ||
|
---|
581 | (this.readFile &&
|
---|
582 | (
|
---|
583 | /**
|
---|
584 | * @param {string} path path
|
---|
585 | * @param {FileSystemCallback<any>} callback
|
---|
586 | */
|
---|
587 | (path, callback) => {
|
---|
588 | this.readFile(path, (err, buffer) => {
|
---|
589 | if (err) return callback(err);
|
---|
590 | if (!buffer || buffer.length === 0)
|
---|
591 | return callback(new Error("No file content"));
|
---|
592 | let data;
|
---|
593 | try {
|
---|
594 | data = JSON.parse(buffer.toString("utf-8"));
|
---|
595 | } catch (e) {
|
---|
596 | return callback(/** @type {Error} */ (e));
|
---|
597 | }
|
---|
598 | callback(null, data);
|
---|
599 | });
|
---|
600 | })
|
---|
601 | ),
|
---|
602 | // prettier-ignore
|
---|
603 | this.fileSystem.readJsonSync ||
|
---|
604 | (this.readFileSync &&
|
---|
605 | (
|
---|
606 | /**
|
---|
607 | * @param {string} path path
|
---|
608 | * @returns {any} result
|
---|
609 | */
|
---|
610 | (path) => {
|
---|
611 | const buffer = this.readFileSync(path);
|
---|
612 | const data = JSON.parse(buffer.toString("utf-8"));
|
---|
613 | return data;
|
---|
614 | }
|
---|
615 | )),
|
---|
616 | this.fileSystem
|
---|
617 | );
|
---|
618 | const readJson = this._readJsonBackend.provide;
|
---|
619 | this.readJson = /** @type {FileSystem["readJson"]} */ (readJson);
|
---|
620 | const readJsonSync = this._readJsonBackend.provideSync;
|
---|
621 | this.readJsonSync = /** @type {SyncFileSystem["readJsonSync"]} */ (
|
---|
622 | readJsonSync
|
---|
623 | );
|
---|
624 |
|
---|
625 | this._readlinkBackend = createBackend(
|
---|
626 | duration,
|
---|
627 | this.fileSystem.readlink,
|
---|
628 | this.fileSystem.readlinkSync,
|
---|
629 | this.fileSystem
|
---|
630 | );
|
---|
631 | const readlink = this._readlinkBackend.provide;
|
---|
632 | this.readlink = /** @type {FileSystem["readlink"]} */ (readlink);
|
---|
633 | const readlinkSync = this._readlinkBackend.provideSync;
|
---|
634 | this.readlinkSync = /** @type {SyncFileSystem["readlinkSync"]} */ (
|
---|
635 | readlinkSync
|
---|
636 | );
|
---|
637 |
|
---|
638 | this._realpathBackend = createBackend(
|
---|
639 | duration,
|
---|
640 | this.fileSystem.realpath,
|
---|
641 | this.fileSystem.realpathSync,
|
---|
642 | this.fileSystem
|
---|
643 | );
|
---|
644 | const realpath = this._realpathBackend.provide;
|
---|
645 | this.realpath = /** @type {FileSystem["realpath"]} */ (realpath);
|
---|
646 | const realpathSync = this._realpathBackend.provideSync;
|
---|
647 | this.realpathSync = /** @type {SyncFileSystem["realpathSync"]} */ (
|
---|
648 | realpathSync
|
---|
649 | );
|
---|
650 | }
|
---|
651 |
|
---|
652 | /**
|
---|
653 | * @param {string | Buffer | URL | number | (string | URL | Buffer | number)[] | Set<string | URL | Buffer | number>} [what] what to purge
|
---|
654 | */
|
---|
655 | purge(what) {
|
---|
656 | this._statBackend.purge(what);
|
---|
657 | this._lstatBackend.purge(what);
|
---|
658 | this._readdirBackend.purgeParent(what);
|
---|
659 | this._readFileBackend.purge(what);
|
---|
660 | this._readlinkBackend.purge(what);
|
---|
661 | this._readJsonBackend.purge(what);
|
---|
662 | this._realpathBackend.purge(what);
|
---|
663 | }
|
---|
664 | };
|
---|